romp 0.5.2

STOMP server and WebSockets platform
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675

use log::*;
use log::Level::Debug;

/// Unfuck a WebSockets stream. Websockets, instead of being a simple byte stream, have pointless framing and
/// the inputs stream is XORed with a key specifically to break proxies that don't understand the protocol.
/// This serves no purpose, it simply complicates matters, since proxies are borken anyway and [0,0,0,0] is a valid key.
/// Since the protocol only adds noise and never compresses data we can parse the incoming bytes and convert the input
/// buffer back to normal plain text STOMP protocol.
///
/// see https://tools.ietf.org/html/rfc6455
///
/// Code ported from xtomp

const MAX_FRAME_LEN: i64 = 1048576;  // 1 Mb;

/// Websockets parser state.  There are a few bytes header at the start of each frame, since these are discarded from the byte stream
/// this struct maintains their state, its reusable, as a frame is finished the struct can be zeroed.
#[derive(Debug)]
pub struct WsState {
    opcode: u8,
    masking_key: [u8; 4],
    frame_len: usize,
    frame_pos: usize,
    frame_state: WsParserState,
    frame_len_idx: u8,
    frame_len_data: i64, // MUST be 64 bits for we parsing to work value is always < MAX_FRAME_LENGTH
    masking_key_len_idx: usize,
}

impl WsState {
    pub fn new() -> WsState {
        WsState {
            opcode: 0,
            masking_key: [0; 4],
            frame_len: 0,
            frame_pos: 0,
            frame_state: WsParserState::SwStart,
            frame_len_idx: 0,
            frame_len_data: 0,
            masking_key_len_idx: 0,
        }
    }

    pub fn frame_len(&self) -> usize {
        self.frame_len
    }

    fn reset(&mut self) {
        // Rust manages to be more verbose than C, memset(ws, 0, sizeof(ws));
        self.opcode = 0;
        self.masking_key = [0; 4];
        self.frame_len = 0;
        self.frame_pos = 0;
        self.frame_state = WsParserState::SwStart;
        self.frame_len_idx = 0;
        self.frame_len_data = 0;
        self.masking_key_len_idx = 0;
    }

}

/// skip over bytes by moving data backwards
fn ws_skip(buffer: &mut [u8], pos: usize, end: usize, num: usize) {
    //debug!("before ws_skip({:?}), pos={}, num={}", String::from_utf8_lossy(&buffer), pos, num);
    buffer.copy_within(pos..end, pos - num);
    //debug!("after ws_skip({:?})", String::from_utf8_lossy(&buffer));
}

/// do the XOR part
/// // returns the position at the end of unmunged data or frame end
fn ws_demunge_buffer(ws: &mut WsState, buffer: &mut [u8], pos: usize, end: usize) -> usize {
    // TODO XOR the data in 64bit chunks will probably be faster. Rust support overloading ^
    let mut p = pos;
    while p < end && ws.frame_pos < ws.frame_len {
        buffer[p] = buffer[p] ^ ws.masking_key[ws.frame_pos % 4];
        p += 1;
        ws.frame_pos += 1;
    }
    return p;
}

/// unmunge one frames worth of data, or all that is available now.
/// @return number of bytes unmunged, or error if we don't like what we see when its unmunged
/// This method enforces one frame per STOMP message, which is not strictly necessary
fn ws_demunge_frame(ws: &mut WsState, buffer: &mut [u8], pos: usize, end: usize, frame_lengths: &mut Box<Vec<usize>>) -> Result<usize, ()> {
    //debug!("ws_demunge_frame({:?}, {}, {}), frame_pos={}, frame_len={} masking_key={:?}", String::from_utf8_lossy(&buffer[pos..end]), pos, end, ws.frame_pos, ws.frame_len, ws.masking_key.as_ref());

    let p = ws_demunge_buffer(ws, buffer, pos, end);

    // if we reached the end of a frame
    if ws.frame_pos == ws.frame_len {
        // if this is a FIN text frame
        if ws.opcode == 0x81  {
            // if we did process at least one char (so p-1 is valid)
            // and the end char of the ws frame is not '\0', i.e a valid STOMP frame, or '\n' a heart-beat
            // fail fast.
            if p != pos {
                if ws.frame_len == 1 && buffer[p - 1] == b'\n' {
                    // heart beat
                    debug!("ws ecg");
                }
                else if buffer[p - 1] != b'\0' {
                    warn!("ws not a stomp message, ends with '{}'", buffer[p - 1]);
                    if log_enabled!(Debug) {
                        debug!("ws not a stomp message buffer={:?} {:?}", buffer, String::from_utf8_lossy(&buffer[pos..end]));
                    }
                    return Err(());
                }
                else {
                    frame_lengths.push(ws.frame_len);
                }
            }
        }
    }

    return Ok(p - pos);
}


