2017-10-09 02:47:41 +02:00
|
|
|
use std::{fmt, mem};
|
2017-10-08 06:48:00 +02:00
|
|
|
use std::iter::FromIterator;
|
2018-02-26 22:58:23 +01:00
|
|
|
use bytes::{Bytes, BytesMut, BufMut};
|
2018-02-10 05:43:14 +01:00
|
|
|
use byteorder::{ByteOrder, BigEndian, NetworkEndian};
|
2018-02-26 22:58:23 +01:00
|
|
|
use futures::{Async, Poll, Stream};
|
2018-02-10 05:43:14 +01:00
|
|
|
use rand;
|
2017-10-08 06:48:00 +02:00
|
|
|
|
2018-01-10 19:12:34 +01:00
|
|
|
use body::Binary;
|
2018-02-26 22:58:23 +01:00
|
|
|
use error::{WsError, PayloadError};
|
|
|
|
use payload::PayloadHelper;
|
2018-01-10 20:13:29 +01:00
|
|
|
use ws::proto::{OpCode, CloseCode};
|
2018-01-21 01:47:34 +01:00
|
|
|
use ws::mask::apply_mask;
|
2017-10-08 06:48:00 +02:00
|
|
|
|
|
|
|
/// A struct representing a `WebSocket` frame.
|
2018-01-10 19:12:34 +01:00
|
|
|
#[derive(Debug)]
|
2017-10-08 07:41:02 +02:00
|
|
|
pub(crate) struct Frame {
|
2017-10-08 06:48:00 +02:00
|
|
|
finished: bool,
|
|
|
|
rsv1: bool,
|
|
|
|
rsv2: bool,
|
|
|
|
rsv3: bool,
|
|
|
|
opcode: OpCode,
|
2018-01-10 19:12:34 +01:00
|
|
|
payload: Binary,
|
2017-10-08 06:48:00 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Frame {
|
|
|
|
|
2018-01-15 22:47:25 +01:00
|
|
|
/// Destruct frame
|
2018-01-10 19:12:34 +01:00
|
|
|
pub fn unpack(self) -> (bool, OpCode, Binary) {
|
2017-10-08 06:48:00 +02:00
|
|
|
(self.finished, self.opcode, self.payload)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Create a new Close control frame.
|
|
|
|
#[inline]
|
2018-02-10 09:05:20 +01:00
|
|
|
pub fn close(code: CloseCode, reason: &str, genmask: bool) -> Binary {
|
2017-10-08 06:48:00 +02:00
|
|
|
let raw: [u8; 2] = unsafe {
|
|
|
|
let u: u16 = code.into();
|
2017-10-09 02:47:41 +02:00
|
|
|
mem::transmute(u.to_be())
|
2017-10-08 06:48:00 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
let payload = if let CloseCode::Empty = code {
|
|
|
|
Vec::new()
|
|
|
|
} else {
|
|
|
|
Vec::from_iter(
|
|
|
|
raw[..].iter()
|
|
|
|
.chain(reason.as_bytes().iter())
|
|
|
|
.cloned())
|
|
|
|
};
|
|
|
|
|
2018-02-10 09:05:20 +01:00
|
|
|
Frame::message(payload, OpCode::Close, true, genmask)
|
2017-10-08 06:48:00 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Parse the input stream into a frame.
|
2018-02-26 22:58:23 +01:00
|
|
|
pub fn parse<S>(pl: &mut PayloadHelper<S>, server: bool) -> Poll<Option<Frame>, WsError>
|
|
|
|
where S: Stream<Item=Bytes, Error=PayloadError>
|
|
|
|
{
|
2017-10-08 06:48:00 +02:00
|
|
|
let mut idx = 2;
|
2018-02-26 22:58:23 +01:00
|
|
|
let buf = match pl.copy(2)? {
|
|
|
|
Async::Ready(Some(buf)) => buf,
|
|
|
|
Async::Ready(None) => return Ok(Async::Ready(None)),
|
|
|
|
Async::NotReady => return Ok(Async::NotReady),
|
|
|
|
};
|
2018-02-10 02:20:28 +01:00
|
|
|
let first = buf[0];
|
|
|
|
let second = buf[1];
|
|
|
|
let finished = first & 0x80 != 0;
|
|
|
|
|
2018-02-10 05:43:14 +01:00
|
|
|
// check masking
|
|
|
|
let masked = second & 0x80 != 0;
|
|
|
|
if !masked && server {
|
2018-02-26 22:58:23 +01:00
|
|
|
return Err(WsError::UnmaskedFrame)
|
2018-02-10 05:43:14 +01:00
|
|
|
} else if masked && !server {
|
2018-02-26 22:58:23 +01:00
|
|
|
return Err(WsError::MaskedFrame)
|
2018-02-10 05:43:14 +01:00
|
|
|
}
|
|
|
|
|
2018-02-10 02:20:28 +01:00
|
|
|
let rsv1 = first & 0x40 != 0;
|
|
|
|
let rsv2 = first & 0x20 != 0;
|
|
|
|
let rsv3 = first & 0x10 != 0;
|
|
|
|
let opcode = OpCode::from(first & 0x0F);
|
|
|
|
let len = second & 0x7F;
|
|
|
|
|
|
|
|
let length = if len == 126 {
|
2018-02-26 22:58:23 +01:00
|
|
|
let buf = match pl.copy(4)? {
|
|
|
|
Async::Ready(Some(buf)) => buf,
|
|
|
|
Async::Ready(None) => return Ok(Async::Ready(None)),
|
|
|
|
Async::NotReady => return Ok(Async::NotReady),
|
|
|
|
};
|
2018-02-10 05:43:14 +01:00
|
|
|
let len = NetworkEndian::read_uint(&buf[idx..], 2) as usize;
|
2018-02-10 02:20:28 +01:00
|
|
|
idx += 2;
|
|
|
|
len
|
|
|
|
} else if len == 127 {
|
2018-02-26 22:58:23 +01:00
|
|
|
let buf = match pl.copy(10)? {
|
|
|
|
Async::Ready(Some(buf)) => buf,
|
|
|
|
Async::Ready(None) => return Ok(Async::Ready(None)),
|
|
|
|
Async::NotReady => return Ok(Async::NotReady),
|
|
|
|
};
|
2018-02-10 05:43:14 +01:00
|
|
|
let len = NetworkEndian::read_uint(&buf[idx..], 8) as usize;
|
2018-02-10 02:20:28 +01:00
|
|
|
idx += 8;
|
|
|
|
len
|
|
|
|
} else {
|
2018-02-10 05:43:14 +01:00
|
|
|
len as usize
|
2018-02-10 02:20:28 +01:00
|
|
|
};
|
2017-10-08 06:48:00 +02:00
|
|
|
|
2018-02-10 05:43:14 +01:00
|
|
|
let mask = if server {
|
2018-02-26 22:58:23 +01:00
|
|
|
let buf = match pl.copy(idx + 4)? {
|
|
|
|
Async::Ready(Some(buf)) => buf,
|
|
|
|
Async::Ready(None) => return Ok(Async::Ready(None)),
|
|
|
|
Async::NotReady => return Ok(Async::NotReady),
|
|
|
|
};
|
|
|
|
|
|
|
|
let mut mask_bytes = [0u8; 4];
|
|
|
|
mask_bytes.copy_from_slice(&buf[idx..idx+4]);
|
|
|
|
idx += 4;
|
|
|
|
Some(mask_bytes)
|
2018-02-10 02:20:28 +01:00
|
|
|
} else {
|
|
|
|
None
|
|
|
|
};
|
2017-10-08 06:48:00 +02:00
|
|
|
|
2018-02-26 22:58:23 +01:00
|
|
|
let mut data = match pl.readexactly(idx + length)? {
|
|
|
|
Async::Ready(Some(buf)) => buf,
|
|
|
|
Async::Ready(None) => return Ok(Async::Ready(None)),
|
|
|
|
Async::NotReady => return Ok(Async::NotReady),
|
|
|
|
};
|
2018-02-10 02:20:28 +01:00
|
|
|
|
|
|
|
// get body
|
2018-02-26 22:58:23 +01:00
|
|
|
data.split_to(idx);
|
2018-02-10 02:20:28 +01:00
|
|
|
|
|
|
|
// Disallow bad opcode
|
|
|
|
if let OpCode::Bad = opcode {
|
2018-02-26 22:58:23 +01:00
|
|
|
return Err(WsError::InvalidOpcode(first & 0x0F))
|
2018-02-10 02:20:28 +01:00
|
|
|
}
|
2017-10-08 06:48:00 +02:00
|
|
|
|
2018-02-10 02:20:28 +01:00
|
|
|
// control frames must have length <= 125
|
|
|
|
match opcode {
|
|
|
|
OpCode::Ping | OpCode::Pong if length > 125 => {
|
2018-02-26 22:58:23 +01:00
|
|
|
return Err(WsError::InvalidLength(length))
|
2017-10-08 06:48:00 +02:00
|
|
|
}
|
2018-02-10 02:20:28 +01:00
|
|
|
OpCode::Close if length > 125 => {
|
|
|
|
debug!("Received close frame with payload length exceeding 125. Morphing to protocol close frame.");
|
2018-02-26 22:58:23 +01:00
|
|
|
return Ok(Async::Ready(Some(Frame::default())))
|
2017-10-08 06:48:00 +02:00
|
|
|
}
|
2018-02-10 02:20:28 +01:00
|
|
|
_ => ()
|
|
|
|
}
|
2017-10-08 06:48:00 +02:00
|
|
|
|
2018-02-10 02:20:28 +01:00
|
|
|
// unmask
|
|
|
|
if let Some(ref mask) = mask {
|
|
|
|
apply_mask(&mut data, mask);
|
|
|
|
}
|
2017-10-08 06:48:00 +02:00
|
|
|
|
2018-02-26 22:58:23 +01:00
|
|
|
Ok(Async::Ready(Some(Frame {
|
2018-02-26 23:33:56 +01:00
|
|
|
finished, rsv1, rsv2, rsv3, opcode, payload: data.into() })))
|
2017-10-08 06:48:00 +02:00
|
|
|
}
|
|
|
|
|
2018-02-10 05:43:14 +01:00
|
|
|
/// Generate binary representation
|
2018-02-10 09:05:20 +01:00
|
|
|
pub fn message<B: Into<Binary>>(data: B, code: OpCode,
|
|
|
|
finished: bool, genmask: bool) -> Binary
|
|
|
|
{
|
|
|
|
let payload = data.into();
|
|
|
|
let one: u8 = if finished {
|
|
|
|
0x80 | Into::<u8>::into(code)
|
2018-02-10 05:43:14 +01:00
|
|
|
} else {
|
2018-02-10 09:05:20 +01:00
|
|
|
code.into()
|
|
|
|
};
|
|
|
|
let payload_len = payload.len();
|
|
|
|
let (two, p_len) = if genmask {
|
|
|
|
(0x80, payload_len + 4)
|
|
|
|
} else {
|
|
|
|
(0, payload_len)
|
2018-02-10 05:43:14 +01:00
|
|
|
};
|
2017-10-08 06:48:00 +02:00
|
|
|
|
2018-02-10 09:05:20 +01:00
|
|
|
let mut buf = if payload_len < 126 {
|
|
|
|
let mut buf = BytesMut::with_capacity(p_len + 2);
|
|
|
|
buf.put_slice(&[one, two | payload_len as u8]);
|
2018-02-10 05:43:14 +01:00
|
|
|
buf
|
|
|
|
} else if payload_len <= 65_535 {
|
2018-02-10 09:05:20 +01:00
|
|
|
let mut buf = BytesMut::with_capacity(p_len + 4);
|
|
|
|
buf.put_slice(&[one, two | 126]);
|
2018-02-10 05:43:14 +01:00
|
|
|
{
|
|
|
|
let buf_mut = unsafe{buf.bytes_mut()};
|
2018-02-10 09:05:20 +01:00
|
|
|
BigEndian::write_u16(&mut buf_mut[..2], payload_len as u16);
|
2018-02-10 05:43:14 +01:00
|
|
|
}
|
2018-02-10 09:05:20 +01:00
|
|
|
unsafe{buf.advance_mut(2)};
|
2018-02-10 05:43:14 +01:00
|
|
|
buf
|
2017-10-08 06:48:00 +02:00
|
|
|
} else {
|
2018-02-23 10:45:33 +01:00
|
|
|
let mut buf = BytesMut::with_capacity(p_len + 10);
|
2018-02-10 09:05:20 +01:00
|
|
|
buf.put_slice(&[one, two | 127]);
|
2018-02-10 05:43:14 +01:00
|
|
|
{
|
|
|
|
let buf_mut = unsafe{buf.bytes_mut()};
|
2018-02-10 09:05:20 +01:00
|
|
|
BigEndian::write_u64(&mut buf_mut[..8], payload_len as u64);
|
2018-02-10 05:43:14 +01:00
|
|
|
}
|
2018-02-10 09:05:20 +01:00
|
|
|
unsafe{buf.advance_mut(8)};
|
2018-02-10 05:43:14 +01:00
|
|
|
buf
|
|
|
|
};
|
2017-10-08 06:48:00 +02:00
|
|
|
|
2018-02-10 05:43:14 +01:00
|
|
|
if genmask {
|
|
|
|
let mask: [u8; 4] = rand::random();
|
2018-02-10 09:05:20 +01:00
|
|
|
unsafe {
|
|
|
|
{
|
|
|
|
let buf_mut = buf.bytes_mut();
|
|
|
|
buf_mut[..4].copy_from_slice(&mask);
|
|
|
|
buf_mut[4..payload_len+4].copy_from_slice(payload.as_ref());
|
|
|
|
apply_mask(&mut buf_mut[4..], &mask);
|
|
|
|
}
|
|
|
|
buf.advance_mut(payload_len + 4);
|
|
|
|
}
|
|
|
|
buf.into()
|
2018-01-10 19:12:34 +01:00
|
|
|
} else {
|
2018-02-10 09:05:20 +01:00
|
|
|
buf.put_slice(payload.as_ref());
|
|
|
|
buf.into()
|
2017-10-08 06:48:00 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for Frame {
|
|
|
|
fn default() -> Frame {
|
|
|
|
Frame {
|
|
|
|
finished: true,
|
|
|
|
rsv1: false,
|
|
|
|
rsv2: false,
|
|
|
|
rsv3: false,
|
|
|
|
opcode: OpCode::Close,
|
2018-01-10 19:12:34 +01:00
|
|
|
payload: Binary::from(&b""[..]),
|
2017-10-08 06:48:00 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl fmt::Display for Frame {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
write!(f,
|
|
|
|
"
|
|
|
|
<FRAME>
|
|
|
|
final: {}
|
|
|
|
reserved: {} {} {}
|
|
|
|
opcode: {}
|
|
|
|
payload length: {}
|
|
|
|
payload: 0x{}
|
|
|
|
</FRAME>",
|
2018-01-10 19:12:34 +01:00
|
|
|
self.finished,
|
|
|
|
self.rsv1,
|
|
|
|
self.rsv2,
|
|
|
|
self.rsv3,
|
|
|
|
self.opcode,
|
|
|
|
self.payload.len(),
|
|
|
|
self.payload.as_ref().iter().map(
|
|
|
|
|byte| format!("{:x}", byte)).collect::<String>())
|
2017-10-08 06:48:00 +02:00
|
|
|
}
|
|
|
|
}
|
2017-10-23 22:31:22 +02:00
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
2018-02-26 22:58:23 +01:00
|
|
|
use futures::stream::once;
|
|
|
|
|
|
|
|
fn is_none(frm: Poll<Option<Frame>, WsError>) -> bool {
|
|
|
|
match frm {
|
|
|
|
Ok(Async::Ready(None)) => true,
|
|
|
|
_ => false,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn extract(frm: Poll<Option<Frame>, WsError>) -> Frame {
|
|
|
|
match frm {
|
|
|
|
Ok(Async::Ready(Some(frame))) => frame,
|
|
|
|
_ => panic!("error"),
|
|
|
|
}
|
|
|
|
}
|
2017-10-23 22:31:22 +02:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_parse() {
|
2018-02-26 22:58:23 +01:00
|
|
|
let mut buf = PayloadHelper::new(
|
|
|
|
once(Ok(BytesMut::from(&[0b00000001u8, 0b00000001u8][..]).freeze())));
|
|
|
|
assert!(is_none(Frame::parse(&mut buf, false)));
|
|
|
|
|
2017-10-23 22:31:22 +02:00
|
|
|
let mut buf = BytesMut::from(&[0b00000001u8, 0b00000001u8][..]);
|
|
|
|
buf.extend(b"1");
|
2018-02-26 22:58:23 +01:00
|
|
|
let mut buf = PayloadHelper::new(once(Ok(buf.freeze())));
|
|
|
|
|
|
|
|
let frame = extract(Frame::parse(&mut buf, false));
|
2017-10-23 23:08:11 +02:00
|
|
|
println!("FRAME: {}", frame);
|
2017-10-23 22:31:22 +02:00
|
|
|
assert!(!frame.finished);
|
|
|
|
assert_eq!(frame.opcode, OpCode::Text);
|
2018-01-10 19:12:34 +01:00
|
|
|
assert_eq!(frame.payload.as_ref(), &b"1"[..]);
|
2017-10-23 22:31:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_parse_length0() {
|
2018-02-26 22:58:23 +01:00
|
|
|
let buf = BytesMut::from(&[0b00000001u8, 0b00000000u8][..]);
|
|
|
|
let mut buf = PayloadHelper::new(once(Ok(buf.freeze())));
|
|
|
|
|
|
|
|
let frame = extract(Frame::parse(&mut buf, false));
|
2017-10-23 22:31:22 +02:00
|
|
|
assert!(!frame.finished);
|
|
|
|
assert_eq!(frame.opcode, OpCode::Text);
|
|
|
|
assert!(frame.payload.is_empty());
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_parse_length2() {
|
2018-02-26 22:58:23 +01:00
|
|
|
let buf = BytesMut::from(&[0b00000001u8, 126u8][..]);
|
|
|
|
let mut buf = PayloadHelper::new(once(Ok(buf.freeze())));
|
|
|
|
assert!(is_none(Frame::parse(&mut buf, false)));
|
|
|
|
|
2017-10-23 22:31:22 +02:00
|
|
|
let mut buf = BytesMut::from(&[0b00000001u8, 126u8][..]);
|
|
|
|
buf.extend(&[0u8, 4u8][..]);
|
|
|
|
buf.extend(b"1234");
|
2018-02-26 22:58:23 +01:00
|
|
|
let mut buf = PayloadHelper::new(once(Ok(buf.freeze())));
|
2017-10-23 22:31:22 +02:00
|
|
|
|
2018-02-26 22:58:23 +01:00
|
|
|
let frame = extract(Frame::parse(&mut buf, false));
|
2017-10-23 22:31:22 +02:00
|
|
|
assert!(!frame.finished);
|
|
|
|
assert_eq!(frame.opcode, OpCode::Text);
|
2018-01-10 19:12:34 +01:00
|
|
|
assert_eq!(frame.payload.as_ref(), &b"1234"[..]);
|
2017-10-23 22:31:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_parse_length4() {
|
2018-02-26 22:58:23 +01:00
|
|
|
let buf = BytesMut::from(&[0b00000001u8, 127u8][..]);
|
|
|
|
let mut buf = PayloadHelper::new(once(Ok(buf.freeze())));
|
|
|
|
assert!(is_none(Frame::parse(&mut buf, false)));
|
|
|
|
|
2017-10-23 22:31:22 +02:00
|
|
|
let mut buf = BytesMut::from(&[0b00000001u8, 127u8][..]);
|
|
|
|
buf.extend(&[0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 4u8][..]);
|
|
|
|
buf.extend(b"1234");
|
2018-02-26 22:58:23 +01:00
|
|
|
let mut buf = PayloadHelper::new(once(Ok(buf.freeze())));
|
2017-10-23 22:31:22 +02:00
|
|
|
|
2018-02-26 22:58:23 +01:00
|
|
|
let frame = extract(Frame::parse(&mut buf, false));
|
2017-10-23 22:31:22 +02:00
|
|
|
assert!(!frame.finished);
|
|
|
|
assert_eq!(frame.opcode, OpCode::Text);
|
2018-01-10 19:12:34 +01:00
|
|
|
assert_eq!(frame.payload.as_ref(), &b"1234"[..]);
|
2017-10-23 22:31:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_parse_frame_mask() {
|
|
|
|
let mut buf = BytesMut::from(&[0b00000001u8, 0b10000001u8][..]);
|
|
|
|
buf.extend(b"0001");
|
|
|
|
buf.extend(b"1");
|
2018-02-26 22:58:23 +01:00
|
|
|
let mut buf = PayloadHelper::new(once(Ok(buf.freeze())));
|
2017-10-23 22:31:22 +02:00
|
|
|
|
2018-02-10 05:43:14 +01:00
|
|
|
assert!(Frame::parse(&mut buf, false).is_err());
|
|
|
|
|
2018-02-26 22:58:23 +01:00
|
|
|
let frame = extract(Frame::parse(&mut buf, true));
|
2018-02-10 05:43:14 +01:00
|
|
|
assert!(!frame.finished);
|
|
|
|
assert_eq!(frame.opcode, OpCode::Text);
|
|
|
|
assert_eq!(frame.payload, vec![1u8].into());
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_parse_frame_no_mask() {
|
|
|
|
let mut buf = BytesMut::from(&[0b00000001u8, 0b00000001u8][..]);
|
|
|
|
buf.extend(&[1u8]);
|
2018-02-26 22:58:23 +01:00
|
|
|
let mut buf = PayloadHelper::new(once(Ok(buf.freeze())));
|
2018-02-10 05:43:14 +01:00
|
|
|
|
|
|
|
assert!(Frame::parse(&mut buf, true).is_err());
|
|
|
|
|
2018-02-26 22:58:23 +01:00
|
|
|
let frame = extract(Frame::parse(&mut buf, false));
|
2017-10-23 22:31:22 +02:00
|
|
|
assert!(!frame.finished);
|
|
|
|
assert_eq!(frame.opcode, OpCode::Text);
|
2018-01-10 19:12:34 +01:00
|
|
|
assert_eq!(frame.payload, vec![1u8].into());
|
2017-10-23 22:31:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_ping_frame() {
|
2018-02-10 09:05:20 +01:00
|
|
|
let frame = Frame::message(Vec::from("data"), OpCode::Ping, true, false);
|
2017-10-23 22:31:22 +02:00
|
|
|
|
|
|
|
let mut v = vec![137u8, 4u8];
|
|
|
|
v.extend(b"data");
|
2018-02-10 09:05:20 +01:00
|
|
|
assert_eq!(frame, v.into());
|
2017-10-23 22:31:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_pong_frame() {
|
2018-02-10 09:05:20 +01:00
|
|
|
let frame = Frame::message(Vec::from("data"), OpCode::Pong, true, false);
|
2017-10-23 22:31:22 +02:00
|
|
|
|
|
|
|
let mut v = vec![138u8, 4u8];
|
|
|
|
v.extend(b"data");
|
2018-02-10 09:05:20 +01:00
|
|
|
assert_eq!(frame, v.into());
|
2017-10-23 22:31:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_close_frame() {
|
2018-02-10 09:05:20 +01:00
|
|
|
let frame = Frame::close(CloseCode::Normal, "data", false);
|
2017-10-23 22:31:22 +02:00
|
|
|
|
|
|
|
let mut v = vec![136u8, 6u8, 3u8, 232u8];
|
|
|
|
v.extend(b"data");
|
2018-02-10 09:05:20 +01:00
|
|
|
assert_eq!(frame, v.into());
|
2017-10-23 22:31:22 +02:00
|
|
|
}
|
|
|
|
}
|