char/avril
(wip) why'd you have to go and make things so complicaaaaated?
git clone https://git.t4t.associates/char/avril
1f64888
main
1mod common; 2 3use std:: io::{ BufRead , BufReader }; 4 5# [ test ] 6fn shared_views_keep_receive_aliasing_and_send_snapshots () { 7let output = common:: run ( 8r#" 9import {listenDatagram} from 'avril:std'; 10const receiver = listenDatagram({port:0}), sender = listenDatagram({port:0}); 11const storage = new SharedArrayBuffer(12), target = new Uint8Array(storage, 3, 4); 12const input = new Uint8Array(new SharedArrayBuffer(4)); input.set([1,2,3,4]); 13const pending = receiver.receive(target); 14const sending = sender.send(input, receiver.addr); 15input.fill(9); 16if (await sending !== 4) throw Error('send count'); 17const [message, remote] = await pending; 18if (message.buffer !== storage || message.byteOffset !== 3 || message.length !== 4 || 19message.join() !== '1,2,3,4' || target.join() !== '1,2,3,4') throw Error('shared view or snapshot'); 20if (remote.transport !== 'udp' || remote.port !== sender.addr.port || 21Object.getPrototypeOf(remote) !== null || !Object.isFrozen(remote)) throw Error('peer address'); 22receiver.close(); sender.close(); 23console.log('shared datagrams'); 24"# , 25& [], 26); 27assert_eq! ( output. stdout , b"shared datagrams\n" ); 28} 29 30# [ test ] 31fn datagrams_preserve_boundaries_empty_messages_and_sender_addresses () { 32let output = common:: run ( 33r#" 34import {listenDatagram,DatagramConn} from 'avril:std'; 35const a=listenDatagram({port:0}),b=listenDatagram({port:0}); 36if(!(a instanceof DatagramConn)||a.addr.transport!=='udp'||!Object.isFrozen(a.addr))throw Error('socket'); 37const bytes=new Uint8Array([1,2,3]),sending=a.send(bytes,b.addr);bytes.fill(0);if(await sending!==3)throw Error('send'); 38let [packet,peer]=await b.receive();if(packet.join()!=='1,2,3'||peer.port!==a.addr.port||peer.transport!=='udp')throw Error('packet'); 39await a.send(new Uint8Array(),b.addr);[packet,peer]=await b.receive();if(packet.length!==0||peer.port!==a.addr.port)throw Error('empty datagram'); 40const large=new Uint8Array(65507);large.fill(42);if(await a.send(large,b.addr)!==large.length)throw Error('large send');[packet]=await b.receive();if(packet.length!==large.length||packet.some(byte=>byte!==42))throw Error('large datagram'); 41a.setBroadcast();a.setBroadcast(false);a.close();b.close();console.log('datagrams'); 42"# , 43& [], 44); 45assert_eq! ( output. stdout , b"datagrams\n" ); 46} 47 48# [ test ] 49fn receive_buffers_truncate_only_the_current_datagram_and_empty_buffers_consume_it () { 50let output = common:: run ( 51r#" 52import {listenDatagram} from 'avril:std'; 53const a=listenDatagram({port:0}),b=listenDatagram({port:0}); 54const buffer=new Uint8Array(5);await a.send(new Uint8Array([1,2,3,4]),b.addr); 55const [packet]=await b.receive(buffer.subarray(1,3));if(packet.buffer!==buffer.buffer||packet.byteOffset!==1||packet.join()!=='1,2')throw Error('buffer'); 56await a.send(new Uint8Array([9]),b.addr);const [next]=await b.receive();if(next.join()!=='9')throw Error('truncated packet spilled'); 57await a.send(new Uint8Array([4]),b.addr);const [empty]=await b.receive(new Uint8Array());if(empty.length!==0)throw Error('empty buffer'); 58await a.send(new Uint8Array([5]),b.addr);const [last]=await b.receive();if(last.join()!=='5')throw Error('empty receive did not consume'); 59a.close();b.close();console.log('buffers'); 60"# , 61& [], 62); 63assert_eq! ( output. stdout , b"buffers\n" ); 64} 65 66# [ test ] 67fn closing_cancels_receive_and_iteration_break_closes_the_socket () { 68let output = common:: run ( 69r#" 70import {listenDatagram,errors} from 'avril:std'; 71const socket=listenDatagram({port:0}),pending=socket.receive().catch(e=>e);socket.close();if(!((await pending) instanceof errors.BadResource))throw Error('cancellation');socket[Symbol.dispose](); 72const a=listenDatagram({port:0}),b=listenDatagram({port:0}); 73const consuming=(async()=>{for await(const [packet] of b){if(packet.join()!=='7')throw Error('iterator');break;}})(); 74await a.send(new Uint8Array([7]),b.addr);await consuming; 75if(!(await b[Symbol.asyncIterator]().next()).done)throw Error('closed iteration');a.close();console.log('closed'); 76"# , 77& [], 78); 79assert_eq! ( output. stdout , b"closed\n" ); 80} 81 82# [ test ] 83fn send_requires_destination_authority_not_just_bind_authority () { 84let output = common:: run ( 85r#" 86import {listenDatagram,pledge,errors} from 'avril:std'; 87const a=listenDatagram({port:0}),b=listenDatagram({port:0}); 88pledge({net:{allow:[`127.0.0.1:${a.addr.port}`]}}); 89try{await a.send(new Uint8Array([1]),b.addr);throw Error('destination escaped');}catch(e){if(!(e instanceof errors.PermissionDenied))throw e;} 90const received=a.receive();await a.send(new Uint8Array([8]),a.addr);if((await received)[0][0]!==8)throw Error('authorized peer'); 91a.close();b.close();console.log('scoped'); 92"# , 93& [], 94); 95assert_eq! ( output. stdout , b"scoped\n" ); 96} 97 98# [ test ] 99fn revocation_blocks_queued_sends_binding_and_broadcast_changes () { 100let output = common:: run ( 101r#" 102import {listenDatagram,pledge,errors} from 'avril:std'; 103const a=listenDatagram({port:0}),send=a.send(new Uint8Array([1]),a.addr);pledge({net:false}); 104for(const operation of [()=>send,()=>a.receive(),()=>a.setBroadcast(),()=>listenDatagram({port:0})]){ 105try{await operation();throw Error('authority escaped');}catch(e){if(!(e instanceof errors.PermissionDenied))throw e;} 106} 107a.close();console.log('revoked'); 108"# , 109& [], 110); 111assert_eq! ( output. stdout , b"revoked\n" ); 112} 113 114# [ test ] 115fn receiving_rechecks_authority_after_waiting_for_a_packet () { 116let ( _directory, mut child) = common:: spawn ( 117r#" 118import {listenDatagram,pledge,errors} from 'avril:std'; 119const a=listenDatagram({port:0});console.log(a.addr.port);const reading=a.receive();await new Promise(resolve=>setTimeout(resolve,10));pledge({net:false});console.log('narrowed'); 120try{await reading;throw Error('received without authority');}catch(e){if(!(e instanceof errors.PermissionDenied))throw e;} 121a.close();console.log('denied'); 122"# , 123); 124let mut stdout =BufReader :: new ( child. stdout . take (). unwrap ()); 125let mut line =String :: new (); 126 stdout. read_line ( & mut line). unwrap (); 127let port = line. trim (). parse ::< u16 >(). unwrap (); 128 line. clear (); 129 stdout. read_line ( & mut line). unwrap (); 130assert_eq! ( line, "narrowed\n" ); 131let socket = std:: net:: UdpSocket :: bind ( "127.0.0.1:0" ). unwrap (); 132 socket. send_to ( b"x" , ( "127.0.0.1" , port)). unwrap (); 133 line. clear (); 134 stdout. read_line ( & mut line). unwrap (); 135assert_eq! ( line, "denied\n" ); 136drop ( stdout); 137let output = common:: wait ( child); 138assert! ( 139 output. status . success (), 140"{}" , 141String :: from_utf8_lossy ( & output. stderr ) 142); 143} 144 145# [ test ] 146fn worker_termination_releases_udp_sockets_and_workers_cannot_escalate_authority () { 147let directory = tempfile:: tempdir (). unwrap (); 148let source = directory. path (). join ( "worker.js" ); 149 std:: fs:: write ( & source, "import {listenDatagram} from 'avril:std';try{const a=listenDatagram({port:0});postMessage(a.addr.port);await a.receive();}catch(e){postMessage(e.name);close();}" ). unwrap (); 150let url = url:: Url :: from_file_path ( & source). unwrap (); 151let output = common:: run ( 152& format! ( 153r#" 154import {{listenDatagram,avril}} from 'avril:std'; 155const denied=new Worker('{url}',{{type:'module',[avril]:{{caps:{{net:false}}}}}});if(await new Promise(resolve=>denied.onmessage=e=>resolve(e.data))!=='PermissionDenied')throw Error('worker escaped');denied.terminate(); 156const worker=new Worker('{url}',{{type:'module'}}),port=await new Promise(resolve=>worker.onmessage=e=>resolve(e.data));worker.terminate(); 157let socket;for(let i=0;i<100&&!socket;i++){{try{{socket=listenDatagram({{port}});}}catch{{await new Promise(resolve=>setTimeout(resolve,5));}}}} 158if(!socket)throw Error('worker leaked UDP socket');socket.close();console.log('terminated'); 159"# 160), 161& [], 162); 163assert_eq! ( output. stdout , b"terminated\n" ); 164} 165 166# [ test ] 167fn udp_uses_captured_intrinsics_and_handles_cannot_be_cloned () { 168let output = common:: run ( 169r#" 170import {listenDatagram} from 'avril:std'; 171const chunk=new Uint8Array([9]),then=Promise.prototype.then,PromiseCtor=Promise; 172function observe(p){Object.defineProperty(p,'constructor',{value:undefined});const result=Reflect.apply(then,p,[v=>v,e=>{throw e;}]);Object.defineProperty(result,'constructor',{value:PromiseCtor});return result;} 173const poison=()=>{throw Error('guest hook');};Object.defineProperty(Promise.prototype,'constructor',{get:poison,configurable:true});Object.defineProperty(Promise,Symbol.species,{get:poison});Promise.prototype.then=poison;Array.prototype[Symbol.iterator]=poison; 174const a=listenDatagram({port:0}),b=listenDatagram({port:0}),reading=b.receive(); 175await observe(a.send(chunk,b.addr));const packet=await observe(reading);if(packet[0][0]!==9)throw Error('packet'); 176try{structuredClone(a);throw Error('cloned');}catch(e){if(e.name!=='DataCloneError')throw e;} 177a.close();b.close();console.log('safe'); 178"# , 179& [], 180); 181assert_eq! ( output. stdout , b"safe\n" ); 182} 183 184# [ test ] 185fn dns_selects_the_bound_address_family () { 186let output = common:: run ( 187r#" 188import {listenDatagram} from 'avril:std'; 189const a=listenDatagram({port:0}),b=listenDatagram({port:0}); 190const reading=b.receive();await a.send(new Uint8Array([6]),{hostname:'localhost',port:b.addr.port}); 191if((await reading)[0][0]!==6)throw Error('DNS family');a.close();b.close();console.log('dns'); 192"# , 193& [], 194); 195assert_eq! ( output. stdout , b"dns\n" ); 196} 197 198# [ test ] 199fn ipv6_datagrams_accept_bracketed_destinations () { 200if std:: net:: UdpSocket :: bind ( "[::1]:0" ). is_err () { 201return ; 202} 203let output = common:: run ( 204r#" 205import {listenDatagram} from 'avril:std'; 206const a=listenDatagram({hostname:'::1',port:0}),b=listenDatagram({hostname:'::1',port:0}); 207const reading=b.receive();await a.send(new Uint8Array([6]),{hostname:'[::1]',port:b.addr.port}); 208const [packet,peer]=await reading;if(packet[0]!==6||peer.hostname!=='::1')throw Error('IPv6');a.close();b.close();console.log('ipv6'); 209"# , 210& [], 211); 212assert_eq! ( output. stdout , b"ipv6\n" ); 213} 214 215# [ test ] 216fn invalid_datagram_options_and_oversized_messages_are_rejected () { 217let output = common:: run ( 218r#" 219import {listenDatagram,DatagramConn} from 'avril:std'; 220for(const port of [-1,65536,NaN,1.5]){try{listenDatagram({port});throw Error('invalid port');}catch(e){if(!(e instanceof RangeError))throw e;}} 221try{new DatagramConn();throw Error('constructed');}catch(e){if(!(e instanceof TypeError))throw e;} 222try{listenDatagram({transport:'tcp',port:0});throw Error('transport');}catch(e){if(!(e instanceof TypeError))throw e;} 223const a=listenDatagram({port:0}); 224try{a.send(new Uint8Array(65508),a.addr);throw Error('oversized');}catch(e){if(!(e instanceof RangeError))throw e;} 225try{await a.receive(new ArrayBuffer(1));throw Error('invalid buffer');}catch(e){if(!(e instanceof TypeError))throw e;} 226a.close();console.log('validated'); 227"# , 228& [], 229); 230assert_eq! ( output. stdout , b"validated\n" ); 231}