Subversion Repositories php-qbpwcf

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
3 liveuser 1
<?php
2
 
3
namespace Ratchet\RFC6455\Test\Unit\Messaging;
4
 
5
use Ratchet\RFC6455\Messaging\Frame;
6
use PHPUnit\Framework\TestCase;
7
 
8
/**
9
 * @covers Ratchet\RFC6455\Messaging\Frame
10
 * @todo getMaskingKey, getPayloadStartingByte don't have tests yet
11
 * @todo Could use some clean up in general, I had to rush to fix a bug for a deadline, sorry.
12
 */
13
class FrameTest extends TestCase {
14
    protected $_firstByteFinText    = '10000001';
15
 
16
    protected $_secondByteMaskedSPL = '11111101';
17
 
18
    /** @var Frame */
19
    protected $_frame;
20
 
21
    protected $_packer;
22
 
23
    public function setUp() {
24
        $this->_frame = new Frame;
25
    }
26
 
27
    /**
28
     * Encode the fake binary string to send over the wire
29
     * @param string of 1's and 0's
30
     * @return string
31
     */
32
    public static function encode($in) {
33
        if (strlen($in) > 8) {
34
            $out = '';
35
            while (strlen($in) >= 8) {
36
                $out .= static::encode(substr($in, 0, 8));
37
                $in   = substr($in, 8);
38
            }
39
            return $out;
40
        }
41
        return chr(bindec($in));
42
    }
43
 
44
    /**
45
     * This is a data provider
46
     * param string The UTF8 message
47
     * param string The WebSocket framed message, then base64_encoded
48
     */
49
    public static function UnframeMessageProvider() {
50
        return array(
51
            array('Hello World!', 'gYydAIfa1WXrtvIg0LXvbOP7'),
52
            array('!@#$%^&*()-=_+[]{}\|/.,<>`~', 'gZv+h96r38f9j9vZ+IHWrvOWoayF9oX6gtfRqfKXwOeg'),
53
            array('ಠ_ಠ', 'gYfnSpu5B/g75gf4Ow=='),
54
            array(
55
                "The quick brown fox jumps over the lazy dog.  All work and no play makes Chris a dull boy.  I'm trying to get past 128 characters for a unit test here...",
56
                'gf4Amahb14P8M7Kj2S6+4MN7tfHHLLmjzjSvo8IuuvPbe7j1zSn398A+9+/JIa6jzDSwrYh7lu/Ee6Ds2jD34sY/9+3He6fvySL37skwsvCIGL/xwSj34og/ou/Ee7Xs0XX3o+F8uqPcKa7qxjz398d7sObce6fi2y/3sppj9+DAOqXiyy+y8dt7sezae7aj3TW+94gvsvDce7/m2j75rYY='
57
            )
58
        );
59
    }
60
 
61
    public static function underflowProvider() {
62
        return array(
63
            array('isFinal', ''),
64
            array('getRsv1', ''),
65
            array('getRsv2', ''),
66
            array('getRsv3', ''),
67
            array('getOpcode', ''),
68
            array('isMasked', '10000001'),
69
            array('getPayloadLength', '10000001'),
70
            array('getPayloadLength', '1000000111111110'),
71
            array('getMaskingKey', '1000000110000111'),
72
            array('getPayload', '100000011000000100011100101010101001100111110100')
73
        );
74
    }
75
 
76
    /**
77
     * @dataProvider underflowProvider
78
     *
79
     * @covers Ratchet\RFC6455\Messaging\Frame::isFinal
80
     * @covers Ratchet\RFC6455\Messaging\Frame::getRsv1
81
     * @covers Ratchet\RFC6455\Messaging\Frame::getRsv2
82
     * @covers Ratchet\RFC6455\Messaging\Frame::getRsv3
83
     * @covers Ratchet\RFC6455\Messaging\Frame::getOpcode
84
     * @covers Ratchet\RFC6455\Messaging\Frame::isMasked
85
     * @covers Ratchet\RFC6455\Messaging\Frame::getPayloadLength
86
     * @covers Ratchet\RFC6455\Messaging\Frame::getMaskingKey
87
     * @covers Ratchet\RFC6455\Messaging\Frame::getPayload
88
     */
89
    public function testUnderflowExceptionFromAllTheMethodsMimickingBuffering($method, $bin) {
90
        $this->setExpectedException('\UnderflowException');
91
        if (!empty($bin)) {
92
            $this->_frame->addBuffer(static::encode($bin));
93
        }
94
        call_user_func(array($this->_frame, $method));
95
    }
96
 
97
    /**
98
     * A data provider for testing the first byte of a WebSocket frame
99
     * param bool Given, is the byte indicate this is the final frame
100
     * param int Given, what is the expected opcode
101
     * param string of 0|1 Each character represents a bit in the byte
102
     */
103
    public static function firstByteProvider() {
104
        return array(
105
            array(false, false, false, true,   8, '00011000'),
106
            array(true,  false, true,  false, 10, '10101010'),
107
            array(false, false, false, false, 15, '00001111'),
108
            array(true,  false, false, false,  1, '10000001'),
109
            array(true,  true,  true,  true,  15, '11111111'),
110
            array(true,  true,  false, false,  7, '11000111')
111
        );
112
    }
113
 
114
    /**
115
     * @dataProvider firstByteProvider
116
     * covers Ratchet\RFC6455\Messaging\Frame::isFinal
117
     */
118
    public function testFinCodeFromBits($fin, $rsv1, $rsv2, $rsv3, $opcode, $bin) {
119
        $this->_frame->addBuffer(static::encode($bin));
120
        $this->assertEquals($fin, $this->_frame->isFinal());
121
    }
122
 
123
    /**
124
     * @dataProvider firstByteProvider
125
     * covers Ratchet\RFC6455\Messaging\Frame::getRsv1
126
     * covers Ratchet\RFC6455\Messaging\Frame::getRsv2
127
     * covers Ratchet\RFC6455\Messaging\Frame::getRsv3
128
     */
129
    public function testGetRsvFromBits($fin, $rsv1, $rsv2, $rsv3, $opcode, $bin) {
130
        $this->_frame->addBuffer(static::encode($bin));
131
        $this->assertEquals($rsv1, $this->_frame->getRsv1());
132
        $this->assertEquals($rsv2, $this->_frame->getRsv2());
133
        $this->assertEquals($rsv3, $this->_frame->getRsv3());
134
    }
135
 
136
    /**
137
     * @dataProvider firstByteProvider
138
     * covers Ratchet\RFC6455\Messaging\Frame::getOpcode
139
     */
140
    public function testOpcodeFromBits($fin, $rsv1, $rsv2, $rsv3, $opcode, $bin) {
141
        $this->_frame->addBuffer(static::encode($bin));
142
        $this->assertEquals($opcode, $this->_frame->getOpcode());
143
    }
144
 
145
    /**
146
     * @dataProvider UnframeMessageProvider
147
     * covers Ratchet\RFC6455\Messaging\Frame::isFinal
148
     */
149
    public function testFinCodeFromFullMessage($msg, $encoded) {
150
        $this->_frame->addBuffer(base64_decode($encoded));
151
        $this->assertTrue($this->_frame->isFinal());
152
    }
153
 
154
    /**
155
     * @dataProvider UnframeMessageProvider
156
     * covers Ratchet\RFC6455\Messaging\Frame::getOpcode
157
     */
158
    public function testOpcodeFromFullMessage($msg, $encoded) {
159
        $this->_frame->addBuffer(base64_decode($encoded));
160
        $this->assertEquals(1, $this->_frame->getOpcode());
161
    }
162
 
163
    public static function payloadLengthDescriptionProvider() {
164
        return array(
165
            array(7,  '01110101'),
166
            array(7,  '01111101'),
167
            array(23, '01111110'),
168
            array(71, '01111111'),
169
            array(7,  '00000000'), // Should this throw an exception?  Can a payload be empty?
170
            array(7,  '00000001')
171
        );
172
    }
173
 
174
    /**
175
     * @dataProvider payloadLengthDescriptionProvider
176
     * covers Ratchet\RFC6455\Messaging\Frame::addBuffer
177
     * covers Ratchet\RFC6455\Messaging\Frame::getFirstPayloadVal
178
     */
179
    public function testFirstPayloadDesignationValue($bits, $bin) {
180
        $this->_frame->addBuffer(static::encode($this->_firstByteFinText));
181
        $this->_frame->addBuffer(static::encode($bin));
182
        $ref = new \ReflectionClass($this->_frame);
183
        $cb  = $ref->getMethod('getFirstPayloadVal');
184
        $cb->setAccessible(true);
185
        $this->assertEquals(bindec($bin), $cb->invoke($this->_frame));
186
    }
187
 
188
    /**
189
     * covers Ratchet\RFC6455\Messaging\Frame::getFirstPayloadVal
190
     */
191
    public function testFirstPayloadValUnderflow() {
192
        $ref = new \ReflectionClass($this->_frame);
193
        $cb  = $ref->getMethod('getFirstPayloadVal');
194
        $cb->setAccessible(true);
195
        $this->setExpectedException('UnderflowException');
196
        $cb->invoke($this->_frame);
197
    }
198
 
199
    /**
200
     * @dataProvider payloadLengthDescriptionProvider
201
     * covers Ratchet\RFC6455\Messaging\Frame::getNumPayloadBits
202
     */
203
    public function testDetermineHowManyBitsAreUsedToDescribePayload($expected_bits, $bin) {
204
        $this->_frame->addBuffer(static::encode($this->_firstByteFinText));
205
        $this->_frame->addBuffer(static::encode($bin));
206
        $ref = new \ReflectionClass($this->_frame);
207
        $cb  = $ref->getMethod('getNumPayloadBits');
208
        $cb->setAccessible(true);
209
        $this->assertEquals($expected_bits, $cb->invoke($this->_frame));
210
    }
211
 
212
    /**
213
     * covers Ratchet\RFC6455\Messaging\Frame::getNumPayloadBits
214
     */
215
    public function testgetNumPayloadBitsUnderflow() {
216
        $ref = new \ReflectionClass($this->_frame);
217
        $cb  = $ref->getMethod('getNumPayloadBits');
218
        $cb->setAccessible(true);
219
        $this->setExpectedException('UnderflowException');
220
        $cb->invoke($this->_frame);
221
    }
222
 
223
    public function secondByteProvider() {
224
        return array(
225
            array(true,   1, '10000001'),
226
            array(false,  1, '00000001'),
227
            array(true, 125, $this->_secondByteMaskedSPL)
228
        );
229
    }
230
    /**
231
     * @dataProvider secondByteProvider
232
     * covers Ratchet\RFC6455\Messaging\Frame::isMasked
233
     */
234
    public function testIsMaskedReturnsExpectedValue($masked, $payload_length, $bin) {
235
        $this->_frame->addBuffer(static::encode($this->_firstByteFinText));
236
        $this->_frame->addBuffer(static::encode($bin));
237
        $this->assertEquals($masked, $this->_frame->isMasked());
238
    }
239
 
240
    /**
241
     * @dataProvider UnframeMessageProvider
242
     * covers Ratchet\RFC6455\Messaging\Frame::isMasked
243
     */
244
    public function testIsMaskedFromFullMessage($msg, $encoded) {
245
        $this->_frame->addBuffer(base64_decode($encoded));
246
        $this->assertTrue($this->_frame->isMasked());
247
    }
248
 
249
    /**
250
     * @dataProvider secondByteProvider
251
     * covers Ratchet\RFC6455\Messaging\Frame::getPayloadLength
252
     */
253
    public function testGetPayloadLengthWhenOnlyFirstFrameIsUsed($masked, $payload_length, $bin) {
254
        $this->_frame->addBuffer(static::encode($this->_firstByteFinText));
255
        $this->_frame->addBuffer(static::encode($bin));
256
        $this->assertEquals($payload_length, $this->_frame->getPayloadLength());
257
    }
258
 
259
    /**
260
     * @dataProvider UnframeMessageProvider
261
     * covers Ratchet\RFC6455\Messaging\Frame::getPayloadLength
262
     * @todo Not yet testing when second additional payload length descriptor
263
     */
264
    public function testGetPayloadLengthFromFullMessage($msg, $encoded) {
265
        $this->_frame->addBuffer(base64_decode($encoded));
266
        $this->assertEquals(strlen($msg), $this->_frame->getPayloadLength());
267
    }
268
 
269
    public function maskingKeyProvider() {
270
        $frame = new Frame;
271
        return array(
272
            array($frame->generateMaskingKey()),
273
            array($frame->generateMaskingKey()),
274
            array($frame->generateMaskingKey())
275
        );
276
    }
277
 
278
    /**
279
     * @dataProvider maskingKeyProvider
280
     * covers Ratchet\RFC6455\Messaging\Frame::getMaskingKey
281
     * @todo I I wrote the dataProvider incorrectly, skipping for now
282
     */
283
    public function testGetMaskingKey($mask) {
284
        $this->_frame->addBuffer(static::encode($this->_firstByteFinText));
285
        $this->_frame->addBuffer(static::encode($this->_secondByteMaskedSPL));
286
        $this->_frame->addBuffer($mask);
287
        $this->assertEquals($mask, $this->_frame->getMaskingKey());
288
    }
289
 
290
    /**
291
     * covers Ratchet\RFC6455\Messaging\Frame::getMaskingKey
292
     */
293
    public function testGetMaskingKeyOnUnmaskedPayload() {
294
        $frame = new Frame('Hello World!');
295
        $this->assertEquals('', $frame->getMaskingKey());
296
    }
297
 
298
    /**
299
     * @dataProvider UnframeMessageProvider
300
     * covers Ratchet\RFC6455\Messaging\Frame::getPayload
301
     * @todo Move this test to bottom as it requires all methods of the class
302
     */
303
    public function testUnframeFullMessage($unframed, $base_framed) {
304
        $this->_frame->addBuffer(base64_decode($base_framed));
305
        $this->assertEquals($unframed, $this->_frame->getPayload());
306
    }
307
 
308
    public static function messageFragmentProvider() {
309
        return array(
310
            array(false, '', '', '', '', '')
311
        );
312
    }
313
 
314
    /**
315
     * @dataProvider UnframeMessageProvider
316
     * covers Ratchet\RFC6455\Messaging\Frame::getPayload
317
     */
318
    public function testCheckPiecingTogetherMessage($msg, $encoded) {
319
        $framed = base64_decode($encoded);
320
        for ($i = 0, $len = strlen($framed);$i < $len; $i++) {
321
            $this->_frame->addBuffer(substr($framed, $i, 1));
322
        }
323
        $this->assertEquals($msg, $this->_frame->getPayload());
324
    }
325
 
326
    /**
327
     * covers Ratchet\RFC6455\Messaging\Frame::__construct
328
     * covers Ratchet\RFC6455\Messaging\Frame::getPayloadLength
329
     * covers Ratchet\RFC6455\Messaging\Frame::getPayload
330
     */
331
    public function testLongCreate() {
332
        $len = 65525;
333
        $pl  = $this->generateRandomString($len);
334
        $frame = new Frame($pl, true, Frame::OP_PING);
335
        $this->assertTrue($frame->isFinal());
336
        $this->assertEquals(Frame::OP_PING, $frame->getOpcode());
337
        $this->assertFalse($frame->isMasked());
338
        $this->assertEquals($len, $frame->getPayloadLength());
339
        $this->assertEquals($pl, $frame->getPayload());
340
    }
341
 
342
    /**
343
     * covers Ratchet\RFC6455\Messaging\Frame::__construct
344
     * covers Ratchet\RFC6455\Messaging\Frame::getPayloadLength
345
     */
346
    public function testReallyLongCreate() {
347
        $len = 65575;
348
        $frame = new Frame($this->generateRandomString($len));
349
        $this->assertEquals($len, $frame->getPayloadLength());
350
    }
351
    /**
352
     * covers Ratchet\RFC6455\Messaging\Frame::__construct
353
     * covers Ratchet\RFC6455\Messaging\Frame::extractOverflow
354
     */
355
    public function testExtractOverflow() {
356
        $string1 = $this->generateRandomString();
357
        $frame1  = new Frame($string1);
358
        $string2 = $this->generateRandomString();
359
        $frame2  = new Frame($string2);
360
        $cat = new Frame;
361
        $cat->addBuffer($frame1->getContents() . $frame2->getContents());
362
        $this->assertEquals($frame1->getContents(), $cat->getContents());
363
        $this->assertEquals($string1, $cat->getPayload());
364
        $uncat = new Frame;
365
        $uncat->addBuffer($cat->extractOverflow());
366
        $this->assertEquals($string1, $cat->getPayload());
367
        $this->assertEquals($string2, $uncat->getPayload());
368
    }
369
 
370
    /**
371
     * covers Ratchet\RFC6455\Messaging\Frame::extractOverflow
372
     */
373
    public function testEmptyExtractOverflow() {
374
        $string = $this->generateRandomString();
375
        $frame  = new Frame($string);
376
        $this->assertEquals($string, $frame->getPayload());
377
        $this->assertEquals('', $frame->extractOverflow());
378
        $this->assertEquals($string, $frame->getPayload());
379
    }
380
 
381
    /**
382
     * covers Ratchet\RFC6455\Messaging\Frame::getContents
383
     */
384
    public function testGetContents() {
385
        $msg = 'The quick brown fox jumps over the lazy dog.';
386
        $frame1 = new Frame($msg);
387
        $frame2 = new Frame($msg);
388
        $frame2->maskPayload();
389
        $this->assertNotEquals($frame1->getContents(), $frame2->getContents());
390
        $this->assertEquals(strlen($frame1->getContents()) + 4, strlen($frame2->getContents()));
391
    }
392
 
393
    /**
394
     * covers Ratchet\RFC6455\Messaging\Frame::maskPayload
395
     */
396
    public function testMasking() {
397
        $msg   = 'The quick brown fox jumps over the lazy dog.';
398
        $frame = new Frame($msg);
399
        $frame->maskPayload();
400
        $this->assertTrue($frame->isMasked());
401
        $this->assertEquals($msg, $frame->getPayload());
402
    }
403
 
404
    /**
405
     * covers Ratchet\RFC6455\Messaging\Frame::unMaskPayload
406
     */
407
    public function testUnMaskPayload() {
408
        $string = $this->generateRandomString();
409
        $frame  = new Frame($string);
410
        $frame->maskPayload()->unMaskPayload();
411
        $this->assertFalse($frame->isMasked());
412
        $this->assertEquals($string, $frame->getPayload());
413
    }
414
 
415
    /**
416
     * covers Ratchet\RFC6455\Messaging\Frame::generateMaskingKey
417
     */
418
    public function testGenerateMaskingKey() {
419
        $dupe = false;
420
        $done = array();
421
        for ($i = 0; $i < 10; $i++) {
422
            $new = $this->_frame->generateMaskingKey();
423
            if (in_array($new, $done)) {
424
                $dupe = true;
425
            }
426
            $done[] = $new;
427
        }
428
        $this->assertEquals(4, strlen($new));
429
        $this->assertFalse($dupe);
430
    }
431
 
432
    /**
433
     * covers Ratchet\RFC6455\Messaging\Frame::maskPayload
434
     */
435
    public function testGivenMaskIsValid() {
436
        $this->setExpectedException('InvalidArgumentException');
437
        $this->_frame->maskPayload('hello world');
438
    }
439
 
440
    /**
441
     * covers Ratchet\RFC6455\Messaging\Frame::maskPayload
442
     */
443
    public function testGivenMaskIsValidAscii() {
444
        if (!extension_loaded('mbstring')) {
445
            $this->markTestSkipped("mbstring required for this test");
446
            return;
447
        }
448
        $this->setExpectedException('OutOfBoundsException');
449
        $this->_frame->maskPayload('x✖');
450
    }
451
 
452
    protected function generateRandomString($length = 10, $addSpaces = true, $addNumbers = true) {
453
        $characters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"$%&/()=[]{}'; // ยง
454
        $useChars = array();
455
        for($i = 0; $i < $length; $i++) {
456
            $useChars[] = $characters[mt_rand(0, strlen($characters) - 1)];
457
        }
458
        if($addSpaces === true) {
459
            array_push($useChars, ' ', ' ', ' ', ' ', ' ', ' ');
460
        }
461
        if($addNumbers === true) {
462
            array_push($useChars, rand(0, 9), rand(0, 9), rand(0, 9));
463
        }
464
        shuffle($useChars);
465
        $randomString = trim(implode('', $useChars));
466
        $randomString = substr($randomString, 0, $length);
467
        return $randomString;
468
    }
469
 
470
    /**
471
     * There was a frame boundary issue when the first 3 bytes of a frame with a payload greater than
472
     * 126 was added to the frame buffer and then Frame::getPayloadLength was called. It would cause the frame
473
     * to set the payload length to 126 and then not recalculate it once the full length information was available.
474
     *
475
     * This is fixed by setting the defPayLen back to -1 before the underflow exception is thrown.
476
     *
477
     * covers Ratchet\RFC6455\Messaging\Frame::getPayloadLength
478
     * covers Ratchet\RFC6455\Messaging\Frame::extractOverflow
479
     */
480
    public function testFrameDeliveredOneByteAtATime() {
481
        $startHeader = "\x01\x7e\x01\x00"; // header for a text frame of 256 - non-final
482
        $framePayload = str_repeat("*", 256);
483
        $rawOverflow = "xyz";
484
        $rawFrame = $startHeader . $framePayload . $rawOverflow;
485
        $frame = new Frame();
486
        $payloadLen = 256;
487
        for ($i = 0; $i < strlen($rawFrame); $i++) {
488
            $frame->addBuffer($rawFrame[$i]);
489
            try {
490
                // payloadLen will
491
                $payloadLen = $frame->getPayloadLength();
492
            } catch (\UnderflowException $e) {
493
                if ($i > 2) { // we should get an underflow on 0,1,2
494
                    $this->fail("Underflow exception when the frame length should be available");
495
                }
496
            }
497
            if ($payloadLen !== 256) {
498
                $this->fail("Payload length of " . $payloadLen . " should have been 256.");
499
            }
500
        }
501
        // make sure the overflow is good
502
        $this->assertEquals($rawOverflow, $frame->extractOverflow());
503
    }
504
}