#[derive(Debug, Clone, PartialEq)]
pub enum WsParserState {
    SwStart = 0,
    SwPayloadLen1,
    SwPayloadLenExt,
    SwMaskingKey,
    SwPayload,
    SwPayloadSkip
}

/// We are passed the buffer containing the data from the network, with pos and end marking new data just arrived.
/// This method hacks at the data buffer "unmunging" it and returns new pos and end, the buffer between pos and end will have usually
/// had its data modified and/or skipped over.
/// `buffer` may contain nothing, partial frames, a whole frame or more than one frame, all of it must be processed in one call
// TODO This code is ported directly from xtomp, a tokio Decoder might be more appropriate?
pub fn ws_demunge(ws: &mut WsState, buffer: &mut Box<[u8]>, pos: usize, mut end: usize, mut frame_lengths: &mut Box<Vec<usize>>) -> Result<(usize, usize), ()> {
    //debug!("ws_demunge() pos={}, end={}", pos, end);

    // flags to detect that one whole frame is delivered and nothing more
    let mut is_single_frame = 0;
    let mut p: usize;
    let mut ch: u8;
    let mut opcode: u8;
    let mut skipped: usize; // number of ws frame info bytes read
    let mut processed: usize;

    let mut state = ws.frame_state.clone();
    skipped = 0;

    if pos == 0 {
        is_single_frame += 1;
    }

    p = pos;
    while p < end { // TODO off by one? should it be <= ?
        ch = buffer[p];

        match state {

            //* REQUEST command */
            WsParserState::SwStart => {
                ws.reset();
                ws.opcode = ch;

                // FIN bit set
                if (ch & 0x80) == 0x80 {
                    is_single_frame += 1;
                }
                state = WsParserState::SwPayloadLen1;

                skipped += 1;
                p += 1;
                continue;
            },
            WsParserState::SwPayloadLen1 => {
                if (ch & 0x80) != 0x80 {
                    warn!("BUG client data must be masked");
                    return Err(());
                }

                if (ch & 0x7f) == 126 {
                    ws.frame_len_idx = 2;
                    state = WsParserState::SwPayloadLenExt;
                } else if (ch & 0x7f) == 127 {
                    ws.frame_len_idx = 8;
                    state = WsParserState::SwPayloadLenExt;
                } else {
                    ws.frame_len = (ch & 0x7f) as usize;
                    state = WsParserState::SwMaskingKey;
                }

                skipped += 1;
                p += 1;
                continue;
            },
            WsParserState::SwPayloadLenExt => {
                // shift 64bit arriving as u8 bytes
                ws.frame_len_idx -= 1;
                ws.frame_len_data |= (ch as i64) << (8 * ws.frame_len_idx) as i64;

                if ws.frame_len_idx == 0 {
                    if ws.frame_len_data < 0 || ws.frame_len_data > MAX_FRAME_LEN {
                        debug!("ws frame flup {}", ws.frame_len_data);
                        return Err(());
                    }
                    ws.frame_len = ws.frame_len_data as usize;
                    state = WsParserState::SwMaskingKey;
                }

                skipped += 1;
                p += 1;

                continue;
            },
            WsParserState::SwMaskingKey => {
                ws.masking_key[ws.masking_key_len_idx] = ch;
                ws.masking_key_len_idx += 1;

                if ws.masking_key_len_idx == 4 {
                    state = WsParserState::SwPayload;
                }

                skipped += 1;
                p += 1;
                continue;
            },
            WsParserState::SwPayload => {

                // TODO why do this here?
                if ws.frame_len > MAX_FRAME_LEN as usize {
                    debug!("ws frame flup {}", ws.frame_len);
                    return Err(());
                }

                opcode = ws.opcode & 0x0f;
                if opcode == 0x08 {
                    // connection close
                    warn!("ws connection close");
                    return Err(());
                }
                if opcode == 0x02 {
                    // binary not supported
                    warn!("ws binary not supported");
                    return Err(());
                }

                match ws_demunge_frame(ws, buffer, p, end, &mut frame_lengths) {
                    Ok(sz) => {
                        processed = sz;
                    },
                    Err(_) => {
                        warn!("ws_demunge_frame() err");
                        return Err(());
                    }
                }

                if opcode == 0x09 || opcode == 0x0a {
                    // per stackoverflow https://stackoverflow.com/questions/10585355/sending-websocket-ping-pong-frame-from-browser
                    // clients dont send pings, so we dont write complicated code to handle them
                    // however just in case, we will skip over them.
                    debug!("unhandled ws ping");
                }

                // any unsupported opcodes, skip over.
                if opcode > 0x02 {
                    debug!("unknown opcode skip processed data");
                    // shift buffer erasing ws junk
                    ws_skip(buffer, p + processed, end, processed + skipped);
                    p -= skipped;
                    end -= processed + skipped;
                    //debug!("p={}, skipped={} ", p, skipped);
                    skipped = 0;
                    is_single_frame = 0;
                    if ws.frame_pos == ws.frame_len {
                        state = WsParserState::SwStart;
                    }
                    else if ws.frame_pos < ws.frame_len {
                        state = WsParserState::SwPayloadSkip;
                    }
                    else {
                        error!("BUG overread ws buf");
                        return Err(());
                    }
                    continue;
                }

                // now we have read all of the ws header and it can be discarded

                if is_single_frame == 2 && buffer.len() - pos - skipped == ws.frame_len {
                    // optimization: instead of moving bytes in the buffer, just shift pos, can only do this when we have exactly one frame
                    // just one frame, is the most common case.
                    ws.frame_state = WsParserState::SwStart;
                    return Ok((pos + skipped, end));
                }
                else {
                    // shift buffer erasing ws junk
                    //debug!("skipping header");
                    ws_skip(buffer, p, end, skipped);
                    p = p - skipped + processed;
                    end -= skipped;
                    skipped = 0;
                    is_single_frame = 0;
                    if ws.frame_pos == ws.frame_len {
                        state = WsParserState::SwStart;
                    }
                    //debug!("looping {} {}", p, end);
                    continue;
                }

            },
            WsParserState::SwPayloadSkip => {
                match ws_demunge_frame(ws, buffer, p, end, &mut frame_lengths) {
                    Ok(sz) => {
                        processed = sz;
                    },
                    Err(_) => {
                        warn!("ws_demunge_frame() error");
                        return Err(());
                    }
                }
                ws_skip(buffer, p, end, processed);
                p = p - processed;
                end -= processed;
                if ws.frame_pos == ws.frame_len {
                    state = WsParserState::SwStart;
                }
                continue;
            }
        }

    }

    ws.frame_state = state;

    if p == pos {
        // did not read anything but everything is OK
        return Ok((pos, end));
    }

    return Ok((pos + skipped, end));

}


