@@ -197,3 +197,104 @@ pub struct Client<S: Strategy, C: Scheme> {
197197 ws_connector : WsConnector ,
198198 strategy : S ,
199199}
200+
201+ #[ cfg( test) ]
202+ mod tests {
203+ use super :: { ClientBuilder , Error } ;
204+ use alto_types:: { Identity , StandardScheme , NAMESPACE } ;
205+ use commonware_math:: algebra:: CryptoGroup ;
206+ use commonware_parallel:: Sequential ;
207+ use futures:: StreamExt ;
208+ use std:: time:: Duration ;
209+ use tokio:: { io:: AsyncWriteExt , net:: TcpListener } ;
210+ use tokio_tungstenite:: tungstenite:: { error:: CapacityError , Error as WsError } ;
211+
212+ /// Send raw frames through a real client and return its transport or artifact error.
213+ async fn receive_error ( block_size : Option < u32 > , frames : Vec < u8 > ) -> Error {
214+ let listener = TcpListener :: bind ( "127.0.0.1:0" ) . await . unwrap ( ) ;
215+ let addr = listener. local_addr ( ) . unwrap ( ) ;
216+ let server = tokio:: spawn ( async move {
217+ let ( socket, _) = listener. accept ( ) . await . unwrap ( ) ;
218+ let mut socket = tokio_tungstenite:: accept_async ( socket) . await . unwrap ( ) ;
219+ socket. get_mut ( ) . write_all ( & frames) . await . unwrap ( ) ;
220+ socket. get_mut ( ) . shutdown ( ) . await . unwrap ( ) ;
221+ } ) ;
222+ let mut builder = ClientBuilder :: new (
223+ & format ! ( "http://{addr}" ) ,
224+ StandardScheme :: certificate_verifier ( NAMESPACE , Identity :: generator ( ) ) ,
225+ Sequential ,
226+ ) ;
227+ if let Some ( block_size) = block_size {
228+ builder = builder. with_block_size ( block_size) ;
229+ }
230+ let client = builder. build ( ) ;
231+ let mut stream = client. listen ( ) . await . unwrap ( ) ;
232+ let result = tokio:: time:: timeout ( Duration :: from_secs ( 5 ) , stream. next ( ) )
233+ . await
234+ . expect ( "frame processing stalled" )
235+ . expect ( "frame did not produce a result" ) ;
236+ server. await . unwrap ( ) ;
237+ match result {
238+ Err ( error) => error,
239+ Ok ( _) => panic ! ( "unexpected consensus artifact" ) ,
240+ }
241+ }
242+
243+ #[ tokio:: test]
244+ async fn oversized_frames_are_rejected_from_the_header ( ) {
245+ for ( block_size, limit) in [
246+ ( None , 5 * 1024 * 1024 + 1 ) ,
247+ ( Some ( 0 ) , 1024 * 1024 + 1 ) ,
248+ ( Some ( 4096 ) , 1024 * 1024 + 4097 ) ,
249+ ] {
250+ for fragmented in [ false , true ] {
251+ // Announce one byte beyond the receive limit, without sending its payload.
252+ let mut frames = Vec :: new ( ) ;
253+ if fragmented {
254+ frames. extend_from_slice ( & [ 0x02 , 0x01 , 0x00 ] ) ;
255+ }
256+ frames. extend_from_slice ( & [ if fragmented { 0x80 } else { 0x82 } , 0x7f ] ) ;
257+ frames. extend_from_slice ( & ( ( limit + 1 ) as u64 ) . to_be_bytes ( ) ) ;
258+ assert ! ( matches!(
259+ receive_error( block_size, frames) . await ,
260+ Error :: Tungstenite ( WsError :: Capacity ( CapacityError :: MessageTooLong {
261+ size,
262+ max_size,
263+ } ) ) if size == limit + 1 && max_size == limit
264+ ) ) ;
265+ }
266+ }
267+ }
268+
269+ #[ tokio:: test]
270+ async fn streaming_budget_includes_the_message_kind ( ) {
271+ // An HTTP artifact can fill the entire encoding allowance. Its stream adds one kind byte.
272+ let length = 1024 * 1024 + 1 ;
273+ let mut frame = vec ! [ 0x82 , 0x7f ] ;
274+ frame. extend_from_slice ( & ( length as u64 ) . to_be_bytes ( ) ) ;
275+ frame. resize ( frame. len ( ) + length, 0xff ) ;
276+
277+ // Reaching kind dispatch proves that the complete message passed the transport limit.
278+ assert ! ( matches!(
279+ receive_error( Some ( 0 ) , frame) . await ,
280+ Error :: UnexpectedResponse
281+ ) ) ;
282+ }
283+
284+ #[ tokio:: test]
285+ async fn fragmented_messages_share_the_receive_budget ( ) {
286+ // Each frame fits by itself, but their combined payload exceeds the message budget.
287+ let length = 1024 * 1024 ;
288+ let mut frames = vec ! [ 0x02 , 0x7f ] ;
289+ frames. extend_from_slice ( & ( length as u64 ) . to_be_bytes ( ) ) ;
290+ frames. resize ( frames. len ( ) + length, 0xff ) ;
291+ frames. extend_from_slice ( & [ 0x80 , 0x02 , 0xff , 0xff ] ) ;
292+ assert ! ( matches!(
293+ receive_error( Some ( 0 ) , frames) . await ,
294+ Error :: Tungstenite ( WsError :: Capacity ( CapacityError :: MessageTooLong {
295+ size,
296+ max_size,
297+ } ) ) if size == length + 2 && max_size == length + 1
298+ ) ) ;
299+ }
300+ }
0 commit comments