213 lines
8.3 KiB
JavaScript
213 lines
8.3 KiB
JavaScript
|
importScripts("https://cdn.jsdelivr.net/npm/xterm-pty@0.9.4/workerTools.js");
|
||
|
importScripts(location.origin + "/browser_wasi_shim/index.js");
|
||
|
importScripts(location.origin + "/browser_wasi_shim/wasi_defs.js");
|
||
|
importScripts(location.origin + "/worker-util.js");
|
||
|
importScripts(location.origin + "/wasi-util.js");
|
||
|
|
||
|
onmessage = (msg) => {
|
||
|
if (serveIfInitMsg(msg)) {
|
||
|
return;
|
||
|
}
|
||
|
var ttyClient = new TtyClient(msg.data);
|
||
|
var args = [];
|
||
|
var env = [];
|
||
|
var fds = [];
|
||
|
var netParam = null;
|
||
|
var listenfd = 3;
|
||
|
fetch(getImagename(), { credentials: 'same-origin' }).then((resp) => {
|
||
|
resp['arrayBuffer']().then((wasm) => {
|
||
|
if (netParam) {
|
||
|
if (netParam.mode == 'delegate') {
|
||
|
args = ['arg0', '--net=socket', '--mac', genmac()];
|
||
|
} else if (netParam.mode == 'browser') {
|
||
|
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 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);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
// 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))
|
||
|
});
|
||
|
}
|