#[cfg(test)]
mod tests {
    use super::*;

    const LEN: usize = 20;

    fn setup_frame_hdr(buffer: &mut Box<[u8]>, len: usize) {
        // opcodes (FIN and text data)
        buffer[0] = 0x81;
        // payload len
        buffer[1] = len as u8 | 0x80; // less than 126
        // noop mask
        buffer[2] = 0x00;
        buffer[3] = 0x00;
        buffer[4] = 0x00;
        buffer[5] = 0x00;
    }

    fn setup_frame_data(buffer: &mut Box<[u8]>) {
        // mocked data "ghijklmnopqrstuvwxy\0" (last digit is left as \0 to appear like a STOMP frame)
        let mut p = 6;
        while p < LEN + 5 {
            buffer[p] = b'a' + p as u8;
            p += 1;
        }
    }

    fn set_mask(buffer: &mut Box<[u8]>) {
        buffer[2] = 1;
        buffer[3] = 2;
        buffer[4] = 3;
        buffer[5] = 4;
    }

    fn mask_frame_data(buffer: &mut Box<[u8]>) {
        let mut p = 6;
        let mut m = 1;
        while p < LEN + 6 {
            buffer[p] = buffer[p] ^ m;
            p += 1;
            m += 1;
            if m > 4 {
                m = 1;
            }
        }
    }

