char/avril
(wip) why'd you have to go and make things so complicaaaaated?
git clone https://git.t4t.associates/char/avril
688e96e
main
1#! [ cfg ( unix )] 2mod common; 3 4use std:: io::{ BufRead , BufReader , Write }; 5use std:: os:: unix:: net:: UnixStream ; 6use std:: process::{ Command , Stdio }; 7 8# [ test ] 9fn unix_streams_need_only_write_authority_and_support_half_close () { 10let directory = tempfile:: tempdir (). unwrap (); 11let path = directory. path (). join ( "stream.sock" ); 12let label = serde_json:: to_string ( path. to_str (). unwrap ()). unwrap (); 13let output = common:: run ( 14& format! ( 15r#" 16import {{listen,connect,pledge}} from 'avril:std'; 17pledge({{net:false,read:false,write:{{allow:[{label}]}}}}); 18const listener=listen({{transport:'unix',path:{label}}}),accept=listener.accept(),client=await connect({{transport:'unix',path:{label}}}),server=await accept; 19if(listener.addr.path!=={label}||client.remoteAddr.path!=={label}||client.localAddr.path!==null||server.remoteAddr.path!==null||server.localAddr.path!=={label})throw Error('addresses'); 20const reading=server.read(new Uint8Array(1));await client.write(new Uint8Array([7]));if(await reading!==1)throw Error('read'); 21await client.closeWrite();if(await server.read(new Uint8Array(1))!==null)throw Error('FIN'); 22await server.write(new Uint8Array([8]));const byte=new Uint8Array(1);if(await client.read(byte)!==1||byte[0]!==8)throw Error('half close'); 23await server.closeWrite();if(await client.read(byte)!==null)throw Error('EOF'); 24client.close();server.close();listener.close();console.log('unix'); 25"# 26), 27& [], 28); 29assert_eq! ( output. stdout , b"unix\n" ); 30assert! ( !path. exists ()); 31} 32 33# [ test ] 34fn unix_cleanup_preserves_replacements_and_existing_entries () { 35let directory = tempfile:: tempdir (). unwrap (); 36let path = directory. path (). join ( "socket" ); 37let label = serde_json:: to_string ( path. to_str (). unwrap ()). unwrap (); 38let ( _source, mut child) = common:: spawn ( & format! ( 39r#" 40import {{listen}} from 'avril:std'; 41const listener=listen({{transport:'unix',path:{label}}});console.log('ready');const connection=await listener.accept();await connection.read(new Uint8Array(1));listener.close();connection.close(); 42"# 43)); 44let mut stdout =BufReader :: new ( child. stdout . take (). unwrap ()); 45let mut line =String :: new (); 46 stdout. read_line ( & mut line). unwrap (); 47assert_eq! ( line, "ready\n" ); 48 child. stdout =Some ( stdout. into_inner ()); 49let mut socket =UnixStream :: connect ( & path). unwrap (); 50 std:: fs:: remove_file ( & path). unwrap (); 51 std:: fs:: write ( & path, "replacement" ). unwrap (); 52 socket. write_all ( b"x" ). unwrap (); 53let output = common:: wait ( child); 54assert! ( 55 output. status . success (), 56"{}" , 57String :: from_utf8_lossy ( & output. stderr ) 58); 59assert_eq! ( std:: fs:: read_to_string ( & path). unwrap (), "replacement" ); 60let output = common:: run ( 61& format! ( 62r#" 63import {{listen,errors}} from 'avril:std'; 64try{{listen({{transport:'unix',path:{label}}});throw Error('existing entry replaced');}}catch(e){{if(e.message==='existing entry replaced')throw e;}} 65console.log('preserved'); 66"# 67), 68& [], 69); 70assert_eq! ( output. stdout , b"preserved\n" ); 71assert_eq! ( std:: fs:: read_to_string ( & path). unwrap (), "replacement" ); 72} 73 74# [ test ] 75fn write_revocation_blocks_all_unix_io_but_not_cleanup () { 76let directory = tempfile:: tempdir (). unwrap (); 77let path = directory. path (). join ( "revoke.sock" ); 78let label = serde_json:: to_string ( path. to_str (). unwrap ()). unwrap (); 79let output = common:: run ( 80& format! ( 81r#" 82import {{listen,connect,pledge,errors}} from 'avril:std'; 83const listener=listen({{transport:'unix',path:{label}}}),accept=listener.accept(),client=await connect({{transport:'unix',path:{label}}}),server=await accept; 84pledge({{write:false}}); 85for(const operation of [()=>client.read(new Uint8Array(1)),()=>client.write(new Uint8Array([1])),()=>client.closeWrite(),()=>listener.accept(),()=>connect({{transport:'unix',path:{label}}})]){{ 86try{{await operation();throw Error('authority escaped');}}catch(e){{if(!(e instanceof errors.PermissionDenied))throw e;}} 87}} 88client.close();server.close();listener.close();console.log('revoked'); 89"# 90), 91& [], 92); 93assert_eq! ( output. stdout , b"revoked\n" ); 94assert! ( !path. exists ()); 95} 96 97# [ test ] 98fn unix_connect_authorizes_the_resolved_symlink_target () { 99let directory = tempfile:: tempdir (). unwrap (); 100let path = directory. path (). join ( "actual.sock" ); 101let link = directory. path (). join ( "alias.sock" ); 102let label = serde_json:: to_string ( path. to_str (). unwrap ()). unwrap (); 103let alias = serde_json:: to_string ( link. to_str (). unwrap ()). unwrap (); 104let listener = std:: os:: unix:: net:: UnixListener :: bind ( & path). unwrap (); 105 std:: os:: unix:: fs:: symlink ( & path, & link). unwrap (); 106let output = common:: run ( 107& format! ( 108r#" 109import {{connect,pledge,errors}} from 'avril:std'; 110pledge({{net:false,write:{{allow:[{alias}],deny:[{label}]}}}}); 111try{{await connect({{transport:'unix',path:{alias}}});throw Error('symlink escaped');}}catch(e){{if(!(e instanceof errors.PermissionDenied))throw e;}} 112console.log('denied'); 113"# 114), 115& [], 116); 117assert_eq! ( output. stdout , b"denied\n" ); 118drop ( listener); 119} 120 121# [ test ] 122fn relative_unix_paths_resolve_against_the_working_directory () { 123let directory = tempfile:: tempdir (). unwrap (); 124let source = directory. path (). join ( "main.js" ); 125 std:: fs:: write ( & source, r#" 126import {listen,connect} from 'avril:std'; 127const listener=listen({transport:'unix',path:'relative.sock'}),accept=listener.accept(),client=await connect({transport:'unix',path:'relative.sock'}),server=await accept; 128if(!listener.addr.path.endsWith('/relative.sock'))throw Error('path');client.close();server.close();listener.close();console.log('relative'); 129"# ). unwrap (); 130let child =Command :: new ( env! ( "CARGO_BIN_EXE_avril" )) 131. args ([ "run" , "main.js" ]) 132. current_dir ( directory. path ()) 133. stdout ( Stdio :: piped ()) 134. stderr ( Stdio :: piped ()) 135. spawn () 136. unwrap (); 137let output = common:: wait ( child); 138assert! ( 139 output. status . success (), 140"{}" , 141String :: from_utf8_lossy ( & output. stderr ) 142); 143assert_eq! ( output. stdout , b"relative\n" ); 144assert! ( !directory. path (). join ( "relative.sock" ). exists ()); 145} 146 147# [ test ] 148fn closing_and_worker_termination_unlink_listeners_with_pending_accepts () { 149let directory = tempfile:: tempdir (). unwrap (); 150let path = directory. path (). join ( "worker.sock" ); 151let label = serde_json:: to_string ( path. to_str (). unwrap ()). unwrap (); 152let worker = directory. path (). join ( "worker.js" ); 153 std:: fs:: write ( & worker, format! ( "import {{listen}} from 'avril:std';const listener=listen({{transport:'unix',path:{label}}});postMessage('ready');await listener.accept();" )). unwrap (); 154let url = url:: Url :: from_file_path ( & worker). unwrap (); 155let output = common:: run ( 156& format! ( 157r#" 158import {{listen,errors}} from 'avril:std'; 159const worker=new Worker('{url}',{{type:'module'}});await new Promise(resolve=>worker.onmessage=resolve);worker.terminate(); 160let listener;for(let i=0;i<100&&!listener;i++){{try{{listener=listen({{transport:'unix',path:{label}}});}}catch{{await new Promise(resolve=>setTimeout(resolve,5));}}}} 161if(!listener)throw Error('worker leaked socket');const accept=listener.accept().catch(e=>e);listener.close();if(!((await accept) instanceof errors.BadResource))throw Error('pending accept');console.log('terminated'); 162"# 163), 164& [], 165); 166assert_eq! ( output. stdout , b"terminated\n" ); 167assert! ( !path. exists ()); 168} 169 170# [ test ] 171fn unix_socket_options_reject_ambiguous_transports () { 172let output = common:: run ( 173r#" 174import {listen,connect} from 'avril:std'; 175for(const options of [{transport:'unix',path:'',port:0},{transport:'unix',path:'x',hostname:'localhost'},{transport:'unix'},{path:'x',port:0}]){ 176try{listen(options);throw Error('invalid Unix options');}catch(e){if(!(e instanceof TypeError))throw e;} 177} 178console.log('validated'); 179"# , 180& [], 181); 182assert_eq! ( output. stdout , b"validated\n" ); 183} 184 185# [ test ] 186fn unix_writev_writes_ordered_vectors_and_validates () { 187let directory = tempfile:: tempdir (). unwrap (); 188let path = directory. path (). join ( "vector.sock" ); 189let label = serde_json:: to_string ( path. to_str (). unwrap ()). unwrap (); 190let output = common:: run ( 191& format! ( 192r#" 193import {{listen,connect,errors}} from 'avril:std'; 194const listener=listen({{transport:'unix',path:{label}}}),accept=listener.accept(); 195const client=await connect({{transport:'unix',path:{label}}}),server=await accept; 196const full=new Uint8Array([5,6,7,8]); 197if(await client.writev([full.subarray(0,2),new Uint8Array(0),full.subarray(2)])!==4)throw Error('Count'); 198if(await client.writev([])!==0)throw Error('Empty'); 199await client.write(new Uint8Array([9])); 200await client.closeWrite(); 201const buf=new Uint8Array(5);let offset=0,count; 202while(offset<5&&(count=await server.read(buf.subarray(offset)))!==null)offset+=count; 203if(offset!==5||buf.join()!=='5,6,7,8,9')throw Error('Order '+buf.join()); 204try{{await client.writev([new Uint8Array([1])]);throw Error('FIN');}}catch(e){{if(!(e instanceof errors.BadResource))throw e;}} 205client.close();server.close(); 206const accept2=listener.accept(),client2=await connect({{transport:'unix',path:{label}}}),server2=await accept2; 207const wide=[];for(let i=0;i<20;i++){{const view=new Uint8Array(1000);view.fill(i+1);wide.push(view);}} 208const gathering=(async()=>{{ 209const rest=new Uint8Array(20000);let at=0,got; 210while(at<20000&&(got=await server2.read(rest.subarray(at)))!==null)at+=got; 211if(at!==20000)throw Error('Wide length'); 212return rest; 213}})(); 214if(await client2.writev(wide)!==20000)throw Error('Wide'); 215await client2.closeWrite(); 216const rest=await gathering; 217for(let i=0;i<20;i++)for(let j=0;j<1000;j++)if(rest[i*1000+j]!==i+1)throw Error('Wide order '+i); 218for(const bad of ['bytes',new Uint8Array([1]),[new Uint8Array([1]),'x']]){{ 219try{{await server2.writev(bad);throw Error('Accepted');}}catch(e){{if(!(e instanceof TypeError))throw e;}} 220}} 221client2.close();server2.close(); 222listener.close();console.log('unix-vectors'); 223"# 224), 225& [], 226); 227assert_eq! ( output. stdout , b"unix-vectors\n" ); 228assert! ( !path. exists ()); 229}