char/avril
(wip) why'd you have to go and make things so complicaaaaated?
git clone https://git.t4t.associates/char/avril
ec3de1c
main
1mod common; 2 3use std:: io::{ Read , Write }; 4use std:: net:: TcpListener ; 5use std:: time::{ Duration , Instant }; 6 7// The test server is shared with no other test target, so it is declared here. 8# [ path = "common/websocket.rs" ] 9mod ws_server; 10 11use ws_server:: * ; 12 13# [ test ] 14fn reflected_construction_live_handlers_and_close_event_conversion () { 15let ( url, thread) =server ( 1 , |session| session. echo ()); 16let source =r#" 17const order = [], poison = function() {throw Error('guest base constructor');}; 18class ChildSocket extends WebSocket {} 19class ChildClose extends CloseEvent {} 20Object.setPrototypeOf(WebSocket, poison); 21Object.setPrototypeOf(CloseEvent, poison); 22if (WebSocket.length !== 1 || CloseEvent.length !== 1 || 23WebSocket.prototype.send.length !== 1 || WebSocket.prototype.close.length !== 0) throw Error('arity'); 24const close = new ChildClose({toString() {order.push('type'); return 'close';}}, { 25get code() {order.push('code'); return {valueOf() {order.push('code:number'); return 65537;}};}, 26get reason() {order.push('reason'); return {toString() {order.push('reason:string'); return '\ud800';}};}, 27get wasClean() {order.push('clean'); return true;} 28}); 29if (order.join('|') !== 'type|code|reason|clean|code:number|reason:string' || 30close.code !== 1 || close.reason !== '\ufffd' || !close.wasClean || 31Object.getPrototypeOf(close) !== ChildClose.prototype) throw Error('CloseEvent conversion'); 32if (Object.getOwnPropertyNames(close).join() !== 'isTrusted') throw Error('emitted event fields'); 33const socket = new ChildSocket('URL'); 34if (Object.getPrototypeOf(socket) !== ChildSocket.prototype || Object.getOwnPropertyNames(socket).length) throw Error('socket shape'); 35const calls = []; 36socket.onmessage = function(event) { 37if (this !== socket) throw Error('handler receiver'); 38calls.push('original'); return false; 39}; 40socket.addEventListener('message', () => calls.push('listener')); 41const first = new MessageEvent('message', {cancelable:true}); 42if (socket.dispatchEvent(first) || !first.defaultPrevented) throw Error('handler cancellation'); 43socket.onmessage = function() {calls.push('replacement');}; 44socket.dispatchEvent(new MessageEvent('message')); 45if (calls.join('|') !== 'original|listener|replacement|listener') throw Error('live handler order'); 46socket.onmessage = null; 47const completed = new Promise((resolve, reject) => { 48socket.onopen = () => socket.send('echo'); 49socket.onmessage = event => { 50if (event.data !== 'echo') reject(Error('echo')); 51socket.close(1000, 'done'); 52}; 53socket.onerror = () => reject(Error('socket error')); 54socket.onclose = event => { 55if (!event.wasClean || event.code !== 1000 || event.reason !== 'done') reject(Error('close')); 56else resolve(); 57}; 58}); 59await completed; 60console.log('reflected sockets and events'); 61"# 62. replace ( "URL" , & url); 63let output = common:: run ( & source, & []); 64assert_eq! ( output. stdout , b"reflected sockets and events\n" ); 65assert! ( 66 output. stderr . is_empty (), 67"{}" , 68String :: from_utf8_lossy ( & output. stderr ) 69); 70 thread. join (). unwrap (); 71} 72 73# [ test ] 74fn handshakes_capture_the_isolate_user_agent_at_construction () { 75let ( url, thread) =server ( 5 , |mut session|{ 76let ua = session 77. headers 78. lines () 79. find_map ( |line| line. strip_prefix ( "user-agent:" )) 80. expect ( "Missing User-Agent" ) 81. trim () 82. to_owned (); 83 session. accept ( None ) ?; 84 session. write ( TEXT , ua. as_bytes ()) ?; 85let close = session. read () ?; 86assert_eq! ( close. opcode , CLOSE ); 87 session. write ( CLOSE , & close. payload ) 88}); 89let source =r#" 90const original = navigator.userAgent; 91function handshake() { 92return new Promise((resolve, reject) => { 93const socket = new WebSocket('URL'); 94let received; 95socket.onmessage = event => {received = event.data; socket.close();}; 96socket.onerror = () => reject(Error('Handshake failed')); 97socket.onclose = event => { 98if (!event.wasClean) reject(Error('Unclean close')); 99else resolve(received); 100}; 101}); 102} 103if (await handshake() !== original) throw Error('Initial default'); 104navigator.userAgent = 'custom/1.0'; 105const pending = handshake(); 106navigator.userAgent = 'changed/2.0'; 107if (await pending !== 'custom/1.0') throw Error('In-flight UA changed'); 108if (await handshake() !== 'changed/2.0') throw Error('Default not updated'); 109navigator.userAgent = ''; 110if (await handshake() !== '') throw Error('Empty UA lost'); 111navigator.userAgent = 'hardened/1.0'; 112Object.defineProperty(navigator, 'userAgent', {get() {throw Error('Guest getter');}}); 113if (await handshake() !== 'hardened/1.0') throw Error('Internal default lost'); 114console.log('WebSocket UA'); 115"# 116. replace ( "URL" , & url); 117assert_eq! ( common:: run ( & source, & []). stdout , b"WebSocket UA\n" ); 118 thread. join (). unwrap (); 119} 120 121# [ test ] 122fn invalid_user_agents_fail_before_opening_a_websocket_transport () { 123let listener =TcpListener :: bind ( "127.0.0.1:0" ). unwrap (); 124let url =format! ( "ws://{}" , listener. local_addr (). unwrap ()); 125let source =r#" 126for (const invalid of ['bad\r\nInjected: yes', 'bad\0value', '\u0100']) { 127navigator.userAgent = invalid; 128await new Promise((resolve, reject) => { 129const socket = new WebSocket('URL'); 130let errored = false; 131socket.onopen = () => {socket.close(); reject(Error('Invalid UA connected'));}; 132socket.onerror = () => {errored = true;}; 133socket.onclose = event => { 134if (!errored || event.wasClean || event.code !== 1006) reject(Error('Failure events')); 135else resolve(); 136}; 137}); 138} 139console.log('invalid WebSocket UA'); 140"# 141. replace ( "URL" , & url); 142assert_eq! ( common:: run ( & source, & []). stdout , b"invalid WebSocket UA\n" ); 143 listener. set_nonblocking ( true ). unwrap (); 144assert_eq! ( 145 listener. accept (). unwrap_err (). kind (), 146 std:: io:: ErrorKind :: WouldBlock 147); 148} 149 150# [ test ] 151fn echo_text_binary_and_binary_types () { 152let ( url, thread) =server ( 1 , |session| session. echo ()); 153let out = common:: run ( 154& connect ( 155& url, 156r#" 157ws.binaryType = 'arraybuffer'; 158const log = []; 159ws.onopen = () => { 160if (ws.url !== 'WSURL/' || ws.readyState !== 1) throw Error('Attributes'); 161ws.send('hello'); 162ws.send(new Uint8Array([1, 2, 255])); 163}; 164ws.onmessage = (event) => { 165if (typeof event.data === 'string') { 166if (event.data !== 'hello') throw Error('Text'); 167log.push('text'); 168} else if (event.data instanceof ArrayBuffer) { 169if ([...new Uint8Array(event.data)].join(',') !== '1,2,255') throw Error('Binary'); 170log.push('binary'); 171ws.binaryType = 'blob'; 172ws.send(new Blob([new Uint8Array([9, 8]), new Uint8Array([7])], { type: 'application/x-test' })); 173} else { 174if (!(event.data instanceof Blob) || event.data.size !== 3 || event.data.type !== '') { 175throw Error('Blob message'); 176} 177log.push('blob'); 178ws.close(1000, 'done'); 179} 180}; 181ws.onerror = () => log.push('error'); 182ws.onclose = (event) => { 183if (log.join('|') !== 'text|binary|blob') throw Error('Order'); 184if (!event.wasClean || event.code !== 1000 || event.reason !== 'done') throw Error('Close'); 185console.log('echo ok', event instanceof CloseEvent); 186}; 187"# ) 188. replace ( "WSURL" , & url), 189& [], 190); 191assert_eq! ( out. stdout , b"echo ok true\n" ); 192 thread. join (). unwrap (); 193} 194 195# [ test ] 196fn http_urls_map_to_websocket_scheme () { 197let ( url, thread) =server ( 1 , |session| session. echo ()); 198let http = url. replacen ( "ws://" , "http://" , 1 ); 199let out = common:: run ( 200& format! ( 201r#" 202const ws = new WebSocket('{http}'); 203ws.onopen = () => {{ 204if (ws.url !== '{url}/') throw Error('Mapped URL'); 205console.log('mapped'); 206ws.close(); 207}}; 208ws.onclose = (event) => {{ 209if (!event.wasClean || event.code !== 1005) throw Error('Close code'); 210console.log('closed'); 211}}; 212"# 213), 214& [], 215); 216assert_eq! ( out. stdout , b"mapped\nclosed\n" ); 217 thread. join (). unwrap (); 218} 219 220# [ test ] 221fn subprotocol_negotiation () { 222let ( url, thread) =server ( 3 , |mut session|{ 223match session. path . as_str () { 224"/chosen" =>{ 225assert_eq! ( session. protocol , "chat, v2" ); 226 session. accept ( Some ( "v2" )) ?; 227let close = session. read () ?; 228assert_eq! ( close. opcode , CLOSE ); 229 session. write ( CLOSE , & close. payload ) ?; 230} 231"/unacknowledged" =>{ 232 session. accept ( None ) ?; 233assert_eq! ( 234 session. read (). err (). unwrap (). kind (), 235 std:: io:: ErrorKind :: UnexpectedEof 236); 237} 238 _ =>{ 239 session. accept ( Some ( "bogus" )) ?; 240assert_eq! ( 241 session. read (). err (). unwrap (). kind (), 242 std:: io:: ErrorKind :: UnexpectedEof 243); 244} 245} 246Ok (()) 247}); 248let out = common:: run ( 249& format! ( 250r#" 251const log = []; 252await new Promise((resolve) => {{ 253const ws = new WebSocket('{url}/chosen', ['chat', 'v2']); 254ws.onopen = () => {{ 255if (ws.protocol !== 'v2') throw Error('Selected protocol'); 256log.push('chosen'); 257ws.close(1000); 258}}; 259ws.onclose = () => resolve(); 260}}); 261await new Promise((resolve) => {{ 262const ws = new WebSocket('{url}/unacknowledged', ['chat']); 263ws.onerror = () => log.push('error'); 264ws.onclose = (event) => {{ 265if (event.wasClean || event.code !== 1006) throw Error('Failed close'); 266if (ws.readyState !== 3) throw Error('Ready state'); 267log.push('close'); 268resolve(); 269}}; 270}}); 271await new Promise((resolve) => {{ 272const ws = new WebSocket('{url}/bogus', ['chat']); 273ws.onclose = (event) => {{ 274if (event.wasClean || event.code !== 1006) throw Error('Unexpected protocol'); 275log.push('close'); 276resolve(); 277}}; 278}}); 279console.log(log.join('|')); 280"# 281), 282& [], 283); 284assert_eq! ( out. stdout , b"chosen|error|close|close\n" ); 285 thread. join (). unwrap (); 286} 287 288# [ test ] 289fn client_close_codes_and_reasons () { 290let ( url, thread) =server ( 3 , |mut session|{ 291 session. accept ( None ) ?; 292let close = session. read () ?; 293assert_eq! ( close. opcode , CLOSE ); 294match session. path . as_str () { 295"/code" =>{ 296assert_eq! ( close. code (), 1000 ); 297assert_eq! ( close. reason (), "bye" ); 298} 299"/bare" =>assert! ( close. payload . is_empty ()), 300 _ =>{ 301assert_eq! ( close. code (), 3001 ); 302assert_eq! ( close. reason (), "later" ); 303} 304} 305 session. write ( CLOSE , & close. payload ) ?; 306Ok (()) 307}); 308let out = common:: run ( 309& format! ( 310r#" 311const events = []; 312await new Promise((resolve) => {{ 313const ws = new WebSocket('{url}/code'); 314ws.onopen = () => ws.close(1000, 'bye'); 315ws.onclose = (event) => {{ 316if (!event.wasClean || event.code !== 1000 || event.reason !== 'bye') throw Error('Echoed close'); 317if (ws.readyState !== 3 || ws.bufferedAmount !== 0) throw Error('State'); 318events.push(1000); 319resolve(); 320}}; 321}}); 322await new Promise((resolve) => {{ 323const ws = new WebSocket('{url}/bare'); 324ws.onopen = () => ws.close(); 325ws.onclose = (event) => {{ 326if (!event.wasClean || event.code !== 1005 || event.reason !== '') throw Error('Bare close'); 327events.push(1005); 328resolve(); 329}}; 330}}); 331await new Promise((resolve) => {{ 332const ws = new WebSocket('{url}/later'); 333ws.onopen = () => ws.close(3001, 'later'); 334ws.onclose = (event) => {{ 335if (event.code !== 3001 || event.reason !== 'later') throw Error('Later close'); 336events.push(3001); 337resolve(); 338}}; 339}}); 340console.log(events.join(',')); 341"# 342), 343& [], 344); 345assert_eq! ( out. stdout , b"1000,1005,3001\n" ); 346 thread. join (). unwrap (); 347} 348 349# [ test ] 350fn server_initiated_close () { 351let ( url, thread) =server ( 1 , |mut session|{ 352 session. accept ( None ) ?; 353 session. write ( CLOSE , & Frame :: close ( 3001 , "done" ). payload ) ?; 354let close = session. read () ?; 355assert_eq! ( close. code (), 3001 ); 356assert_eq! ( close. reason (), "done" ); 357Ok (()) 358}); 359let out = common:: run ( 360& connect ( 361& url, 362r#" 363ws.onopen = () => { 364if (ws.readyState !== 1) throw Error('Ready state'); 365}; 366ws.onclose = (event) => { 367if (!event.wasClean || event.code !== 3001 || event.reason !== 'done') throw Error('Server close'); 368if (ws.readyState !== 3) throw Error('Ready state'); 369console.log('server closed'); 370}; 371"# , 372), 373& [], 374); 375assert_eq! ( out. stdout , b"server closed\n" ); 376 thread. join (). unwrap (); 377} 378 379# [ test ] 380fn handshake_failure_fires_error_then_close () { 381let ( url, thread) =server ( 1 , |mut session| session. reject ()); 382let out = common:: run ( 383& connect ( 384& url, 385r#" 386const order = []; 387ws.onerror = (event) => order.push('error'); 388ws.onclose = (event) => { 389if (order.join('|') !== 'error') throw Error('Error before close'); 390if (event.wasClean || event.code !== 1006) throw Error('Failed close'); 391if (ws.readyState !== 3) throw Error('Ready state'); 392console.log('handshake failed'); 393}; 394"# , 395), 396& [], 397); 398assert_eq! ( out. stdout , b"handshake failed\n" ); 399 thread. join (). unwrap (); 400} 401 402# [ test ] 403fn refused_connection_fails () { 404let out = common:: run ( 405r#" 406let errors = 0; 407await new Promise((resolve) => { 408const ws = new WebSocket('ws://127.0.0.1:1/'); 409ws.onerror = () => errors++; 410ws.onclose = (event) => { 411if (errors !== 1 || event.wasClean || event.code !== 1006) throw Error('Refused close'); 412console.log('refused'); 413resolve(); 414}; 415}); 416"# , 417& [], 418); 419assert_eq! ( out. stdout , b"refused\n" ); 420} 421 422# [ test ] 423fn permission_denied () { 424let out = common:: run ( 425r#" 426import { pledge } from 'avril:std'; 427pledge({ net: false }); 428try { 429new WebSocket('ws://127.0.0.1:1/'); 430throw Error('Permission ignored'); 431} catch (error) { 432if (!error.message.includes('PermissionDenied')) throw error; 433} 434console.log('denied'); 435"# , 436& [], 437); 438assert_eq! ( out. stdout , b"denied\n" ); 439} 440 441# [ test ] 442fn constructor_validation () { 443let out = common:: run ( 444r#" 445const syntax = (operation) => { 446try { 447operation(); 448throw Error('Accepted invalid URL'); 449} catch (error) { 450if (!(error instanceof DOMException) || error.name !== 'SyntaxError') throw error; 451} 452}; 453syntax(() => new WebSocket('ftp://example.com')); 454syntax(() => new WebSocket('/relative')); 455syntax(() => new WebSocket('ws://example.com/#fragment')); 456syntax(() => new WebSocket('ws://example.com/#')); 457syntax(() => new WebSocket('ws://example.com', 'chat\n')); 458syntax(() => new WebSocket('ws://example.com', 'a b')); 459syntax(() => new WebSocket('ws://example.com', ['a', 'a'])); 460syntax(() => new WebSocket('ws://example.com', ['a,'])); 461syntax(() => new WebSocket('ws://example.com', {})); 462const numeric = new WebSocket('ws://127.0.0.1:1/x', 5); 463if (numeric.protocol !== '') throw Error('Numeric protocols'); 464numeric.close(); 465numeric.onmessage = () => false; 466const canceled = new MessageEvent('message', { cancelable: true }); 467if (numeric.dispatchEvent(canceled) || !canceled.defaultPrevented) { 468throw Error('Event handler return value'); 469} 470const invalid = (operation, name) => { 471try { 472operation(); 473throw Error('Accepted invalid argument'); 474} catch (error) { 475if (error.name !== name) throw error; 476} 477}; 478const ws = new WebSocket('ws://127.0.0.1:1/x'); 479for (const code of [0, 999, 1001, 2999, 5000]) { 480invalid(() => ws.close(code), 'InvalidAccessError'); 481} 482invalid(() => ws.close(1000, 'x'.repeat(124)), 'SyntaxError'); 483invalid(() => ws.send('too early'), 'InvalidStateError'); 484invalid(() => ws.send(), 'TypeError'); 485invalid(() => ws.send(Symbol()), 'TypeError'); 486invalid(() => new WebSocket(Symbol()), 'TypeError'); 487const marker = {}; 488try { 489new WebSocket({ toString() { throw marker; } }); 490throw Error('Swallowed coercion failure'); 491} catch (error) { 492if (error !== marker) throw error; 493} 494try { 495new WebSocket('invalid URL', [{ toString() { throw marker; } }]); 496throw Error('Protocol conversion skipped'); 497} catch (error) { 498if (error !== marker) throw error; 499} 500invalid(() => ws.close(0, Symbol()), 'TypeError'); 501invalid(() => ws.send(new ArrayBuffer(1, { maxByteLength: 2 })), 'TypeError'); 502ws.binaryType = 'nodebuffer'; 503if (ws.binaryType !== 'blob') throw Error('binaryType setter'); 504ws.binaryType = 'arraybuffer'; 505if (ws.binaryType !== 'arraybuffer') throw Error('binaryType setter'); 506if (new CloseEvent('close', { code: -1 }).code !== 65535) throw Error('CloseEvent code'); 507if (new CloseEvent('close', { code: 65537 }).code !== 1) throw Error('CloseEvent code wrap'); 508if (ws.url !== 'ws://127.0.0.1:1/x' || ws.readyState !== 0 || ws.bufferedAmount !== 0) { 509throw Error('Initial attributes'); 510} 511if (ws.extensions !== '' || ws.protocol !== '') throw Error('Initial protocol'); 512if (WebSocket.CONNECTING !== 0 || ws.OPEN !== 1 || WebSocket.CLOSING !== 2 || ws.CLOSED !== 3) { 513throw Error('Ready state constants'); 514} 515if (ws.onopen !== null || typeof ws.addEventListener !== 'function') throw Error('Event handlers'); 516ws.close(); 517console.log('validated', ws.readyState); 518"# , 519& [], 520); 521assert_eq! ( out. stdout , b"validated 2\n" ); 522} 523 524# [ test ] 525fn constructor_checks_url_before_validating_protocols () { 526let out = common:: run ( 527r#" 528for (const [url, message] of [ 529['invalid URL', 'URL is invalid'], 530['ftp://x', 'scheme'], 531['ws://x/#', 'fragment'], 532]) { 533for (const protocols of ['a b', ['chat', 'chat']]) { 534try { 535new WebSocket(url, protocols); 536throw Error('Accepted invalid constructor arguments'); 537} catch (error) { 538if (error.name !== 'SyntaxError' || !error.message.includes(message)) throw error; 539} 540} 541} 542const marker = {}; 543try { 544new WebSocket('ftp://x', { 545get [Symbol.iterator]() { throw marker; } 546}); 547throw Error('Protocol conversion skipped'); 548} catch (error) { 549if (error !== marker) throw error; 550} 551console.log('constructor order checked'); 552"# , 553& [], 554); 555assert_eq! ( out. stdout , b"constructor order checked\n" ); 556} 557 558# [ test ] 559fn buffer_sources_send_detached_as_empty_and_reject_shared () { 560let ( url, thread) =server ( 1 , |mut session|{ 561 session. accept ( None ) ?; 562for expectedin [ vec! [], vec! [], vec! [], vec! [ 1 , 2 ]] { 563let frame = session. read () ?; 564assert_eq! ( frame. opcode , BINARY ); 565assert_eq! ( frame. payload , expected); 566} 567let close = session. read () ?; 568assert_eq! ( close. opcode , CLOSE ); 569 session. write ( CLOSE , & close. payload ) ?; 570Ok (()) 571}); 572let out = common:: run ( 573& connect ( 574& url, 575r#" 576ws.onopen = () => { 577const shared = new SharedArrayBuffer(3); 578const resizable = new ArrayBuffer(3, { maxByteLength: 4 }); 579for (const data of [ 580shared, new Uint8Array(shared), new DataView(shared), 581resizable, new Uint8Array(resizable), new DataView(resizable), 582]) { 583try { 584ws.send(data); 585throw Error('Accepted shared or resizable data'); 586} catch (error) { 587if (!(error instanceof TypeError)) throw error; 588} 589} 590const detached = new ArrayBuffer(3); 591const typed = new Uint8Array(detached); 592const view = new DataView(detached); 593detached.transfer(); 594ws.send(detached); 595ws.send(typed); 596ws.send(view); 597ws.send(new Uint8Array([0, 1, 2, 3]).subarray(1, 3)); 598if (ws.bufferedAmount !== 2) throw Error('Buffered amount'); 599ws.close(1000); 600}; 601ws.onclose = event => { 602if (!event.wasClean || event.code !== 1000) throw Error('Close'); 603console.log('buffer sources checked'); 604}; 605"# , 606), 607& [], 608); 609assert_eq! ( out. stdout , b"buffer sources checked\n" ); 610 thread. join (). unwrap (); 611} 612 613# [ test ] 614fn send_converts_other_arguments_to_text () { 615let ( url, thread) =server ( 1 , |mut session|{ 616 session. accept ( None ) ?; 617for expectedin [ 618& b"5" [ ..], 619& b"[object Object]" [ ..], 620& b"1,2" [ ..], 621& b"null" [ ..], 622] { 623let frame = session. read () ?; 624assert_eq! ( frame. opcode , TEXT , "must be sent as a text frame" ); 625assert_eq! ( frame. payload , expected); 626} 627let frame = session. read () ?; 628assert_eq! ( frame. opcode , BINARY ); 629assert_eq! ( frame. payload , [ 7 ]); 630let close = session. read () ?; 631 session. write ( CLOSE , & close. payload ) ?; 632Ok (()) 633}); 634let out = common:: run ( 635& connect ( 636& url, 637r#" 638ws.onopen = () => { 639ws.send(5); 640ws.send({}); 641ws.send([1, 2]); 642ws.send(null); 643ws.send(new Uint8Array([7])); 644ws.close(1000); 645}; 646ws.onclose = (event) => { 647if (!event.wasClean || event.code !== 1000) throw Error('Close'); 648console.log('converted'); 649}; 650"# , 651), 652& [], 653); 654assert_eq! ( out. stdout , b"converted\n" ); 655 thread. join (). unwrap (); 656} 657 658# [ test ] 659fn fragmentation_and_ping () { 660let ( url, thread) =server ( 1 , |mut session|{ 661 session. accept ( None ) ?; 662write_frame ( & mut session. stream , false , TEXT , b"hel" ) ?; 663write_frame ( & mut session. stream , true , CONTINUATION , b"lo" ) ?; 664 session. write ( PING , b"keepalive" ) ?; 665let deadline =Instant :: now () +Duration :: from_secs ( 5 ); 666let mut pong =None ; 667let mut reply =None ; 668while ( pong. is_none () || reply. is_none ()) &&Instant :: now () < deadline{ 669let frame =match session. read () { 670Ok ( frame) => frame, 671Err ( _) =>break , 672}; 673if frame. opcode ==PONG { 674 pong =Some ( frame. payload ); 675} else if frame. opcode ==TEXT { 676 reply =Some ( frame. payload ); 677} 678} 679assert_eq! ( pong. as_deref (), Some ( & b"keepalive" [ ..])); 680// The fragmented message must arrive as a single text message. 681assert_eq! ( reply. as_deref (), Some ( & b"reply" [ ..])); 682 session. write ( CLOSE , & Frame :: close ( 1000 , "" ). payload ) ?; 683Ok (()) 684}); 685let out = common:: run ( 686& connect ( 687& url, 688r#" 689ws.onmessage = (event) => { 690if (event.data !== 'hello') throw Error('Fragmented message'); 691console.log('fragmented', event.origin); 692ws.send('reply'); 693}; 694"# , 695), 696& [], 697); 698let expected =format! ( "fragmented {}\n" , url); 699assert_eq! ( out. stdout , expected. as_bytes ()); 700 thread. join (). unwrap (); 701} 702 703# [ test ] 704fn invalid_utf8_text_fails () { 705let ( url, thread) =server ( 1 , |mut session|{ 706 session. accept ( None ) ?; 707// A text frame carrying invalid UTF-8. 708 session. stream . write_all ( & [ 0x81 , 0x02 , 0xff , 0xfe ]) ?; 709let close = session. read () ?; 710assert_eq! ( close. opcode , CLOSE ); 711assert_eq! ( close. code (), 1007 ); 712Ok (()) 713}); 714let out = common:: run ( 715& connect ( 716& url, 717r#" 718const order = []; 719ws.onmessage = () => order.push('message'); 720ws.onerror = (event) => { 721if (!event.message.includes('UTF-8')) throw Error('Missing failure reason'); 722order.push('error'); 723}; 724ws.onclose = (event) => { 725if (order.join('|') !== 'error') throw Error('Error before close'); 726if (event.wasClean || event.code !== 1006) throw Error('Failed close'); 727console.log('utf8 failed'); 728}; 729"# , 730), 731& [], 732); 733assert_eq! ( out. stdout , b"utf8 failed\n" ); 734 thread. join (). unwrap (); 735} 736 737# [ test ] 738fn buffered_amount_only_grows_after_close () { 739let ( url, thread) =server ( 1 , |mut session|{ 740 session. accept ( None ) ?; 741 session. write ( CLOSE , & Frame :: close ( 1000 , "" ). payload ) ?; 742assert_eq! ( session. read () ?. opcode , CLOSE ); 743Ok (()) 744}); 745let out = common:: run ( 746& connect ( 747& url, 748r#" 749ws.onclose = () => { 750ws.send('a'); 751ws.send('bb'); 752ws.send(new Uint8Array(3)); 753if (ws.bufferedAmount !== 6) throw Error('Buffered amount'); 754console.log('buffered', ws.bufferedAmount); 755}; 756"# , 757), 758& [], 759); 760assert_eq! ( out. stdout , b"buffered 6\n" ); 761 thread. join (). unwrap (); 762} 763 764# [ test ] 765fn socket_keeps_process_alive_until_closed () { 766let ( close, closing) = std:: sync:: mpsc:: channel (); 767let ( url, thread) =server ( 1 , move |mut session|{ 768 session. accept ( None ) ?; 769 closing. recv_timeout ( Duration :: from_secs ( 5 )). unwrap (); 770 session. write ( CLOSE , & Frame :: close ( 1000 , "" ). payload ) ?; 771assert_eq! ( session. read () ?. opcode , CLOSE ); 772Ok (()) 773}); 774let ( _dir, mut child) = common:: spawn ( & connect ( 775& url, 776r#" 777ws.onopen = () => console.log('open'); 778ws.onclose = () => console.log('closed'); 779"# , 780)); 781let stdout = child. stdout . take (). unwrap (); 782let ( lines, received) = std:: sync:: mpsc:: channel (); 783let output = std:: thread:: spawn ( move ||{ 784use std:: io:: BufRead ; 785for linein std:: io:: BufReader :: new ( stdout). lines () { 786 lines. send ( line. unwrap ()). unwrap (); 787} 788}); 789assert_eq! ( 790 received. recv_timeout ( Duration :: from_secs ( 5 )). unwrap (), 791"open" 792); 793 std:: thread:: sleep ( Duration :: from_millis ( 100 )); 794assert! ( 795 child. try_wait (). unwrap (). is_none (), 796"an open socket must keep the process alive" 797); 798 close. send (()). unwrap (); 799let out = common:: wait ( child); 800assert! ( 801 out. status . success (), 802"{}" , 803String :: from_utf8_lossy ( & out. stderr ) 804); 805assert_eq! ( 806 received. recv_timeout ( Duration :: from_secs ( 5 )). unwrap (), 807"closed" 808); 809 output. join (). unwrap (); 810 thread. join (). unwrap (); 811} 812 813# [ test ] 814fn ipv6_loopback () { 815let Ok (( url, thread)) = 816TcpListener :: bind ( "[::1]:0" ). map ( |_|server_on ( "::1" , 1 , |session| session. echo ())) 817else { 818// IPv6 loopback is unavailable; nothing to test. 819return ; 820}; 821let out = common:: run ( 822& connect ( 823& url, 824r#" 825ws.onopen = () => { 826if (ws.url !== 'WSURL/') throw Error('URL'); 827console.log('open', ws.readyState); 828ws.send('hello'); 829}; 830ws.onmessage = (event) => { 831if (event.data !== 'hello') throw Error('Echo'); 832if (event.origin !== 'WSURL') throw Error('Origin'); 833ws.close(1000); 834}; 835ws.onclose = (event) => { 836if (!event.wasClean || event.code !== 1000) throw Error('Close'); 837console.log('closed'); 838}; 839"# , 840) 841. replace ( "WSURL" , & url), 842& [], 843); 844assert_eq! ( out. stdout , b"open 1\nclosed\n" ); 845 thread. join (). unwrap (); 846} 847 848# [ test ] 849fn tls_handshake_against_plain_server_fails () { 850let listener =TcpListener :: bind ( "127.0.0.1:0" ). unwrap (); 851let url =format! ( "wss://{}" , listener. local_addr (). unwrap ()); 852let thread = std:: thread:: spawn ( move ||{ 853for _in 0 ..1 { 854let mut stream =incoming ( & listener); 855// Accept the TCP connection but never speak TLS; the client must fail. 856let mut buffer =[ 0 ; 64 ]; 857let _ = stream. read ( & mut buffer); 858} 859}); 860let out = common:: run ( 861& connect ( 862& url, 863r#" 864const order = []; 865ws.onerror = () => order.push('error'); 866ws.onclose = (event) => { 867if (order.join('|') !== 'error') throw Error('Error before close'); 868if (event.wasClean || event.code !== 1006) throw Error('Failed close'); 869console.log('tls failed'); 870}; 871"# , 872), 873& [], 874); 875assert_eq! ( out. stdout , b"tls failed\n" ); 876 thread. join (). unwrap (); 877} 878 879# [ test ] 880fn close_during_connecting_fails_the_connection () { 881let listener =TcpListener :: bind ( "127.0.0.1:0" ). unwrap (); 882let url =format! ( "ws://{}" , listener. local_addr (). unwrap ()); 883let out = common:: run ( 884& connect ( 885& url, 886r#" 887const order = []; 888ws.close(1000, 'never opened'); 889if (ws.readyState !== 2) throw Error('Ready state after close'); 890ws.onopen = () => order.push('open'); 891ws.onerror = () => order.push('error'); 892ws.onclose = (event) => { 893order.push('close'); 894if (order.join('|') !== 'error|close') throw Error('Order: ' + order.join('|')); 895if (event.wasClean || event.code !== 1006) throw Error('Failed close'); 896if (ws.readyState !== 3) throw Error('Ready state'); 897console.log('closed while connecting'); 898}; 899"# , 900), 901& [], 902); 903assert_eq! ( out. stdout , b"closed while connecting\n" ); 904} 905 906# [ test ] 907fn sends_are_ordered () { 908let ( url, thread) =server ( 1 , |mut session|{ 909 session. accept ( None ) ?; 910let mut received =Vec :: new (); 911loop { 912let frame = session. read () ?; 913if frame. opcode ==CLOSE { 914 session. write ( CLOSE , & frame. payload ) ?; 915break ; 916} 917assert_eq! ( frame. opcode , TEXT ); 918 session. write ( TEXT , & frame. payload ) ?; 919 received. push ( String :: from_utf8 ( frame. payload ). unwrap ()); 920} 921assert_eq! ( received. len (), 50 ); 922for ( index, message) in received. iter (). enumerate () { 923assert_eq! ( message, & format! ( "message {index}" )); 924} 925Ok (()) 926}); 927let out = common:: run ( 928& connect ( 929& url, 930r#" 931const replies = []; 932ws.onopen = () => { 933for (let i = 0; i < 50; i++) ws.send('message ' + i); 934}; 935ws.onmessage = (event) => { 936replies.push(event.data); 937if (replies.length < 50) return; 938for (let i = 0; i < replies.length; i++) { 939if (replies[i] !== 'message ' + i) throw Error('Out of order'); 940} 941console.log('ordered', replies.length); 942ws.close(1000); 943}; 944"# , 945), 946& [], 947); 948assert_eq! ( out. stdout , b"ordered 50\n" ); 949 thread. join (). unwrap (); 950} 951 952# [ test ] 953fn handshake_response_validation_and_failure_events () { 954let ( url, thread) =server ( 4 , |mut session|{ 955let key =accept ( & session. key ()); 956let response =match session. path . as_str () { 957"/tokens" =>format! ( "HTTP/1.1 101 Switching Protocols\r\nUpgrade: example, WebSocket\r\nConnection: keep-alive, Upgrade\r\nSec-WebSocket-Accept: {key}\r\n\r\n" ), 958"/duplicate" =>format! ( "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: {key}\r\nSec-WebSocket-Accept: wrong\r\n\r\n" ), 959"/extension" =>format! ( "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: {key}\r\nSec-WebSocket-Extensions: permessage-deflate\r\n\r\n" ), 960 _ =>"HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: wrong\r\n\r\n" . to_owned (), 961}; 962 session. stream . write_all ( response. as_bytes ()) ?; 963if session. path =="/tokens" { 964let close = session. read () ?; 965assert_eq! ( close. opcode , CLOSE ); 966 session. write ( CLOSE , & close. payload ) ?; 967} else { 968assert_eq! ( 969 session. read (). err (). unwrap (). kind (), 970 std:: io:: ErrorKind :: UnexpectedEof 971); 972} 973Ok (()) 974}); 975let out = common:: run ( 976& format! ( 977r#" 978for (const path of ['tokens', 'duplicate', 'extension', 'accept']) {{ 979await new Promise(resolve => {{ 980const ws = new WebSocket('{url}/' + path); 981let errors = 0, microtaskRan = false; 982ws.onopen = () => {{ 983if (path !== 'tokens') throw Error('Accepted bad handshake'); 984ws.close(1000); 985}}; 986ws.onerror = () => {{ 987if (ws.readyState !== WebSocket.CLOSED) throw Error('Error readyState'); 988errors++; 989queueMicrotask(() => microtaskRan = true); 990ws.send('discarded'); 991if (ws.bufferedAmount !== 9) throw Error('Failed socket buffering'); 992}}; 993ws.onclose = event => {{ 994if (microtaskRan) throw Error('Error and close must share a turn'); 995if (path === 'tokens') {{ 996if (!event.wasClean || event.code !== 1000 || errors) throw Error('Valid tokens'); 997}} else if (event.wasClean || event.code !== 1006 || errors !== 1) {{ 998throw Error('Failed handshake'); 999}} 1000resolve(); 1001}}; 1002}}); 1003}} 1004console.log('handshakes checked'); 1005"# 1006), 1007& [], 1008); 1009assert_eq! ( out. stdout , b"handshakes checked\n" ); 1010 thread. join (). unwrap (); 1011} 1012 1013# [ test ] 1014fn close_reason_preserves_bom_and_registered_code () { 1015let ( url, thread) =server ( 1 , |mut session|{ 1016 session. accept ( None ) ?; 1017let payload =Frame :: close ( 1014 , "\u{feff}gateway" ). payload ; 1018 session. write ( CLOSE , & payload) ?; 1019let reply = session. read () ?; 1020assert_eq! ( reply. opcode , CLOSE ); 1021assert_eq! ( reply. payload , payload); 1022Ok (()) 1023}); 1024let out = common:: run ( 1025& connect ( 1026& url, 1027r#" 1028ws.onclose = event => { 1029if (!event.wasClean || event.code !== 1014 || event.reason !== '\ufeffgateway') { 1030throw Error('Close payload changed'); 1031} 1032console.log('close payload preserved'); 1033}; 1034"# , 1035), 1036& [], 1037); 1038assert_eq! ( out. stdout , b"close payload preserved\n" ); 1039 thread. join (). unwrap (); 1040} 1041 1042# [ test ] 1043fn invalid_control_frames_fail () { 1044let ( url, thread) =server ( 5 , |mut session|{ 1045 session. accept ( None ) ?; 1046let ( opcode, payload, code) =match session. path . as_str () { 1047"/pong" =>( PONG , vec! [ 0 ; 126 ], 1002 ), 1048"/long-close" =>( CLOSE , Frame :: close ( 1000 , & "x" . repeat ( 124 )). payload , 1002 ), 1049"/short-close" =>( CLOSE , vec! [ 0 ], 1002 ), 1050"/code" =>( CLOSE , Frame :: close ( 1005 , "" ). payload , 1002 ), 1051 _ =>( CLOSE , vec! [ 3 , 232 , 255 ], 1007 ), 1052}; 1053 session. write ( opcode, & payload) ?; 1054let close = session. read () ?; 1055assert_eq! ( close. opcode , CLOSE ); 1056assert_eq! ( close. code (), code); 1057Ok (()) 1058}); 1059let out = common:: run ( 1060& format! ( 1061r#" 1062for (const path of ['pong', 'long-close', 'short-close', 'code', 'utf8']) {{ 1063await new Promise(resolve => {{ 1064const ws = new WebSocket('{url}/' + path); 1065let errors = 0; 1066ws.onerror = () => errors++; 1067ws.onmessage = () => {{ throw Error('Control frame delivered'); }}; 1068ws.onclose = event => {{ 1069if (event.wasClean || event.code !== 1006 || errors !== 1) throw Error('Invalid control frame'); 1070resolve(); 1071}}; 1072}}); 1073}} 1074console.log('invalid controls rejected'); 1075"# 1076), 1077& [], 1078); 1079assert_eq! ( out. stdout , b"invalid controls rejected\n" ); 1080 thread. join (). unwrap (); 1081} 1082 1083# [ test ] 1084fn call_time_primordials_and_protocol_iterator () { 1085let ( url, thread) =server ( 1 , |mut session|{ 1086assert_eq! ( session. path , "/CaseSensitive" ); 1087assert_eq! ( session. protocol , "chat" ); 1088 session. accept ( Some ( "chat" )) ?; 1089let text = session. read () ?; 1090assert_eq! ( text. opcode , TEXT ); 1091assert_eq! ( text. payload , b"hello" ); 1092 session. write ( TEXT , & text. payload ) ?; 1093let close = session. read () ?; 1094assert_eq! ( close. code (), 1000 ); 1095 session. write ( CLOSE , & close. payload ) ?; 1096Ok (()) 1097}); 1098let out = common:: run ( 1099& format! ( 1100r#" 1101const poison = () => {{ throw Error('Guest replacement invoked'); }}; 1102Math.floor = poison; 1103Promise.prototype.then = poison; 1104Object.defineProperty(Promise.prototype, 'constructor', {{ get: poison }}); 1105Object.setPrototypeOf(WebSocket, poison); 1106Object.setPrototypeOf(CloseEvent, poison); 1107for (const name of ['href', 'protocol', 'origin', 'hash']) {{ 1108Object.defineProperty(URL.prototype, name, {{ get: poison }}); 1109}} 1110Object.defineProperty(Object.getPrototypeOf(Uint8Array.prototype), 'length', {{ get: poison }}); 1111let gets = 0; 1112const protocols = {{ 1113get [Symbol.iterator]() {{ 1114if (++gets !== 1) throw Error('Iterator read twice'); 1115return function* () {{ yield 'chat'; }}; 1116}} 1117}}; 1118const ws = new WebSocket('{url}/CaseSensitive', protocols); 1119ws.onopen = () => {{ 1120if (ws.url !== '{url}/CaseSensitive' || gets !== 1) throw Error('Constructor'); 1121ws.send('hello'); 1122if (ws.bufferedAmount !== 5) throw Error('Buffered amount'); 1123}}; 1124ws.onmessage = event => {{ 1125if (event.data !== 'hello' || event.origin !== '{url}') throw Error('Message'); 1126ws.close(1000.4); 1127}}; 1128ws.onclose = event => {{ 1129if (!event.wasClean || event.code !== 1000) throw Error('Close'); 1130console.log('primordials safe'); 1131}}; 1132"# 1133), 1134& [], 1135); 1136assert_eq! ( out. stdout , b"primordials safe\n" ); 1137 thread. join (). unwrap (); 1138} 1139 1140# [ test ] 1141fn canceling_a_pending_handshake_drops_the_transport () { 1142let ( ready, handshake) = std:: sync:: mpsc:: channel (); 1143let ( closed, disconnected) = std:: sync:: mpsc:: channel (); 1144let ( url, thread) =server ( 1 , move |mut session|{ 1145 ready. send (()). unwrap (); 1146let mut byte =[ 0 ]; 1147assert_eq! ( 1148 session. stream . read ( & mut byte) ?, 11490 , 1150"canceled handshake retained TCP socket" 1151); 1152 closed. send (()). unwrap (); 1153Ok (()) 1154}); 1155let ( _dir, mut child) = common:: spawn ( & connect ( 1156& url, 1157r#" 1158import { stdin } from "avril:std"; 1159const reader = stdin.getReader(); 1160ws.onopen = () => { throw Error('Pending handshake opened'); }; 1161ws.onclose = event => { 1162if (event.wasClean || event.code !== 1006) throw Error('Failed close'); 1163console.log('canceled'); 1164}; 1165await reader.read(); 1166ws.close(); 1167await reader.read(); 1168reader.releaseLock(); 1169"# , 1170)); 1171 handshake. recv_timeout ( Duration :: from_secs ( 5 )). unwrap (); 1172let mut stdin = child. stdin . take (). unwrap (); 1173 stdin. write_all ( b"a" ). unwrap (); 1174 disconnected. recv_timeout ( Duration :: from_secs ( 5 )). unwrap (); 1175 stdin. write_all ( b"b" ). unwrap (); 1176drop ( stdin); 1177let out = common:: wait ( child); 1178assert! ( 1179 out. status . success (), 1180"{}" , 1181String :: from_utf8_lossy ( & out. stderr ) 1182); 1183assert_eq! ( out. stdout , b"canceled\n" ); 1184 thread. join (). unwrap (); 1185} 1186 1187# [ test ] 1188fn malformed_wire_frames_fail_with_protocol_or_utf8_close () { 1189let cases: & [( & str , & [ u8 ], u16 )] =& [ 1190( "masked-server-text" , b"\x81\x82\0\0\0\0hi" , 1002 ), 1191( 1192"overlapping-text" , 1193b"\x01\x03old\x01\x03new\x80\x03end" , 11941002 , 1195), 1196( "interleaved-binary" , b"\x02\x01a\x01\x01b\x80\x01c" , 1002 ), 1197( "interleaved-complete-message" , b"\x01\x01a\x82\x01b" , 1002 ), 1198( 1199"incomplete-fragmented-utf8" , 1200b"\x01\x03hi\xe2\x80\x00" , 12011007 , 1202), 1203( "invalid-fragmented-utf8" , b"\x01\x01\xc2\x80\x01x" , 1007 ), 1204( "nonminimal-16-bit-length" , b"\x82\x7e\x00\x01x" , 1002 ), 1205( 1206"nonminimal-64-bit-length" , 1207b"\x82\x7f\0\0\0\0\0\0\0\x01x" , 12081002 , 1209), 1210( 1211"negative-64-bit-length" , 1212b"\x82\x7f\x80\0\0\0\0\0\0\0" , 12131002 , 1214), 1215( "unexpected-continuation" , b"\x80\x00" , 1002 ), 1216( "reserved-bits" , b"\xc1\x00" , 1002 ), 1217( "reserved-opcode" , b"\x83\x00" , 1002 ), 1218( "fragmented-control" , b"\x09\x00" , 1002 ), 1219]; 1220for & ( name, wire, code) in cases{ 1221let ( url, thread) =server ( 1 , move |mut session|{ 1222 session. accept ( None ) ?; 1223 session. stream . write_all ( wire) ?; 1224let close = session. read () ?; 1225assert_eq! ( close. opcode , CLOSE , "{name}" ); 1226assert_eq! ( close. code (), code, "{name}" ); 1227Ok (()) 1228}); 1229let out = common:: run ( 1230& connect ( 1231& url, 1232r#" 1233let errors = 0; 1234ws.onmessage = () => { throw Error('Malformed message delivered'); }; 1235ws.onerror = () => errors++; 1236ws.onclose = event => { 1237if (event.wasClean || event.code !== 1006 || errors !== 1) { 1238throw Error('Malformed frame did not fail the connection'); 1239} 1240console.log('framing failed'); 1241}; 1242"# , 1243), 1244& [], 1245); 1246assert_eq! ( out. stdout , b"framing failed\n" , "{name}" ); 1247 thread. join (). unwrap (); 1248} 1249} 1250 1251# [ test ] 1252fn fragmented_utf8_survives_control_frames_and_resets_between_messages () { 1253let ( url, thread) =server ( 1 , |mut session|{ 1254 session. accept ( None ) ?; 1255write_frame ( & mut session. stream , false , TEXT , b"a\xf0" ) ?; 1256write_frame ( & mut session. stream , false , CONTINUATION , b"\x9f" ) ?; 1257 session. write ( PING , b"between fragments" ) ?; 1258write_frame ( & mut session. stream , true , CONTINUATION , b"\x92\xa9" ) ?; 1259 session. write ( TEXT , b"next" ) ?; 1260let mut pong =false ; 1261loop { 1262let frame = session. read () ?; 1263match frame. opcode { 1264PONG =>{ 1265assert_eq! ( frame. payload , b"between fragments" ); 1266 pong =true ; 1267} 1268CLOSE =>{ 1269assert! ( pong, "control frames must not interrupt message assembly" ); 1270 session. write ( CLOSE , & frame. payload ) ?; 1271break ; 1272} 1273 _ =>panic! ( "unexpected client frame" ), 1274} 1275} 1276Ok (()) 1277}); 1278let out = common:: run ( 1279& connect ( 1280& url, 1281r#" 1282const messages = []; 1283ws.onmessage = event => { 1284messages.push(event.data); 1285if (messages.length === 2) { 1286if (messages[0] !== 'a\u{1f4a9}' || messages[1] !== 'next') throw Error('Fragmented UTF-8'); 1287ws.close(1000); 1288} 1289}; 1290ws.onclose = event => { 1291if (!event.wasClean || event.code !== 1000) throw Error('Close'); 1292console.log('fragmented utf8 checked'); 1293}; 1294"# , 1295), 1296& [], 1297); 1298assert_eq! ( out. stdout , b"fragmented utf8 checked\n" ); 1299 thread. join (). unwrap (); 1300} 1301 1302# [ test ] 1303fn message_size_limit_applies_before_reading_excess_payload () { 1304for fragmentedin [ false , true ] { 1305let ( url, thread) =server ( 1 , move |mut session|{ 1306 session. accept ( None ) ?; 1307if fragmented{ 1308let fragment =vec! [ 0 ; 8 * 1024 * 1024 ]; 1309write_frame ( & mut session. stream , false , BINARY , & fragment) ?; 1310write_frame ( & mut session. stream , false , CONTINUATION , & fragment) ?; 1311// No payload follows: the aggregate length alone must trigger failure. 1312 session. stream . write_all ( b"\x80\x01" ) ?; 1313} else { 1314 session. stream . write_all ( b"\x82\x7f" ) ?; 1315 session 1316. stream 1317. write_all ( & ( 16 * 1024 * 1024 +1u64 ). to_be_bytes ()) ?; 1318} 1319let close = session. read () ?; 1320assert_eq! ( close. opcode , CLOSE ); 1321assert_eq! ( close. code (), 1009 ); 1322Ok (()) 1323}); 1324let out = common:: run ( 1325& connect ( 1326& url, 1327r#" 1328let errors = 0; 1329ws.onerror = () => errors++; 1330ws.onmessage = () => { throw Error('Oversized message delivered'); }; 1331ws.onclose = event => { 1332if (event.wasClean || event.code !== 1006 || errors !== 1) throw Error('Oversized message'); 1333console.log('size limit checked'); 1334}; 1335"# , 1336), 1337& [], 1338); 1339assert_eq! ( out. stdout , b"size limit checked\n" ); 1340 thread. join (). unwrap (); 1341} 1342} 1343 1344# [ test ] 1345fn fragmented_message_at_size_limit_is_accepted () { 1346let ( url, thread) =server ( 1 , |mut session|{ 1347 session. accept ( None ) ?; 1348let fragment =vec! [ 0x5a ; 8 * 1024 * 1024 ]; 1349write_frame ( & mut session. stream , false , BINARY , & fragment) ?; 1350write_frame ( & mut session. stream , true , CONTINUATION , & fragment) ?; 1351let close = session. read () ?; 1352assert_eq! ( close. opcode , CLOSE ); 1353 session. write ( CLOSE , & close. payload ) ?; 1354Ok (()) 1355}); 1356let out = common:: run ( 1357& connect ( 1358& url, 1359r#" 1360ws.binaryType = 'arraybuffer'; 1361let messages = 0; 1362ws.onmessage = event => { 1363const bytes = new Uint8Array(event.data); 1364if (++messages !== 1 || bytes.length !== 16 * 1024 * 1024 || 1365bytes[0] !== 0x5a || bytes[bytes.length - 1] !== 0x5a) throw Error('Size limit boundary'); 1366ws.close(1000); 1367}; 1368ws.onclose = event => { 1369if (!event.wasClean || event.code !== 1000 || messages !== 1) throw Error('Close'); 1370console.log('size boundary checked'); 1371}; 1372"# , 1373), 1374& [], 1375); 1376assert_eq! ( out. stdout , b"size boundary checked\n" ); 1377 thread. join (). unwrap (); 1378}