    #[test]
    fn test_happy_noop_mask() {
        //config::log::init_log4rs();

        let mut frame_lengths = Box::new(vec![]);

        let mut buffer: Box<[u8]> = Box::new([0; 6 + LEN]);
        let pos: usize = 0;
        let end = buffer.len();

        setup_frame_hdr(&mut buffer, LEN);
        setup_frame_data(&mut buffer);

        let mut ws = WsState::new();

        match ws_demunge(&mut ws, &mut buffer, pos, end, &mut frame_lengths) {
            Ok((new_pos, new_end)) => {
                println!("pos={}, end={} buffer={:?}", new_pos, new_end, String::from_utf8_lossy(&buffer[new_pos..new_end]));
                // pos is shifted 6 forward since this was only one whole frame, buffer is not changed and pos is adjusted
                assert_eq!(pos + 6, new_pos, "new_pos test");
                assert_eq!(LEN, new_end - new_pos, "len test");
                assert_eq!(b"ghijklmnopqrstuvwxy\0", &buffer[new_pos..new_end]);
            },
            _ => panic!("parse error"),
        }

        // reset the test data
        setup_frame_hdr(&mut buffer, LEN);
        setup_frame_data(&mut buffer);

        // arbitrary chop point  16

        let mut buffer1: Box<[u8]> = Box::new([0; 16]);
        buffer1.copy_from_slice(&buffer[0..16]);
        let pos1 = 0;
        let end1 = buffer1.len();

        let mut buffer2: Box<[u8]> = Box::new([0; 10]);
        buffer2.copy_from_slice(&buffer[16..26]);
        let pos2 = 0;
        let end2 = buffer2.len();

        match ws_demunge(&mut ws, &mut buffer1, pos1, end1, &mut frame_lengths) {
            Ok((new_pos, new_end)) => {
                // pos is left at 0, the data in buffer is shifted forward
                assert_eq!(0, new_pos);
                assert_eq!(10, new_end);
            },
            _ => panic!("parse error"),
        }
        match ws_demunge(&mut ws, &mut buffer2, pos2, end2, &mut frame_lengths) {
            Ok((new_pos, new_end)) => {
                // buffer not changed at all
                println!("new_pos={}, new_end={} buffer={:?}", new_pos, new_end, String::from_utf8_lossy(&buffer2[0..10]));
                println!("WsState {:?}", ws)
            },
            _ => panic!("parse error"),
        }

        // lots of different chop points, just check it does not panic, TODO assert logic

        setup_frame_hdr(&mut buffer, LEN);
        setup_frame_data(&mut buffer);
        re_test(&mut ws, buffer.clone(), 0, 26, 1);

        setup_frame_hdr(&mut buffer, LEN);
        setup_frame_data(&mut buffer);
        re_test(&mut ws, buffer.clone(), 0, 26, 2);

        setup_frame_hdr(&mut buffer, LEN);
        setup_frame_data(&mut buffer);
        re_test(&mut ws, buffer.clone(), 0, 26, 3);

        setup_frame_hdr(&mut buffer, LEN);
        setup_frame_data(&mut buffer);
        re_test(&mut ws, buffer.clone(), 0, 26, 4);

        setup_frame_hdr(&mut buffer, LEN);
        setup_frame_data(&mut buffer);
        re_test(&mut ws, buffer.clone(), 0, 26, 5);

        setup_frame_hdr(&mut buffer, LEN);
        setup_frame_data(&mut buffer);
        re_test(&mut ws, buffer.clone(), 0, 26, 6);

        setup_frame_hdr(&mut buffer, LEN);
        setup_frame_data(&mut buffer);
        re_test(&mut ws, buffer.clone(), 0, 26, 7);

        setup_frame_hdr(&mut buffer, LEN);
        setup_frame_data(&mut buffer);
        re_test(&mut ws, buffer.clone(), 0, 26, 8);
    }

