bitformat/
websocket_frame.rs

1mod websocket_opcode;
2
3use std::convert::TryInto;
4use colored::Colorize;
5use super::color::Color;
6use websocket_opcode::WebSocketOpCode;
7
8const BITS_IN_BYTE: usize = 8;
9const BYTES_IN_DWORD: usize = 4;
10
11pub struct FormatStyle {
12    pub border_color: Color,
13    pub tick_mark_color: Color,
14    pub title_color: Color,
15    pub column_title_color: Color,
16    pub dword_title_color: Color,
17    pub notes_color: Color,
18    pub bit_color: Color,
19    pub unmasked_payload_bit_color: Color,
20    pub byte_value_color: Color,
21    pub data_value_color: Color,
22    pub summary_title_color: Color,
23    pub summary_value_color: Color,
24}
25
26impl FormatStyle {
27    pub fn new() -> FormatStyle {
28        FormatStyle {
29            border_color: Color::Cyan,
30            tick_mark_color: Color::Green,
31            title_color: Color::White,
32            column_title_color: Color::Green,
33            dword_title_color: Color::Green,
34            notes_color: Color::Magenta,
35            bit_color: Color::White,
36            unmasked_payload_bit_color: Color::Yellow,
37            byte_value_color: Color::Blue,
38            data_value_color: Color::Red,
39            summary_title_color: Color::Magenta,
40            summary_value_color: Color::Red,
41        }
42    }
43}
44
45/// The length of a WebSocket data frame payload.
46#[derive(Debug)]
47#[derive(PartialEq)]
48pub enum PayloadLength {
49    Short(u8),
50    Medium(u16),
51    Long(u64)
52}
53
54impl std::fmt::Display for PayloadLength {
55    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
56        let out = match *self {
57            PayloadLength::Short(length) => format!("Short ({0} bytes)", length),
58            PayloadLength::Medium(length) => format!("Medium ({0} bytes)", length),
59            PayloadLength::Long(length) => format!("Long ({0} bytes)", length)
60        };
61        write!(f, "{}", out)
62    }
63}
64
65pub struct WebSocketFrame<'a> {
66    pub frame_len: u8,
67    pub is_payload_masked: bool,
68    pub payload_length: PayloadLength,
69    pub format_style: FormatStyle,
70    fin_bit: bool,
71    rsv1: bool,
72    rsv2: bool,
73    rsv3: bool,
74    opcode_bits: u8,
75    opcode: WebSocketOpCode,
76    mask_bit: bool,
77    payload_length_code: u8,
78    masking_key: [u8; 4],
79    masked_payload: &'a [u8],
80    unmasked_payload: Vec<u8>,
81    payload_chars: Vec<char>,
82}
83
84impl<'a> WebSocketFrame<'a> {
85    /// Builds a websocket frame from a byte array
86    ///
87    /// # Arguments
88    ///
89    /// * `data` - The byte array to convert to a `WebSocketFrame`.
90    pub fn from_bytes(data: &Vec<u8>) -> WebSocketFrame {
91        const NUM_MASK_BYTES: usize = 4;
92
93        // Get frame length
94        let frame_length: usize = data.len();
95
96        // Get the opcode bit values
97        let opcode_bits = get_bits_from_byte(data[0], 0b00001111);
98
99        // Check if the payload is masked
100        let is_payload_masked: bool = get_bit(data[1], 0);
101
102        // Get the payload length code (bits 9 - 15)
103        let payload_length_code: u8 = get_bits_from_byte(data[1], 0b01111111);
104
105        // Assemble extension data
106        let mut extension_data: Vec<u8> = Vec::new();
107        for ix in 0..8 {
108            if data.len() > ix + 2 {
109                extension_data.push(data[ix + 2]);
110            }
111        }
112
113        // TODO: Handle larger payloads and unmasked payloads
114        let payload_start_index = 6;
115
116        let num_payload_bytes: usize = frame_length - payload_start_index;
117
118        // Get mask
119        let masking_key: [u8; 4] = [data[2], data[3], data[4], data[5]];
120
121        // Unmask and parse payload data
122        let mut unmasked_payload: Vec<u8> = Vec::new();
123        let mut payload_chars: Vec<char> = Vec::new();
124        for i in 0..num_payload_bytes {
125            let byte: u8 = data[payload_start_index + i] ^ masking_key[i % NUM_MASK_BYTES];
126            unmasked_payload.push(byte); // 32 mask bits are used repeatedly
127                                         //payload.push(byte as char);
128            payload_chars.push(byte as char);
129        }
130
131        WebSocketFrame {
132            // Bytes in frame
133            frame_len: data.len() as u8,
134            // Mask bit (bit 8) indicates if the payload is masked
135            is_payload_masked,
136            // Payload length
137            payload_length: WebSocketFrame::get_payload_length(payload_length_code, extension_data),
138            // Use default format style
139            format_style: FormatStyle::new(),
140            // Bit 0 contains fin bit
141            fin_bit: get_bit(data[0], 0),
142            // Bit 1 contains rsv1
143            rsv1: get_bit(data[0], 1),
144            // Bit 2 contains rsv2
145            rsv2: get_bit(data[0], 2),
146            // Bit 3 contains rsv3
147            rsv3: get_bit(data[0], 3),
148            // Bits 4 - 7 contain the opcode
149            opcode_bits,
150            // Look up the opcode from the bits
151            opcode: WebSocketOpCode::from_bit_value(opcode_bits),
152            // Bit 8 contains mask flag
153            mask_bit: is_payload_masked,
154            // Bits 9 - 15 contain payload length code
155            payload_length_code,
156            // Next 4 bytes contain masking key
157            masking_key,
158            // Masked payload is from byte 6 to end of frame
159            masked_payload: &data[payload_start_index..data.len()],
160            // Unmasked payload
161            unmasked_payload,
162            // Vector of chars in payload
163            payload_chars,
164        }
165    }
166
167    /// Formats the websocket frame.
168    ///
169    /// # Arguments
170    ///
171    /// * `self` - The `WebSocketFrame` being formatted.
172    pub fn format(self: &WebSocketFrame<'a>) -> String {
173        let mut result = self.format_header();
174
175        result.push_str(&self.format_first_two_dwords());
176
177        let payload_length: usize = 
178            match self.payload_length {
179                PayloadLength::Short(length) => length.into(),
180                PayloadLength::Medium(length) => length.into(),
181                PayloadLength::Long(length) => length.try_into().unwrap()
182            };
183
184        let dword_from = 
185            match self.payload_length {
186                PayloadLength::Short(_) => 3,
187                PayloadLength::Medium(_) => 3,
188                PayloadLength::Long(_) => 4
189            };
190
191        // Format remaining full dwords
192        let remaining_payload_dwords = (payload_length - 2).div_euclid(BYTES_IN_DWORD.into());
193        for i in 0..remaining_payload_dwords {
194            let from_byte_ix = (i * BYTES_IN_DWORD) + 2;
195            let to_byte_ix = BYTES_IN_DWORD * from_byte_ix;
196            result.push_str(&self.format_payload_dword_row(
197                from_byte_ix, 
198                to_byte_ix, 
199                i + dword_from, 
200                i + 2
201            ));
202        }
203        // Format remaining bytes (formatted as partial dword)
204        let remaining_bytes: usize = (payload_length - 2).rem_euclid(BYTES_IN_DWORD);
205        if remaining_bytes > 0 {
206            let from_byte_ix: usize = (remaining_payload_dwords * BYTES_IN_DWORD) + 2;
207            let to_byte_ix: usize = from_byte_ix + remaining_bytes;
208            result.push_str(&self.format_payload_dword_row(
209                from_byte_ix,
210                to_byte_ix,
211                remaining_payload_dwords + 3,
212                (remaining_payload_dwords * 2) + 2,
213            ));
214        }
215
216        result
217    }
218
219    /// Formats the WebSocket frame header.
220    ///
221    /// # Arguments
222    ///
223    /// * `self` The WebSocket frame being formatted.
224    fn format_header(self: &WebSocketFrame<'a>) -> String {
225        // Closures to help apply style colors
226        let border_color = |s: &str| s.color(self.format_style.border_color.to_string());
227        let tick_color = |s: &str| s.color(self.format_style.tick_mark_color.to_string());
228        let title_color = |s: &str| s.color(self.format_style.title_color.to_string());
229        let column_title_color = |s: &str| s.color(self.format_style.column_title_color.to_string());
230
231        // Start with the top border
232        let mut result: String = 
233            format!(
234                "{0:15}{1}\n", 
235                "",
236                border_color("+---------------+---------------+---------------+---------------+")
237            );
238
239        // Append column headers
240        result.push_str(
241            &format!(
242                "{0:2}{2}{0:3}{1}{3:^15}{1}{4:^15}{1}{5:^15}{1}{6:^15}{1}\n", 
243                "", 
244                border_color("|"),
245                title_color("Frame Data"),
246                column_title_color("Byte  1"),
247                column_title_color("Byte  2"),
248                column_title_color("Byte  3"),
249                column_title_color("Byte  4")
250            )
251        );
252        // Append divider (between byte headers and bit tick marks)
253        result.push_str(
254            &format!(
255                "{0:2}{1}\n", 
256                " ",
257                format!(
258                    "{0:^10}{1:3}{2}",
259                    if self.is_payload_masked { title_color("(Masked)") } else { title_color("(Unmasked)") },
260                    "",
261                    border_color("+---------------+---------------+---------------+---------------+"),
262                )
263            )
264        );
265        // Append tens tick marks
266        result.push_str(
267            &format!(
268                "{0:2}{1:^10}{0:3}{2}{3}{0:14}{2}{0:4}{4}{0:10}{2}{0:8}{5}{0:6}{2}{0:12}{6}{0:2}{2}\n",
269                "",
270                format!("{:?}", self.payload_length),
271                border_color("|"),
272                tick_color("0"),
273                tick_color("1"),
274                tick_color("2"),
275                tick_color("3")
276            )
277        );
278        // Append unit tick marks
279        result.push_str(
280            &format!(
281                "{0:15}{1}{2}{1}{3}{1}{4}{1}{5}{1}\n",
282                "",
283                border_color("|"),
284                tick_color("0 1 2 3 4 5 6 7"),
285                tick_color("8 9 0 1 2 3 4 5"),
286                tick_color("6 7 8 9 0 1 2 3"),
287                tick_color("4 5 6 7 8 9 0 1")
288            )
289        );
290        
291        result
292    }
293
294    fn format_first_two_dwords(
295        self: &WebSocketFrame<'a>
296    ) -> String {
297        // Closures to help apply style colors
298        let border_color = |s: &str| s.color(self.format_style.border_color.to_string());
299        let dword_title_color = |s: &str| s.color(self.format_style.dword_title_color.to_string());
300        let notes_color = |s: &str| s.color(self.format_style.notes_color.to_string());
301        let bit_color = |s: &str| s.color(self.format_style.bit_color.to_string());
302        let unmasked_payload_bit_color = |s: &str| s.color(self.format_style.unmasked_payload_bit_color.to_string());
303        let byte_value_color = |s: &str| s.color(self.format_style.byte_value_color.to_string());
304        let data_value_color = |s: &str| s.color(self.format_style.data_value_color.to_string());
305
306        // Start with the top border
307        let mut result: String = 
308            format!(
309                "{0:7}{1}\n", 
310                "",
311                border_color("+-------+---------------+---------------+---------------+---------------+")
312            );
313        // Line 1: DWORD 1 bit values
314        result.push_str(
315            &format!(
316                "{0:7}{1}{2:^7}{1}{3}{1}{4}{1}{5}{1}{6}{1}{7}{1}{8}{1}{9}{1}{10}{1}{11}{1}\n",
317                "",
318                border_color("|"),
319                dword_title_color("DWORD"),
320                bit_color(bit_str(self.fin_bit)),
321                bit_color(bit_str(self.rsv1)),
322                bit_color(bit_str(self.rsv2)),
323                bit_color(bit_str(self.rsv3)),
324                bit_color(&byte_str(self.opcode_bits, 4)),
325                bit_color(bit_str(self.mask_bit)),
326                bit_color(&byte_str(self.payload_length_code, 7)),
327                bit_color(&byte_str(self.masking_key[0], 8)),
328                bit_color(&byte_str(self.masking_key[1], 8)),
329            )
330        );
331        // Line 2: Op code and first line of bit names
332        result.push_str(
333            &format!(
334                "{0:7}{1}{2:^7}{1}{3}{1}{4}{1}{4}{1}{4}{1}{6:^7}{1}{5}{1}{7:^13}{1}{0:31}{1}\n",
335                "",
336                border_color("|"),
337                dword_title_color("1"),
338                notes_color("F"),
339                notes_color("R"),
340                notes_color("M"),
341                data_value_color(&format!("{:?}",self.opcode)),
342                data_value_color(&format!("{:?}", self.payload_length)),
343            )
344        );
345        // Append the second line of bit identifiers
346        result.push_str(
347            &format!(
348                "{0:7}{1}{0:7}{1}{2}{1}{3}{1}{3}{1}{3}{1}{4:7}{1}{5}{1}{6:^13}{1}{7:^31}{1}\n",
349                "",
350                border_color("|"),
351                notes_color("I"),
352                notes_color("S"),
353                notes_color("op code"),
354                notes_color("A"),
355                notes_color("Payload len"),
356                notes_color("Masking-key (part 1)"),
357            )
358        );
359        // Append the third line of bit identifiers
360        result.push_str(
361            &format!(
362                "{0:7}{1}{0:7}{1}{2}{1}{3}{1}{3}{1}{3}{1}{4:^7}{1}{5}{1}{6:^13}{1}{0:31}{1}\n",
363                "",
364                border_color("|"),
365                notes_color("N"),
366                notes_color("V"),
367                notes_color("(4 b)"),
368                notes_color("S"),
369                notes_color("(7 bits)"),
370            )
371        );
372        // Append the final line of bit identifiers
373        result.push_str(
374            &format!(
375                "{0:7}{1}{0:7}{1}{0:1}{1}{2}{1}{3}{1}{4}{1}{0:7}{1}{5}{1}{0:13}{1}{0:31}{1}\n",
376                "",
377                border_color("|"),
378                notes_color("1"),
379                notes_color("2"),
380                notes_color("3"),
381                notes_color("K"),
382            )
383        );
384        // Append border separating DWORD 1 and DWORD 2
385        result.push_str(
386            &format!(
387                "{0:7}{1}\n",
388                "",
389                border_color("+-------+-+-+-+-+-------+-+-------------+-------------------------------+")
390            )    
391        );
392        // Append the first line of DWORD 2
393        result.push_str(
394            &format!(
395                "{0:7}{1}{2:^7}{1}{3:^15}{1}{4:^15}{1}{5:^15}{1}{6:^15}{1}\n",
396                "",
397                border_color("|"),
398                dword_title_color("DWORD"),
399                bit_color(&byte_str(self.masking_key[2], 8)),
400                bit_color(&byte_str(self.masking_key[3], 8)),
401                bit_color(&byte_str(self.masked_payload[0], 8)),
402                bit_color(&byte_str(self.masked_payload[1], 8)),
403            )
404        );
405        // Append the second line of DWORD 2
406        result.push_str(
407            &format!(
408                "{0:7}{1}{2:^7}{1}{0:^31}{1}{0:1}{4:>5}{0:6}{3}{0:2}{5:>5}{0:6}{1}\n",
409                "",
410                border_color("|"),
411                dword_title_color("2"),
412                notes_color("MASKED"),
413                byte_value_color(&format!("({})", self.masked_payload[0])),
414                byte_value_color(&format!("({})", self.masked_payload[1])),
415            )
416        );
417        // Append the third line of DWORD 2
418        result.push_str(
419            &format!(
420                "{0:7}{1}{0:7}{1}{2:^31}{1}{3:^15}{1}{4:^15}{1}\n",
421                "",
422                border_color("|"),
423                notes_color("Masking-key (part 2)"),
424                unmasked_payload_bit_color(&byte_str(self.unmasked_payload[0], 8)),
425                unmasked_payload_bit_color(&byte_str(self.unmasked_payload[1], 8)),
426            )
427        );
428        // Append the fourth line of DWORD 2
429        result.push_str(
430            &format!(
431                "{0:7}{1}{0:7}{1}{2:^31}{1}{0:1}{4:>5}{0:1}{5:3}{0:1}{3}{0:1}{6:>5}{0:1}{7:3}{0:2}{1}\n",
432                "",
433                border_color("|"),
434                notes_color("(16 bits)"),
435                notes_color("UNMASKED"),
436                byte_value_color(&format!("({})", self.unmasked_payload[0])),
437                data_value_color(&format!("'{0}'", &self.payload_chars[0])),
438                byte_value_color(&format!("({})", self.unmasked_payload[1])),
439                data_value_color(&format!("'{0}'", &self.payload_chars[1]))
440            )
441        );
442        // Append the fifth line of DWORD 2
443        result.push_str(
444            &format!(
445                "{0:7}{1}{0:7}{1}{0:^31}{1}{2:^31}{1}\n",
446                "",
447                border_color("|"),
448                notes_color("Payload Data (part 1)")
449            )
450        );
451        // Append the bottom border
452        result.push_str(
453            &format!(
454                "{0:7}{1}\n",
455                "",
456                border_color("+-------+-------------------------------+-------------------------------+"),
457            )
458        );
459
460        result
461    }
462
463    /// Formats a dword table row displaying part of a websocket frame payload.
464    /// # Arguments
465    ///
466    /// * `self` The WebSocket frame being formatted.
467    fn format_payload_dword_row(
468        self: &WebSocketFrame<'a>,
469        from_byte_ix: usize,
470        to_byte_ix: usize,
471        dword_number: usize,
472        part_number: usize,
473    ) -> String {
474        // Closures to help apply style colors
475        let border_color = |s: &str| s.color(self.format_style.border_color.to_string());
476        let dword_title_color = |s: &str| s.color(self.format_style.dword_title_color.to_string());
477        let notes_color = |s: &str| s.color(self.format_style.notes_color.to_string());
478        let bit_color = |s: &str| s.color(self.format_style.bit_color.to_string());
479        let unmasked_payload_bit_color = |s: &str| s.color(self.format_style.unmasked_payload_bit_color.to_string());
480        let data_value_color = |s: &str| s.color(self.format_style.data_value_color.to_string());
481        let byte_value_color = |s: &str| s.color(self.format_style.byte_value_color.to_string());
482        
483        let mut result: String = String::from("");
484
485        // Calculate number of bytes to include in this row
486        let num_bytes = to_byte_ix - from_byte_ix;
487
488        let masked_bits: &[u8] = &self.masked_payload[from_byte_ix..to_byte_ix];
489        let unmasked_bits: &[u8] = &self.unmasked_payload[from_byte_ix..to_byte_ix];
490        let payload_data: &[char] = &self.payload_chars[from_byte_ix..to_byte_ix];
491
492        // Check indexes form a valid range
493        if num_bytes < 1 || num_bytes > 4  {
494            return String::from(
495                format!("ERROR: Cannot print dword row. Illegal byte indexes provided. from_byte_ix: {} to_byte_ix: {}", 
496                from_byte_ix, 
497                to_byte_ix));
498        }
499
500        // Format masked bits (line 1)
501        result.push_str(
502            &format!(
503                "{0:7}{1}{2:^7}{1}",
504                "",
505                border_color("|"),
506                dword_title_color("DWORD"),
507            )
508        );
509        result.push_str(
510            &(0..num_bytes)
511                .map(|i| format!(
512                    "{1}{0}",
513                    border_color("|"), 
514                    bit_color(&byte_str(masked_bits[i], BITS_IN_BYTE as u8))))
515                .collect::<String>(),
516        );
517        result.push_str("\n");
518
519        // Line 2: Masked char previews
520        result.push_str(
521            &format!(
522                "{0:7}{1}{2:^7}{1}",
523                "",
524                border_color("|"),
525                dword_title_color(&dword_number.to_string())
526            )
527        );
528        match num_bytes {
529            1 => result.push_str(&format!(
530                "{0:1}{3:>5}{0:5}{2}{0:1}{1}",
531                "",
532                border_color("|"),
533                notes_color("MSK"),
534                byte_value_color(&format!("({})", masked_bits[0]))
535            )),
536            2 => result.push_str(&format!(
537                "{0:1}{3:>5}{0:6}{2}{0:2}{4:>5}{0:6}{1}",
538                "",
539                border_color("|"),
540                notes_color("MASKED"),
541                byte_value_color(&format!("({})", masked_bits[0])),
542                byte_value_color(&format!("({})", masked_bits[1]))
543            )),
544            3 => result.push_str(&format!(
545                "{0:1}{4:>5}{0:6}{2}{0:2}{5:>5}{0:6}{1}{0:1}{6:>5}{0:5}{3}{0:1}{1}",
546                "",
547                border_color("|"),
548                notes_color("MASKED"),
549                notes_color("MSK"),
550                byte_value_color(&format!("({})", masked_bits[0])),
551                byte_value_color(&format!("({})", masked_bits[1])),
552                byte_value_color(&format!("({})", masked_bits[2]))
553            )),
554            4 => result.push_str(&format!(
555                "{0:1}{4:>5}{0:6}{2}{0:2}{5:>5}{0:6}{1}{0:1}{6:>5}{0:6}{3}{0:2}{7:>5}{0:6}{1}",
556                "",
557                border_color("|"),
558                notes_color("MASKED"),
559                notes_color("MASKED"),
560                byte_value_color(&format!("({})", masked_bits[0])),
561                byte_value_color(&format!("({})", masked_bits[1])),
562                byte_value_color(&format!("({})", masked_bits[2])),
563                byte_value_color(&format!("({})", masked_bits[3]))
564            )),
565            _ => {}
566        }
567        result.push_str("\n");
568
569        // Line 3: Unmasked bits
570        result.push_str(
571            &format!(
572                "{0:7}{1}{0:7}{1}",
573                "",
574                border_color("|")
575            )
576        );
577        result.push_str(
578            &(0..num_bytes)
579                .map(|i| format!("{1}{0}", border_color("|"), unmasked_payload_bit_color(&byte_str(unmasked_bits[i], BITS_IN_BYTE as u8))))
580                .collect::<String>(),
581        );
582        result.push_str("\n");
583
584        // Line 4: Unmasked char previews
585        result.push_str(&format!("{0:7}{1}{0:7}{1}", "", border_color("|")));
586        match num_bytes {
587            1 => result.push_str(&format!(
588                "{0:1}{3:>5}{0:1}{4}{0:1}{2}{0:1}{1}",
589                "",
590                border_color("|"),
591                notes_color("UNM"),
592                byte_value_color(&format!("({})", unmasked_bits[0])),
593                data_value_color(&format!("'{0}'", payload_data[0]))
594            )),
595            2 => result.push_str(&format!(
596                "{0:1}{3:>5}{0:1}{4:3}{0:1}{2}{0:1}{5:>5}{0:1}{6:3}{0:2}{1}",
597                "",
598                border_color("|"),
599                notes_color("UNMASKED"),
600                byte_value_color(&format!("({})", unmasked_bits[0])),
601                data_value_color(&format!("'{}'", payload_data[0])),
602                byte_value_color(&format!("({})", unmasked_bits[1])),
603                data_value_color(&format!("'{}'", payload_data[1]))
604            )),
605            3 => result.push_str(&format!(
606                "{0:1}{4:>5}{0:1}{5:3}{0:1}{2}{0:1}{6:>5}{0:1}{7:3}{0:2}{1}{0:1}{8:>5}{0:1}{9:3}{0:1}{3}{0:1}{1}",
607                "",
608                border_color("|"),
609                notes_color("UNMASKED"),
610                notes_color("UNM"),
611                byte_value_color(&format!("({})", unmasked_bits[0])),
612                data_value_color(&format!("'{}'", payload_data[0])),
613                byte_value_color(&format!("({})", unmasked_bits[1])),
614                data_value_color(&format!("'{}'", payload_data[1])),
615                byte_value_color(&format!("({})", unmasked_bits[2])),
616                data_value_color(&format!("'{}'", payload_data[2])),
617            )),
618            4 => result.push_str(&format!(
619                "{0:1}{3:>5}{0:1}{4:3}{0:1}{2}{0:1}{5:>5}{0:1}{6:3}{0:2}{1}{0:1}{7:>5}{0:1}{8:3}{0:1}{2}{0:1}{9:>5}{0:1}{10:3}{0:2}{1}",
620                "",
621                border_color("|"),
622                notes_color("UNMASKED"),
623                byte_value_color(&format!("({})", unmasked_bits[0])),
624                data_value_color(&format!("'{}'", payload_data[0])),
625                byte_value_color(&format!("({})", unmasked_bits[1])),
626                data_value_color(&format!("'{}'", payload_data[1])),
627                byte_value_color(&format!("({})", unmasked_bits[2])),
628                data_value_color(&format!("'{}'", payload_data[2])),
629                byte_value_color(&format!("({})", unmasked_bits[3])),
630                data_value_color(&format!("'{}'", payload_data[3])),
631            )),
632            _ => {}
633        }
634        result.push_str("\n");
635
636        // Line 5: Payload part
637        result.push_str(&format!("{0:7}{1}{0:7}{1}", "", border_color("|")));
638        match num_bytes {
639            1 => result.push_str(&format!(
640                "{1:^15}{0}",
641                border_color("|"),
642                notes_color(&format!("Payload pt {}", part_number))
643            )),
644            2 => result.push_str(&format!(
645                "{1:^31}{0}",
646                border_color("|"),
647                notes_color(&format!("Payload Data (part {})", part_number))
648            )),
649            3 => result.push_str(&format!(
650                "{1:^47}{0}",
651                border_color("|"),
652                notes_color(&format!("Payload Data (part {})", part_number)),
653            )),
654            4 => result.push_str(&format!(
655                "{1:^63}{0}",
656                border_color("|"),
657                notes_color(&format!("Payload Data (part {})", part_number)),
658            )),
659            _ => {}
660        }
661        result.push_str("\n");
662
663        // Format bottom border
664        result.push_str(&format!("{0:7}{1}", "", border_color("+-------+")));
665        result.push_str(
666            &(0..num_bytes)
667                .map(|_| border_color("---------------+").to_string())
668                .collect::<String>(),
669        );
670        result.push_str("\n");
671
672        result
673    }
674
675    /// Derives a WebSocket payload length from its payload length code and extension bytes.
676    /// 
677    /// Per RFC 6455 Section 5.2: https://tools.ietf.org/html/rfc6455#section-5.2
678    /// 
679    /// # Arguments
680    /// 
681    /// * `code` - The payload length code.
682    /// * `ext_bytes` - The extension bytes.
683    fn get_payload_length(code: u8, ext_bytes: Vec<u8>) -> PayloadLength {
684        // Code <= 125: The code *is* the payload length
685        if code <= 125 {
686            return PayloadLength::Short(code);
687        }
688        // Code 126: The first 2 extension bytes contain the payload length
689        if code == 126 {
690            return PayloadLength::Medium(u16::from_le_bytes([ext_bytes[0], ext_bytes[1]]));
691        }
692        // Code 127: The 8 extension bytes contain the payload length
693        if code == 127 {
694            return PayloadLength::Long(u64::from_le_bytes([ext_bytes[0], ext_bytes[1], ext_bytes[2], ext_bytes[3], ext_bytes[4], ext_bytes[5], ext_bytes[6], ext_bytes[7]]));
695        }
696        // Code must have been an 8-bit value
697        panic!("ERROR: Unable to determine payload length from code: {}", code);
698    }
699}
700
701fn get_bits_from_byte(byte: u8, mask: u8) -> u8 {
702    byte & mask
703}
704
705/// Formats a byte or partial byte.
706///
707/// # Arguments
708///
709/// * `byte` - The byte to format.
710/// * `num_bits` - The number of bits to format.
711fn byte_str<'a>(byte: u8, num_bits: u8) -> String {
712    let mut result: String = String::from("");
713    result.push_str(
714        &(8 - num_bits..8)
715            .map(|i| format!("{} ", bit_str(get_bit(byte, i))))
716            .collect::<String>(),
717    );
718    result.trim().to_string()
719}
720
721fn bit_str<'a>(bit: bool) -> &'a str {
722    if bit == true {
723        "1"
724    } else {
725        "0"
726    }
727}
728
729fn get_bit(byte: u8, bit_position: u8) -> bool {
730    match bit_position {
731        0 => byte & 0b10000000 != 0,
732        1 => byte & 0b01000000 != 0,
733        2 => byte & 0b00100000 != 0,
734        3 => byte & 0b00010000 != 0,
735        4 => byte & 0b00001000 != 0,
736        5 => byte & 0b00000100 != 0,
737        6 => byte & 0b00000010 != 0,
738        7 => byte & 0b00000001 != 0,
739        _ => false,
740    }
741}
742
743// #region WebSocket Frame Unit Tests
744
745#[cfg(test)]
746mod tests {
747    use super::*;
748
749    #[test]
750    fn test_short_masked_text_frame() {
751        let bytes = base64::decode("gYR7q0rdD845qQ==").unwrap();
752
753        let frame = WebSocketFrame::from_bytes(&bytes);
754        let expected = "               \u{1b}[36m+---------------+---------------+---------------+---------------+\u{1b}[0m\n  \u{1b}[37mFrame Data\u{1b}[0m   \u{1b}[36m|\u{1b}[0m\u{1b}[32m    Byte  1    \u{1b}[0m\u{1b}[36m|\u{1b}[0m\u{1b}[32m    Byte  2    \u{1b}[0m\u{1b}[36m|\u{1b}[0m\u{1b}[32m    Byte  3    \u{1b}[0m\u{1b}[36m|\u{1b}[0m\u{1b}[32m    Byte  4    \u{1b}[0m\u{1b}[36m|\u{1b}[0m\n  \u{1b}[37m (Masked) \u{1b}[0m   \u{1b}[36m+---------------+---------------+---------------+---------------+\u{1b}[0m\n   Short(4)    \u{1b}[36m|\u{1b}[0m\u{1b}[32m0\u{1b}[0m              \u{1b}[36m|\u{1b}[0m    \u{1b}[32m1\u{1b}[0m          \u{1b}[36m|\u{1b}[0m        \u{1b}[32m2\u{1b}[0m      \u{1b}[36m|\u{1b}[0m            \u{1b}[32m3\u{1b}[0m  \u{1b}[36m|\u{1b}[0m\n               \u{1b}[36m|\u{1b}[0m\u{1b}[32m0 1 2 3 4 5 6 7\u{1b}[0m\u{1b}[36m|\u{1b}[0m\u{1b}[32m8 9 0 1 2 3 4 5\u{1b}[0m\u{1b}[36m|\u{1b}[0m\u{1b}[32m6 7 8 9 0 1 2 3\u{1b}[0m\u{1b}[36m|\u{1b}[0m\u{1b}[32m4 5 6 7 8 9 0 1\u{1b}[0m\u{1b}[36m|\u{1b}[0m\n       \u{1b}[36m+-------+---------------+---------------+---------------+---------------+\u{1b}[0m\n       \u{1b}[36m|\u{1b}[0m\u{1b}[32m DWORD \u{1b}[0m\u{1b}[36m|\u{1b}[0m\u{1b}[37m1\u{1b}[0m\u{1b}[36m|\u{1b}[0m\u{1b}[37m0\u{1b}[0m\u{1b}[36m|\u{1b}[0m\u{1b}[37m0\u{1b}[0m\u{1b}[36m|\u{1b}[0m\u{1b}[37m0\u{1b}[0m\u{1b}[36m|\u{1b}[0m\u{1b}[37m0 0 0 1\u{1b}[0m\u{1b}[36m|\u{1b}[0m\u{1b}[37m1\u{1b}[0m\u{1b}[36m|\u{1b}[0m\u{1b}[37m0 0 0 0 1 0 0\u{1b}[0m\u{1b}[36m|\u{1b}[0m\u{1b}[37m0 1 1 1 1 0 1 1\u{1b}[0m\u{1b}[36m|\u{1b}[0m\u{1b}[37m1 0 1 0 1 0 1 1\u{1b}[0m\u{1b}[36m|\u{1b}[0m\n       \u{1b}[36m|\u{1b}[0m\u{1b}[32m   1   \u{1b}[0m\u{1b}[36m|\u{1b}[0m\u{1b}[35mF\u{1b}[0m\u{1b}[36m|\u{1b}[0m\u{1b}[35mR\u{1b}[0m\u{1b}[36m|\u{1b}[0m\u{1b}[35mR\u{1b}[0m\u{1b}[36m|\u{1b}[0m\u{1b}[35mR\u{1b}[0m\u{1b}[36m|\u{1b}[0m\u{1b}[31m Text  \u{1b}[0m\u{1b}[36m|\u{1b}[0m\u{1b}[35mM\u{1b}[0m\u{1b}[36m|\u{1b}[0m\u{1b}[31m  Short(4)   \u{1b}[0m\u{1b}[36m|\u{1b}[0m                               \u{1b}[36m|\u{1b}[0m\n       \u{1b}[36m|\u{1b}[0m       \u{1b}[36m|\u{1b}[0m\u{1b}[35mI\u{1b}[0m\u{1b}[36m|\u{1b}[0m\u{1b}[35mS\u{1b}[0m\u{1b}[36m|\u{1b}[0m\u{1b}[35mS\u{1b}[0m\u{1b}[36m|\u{1b}[0m\u{1b}[35mS\u{1b}[0m\u{1b}[36m|\u{1b}[0m\u{1b}[35mop code\u{1b}[0m\u{1b}[36m|\u{1b}[0m\u{1b}[35mA\u{1b}[0m\u{1b}[36m|\u{1b}[0m\u{1b}[35m Payload len \u{1b}[0m\u{1b}[36m|\u{1b}[0m\u{1b}[35m     Masking-key (part 1)      \u{1b}[0m\u{1b}[36m|\u{1b}[0m\n       \u{1b}[36m|\u{1b}[0m       \u{1b}[36m|\u{1b}[0m\u{1b}[35mN\u{1b}[0m\u{1b}[36m|\u{1b}[0m\u{1b}[35mV\u{1b}[0m\u{1b}[36m|\u{1b}[0m\u{1b}[35mV\u{1b}[0m\u{1b}[36m|\u{1b}[0m\u{1b}[35mV\u{1b}[0m\u{1b}[36m|\u{1b}[0m\u{1b}[35m (4 b) \u{1b}[0m\u{1b}[36m|\u{1b}[0m\u{1b}[35mS\u{1b}[0m\u{1b}[36m|\u{1b}[0m\u{1b}[35m  (7 bits)   \u{1b}[0m\u{1b}[36m|\u{1b}[0m                               \u{1b}[36m|\u{1b}[0m\n       \u{1b}[36m|\u{1b}[0m       \u{1b}[36m|\u{1b}[0m \u{1b}[36m|\u{1b}[0m\u{1b}[35m1\u{1b}[0m\u{1b}[36m|\u{1b}[0m\u{1b}[35m2\u{1b}[0m\u{1b}[36m|\u{1b}[0m\u{1b}[35m3\u{1b}[0m\u{1b}[36m|\u{1b}[0m       \u{1b}[36m|\u{1b}[0m\u{1b}[35mK\u{1b}[0m\u{1b}[36m|\u{1b}[0m             \u{1b}[36m|\u{1b}[0m                               \u{1b}[36m|\u{1b}[0m\n       \u{1b}[36m+-------+-+-+-+-+-------+-+-------------+-------------------------------+\u{1b}[0m\n       \u{1b}[36m|\u{1b}[0m\u{1b}[32m DWORD \u{1b}[0m\u{1b}[36m|\u{1b}[0m\u{1b}[37m0 1 0 0 1 0 1 0\u{1b}[0m\u{1b}[36m|\u{1b}[0m\u{1b}[37m1 1 0 1 1 1 0 1\u{1b}[0m\u{1b}[36m|\u{1b}[0m\u{1b}[37m0 0 0 0 1 1 1 1\u{1b}[0m\u{1b}[36m|\u{1b}[0m\u{1b}[37m1 1 0 0 1 1 1 0\u{1b}[0m\u{1b}[36m|\u{1b}[0m\n       \u{1b}[36m|\u{1b}[0m\u{1b}[32m   2   \u{1b}[0m\u{1b}[36m|\u{1b}[0m                               \u{1b}[36m|\u{1b}[0m \u{1b}[34m (15)\u{1b}[0m      \u{1b}[35mMASKED\u{1b}[0m  \u{1b}[34m(206)\u{1b}[0m      \u{1b}[36m|\u{1b}[0m\n       \u{1b}[36m|\u{1b}[0m       \u{1b}[36m|\u{1b}[0m\u{1b}[35m     Masking-key (part 2)      \u{1b}[0m\u{1b}[36m|\u{1b}[0m\u{1b}[33m0 1 1 1 0 1 0 0\u{1b}[0m\u{1b}[36m|\u{1b}[0m\u{1b}[33m0 1 1 0 0 1 0 1\u{1b}[0m\u{1b}[36m|\u{1b}[0m\n       \u{1b}[36m|\u{1b}[0m       \u{1b}[36m|\u{1b}[0m\u{1b}[35m           (16 bits)           \u{1b}[0m\u{1b}[36m|\u{1b}[0m \u{1b}[34m(116)\u{1b}[0m \u{1b}[31m\'t\'\u{1b}[0m \u{1b}[35mUNMASKED\u{1b}[0m \u{1b}[34m(101)\u{1b}[0m \u{1b}[31m\'e\'\u{1b}[0m  \u{1b}[36m|\u{1b}[0m\n       \u{1b}[36m|\u{1b}[0m       \u{1b}[36m|\u{1b}[0m                               \u{1b}[36m|\u{1b}[0m\u{1b}[35m     Payload Data (part 1)     \u{1b}[0m\u{1b}[36m|\u{1b}[0m\n       \u{1b}[36m+-------+-------------------------------+-------------------------------+\u{1b}[0m\n       \u{1b}[36m|\u{1b}[0m\u{1b}[32m DWORD \u{1b}[0m\u{1b}[36m|\u{1b}[0m\u{1b}[37m0 0 1 1 1 0 0 1\u{1b}[0m\u{1b}[36m|\u{1b}[0m\u{1b}[37m1 0 1 0 1 0 0 1\u{1b}[0m\u{1b}[36m|\u{1b}[0m\n       \u{1b}[36m|\u{1b}[0m\u{1b}[32m   3   \u{1b}[0m\u{1b}[36m|\u{1b}[0m \u{1b}[34m (57)\u{1b}[0m      \u{1b}[35mMASKED\u{1b}[0m  \u{1b}[34m(169)\u{1b}[0m      \u{1b}[36m|\u{1b}[0m\n       \u{1b}[36m|\u{1b}[0m       \u{1b}[36m|\u{1b}[0m\u{1b}[33m0 1 1 1 0 0 1 1\u{1b}[0m\u{1b}[36m|\u{1b}[0m\u{1b}[33m0 1 1 1 0 1 0 0\u{1b}[0m\u{1b}[36m|\u{1b}[0m\n       \u{1b}[36m|\u{1b}[0m       \u{1b}[36m|\u{1b}[0m \u{1b}[34m(115)\u{1b}[0m \u{1b}[31m\'s\'\u{1b}[0m \u{1b}[35mUNMASKED\u{1b}[0m \u{1b}[34m(116)\u{1b}[0m \u{1b}[31m\'t\'\u{1b}[0m  \u{1b}[36m|\u{1b}[0m\n       \u{1b}[36m|\u{1b}[0m       \u{1b}[36m|\u{1b}[0m\u{1b}[35m     Payload Data (part 2)     \u{1b}[0m\u{1b}[36m|\u{1b}[0m\n       \u{1b}[36m+-------+\u{1b}[0m\u{1b}[36m---------------+\u{1b}[0m\u{1b}[36m---------------+\u{1b}[0m\n";
755        
756        //println!("{}", frame.format());
757
758        assert_eq!(frame.format(), expected);
759    }
760}
761
762// #endregion WebSocket Frame Unit Tests