diff --git a/coi-serviceworker.js b/coi-serviceworker.js new file mode 100644 index 0000000..2e76c21 --- /dev/null +++ b/coi-serviceworker.js @@ -0,0 +1,115 @@ +/*! coi-serviceworker v0.1.6 - Guido Zuidhof, licensed under MIT */ +let coepCredentialless = false; +if (typeof window === 'undefined') { + self.addEventListener("install", () => self.skipWaiting()); + self.addEventListener("activate", (event) => event.waitUntil(self.clients.claim())); + + self.addEventListener("message", (ev) => { + if (!ev.data) { + return; + } else if (ev.data.type === "deregister") { + self.registration + .unregister() + .then(() => { + return self.clients.matchAll(); + }) + .then(clients => { + clients.forEach((client) => client.navigate(client.url)); + }); + } else if (ev.data.type === "coepCredentialless") { + coepCredentialless = ev.data.value; + } + }); + + self.addEventListener("fetch", function (event) { + const r = event.request; + if (r.cache === "only-if-cached" && r.mode !== "same-origin") { + return; + } + + const request = (coepCredentialless && r.mode === "no-cors") + ? new Request(r, { + credentials: "omit", + }) + : r; + event.respondWith( + fetch(request) + .then((response) => { + if (response.status === 0) { + return response; + } + + const newHeaders = new Headers(response.headers); + newHeaders.set("Cross-Origin-Embedder-Policy", + coepCredentialless ? "credentialless" : "require-corp" + ); + newHeaders.set("Cross-Origin-Opener-Policy", "same-origin"); + + return new Response(response.body, { + status: response.status, + statusText: response.statusText, + headers: newHeaders, + }); + }) + .catch((e) => console.error(e)) + ); + }); + +} else { + (() => { + // You can customize the behavior of this script through a global `coi` variable. + const coi = { + shouldRegister: () => true, + shouldDeregister: () => false, + coepCredentialless: () => false, + doReload: () => window.location.reload(), + quiet: false, + ...window.coi + }; + + const n = navigator; + + if (n.serviceWorker && n.serviceWorker.controller) { + n.serviceWorker.controller.postMessage({ + type: "coepCredentialless", + value: coi.coepCredentialless(), + }); + + if (coi.shouldDeregister()) { + n.serviceWorker.controller.postMessage({ type: "deregister" }); + } + } + + // If we're already coi: do nothing. Perhaps it's due to this script doing its job, or COOP/COEP are + // already set from the origin server. Also if the browser has no notion of crossOriginIsolated, just give up here. + if (window.crossOriginIsolated !== false || !coi.shouldRegister()) return; + + if (!window.isSecureContext) { + !coi.quiet && console.log("COOP/COEP Service Worker not registered, a secure context is required."); + return; + } + + // In some environments (e.g. Chrome incognito mode) this won't be available + if (n.serviceWorker) { + n.serviceWorker.register(window.document.currentScript.src).then( + (registration) => { + !coi.quiet && console.log("COOP/COEP Service Worker registered", registration.scope); + + registration.addEventListener("updatefound", () => { + !coi.quiet && console.log("Reloading page to make use of updated COOP/COEP Service Worker."); + coi.doReload(); + }); + + // If the registration is active, but it's not controlling the page + if (registration.active && !n.serviceWorker.controller) { + !coi.quiet && console.log("Reloading page to make use of COOP/COEP Service Worker."); + coi.doReload(); + } + }, + (err) => { + !coi.quiet && console.error("COOP/COEP Service Worker failed to register:", err); + } + ); + } + })(); +} diff --git a/index.html b/index.html new file mode 100644 index 0000000..fd0fb9d --- /dev/null +++ b/index.html @@ -0,0 +1,36 @@ + + + +Thistle OTA update demo (x86_64) + + + + + + +
+ +

Thistle Demo

+ +

Terminal

+
+ +

It can take some time to load and start the console.

+ +
+ +

Tested on Chromium Version 126.0.6478.126 (Official Build) (64-bit).

+ +

Back to top

+ +
+ + + + + + + + + + diff --git a/src/browser_wasi_shim/index.js b/src/browser_wasi_shim/index.js new file mode 100644 index 0000000..82a6032 --- /dev/null +++ b/src/browser_wasi_shim/index.js @@ -0,0 +1 @@ +!function(t,e){if("object"==typeof exports&&"object"==typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var r=e();for(var s in r)("object"==typeof exports?exports:t)[s]=r[s]}}(self,(()=>(()=>{"use strict";var t={759:(t,e,r)=>{r.d(e,{CLOCKID_MONOTONIC:()=>n,CLOCKID_REALTIME:()=>s,Ciovec:()=>_,Dirent:()=>c,ERRNO_BADF:()=>i,ERRNO_INVAL:()=>f,FILETYPE_DIRECTORY:()=>u,FILETYPE_REGULAR_FILE:()=>h,Fdstat:()=>p,Filestat:()=>b,Iovec:()=>l,OFLAGS_CREAT:()=>y,OFLAGS_DIRECTORY:()=>m,OFLAGS_EXCL:()=>w,OFLAGS_TRUNC:()=>g,Prestat:()=>E,WHENCE_CUR:()=>d,WHENCE_END:()=>o,WHENCE_SET:()=>a});const s=0,n=1,i=8,f=28;class l{static read_bytes(t,e){let r=new l;return r.buf=t.getUint32(e,!0),r.buf_len=t.getUint32(e+4,!0),r}static read_bytes_array(t,e,r){let s=[];for(let n=0;n{for(var s in e)r.o(e,s)&&!r.o(t,s)&&Object.defineProperty(t,s,{enumerable:!0,get:e[s]})},r.o=(t,e)=>Object.prototype.hasOwnProperty.call(t,e),r.r=t=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})};var s={};return(()=>{r.r(s),r.d(s,{Directory:()=>f,Fd:()=>n,File:()=>i,OpenDirectory:()=>_,OpenFile:()=>l,PreopenDirectory:()=>a,WASI:()=>e,strace:()=>d});var t=r(759);let e=class{start(t){this.inst=t,t.exports._start()}initialize(t){this.inst=t,t.exports._initialize()}constructor(e,r,s){this.args=[],this.env=[],this.fds=[],this.args=e,this.env=r,this.fds=s;let n=this;this.wasiImport={args_sizes_get(t,e){let r=new DataView(n.inst.exports.memory.buffer);r.setUint32(t,n.args.length,!0);let s=0;for(let t of n.args)s+=t.length+1;return r.setUint32(e,s,!0),0},args_get(t,e){let r=new DataView(n.inst.exports.memory.buffer),s=new Uint8Array(n.inst.exports.memory.buffer);for(let i=0;inull!=n.fds[e]?n.fds[e].fd_advise(r,s,i):t.ERRNO_BADF,fd_allocate:(e,r,s)=>null!=n.fds[e]?n.fds[e].fd_allocate(r,s):t.ERRNO_BADF,fd_close(e){if(null!=n.fds[e]){let t=n.fds[e].fd_close();return n.fds[e]=void 0,t}return t.ERRNO_BADF},fd_datasync:e=>null!=n.fds[e]?n.fds[e].fd_datasync():t.ERRNO_BADF,fd_fdstat_get(e,r){if(null!=n.fds[e]){let{ret:t,fdstat:s}=n.fds[e].fd_fdstat_get();return null!=s&&s.write_bytes(new DataView(n.inst.exports.memory.buffer),r),t}return t.ERRNO_BADF},fd_fdstat_set_flags:(e,r)=>null!=n.fds[e]?n.fds[e].fd_fdstat_set_flags(r):t.ERRNO_BADF,fd_fdstat_set_rights:(e,r,s)=>null!=n.fds[e]?n.fds[e].fd_fdstat_set_rights(r,s):t.ERRNO_BADF,fd_filestat_get(e,r){if(null!=n.fds[e]){let{ret:t,filestat:s}=n.fds[e].fd_filestat_get();return null!=s&&s.write_bytes(new DataView(n.inst.exports.memory.buffer),r),t}return t.ERRNO_BADF},fd_filestat_set_size:(e,r)=>null!=n.fds[e]?n.fds[e].fd_filestat_set_size(r):t.ERRNO_BADF,fd_filestat_set_times:(e,r,s,i)=>null!=n.fds[e]?n.fds[e].fd_filestat_set_times(r,s,i):t.ERRNO_BADF,fd_pread(e,r,s,i,f){let l=new DataView(n.inst.exports.memory.buffer),_=new Uint8Array(n.inst.exports.memory.buffer);if(null!=n.fds[e]){let a=t.Iovec.read_bytes_array(l,r,s),{ret:d,nread:o}=n.fds[e].fd_pread(_,a,i);return l.setUint32(f,o,!0),d}return t.ERRNO_BADF},fd_prestat_get(e,r){let s=new DataView(n.inst.exports.memory.buffer);if(null!=n.fds[e]){let{ret:t,prestat:i}=n.fds[e].fd_prestat_get();return null!=i&&i.write_bytes(s,r),t}return t.ERRNO_BADF},fd_prestat_dir_name(e,r,s){if(null!=n.fds[e]){let{ret:t,prestat_dir_name:s}=n.fds[e].fd_prestat_dir_name();return null!=s&&new Uint8Array(n.inst.exports.memory.buffer).set(s,r),t}return t.ERRNO_BADF},fd_pwrite(e,r,s,i,f){let l=new DataView(n.inst.exports.memory.buffer),_=new Uint8Array(n.inst.exports.memory.buffer);if(null!=n.fds[e]){let a=t.Ciovec.read_bytes_array(l,r,s),{ret:d,nwritten:o}=n.fds[e].fd_pwrite(_,a,i);return l.setUint32(f,o,!0),d}return t.ERRNO_BADF},fd_read(e,r,s,i){let f=new DataView(n.inst.exports.memory.buffer),l=new Uint8Array(n.inst.exports.memory.buffer);if(null!=n.fds[e]){let _=t.Iovec.read_bytes_array(f,r,s),{ret:a,nread:d}=n.fds[e].fd_read(l,_);return f.setUint32(i,d,!0),a}return t.ERRNO_BADF},fd_readdir(e,r,s,i,f){let l=new DataView(n.inst.exports.memory.buffer),_=new Uint8Array(n.inst.exports.memory.buffer);if(null!=n.fds[e]){let t=0;for(;;){let{ret:a,dirent:d}=n.fds[e].fd_readdir_single(i);if(0!=a)return l.setUint32(f,t,!0),a;if(null==d)break;let o=d.length();if(s-tnull!=n.fds[e]?n.fds[e].fd_sync():t.ERRNO_BADF,fd_tell(e,r){let s=new DataView(n.inst.exports.memory.buffer);if(null!=n.fds[e]){let{ret:t,offset:i}=n.fds[e].fd_tell();return s.setUint32(r,i,!0),t}return t.ERRNO_BADF},fd_write(e,r,s,i){let f=new DataView(n.inst.exports.memory.buffer),l=new Uint8Array(n.inst.exports.memory.buffer);if(null!=n.fds[e]){let _=t.Ciovec.read_bytes_array(f,r,s),{ret:a,nwritten:d}=n.fds[e].fd_write(l,_);return f.setUint32(i,d,!0),a}return t.ERRNO_BADF},path_create_directory(t,e,r){let s=new Uint8Array(n.inst.exports.memory.buffer);if(null!=n.fds[t]){let i=new TextDecoder("utf-8").decode(s.slice(e,e+r));return n.fds[t].path_create_directory(i)}},path_filestat_get(e,r,s,i,f){let l=new DataView(n.inst.exports.memory.buffer),_=new Uint8Array(n.inst.exports.memory.buffer);if(null!=n.fds[e]){let t=new TextDecoder("utf-8").decode(_.slice(s,s+i)),{ret:a,filestat:d}=n.fds[e].path_filestat_get(r,t);return null!=d&&d.write_bytes(l,f),a}return t.ERRNO_BADF},path_filestat_set_times(e,r,s,i,f,l,_){let a=new Uint8Array(n.inst.exports.memory.buffer);if(null!=n.fds[e]){let t=new TextDecoder("utf-8").decode(a.slice(s,s+i));return n.fds[e].path_filestat_set_times(r,t,f,l,_)}return t.ERRNO_BADF},path_link(e,r,s,i,f,l,_){let a=new Uint8Array(n.inst.exports.memory.buffer);if(null!=n.fds[e]&&null!=n.fds[f]){let t=new TextDecoder("utf-8").decode(a.slice(s,s+i)),d=new TextDecoder("utf-8").decode(a.slice(l,l+_));return n.fds[f].path_link(e,r,t,d)}return t.ERRNO_BADF},path_open(e,r,s,i,f,l,_,a,d){let o=new DataView(n.inst.exports.memory.buffer),u=new Uint8Array(n.inst.exports.memory.buffer);if(null!=n.fds[e]){let t=new TextDecoder("utf-8").decode(u.slice(s,s+i)),{ret:h,fd_obj:c}=n.fds[e].path_open(r,t,f,l,_,a);if(0!=h)return h;n.fds.push(c);let p=n.fds.length-1;return o.setUint32(d,p,!0),0}return t.ERRNO_BADF},path_readlink(e,r,s,i,f,l){let _=new DataView(n.inst.exports.memory.buffer),a=new Uint8Array(n.inst.exports.memory.buffer);if(null!=n.fds[e]){let d=new TextDecoder("utf-8").decode(a.slice(r,r+s)),{ret:o,data:u}=n.fds[e].path_readlink(d);if(null!=u){if(u.length>f)return _.setUint32(l,0,!0),t.ERRNO_BADF;a.set(u,i),_.setUint32(l,u.length,!0)}return o}return t.ERRNO_BADF},path_remove_directory(e,r,s){let i=new Uint8Array(n.inst.exports.memory.buffer);if(null!=n.fds[e]){let t=new TextDecoder("utf-8").decode(i.slice(r,r+s));return n.fds[e].path_remove_directory(t)}return t.ERRNO_BADF},path_rename(t,e,r,s,n,i){throw"FIXME what is the best abstraction for this?"},path_symlink(e,r,s,i,f){let l=new Uint8Array(n.inst.exports.memory.buffer);if(null!=n.fds[s]){let t=new TextDecoder("utf-8").decode(l.slice(e,e+r)),_=new TextDecoder("utf-8").decode(l.slice(i,i+f));return n.fds[s].path_symlink(t,_)}return t.ERRNO_BADF},path_unlink_file(e,r,s){let i=new Uint8Array(n.inst.exports.memory.buffer);if(null!=n.fds[e]){let t=new TextDecoder("utf-8").decode(i.slice(r,r+s));return n.fds[e].path_unlink_file(t)}return t.ERRNO_BADF},poll_oneoff(t,e,r){throw"async io not supported"},proc_exit(t){throw"exit with exit code "+t},proc_raise(t){throw"raised signal "+t},sched_yield(){},random_get(t,e){let r=new Uint8Array(n.inst.exports.memory.buffer);for(let s=0;s"/"!=t));for(let t=0;tthis.file.size){let t=this.file.data;this.file.data=new Uint8Array(Number(this.file_pos+BigInt(e.byteLength))),this.file.data.set(t)}this.file.data.set(e.slice(0,this.file.size-Number(this.file_pos)),Number(this.file_pos)),this.file_pos+=BigInt(e.byteLength),r+=s.buf_len}return{ret:0,nwritten:r}}fd_filestat_get(){return{ret:0,filestat:this.file.stat()}}constructor(t){super(),this.file_pos=0n,this.file=t}}class _ extends n{fd_fdstat_get(){return{ret:0,fdstat:new t.Fdstat(t.FILETYPE_DIRECTORY,0)}}fd_readdir_single(e){if(e>=BigInt(Object.keys(this.dir.contents).length))return{ret:0,dirent:null};let r=Object.keys(this.dir.contents)[Number(e)],s=this.dir.contents[r];return new TextEncoder("utf-8").encode(r),{ret:0,dirent:new t.Dirent(e+1n,r,s.stat().filetype)}}path_filestat_get(t,e){let r=this.dir.get_entry_for_path(e);return null==r?{ret:-1,filestat:null}:{ret:0,filestat:r.stat()}}path_open(e,r,s,n,a,d){let o=this.dir.get_entry_for_path(r);if(null==o){if((s&t.OFLAGS_CREAT)!=t.OFLAGS_CREAT)return{ret:-1,fd_obj:null};o=this.dir.create_entry_for_path(r)}else if((s&t.OFLAGS_EXCL)==t.OFLAGS_EXCL)return{ret:-1,fd_obj:null};if((s&t.OFLAGS_DIRECTORY)==t.OFLAGS_DIRECTORY&&o.stat().filetype!=t.FILETYPE_DIRECTORY)return{ret:-1,fd_obj:null};if((s&t.OFLAGS_TRUNC)==t.OFLAGS_TRUNC&&o.truncate(),o instanceof i)return{ret:0,fd_obj:new l(o)};if(o instanceof f)return{ret:0,fd_obj:new _(o)};throw"dir entry neither file nor dir"}constructor(t){super(),this.dir=t}}class a extends _{fd_prestat_get(){return{ret:0,prestat:t.Prestat.dir(this.prestat_name.length)}}fd_prestat_dir_name(){return{ret:0,prestat_dir_name:this.prestat_name}}constructor(t,e){super(new f(e)),this.prestat_name=new TextEncoder("utf-8").encode(t)}}function d(t,e){return new Proxy(t,{get(t,r,s){let n=Reflect.get(t,r,s);return e.includes(r)?n:function(...t){return console.log(r,"(",...t,")"),Reflect.apply(n,s,t)}}})}})(),s})())); \ No newline at end of file diff --git a/src/browser_wasi_shim/wasi_defs.js b/src/browser_wasi_shim/wasi_defs.js new file mode 100644 index 0000000..1d7cf70 --- /dev/null +++ b/src/browser_wasi_shim/wasi_defs.js @@ -0,0 +1 @@ +!function(_,R){if("object"==typeof exports&&"object"==typeof module)module.exports=R();else if("function"==typeof define&&define.amd)define([],R);else{var E=R();for(var N in E)("object"==typeof exports?exports:_)[N]=E[N]}}(self,(()=>(()=>{"use strict";var _={d:(R,E)=>{for(var N in E)_.o(E,N)&&!_.o(R,N)&&Object.defineProperty(R,N,{enumerable:!0,get:E[N]})},o:(_,R)=>Object.prototype.hasOwnProperty.call(_,R),r:_=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(_,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(_,"__esModule",{value:!0})}},R={};_.r(R),_.d(R,{ADVICE_DONTNEED:()=>hR,ADVICE_NOREUSE:()=>lR,ADVICE_NORMAL:()=>UR,ADVICE_RANDOM:()=>oR,ADVICE_SEQUENTIAL:()=>HR,ADVICE_WILLNEED:()=>aR,CLOCKID_MONOTONIC:()=>T,CLOCKID_PROCESS_CPUTIME_ID:()=>S,CLOCKID_REALTIME:()=>O,CLOCKID_THREAD_CPUTIME_ID:()=>e,Ciovec:()=>eR,Dirent:()=>PR,ERRNO_2BIG:()=>A,ERRNO_ACCES:()=>i,ERRNO_ADDRINUSE:()=>s,ERRNO_ADDRNOTAVAIL:()=>L,ERRNO_AFNOSUPPORT:()=>n,ERRNO_AGAIN:()=>D,ERRNO_ALREADY:()=>G,ERRNO_BADF:()=>C,ERRNO_BADMSG:()=>r,ERRNO_BUSY:()=>F,ERRNO_CANCELED:()=>P,ERRNO_CHILD:()=>U,ERRNO_CONNABORTED:()=>H,ERRNO_CONNREFUSED:()=>o,ERRNO_CONNRESET:()=>a,ERRNO_DEADLK:()=>h,ERRNO_DESTADDRREQ:()=>l,ERRNO_DOM:()=>c,ERRNO_DQUOT:()=>M,ERRNO_EXIST:()=>d,ERRNO_FAULT:()=>f,ERRNO_FBIG:()=>y,ERRNO_HOSTUNREACH:()=>g,ERRNO_IDRM:()=>b,ERRNO_ILSEQ:()=>Y,ERRNO_INPROGRESS:()=>B,ERRNO_INTR:()=>u,ERRNO_INVAL:()=>K,ERRNO_IO:()=>p,ERRNO_ISCONN:()=>V,ERRNO_ISDIR:()=>m,ERRNO_LOOP:()=>W,ERRNO_MFILE:()=>w,ERRNO_MLINK:()=>v,ERRNO_MSGSIZE:()=>X,ERRNO_MULTIHOP:()=>j,ERRNO_NAMETOOLONG:()=>x,ERRNO_NETDOWN:()=>Q,ERRNO_NETRESET:()=>Z,ERRNO_NETUNREACH:()=>k,ERRNO_NFILE:()=>z,ERRNO_NOBUFS:()=>q,ERRNO_NODEV:()=>J,ERRNO_NOENT:()=>$,ERRNO_NOEXEC:()=>__,ERRNO_NOLCK:()=>R_,ERRNO_NOLINK:()=>E_,ERRNO_NOMEM:()=>N_,ERRNO_NOMSG:()=>t_,ERRNO_NOPROTOOPT:()=>O_,ERRNO_NOSPC:()=>T_,ERRNO_NOSYS:()=>S_,ERRNO_NOTCAPABLE:()=>y_,ERRNO_NOTCONN:()=>e_,ERRNO_NOTDIR:()=>I_,ERRNO_NOTEMPTY:()=>A_,ERRNO_NOTRECOVERABLE:()=>i_,ERRNO_NOTSOCK:()=>s_,ERRNO_NOTSUP:()=>L_,ERRNO_NOTTY:()=>n_,ERRNO_NXIO:()=>D_,ERRNO_OVERFLOW:()=>G_,ERRNO_OWNERDEAD:()=>C_,ERRNO_PERM:()=>r_,ERRNO_PIPE:()=>F_,ERRNO_PROTO:()=>P_,ERRNO_PROTONOSUPPORT:()=>U_,ERRNO_PROTOTYPE:()=>H_,ERRNO_RANGE:()=>o_,ERRNO_ROFS:()=>a_,ERRNO_SPIPE:()=>h_,ERRNO_SRCH:()=>l_,ERRNO_STALE:()=>c_,ERRNO_SUCCESS:()=>I,ERRNO_TIMEDOUT:()=>M_,ERRNO_TXTBSY:()=>d_,ERRNO_XDEV:()=>f_,EVENTRWFLAGS_FD_READWRITE_HANGUP:()=>jR,EVENTTYPE_CLOCK:()=>wR,EVENTTYPE_FD_READ:()=>vR,EVENTTYPE_FD_WRITE:()=>XR,FDFLAGS_APPEND:()=>cR,FDFLAGS_DSYNC:()=>MR,FDFLAGS_NONBLOCK:()=>dR,FDFLAGS_RSYNC:()=>fR,FDFLAGS_SYNC:()=>yR,FD_STDERR:()=>t,FD_STDIN:()=>E,FD_STDOUT:()=>N,FILETYPE_BLOCK_DEVICE:()=>LR,FILETYPE_CHARACTER_DEVICE:()=>nR,FILETYPE_DIRECTORY:()=>DR,FILETYPE_REGULAR_FILE:()=>GR,FILETYPE_SOCKET_DGRAM:()=>CR,FILETYPE_SOCKET_STREAM:()=>rR,FILETYPE_SYMBOLIC_LINK:()=>FR,FILETYPE_UNKNOWN:()=>sR,FSTFLAGS_ATIM:()=>bR,FSTFLAGS_ATIM_NOW:()=>YR,FSTFLAGS_MTIM:()=>BR,FSTFLAGS_MTIM_NOW:()=>uR,Fdstat:()=>gR,Filestat:()=>WR,Iovec:()=>SR,OFLAGS_CREAT:()=>KR,OFLAGS_DIRECTORY:()=>pR,OFLAGS_EXCL:()=>VR,OFLAGS_TRUNC:()=>mR,PREOPENTYPE_DIR:()=>dE,Prestat:()=>yE,PrestatDir:()=>fE,RIFLAGS_RECV_PEEK:()=>aE,RIFLAGS_RECV_WAITALL:()=>hE,RIGHTS_FD_ADVISE:()=>V_,RIGHTS_FD_ALLOCATE:()=>m_,RIGHTS_FD_DATASYNC:()=>g_,RIGHTS_FD_FDSTAT_SET_FLAGS:()=>B_,RIGHTS_FD_FILESTAT_GET:()=>$_,RIGHTS_FD_FILESTAT_SET_SIZE:()=>_R,RIGHTS_FD_FILESTAT_SET_TIMES:()=>RR,RIGHTS_FD_READ:()=>b_,RIGHTS_FD_READDIR:()=>x_,RIGHTS_FD_SEEK:()=>Y_,RIGHTS_FD_SYNC:()=>u_,RIGHTS_FD_TELL:()=>K_,RIGHTS_FD_WRITE:()=>p_,RIGHTS_PATH_CREATE_DIRECTORY:()=>W_,RIGHTS_PATH_CREATE_FILE:()=>w_,RIGHTS_PATH_FILESTAT_GET:()=>z_,RIGHTS_PATH_FILESTAT_SET_SIZE:()=>q_,RIGHTS_PATH_FILESTAT_SET_TIMES:()=>J_,RIGHTS_PATH_LINK_SOURCE:()=>v_,RIGHTS_PATH_LINK_TARGET:()=>X_,RIGHTS_PATH_OPEN:()=>j_,RIGHTS_PATH_READLINK:()=>Q_,RIGHTS_PATH_REMOVE_DIRECTORY:()=>NR,RIGHTS_PATH_RENAME_SOURCE:()=>Z_,RIGHTS_PATH_RENAME_TARGET:()=>k_,RIGHTS_PATH_SYMLINK:()=>ER,RIGHTS_PATH_UNLINK_FILE:()=>tR,RIGHTS_POLL_FD_READWRITE:()=>OR,RIGHTS_SOCK_SHUTDOWN:()=>TR,ROFLAGS_RECV_DATA_TRUNCATED:()=>lE,SDFLAGS_RD:()=>cE,SDFLAGS_WR:()=>ME,SIGNAL_ABRT:()=>$R,SIGNAL_ALRM:()=>SE,SIGNAL_BUS:()=>_E,SIGNAL_CHLD:()=>IE,SIGNAL_CONT:()=>AE,SIGNAL_FPE:()=>RE,SIGNAL_HUP:()=>ZR,SIGNAL_ILL:()=>qR,SIGNAL_INT:()=>kR,SIGNAL_KILL:()=>EE,SIGNAL_NONE:()=>QR,SIGNAL_PIPE:()=>TE,SIGNAL_POLL:()=>UE,SIGNAL_PROF:()=>FE,SIGNAL_PWR:()=>HE,SIGNAL_QUIT:()=>zR,SIGNAL_SEGV:()=>tE,SIGNAL_STOP:()=>iE,SIGNAL_SYS:()=>oE,SIGNAL_TERM:()=>eE,SIGNAL_TRAP:()=>JR,SIGNAL_TSTP:()=>sE,SIGNAL_TTIN:()=>LE,SIGNAL_TTOU:()=>nE,SIGNAL_URG:()=>DE,SIGNAL_USR1:()=>NE,SIGNAL_USR2:()=>OE,SIGNAL_VTALRM:()=>rE,SIGNAL_WINCH:()=>PE,SIGNAL_XCPU:()=>GE,SIGNAL_XFSZ:()=>CE,SUBCLOCKFLAGS_SUBSCRIPTION_CLOCK_ABSTIME:()=>xR,WHENCE_CUR:()=>AR,WHENCE_END:()=>iR,WHENCE_SET:()=>IR});const E=0,N=1,t=2,O=0,T=1,S=2,e=3,I=0,A=1,i=2,s=3,L=4,n=5,D=6,G=7,C=8,r=9,F=10,P=11,U=12,H=13,o=14,a=15,h=16,l=17,c=18,M=19,d=20,f=21,y=22,g=23,b=24,Y=25,B=26,u=27,K=28,p=29,V=30,m=31,W=32,w=33,v=34,X=35,j=36,x=37,Q=38,Z=39,k=40,z=41,q=42,J=43,$=44,__=45,R_=46,E_=47,N_=48,t_=49,O_=50,T_=51,S_=52,e_=53,I_=54,A_=55,i_=56,s_=57,L_=58,n_=59,D_=60,G_=61,C_=62,r_=63,F_=64,P_=65,U_=66,H_=67,o_=68,a_=69,h_=70,l_=71,c_=72,M_=73,d_=74,f_=75,y_=76,g_=1,b_=2,Y_=4,B_=8,u_=16,K_=32,p_=64,V_=128,m_=256,W_=512,w_=1024,v_=2048,X_=4096,j_=8192,x_=16384,Q_=32768,Z_=65536,k_=1<<17,z_=1<<18,q_=1<<19,J_=1<<20,$_=1<<21,_R=1<<22,RR=1<<23,ER=1<<24,NR=1<<25,tR=1<<26,OR=1<<27,TR=1<<28;class SR{static read_bytes(_,R){let E=new SR;return E.buf=_.getUint32(R,!0),E.buf_len=_.getUint32(R+4,!0),E}static read_bytes_array(_,R,E){let N=[];for(let t=0;t { + serveIfInitMsg(msg); + var fds = [ + undefined, // 0: stdin + undefined, // 1: stdout + undefined, // 2: stderr + undefined, // 3: receive certificates + undefined, // 4: socket listenfd + undefined, // 5: accepted socket fd (multi-connection is unsupported) + // 6...: used by wasi shim + ]; + var certfd = 3; + var listenfd = 4; + var args = ['arg0', '--certfd='+certfd, '--net-listenfd='+listenfd, '--debug']; + var env = []; + var wasi = new WASI(args, env, fds); + wasiHack(wasi, certfd, 5); + wasiHackSocket(wasi, listenfd, 5); + fetch(getImagename(), { credentials: 'same-origin' }).then((resp) => { + resp['arrayBuffer']().then((wasm) => { + WebAssembly.instantiate(wasm, { + "wasi_snapshot_preview1": wasi.wasiImport, + "env": envHack(wasi), + }).then((inst) => { + wasi.start(inst.instance); + }); + }) + }); +}; + +// definition from wasi-libc https://github.com/WebAssembly/wasi-libc/blob/wasi-sdk-19/expected/wasm32-wasi/predefined-macros.txt +const ERRNO_INVAL = 28; +const ERRNO_AGAIN= 6; + +function wasiHack(wasi, certfd, connfd) { + var certbuf = new Uint8Array(0); + var _fd_close = wasi.wasiImport.fd_close; + wasi.wasiImport.fd_close = (fd) => { + if (fd == certfd) { + sendCert(certbuf); + return 0; + } + return _fd_close.apply(wasi.wasiImport, [fd]); + } + var _fd_fdstat_get = wasi.wasiImport.fd_fdstat_get; + wasi.wasiImport.fd_fdstat_get = (fd, fdstat_ptr) => { + if (fd == certfd) { + return 0; + } + return _fd_fdstat_get.apply(wasi.wasiImport, [fd, fdstat_ptr]); + } + wasi.wasiImport.fd_fdstat_set_flags = (fd, fdflags) => { + // TODO + return 0; + } + var _fd_write = wasi.wasiImport.fd_write; + wasi.wasiImport.fd_write = (fd, iovs_ptr, iovs_len, nwritten_ptr) => { + if ((fd == 1) || (fd == 2) || (fd == certfd)) { + var buffer = new DataView(wasi.inst.exports.memory.buffer); + var buffer8 = new Uint8Array(wasi.inst.exports.memory.buffer); + var iovecs = Ciovec.read_bytes_array(buffer, iovs_ptr, iovs_len); + var wtotal = 0 + for (i = 0; i < iovecs.length; i++) { + var iovec = iovecs[i]; + var buf = buffer8.slice(iovec.buf, iovec.buf + iovec.buf_len); + if (buf.length == 0) { + continue; + } + console.log(new TextDecoder().decode(buf)); + if (fd == certfd) { + certbuf = appendData(certbuf, buf); + } + wtotal += buf.length; + } + buffer.setUint32(nwritten_ptr, wtotal, true); + return 0; + } + console.log("fd_write: unknown fd " + fd); + return _fd_write.apply(wasi.wasiImport, [fd, iovs_ptr, iovs_len, nwritten_ptr]); + } + wasi.wasiImport.poll_oneoff = (in_ptr, out_ptr, nsubscriptions, nevents_ptr) => { + if (nsubscriptions == 0) { + return ERRNO_INVAL; + } + let buffer = new DataView(wasi.inst.exports.memory.buffer); + let in_ = Subscription.read_bytes_array(buffer, in_ptr, nsubscriptions); + let isReadPollStdin = false; + let isReadPollConn = false; + let isClockPoll = false; + let pollSubStdin; + let pollSubConn; + let clockSub; + let timeout = Number.MAX_VALUE; + for (let sub of in_) { + if (sub.u.tag.variant == "fd_read") { + if ((sub.u.data.fd != 0) && (sub.u.data.fd != connfd)) { + return ERRNO_INVAL; // only fd=0 and connfd is supported as of now (FIXME) + } + if (sub.u.data.fd == 0) { + isReadPollStdin = true; + pollSubStdin = sub; + } else { + isReadPollConn = true; + pollSubConn = sub; + } + } else if (sub.u.tag.variant == "clock") { + if (sub.u.data.timeout < timeout) { + timeout = sub.u.data.timeout + isClockPoll = true; + clockSub = sub; + } + } else { + return ERRNO_INVAL; // FIXME + } + } + let events = []; + if (isReadPollStdin || isReadPollConn || isClockPoll) { + var sockreadable = sockWaitForReadable(timeout / 1000000000); + if (isReadPollConn) { + if (sockreadable == errStatus) { + return ERRNO_INVAL; + } else if (sockreadable == true) { + let event = new Event(); + event.userdata = pollSubConn.userdata; + event.error = 0; + event.type = new EventType("fd_read"); + events.push(event); + } + } + if (isClockPoll) { + let event = new Event(); + event.userdata = clockSub.userdata; + event.error = 0; + event.type = new EventType("clock"); + events.push(event); + } + } + var len = events.length; + Event.write_bytes_array(buffer, out_ptr, events); + buffer.setUint32(nevents_ptr, len, true); + return 0; + } +} + +function envHack(wasi){ + return { + http_send: function(addressP, addresslen, reqP, reqlen, idP){ + var buffer = new DataView(wasi.inst.exports.memory.buffer); + var address = new Uint8Array(wasi.inst.exports.memory.buffer, addressP, addresslen); + var req = new Uint8Array(wasi.inst.exports.memory.buffer, reqP, reqlen); + streamCtrl[0] = 0; + postMessage({ + type: "http_send", + address: address, + req: req, + }); + Atomics.wait(streamCtrl, 0, 0); + if (streamStatus[0] < 0) { + return ERRNO_INVAL; + } + var id = streamStatus[0]; + buffer.setUint32(idP, id, true); + return 0; + }, + http_writebody: function(id, bodyP, bodylen, nwrittenP, isEOF){ + var buffer = new DataView(wasi.inst.exports.memory.buffer); + var body = new Uint8Array(wasi.inst.exports.memory.buffer, bodyP, bodylen); + streamCtrl[0] = 0; + postMessage({ + type: "http_writebody", + id: id, + body: body, + isEOF: isEOF, + }); + Atomics.wait(streamCtrl, 0, 0); + if (streamStatus[0] < 0) { + return ERRNO_INVAL; + } + buffer.setUint32(nwrittenP, bodylen, true); + return 0; + }, + http_isreadable: function(id, isOKP){ + var buffer = new DataView(wasi.inst.exports.memory.buffer); + streamCtrl[0] = 0; + postMessage({type: "http_isreadable", id: id}); + Atomics.wait(streamCtrl, 0, 0); + if (streamStatus[0] < 0) { + return ERRNO_INVAL; + } + var readable = 0; + if (streamData[0] == 1) { + readable = 1; + } + buffer.setUint32(isOKP, readable, true); + return 0; + }, + http_recv: function(id, respP, bufsize, respsizeP, isEOFP){ + var buffer = new DataView(wasi.inst.exports.memory.buffer); + var buffer8 = new Uint8Array(wasi.inst.exports.memory.buffer); + + streamCtrl[0] = 0; + postMessage({type: "http_recv", id: id, len: bufsize}); + Atomics.wait(streamCtrl, 0, 0); + if (streamStatus[0] < 0) { + return ERRNO_INVAL; + } + var ddlen = streamLen[0]; + var resp = streamData.slice(0, ddlen); + buffer8.set(resp, respP); + buffer.setUint32(respsizeP, ddlen, true); + if (streamStatus[0] == 1) { + buffer.setUint32(isEOFP, 1, true); + } else { + buffer.setUint32(isEOFP, 0, true); + } + return 0; + }, + http_readbody: function(id, bodyP, bufsize, bodysizeP, isEOFP){ + var buffer = new DataView(wasi.inst.exports.memory.buffer); + var buffer8 = new Uint8Array(wasi.inst.exports.memory.buffer); + + streamCtrl[0] = 0; + postMessage({type: "http_readbody", id: id, len: bufsize}); + Atomics.wait(streamCtrl, 0, 0); + if (streamStatus[0] < 0) { + return ERRNO_INVAL; + } + var ddlen = streamLen[0]; + var body = streamData.slice(0, ddlen); + buffer8.set(body, bodyP); + buffer.setUint32(bodysizeP, ddlen, true); + if (streamStatus[0] == 1) { + buffer.setUint32(isEOFP, 1, true); + } else { + buffer.setUint32(isEOFP, 0, true); + } + return 0; + } + }; +} diff --git a/src/stack.js b/src/stack.js new file mode 100644 index 0000000..5511aec --- /dev/null +++ b/src/stack.js @@ -0,0 +1,259 @@ +function newStack(worker, workerImageNamePrefix, workerImageChunks, stackWorker, stackImageName) { + let p2vbuf = { + buf: new Uint8Array(0) // proxy => vm + }; + let v2pbuf = { + buf: new Uint8Array(0) // vm => proxy + }; + var proxyConn = { + sendbuf: p2vbuf, + recvbuf: v2pbuf + }; + var vmConn = { + sendbuf: v2pbuf, + recvbuf: p2vbuf + } + var proxyShared = new SharedArrayBuffer(12 + 4096); + var certbuf = { + buf: new Uint8Array(0), + done: false + } + stackWorker.onmessage = connect("proxy", proxyShared, proxyConn, certbuf); + stackWorker.postMessage({type: "init", buf: proxyShared, imagename: stackImageName}); + + var vmShared = new SharedArrayBuffer(12 + 4096); + worker.postMessage({type: "init", buf: vmShared, imagename: workerImageNamePrefix, chunks: workerImageChunks}); + return connect("vm", vmShared, vmConn, certbuf); +} + +function connect(name, shared, conn, certbuf) { + var streamCtrl = new Int32Array(shared, 0, 1); + var streamStatus = new Int32Array(shared, 4, 1); + var streamLen = new Int32Array(shared, 8, 1); + var streamData = new Uint8Array(shared, 12); + var sendbuf = conn.sendbuf; + var recvbuf = conn.recvbuf; + let accepted = false; + var httpConnections = {}; + var curID = 0; + var maxID = 0x7FFFFFFF; // storable in streamStatus(signed 32bits) + function getID() { + var startID = curID; + while (true) { + if (httpConnections[curID] == undefined) { + return curID; + } + if (curID >= maxID) { + curID = 0; + } else { + curID++; + } + if (curID == startID) { + return -1; // exhausted + } + } + return curID; + } + function serveData(data, len) { + var length = len; + if (length > streamData.byteLength) + length = streamData.byteLength; + if (length > data.byteLength) + length = data.byteLength + var buf = data.slice(0, length); + var remain = data.slice(length, data.byteLength); + streamLen[0] = buf.byteLength; + streamData.set(buf, 0); + return remain; + } + return function(msg){ + const req_ = msg.data; + if (typeof req_ == "object" && req_.type) { + switch (req_.type) { + case "accept": + accepted = true; + streamData[0] = 1; // opened + streamStatus[0] = 0; + break; + case "send": + if (!accepted) { + console.log(name + ":" + "cannot send to unaccepted socket"); + streamStatus[0] = -1; + break; + } + sendbuf.buf = appendData(sendbuf.buf, req_.buf); + streamStatus[0] = 0; + break; + case "recv": + if (!accepted) { + console.log(name + ":" + "cannot recv from unaccepted socket"); + streamStatus[0] = -1; + break; + } + recvbuf.buf = serveData(recvbuf.buf, req_.len); + streamStatus[0] = 0; + break; + case "recv-is-readable": + var recvbufP = recvbuf.buf; + if (recvbufP.byteLength > 0) { + streamData[0] = 1; // ready for reading + } else { + if ((req_.timeout != undefined) && (req_.timeout > 0)) { + if (this.timeoutHandler) { + clearTimeout(this.timeoutHandler); + this.timeoutHandler = null; + } + this.timeoutHandler = setTimeout(() => { + if (this.timeoutHandler) { + clearTimeout(this.timeoutHandler); + this.timeoutHandler = null; + } + if (recvbuf.buf.byteLength > 0) { + streamData[0] = 1; // ready for reading + } else { + streamData[0] = 0; // timeout + } + streamStatus[0] = 0; + Atomics.store(streamCtrl, 0, 1); + Atomics.notify(streamCtrl, 0); + }, req_.timeout * 1000); + return; + } + streamData[0] = 0; // timeout + } + streamStatus[0] = 0; + break; + case "http_send": + var reqObj = JSON.parse(new TextDecoder().decode(req_.req)); + reqObj.mode = "cors"; + reqObj.credentials = "omit"; + if (reqObj.headers && reqObj.headers["User-Agent"] != "") { + delete reqObj.headers["User-Agent"]; // Browser will add its own value. + } + var reqID = getID(); + if (reqID < 0) { + console.log(name + ":" + "failed to get id"); + streamStatus[0] = -1; + break; + } + var connObj = { + address: new TextDecoder().decode(req_.address), + request: reqObj, + requestSent: false, + reqBodybuf: new Uint8Array(0), + reqBodyEOF: false, + }; + httpConnections[reqID] = connObj; + streamStatus[0] = reqID; + break; + case "http_writebody": + httpConnections[req_.id].reqBodybuf = appendData(httpConnections[req_.id].reqBodybuf, req_.body) + httpConnections[req_.id].reqBodyEOF = req_.isEOF; + streamStatus[0] = 0; + if (req_.isEOF && !httpConnections[req_.id].requestSent) { + httpConnections[req_.id].requestSent = true; + var connObj = httpConnections[req_.id]; + if ((connObj.request.method != "HEAD") && (connObj.request.method != "GET")) { + connObj.request.body = connObj.reqBodybuf; + } + fetch(connObj.address, connObj.request).then((resp) => { + connObj.response = new TextEncoder().encode(JSON.stringify({ + bodyUsed: resp.bodyUsed, + headers: resp.headers, + redirected: resp.redirected, + status: resp.status, + statusText: resp.statusText, + type: resp.type, + url: resp.url + })), + connObj.done = false; + connObj.respBodybuf = new Uint8Array(0); + if (resp.ok) { + resp.arrayBuffer().then((data) => { + connObj.respBodybuf = new Uint8Array(data); + connObj.done = true; + }).catch((error) => { + connObj.respBodybuf = new Uint8Array(0); + connObj.done = true; + console.log("failed to fetch body: " + error); + }); + } else { + connObj.done = true; + } + }).catch((error) => { + connObj.response = new TextEncoder().encode(JSON.stringify({ + status: 503, + statusText: "Service Unavailable", + })) + connObj.respBodybuf = new Uint8Array(0); + connObj.done = true; + }); + } + break; + case "http_isreadable": + if ((httpConnections[req_.id] != undefined) && (httpConnections[req_.id].response != undefined)) { + streamData[0] = 1; // ready for reading + } else { + streamData[0] = 0; // nothing to read + } + streamStatus[0] = 0; + break; + case "http_recv": + if ((httpConnections[req_.id] == undefined) || (httpConnections[req_.id].response == undefined)) { + console.log(name + ":" + "response is not available"); + streamStatus[0] = -1; + break; + } + httpConnections[req_.id].response = serveData(httpConnections[req_.id].response, req_.len); + streamStatus[0] = 0; + if (httpConnections[req_.id].response.byteLength == 0) { + streamStatus[0] = 1; // isEOF + } + break; + case "http_readbody": + if ((httpConnections[req_.id] == undefined) || (httpConnections[req_.id].response == undefined)) { + console.log(name + ":" + "response body is not available"); + streamStatus[0] = -1; + break; + } + httpConnections[req_.id].respBodybuf = serveData(httpConnections[req_.id].respBodybuf, req_.len); + streamStatus[0] = 0; + if ((httpConnections[req_.id].done) && (httpConnections[req_.id].respBodybuf.byteLength == 0)) { + streamStatus[0] = 1; + delete httpConnections[req_.id]; // connection done + } + break; + case "send_cert": + certbuf.buf = appendData(certbuf.buf, req_.buf); + certbuf.done = true; + streamStatus[0] = 0; + break; + case "recv_cert": + if (!certbuf.done) { + streamStatus[0] = -1; + break; + } + certbuf.buf = serveData(certbuf.buf, req_.len); + streamStatus[0] = 0; + if (certbuf.buf.byteLength == 0) { + streamStatus[0] = 1; // isEOF + } + break; + default: + console.log(name + ":" + "unknown request: " + req_.type) + return; + } + Atomics.store(streamCtrl, 0, 1); + Atomics.notify(streamCtrl, 0); + } else { + console.log("UNKNOWN MSG " + msg); + } + } +} + +function appendData(data1, data2) { + buf2 = new Uint8Array(data1.byteLength + data2.byteLength); + buf2.set(new Uint8Array(data1), 0); + buf2.set(new Uint8Array(data2), data1.byteLength); + return buf2; +} diff --git a/src/wasi-util.js b/src/wasi-util.js new file mode 100644 index 0000000..7714298 --- /dev/null +++ b/src/wasi-util.js @@ -0,0 +1,127 @@ +//////////////////////////////////////////////////////////// +// +// event-related classes adopted from the on-going discussion +// towards poll_oneoff support in browser_wasi_sim project. +// Ref: https://github.com/bjorn3/browser_wasi_shim/issues/14#issuecomment-1450351935 +// +//////////////////////////////////////////////////////////// + +class EventType { + /*:: variant: "clock" | "fd_read" | "fd_write"*/ + + constructor(variant/*: "clock" | "fd_read" | "fd_write"*/) { + this.variant = variant; + } + + static from_u8(data/*: number*/)/*: EventType*/ { + switch (data) { + case EVENTTYPE_CLOCK: + return new EventType("clock"); + case EVENTTYPE_FD_READ: + return new EventType("fd_read"); + case EVENTTYPE_FD_WRITE: + return new EventType("fd_write"); + default: + throw "Invalid event type " + String(data); + } + } + + to_u8()/*: number*/ { + switch (this.variant) { + case "clock": + return EVENTTYPE_CLOCK; + case "fd_read": + return EVENTTYPE_FD_READ; + case "fd_write": + return EVENTTYPE_FD_WRITE; + default: + throw "unreachable"; + } + } +} + +class Event { + /*:: userdata: UserData*/ + /*:: error: number*/ + /*:: type: EventType*/ + /*:: fd_readwrite: EventFdReadWrite | null*/ + + write_bytes(view/*: DataView*/, ptr/*: number*/) { + view.setBigUint64(ptr, this.userdata, true); + view.setUint8(ptr + 8, this.error); + view.setUint8(ptr + 9, 0); + view.setUint8(ptr + 10, this.type.to_u8()); + // if (this.fd_readwrite) { + // this.fd_readwrite.write_bytes(view, ptr + 16); + // } + } + + static write_bytes_array(view/*: DataView*/, ptr/*: number*/, events/*: Array*/) { + for (let i = 0; i < events.length; i++) { + events[i].write_bytes(view, ptr + 32 * i); + } + } +} + +class SubscriptionClock { + /*:: timeout: number*/ + + static read_bytes(view/*: DataView*/, ptr/*: number*/)/*: SubscriptionFdReadWrite*/ { + let self = new SubscriptionClock(); + self.timeout = Number(view.getBigUint64(ptr + 8, true)); + return self; + } +} + +class SubscriptionFdReadWrite { + /*:: fd: number*/ + + static read_bytes(view/*: DataView*/, ptr/*: number*/)/*: SubscriptionFdReadWrite*/ { + let self = new SubscriptionFdReadWrite(); + self.fd = view.getUint32(ptr, true); + return self; + } +} + +class SubscriptionU { + /*:: tag: EventType */ + /*:: data: SubscriptionClock | SubscriptionFdReadWrite */ + + static read_bytes(view/*: DataView*/, ptr/*: number*/)/*: SubscriptionU*/ { + let self = new SubscriptionU(); + self.tag = EventType.from_u8(view.getUint8(ptr)); + switch (self.tag.variant) { + case "clock": + self.data = SubscriptionClock.read_bytes(view, ptr + 8); + break; + case "fd_read": + case "fd_write": + self.data = SubscriptionFdReadWrite.read_bytes(view, ptr + 8); + break; + default: + throw "unreachable"; + } + return self; + } +} + +class Subscription { + /*:: userdata: UserData */ + /*:: u: SubscriptionU */ + + static read_bytes(view/*: DataView*/, ptr/*: number*/)/*: Subscription*/ { + let subscription = new Subscription(); + subscription.userdata = view.getBigUint64(ptr, true); + subscription.u = SubscriptionU.read_bytes(view, ptr + 8); + return subscription; + } + + static read_bytes_array(view/*: DataView*/, ptr/*: number*/, len/*: number*/)/*: Array*/ { + let subscriptions = []; + for (let i = 0; i < len; i++) { + subscriptions.push(Subscription.read_bytes(view, ptr + 48 * i)); + } + return subscriptions; + } +} + diff --git a/src/worker-util.js b/src/worker-util.js new file mode 100644 index 0000000..674d904 --- /dev/null +++ b/src/worker-util.js @@ -0,0 +1,295 @@ +var streamCtrl; +var streamStatus; +var streamLen; +var streamData; +function registerSocketBuffer(shared){ + streamCtrl = new Int32Array(shared, 0, 1); + streamStatus = new Int32Array(shared, 4, 1); + streamLen = new Int32Array(shared, 8, 1); + streamData = new Uint8Array(shared, 12); +} + +var imagename; +var numchunks; +function serveIfInitMsg(msg) { + const req_ = msg.data; + if (typeof req_ == "object"){ + if (req_.type == "init") { + if (req_.buf) { + var shared = req_.buf; + registerSocketBuffer(shared); + } + if (req_.imagename) + imagename = req_.imagename; + numchunks = req_.chunks; + return true; + } + } + + return false; +} + +function getImagename() { + return imagename; +} + +// fetchChunks fetches specified number of consecutive chunks and +// passes the concatinated array buffer to the callback. +function fetchChunks(f) { + var prefix = imagename; + var chunks = numchunks; + var format = s => prefix + s + '.wasm'; + var files = []; + for (i = 0; i < chunks; i++) { + var s = i.toString(); + while (s.length < 2) s = "0" + s; + files[i] = s; + } + files = files.map(format); + var list = []; + files.forEach(file => list.push(fetch(file))); + var results = []; + var blob; + Promise.all(list).then(resps => { + resps.forEach(r => results.push(r['arrayBuffer']())); + Promise.all(results).then(ab => { + blob = new Blob(ab); + blob.arrayBuffer().then(f); + }) + }); +} + +const errStatus = { + val: 0, +}; + +function sockAccept(){ + streamCtrl[0] = 0; + postMessage({type: "accept"}); + Atomics.wait(streamCtrl, 0, 0); + return streamData[0] == 1; +} +function sockSend(data){ + streamCtrl[0] = 0; + postMessage({type: "send", buf: data}); + Atomics.wait(streamCtrl, 0, 0); + if (streamStatus[0] < 0) { + errStatus.val = streamStatus[0] + return errStatus; + } +} +function sockRecv(len){ + streamCtrl[0] = 0; + postMessage({type: "recv", len: len}); + Atomics.wait(streamCtrl, 0, 0); + if (streamStatus[0] < 0) { + errStatus.val = streamStatus[0] + return errStatus; + } + let ddlen = streamLen[0]; + var res = streamData.slice(0, ddlen); + return res; +} + +function sockWaitForReadable(timeout){ + streamCtrl[0] = 0; + postMessage({type: "recv-is-readable", timeout: timeout}); + Atomics.wait(streamCtrl, 0, 0); + if (streamStatus[0] < 0) { + errStatus.val = streamStatus[0] + return errStatus; + } + return streamData[0] == 1; +} + +function sendCert(data){ + streamCtrl[0] = 0; + postMessage({type: "send_cert", buf: data}); + Atomics.wait(streamCtrl, 0, 0); + if (streamStatus[0] < 0) { + errStatus.val = streamStatus[0] + return errStatus; + } +} + +function recvCert(){ + var buf = new Uint8Array(0); + return new Promise((resolve, reject) => { + function getCert(){ + streamCtrl[0] = 0; + postMessage({type: "recv_cert"}); + Atomics.wait(streamCtrl, 0, 0); + if (streamStatus[0] < 0) { + setTimeout(getCert, 100); + return; + } + var ddlen = streamLen[0]; + buf = appendData(buf, streamData.slice(0, ddlen)); + if (streamStatus[0] == 0) { + resolve(buf); // EOF + } else { + setTimeout(getCert, 0); + return; + } + } + getCert(); + }); +} + +function appendData(data1, data2) { + buf2 = new Uint8Array(data1.byteLength + data2.byteLength); + buf2.set(new Uint8Array(data1), 0); + buf2.set(new Uint8Array(data2), data1.byteLength); + return buf2; +} + +function getCertDir(cert) { + var certDir = new PreopenDirectory("/.wasmenv", { + "proxy.crt": new File(cert) + }); + var _path_open = certDir.path_open; + certDir.path_open = (e, r, s, n, a, d) => { + var ret = _path_open.apply(certDir, [e, r, s, n, a, d]); + if (ret.fd_obj != null) { + var o = ret.fd_obj; + ret.fd_obj.fd_pread = (view8, iovs, offset) => { + var old_offset = o.file_pos; + var r = o.fd_seek(offset, WHENCE_SET); + if (r.ret != 0) { + return { ret: -1, nread: 0 }; + } + var read_ret = o.fd_read(view8, iovs); + r = o.fd_seek(old_offset, WHENCE_SET); + if (r.ret != 0) { + return { ret: -1, nread: 0 }; + } + return read_ret; + } + } + return ret; + } + certDir.dir.contents["."] = certDir.dir; + return certDir; +} + +function wasiHackSocket(wasi, listenfd, connfd) { + // definition from wasi-libc https://github.com/WebAssembly/wasi-libc/blob/wasi-sdk-19/expected/wasm32-wasi/predefined-macros.txt + const ERRNO_INVAL = 28; + const ERRNO_AGAIN= 6; + var connfdUsed = false; + var connbuf = new Uint8Array(0); + var _fd_close = wasi.wasiImport.fd_close; + wasi.wasiImport.fd_close = (fd) => { + if (fd == connfd) { + connfdUsed = false; + return 0; + } + return _fd_close.apply(wasi.wasiImport, [fd]); + } + var _fd_read = wasi.wasiImport.fd_read; + wasi.wasiImport.fd_read = (fd, iovs_ptr, iovs_len, nread_ptr) => { + if (fd == connfd) { + return wasi.wasiImport.sock_recv(fd, iovs_ptr, iovs_len, 0, nread_ptr, 0); + } + return _fd_read.apply(wasi.wasiImport, [fd, iovs_ptr, iovs_len, nread_ptr]); + } + var _fd_write = wasi.wasiImport.fd_write; + wasi.wasiImport.fd_write = (fd, iovs_ptr, iovs_len, nwritten_ptr) => { + if (fd == connfd) { + return wasi.wasiImport.sock_send(fd, iovs_ptr, iovs_len, 0, nwritten_ptr); + } + return _fd_write.apply(wasi.wasiImport, [fd, iovs_ptr, iovs_len, nwritten_ptr]); + } + var _fd_fdstat_get = wasi.wasiImport.fd_fdstat_get; + wasi.wasiImport.fd_fdstat_get = (fd, fdstat_ptr) => { + if ((fd == listenfd) || (fd == connfd) && connfdUsed){ + let buffer = new DataView(wasi.inst.exports.memory.buffer); + // https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fdstat-struct + buffer.setUint8(fdstat_ptr, 6); // filetype = 6 (socket_stream) + buffer.setUint8(fdstat_ptr + 1, 2); // fdflags = 2 (nonblock) + return 0; + } + return _fd_fdstat_get.apply(wasi.wasiImport, [fd, fdstat_ptr]); + } + wasi.wasiImport.sock_accept = (fd, flags, fd_ptr) => { + if (fd != listenfd) { + console.log("sock_accept: unknown fd " + fd); + return ERRNO_INVAL; + } + if (connfdUsed) { + console.log("sock_accept: multi-connection is unsupported"); + return ERRNO_INVAL; + } + if (!sockAccept()) { + return ERRNO_AGAIN; + } + connfdUsed = true; + var buffer = new DataView(wasi.inst.exports.memory.buffer); + buffer.setUint32(fd_ptr, connfd, true); + return 0; + } + wasi.wasiImport.sock_send = (fd, iovs_ptr, iovs_len, si_flags/*not defined*/, nwritten_ptr) => { + if (fd != connfd) { + console.log("sock_send: unknown fd " + fd); + return ERRNO_INVAL; + } + var buffer = new DataView(wasi.inst.exports.memory.buffer); + var buffer8 = new Uint8Array(wasi.inst.exports.memory.buffer); + var iovecs = Ciovec.read_bytes_array(buffer, iovs_ptr, iovs_len); + var wtotal = 0 + for (i = 0; i < iovecs.length; i++) { + var iovec = iovecs[i]; + var buf = buffer8.slice(iovec.buf, iovec.buf + iovec.buf_len); + if (buf.length == 0) { + continue; + } + var ret = sockSend(buf.buffer.slice(0, iovec.buf_len)); + if (ret == errStatus) { + return ERRNO_INVAL; + } + wtotal += buf.length; + } + buffer.setUint32(nwritten_ptr, wtotal, true); + return 0; + } + wasi.wasiImport.sock_recv = (fd, iovs_ptr, iovs_len, ri_flags, nread_ptr, ro_flags_ptr) => { + if (ri_flags != 0) { + console.log("ri_flags are unsupported"); // TODO + } + if (fd != connfd) { + console.log("sock_recv: unknown fd " + fd); + return ERRNO_INVAL; + } + var sockreadable = sockWaitForReadable(); + if (sockreadable == errStatus) { + return ERRNO_INVAL; + } else if (sockreadable == false) { + return ERRNO_AGAIN; + } + var buffer = new DataView(wasi.inst.exports.memory.buffer); + var buffer8 = new Uint8Array(wasi.inst.exports.memory.buffer); + var iovecs = Iovec.read_bytes_array(buffer, iovs_ptr, iovs_len); + var nread = 0; + for (i = 0; i < iovecs.length; i++) { + var iovec = iovecs[i]; + if (iovec.buf_len == 0) { + continue; + } + var data = sockRecv(iovec.buf_len); + if (data == errStatus) { + return ERRNO_INVAL; + } + buffer8.set(data, iovec.buf); + nread += data.length; + } + buffer.setUint32(nread_ptr, nread, true); + // TODO: support ro_flags_ptr + return 0; + } + wasi.wasiImport.sock_shutdown = (fd, sdflags) => { + if (fd == connfd) { + connfdUsed = false; + } + return 0; + } +} diff --git a/src/worker.js b/src/worker.js new file mode 100644 index 0000000..f181089 --- /dev/null +++ b/src/worker.js @@ -0,0 +1,226 @@ +importScripts("https://cdn.jsdelivr.net/npm/xterm-pty@0.9.4/workerTools.js"); +importScripts(location.origin + "/tib-demo" + "/src/browser_wasi_shim/index.js"); +importScripts(location.origin + "/tib-demo" + "/src/browser_wasi_shim/wasi_defs.js"); +importScripts(location.origin + "/tib-demo" + "/src/worker-util.js"); +importScripts(location.origin + "/tib-demo" + "/src/wasi-util.js"); + +onmessage = (msg) => { + if (serveIfInitMsg(msg)) { + return; + } + var ttyClient = new TtyClient(msg.data); + var args = []; + var env = []; + var fds = []; + var netParam = getNetParam(); + var listenfd = 3; + fetchChunks((wasm) => { + if (!netParam || netParam.mode != 'none') { + recvCert().then((cert) => { + var certDir = getCertDir(cert); + fds = [ + undefined, // 0: stdin + undefined, // 1: stdout + undefined, // 2: stderr + certDir, // 3: certificates dir + undefined, // 4: socket listenfd + undefined, // 5: accepted socket fd (multi-connection is unsupported) + // 6...: used by wasi shim + ]; + args = ['arg0', '--net=socket=listenfd=4', '--mac', genmac()]; + env = [ + "SSL_CERT_FILE=/.wasmenv/proxy.crt", + "https_proxy=http://192.168.127.253:80", + "http_proxy=http://192.168.127.253:80", + "HTTPS_PROXY=http://192.168.127.253:80", + "HTTP_PROXY=http://192.168.127.253:80" + ]; + listenfd = 4; + startWasi(wasm, ttyClient, args, env, fds, listenfd, 5); + }); + return; + } + startWasi(wasm, ttyClient, args, env, fds, listenfd, 5); + }); +}; + +function startWasi(wasm, ttyClient, args, env, fds, listenfd, connfd) { + var cmd = getArgs(); + if (cmd) { + var flags = ['-entrypoint', '/bin/sh', '--', '-c', cmd]; + if (args.length > 0) { + args = args.concat(flags); + } else { + args = ['arg0']; + args = args.concat(flags); + } + } + var wasi = new WASI(args, env, fds); + wasiHack(wasi, ttyClient, connfd); + wasiHackSocket(wasi, listenfd, connfd); + WebAssembly.instantiate(wasm, { + "wasi_snapshot_preview1": wasi.wasiImport, + }).then((inst) => { + wasi.start(inst.instance); + }); +} + +function getArgs() { + var vars = location.search.substring(1).split('&'); + for (var i = 0; i < vars.length; i++) { + var kv = vars[i].split('='); + if (decodeURIComponent(kv[0]) == 'args') { + return decodeURIComponent(kv[1]); + } + } +} + +// wasiHack patches wasi object for integrating it to xterm-pty. +function wasiHack(wasi, ttyClient, connfd) { + // definition from wasi-libc https://github.com/WebAssembly/wasi-libc/blob/wasi-sdk-19/expected/wasm32-wasi/predefined-macros.txt + const ERRNO_INVAL = 28; + const ERRNO_AGAIN= 6; + var _fd_read = wasi.wasiImport.fd_read; + wasi.wasiImport.fd_read = (fd, iovs_ptr, iovs_len, nread_ptr) => { + if (fd == 0) { + var buffer = new DataView(wasi.inst.exports.memory.buffer); + var buffer8 = new Uint8Array(wasi.inst.exports.memory.buffer); + var iovecs = Iovec.read_bytes_array(buffer, iovs_ptr, iovs_len); + var nread = 0; + for (i = 0; i < iovecs.length; i++) { + var iovec = iovecs[i]; + if (iovec.buf_len == 0) { + continue; + } + var data = ttyClient.onRead(iovec.buf_len); + buffer8.set(data, iovec.buf); + nread += data.length; + } + buffer.setUint32(nread_ptr, nread, true); + return 0; + } else { + console.log("fd_read: unknown fd " + fd); + return _fd_read.apply(wasi.wasiImport, [fd, iovs_ptr, iovs_len, nread_ptr]); + } + return ERRNO_INVAL; + } + var _fd_write = wasi.wasiImport.fd_write; + wasi.wasiImport.fd_write = (fd, iovs_ptr, iovs_len, nwritten_ptr) => { + if ((fd == 1) || (fd == 2)) { + var buffer = new DataView(wasi.inst.exports.memory.buffer); + var buffer8 = new Uint8Array(wasi.inst.exports.memory.buffer); + var iovecs = Ciovec.read_bytes_array(buffer, iovs_ptr, iovs_len); + var wtotal = 0 + for (i = 0; i < iovecs.length; i++) { + var iovec = iovecs[i]; + var buf = buffer8.slice(iovec.buf, iovec.buf + iovec.buf_len); + if (buf.length == 0) { + continue; + } + ttyClient.onWrite(Array.from(buf)); + wtotal += buf.length; + } + buffer.setUint32(nwritten_ptr, wtotal, true); + return 0; + } else { + console.log("fd_write: unknown fd " + fd); + return _fd_write.apply(wasi.wasiImport, [fd, iovs_ptr, iovs_len, nwritten_ptr]); + } + return ERRNO_INVAL; + } + wasi.wasiImport.poll_oneoff = (in_ptr, out_ptr, nsubscriptions, nevents_ptr) => { + if (nsubscriptions == 0) { + return ERRNO_INVAL; + } + let buffer = new DataView(wasi.inst.exports.memory.buffer); + let in_ = Subscription.read_bytes_array(buffer, in_ptr, nsubscriptions); + let isReadPollStdin = false; + let isReadPollConn = false; + let isClockPoll = false; + let pollSubStdin; + let pollSubConn; + let clockSub; + let timeout = Number.MAX_VALUE; + for (let sub of in_) { + if (sub.u.tag.variant == "fd_read") { + if ((sub.u.data.fd != 0) && (sub.u.data.fd != connfd)) { + console.log("poll_oneoff: unknown fd " + sub.u.data.fd); + return ERRNO_INVAL; // only fd=0 and connfd is supported as of now (FIXME) + } + if (sub.u.data.fd == 0) { + isReadPollStdin = true; + pollSubStdin = sub; + } else { + isReadPollConn = true; + pollSubConn = sub; + } + } else if (sub.u.tag.variant == "clock") { + if (sub.u.data.timeout < timeout) { + timeout = sub.u.data.timeout + isClockPoll = true; + clockSub = sub; + } + } else { + console.log("poll_oneoff: unknown variant " + sub.u.tag.variant); + return ERRNO_INVAL; // FIXME + } + } + let events = []; + if (isReadPollStdin || isReadPollConn || isClockPoll) { + var readable = false; + if (isReadPollStdin || (isClockPoll && timeout > 0)) { + readable = ttyClient.onWaitForReadable(timeout / 1000000000); + } + if (readable && isReadPollStdin) { + let event = new Event(); + event.userdata = pollSubStdin.userdata; + event.error = 0; + event.type = new EventType("fd_read"); + events.push(event); + } + if (isReadPollConn) { + var sockreadable = sockWaitForReadable(); + if (sockreadable == errStatus) { + return ERRNO_INVAL; + } else if (sockreadable == true) { + let event = new Event(); + event.userdata = pollSubConn.userdata; + event.error = 0; + event.type = new EventType("fd_read"); + events.push(event); + } + } + if (isClockPoll) { + let event = new Event(); + event.userdata = clockSub.userdata; + event.error = 0; + event.type = new EventType("clock"); + events.push(event); + } + } + var len = events.length; + Event.write_bytes_array(buffer, out_ptr, events); + buffer.setUint32(nevents_ptr, len, true); + return 0; + } +} + +function getNetParam() { + var vars = location.search.substring(1).split('&'); + for (var i = 0; i < vars.length; i++) { + var kv = vars[i].split('='); + if (decodeURIComponent(kv[0]) == 'net') { + return { + mode: kv[1], + param: kv[2], + }; + } + } + return null; +} + +function genmac(){ + return "02:XX:XX:XX:XX:XX".replace(/X/g, function() { + return "0123456789ABCDEF".charAt(Math.floor(Math.random() * 16)) + }); +} diff --git a/worker.js b/worker.js new file mode 100644 index 0000000..d403009 --- /dev/null +++ b/worker.js @@ -0,0 +1,42 @@ +var worker; +var stackWorker; + +function startWasi(elemId, workerFileName, workerImageNamePrefix, workerImageChunks) { + const xterm = new Terminal(); + xterm.open(document.getElementById(elemId)); + const { master, slave } = openpty(); + var termios = slave.ioctl("TCGETS"); + termios.iflag &= ~(/*IGNBRK | BRKINT | PARMRK |*/ ISTRIP | INLCR | IGNCR | ICRNL | IXON); + termios.oflag &= ~(OPOST); + termios.lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN); + //termios.cflag &= ~(CSIZE | PARENB); + //termios.cflag |= CS8; + slave.ioctl("TCSETS", new Termios(termios.iflag, termios.oflag, termios.cflag, termios.lflag, termios.cc)); + xterm.loadAddon(master); + worker = new Worker(workerFileName); + + var nwStack; + var netParam = getNetParam(); + if (!netParam || netParam.mode != 'none') { + stackWorker = new Worker("./src/stack-worker.js"+location.search); + nwStack = newStack(worker, workerImageNamePrefix, workerImageChunks, stackWorker, location.origin + "/tib-demo" + "/src/c2w-net-proxy.wasm"); + } + if (!nwStack) { + worker.postMessage({type: "init", imagename: workerImageNamePrefix, chunks: workerImageChunks}); + } + new TtyServer(slave).start(worker, nwStack); +} + +function getNetParam() { + var vars = location.search.substring(1).split('&'); + for (var i = 0; i < vars.length; i++) { + var kv = vars[i].split('='); + if (decodeURIComponent(kv[0]) == 'net') { + return { + mode: kv[1], + param: kv[2], + }; + } + } + return null; +}