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;
+}