Skip to content

Commit c6e6a14

Browse files
committed
test(protocol): added protocol tests
Signed-off-by: Jad K. Haddad <jadkhaddad@gmail.com>
1 parent e250522 commit c6e6a14

4 files changed

Lines changed: 125 additions & 13 deletions

File tree

examples/simple.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,22 +45,22 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
4545
websocketz.framable()
4646
);
4747

48-
'ws: loop {
48+
loop {
4949
websocketz.send(Message::Text("Hello, WebSocket!")).await?;
5050

5151
match next!(websocketz) {
5252
None => {
5353
println!("EOF");
5454

55-
break 'ws;
55+
break;
5656
}
5757
Some(Ok(msg)) => {
5858
println!("Received message: {msg:?}");
5959
}
6060
Some(Err(err)) => {
6161
eprintln!("Error receiving message: {err:?}");
6262

63-
break 'ws;
63+
break;
6464
}
6565
}
6666

src/error.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
use crate::CloseCode;
2-
31
#[derive(Debug, thiserror::Error)]
42
pub enum FrameDecodeError {
53
#[error("Reserved bits must be zero")]
@@ -49,8 +47,8 @@ pub enum HttpEncodeError {
4947
pub enum ProtocolError {
5048
#[error("Invalid close frame")]
5149
InvalidCloseFrame,
52-
#[error("Invalid close code: {code:?}")]
53-
InvalidCloseCode { code: CloseCode },
50+
#[error("Invalid close code")]
51+
InvalidCloseCode,
5452
#[error("Invalid UTF-8")]
5553
InvalidUTF8,
5654
#[error("Invalid fragment")]

src/tests.rs

Lines changed: 119 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1003,7 +1003,6 @@ mod fragmentation {
10031003
}
10041004

10051005
mod auto {
1006-
10071006
use crate::{
10081007
CloseFrame,
10091008
error::{Error, WriteError},
@@ -1107,9 +1106,6 @@ mod auto {
11071106
assert!(matches!(error, Error::Write(WriteError::ConnectionClosed)));
11081107
}
11091108
}
1110-
1111-
// Reading after eof should always return None
1112-
assert!(next!(websocketz).is_none());
11131109
};
11141110

11151111
let server = async move {
@@ -1140,4 +1136,122 @@ mod auto {
11401136
}
11411137
}
11421138

1143-
// TODO: test for read errors
1139+
mod protocol {
1140+
use tokio::io::AsyncWriteExt;
1141+
1142+
use crate::error::{Error, ProtocolError, ReadError};
1143+
1144+
use super::*;
1145+
1146+
macro_rules! quick_protocol_error {
1147+
($frame:ident, $error:ident) => {
1148+
let (client, mut server) = tokio::io::duplex(16);
1149+
1150+
let client = async move {
1151+
let read_buf = &mut [0u8; SIZE];
1152+
let write_buf = &mut [0u8; SIZE];
1153+
let fragments_buf = &mut [0u8; SIZE];
1154+
1155+
let mut websocketz = WebSocket::client(
1156+
FromTokio::new(client),
1157+
StdRng::from_os_rng(),
1158+
read_buf,
1159+
write_buf,
1160+
fragments_buf,
1161+
);
1162+
1163+
match next!(websocketz) {
1164+
Some(Err(error)) => {
1165+
std::println!("Received error: {error:?}");
1166+
assert!(matches!(
1167+
error,
1168+
Error::Read(ReadError::Protocol(ProtocolError::$error))
1169+
));
1170+
}
1171+
message => panic!("Unexpected message: {message:?}"),
1172+
}
1173+
};
1174+
1175+
let server = async move {
1176+
server.write_all($frame).await.unwrap();
1177+
1178+
server
1179+
};
1180+
1181+
tokio::join!(client, server);
1182+
};
1183+
}
1184+
1185+
#[tokio::test]
1186+
async fn invalid_close_frame() {
1187+
const FRAME: &[u8] = &[
1188+
0x88, // FIN=1, RSV1-3=0, opcode=0x8 (Close)
1189+
0x01, // MASK=0 (unmasked), payload length = 1
1190+
0x37, // Single byte of payload (invalid)
1191+
];
1192+
1193+
quick_protocol_error!(FRAME, InvalidCloseFrame);
1194+
}
1195+
1196+
#[tokio::test]
1197+
async fn invalid_close_code() {
1198+
const FRAME: &[u8] = &[
1199+
0x88, // FIN + opcode=0x8 (Close)
1200+
0x02, // Payload length = 2 (only status code, no reason)
1201+
0x03, 0xED, // Status code: 1005 (not allowed)
1202+
];
1203+
1204+
quick_protocol_error!(FRAME, InvalidCloseCode);
1205+
}
1206+
1207+
#[tokio::test]
1208+
async fn invalid_utf8_close() {
1209+
const FRAME: &[u8] = &[
1210+
0x88, // FIN + opcode=0x8 (Close)
1211+
0x03, // Payload length = 3 (2 bytes code + 1 byte invalid UTF-8)
1212+
0x03, 0xE8, // Status code: 1000 (normal closure)
1213+
0xFF, // Invalid UTF-8 byte
1214+
];
1215+
1216+
quick_protocol_error!(FRAME, InvalidUTF8);
1217+
}
1218+
1219+
#[tokio::test]
1220+
async fn invalid_utf8_text() {
1221+
const FRAME: &[u8] = &[
1222+
0x81, // FIN + opcode 0x1 (text)
1223+
0x01, // payload length = 1
1224+
0xFF, // invalid UTF-8 byte
1225+
];
1226+
1227+
quick_protocol_error!(FRAME, InvalidUTF8);
1228+
}
1229+
1230+
#[tokio::test]
1231+
async fn invalid_fragment() {
1232+
const FRAMES: &[u8] = &[
1233+
// Start a fragmented text frame
1234+
0x01, // FIN = 0, opcode = 0x1 (Text, not final)
1235+
0x01, // Payload length = 1
1236+
0x41, // 'A'
1237+
// Try to send a new Binary message while the previous fragment isn't finished
1238+
0x82, // FIN = 1, opcode = 0x2 (Binary, complete)
1239+
0x01, // Payload length = 1
1240+
0x42, // 'B'
1241+
];
1242+
1243+
quick_protocol_error!(FRAMES, InvalidFragment);
1244+
}
1245+
1246+
#[tokio::test]
1247+
async fn invalid_continuation_frame() {
1248+
// Continuation frame without a preceding fragmented message
1249+
const FRAME: &[u8] = &[
1250+
0x80, // FIN = 1, opcode = 0x0 (Continuation)
1251+
0x01, // Payload length = 1
1252+
0x41, // ASCII 'A'
1253+
];
1254+
1255+
quick_protocol_error!(FRAME, InvalidContinuationFrame);
1256+
}
1257+
}

src/websocket_core.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -468,7 +468,7 @@ impl<'buf, RW, Rng> WebSocketCore<'buf, RW, Rng> {
468468
let code = CloseCode::from_u16(u16::from_be_bytes([payload[0], payload[1]]));
469469

470470
if !code.is_allowed() {
471-
return Err(ProtocolError::InvalidCloseCode { code });
471+
return Err(ProtocolError::InvalidCloseCode);
472472
}
473473

474474
match core::str::from_utf8(&payload[2..]) {

0 commit comments

Comments
 (0)