1/*
2 * Copyright (C) 2013 Google Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
6 * met:
7 *
8 *     * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 *     * Redistributions in binary form must reproduce the above
11 * copyright notice, this list of conditions and the following disclaimer
12 * in the documentation and/or other materials provided with the
13 * distribution.
14 *     * Neither the name of Google Inc. nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31#include "config.h"
32#include "modules/websockets/WebSocketPerMessageDeflate.h"
33
34#include "wtf/text/StringHash.h"
35
36#include <algorithm>
37#include <gtest/gtest.h>
38#include <iterator>
39
40namespace blink {
41namespace {
42
43TEST(WebSocketPerMessageDeflateTest, TestDeflateHelloTakeOver)
44{
45    WebSocketPerMessageDeflate c;
46    c.enable(8, WebSocketDeflater::TakeOverContext);
47    WebSocketFrame::OpCode opcode = WebSocketFrame::OpCodeText;
48    WebSocketFrame f1(opcode, "Hello", 5, WebSocketFrame::Final);
49    WebSocketFrame f2(opcode, "Hello", 5, WebSocketFrame::Final);
50
51    ASSERT_TRUE(c.deflate(f1));
52    EXPECT_EQ(7u, f1.payloadLength);
53    EXPECT_EQ(0, memcmp("\xf2\x48\xcd\xc9\xc9\x07\x00", f1.payload, f1.payloadLength));
54    EXPECT_TRUE(f1.compress);
55    EXPECT_TRUE(f1.final);
56
57    c.resetDeflateBuffer();
58    ASSERT_TRUE(c.deflate(f2));
59    EXPECT_EQ(5u, f2.payloadLength);
60    EXPECT_EQ(0, memcmp("\xf2\x00\x11\x00\x00", f2.payload, f2.payloadLength));
61    EXPECT_TRUE(f2.compress);
62    EXPECT_TRUE(f2.final);
63}
64
65TEST(WebSocketPerMessageTest, TestDeflateHelloNoTakeOver)
66{
67    WebSocketPerMessageDeflate c;
68    c.enable(8, WebSocketDeflater::DoNotTakeOverContext);
69    WebSocketFrame::OpCode opcode = WebSocketFrame::OpCodeText;
70    WebSocketFrame f1(opcode, "Hello", 5, WebSocketFrame::Final);
71    WebSocketFrame f2(opcode, "Hello", 5, WebSocketFrame::Final);
72
73    ASSERT_TRUE(c.deflate(f1));
74    EXPECT_EQ(7u, f1.payloadLength);
75    EXPECT_EQ(0, memcmp("\xf2\x48\xcd\xc9\xc9\x07\x00", f1.payload, f1.payloadLength));
76    EXPECT_TRUE(f1.compress);
77    EXPECT_TRUE(f1.final);
78
79    c.resetDeflateBuffer();
80    ASSERT_TRUE(c.deflate(f2));
81    EXPECT_EQ(7u, f2.payloadLength);
82    EXPECT_EQ(0, memcmp("\xf2\x48\xcd\xc9\xc9\x07\x00", f2.payload, f2.payloadLength));
83    EXPECT_TRUE(f2.compress);
84    EXPECT_TRUE(f2.final);
85}
86
87TEST(WebSocketPerMessageDeflateTest, TestDeflateInflateMultipleFrame)
88{
89    WebSocketPerMessageDeflate c;
90    WebSocketFrame::OpCode text = WebSocketFrame::OpCodeText;
91    c.enable(8, WebSocketDeflater::DoNotTakeOverContext);
92    size_t length = 64 * 1024;
93    std::string payload;
94    std::string expected;
95    std::string actual;
96    std::string inflated;
97    // Generate string by a linear congruential generator.
98    uint64_t r = 0;
99    for (size_t i = 0; i < length; ++i) {
100        payload += 'a' + (r % 25);
101        r = (r * 12345 + 1103515245) % (static_cast<uint64_t>(1) << 31);
102    }
103
104    WebSocketFrame frame(text, &payload[0], payload.size(), WebSocketFrame::Final);
105    ASSERT_TRUE(c.deflate(frame));
106    ASSERT_TRUE(frame.final);
107    ASSERT_TRUE(frame.compress);
108    expected = std::string(frame.payload, frame.payloadLength);
109    for (size_t i = 0; i < length; ++i) {
110        WebSocketFrame::OpCode opcode = !i ? text : WebSocketFrame::OpCodeContinuation;
111        c.resetDeflateBuffer();
112        WebSocketFrame frame(opcode, &payload[i], 1);
113        frame.final = (i == length - 1);
114
115        ASSERT_TRUE(c.deflate(frame));
116        ASSERT_EQ(i == length - 1, frame.final);
117        ASSERT_EQ(!i, frame.compress);
118        actual += std::string(frame.payload, frame.payloadLength);
119    }
120    EXPECT_EQ(expected, actual);
121
122    for (size_t i = 0; i < actual.size(); ++i) {
123        WebSocketFrame::OpCode opcode = !i ? text : WebSocketFrame::OpCodeContinuation;
124        c.resetInflateBuffer();
125        WebSocketFrame frame(opcode, &actual[i], 1);
126        frame.final = (i == actual.size() - 1);
127        frame.compress = !i;
128
129        ASSERT_TRUE(c.inflate(frame));
130        ASSERT_EQ(i == actual.size() - 1, frame.final);
131        ASSERT_FALSE(frame.compress);
132        inflated += std::string(frame.payload, frame.payloadLength);
133    }
134    EXPECT_EQ(payload, inflated);
135}
136
137TEST(WebSocketPerMessageDeflateTest, TestDeflateBinary)
138{
139    WebSocketPerMessageDeflate c;
140    c.enable(8, WebSocketDeflater::TakeOverContext);
141    WebSocketFrame::OpCode opcode = WebSocketFrame::OpCodeBinary;
142    WebSocketFrame f1(opcode, "Hello", 5, WebSocketFrame::Final);
143
144    ASSERT_TRUE(c.deflate(f1));
145    EXPECT_EQ(7u, f1.payloadLength);
146    EXPECT_EQ(0, memcmp("\xf2\x48\xcd\xc9\xc9\x07\x00", f1.payload, f1.payloadLength));
147    EXPECT_EQ(opcode, f1.opCode);
148    EXPECT_TRUE(f1.compress);
149    EXPECT_TRUE(f1.final);
150}
151
152TEST(WebSocketPerMessageDeflateTest, TestDeflateEmptyFrame)
153{
154    WebSocketPerMessageDeflate c;
155    c.enable(8, WebSocketDeflater::TakeOverContext);
156    WebSocketFrame f1(WebSocketFrame::OpCodeText, "Hello", 5);
157    WebSocketFrame f2(WebSocketFrame::OpCodeContinuation, "", 0, WebSocketFrame::Final);
158
159    ASSERT_TRUE(c.deflate(f1));
160    EXPECT_EQ(0u, f1.payloadLength);
161    EXPECT_FALSE(f1.final);
162    EXPECT_TRUE(f1.compress);
163
164    c.resetDeflateBuffer();
165    ASSERT_TRUE(c.deflate(f2));
166    EXPECT_EQ(7u, f2.payloadLength);
167    EXPECT_EQ(0, memcmp("\xf2\x48\xcd\xc9\xc9\x07\x00", f2.payload, f2.payloadLength));
168    EXPECT_TRUE(f2.final);
169    EXPECT_FALSE(f2.compress);
170}
171
172TEST(WebSocketPerMessageDeflateTest, TestDeflateEmptyMessages)
173{
174    WebSocketPerMessageDeflate c;
175    c.enable(8, WebSocketDeflater::TakeOverContext);
176    WebSocketFrame f1(WebSocketFrame::OpCodeText, "", 0);
177    WebSocketFrame f2(WebSocketFrame::OpCodeContinuation, "", 0, WebSocketFrame::Final);
178    WebSocketFrame f3(WebSocketFrame::OpCodeText, "", 0, WebSocketFrame::Final);
179    WebSocketFrame f4(WebSocketFrame::OpCodeText, "", 0, WebSocketFrame::Final);
180    WebSocketFrame f5(WebSocketFrame::OpCodeText, "Hello", 5, WebSocketFrame::Final);
181
182    ASSERT_TRUE(c.deflate(f1));
183    EXPECT_EQ(0u, f1.payloadLength);
184    EXPECT_FALSE(f1.final);
185    EXPECT_TRUE(f1.compress);
186
187    c.resetDeflateBuffer();
188    ASSERT_TRUE(c.deflate(f2));
189    EXPECT_EQ(1u, f2.payloadLength);
190    EXPECT_EQ(0, memcmp("\x00", f2.payload, f2.payloadLength));
191    EXPECT_TRUE(f2.final);
192    EXPECT_FALSE(f2.compress);
193
194    c.resetDeflateBuffer();
195    ASSERT_TRUE(c.deflate(f3));
196    EXPECT_EQ(0u, f3.payloadLength);
197    EXPECT_TRUE(f3.final);
198    EXPECT_FALSE(f3.compress);
199
200    c.resetDeflateBuffer();
201    ASSERT_TRUE(c.deflate(f4));
202    EXPECT_EQ(0u, f4.payloadLength);
203    EXPECT_TRUE(f4.final);
204    EXPECT_FALSE(f4.compress);
205
206    c.resetDeflateBuffer();
207    ASSERT_TRUE(c.deflate(f5));
208    EXPECT_EQ(7u, f5.payloadLength);
209    EXPECT_EQ(0, memcmp("\xf2\x48\xcd\xc9\xc9\x07\x00", f5.payload, f5.payloadLength));
210    EXPECT_TRUE(f5.final);
211    EXPECT_TRUE(f5.compress);
212}
213
214TEST(WebSocketPerMessageDeflateTest, TestControlMessage)
215{
216    WebSocketPerMessageDeflate c;
217    c.enable(8, WebSocketDeflater::TakeOverContext);
218    WebSocketFrame::OpCode opcode = WebSocketFrame::OpCodeClose;
219    WebSocketFrame f1(opcode, "Hello", 5, WebSocketFrame::Final);
220
221    ASSERT_TRUE(c.deflate(f1));
222    EXPECT_TRUE(f1.final);
223    EXPECT_FALSE(f1.compress);
224    EXPECT_EQ(std::string("Hello"), std::string(f1.payload, f1.payloadLength));
225}
226
227TEST(WebSocketPerMessageDeflateTest, TestDeflateControlMessageBetweenTextFrames)
228{
229    WebSocketPerMessageDeflate c;
230    c.enable(8, WebSocketDeflater::TakeOverContext);
231    WebSocketFrame::OpCode close = WebSocketFrame::OpCodeClose;
232    WebSocketFrame::OpCode text = WebSocketFrame::OpCodeText;
233    WebSocketFrame::OpCode continuation = WebSocketFrame::OpCodeContinuation;
234    WebSocketFrame f1(text, "Hello", 5);
235    WebSocketFrame f2(close, "close", 5, WebSocketFrame::Final);
236    WebSocketFrame f3(continuation, "", 0, WebSocketFrame::Final);
237
238    std::vector<char> compressed;
239    ASSERT_TRUE(c.deflate(f1));
240    EXPECT_FALSE(f1.final);
241    EXPECT_TRUE(f1.compress);
242    std::copy(&f1.payload[0], &f1.payload[f1.payloadLength], std::inserter(compressed, compressed.end()));
243
244    c.resetDeflateBuffer();
245    ASSERT_TRUE(c.deflate(f2));
246    EXPECT_TRUE(f2.final);
247    EXPECT_FALSE(f2.compress);
248    EXPECT_EQ(std::string("close"), std::string(f2.payload, f2.payloadLength));
249
250    c.resetDeflateBuffer();
251    ASSERT_TRUE(c.deflate(f3));
252    EXPECT_TRUE(f3.final);
253    EXPECT_FALSE(f3.compress);
254    std::copy(&f3.payload[0], &f3.payload[f3.payloadLength], std::inserter(compressed, compressed.end()));
255
256    EXPECT_EQ(7u, compressed.size());
257    EXPECT_EQ(0, memcmp("\xf2\x48\xcd\xc9\xc9\x07\x00", &compressed[0], compressed.size()));
258}
259
260TEST(WebSocketPerMessageDeflateTest, TestInflate)
261{
262    WebSocketPerMessageDeflate c;
263    c.enable(8, WebSocketDeflater::TakeOverContext);
264    WebSocketFrame::OpCode opcode = WebSocketFrame::OpCodeText;
265    WebSocketFrame::OpCode continuation = WebSocketFrame::OpCodeContinuation;
266    std::string expected = "HelloHi!Hello";
267    std::string actual;
268    WebSocketFrame f1(opcode, "\xf2\x48\xcd\xc9\xc9\x07\x00", 7, WebSocketFrame::Final | WebSocketFrame::Compress);
269    WebSocketFrame f2(continuation, "Hi!", 3, WebSocketFrame::Final);
270    WebSocketFrame f3(opcode, "\xf2\x00\x11\x00\x00", 5, WebSocketFrame::Final | WebSocketFrame::Compress);
271
272    ASSERT_TRUE(c.inflate(f1));
273    EXPECT_EQ(5u, f1.payloadLength);
274    EXPECT_EQ(std::string("Hello"), std::string(f1.payload, f1.payloadLength));
275    EXPECT_FALSE(f1.compress);
276    EXPECT_TRUE(f1.final);
277
278    c.resetInflateBuffer();
279    ASSERT_TRUE(c.inflate(f2));
280    EXPECT_EQ(3u, f2.payloadLength);
281    EXPECT_EQ(std::string("Hi!"), std::string(f2.payload, f2.payloadLength));
282    EXPECT_FALSE(f2.compress);
283    EXPECT_TRUE(f2.final);
284
285    c.resetInflateBuffer();
286    ASSERT_TRUE(c.inflate(f3));
287    EXPECT_EQ(5u, f3.payloadLength);
288    EXPECT_EQ(std::string("Hello"), std::string(f3.payload, f3.payloadLength));
289    EXPECT_FALSE(f3.compress);
290    EXPECT_TRUE(f3.final);
291}
292
293TEST(WebSocketPerMessageDeflateTest, TestInflateMultipleBlocksOverMultipleFrames)
294{
295    WebSocketPerMessageDeflate c;
296    c.enable(8, WebSocketDeflater::TakeOverContext);
297    WebSocketFrame::OpCode opcode = WebSocketFrame::OpCodeText;
298    WebSocketFrame::OpCode continuation = WebSocketFrame::OpCodeContinuation;
299    std::string expected = "HelloHello";
300    std::string actual;
301    WebSocketFrame f1(opcode, "\xf2\x48\xcd\xc9\xc9\x07\x00\x00\x00\xff\xff", 11, WebSocketFrame::Compress);
302    WebSocketFrame f2(continuation, "\xf2\x00\x11\x00\x00", 5, WebSocketFrame::Final);
303
304    ASSERT_TRUE(c.inflate(f1));
305    EXPECT_FALSE(f1.compress);
306    EXPECT_FALSE(f1.final);
307    actual += std::string(f1.payload, f1.payloadLength);
308
309    c.resetInflateBuffer();
310    ASSERT_TRUE(c.inflate(f2));
311    EXPECT_FALSE(f2.compress);
312    EXPECT_TRUE(f2.final);
313    actual += std::string(f2.payload, f2.payloadLength);
314
315    EXPECT_EQ(expected, actual);
316}
317
318TEST(WebSocketPerMessageDeflateTest, TestInflateEmptyFrame)
319{
320    WebSocketPerMessageDeflate c;
321    c.enable(8, WebSocketDeflater::TakeOverContext);
322    WebSocketFrame::OpCode opcode = WebSocketFrame::OpCodeText;
323    WebSocketFrame::OpCode continuation = WebSocketFrame::OpCodeContinuation;
324    WebSocketFrame f1(opcode, "", 0, WebSocketFrame::Compress);
325    WebSocketFrame f2(continuation, "\xf2\x48\xcd\xc9\xc9\x07\x00", 7, WebSocketFrame::Final);
326
327    ASSERT_TRUE(c.inflate(f1));
328    EXPECT_EQ(0u, f1.payloadLength);
329    EXPECT_FALSE(f1.compress);
330    EXPECT_FALSE(f1.final);
331
332    c.resetInflateBuffer();
333    ASSERT_TRUE(c.inflate(f2));
334    EXPECT_EQ(5u, f2.payloadLength);
335    EXPECT_EQ(std::string("Hello"), std::string(f2.payload, f2.payloadLength));
336    EXPECT_FALSE(f2.compress);
337    EXPECT_TRUE(f2.final);
338}
339
340TEST(WebSocketPerMessageDeflateTest, TestInflateControlMessageBetweenTextFrames)
341{
342    WebSocketPerMessageDeflate c;
343    c.enable(8, WebSocketDeflater::TakeOverContext);
344    WebSocketFrame::OpCode close = WebSocketFrame::OpCodeClose;
345    WebSocketFrame::OpCode text = WebSocketFrame::OpCodeText;
346    WebSocketFrame f1(text, "\xf2\x48", 2, WebSocketFrame::Compress);
347    WebSocketFrame f2(close, "close", 5, WebSocketFrame::Final);
348    WebSocketFrame f3(text, "\xcd\xc9\xc9\x07\x00", 5, WebSocketFrame::Final);
349
350    std::vector<char> decompressed;
351    ASSERT_TRUE(c.inflate(f1));
352    EXPECT_FALSE(f1.final);
353    EXPECT_FALSE(f1.compress);
354    std::copy(&f1.payload[0], &f1.payload[f1.payloadLength], std::inserter(decompressed, decompressed.end()));
355
356    c.resetInflateBuffer();
357    ASSERT_TRUE(c.inflate(f2));
358    EXPECT_TRUE(f2.final);
359    EXPECT_FALSE(f2.compress);
360    EXPECT_EQ(std::string("close"), std::string(f2.payload, f2.payloadLength));
361
362    c.resetInflateBuffer();
363    ASSERT_TRUE(c.inflate(f3));
364    std::copy(&f3.payload[0], &f3.payload[f3.payloadLength], std::inserter(decompressed, decompressed.end()));
365    EXPECT_TRUE(f3.final);
366    EXPECT_FALSE(f3.compress);
367
368    EXPECT_EQ(std::string("Hello"), std::string(&decompressed[0], decompressed.size()));
369}
370
371TEST(WebSocketPerMessageDeflateTest, TestNotEnabled)
372{
373    WebSocketPerMessageDeflate c;
374    WebSocketFrame::OpCode opcode = WebSocketFrame::OpCodeClose;
375    WebSocketFrame f1(opcode, "Hello", 5, WebSocketFrame::Final | WebSocketFrame::Compress);
376    WebSocketFrame f2(opcode, "\xf2\x48\xcd\xc9\xc9\x07\x00", 7, WebSocketFrame::Final | WebSocketFrame::Compress);
377
378    // deflate and inflate return true and do nothing if it is not enabled.
379    ASSERT_TRUE(c.deflate(f1));
380    ASSERT_TRUE(f1.compress);
381    ASSERT_TRUE(c.inflate(f2));
382    ASSERT_TRUE(f2.compress);
383}
384
385bool processResponse(const HashMap<String, String>& serverParameters)
386{
387    return WebSocketPerMessageDeflate().createExtensionProcessor()->processResponse(serverParameters);
388}
389
390TEST(WebSocketPerMessageDeflateTest, TestValidNegotiationResponse)
391{
392    {
393        HashMap<String, String> params;
394        EXPECT_TRUE(processResponse(params));
395    }
396    {
397        HashMap<String, String> params;
398        params.add("client_max_window_bits", "15");
399        EXPECT_TRUE(processResponse(params));
400    }
401    {
402        HashMap<String, String> params;
403        params.add("client_max_window_bits", "8");
404        EXPECT_TRUE(processResponse(params));
405    }
406    {
407        HashMap<String, String> params;
408        params.add("client_max_window_bits", "15");
409        params.add("client_no_context_takeover", String());
410        EXPECT_TRUE(processResponse(params));
411    }
412    {
413        // Unsolicited server_no_context_takeover should be ignored.
414        HashMap<String, String> params;
415        params.add("server_no_context_takeover", String());
416        EXPECT_TRUE(processResponse(params));
417    }
418    {
419        // Unsolicited server_max_window_bits should be ignored.
420        HashMap<String, String> params;
421        params.add("server_max_window_bits", "15");
422        EXPECT_TRUE(processResponse(params));
423    }
424}
425
426TEST(WebSocketPerMessageDeflateTest, TestInvalidNegotiationResponse)
427{
428    {
429        HashMap<String, String> params;
430        params.add("method", "deflate");
431        EXPECT_FALSE(processResponse(params));
432    }
433    {
434        HashMap<String, String> params;
435        params.add("foo", "");
436        EXPECT_FALSE(processResponse(params));
437    }
438    {
439        HashMap<String, String> params;
440        params.add("foo", "bar");
441        EXPECT_FALSE(processResponse(params));
442    }
443    {
444        HashMap<String, String> params;
445        params.add("client_max_window_bits", "");
446        EXPECT_FALSE(processResponse(params));
447    }
448    {
449        HashMap<String, String> params;
450        params.add("client_max_window_bits", "16");
451        EXPECT_FALSE(processResponse(params));
452    }
453    {
454        HashMap<String, String> params;
455        params.add("client_max_window_bits", "7");
456        EXPECT_FALSE(processResponse(params));
457    }
458    {
459        HashMap<String, String> params;
460        params.add("client_max_window_bits", "+15");
461        EXPECT_FALSE(processResponse(params));
462    }
463    {
464        HashMap<String, String> params;
465        params.add("client_max_window_bits", "0x9");
466        EXPECT_FALSE(processResponse(params));
467    }
468    {
469        HashMap<String, String> params;
470        params.add("client_max_window_bits", "08");
471        EXPECT_FALSE(processResponse(params));
472    }
473    {
474        // Unsolicited server_no_context_takeover should be verified though it is not used.
475        HashMap<String, String> params;
476        params.add("server_no_context_takeover", "foo");
477        EXPECT_FALSE(processResponse(params));
478    }
479    {
480        // Unsolicited server_max_window_bits should be verified though it is not used.
481        HashMap<String, String> params;
482        params.add("server_max_window_bits", "7");
483        EXPECT_FALSE(processResponse(params));
484    }
485    {
486        // Unsolicited server_max_window_bits should be verified though it is not used.
487        HashMap<String, String> params;
488        params.add("server_max_window_bits", "bar");
489        EXPECT_FALSE(processResponse(params));
490    }
491    {
492        // Unsolicited server_max_window_bits should be verified though it is not used.
493        HashMap<String, String> params;
494        params.add("server_max_window_bits", "16");
495        EXPECT_FALSE(processResponse(params));
496    }
497    {
498        // Unsolicited server_max_window_bits should be verified though it is not used.
499        HashMap<String, String> params;
500        params.add("server_max_window_bits", "08");
501        EXPECT_FALSE(processResponse(params));
502    }
503}
504
505TEST(WebSocketPerMessageDeflateTest, TestNegotiationRequest)
506{
507    String actual = WebSocketPerMessageDeflate().createExtensionProcessor()->handshakeString();
508    EXPECT_EQ(String("permessage-deflate; client_max_window_bits"), actual);
509}
510
511} // namespace
512} // namespace blink
513