    fn re_test(mut ws: &mut WsState, buffer: Box<[u8]>, _pos: usize, _len: usize, split_at: usize) {

        let mut frame_lengths = Box::new(vec![]);

        let mut buffer1: Box<[u8]> = vec![0; split_at].into_boxed_slice();
        buffer1.copy_from_slice(&buffer[0..split_at]);
        let pos1 = 0;
        let end1 = buffer1.len();

        let mut buffer2: Box<[u8]> = vec![0; buffer.len() - split_at].into_boxed_slice();
        buffer2.copy_from_slice(&buffer[split_at..buffer.len()]);
        let pos2 = 0;
        let end2 = buffer2.len();

        match ws_demunge(&mut ws, &mut buffer1, pos1, end1, &mut frame_lengths) {
            Ok((_new_pos, _new_end)) => {
            },
            _ => panic!("parse error"),
        }
        match ws_demunge(&mut ws, &mut buffer2, pos2, end2, &mut frame_lengths) {
            Ok((_new_pos, _new_end)) => {
            },
            _ => panic!("parse error"),
        }
    }


    #[test]
    fn test_happy_with_mask() {
        //config::log::init_log4rs();

        let mut frame_lengths = Box::new(vec![]);

        let mut buffer: Box<[u8]> = Box::new([0; 6 + LEN]);
        let pos: usize = 0;
        let end = buffer.len();

        setup_frame_hdr(&mut buffer, LEN);
        setup_frame_data(&mut buffer);
        set_mask(&mut buffer);
        mask_frame_data(&mut buffer);

        let mut ws = WsState::new();

        match ws_demunge(&mut ws, &mut buffer, pos, end, &mut frame_lengths) {
            Ok((new_pos, new_end)) => {
                println!("pos={}, end={} buffer={:?}", new_pos, new_end, String::from_utf8_lossy(&buffer[new_pos..new_end]));
                assert_eq!(LEN, new_end - new_pos, "len test");
                assert_eq!(pos + 6, new_pos, "new_pos test");
                assert_eq!(b"ghijklmnopqrstuvwxy\0", &buffer[new_pos..new_end]);
            },
            _ => panic!("parse error"),
        }
    }

    #[test]
    fn test_skip_ping_frames() {
        //config::log::init_log4rs();

        let mut frame_lengths = Box::new(vec![]);

        let mut buffer: Box<[u8]> = Box::new([0; 6 + 4 + 8 + 6 + 16]);
        let pos: usize = 0;
        let end = buffer.len();

        let prefix = "CONN";
        setup_frame_hdr(&mut buffer, prefix.len());
        // hack initial frame to be non final
        buffer[0] = 0x01;
        buffer[6..6+4].copy_from_slice(prefix.as_bytes());

        // ping frame
        buffer[10] = 0x89; // FIN + op 9 ping
        buffer[11] = 0x82; // mask plus len 2
        buffer[12] = 0x00; // noop mask
        buffer[13] = 0x00;
        buffer[14] = 0x00;
        buffer[15] = 0x00;
        buffer[16] = 0xaa; // 2 bytes data
        buffer[17] = 0xbb;

        // next frame
        let suffix = "ECT\n1234:1234\n\n\0";
        // opcodes (continuation frame and text data)
        buffer[18] = 0x80;
        // payload len
        buffer[19] = suffix.len() as u8 | 0x80; // less than 126
        // noop mask
        buffer[20] = 0x00;
        buffer[21] = 0x00;
        buffer[22] = 0x00;
        buffer[23] = 0x00;
        buffer[24..end].copy_from_slice(suffix.as_bytes());

        let mut ws = WsState::new();

        match ws_demunge(&mut ws, &mut buffer, pos, end, &mut frame_lengths) {
            Ok((new_pos, new_end)) => {
                println!("pos={}, end={} buffer={:?}", new_pos, new_end, String::from_utf8_lossy(&buffer[new_pos..new_end]));
                assert_eq!(prefix.len() + suffix.len(), new_end - new_pos, "len test");
                assert_eq!(0, new_pos, "new_pos test");
            },
            _ => panic!("parse error"),
        }
    }

