OLD | NEW |
---|---|
(Empty) | |
1 // Copyright 2013 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 #include "net/websockets/websocket_channel.h" | |
6 | |
7 #include <iostream> | |
8 #include <string> | |
9 #include <vector> | |
10 | |
11 #include "base/bind.h" | |
12 #include "base/bind_helpers.h" | |
13 #include "base/callback.h" | |
14 #include "base/memory/scoped_ptr.h" | |
15 #include "base/memory/scoped_vector.h" | |
16 #include "base/message_loop/message_loop.h" | |
17 #include "base/strings/string_piece.h" | |
18 #include "googleurl/src/gurl.h" | |
19 #include "net/base/net_errors.h" | |
20 #include "net/url_request/url_request_context.h" | |
21 #include "net/websockets/websocket_errors.h" | |
22 #include "net/websockets/websocket_event_interface.h" | |
23 #include "net/websockets/websocket_mux.h" | |
24 #include "testing/gmock/include/gmock/gmock.h" | |
25 #include "testing/gtest/include/gtest/gtest.h" | |
26 | |
27 namespace net { | |
28 | |
29 // Printing helpers to allow GoogleMock to print frame chunks. These are | |
30 // explicitly designed to look like the static initialisation format we use in | |
31 // these tests. Static to reduce the risk of linker clashes. | |
32 | |
33 static std::ostream& operator<<(std::ostream& os, | |
34 const WebSocketFrameHeader& header) { | |
35 return os << "{" << (header.final ? "FINAL_FRAME" : "NOT_FINAL_FRAME") << ", " | |
36 << header.opcode << ", " | |
37 << (header.masked ? "MASKED" : "NOT_MASKED") << ", " | |
38 << header.payload_length << "}"; | |
39 } | |
40 | |
41 static std::ostream& operator<<(std::ostream& os, | |
42 const WebSocketFrameChunk& chunk) { | |
43 os << "{"; | |
44 if (chunk.header) { | |
45 os << *chunk.header; | |
46 } else { | |
47 os << "{NO_HEADER}"; | |
48 } | |
49 return os << ", " << (chunk.final_chunk ? "FINAL_CHUNK" : "NOT_FINAL_CHUNK") | |
50 << ", \"" << base::StringPiece(chunk.data->data(), | |
51 chunk.data->size()) << "\"}"; | |
52 } | |
53 | |
54 namespace { | |
55 | |
56 using ::testing::AnyNumber; | |
57 using ::testing::Field; | |
58 using ::testing::InSequence; | |
59 using ::testing::MockFunction; | |
60 using ::testing::Pointee; | |
61 using ::testing::Return; | |
62 using ::testing::_; | |
63 | |
64 // This mock is for testing expectations about how the EventInterface is used. | |
65 class MockWebSocketEventInterface : public WebSocketEventInterface { | |
66 public: | |
67 MOCK_METHOD2(OnAddChannelResponse, void(bool, const std::string&)); | |
68 MOCK_METHOD3(OnDataFrame, | |
69 void(bool, WebSocketMessageType, const std::vector<char>&)); | |
70 MOCK_METHOD1(OnFlowControl, void(int64)); | |
71 MOCK_METHOD0(OnClosingHandshake, void(void)); | |
72 MOCK_METHOD2(OnDropChannel, void(uint16, const std::string&)); | |
73 }; | |
74 | |
75 // This fake EventInterface is for tests which need a WebSocketEventInterface | |
76 // implementation but are not verifying how it is used. | |
77 class FakeWebSocketEventInterface : public WebSocketEventInterface { | |
78 virtual void OnAddChannelResponse(bool fail, | |
79 const std::string& selected_protocol) | |
80 OVERRIDE {} | |
81 virtual void OnDataFrame(bool fin, | |
82 WebSocketMessageType type, | |
83 const std::vector<char>& data) OVERRIDE {} | |
84 virtual void OnFlowControl(int64 quota) OVERRIDE {} | |
85 virtual void OnClosingHandshake() OVERRIDE {} | |
86 virtual void OnDropChannel(uint16 code, const std::string& reason) OVERRIDE {} | |
87 }; | |
88 | |
89 // This fake WebSocketStream is for tests that require a WebSocketStream but are | |
90 // not testing the way it is used. It has minimal functionality to return | |
91 // the |protocol| and |extensions| that it was constructed with. | |
92 class FakeWebSocketStream : public WebSocketStream { | |
93 public: | |
94 // Constructs with empty protocol and extensions. | |
95 FakeWebSocketStream() : WebSocketStream(), protocol_(), extensions_() {} | |
96 | |
97 // Constructs with specified protocol and extensions. | |
98 FakeWebSocketStream(const std::string& protocol, | |
99 const std::string& extensions) | |
100 : WebSocketStream(), protocol_(protocol), extensions_(extensions) {} | |
101 | |
102 virtual int SendHandshakeRequest(const GURL& url, | |
103 const HttpRequestHeaders& headers, | |
104 HttpResponseInfo* response_info, | |
105 const CompletionCallback& callback) | |
106 OVERRIDE { | |
107 return ERR_IO_PENDING; | |
108 } | |
109 | |
110 virtual int ReadHandshakeResponse(const CompletionCallback& callback) | |
111 OVERRIDE { | |
112 return ERR_IO_PENDING; | |
113 } | |
114 | |
115 virtual int ReadFrames(ScopedVector<WebSocketFrameChunk>* frame_chunks, | |
116 const CompletionCallback& callback) OVERRIDE { | |
117 return ERR_IO_PENDING; | |
118 } | |
119 | |
120 virtual int WriteFrames(ScopedVector<WebSocketFrameChunk>* frame_chunks, | |
121 const CompletionCallback& callback) OVERRIDE { | |
122 return ERR_IO_PENDING; | |
123 } | |
124 | |
125 virtual void Close() OVERRIDE {} | |
126 | |
127 // Returns the string passed to the constructor. | |
128 virtual std::string GetSubProtocol() const OVERRIDE { return protocol_; } | |
129 | |
130 // Returns the string passed to the constructor. | |
131 virtual std::string GetExtensions() const OVERRIDE { return extensions_; } | |
132 | |
133 private: | |
134 // The string to return from GetSubProtocol(). | |
135 std::string protocol_; | |
136 | |
137 // The string to return from GetExtensions(). | |
138 std::string extensions_; | |
139 }; | |
140 | |
141 // To make the static initialisers easier to read, we use enums rather than | |
142 // bools. | |
143 | |
144 // NO_HEADER means there shouldn't be a header included in the generated | |
145 // WebSocketFrameChunk. The static initialiser always has a header, but we can | |
146 // avoid specifying the rest of the fields. | |
147 enum IsFinal { | |
148 NO_HEADER, | |
149 NOT_FINAL_FRAME, | |
150 FINAL_FRAME | |
151 }; | |
152 | |
153 enum IsMasked { | |
154 NOT_MASKED, | |
155 MASKED | |
156 }; | |
157 | |
158 enum IsFinalChunk { | |
159 NOT_FINAL_CHUNK, | |
160 FINAL_CHUNK | |
161 }; | |
162 | |
163 // This is used to initialise a WebSocketFrameChunk but is statically | |
164 // initialisable. | |
165 struct InitFrameChunk { | |
166 struct FrameHeader { | |
167 IsFinal final; | |
168 // Reserved fields omitted for now. Add them if you need them. | |
169 WebSocketFrameHeader::OpCode opcode; | |
170 IsMasked masked; | |
171 // payload_length is the length of the whole frame. The length of the data | |
172 // members from every chunk in the frame must add up to the payload_length. | |
173 uint64 payload_length; | |
174 }; | |
175 FrameHeader header; | |
176 | |
177 // Directly equivalent to WebSocketFrameChunk::final_chunk | |
178 IsFinalChunk final_chunk; | |
179 | |
180 // Will be used to create the IOBuffer member. Can be NULL for NULL data. Is a | |
181 // nul-terminated string for ease-of-use. This means it is not 8-bit clean, | |
182 // but this is not an issue for test data. | |
183 const char* const data; | |
184 }; | |
185 | |
186 // Convert a const array of InitFrameChunks to the format used at | |
187 // runtime. Templated on the size of the array to save typing. | |
188 template <size_t N> | |
189 ScopedVector<WebSocketFrameChunk> CreateFrameChunkVector( | |
190 const InitFrameChunk (&source_chunks)[N]) { | |
191 ScopedVector<WebSocketFrameChunk> result_chunks; | |
192 result_chunks.reserve(N); | |
193 for (size_t i = 0; i < N; ++i) { | |
194 scoped_ptr<WebSocketFrameChunk> result_chunk(new WebSocketFrameChunk); | |
195 size_t chunk_length = | |
196 source_chunks[i].data ? strlen(source_chunks[i].data) : 0; | |
197 if (source_chunks[i].header.final != NO_HEADER) { | |
198 const InitFrameChunk::FrameHeader& source_header = | |
199 source_chunks[i].header; | |
200 scoped_ptr<WebSocketFrameHeader> result_header( | |
201 new WebSocketFrameHeader(source_header.opcode)); | |
202 result_header->final = source_header.final == FINAL_FRAME; | |
szym
2013/07/12 17:44:51
nit: parens would help
Adam Rice
2013/07/12 20:46:55
I was getting kind of an anti-params vibe from the
szym
2013/07/12 21:03:22
Maybe it's my eyes, but while I have no problem wi
| |
203 result_header->opcode = source_header.opcode; | |
204 result_header->masked = source_header.masked == MASKED; | |
szym
2013/07/12 17:44:51
ditto
Adam Rice
2013/07/12 20:46:55
Done.
| |
205 result_header->payload_length = source_header.payload_length; | |
206 DCHECK(chunk_length <= source_header.payload_length); | |
207 result_chunk->header.swap(result_header); | |
208 } | |
209 result_chunk->final_chunk = source_chunks[i].final_chunk == FINAL_CHUNK; | |
szym
2013/07/12 17:44:51
ditto
Adam Rice
2013/07/12 20:46:55
Done.
| |
210 if (source_chunks[i].data) { | |
211 result_chunk->data = new IOBufferWithSize(chunk_length); | |
212 memcpy(result_chunk->data->data(), source_chunks[i].data, chunk_length); | |
213 } | |
214 result_chunks.push_back(result_chunk.release()); | |
215 } | |
216 return result_chunks.Pass(); | |
217 } | |
218 | |
219 // A GoogleMock action which can be used to respond to call to ReadFrames with | |
220 // some frames. Use like ReadFrames(_, _).WillOnce(ReturnChunks(chunks)); | |
221 ACTION_P(ReturnChunks, source_chunks) { | |
222 *arg0 = CreateFrameChunkVector(source_chunks); | |
223 return OK; | |
224 } | |
225 | |
226 // A FakeWebSocketStream whose ReadFrames() function returns data. | |
227 class ReadableFakeWebSocketStream : public FakeWebSocketStream { | |
228 public: | |
229 enum IsSync { | |
230 SYNC, | |
231 ASYNC | |
232 }; | |
233 | |
234 // After constructing the object, call PrepareReadFrames() once for each | |
235 // time you wish it to return from the test. | |
236 ReadableFakeWebSocketStream() | |
237 : FakeWebSocketStream(), responses_(), index_(0) {} | |
238 | |
239 // Prepares a fake responses. Fake responses will be returned from | |
240 // ReadFrames() in the same order they were prepared with PrepareReadFrames() | |
241 // and PrepareReadFramesError(). If |async| is ASYNC, then ReadFrames() will | |
242 // return ERR_IO_PENDING and the callback will be scheduled to run on the | |
243 // message loop. This requires the test case to run the message loop. If | |
244 // |async| is SYNC, the response will be returned synchronously. |error| is | |
245 // returned directly from ReadFrames() in the synchronous case, or passed to | |
246 // the callback in the asynchronous case. |chunks| will be converted to a | |
247 // ScopedVector<WebSocketFrameChunks> and copied to the pointer that was | |
248 // passed to ReadFrames(). | |
249 template <size_t N> | |
250 void PrepareReadFrames(IsSync async, | |
251 int error, | |
252 const InitFrameChunk (&chunks)[N]) { | |
253 responses_.push_back( | |
254 new Response(async, error, CreateFrameChunkVector(chunks))); | |
255 } | |
256 | |
257 // Prepares a fake error response (ie. there is no data). | |
258 void PrepareReadFramesError(IsSync async, int error) { | |
259 responses_.push_back( | |
260 new Response(async, error, ScopedVector<WebSocketFrameChunk>())); | |
261 } | |
262 | |
263 virtual int ReadFrames(ScopedVector<WebSocketFrameChunk>* frame_chunks, | |
264 const CompletionCallback& callback) OVERRIDE { | |
szym
2013/07/12 17:44:51
Maybe add some check that this is not called while
Adam Rice
2013/07/12 20:46:55
Done.
| |
265 if (index_ >= responses_.size()) { | |
266 return ERR_IO_PENDING; | |
267 } | |
szym
2013/07/12 17:44:51
nit: no need for {}
Adam Rice
2013/07/12 20:46:55
Done.
| |
268 if (responses_[index_]->async == ASYNC) { | |
269 base::MessageLoop::current()->PostTask( | |
270 FROM_HERE, | |
271 base::Bind(&ReadableFakeWebSocketStream::DoCallback, | |
272 base::Unretained(this), | |
szym
2013/07/12 17:44:51
To avoid confusing use-after-free I suggest adding
Adam Rice
2013/07/12 20:46:55
Done.
| |
273 frame_chunks, | |
274 callback)); | |
275 return ERR_IO_PENDING; | |
276 } else { | |
277 frame_chunks->swap(responses_[index_]->chunks); | |
278 return responses_[index_++]->error; | |
279 } | |
280 } | |
281 | |
282 private: | |
283 void DoCallback(ScopedVector<WebSocketFrameChunk>* frame_chunks, | |
284 const CompletionCallback& callback) { | |
285 frame_chunks->swap(responses_[index_]->chunks); | |
286 callback.Run(responses_[index_++]->error); | |
287 return; | |
288 } | |
289 | |
290 struct Response { | |
291 Response(IsSync async, int error, ScopedVector<WebSocketFrameChunk> chunks) | |
292 : async(async), error(error), chunks(chunks.Pass()) {} | |
293 | |
294 IsSync async; | |
295 int error; | |
296 ScopedVector<WebSocketFrameChunk> chunks; | |
297 | |
298 private: | |
299 // Bad things will happen if we attempt to copy or assign "chunks". | |
300 DISALLOW_COPY_AND_ASSIGN(Response); | |
301 }; | |
302 ScopedVector<Response> responses_; | |
303 size_t index_; | |
304 }; | |
305 | |
306 // A FakeWebSocketStream where writes always complete successfully and | |
307 // synchronously. | |
308 class WriteableFakeWebSocketStream : public FakeWebSocketStream { | |
309 public: | |
310 virtual int WriteFrames(ScopedVector<WebSocketFrameChunk>* frame_chunks, | |
311 const CompletionCallback& callback) OVERRIDE { | |
312 return OK; | |
313 } | |
314 }; | |
315 | |
316 // A FakeWebSocketStream where writes always fail. | |
317 class UnWriteableFakeWebSocketStream : public FakeWebSocketStream { | |
318 public: | |
319 virtual int WriteFrames(ScopedVector<WebSocketFrameChunk>* frame_chunks, | |
320 const CompletionCallback& callback) OVERRIDE { | |
321 return ERR_CONNECTION_RESET; | |
322 } | |
323 }; | |
324 | |
325 // A FakeWebSocketStream which echoes any frames written back. It unset the | |
szym
2013/07/12 17:44:51
"It unset" => "Clears"
Adam Rice
2013/07/12 20:46:55
Done.
| |
326 // "masked" header bit, but makes no other checks for validity. Tests using this | |
327 // must run the MessageLoop to receive the callback(s). If a message with opcode | |
328 // Close is echoed, then an ERR_CONNECTION_CLOSED is returned in the next | |
329 // callback. The test must do something to cause WriteFrames() to be called, | |
330 // otherwise the ReadFrames() callback will never be called. | |
331 class EchoeyFakeWebSocketStream : public FakeWebSocketStream { | |
332 public: | |
333 EchoeyFakeWebSocketStream() : read_frame_chunks_(NULL), done_(false) {} | |
334 | |
335 virtual int WriteFrames(ScopedVector<WebSocketFrameChunk>* frame_chunks, | |
336 const CompletionCallback& callback) OVERRIDE { | |
337 // Users of WebSocketStream will not expect the ReadFrames() callback to be | |
338 // called from within WriteFrames(), so post it to the message loop instead. | |
339 stored_frame_chunks_.insert( | |
340 stored_frame_chunks_.end(), frame_chunks->begin(), frame_chunks->end()); | |
341 frame_chunks->weak_clear(); | |
342 PostCallback(); | |
343 return OK; | |
344 } | |
345 | |
346 virtual int ReadFrames(ScopedVector<WebSocketFrameChunk>* frame_chunks, | |
347 const CompletionCallback& callback) OVERRIDE { | |
348 read_callback_ = callback; | |
349 read_frame_chunks_ = frame_chunks; | |
350 if (done_) { | |
351 PostCallback(); | |
352 } | |
353 return ERR_IO_PENDING; | |
354 } | |
355 | |
356 private: | |
357 void PostCallback() { | |
358 base::MessageLoop::current()->PostTask( | |
359 FROM_HERE, | |
360 base::Bind(&EchoeyFakeWebSocketStream::DoCallback, | |
361 base::Unretained(this))); | |
362 } | |
363 | |
364 void DoCallback() { | |
365 if (done_) { | |
366 read_callback_.Run(ERR_CONNECTION_CLOSED); | |
367 } else if (!stored_frame_chunks_.empty()) { | |
368 done_ = MoveFrameChunks(read_frame_chunks_); | |
369 read_frame_chunks_ = NULL; | |
370 read_callback_.Run(OK); | |
371 } | |
372 } | |
373 | |
374 // Copy the chunks stored in stored_frame_chunks_ to |to|, while unsetting the | |
szym
2013/07/12 17:44:51
suggest "unsetting" -> "clearing"
Adam Rice
2013/07/12 20:46:55
Done.
| |
375 // "masked" header bit. Returns true if a Close Frame was seen, false | |
376 // otherwise. | |
377 bool MoveFrameChunks(ScopedVector<WebSocketFrameChunk>* to) { | |
szym
2013/07/12 17:44:51
Suggest |out| rather than |to|.
Adam Rice
2013/07/12 20:46:55
Done.
| |
378 bool seen_close = false; | |
379 to->assign(stored_frame_chunks_.begin(), stored_frame_chunks_.end()); | |
szym
2013/07/12 17:44:51
out->swap(stored_frame_chunks_);
// alternatively
Adam Rice
2013/07/12 20:46:55
Oh yeah. I don't know why I did it the hard way.
| |
380 for (ScopedVector<WebSocketFrameChunk>::iterator it = to->begin(); | |
381 it != to->end(); | |
382 ++it) { | |
383 if ((*it)->header) { | |
384 const scoped_ptr<WebSocketFrameHeader>& header = (*it)->header; | |
szym
2013/07/12 17:44:51
Suggest simple pointer instead of const scoped_ptr
Adam Rice
2013/07/12 20:46:55
Done.
| |
385 if (header->masked) { | |
szym
2013/07/12 17:44:51
unnecessary condition
Adam Rice
2013/07/12 20:46:55
Done.
| |
386 header->masked = false; | |
387 } | |
388 if (header->opcode == WebSocketFrameHeader::kOpCodeClose) { | |
389 seen_close = true; | |
390 } | |
szym
2013/07/12 17:44:51
nit: unnecessary {}
Adam Rice
2013/07/12 20:46:55
Done.
| |
391 } | |
392 } | |
393 stored_frame_chunks_.weak_clear(); | |
394 return seen_close; | |
395 } | |
396 | |
397 ScopedVector<WebSocketFrameChunk> stored_frame_chunks_; | |
398 CompletionCallback read_callback_; | |
399 // Owned by the caller of ReadFrames(). | |
400 ScopedVector<WebSocketFrameChunk>* read_frame_chunks_; | |
401 // True if we should close the connection. | |
402 bool done_; | |
403 }; | |
404 | |
405 // This mock is for verifying that WebSocket protocol semantics are obeyed (to | |
406 // the extent that they are implemented in WebSocketCommon). | |
407 class MockWebSocketStream : public WebSocketStream { | |
408 public: | |
409 MOCK_METHOD2(ReadFrames, | |
410 int(ScopedVector<WebSocketFrameChunk>* frame_chunks, | |
411 const CompletionCallback& callback)); | |
412 MOCK_METHOD2(WriteFrames, | |
413 int(ScopedVector<WebSocketFrameChunk>* frame_chunks, | |
414 const CompletionCallback& callback)); | |
415 MOCK_METHOD0(Close, void()); | |
416 MOCK_CONST_METHOD0(GetSubProtocol, std::string()); | |
417 MOCK_CONST_METHOD0(GetExtensions, std::string()); | |
418 MOCK_METHOD0(AsWebSocketStream, WebSocketStream*()); | |
419 MOCK_METHOD4(SendHandshakeRequest, | |
420 int(const GURL& url, | |
421 const HttpRequestHeaders& headers, | |
422 HttpResponseInfo* response_info, | |
423 const CompletionCallback& callback)); | |
424 MOCK_METHOD1(ReadHandshakeResponse, int(const CompletionCallback& callback)); | |
425 }; | |
426 | |
427 struct ArgumentCopyingWebSocketFactory { | |
428 scoped_ptr<WebSocketStreamRequest> Factory( | |
429 const GURL& socket_url, | |
430 const std::vector<std::string>& requested_subprotocols, | |
431 const GURL& origin, | |
432 URLRequestContext* url_request_context, | |
433 const BoundNetLog& net_log, | |
434 scoped_ptr<WebSocketStream::ConnectDelegate> connect_delegate) { | |
435 this->socket_url = socket_url; | |
436 this->requested_subprotocols = requested_subprotocols; | |
437 this->origin = origin; | |
438 this->url_request_context = url_request_context; | |
439 this->net_log = net_log; | |
440 this->connect_delegate = connect_delegate.Pass(); | |
441 return make_scoped_ptr(new WebSocketStreamRequest); | |
442 } | |
443 | |
444 GURL socket_url; | |
445 GURL origin; | |
446 std::vector<std::string> requested_subprotocols; | |
447 URLRequestContext* url_request_context; | |
448 BoundNetLog net_log; | |
449 scoped_ptr<WebSocketStream::ConnectDelegate> connect_delegate; | |
450 }; | |
451 | |
452 // Converts a std::string to a std::vector<char>. For test purposes, it is | |
453 // convenient to be able to specify data as a string, but the | |
454 // WebSocketEventInterface requires the vector<char> type. | |
455 std::vector<char> AsVector(const std::string& s) { | |
456 return std::vector<char>(s.begin(), s.end()); | |
457 } | |
458 | |
459 // Base class for WebSocketChannelTests. | |
szym
2013/07/12 17:44:51
Suggest: // Base class for all test fixtures.
Adam Rice
2013/07/12 20:46:55
Done.
| |
460 class WebSocketChannelTest : public ::testing::Test { | |
461 protected: | |
462 WebSocketChannelTest() | |
463 : data_(), channel_(), stream_(new FakeWebSocketStream) {} | |
szym
2013/07/12 17:44:51
no need for default initializers
Adam Rice
2013/07/12 20:46:55
Done.
| |
464 | |
465 // Creates a new WebSocketChannel and connects it, using the settings stored | |
466 // in |data_|. | |
467 void CreateChannelAndConnect() { | |
468 channel_.reset(new WebSocketChannel(data_.url, CreateEventInterface())); | |
469 channel_->SendAddChannelRequestForTesting( | |
470 data_.requested_subprotocols, | |
471 data_.origin, | |
472 &data_.url_request_context, | |
473 base::Bind(&ArgumentCopyingWebSocketFactory::Factory, | |
474 base::Unretained(&data_.factory))); | |
475 } | |
476 | |
477 // Same as CreateChannelAndConnect(), but calls the on_success callback as | |
478 // well. This method is virtual so that WebSocketChannelStreamTest can also | |
szym
2013/07/12 17:44:51
To avoid "comment drift", just "subclasses" instea
Adam Rice
2013/07/12 20:46:55
"Virtual to allow override" is uncomfortably close
| |
479 // set the stream. | |
480 virtual void CreateChannelAndConnectSuccessfully() { | |
481 CreateChannelAndConnect(); | |
482 data_.factory.connect_delegate->OnSuccess(stream_.Pass()); | |
483 } | |
484 | |
485 // Returns a WebSocketEventInterface to be passed to the WebSocketChannel. | |
486 // This implementation returns a newly-created fake. Subclasses may return a | |
487 // mock instead. | |
488 virtual scoped_ptr<WebSocketEventInterface> CreateEventInterface() { | |
489 return scoped_ptr<WebSocketEventInterface>(new FakeWebSocketEventInterface); | |
490 } | |
491 | |
492 // This method serves no other purpose than to provide a nice syntax for | |
493 // assigning to stream_. class T must be a subclass of WebSocketStream or you | |
494 // will have unpleasant compile errors. | |
495 template <class T> | |
496 void set_stream(scoped_ptr<T> stream) { | |
497 // Since the definition of "PassAs" depends on the type T, the C++ standard | |
498 // requires the "template" keyword to indicate that "PassAs" should be | |
499 // parsed as a template method. | |
szym
2013/07/12 17:44:51
remark: I learn something new about C++ every day.
| |
500 stream_ = stream.template PassAs<WebSocketStream>(); | |
501 } | |
502 | |
503 // A struct containing the data that will be used to connect the channel. | |
504 struct ConnectData { | |
505 // URL to (pretend to) connect to. | |
506 GURL url; | |
507 // Origin of the request | |
508 GURL origin; | |
509 // Requested protocols for the request. | |
510 std::vector<std::string> requested_subprotocols; | |
511 // URLRequestContext object. | |
512 URLRequestContext url_request_context; | |
513 // A fake WebSocketFactory that just records its arguments. | |
514 ArgumentCopyingWebSocketFactory factory; | |
515 }; | |
516 ConnectData data_; | |
szym
2013/07/12 17:44:51
suggest connect_data_, but I don't really see the
Adam Rice
2013/07/12 20:46:55
Basically it's a struct so that I don't have to ty
| |
517 | |
518 // The channel we are testing. Not initialised until SetChannel() is called. | |
519 scoped_ptr<WebSocketChannel> channel_; | |
520 | |
521 // A mock or fake stream for tests that need one. | |
522 scoped_ptr<WebSocketStream> stream_; | |
523 }; | |
524 | |
525 // Base class for tests which verify that EventInterface methods are called | |
526 // appropriately. | |
527 class WebSocketChannelEventInterfaceTest : public WebSocketChannelTest { | |
528 protected: | |
529 WebSocketChannelEventInterfaceTest() | |
530 : WebSocketChannelTest(), | |
531 event_interface_(new MockWebSocketEventInterface) {} | |
532 | |
533 // Tests using this fixture must set expectations on the event_interface_ mock | |
534 // object before calling CreateChannelAndConnect() or | |
535 // CreateChannelAndConnectSuccessfully(). This will only work once per test | |
536 // case, but once should be enough. | |
szym
2013/07/12 17:44:51
Just a remark: In test environment, it's typically
Adam Rice
2013/07/12 20:46:55
The amount of time I want to spend debugging use-a
| |
537 virtual scoped_ptr<WebSocketEventInterface> CreateEventInterface() OVERRIDE { | |
538 return scoped_ptr<WebSocketEventInterface>(event_interface_.release()); | |
539 } | |
540 | |
541 scoped_ptr<MockWebSocketEventInterface> event_interface_; | |
szym
2013/07/12 17:44:51
wondering if this should be a StrictMock.
Adam Rice
2013/07/12 20:46:55
Yes, I had been wondering the same thing. I have m
| |
542 }; | |
543 | |
544 // Base class for tests which verify that WebSocketStream methods are called | |
545 // appropriately by using a MockWebSocketStream. | |
546 class WebSocketChannelStreamTest : public WebSocketChannelTest { | |
547 protected: | |
548 WebSocketChannelStreamTest() | |
549 : WebSocketChannelTest(), mock_stream_(new MockWebSocketStream) {} | |
550 | |
551 virtual void CreateChannelAndConnectSuccessfully() OVERRIDE { | |
552 set_stream(mock_stream_.Pass()); | |
553 WebSocketChannelTest::CreateChannelAndConnectSuccessfully(); | |
554 } | |
555 | |
556 scoped_ptr<MockWebSocketStream> mock_stream_; | |
557 }; | |
558 | |
559 // Simple test that everything that should be passed to the factory function is | |
560 // passed to the factory function. | |
561 TEST_F(WebSocketChannelTest, EverythingIsPassedToTheFactoryFunction) { | |
562 data_.url = GURL("ws://example.com/test"); | |
563 data_.origin = GURL("http://example.com/test"); | |
564 data_.requested_subprotocols.push_back("Sinbad"); | |
565 | |
566 CreateChannelAndConnect(); | |
567 | |
568 EXPECT_EQ(data_.url, data_.factory.socket_url); | |
569 EXPECT_EQ(data_.origin, data_.factory.origin); | |
570 EXPECT_EQ(data_.requested_subprotocols, data_.factory.requested_subprotocols); | |
571 EXPECT_EQ(&data_.url_request_context, data_.factory.url_request_context); | |
572 } | |
573 | |
574 TEST_F(WebSocketChannelEventInterfaceTest, ConnectSuccessReported) { | |
575 // false means success. | |
szym
2013/07/12 17:44:51
Would changing OnAddChannelResponse to (bool succe
Adam Rice
2013/07/12 20:46:55
I've been bitten by this a few times, but I am try
szym
2013/07/12 21:03:22
Agreed, also see Function(string, string, string,
| |
576 EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, "")); | |
577 // OnFlowControl is always called immediately after connect to provide initial | |
578 // quota to the renderer. | |
579 EXPECT_CALL(*event_interface_, OnFlowControl(_)); | |
580 | |
581 CreateChannelAndConnect(); | |
582 | |
583 data_.factory.connect_delegate->OnSuccess(stream_.Pass()); | |
584 } | |
585 | |
586 TEST_F(WebSocketChannelEventInterfaceTest, ConnectFailureReported) { | |
587 // true means failure. | |
588 EXPECT_CALL(*event_interface_, OnAddChannelResponse(true, "")); | |
589 | |
590 CreateChannelAndConnect(); | |
591 | |
592 data_.factory.connect_delegate->OnFailure(kWebSocketErrorNoStatusReceived); | |
593 } | |
594 | |
595 TEST_F(WebSocketChannelEventInterfaceTest, ProtocolPassed) { | |
596 EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, "Bob")); | |
597 EXPECT_CALL(*event_interface_, OnFlowControl(_)); | |
598 | |
599 CreateChannelAndConnect(); | |
600 | |
601 data_.factory.connect_delegate->OnSuccess( | |
602 scoped_ptr<WebSocketStream>(new FakeWebSocketStream("Bob", ""))); | |
603 } | |
604 | |
605 // The first frames from the server can arrive together with the handshake, in | |
606 // which case they will be available as soon as ReadFrames() is called the first | |
607 // time. | |
608 TEST_F(WebSocketChannelEventInterfaceTest, DataLeftFromHandshake) { | |
609 scoped_ptr<ReadableFakeWebSocketStream> stream( | |
610 new ReadableFakeWebSocketStream); | |
611 static const InitFrameChunk chunks[] = { | |
612 {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, NOT_MASKED, 5}, | |
613 FINAL_CHUNK, "HELLO"}, | |
614 }; | |
615 stream->PrepareReadFrames(ReadableFakeWebSocketStream::SYNC, OK, chunks); | |
616 set_stream(stream.Pass()); | |
617 { | |
618 InSequence s; | |
619 EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _)); | |
620 EXPECT_CALL(*event_interface_, OnFlowControl(_)); | |
621 EXPECT_CALL( | |
622 *event_interface_, | |
623 OnDataFrame( | |
624 true, WebSocketFrameHeader::kOpCodeText, AsVector("HELLO"))); | |
625 } | |
626 | |
627 CreateChannelAndConnectSuccessfully(); | |
628 } | |
629 | |
630 // A remote server could accept the handshake, but then immediately send a | |
631 // Close frame. | |
632 TEST_F(WebSocketChannelEventInterfaceTest, CloseAfterHandshake) { | |
633 scoped_ptr<ReadableFakeWebSocketStream> stream( | |
634 new ReadableFakeWebSocketStream); | |
635 static const InitFrameChunk chunks[] = { | |
636 {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, NOT_MASKED, 23}, | |
637 FINAL_CHUNK, "\x03\xf3Internal Server Error"}, | |
638 }; | |
639 stream->PrepareReadFrames(ReadableFakeWebSocketStream::SYNC, OK, chunks); | |
640 stream->PrepareReadFramesError(ReadableFakeWebSocketStream::SYNC, | |
641 ERR_CONNECTION_CLOSED); | |
642 set_stream(stream.Pass()); | |
643 { | |
644 InSequence s; | |
645 EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _)); | |
646 EXPECT_CALL(*event_interface_, OnFlowControl(_)); | |
647 EXPECT_CALL(*event_interface_, OnClosingHandshake()); | |
648 EXPECT_CALL(*event_interface_, | |
649 OnDropChannel(kWebSocketErrorInternalServerError, | |
650 "Internal Server Error")); | |
651 } | |
652 | |
653 CreateChannelAndConnectSuccessfully(); | |
654 } | |
655 | |
656 // A remote server could close the connection immediately after sending the | |
657 // handshake response (most likely a bug in the server). | |
658 TEST_F(WebSocketChannelEventInterfaceTest, ConnectionCloseAfterHandshake) { | |
659 scoped_ptr<ReadableFakeWebSocketStream> stream( | |
660 new ReadableFakeWebSocketStream); | |
661 stream->PrepareReadFramesError(ReadableFakeWebSocketStream::SYNC, | |
662 ERR_CONNECTION_CLOSED); | |
663 set_stream(stream.Pass()); | |
664 { | |
665 InSequence s; | |
666 EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _)); | |
667 EXPECT_CALL(*event_interface_, OnFlowControl(_)); | |
668 EXPECT_CALL( | |
669 *event_interface_, | |
670 OnDropChannel(kWebSocketErrorAbnormalClosure, "Abnormal Closure")); | |
szym
2013/07/12 17:44:51
If the error message strings matter, should they b
Adam Rice
2013/07/12 20:46:55
No. Or rather, yes, if they did matter. But this o
| |
671 } | |
672 | |
673 CreateChannelAndConnectSuccessfully(); | |
674 } | |
675 | |
676 TEST_F(WebSocketChannelEventInterfaceTest, NormalAsyncRead) { | |
677 scoped_ptr<ReadableFakeWebSocketStream> stream( | |
678 new ReadableFakeWebSocketStream); | |
679 static const InitFrameChunk chunks[] = { | |
680 {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, NOT_MASKED, 5}, | |
681 FINAL_CHUNK, "HELLO"}, | |
682 }; | |
683 // We use this checkpoint object to verify that the callback isn't called | |
684 // until we expect it to be. | |
685 MockFunction<void(int)> checkpoint; | |
686 stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks); | |
687 set_stream(stream.Pass()); | |
688 { | |
689 InSequence s; | |
690 EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _)); | |
691 EXPECT_CALL(*event_interface_, OnFlowControl(_)); | |
692 EXPECT_CALL(checkpoint, Call(1)); | |
693 EXPECT_CALL( | |
694 *event_interface_, | |
695 OnDataFrame( | |
696 true, WebSocketFrameHeader::kOpCodeText, AsVector("HELLO"))); | |
697 EXPECT_CALL(checkpoint, Call(2)); | |
698 } | |
699 | |
700 CreateChannelAndConnectSuccessfully(); | |
701 checkpoint.Call(1); | |
702 base::MessageLoop::current()->RunUntilIdle(); | |
703 checkpoint.Call(2); | |
704 } | |
705 | |
706 // Extra data can arrive while a read is being processed, resulting in the next | |
707 // read completing synchronously. | |
708 TEST_F(WebSocketChannelEventInterfaceTest, AsyncThenSyncRead) { | |
709 scoped_ptr<ReadableFakeWebSocketStream> stream( | |
710 new ReadableFakeWebSocketStream); | |
711 static const InitFrameChunk chunks1[] = { | |
712 {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, NOT_MASKED, 5}, | |
713 FINAL_CHUNK, "HELLO"}, | |
714 }; | |
715 static const InitFrameChunk chunks2[] = { | |
716 {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, NOT_MASKED, 5}, | |
717 FINAL_CHUNK, "WORLD"}, | |
718 }; | |
719 stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks1); | |
720 stream->PrepareReadFrames(ReadableFakeWebSocketStream::SYNC, OK, chunks2); | |
721 set_stream(stream.Pass()); | |
722 { | |
723 InSequence s; | |
724 EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _)); | |
725 EXPECT_CALL(*event_interface_, OnFlowControl(_)); | |
726 EXPECT_CALL( | |
727 *event_interface_, | |
728 OnDataFrame( | |
729 true, WebSocketFrameHeader::kOpCodeText, AsVector("HELLO"))); | |
730 EXPECT_CALL( | |
731 *event_interface_, | |
732 OnDataFrame( | |
733 true, WebSocketFrameHeader::kOpCodeText, AsVector("WORLD"))); | |
734 } | |
735 | |
736 CreateChannelAndConnectSuccessfully(); | |
737 base::MessageLoop::current()->RunUntilIdle(); | |
738 } | |
739 | |
740 // Data frames that arrive in fragments are turned into individual frames | |
741 TEST_F(WebSocketChannelEventInterfaceTest, FragmentedFrames) { | |
742 scoped_ptr<ReadableFakeWebSocketStream> stream( | |
743 new ReadableFakeWebSocketStream); | |
744 // Here we have one message split into 3 frames which arrive in 3 chunks. The | |
745 // first frame is entirely in the first chunk, the second frame is split | |
746 // across all the chunks, and the final frame is entirely in the final | |
747 // chunk. The frame fragments are converted to separate frames so that they | |
748 // can be delivered immediatedly. So the EventInterface should see a Text | |
749 // message with 5 frames. | |
750 static const InitFrameChunk chunks1[] = { | |
751 {{NOT_FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, NOT_MASKED, 5}, | |
752 FINAL_CHUNK, "THREE"}, | |
753 {{NOT_FINAL_FRAME, WebSocketFrameHeader::kOpCodeContinuation, NOT_MASKED, | |
754 7}, | |
755 NOT_FINAL_CHUNK, " "}, | |
756 }; | |
757 static const InitFrameChunk chunks2[] = {{{NO_HEADER}, NOT_FINAL_CHUNK, | |
758 "SMALL"}}; | |
759 static const InitFrameChunk chunks3[] = { | |
760 {{NO_HEADER}, FINAL_CHUNK, " "}, | |
761 {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeContinuation, NOT_MASKED, 6}, | |
762 FINAL_CHUNK, "FRAMES"}, | |
763 }; | |
764 stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks1); | |
765 stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks2); | |
766 stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks3); | |
767 set_stream(stream.Pass()); | |
768 { | |
769 InSequence s; | |
770 EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _)); | |
771 EXPECT_CALL(*event_interface_, OnFlowControl(_)); | |
772 EXPECT_CALL( | |
773 *event_interface_, | |
774 OnDataFrame( | |
775 false, WebSocketFrameHeader::kOpCodeText, AsVector("THREE"))); | |
776 EXPECT_CALL( | |
777 *event_interface_, | |
778 OnDataFrame( | |
779 false, WebSocketFrameHeader::kOpCodeContinuation, AsVector(" "))); | |
780 EXPECT_CALL(*event_interface_, | |
781 OnDataFrame(false, | |
782 WebSocketFrameHeader::kOpCodeContinuation, | |
783 AsVector("SMALL"))); | |
784 EXPECT_CALL( | |
785 *event_interface_, | |
786 OnDataFrame( | |
787 false, WebSocketFrameHeader::kOpCodeContinuation, AsVector(" "))); | |
788 EXPECT_CALL(*event_interface_, | |
789 OnDataFrame(true, | |
790 WebSocketFrameHeader::kOpCodeContinuation, | |
791 AsVector("FRAMES"))); | |
792 } | |
793 | |
794 CreateChannelAndConnectSuccessfully(); | |
795 base::MessageLoop::current()->RunUntilIdle(); | |
796 } | |
797 | |
798 // In the case when a single-frame message because fragmented, it must be | |
799 // correctly transformed to multiple frames. | |
800 TEST_F(WebSocketChannelEventInterfaceTest, MessageFragmentation) { | |
801 scoped_ptr<ReadableFakeWebSocketStream> stream( | |
802 new ReadableFakeWebSocketStream); | |
803 // A single-frame Text message arrives in three chunks. This should be | |
804 // delivered as three frames. | |
805 static const InitFrameChunk chunks1[] = { | |
806 {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, NOT_MASKED, 12}, | |
807 NOT_FINAL_CHUNK, "TIME"}, | |
808 }; | |
809 static const InitFrameChunk chunks2[] = {{{NO_HEADER}, NOT_FINAL_CHUNK, | |
810 " FOR "}}; | |
811 static const InitFrameChunk chunks3[] = {{{NO_HEADER}, FINAL_CHUNK, "TEA"}}; | |
812 stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks1); | |
813 stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks2); | |
814 stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks3); | |
815 set_stream(stream.Pass()); | |
816 { | |
817 InSequence s; | |
818 EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _)); | |
819 EXPECT_CALL(*event_interface_, OnFlowControl(_)); | |
820 EXPECT_CALL( | |
821 *event_interface_, | |
822 OnDataFrame( | |
823 false, WebSocketFrameHeader::kOpCodeText, AsVector("TIME"))); | |
824 EXPECT_CALL(*event_interface_, | |
825 OnDataFrame(false, | |
826 WebSocketFrameHeader::kOpCodeContinuation, | |
827 AsVector(" FOR "))); | |
828 EXPECT_CALL( | |
829 *event_interface_, | |
830 OnDataFrame( | |
831 true, WebSocketFrameHeader::kOpCodeContinuation, AsVector("TEA"))); | |
832 } | |
833 | |
834 CreateChannelAndConnectSuccessfully(); | |
835 base::MessageLoop::current()->RunUntilIdle(); | |
836 } | |
837 | |
838 // If a control message is fragmented, it must be re-assembled before being | |
839 // delivered. A control message can only be fragmented at the network level; it | |
840 // is not permitted to be split into multiple frames. | |
841 TEST_F(WebSocketChannelEventInterfaceTest, FragmentedControlMessage) { | |
842 scoped_ptr<ReadableFakeWebSocketStream> stream( | |
843 new ReadableFakeWebSocketStream); | |
844 static const InitFrameChunk chunks1[] = { | |
845 {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, NOT_MASKED, 7}, | |
846 NOT_FINAL_CHUNK, "\x03\xe8"}, | |
847 }; | |
848 static const InitFrameChunk chunks2[] = {{{NO_HEADER}, NOT_FINAL_CHUNK, | |
849 "Clo"}}; | |
850 static const InitFrameChunk chunks3[] = {{{NO_HEADER}, FINAL_CHUNK, "se"}}; | |
851 stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks1); | |
852 stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks2); | |
853 stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks3); | |
854 stream->PrepareReadFramesError(ReadableFakeWebSocketStream::ASYNC, | |
855 ERR_CONNECTION_CLOSED); | |
856 set_stream(stream.Pass()); | |
857 { | |
858 InSequence s; | |
859 EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _)); | |
860 EXPECT_CALL(*event_interface_, OnFlowControl(_)); | |
861 EXPECT_CALL(*event_interface_, OnClosingHandshake()); | |
862 EXPECT_CALL(*event_interface_, | |
863 OnDropChannel(kWebSocketNormalClosure, "Close")); | |
864 } | |
865 | |
866 CreateChannelAndConnectSuccessfully(); | |
867 base::MessageLoop::current()->RunUntilIdle(); | |
868 } | |
869 | |
870 // Connection closed unexpectedly. | |
871 TEST_F(WebSocketChannelEventInterfaceTest, AsyncAbnormalClosure) { | |
872 scoped_ptr<ReadableFakeWebSocketStream> stream( | |
873 new ReadableFakeWebSocketStream); | |
874 stream->PrepareReadFramesError(ReadableFakeWebSocketStream::ASYNC, | |
875 ERR_CONNECTION_CLOSED); | |
876 set_stream(stream.Pass()); | |
877 { | |
878 InSequence s; | |
879 EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _)); | |
880 EXPECT_CALL(*event_interface_, OnFlowControl(_)); | |
881 EXPECT_CALL(*event_interface_, | |
882 OnDropChannel(kWebSocketErrorAbnormalClosure, _)); | |
883 } | |
884 | |
885 CreateChannelAndConnectSuccessfully(); | |
886 base::MessageLoop::current()->RunUntilIdle(); | |
887 } | |
888 | |
889 // Connection reset. | |
szym
2013/07/12 17:44:51
Uninformative comment. I wonder if there is any te
Adam Rice
2013/07/12 20:46:55
The point is actually to verify that the behaviour
szym
2013/07/12 21:03:22
ok
| |
890 TEST_F(WebSocketChannelEventInterfaceTest, ConnectionReset) { | |
891 scoped_ptr<ReadableFakeWebSocketStream> stream( | |
892 new ReadableFakeWebSocketStream); | |
893 stream->PrepareReadFramesError(ReadableFakeWebSocketStream::ASYNC, | |
894 ERR_CONNECTION_RESET); | |
895 set_stream(stream.Pass()); | |
896 { | |
897 InSequence s; | |
898 EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _)); | |
899 EXPECT_CALL(*event_interface_, OnFlowControl(_)); | |
900 EXPECT_CALL(*event_interface_, | |
901 OnDropChannel(kWebSocketErrorAbnormalClosure, _)); | |
902 } | |
903 | |
904 CreateChannelAndConnectSuccessfully(); | |
905 base::MessageLoop::current()->RunUntilIdle(); | |
906 } | |
907 | |
908 // Connection closed in the middle of a Close message (server bug, etc.) | |
909 TEST_F(WebSocketChannelEventInterfaceTest, ConnectionClosedInMessage) { | |
910 scoped_ptr<ReadableFakeWebSocketStream> stream( | |
911 new ReadableFakeWebSocketStream); | |
912 static const InitFrameChunk chunks[] = { | |
913 {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, NOT_MASKED, 7}, | |
914 NOT_FINAL_CHUNK, "\x03\xe8"}, | |
915 }; | |
916 | |
917 stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks); | |
918 stream->PrepareReadFramesError(ReadableFakeWebSocketStream::ASYNC, | |
919 ERR_CONNECTION_CLOSED); | |
920 set_stream(stream.Pass()); | |
921 { | |
922 InSequence s; | |
923 EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _)); | |
924 EXPECT_CALL(*event_interface_, OnFlowControl(_)); | |
925 EXPECT_CALL(*event_interface_, | |
926 OnDropChannel(kWebSocketErrorAbnormalClosure, _)); | |
927 } | |
928 | |
929 CreateChannelAndConnectSuccessfully(); | |
930 base::MessageLoop::current()->RunUntilIdle(); | |
931 } | |
932 | |
933 // RFC6455 5.1 "A client MUST close a connection if it detects a masked frame." | |
934 TEST_F(WebSocketChannelEventInterfaceTest, MaskedFramesAreRejected) { | |
935 scoped_ptr<ReadableFakeWebSocketStream> stream( | |
936 new ReadableFakeWebSocketStream); | |
937 static const InitFrameChunk chunks[] = { | |
938 {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, MASKED, 5}, FINAL_CHUNK, | |
939 "HELLO"} | |
940 }; | |
941 | |
942 stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks); | |
943 set_stream(stream.Pass()); | |
944 { | |
945 InSequence s; | |
946 EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _)); | |
947 EXPECT_CALL(*event_interface_, OnFlowControl(_)); | |
948 EXPECT_CALL(*event_interface_, | |
949 OnDropChannel(kWebSocketErrorProtocolError, _)); | |
950 } | |
951 | |
952 CreateChannelAndConnectSuccessfully(); | |
953 base::MessageLoop::current()->RunUntilIdle(); | |
954 } | |
955 | |
956 // RFC6455 5.2 "If an unknown opcode is received, the receiving endpoint MUST | |
957 // _Fail the WebSocket Connection_." | |
958 TEST_F(WebSocketChannelEventInterfaceTest, UnknownOpCodeIsRejected) { | |
959 scoped_ptr<ReadableFakeWebSocketStream> stream( | |
960 new ReadableFakeWebSocketStream); | |
961 static const InitFrameChunk chunks[] = {{{FINAL_FRAME, 4, NOT_MASKED, 5}, | |
962 FINAL_CHUNK, "HELLO"}}; | |
963 | |
964 stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks); | |
965 set_stream(stream.Pass()); | |
966 { | |
967 InSequence s; | |
968 EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _)); | |
969 EXPECT_CALL(*event_interface_, OnFlowControl(_)); | |
970 EXPECT_CALL(*event_interface_, | |
971 OnDropChannel(kWebSocketErrorProtocolError, _)); | |
972 } | |
973 | |
974 CreateChannelAndConnectSuccessfully(); | |
975 base::MessageLoop::current()->RunUntilIdle(); | |
976 } | |
977 | |
978 // RFC6455 5.4 "Control frames ... MAY be injected in the middle of a | |
979 // fragmented message." | |
980 TEST_F(WebSocketChannelEventInterfaceTest, ControlFrameInDataMessage) { | |
981 scoped_ptr<ReadableFakeWebSocketStream> stream( | |
982 new ReadableFakeWebSocketStream); | |
983 // We have one message of type Text split into two frames. In the middle is a | |
984 // control message of type Pong. | |
985 static const InitFrameChunk chunks1[] = { | |
986 {{NOT_FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, NOT_MASKED, 6}, | |
987 FINAL_CHUNK, "SPLIT "}, | |
988 }; | |
989 static const InitFrameChunk chunks2[] = { | |
990 {{FINAL_FRAME, WebSocketFrameHeader::kOpCodePong, NOT_MASKED, 0}, | |
991 FINAL_CHUNK, ""} | |
992 }; | |
993 static const InitFrameChunk chunks3[] = { | |
994 {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeContinuation, NOT_MASKED, 7}, | |
995 FINAL_CHUNK, "MESSAGE"} | |
996 }; | |
997 stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks1); | |
998 stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks2); | |
999 stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks3); | |
1000 set_stream(stream.Pass()); | |
1001 { | |
1002 InSequence s; | |
1003 EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _)); | |
1004 EXPECT_CALL(*event_interface_, OnFlowControl(_)); | |
1005 EXPECT_CALL( | |
1006 *event_interface_, | |
1007 OnDataFrame( | |
1008 false, WebSocketFrameHeader::kOpCodeText, AsVector("SPLIT "))); | |
1009 EXPECT_CALL(*event_interface_, | |
1010 OnDataFrame(true, | |
1011 WebSocketFrameHeader::kOpCodeContinuation, | |
1012 AsVector("MESSAGE"))); | |
1013 } | |
1014 | |
1015 CreateChannelAndConnectSuccessfully(); | |
1016 base::MessageLoop::current()->RunUntilIdle(); | |
1017 } | |
1018 | |
1019 // If the renderer sends lots of small writes, we don't want to update the quota | |
1020 // for each one. | |
1021 TEST_F(WebSocketChannelEventInterfaceTest, SmallWriteDoesntUpdateQuota) { | |
1022 set_stream(make_scoped_ptr(new WriteableFakeWebSocketStream)); | |
1023 { | |
1024 InSequence s; | |
1025 EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _)); | |
1026 EXPECT_CALL(*event_interface_, OnFlowControl(_)); | |
1027 } | |
1028 | |
1029 CreateChannelAndConnectSuccessfully(); | |
1030 channel_->SendFrame(true, WebSocketFrameHeader::kOpCodeText, AsVector("B")); | |
1031 } | |
1032 | |
1033 // If we send enough to go below send_quota_low_water_mask_ we should get our | |
1034 // quota refreshed. | |
1035 TEST_F(WebSocketChannelEventInterfaceTest, LargeWriteUpdatesQuota) { | |
1036 set_stream(make_scoped_ptr(new WriteableFakeWebSocketStream)); | |
1037 // We use this checkpoint object to verify that the quota update comes after | |
1038 // the write. | |
1039 MockFunction<void(int)> checkpoint; | |
1040 { | |
1041 InSequence s; | |
1042 EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _)); | |
1043 EXPECT_CALL(*event_interface_, OnFlowControl(_)); | |
1044 EXPECT_CALL(checkpoint, Call(1)); | |
1045 EXPECT_CALL(*event_interface_, OnFlowControl(_)); | |
1046 EXPECT_CALL(checkpoint, Call(2)); | |
1047 } | |
1048 | |
1049 CreateChannelAndConnectSuccessfully(); | |
1050 checkpoint.Call(1); | |
1051 // TODO(ricea): If kDefaultSendQuotaHighWaterMark changes, then this value | |
1052 // will need to be updated. | |
1053 channel_->SendFrame( | |
1054 true, WebSocketFrameHeader::kOpCodeText, std::vector<char>(1 << 17, 'B')); | |
1055 checkpoint.Call(2); | |
1056 } | |
1057 | |
1058 // Verify that our quota actually is refreshed when we are told it is. | |
1059 TEST_F(WebSocketChannelEventInterfaceTest, QuotaReallyIsRefreshed) { | |
1060 set_stream(make_scoped_ptr(new WriteableFakeWebSocketStream)); | |
1061 MockFunction<void(int)> checkpoint; | |
1062 { | |
1063 InSequence s; | |
1064 EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _)); | |
1065 EXPECT_CALL(*event_interface_, OnFlowControl(_)); | |
1066 EXPECT_CALL(checkpoint, Call(1)); | |
1067 EXPECT_CALL(*event_interface_, OnFlowControl(_)); | |
1068 EXPECT_CALL(checkpoint, Call(2)); | |
1069 // If quota was not really refreshed, we would get an OnDropChannel() | |
1070 // message. | |
1071 EXPECT_CALL(*event_interface_, OnFlowControl(_)); | |
1072 EXPECT_CALL(checkpoint, Call(3)); | |
1073 } | |
1074 | |
1075 CreateChannelAndConnectSuccessfully(); | |
1076 checkpoint.Call(1); | |
1077 // TODO(ricea): If kDefaultSendQuotaLowWaterMark and/or | |
1078 // kDefaultSendQuotaHighWaterMark change, then this value will need to be | |
1079 // updated. | |
1080 channel_->SendFrame(true, | |
1081 WebSocketFrameHeader::kOpCodeText, | |
1082 std::vector<char>((1 << 16) + 1, 'D')); | |
1083 checkpoint.Call(2); | |
1084 // We should have received more quota at this point. | |
1085 channel_->SendFrame(true, | |
1086 WebSocketFrameHeader::kOpCodeText, | |
1087 std::vector<char>((1 << 16) + 1, 'E')); | |
1088 checkpoint.Call(3); | |
1089 } | |
1090 | |
1091 // If we send more than the available quota then the connection will be closed | |
1092 // with an error. | |
1093 TEST_F(WebSocketChannelEventInterfaceTest, WriteOverQuotaIsRejected) { | |
1094 set_stream(make_scoped_ptr(new WriteableFakeWebSocketStream)); | |
1095 { | |
1096 InSequence s; | |
1097 EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _)); | |
1098 // TODO(ricea): Change this if kDefaultSendQuotaHighWaterMark changes. | |
1099 EXPECT_CALL(*event_interface_, OnFlowControl(1 << 17)); | |
1100 EXPECT_CALL(*event_interface_, | |
1101 OnDropChannel(kWebSocketMuxErrorSendQuotaViolation, _)); | |
1102 } | |
1103 | |
1104 CreateChannelAndConnectSuccessfully(); | |
1105 channel_->SendFrame(true, | |
1106 WebSocketFrameHeader::kOpCodeText, | |
1107 std::vector<char>((1 << 17) + 1, 'C')); | |
1108 } | |
1109 | |
1110 // If a write fails, the channel is dropped. | |
1111 TEST_F(WebSocketChannelEventInterfaceTest, FailedWrite) { | |
1112 set_stream(make_scoped_ptr(new UnWriteableFakeWebSocketStream)); | |
1113 MockFunction<void(int)> checkpoint; | |
1114 { | |
1115 InSequence s; | |
1116 EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _)); | |
1117 EXPECT_CALL(*event_interface_, OnFlowControl(_)); | |
1118 EXPECT_CALL(checkpoint, Call(1)); | |
1119 EXPECT_CALL(*event_interface_, | |
1120 OnDropChannel(kWebSocketErrorAbnormalClosure, _)); | |
1121 EXPECT_CALL(checkpoint, Call(2)); | |
1122 } | |
1123 | |
1124 CreateChannelAndConnectSuccessfully(); | |
1125 checkpoint.Call(1); | |
1126 | |
1127 channel_->SendFrame(true, WebSocketFrameHeader::kOpCodeText, AsVector("H")); | |
1128 checkpoint.Call(2); | |
1129 } | |
1130 | |
1131 // OnDropChannel() is called exactly once when StartClosingHandshake() is used. | |
1132 TEST_F(WebSocketChannelEventInterfaceTest, SendCloseDropsChannel) { | |
1133 set_stream(make_scoped_ptr(new EchoeyFakeWebSocketStream)); | |
1134 { | |
1135 InSequence s; | |
1136 EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _)); | |
1137 EXPECT_CALL(*event_interface_, OnFlowControl(_)); | |
1138 EXPECT_CALL(*event_interface_, | |
1139 OnDropChannel(kWebSocketNormalClosure, "Fred")); | |
1140 } | |
1141 | |
1142 CreateChannelAndConnectSuccessfully(); | |
1143 | |
1144 channel_->StartClosingHandshake(kWebSocketNormalClosure, "Fred"); | |
1145 base::MessageLoop::current()->RunUntilIdle(); | |
1146 } | |
1147 | |
1148 // RFC6455 5.1 "a client MUST mask all frames that it sends to the server". | |
1149 // WebSocketChannel actually only sets the mask bit in the header, it doesn't | |
1150 // perform masking itself (not all transports actually use masking). | |
1151 TEST_F(WebSocketChannelStreamTest, SentFramesAreMasked) { | |
1152 EXPECT_CALL(*mock_stream_, GetSubProtocol()).Times(AnyNumber()); | |
1153 EXPECT_CALL(*mock_stream_, ReadFrames(_, _)).WillOnce(Return(ERR_IO_PENDING)); | |
1154 EXPECT_CALL( | |
1155 *mock_stream_, | |
1156 WriteFrames(Pointee(ElementsAre(Pointee(Field( | |
1157 &WebSocketFrameChunk::header, | |
1158 Pointee(Field(&WebSocketFrameHeader::masked, true)))))), | |
szym
2013/07/12 17:44:51
remark: feels like Lisp, http://xkcd.com/297/
I d
Adam Rice
2013/07/12 20:46:55
Yeah. And this is one of the simpler cases. In the
| |
1159 _)).WillOnce(Return(ERR_IO_PENDING)); | |
1160 | |
1161 CreateChannelAndConnectSuccessfully(); | |
1162 channel_->SendFrame( | |
1163 true, WebSocketFrameHeader::kOpCodeText, AsVector("NEEDS MASKING")); | |
1164 } | |
1165 | |
1166 } // namespace | |
1167 } // namespace net | |
OLD | NEW |