char/avril
(wip) why'd you have to go and make things so complicaaaaated?
git clone https://git.t4t.associates/char/avril
ec3de1c
main
1use std:: borrow:: Cow ; 2use std:: collections:: HashMap ; 3use std:: io; 4use std:: sync:: Arc ; 5use std:: sync:: Mutex ; 6use std:: sync:: atomic::{ AtomicU64 , Ordering }; 7use std:: time:: Duration ; 8 9use avril_caps:: Capabilities ; 10use avril_core:: serde_v8::{ JsBuffer , ToJsBuffer }; 11use avril_core::{ Extension , Op }; 12use base64:: Engine ; 13use base64:: engine:: general_purpose:: STANDARD as BASE64 ; 14use bytes:: Bytes ; 15use fastwebsockets::{ Frame , OpCode , WebSocketError , WebSocketWrite }; 16use futures:: channel:: oneshot; 17use http_body_util:: Empty ; 18use hyper:: body:: Incoming ; 19use hyper:: header::{ self , HeaderValue }; 20use hyper::{ Method , Request , StatusCode }; 21use hyper_util:: rt:: TokioIo ; 22use rustls:: RootCertStore ; 23use sha1::{ Digest , Sha1 }; 24use tokio:: io::{ AsyncRead , AsyncWrite , ReadHalf , WriteHalf }; 25use tokio:: net:: TcpStream ; 26use tokio:: sync::{ Mutex as AsyncMutex , mpsc}; 27use tokio_rustls::{ TlsConnector , rustls:: pki_types:: ServerName }; 28use tokio_util:: sync:: CancellationToken ; 29use tokio_util:: task:: AbortOnDropHandle ; 30use url::{ Host , Url }; 31 32mod reader; 33 34type Error =Box < dyn std:: error:: Error +Send +Sync >; 35/// Undelivered events; the guest pump drains this promptly, stalling the read 36/// loop (and with it the peer) when it falls behind. 37const EVENT_QUEUE : usize =64 ; 38/// Frames read but not yet handled; bounds what a stalled guest can buffer. 39const READ_QUEUE : usize =16 ; 40/// RFC 6455 §7.1.5 recommends waiting a reasonable time for the peer's close frame. 41const CLOSE_TIMEOUT : Duration =Duration :: from_secs ( 30 ); 42const TCP_CLOSE_TIMEOUT : Duration =Duration :: from_secs ( 2 ); 43const MAX_ID : u64 =9_007_199_254_740_991 ; 44 45trait Duplex : AsyncRead +AsyncWrite {} 46impl < T : AsyncRead +AsyncWrite > Duplex for T {} 47type Socket =Box < dyn Duplex +Send +Unpin >; 48type Negotiated =TokioIo < hyper:: upgrade:: Upgraded >; 49/// Reading runs in its own task: decoding consumes frame bytes across 50/// await points, so a dropped read desynchronizes the stream. It is reached 51/// through a channel instead, whose receive is cancel-safe. 52type Reader = reader:: Reader < ReadHalf < Negotiated >>; 53type Writer =WebSocketWrite < WriteHalf < Negotiated >>; 54 55/// A received frame, owned so it can cross a task boundary. 56struct Received { 57opcode : OpCode , 58payload : Vec < u8 >, 59} 60 61enum FrameKind { 62Text , 63Binary , 64} 65 66enum Command { 67Send ( FrameKind , Vec < u8 >, oneshot:: Sender < bool >), 68Close ( Option < Vec < u8 >>), 69} 70 71struct Resource { 72/// Aborts a connection that was not yet established, per close() during CONNECTING. 73canceled : CancellationToken , 74commands : mpsc:: UnboundedSender < Command >, 75events : AsyncMutex < mpsc:: Receiver < Event >>, 76} 77 78/// One event per pump turn, shaped for `serde_v8` as a plain object. 79# [ derive ( serde :: Serialize )] 80# [ serde ( tag = "kind" , rename_all = "lowercase" )] 81enum Event { 82Open { 83protocol : String , 84}, 85Text { 86data : String , 87}, 88Binary { 89data : ToJsBuffer , 90}, 91Error { 92message : String , 93}, 94Closing , 95Close { 96clean : bool , 97code : u16 , 98reason : String , 99}, 100} 101 102struct CreatedSocket { 103id : u64 , 104resource : Arc < Resource >, 105commands : mpsc:: UnboundedReceiver < Command >, 106events : mpsc:: Sender < Event >, 107} 108 109pub struct Sockets { 110runtime : tokio:: runtime:: Handle , 111tls : TlsConnector , 112resources : Arc < Mutex < HashMap < u64 , Arc < Resource >>>>, 113next : AtomicU64 , 114caps : Arc < Capabilities >, 115} 116 117impl Sockets { 118fn create ( & self ) -> io:: Result < CreatedSocket > { 119let id =self 120. next 121. fetch_update ( Ordering :: Relaxed , Ordering :: Relaxed , |id|{ 122( id <=MAX_ID ). then_some ( id +1 ) 123}) 124. map_err ( |_| io:: Error :: other ( "WebSocket IDs exhausted" )) ?; 125let ( commands, receiving) = mpsc:: unbounded_channel (); 126let ( events, event_queue) = mpsc:: channel ( EVENT_QUEUE ); 127let resource =Arc :: new ( Resource { 128canceled : CancellationToken :: new (), 129 commands, 130events : AsyncMutex :: new ( event_queue), 131}); 132self . resources . lock (). unwrap (). insert ( id, resource. clone ()); 133Ok ( CreatedSocket { 134 id, 135 resource, 136commands : receiving, 137 events, 138}) 139} 140 141pub fn accept ( & self , upgrade : avril_http:: Upgrade , protocol : String ) -> io:: Result < u64 > { 142let CreatedSocket { id, resource, commands, events} =self . create () ?; 143self . runtime . spawn ( async move { 144let server_abort = upgrade. canceled . clone (); 145let accepted = tokio:: select!{ 146 _ = resource. canceled . cancelled () =>Err ( "WebSocket upgrade canceled" . into ()), 147 result = upgrade. accept () => result, 148}; 149let ( stream, _lease) =match accepted{ 150Ok ( accepted) => accepted, 151Err ( error) =>{ 152fail ( & events, error. to_string ()). await ; 153return ; 154} 155}; 156let ( reader, writer) = tokio:: io:: split ( TokioIo :: new ( stream)); 157let ( _, writer) = fastwebsockets:: after_handshake_split ( 158 tokio:: io:: empty (), 159 writer, 160 fastwebsockets:: Role :: Server , 161); 162connected ( 163( reader:: Reader :: new ( reader, true ), writer, protocol), 164 resource. canceled . clone (), 165 commands, 166 events, 167Some ( server_abort), 168) 169. await ; 170}); 171Ok ( id) 172} 173 174fn resource ( & self , id : u64 ) ->Result < Arc < Resource >, Error > { 175self . resources 176. lock () 177. unwrap () 178. get ( & id) 179. cloned () 180. ok_or_else ( ||"WebSocket is closed" . into ()) 181} 182} 183 184fn accept_key ( key : & str ) ->String { 185let mut hasher =Sha1 :: new (); 186 hasher. update ( key. as_bytes ()); 187 hasher. update ( b"258EAFA5-E914-47DA-95CA-C5AB0DC85B11" ); 188BASE64 . encode ( hasher. finalize ()) 189} 190 191async fn attempt ( 192tls : & TlsConnector , 193url : & Url , 194protocols : & [ String ], 195user_agent : & str , 196) ->Result <( Reader , Writer , String ), Error > { 197let user_agent = user_agent 198. chars () 199. map ( |c| u8:: try_from ( cas u32 )) 200. collect ::< Result < Vec < _ >, _ >>() ?; 201let user_agent =HeaderValue :: from_bytes ( & user_agent) ?; 202// hosts serialize with brackets for IPv6, which neither TcpStream nor SNI accept. 203let ( host, server_name) =match url. host () { 204Some ( Host :: Domain ( domain)) =>( 205Cow :: Borrowed ( domain), 206ServerName :: try_from ( domain. to_owned ()) ?, 207), 208Some ( Host :: Ipv4 ( ip)) =>( Cow :: Owned ( ip. to_string ()), ServerName :: IpAddress ( ip. into ())), 209Some ( Host :: Ipv6 ( ip)) =>( Cow :: Owned ( ip. to_string ()), ServerName :: IpAddress ( ip. into ())), 210None =>return Err ( "Missing WebSocket host" . into ()), 211}; 212let port = url 213. port_or_known_default () 214. ok_or ( "Missing WebSocket port" ) ?; 215let socket: Socket =if url. scheme () =="wss" { 216let stream =TcpStream :: connect (( host. as_ref (), port)). await ?; 217Box :: new ( tls. connect ( server_name, stream). await ?) 218} else { 219Box :: new ( TcpStream :: connect (( host. as_ref (), port)). await ?) 220}; 221// The request target is origin-form, as the fetch-integrated spec requires. 222let target =match url. query () { 223Some ( query) =>format! ( "{}?{query}" , url. path ()), 224None => url. path (). to_owned (), 225}; 226let host_header =match url. port () { 227Some ( port) =>format! ( "{}:{port}" , url. host (). ok_or ( "Missing WebSocket host" ) ?), 228None => url. host (). ok_or ( "Missing WebSocket host" ) ?. to_string (), 229}; 230let key = fastwebsockets:: handshake:: generate_key (); 231let mut request =Request :: builder () 232. method ( Method :: GET ) 233. uri ( target) 234. header ( header:: HOST , host_header) 235. header ( header:: UPGRADE , "websocket" ) 236. header ( header:: CONNECTION , "Upgrade" ) 237. header ( "sec-websocket-key" , key. as_str ()) 238. header ( "sec-websocket-version" , "13" ) 239. header ( header:: USER_AGENT , user_agent); 240if !protocols. is_empty () { 241 request = request. header ( "sec-websocket-protocol" , protocols. join ( ", " )); 242} 243let request = request. body ( Empty ::< Bytes >:: new ()) ?; 244let ( mut sender, connection) = 245 hyper:: client:: conn:: http1:: handshake ( TokioIo :: new ( socket)). await ?; 246// An abandoned handshake must not leave Hyper holding the transport. 247let _connection =AbortOnDropHandle :: new ( tokio:: spawn ( connection. with_upgrades ())); 248let mut response = sender. send_request ( request). await ?; 249let protocol =validate ( & response, & key, protocols) ?; 250let upgraded = hyper:: upgrade:: on ( & mut response). await ?; 251let ( reader, writer) = tokio:: io:: split ( TokioIo :: new ( upgraded)); 252let ( _, writer) = fastwebsockets:: after_handshake_split ( 253 tokio:: io:: empty (), 254 writer, 255 fastwebsockets:: Role :: Client , 256); 257Ok (( reader:: Reader :: new ( reader, false ), writer, protocol)) 258} 259 260fn validate ( 261response : & hyper:: Response < Incoming >, 262key : & str , 263protocols : & [ String ], 264) ->Result < String , Error > { 265let headers = response. headers (); 266let contains_token = |name : & str , token : & str |{ 267 headers. get_all ( name). iter (). any ( |value|{ 268 value. to_str (). is_ok_and ( |value|{ 269 value 270. split ( ',' ) 271. any ( |value| value. trim (). eq_ignore_ascii_case ( token)) 272}) 273}) 274}; 275if response. status () !=StatusCode :: SWITCHING_PROTOCOLS 276 || !contains_token ( "upgrade" , "websocket" ) 277 || !contains_token ( "connection" , "upgrade" ) 278{ 279return Err ( "The WebSocket handshake response is invalid" . into ()); 280} 281let accepted: Vec < & HeaderValue > = headers. get_all ( "sec-websocket-accept" ). iter (). collect (); 282if accepted. len () !=1 || accepted[ 0 ]. to_str (). ok () !=Some ( accept_key ( key). as_str ()) { 283return Err ( "The WebSocket handshake response is invalid" . into ()); 284} 285// No extensions are offered, so any response extension is unsupported. 286if headers. contains_key ( "sec-websocket-extensions" ) { 287return Err ( "The WebSocket server selected an unsupported extension" . into ()); 288} 289let chosen: Vec < & HeaderValue > = headers. get_all ( "sec-websocket-protocol" ). iter (). collect (); 290if protocols. is_empty () { 291if !chosen. is_empty () { 292return Err ( "The WebSocket server selected an unexpected subprotocol" . into ()); 293} 294return Ok ( String :: new ()); 295} 296if chosen. len () !=1 { 297return Err ( "The WebSocket server did not select one subprotocol" . into ()); 298} 299let value = chosen[ 0 ] 300. to_str () 301. map_err ( |_|"The WebSocket server selected an invalid subprotocol" ) ?; 302if value. is_empty () || !protocols. iter (). any ( |protocol| protocol == value) { 303return Err ( "The WebSocket server selected an invalid subprotocol" . into ()); 304} 305Ok ( value. to_owned ()) 306} 307 308/// Validate a close frame payload, returning its code and reason or the failure code. 309fn close_payload ( payload : & [ u8 ]) ->Result < Option <( u16 , String )>, u16 > { 310match payload. len () { 3110 =>Ok ( None ), 3121 =>Err ( 1002 ), 313 _ =>{ 314let code = u16:: from_be_bytes ([ payload[ 0 ], payload[ 1 ]]); 315let reason =match std:: str:: from_utf8 ( & payload[ 2 ..]) { 316Ok ( reason) => reason. to_owned (), 317Err ( _) =>return Err ( 1007 ), 318}; 319if matches! ( code, 1000 ..=1003 |1007 ..=1014 |3000 ..=4999 ) { 320Ok ( Some (( code, reason))) 321} else { 322Err ( 1002 ) 323} 324} 325} 326} 327 328/// The close frame code for a failed connection, per RFC 6455 §7.1.7. 329fn failure_code ( error : & WebSocketError ) ->u16 { 330match error{ 331WebSocketError :: InvalidUTF8 =>1007 , 332WebSocketError :: FrameTooLarge =>1009 , 333WebSocketError :: IoError ( _) |WebSocketError :: UnexpectedEOF =>1006 , 334 _ =>1002 , 335} 336} 337 338/// Forward frames to the event loop, which owns the write half. 339async fn read_frames ( 340mut reader : reader:: Reader < impl AsyncRead +Unpin >, 341received : mpsc:: Sender < Result < Received , WebSocketError >>, 342transport_closed : oneshot:: Sender <()>, 343shutdown : CancellationToken , 344) { 345loop { 346let frame = tokio:: select!{ 347 _ = shutdown. cancelled () =>return , 348 frame = reader. read () => frame, 349}; 350match frame{ 351Ok ( frame) =>{ 352let close = frame. opcode ==OpCode :: Close ; 353if received. send ( Ok ( frame)). await . is_err () { 354return ; 355} 356if close{ 357// After Close, only transport termination matters; do not assemble more messages. 358let mut discard = tokio:: io:: sink (); 359 tokio:: select!{ 360 _ = shutdown. cancelled () =>return , 361 _ = tokio:: io:: copy ( & mut reader. stream , & mut discard) =>{ 362let _ = transport_closed. send (()); 363} 364} 365return ; 366} 367} 368Err ( error) =>{ 369let _ = received. send ( Err ( error)). await ; 370return ; 371} 372} 373} 374} 375 376async fn write ( 377writer : & mut WebSocketWrite < impl AsyncWrite +Unpin >, 378frame : Frame < ' _ >, 379) ->Result <(), WebSocketError > { 380 writer. write_frame ( frame). await ?; 381// TLS buffers writes until flushed. 382 writer. flush (). await 383} 384 385async fn push ( events : & mpsc:: Sender < Event >, event : Event ) { 386// A dropped receiver means the connection is already gone. 387let _ = events. send ( event). await ; 388} 389 390async fn closed ( events : & mpsc:: Sender < Event >, clean : bool , code : u16 , reason : String ) { 391push ( events, Event :: Close { clean, code, reason}). await ; 392} 393 394/// Fail the connection without an established stream. 395async fn fail ( events : & mpsc:: Sender < Event >, message : String ) { 396push ( events, Event :: Error { message}). await ; 397closed ( events, false , 1006 , String :: new ()). await ; 398} 399 400/// Fail an established connection: send the close frame for the failure, then error and close. 401async fn abort ( 402writer : & mut WebSocketWrite < impl AsyncWrite +Unpin >, 403events : & mpsc:: Sender < Event >, 404message : String , 405code : u16 , 406wrote_close : bool , 407) { 408// An abruptly closed transport cannot carry a close frame (code 1006). 409if !wrote_close && code !=1006 { 410let _ = tokio:: time:: timeout ( CLOSE_TIMEOUT , write ( writer, Frame :: close ( code, b"" ))). await ; 411} 412push ( events, Event :: Error { message}). await ; 413closed ( events, false , 1006 , String :: new ()). await ; 414} 415 416async fn connect ( 417tls : TlsConnector , 418canceled : CancellationToken , 419commands : mpsc:: UnboundedReceiver < Command >, 420events : mpsc:: Sender < Event >, 421url : Url , 422protocols : Vec < String >, 423user_agent : String , 424) { 425let established = tokio:: select!{ 426 _ = canceled. cancelled () =>{ 427Err ( "The WebSocket connection was closed while connecting" . into ()) 428} 429 result =attempt ( & tls, & url, & protocols, & user_agent) =>match result{ 430Ok ( established) =>Ok ( established), 431Err ( error) =>Err ( format! ( "failed to connect to WebSocket: {error}" )), 432}, 433}; 434let established =match established{ 435Ok ( established) => established, 436Err ( message) =>{ 437fail ( & events, message). await ; 438return ; 439} 440}; 441if canceled. is_cancelled () { 442fail ( 443& events, 444"The WebSocket connection was closed while connecting" . into (), 445) 446. await ; 447return ; 448} 449connected ( established, canceled, commands, events, None ). await ; 450} 451 452async fn connected ( 453( reader, mut writer, protocol): ( Reader , Writer , String ), 454canceled : CancellationToken , 455commands : mpsc:: UnboundedReceiver < Command >, 456events : mpsc:: Sender < Event >, 457server_abort : Option < CancellationToken >, 458) { 459push ( & events, Event :: Open { protocol}). await ; 460let ( received, frames) = mpsc:: channel ( READ_QUEUE ); 461let shutdown =CancellationToken :: new (); 462let ( ended, transport_closed) = oneshot:: channel (); 463let reading = tokio:: spawn ( read_frames ( reader, received, ended, shutdown. clone ())); 464 tokio:: select!{ 465// close() during CONNECTING can land after `open` was queued. Keep 466// cancellation outside the loop so it also interrupts blocked writes. 467 _ = canceled. cancelled () =>{ 468fail ( & events, "The WebSocket connection was closed while connecting" . into ()). await ; 469} 470 _ =async { 471if let Some ( token) =& server_abort{ token. cancelled (). await ; } 472else { std:: future:: pending ::<()>(). await ; } 473} =>{ closed ( & events, false , 1006 , String :: new ()). await ; } 474 _ = events. closed () =>{} 475 _ =stream_loop ( & mut writer, commands, frames, transport_closed, & events, server_abort. is_some ()) =>{} 476} 477// Drops the read half, closing the transport with the connection. 478 shutdown. cancel (); 479let _ = reading. await ; 480} 481 482async fn stream_loop ( 483writer : & mut WebSocketWrite < impl AsyncWrite +Unpin >, 484mut commands : mpsc:: UnboundedReceiver < Command >, 485mut frames : mpsc:: Receiver < Result < Received , WebSocketError >>, 486transport_closed : oneshot:: Receiver <()>, 487events : & mpsc:: Sender < Event >, 488server : bool , 489) { 490let mut wrote_close =false ; 491let mut timer =Box :: pin ( tokio:: time:: sleep ( CLOSE_TIMEOUT )); 492loop { 493 tokio:: select!{ 494 _ = timer. as_mut (), if wrote_close =>{ 495// The peer never completed the closing handshake. 496closed ( events, false , 1006 , String :: new ()). await ; 497return ; 498} 499 command = commands. recv () =>match command{ 500Some ( Command :: Send ( kind, payload, ack)) =>{ 501let result =if wrote_close{ 502Ok (()) 503} else { 504let frame =match kind{ 505FrameKind :: Text =>Frame :: text ( payload. into ()), 506FrameKind :: Binary =>Frame :: binary ( payload. into ()), 507}; 508write ( writer, frame). await 509}; 510let _ = ack. send ( result. is_ok () && !wrote_close); 511if let Err ( error) = result{ 512abort ( writer, events, error. to_string (), 1006 , wrote_close). await ; 513return ; 514} 515} 516Some ( Command :: Close ( payload)) =>{ 517if wrote_close{ 518continue ; 519} 520let frame =match payload{ 521Some ( payload) =>Frame :: close_raw ( payload. into ()), 522None =>Frame :: close_raw ( Vec :: new (). into ()), 523}; 524let deadline = tokio:: time:: Instant :: now () +CLOSE_TIMEOUT ; 525match tokio:: time:: timeout_at ( deadline, write ( writer, frame)). await { 526Ok ( Ok (())) =>{ 527 wrote_close =true ; 528 timer. as_mut (). reset ( deadline); 529} 530Ok ( Err ( error)) =>{ 531abort ( writer, events, error. to_string (), 1006 , wrote_close). await ; 532return ; 533} 534Err ( _) =>{ 535closed ( events, false , 1006 , String :: new ()). await ; 536return ; 537} 538} 539} 540None =>{ 541closed ( events, false , 1006 , String :: new ()). await ; 542return ; 543} 544}, 545 frame = frames. recv () =>match frame{ 546Some ( Err ( error)) =>{ 547if wrote_close{ 548closed ( events, false , 1006 , String :: new ()). await ; 549} else { 550abort ( writer, events, error. to_string (), failure_code ( & error), false ). await ; 551} 552return ; 553} 554None =>{ 555closed ( events, false , 1006 , String :: new ()). await ; 556return ; 557} 558Some ( Ok ( frame)) =>match frame. opcode { 559OpCode :: Ping if !wrote_close =>{ 560if let Err ( error) =write ( writer, Frame :: pong ( frame. payload . into ())). await { 561abort ( writer, events, error. to_string (), 1006 , wrote_close). await ; 562return ; 563} 564} 565OpCode :: Close =>match close_payload ( & frame. payload ) { 566Ok ( payload) =>{ 567push ( events, Event :: Closing ). await ; 568let ( code, reason) =match payload{ 569Some (( code, reason)) =>( code, reason), 570None =>( 1005 , String :: new ()), 571}; 572if !wrote_close{ 573let echo =Frame :: close_raw ( frame. payload . into ()); 574let result = tokio:: time:: timeout ( CLOSE_TIMEOUT , write ( writer, echo)). await ; 575let message =match result{ 576Ok ( Ok (())) =>None , 577Ok ( Err ( error)) =>Some ( error. to_string ()), 578Err ( _) =>Some ( "The WebSocket close reply timed out" . into ()), 579}; 580if let Some ( message) = message{ 581push ( events, Event :: Error { message}). await ; 582closed ( events, false , code, reason). await ; 583return ; 584} 585} 586// The handshake is complete. Closing our own transport after 587// the grace period is still a clean closure (RFC 6455 §7.1.1). 588if !server{ let _ = tokio:: time:: timeout ( TCP_CLOSE_TIMEOUT , transport_closed). await ; } 589closed ( events, true , code, reason). await ; 590return ; 591} 592Err ( code) =>{ 593abort ( 594 writer, 595 events, 596"The WebSocket connection received an invalid close frame" . into (), 597 code, 598 wrote_close, 599) 600. await ; 601return ; 602} 603}, 604OpCode :: Text =>match String :: from_utf8 ( frame. payload ) { 605Ok ( data) =>{ 606push ( events, Event :: Text { data}). await ; 607} 608Err ( _) =>{ 609abort ( 610 writer, 611 events, 612"The WebSocket connection received invalid UTF-8 in a text message" . into (), 6131007 , 614 wrote_close, 615) 616. await ; 617return ; 618} 619}, 620OpCode :: Binary =>{ 621let data =ToJsBuffer :: from ( frame. payload ); 622push ( events, Event :: Binary { data}). await ; 623} 624 _ =>{} 625}, 626}, 627} 628} 629} 630 631pub fn init ( 632runtime : tokio:: runtime:: Handle , 633caps : Arc < Capabilities >, 634) -> io:: Result <( Extension , Arc < Sockets >)> { 635let mut roots =RootCertStore :: empty (); 636 roots. extend ( webpki_roots:: TLS_SERVER_ROOTS . iter (). cloned ()); 637let config = rustls:: ClientConfig :: builder_with_provider ( Arc :: new ( 638 rustls:: crypto:: ring:: default_provider (), 639)) 640. with_safe_default_protocol_versions () 641. map_err ( io:: Error :: other) ? 642. with_root_certificates ( roots) 643. with_no_client_auth (); 644let tls =TlsConnector :: from ( Arc :: new ( config)); 645let state =Arc :: new ( Sockets { 646 runtime, 647 tls, 648resources : Arc :: new ( Mutex :: new ( HashMap :: new ())), 649next : AtomicU64 :: new ( 1 ), 650 caps, 651}); 652let create = state. clone (); 653let send = state. clone (); 654let close = state. clone (); 655let fail = state. clone (); 656let retire = state. clone (); 657let wait = state. clone (); 658Ok (( 659Extension { 660name : "ext:websocket" , 661dependencies : & [ "ext:blob" , "ext:encoding" , "ext:url" , "ext:web" ], 662ops : vec! [ 663Op :: new ( 664"op_ws_create" , 665move |( url, protocols, user_agent): ( String , Vec < String >, String ) |{ 666let url =Url :: parse ( & url). map_err ( io:: Error :: other) ?; 667if !matches! ( url. scheme (), "ws" |"wss" ) { 668return Err ( io:: Error :: other ( "Invalid WebSocket scheme" )); 669} 670let host = url 671. host_str () 672. ok_or_else ( || io:: Error :: other ( "Missing WebSocket host" )) ?; 673let port = url 674. port_or_known_default () 675. ok_or_else ( || io:: Error :: other ( "Missing WebSocket port" )) ?; 676 create 677. caps 678. check_net ( host, port) 679. map_err ( io:: Error :: other) ?; 680let CreatedSocket { 681 id, 682 resource, 683commands : receiving, 684 events, 685} = create. create () ?; 686 create. runtime . spawn ( connect ( 687 create. tls . clone (), 688 resource. canceled . clone (), 689 receiving, 690 events, 691 url, 692 protocols, 693 user_agent, 694)); 695Ok ::< _ , io:: Error >( id) 696}, 697), 698// The command is enqueued at dispatch so queue order equals call order; 699// the future only awaits the write acknowledgement. 700Op :: asynchronous_unreferenced ( 701"op_ws_send" , 702move |( id, text, bytes): ( u64 , bool , Option < JsBuffer >) |{ 703let ( ack, receive) = oneshot:: channel (); 704if let Ok ( resource) = send. resource ( id) { 705let kind =if text{ 706FrameKind :: Text 707} else { 708FrameKind :: Binary 709}; 710let payload = bytes. map ( |bytes| bytes. to_vec ()). unwrap_or_default (); 711let _ = resource. commands . send ( Command :: Send ( kind, payload, ack)); 712} else { 713drop ( ack); 714} 715async move { Ok ::< _ , io:: Error >( receive. await . unwrap_or ( false )) } 716}, 717), 718Op :: new ( 719"op_ws_close" , 720move |( id, code, reason): ( u64 , Option < u16 >, Option < String >) |{ 721let payload =match ( code, reason) { 722( Some ( code), Some ( reason)) =>{ 723let mut payload = code. to_be_bytes (). to_vec (); 724 payload. extend_from_slice ( reason. as_bytes ()); 725Some ( payload) 726} 727( Some ( code), None ) =>Some ( code. to_be_bytes (). to_vec ()), 728// A reason without a code is not a valid close payload; use 1000. 729( None , Some ( reason)) =>{ 730let mut payload =1000u16 . to_be_bytes (). to_vec (); 731 payload. extend_from_slice ( reason. as_bytes ()); 732Some ( payload) 733} 734( None , None ) =>None , 735}; 736if let Ok ( resource) = close. resource ( id) { 737let _ = resource. commands . send ( Command :: Close ( payload)); 738} 739Ok ::< _ , io:: Error >(()) 740}, 741), 742Op :: new ( "op_ws_fail" , move |id : u64 |{ 743if let Ok ( resource) = fail. resource ( id) { 744 resource. canceled . cancel (); 745} 746Ok ::< _ , io:: Error >(()) 747}), 748Op :: new ( "op_ws_retire" , move |id : u64 |{ 749if let Some ( resource) = retire. resources . lock (). unwrap (). remove ( & id) { 750 resource. canceled . cancel (); 751} 752Ok ::< _ , io:: Error >(()) 753}), 754Op :: asynchronous ( "op_ws_wait" , move |id : u64 |{ 755let resource = wait. resource ( id); 756async move { 757let Ok ( resource) = resourceelse { 758return Err ( io:: Error :: other ( "WebSocket is closed" )); 759}; 760let mut events = resource. events . lock (). await ; 761 events 762. recv () 763. await 764. ok_or_else ( || io:: Error :: other ( "WebSocket task ended" )) 765} 766}), 767], 768modules : & [ 769( 770"ext:websocket/main.js" , 771include_str! ( concat! ( env! ( "OUT_DIR" ), "/main.js" )), 772), 773( 774"ext:websocket/websocket.js" , 775include_str! ( concat! ( env! ( "OUT_DIR" ), "/websocket.js" )), 776), 777], 778entry_point : Some ( "ext:websocket/main.js" ), 779}, 780 state, 781)) 782} 783 784# [ cfg ( test )] 785mod tests{ 786use super :: * ; 787 788enum Input { 789Command ( Command ), 790Frame ( Result < Received , WebSocketError >), 791} 792 793async fn events_with_blocked_transport ( input : Input ) ->Vec < Event > { 794let ( socket, _peer) = tokio:: io:: duplex ( 1 ); 795let ws = fastwebsockets:: WebSocket :: after_handshake ( socket, fastwebsockets:: Role :: Client ); 796let ( _, mut writer) = ws. split ( tokio:: io:: split); 797let ( commands, queued) = mpsc:: unbounded_channel (); 798let ( frames, received) = mpsc:: channel ( 1 ); 799let ( events, mut emitted) = mpsc:: channel ( 2 ); 800let ( _ended, transport_closed) = oneshot:: channel (); 801match input{ 802Input :: Command ( command) => commands. send ( command). unwrap (), 803Input :: Frame ( frame) => frames. send ( frame). await . unwrap (), 804} 805let started = tokio:: time:: Instant :: now (); 806let connection = tokio:: spawn ( async move { 807stream_loop ( 808& mut writer, 809 queued, 810 received, 811 transport_closed, 812& events, 813false , 814) 815. await ; 816}); 817let mut result =Vec :: new (); 818while let Some ( event) = emitted. recv (). await { 819 result. push ( event); 820} 821 connection. await . unwrap (); 822assert_eq! ( started. elapsed (), Duration :: from_secs ( 30 )); 823 result 824} 825 826# [ tokio :: test ( start_paused = true )] 827async fn client_close_write_cannot_stall_shutdown () { 828let events =events_with_blocked_transport ( Input :: Command ( Command :: Close ( None ))). await ; 829assert! ( matches! ( 830 events. as_slice (), 831[ Event :: Close { clean : false , code : 1006 , reason}] if reason. is_empty () 832)); 833} 834 835# [ tokio :: test ( start_paused = true )] 836async fn peer_close_reply_cannot_stall_shutdown () { 837let events =events_with_blocked_transport ( Input :: Frame ( Ok ( Received { 838opcode : OpCode :: Close , 839payload : [ 3001u16 . to_be_bytes (). as_slice (), b"bye" ]. concat (), 840}))) 841. await ; 842assert! ( matches! ( 843 events. as_slice (), 844[ Event :: Closing , Event :: Error { ..}, Event :: Close { clean : false , code : 3001 , reason}] 845if reason =="bye" 846)); 847} 848 849# [ tokio :: test ( start_paused = true )] 850async fn failure_close_write_cannot_stall_shutdown () { 851let events = 852events_with_blocked_transport ( Input :: Frame ( Err ( WebSocketError :: InvalidValue ))). await ; 853assert! ( matches! ( 854 events. as_slice (), 855[ Event :: Error { ..}, Event :: Close { clean : false , code : 1006 , reason}] 856if reason. is_empty () 857)); 858} 859 860struct Connection { 861peer : tokio:: io:: DuplexStream , 862events : mpsc:: Receiver < Event >, 863task : tokio:: task:: JoinHandle <()>, 864_commands : mpsc:: UnboundedSender < Command >, 865} 866 867async fn completed_handshake ( client_initiated : bool ) ->Connection { 868use tokio:: io::{ AsyncReadExt , AsyncWriteExt }; 869 870let ( socket, mut peer) = tokio:: io:: duplex ( 1024 ); 871let ( reader, writer) = tokio:: io:: split ( socket); 872let ( _, mut writer) = fastwebsockets:: after_handshake_split ( 873 tokio:: io:: empty (), 874 writer, 875 fastwebsockets:: Role :: Client , 876); 877let ( commands, queued) = mpsc:: unbounded_channel (); 878let ( received, frames) = mpsc:: channel ( READ_QUEUE ); 879let ( ended, transport_closed) = oneshot:: channel (); 880let ( events, mut emitted) = mpsc:: channel ( EVENT_QUEUE ); 881let shutdown =CancellationToken :: new (); 882let reading = tokio:: spawn ( read_frames ( 883 reader:: Reader :: new ( reader, false ), 884 received, 885 ended, 886 shutdown. clone (), 887)); 888let task = tokio:: spawn ( async move { 889stream_loop ( 890& mut writer, 891 queued, 892 frames, 893 transport_closed, 894& events, 895false , 896) 897. await ; 898 shutdown. cancel (); 899 reading. await . unwrap (); 900}); 901let payload =[ 3001u16 . to_be_bytes (). as_slice (), b"bye" ]. concat (); 902let server_close =[ b"\x88\x05" . as_slice (), & payload]. concat (); 903if client_initiated{ 904 commands 905. send ( Command :: Close ( Some ( payload. clone ()))) 906. unwrap (); 907} else { 908 peer. write_all ( & server_close). await . unwrap (); 909} 910let mut reply =[ 0 ; 11 ]; 911 peer. read_exact ( & mut reply). await . unwrap (); 912assert_eq! ( & reply[ ..2 ], b"\x88\x85" ); 913for indexin 0 ..5 { 914assert_eq! ( reply[ 6 + index] ^ reply[ 2 + index %4 ], payload[ index]); 915} 916if client_initiated{ 917 peer. write_all ( & server_close). await . unwrap (); 918} 919assert! ( matches! ( emitted. recv (). await , Some ( Event :: Closing ))); 920Connection { 921 peer, 922events : emitted, 923 task, 924_commands : commands, 925} 926} 927 928async fn assert_waits_for_eof ( client_initiated : bool ) { 929use tokio:: io:: AsyncWriteExt ; 930 931let mut connection =completed_handshake ( client_initiated). await ; 932 tokio:: time:: advance ( Duration :: from_secs ( 1 )). await ; 933assert! ( matches! ( 934 connection. events . try_recv (), 935Err ( mpsc:: error:: TryRecvError :: Empty ) 936)); 937 connection. peer . shutdown (). await . unwrap (); 938assert! ( matches! ( connection. events . recv (). await , 939Some ( Event :: Close { clean : true , code : 3001 , reason}) if reason =="bye" )); 940 connection. task . await . unwrap (); 941assert! ( connection. events . recv (). await . is_none ()); 942} 943 944# [ tokio :: test ( start_paused = true )] 945async fn client_close_waits_for_transport_eof () { 946assert_waits_for_eof ( true ). await ; 947} 948 949# [ tokio :: test ( start_paused = true )] 950async fn peer_close_waits_for_transport_eof () { 951assert_waits_for_eof ( false ). await ; 952} 953 954# [ tokio :: test ( start_paused = true )] 955async fn tcp_close_timeout_still_reports_clean_close () { 956use tokio:: io:: AsyncReadExt ; 957 958let mut connection =completed_handshake ( false ). await ; 959let started = tokio:: time:: Instant :: now (); 960assert! ( matches! ( connection. events . recv (). await , 961Some ( Event :: Close { clean : true , code : 3001 , reason}) if reason =="bye" )); 962assert_eq! ( started. elapsed (), Duration :: from_secs ( 2 )); 963 connection. task . await . unwrap (); 964let mut byte =[ 0 ]; 965assert_eq! ( connection. peer . read ( & mut byte). await . unwrap (), 0 ); 966assert! ( connection. events . recv (). await . is_none ()); 967} 968}