    #[test]
    pub fn test_mask() {
        if (0xaa & 0x80) != 0x80 {
            panic!("here");
        }
        println!("OK");
    }

    // real data from a FF packet this sends JUST the frame header and zero STONP µessage data.
    #[test]
    pub fn test_firefox_media_header() {
        let mut frame_lengths = Box::new(vec![]);
        let mut ws = WsState::new();
        let mut buffer: Box<[u8]> = Box::new([0; 14]);
        buffer[0] = 129; // 1 0 0 0  0 0 0 1  =  0x81 fin text
        buffer[1] = 255; // 1 1 1 1  1 1 1 1  = mask + 127 len
        buffer[2] = 0;
        buffer[3] = 0;
        buffer[4] = 0;
        buffer[5] = 0;
        buffer[6] = 0;
        buffer[7] = 6;   // 0000 0110
        buffer[8] = 253; //
        buffer[9] = 129; //
        buffer[10] = 212;  // key
        buffer[11] = 137;  // key
        buffer[12] = 3;    // key
        buffer[13] = 53;   // key

        match ws_demunge(&mut ws, &mut buffer, 0, 14, &mut frame_lengths) {
            Ok((new_pos, new_end)) => {
                println!("pos={}, end={}, state={:?}", new_pos, new_end, ws.frame_state);
                assert_eq!(new_pos, 14); // read all the frame data but no STOMP data
                assert_eq!(new_end, 14);
                assert_eq!(ws.frame_len, 458113);
            },
            _ => panic!("parse error"),
        }
    }

    // same test but imagine we only got opart of the frame header
    #[test]
    pub fn test_firefox_media_header_partial() {
        let mut frame_lengths = Box::new(vec![]);
        let mut ws = WsState::new();
        let mut buffer: Box<[u8]> = Box::new([0; 7]);
        buffer[0] = 129; // 1 0 0 0  0 0 0 1  =  0x81 fin text
        buffer[1] = 255; // 1 1 1 1  1 1 1 1  = mask + 127 len
        buffer[2] = 0;
        buffer[3] = 0;
        buffer[4] = 0;
        buffer[5] = 0;
        buffer[6] = 0;

        match ws_demunge(&mut ws, &mut buffer, 0, 7, &mut frame_lengths) {
            Ok((new_pos, new_end)) => {
                println!("pos={}, end={}, state={:?}", new_pos, new_end, ws.frame_state);
                assert_eq!(new_pos, 7); // read all the frame data but no STOMP data
                assert_eq!(new_end, 7);
            },
            _ => panic!("parse error"),
        }
        // no read lets read the rest
        let mut buffer: Box<[u8]> = Box::new([0; 14]);
        buffer[7] = 6;   // 0000 0110
        buffer[8] = 253; //
        buffer[9] = 129; //
        buffer[10] = 212;  // key
        buffer[11] = 137;  // key
        buffer[12] = 3;    // key
        buffer[13] = 53;   // key

        match ws_demunge(&mut ws, &mut buffer, 7, 14, &mut frame_lengths) {
            Ok((new_pos, new_end)) => {
                println!("pos={}, end={}, state={:?}", new_pos, new_end, ws.frame_state);
                assert_eq!(new_pos, 14); // read all the frame data but no STOMP data
                assert_eq!(new_end, 14);
                assert_eq!(ws.frame_len, 458113);
            },
            _ => panic!("parse error"),
        }

    }
}