mirror of
https://github.com/copy/v86.git
synced 2025-12-31 04:23:15 +00:00
wisp_network adapter (#1097)
This commit is contained in:
parent
23d279c26d
commit
67bfed335d
5 changed files with 687 additions and 56 deletions
3
Makefile
3
Makefile
|
|
@ -87,7 +87,7 @@ CORE_FILES=const.js config.js io.js main.js lib.js buffer.js ide.js pci.js flopp
|
|||
LIB_FILES=9p.js filesystem.js jor1k.js marshall.js utf8.js
|
||||
BROWSER_FILES=screen.js keyboard.js mouse.js speaker.js serial.js \
|
||||
network.js starter.js worker_bus.js dummy_screen.js \
|
||||
fake_network.js fetch_network.js print_stats.js filestorage.js
|
||||
fake_network.js wisp_network.js fetch_network.js print_stats.js filestorage.js
|
||||
|
||||
RUST_FILES=$(shell find src/rust/ -name '*.rs') \
|
||||
src/rust/gen/interpreter.rs src/rust/gen/interpreter0f.rs \
|
||||
|
|
@ -306,6 +306,7 @@ devices-test: all-debug
|
|||
./tests/devices/virtio_9p.js
|
||||
./tests/devices/virtio_console.js
|
||||
./tests/devices/fetch_network.js
|
||||
./tests/devices/wisp_network.js
|
||||
|
||||
rust-test: $(RUST_FILES)
|
||||
env RUSTFLAGS="-D warnings" RUST_BACKTRACE=full RUST_TEST_THREADS=1 cargo test -- --nocapture
|
||||
|
|
|
|||
|
|
@ -225,33 +225,7 @@ function handle_fake_networking(data, adapter) {
|
|||
}
|
||||
|
||||
if(packet.arp && packet.arp.oper === 1 && packet.arp.ptype === ETHERTYPE_IPV4) {
|
||||
let packet_subnet = iptolong(packet.arp.tpa) & 0xFFFFFF00;
|
||||
let router_subnet = iptolong(adapter.router_ip) & 0xFFFFFF00;
|
||||
|
||||
if(!adapter.masquerade) {
|
||||
if(packet_subnet !== router_subnet) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if(packet_subnet === router_subnet) {
|
||||
// Ignore the DHCP client area
|
||||
if(packet.arp.tpa[3] > 99) return;
|
||||
}
|
||||
|
||||
// Reply to ARP Whohas
|
||||
let reply = {};
|
||||
reply.eth = { ethertype: ETHERTYPE_ARP, src: adapter.router_mac, dest: packet.eth.src };
|
||||
reply.arp = {
|
||||
htype: 1,
|
||||
ptype: ETHERTYPE_IPV4,
|
||||
oper: 2,
|
||||
sha: adapter.router_mac,
|
||||
spa: packet.arp.tpa,
|
||||
tha: packet.eth.src,
|
||||
tpa: packet.arp.spa
|
||||
};
|
||||
adapter.receive(make_packet(reply));
|
||||
arp_whohas(packet, adapter);
|
||||
}
|
||||
|
||||
if(packet.dns) {
|
||||
|
|
@ -264,20 +238,7 @@ function handle_fake_networking(data, adapter) {
|
|||
|
||||
// ICMP Ping
|
||||
if(packet.icmp && packet.icmp.type === 8) {
|
||||
let reply = {};
|
||||
reply.eth = { ethertype: ETHERTYPE_IPV4, src: adapter.router_mac, dest: packet.eth.src };
|
||||
reply.ipv4 = {
|
||||
proto: IPV4_PROTO_ICMP,
|
||||
src: adapter.router_ip,
|
||||
dest: packet.ipv4.src,
|
||||
};
|
||||
reply.icmp = {
|
||||
type: 0,
|
||||
code: packet.icmp.code,
|
||||
data: packet.icmp.data
|
||||
};
|
||||
adapter.receive(make_packet(reply));
|
||||
return;
|
||||
handle_fake_ping(packet, adapter);
|
||||
}
|
||||
|
||||
if(packet.dhcp) {
|
||||
|
|
@ -285,20 +246,7 @@ function handle_fake_networking(data, adapter) {
|
|||
}
|
||||
|
||||
if(packet.udp && packet.udp.dport === 8) {
|
||||
// UDP Echo Server
|
||||
let reply = {};
|
||||
reply.eth = { ethertype: ETHERTYPE_IPV4, src: adapter.router_mac, dest: packet.eth.src };
|
||||
reply.ipv4 = {
|
||||
proto: IPV4_PROTO_UDP,
|
||||
src: packet.ipv4.dest,
|
||||
dest: packet.ipv4.src,
|
||||
};
|
||||
reply.udp = {
|
||||
sport: packet.udp.dport,
|
||||
dport: packet.udp.sport,
|
||||
data: new TextEncoder().encode(packet.udp.data_s)
|
||||
};
|
||||
adapter.receive(make_packet(reply));
|
||||
handle_udp_echo(packet, adapter);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -528,6 +476,7 @@ function parse_udp(data, o) {
|
|||
dport: view.getUint16(2),
|
||||
len: view.getUint16(4),
|
||||
checksum: view.getUint16(6),
|
||||
data: data.subarray(8),
|
||||
data_s: new TextDecoder().decode(data.subarray(8))
|
||||
};
|
||||
|
||||
|
|
@ -1133,3 +1082,67 @@ TCPConnection.prototype.pump = function() {
|
|||
this.net.receive(make_packet(reply));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
function arp_whohas(packet, adapter) {
|
||||
let packet_subnet = iptolong(packet.arp.tpa) & 0xFFFFFF00;
|
||||
let router_subnet = iptolong(adapter.router_ip) & 0xFFFFFF00;
|
||||
|
||||
if(!adapter.masquerade) {
|
||||
if(packet_subnet !== router_subnet) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if(packet_subnet === router_subnet) {
|
||||
// Ignore the DHCP client area
|
||||
if(packet.arp.tpa[3] > 99) return;
|
||||
}
|
||||
|
||||
// Reply to ARP Whohas
|
||||
let reply = {};
|
||||
reply.eth = { ethertype: ETHERTYPE_ARP, src: adapter.router_mac, dest: packet.eth.src };
|
||||
reply.arp = {
|
||||
htype: 1,
|
||||
ptype: ETHERTYPE_IPV4,
|
||||
oper: 2,
|
||||
sha: adapter.router_mac,
|
||||
spa: packet.arp.tpa,
|
||||
tha: packet.eth.src,
|
||||
tpa: packet.arp.spa
|
||||
};
|
||||
adapter.receive(make_packet(reply));
|
||||
}
|
||||
|
||||
function handle_fake_ping(packet, adapter) {
|
||||
let reply = {};
|
||||
reply.eth = { ethertype: ETHERTYPE_IPV4, src: adapter.router_mac, dest: packet.eth.src };
|
||||
reply.ipv4 = {
|
||||
proto: IPV4_PROTO_ICMP,
|
||||
src: adapter.router_ip,
|
||||
dest: packet.ipv4.src,
|
||||
};
|
||||
reply.icmp = {
|
||||
type: 0,
|
||||
code: packet.icmp.code,
|
||||
data: packet.icmp.data
|
||||
};
|
||||
adapter.receive(make_packet(reply));
|
||||
}
|
||||
|
||||
function handle_udp_echo(packet, adapter) {
|
||||
// UDP Echo Server
|
||||
let reply = {};
|
||||
reply.eth = { ethertype: ETHERTYPE_IPV4, src: adapter.router_mac, dest: packet.eth.src };
|
||||
reply.ipv4 = {
|
||||
proto: IPV4_PROTO_UDP,
|
||||
src: packet.ipv4.dest,
|
||||
dest: packet.ipv4.src,
|
||||
};
|
||||
reply.udp = {
|
||||
sport: packet.udp.dport,
|
||||
dport: packet.udp.sport,
|
||||
data: new TextEncoder().encode(packet.udp.data_s)
|
||||
};
|
||||
adapter.receive(make_packet(reply));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -297,6 +297,9 @@ V86.prototype.continue_init = async function(emulator, options)
|
|||
{
|
||||
this.network_adapter = new FetchNetworkAdapter(this.bus);
|
||||
}
|
||||
else if(options.network_relay_url.startsWith("wisp://") || options.network_relay_url.startsWith("wisps://")) {
|
||||
this.network_adapter = new WispNetworkAdapter(options.network_relay_url, this.bus, options);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.network_adapter = new NetworkAdapter(options.network_relay_url, this.bus);
|
||||
|
|
|
|||
302
src/browser/wisp_network.js
Normal file
302
src/browser/wisp_network.js
Normal file
|
|
@ -0,0 +1,302 @@
|
|||
"use strict";
|
||||
|
||||
const DEFAULT_DOH_SERVER = "cloudflare-dns.com";
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*
|
||||
* @param {BusConnector} bus
|
||||
* @param {*=} config
|
||||
*/
|
||||
function WispNetworkAdapter(wisp_url, bus, config)
|
||||
{
|
||||
|
||||
this.register_ws(wisp_url);
|
||||
this.last_stream = 1;
|
||||
this.connections = {0: {congestion: 0}};
|
||||
this.congested_buffer = [];
|
||||
|
||||
config = config || {};
|
||||
this.bus = bus;
|
||||
this.id = config.id || 0;
|
||||
this.router_mac = new Uint8Array((config.router_mac || "52:54:0:1:2:3").split(":").map(function(x) { return parseInt(x, 16); }));
|
||||
this.router_ip = new Uint8Array((config.router_ip || "192.168.86.1").split(".").map(function(x) { return parseInt(x, 10); }));
|
||||
this.vm_ip = new Uint8Array((config.vm_ip || "192.168.86.100").split(".").map(function(x) { return parseInt(x, 10); }));
|
||||
this.masquerade = config.masquerade === undefined || !!config.masquerade;
|
||||
this.vm_mac = new Uint8Array(6);
|
||||
this.doh_server = config.doh_server || DEFAULT_DOH_SERVER;
|
||||
this.tcp_conn = {};
|
||||
|
||||
this.bus.register("net" + this.id + "-mac", function(mac) {
|
||||
this.vm_mac = new Uint8Array(mac.split(":").map(function(x) { return parseInt(x, 16); }));
|
||||
}, this);
|
||||
this.bus.register("net" + this.id + "-send", function(data)
|
||||
{
|
||||
this.send(data);
|
||||
}, this);
|
||||
}
|
||||
|
||||
WispNetworkAdapter.prototype.register_ws = function(wisp_url) {
|
||||
this.wispws = new WebSocket(wisp_url.replace("wisp://", "ws://").replace("wisps://", "wss://"));
|
||||
this.wispws.binaryType = "arraybuffer";
|
||||
this.wispws.onmessage = (event) => {
|
||||
this.process_incoming_wisp_frame(new Uint8Array(event.data));
|
||||
};
|
||||
this.wispws.onclose = () => {
|
||||
setTimeout(() => {
|
||||
this.register_ws(wisp_url);
|
||||
}, 10000); // wait 10s before reconnecting
|
||||
};
|
||||
};
|
||||
|
||||
WispNetworkAdapter.prototype.send_packet = function(data, type, stream_id) {
|
||||
if(this.connections[stream_id].congestion > 0) {
|
||||
if(type === "DATA") {
|
||||
this.connections[stream_id].congestion--;
|
||||
}
|
||||
this.wispws.send(data);
|
||||
} else {
|
||||
this.connections[stream_id].congested = true;
|
||||
this.congested_buffer.push({data: data, type: type});
|
||||
}
|
||||
};
|
||||
|
||||
WispNetworkAdapter.prototype.process_incoming_wisp_frame = function(frame) {
|
||||
const view = new DataView(frame.buffer);
|
||||
const stream_id = view.getUint32(1, true);
|
||||
switch(frame[0]) {
|
||||
case 1: // CONNECT
|
||||
// The server should never send this actually
|
||||
dbg_log("Server sent client-only packet CONNECT", LOG_NET);
|
||||
break;
|
||||
case 2: // DATA
|
||||
if(this.connections[stream_id])
|
||||
this.connections[stream_id].data_callback(frame.slice(5));
|
||||
else
|
||||
throw new Error("Got a DATA packet but stream not registered. ID: " + stream_id);
|
||||
break;
|
||||
case 3: // CONTINUE
|
||||
if(this.connections[stream_id]) {
|
||||
this.connections[stream_id].congestion = view.getUint32(5, true);
|
||||
}
|
||||
|
||||
if(this.connections[stream_id].congested) {
|
||||
for(const packet of this.congested_buffer) {
|
||||
this.send_packet(packet.data, packet.type, stream_id);
|
||||
}
|
||||
this.connections[stream_id].congested = false;
|
||||
}
|
||||
break;
|
||||
case 4: // CLOSE
|
||||
if(this.connections[stream_id])
|
||||
this.connections[stream_id].close_callback(view.getUint8(5));
|
||||
delete this.connections[stream_id];
|
||||
break;
|
||||
case 5: // PROTOEXT
|
||||
dbg_log("got a wisp V2 upgrade request, ignoring", LOG_NET);
|
||||
// Not responding, this is wisp v1 client not wisp v2;
|
||||
break;
|
||||
default:
|
||||
dbg_log("Wisp server returned unknown packet: " + frame[0], LOG_NET);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// FrameObj will be the following
|
||||
// FrameObj.stream_id (number)
|
||||
//
|
||||
// FrameObj.type -- CONNECT
|
||||
// FrameObj.hostname (string)
|
||||
// FrameObj.port (number)
|
||||
// FrameObj.data_callback (function (Uint8Array))
|
||||
// FrameObj.close_callback (function (number)) OPTIONAL
|
||||
//
|
||||
//
|
||||
// FrameObj.type -- DATA
|
||||
// FrameObj.data (Uint8Array)
|
||||
//
|
||||
// FrameObj.type -- CLOSE
|
||||
// FrameObj.reason (number)
|
||||
//
|
||||
//
|
||||
|
||||
WispNetworkAdapter.prototype.send_wisp_frame = function(frame_obj) {
|
||||
let full_packet;
|
||||
let view;
|
||||
switch(frame_obj.type) {
|
||||
case "CONNECT":
|
||||
const hostname_buffer = new TextEncoder().encode(frame_obj.hostname);
|
||||
full_packet = new Uint8Array(5 + 1 + 2 + hostname_buffer.length);
|
||||
view = new DataView(full_packet.buffer);
|
||||
view.setUint8(0, 0x01); // TYPE
|
||||
view.setUint32(1, frame_obj.stream_id, true); // Stream ID
|
||||
view.setUint8(5, 0x01); // TCP
|
||||
view.setUint16(6, frame_obj.port, true); // PORT
|
||||
full_packet.set(hostname_buffer, 8); // hostname
|
||||
|
||||
// Setting callbacks
|
||||
this.connections[frame_obj.stream_id] = {
|
||||
data_callback: frame_obj.data_callback,
|
||||
close_callback: frame_obj.close_callback,
|
||||
congestion: this.connections[0].congestion
|
||||
};
|
||||
break;
|
||||
case "DATA":
|
||||
full_packet = new Uint8Array(5 + frame_obj.data.length);
|
||||
view = new DataView(full_packet.buffer);
|
||||
view.setUint8(0, 0x02); // TYPE
|
||||
view.setUint32(1, frame_obj.stream_id, true); // Stream ID
|
||||
full_packet.set(frame_obj.data, 5); // Actual data
|
||||
break;
|
||||
case "CLOSE":
|
||||
full_packet = new Uint8Array(5 + 1);
|
||||
view = new DataView(full_packet.buffer);
|
||||
view.setUint8(0, 0x04); // TYPE
|
||||
view.setUint32(1, frame_obj.stream_id, true); // Stream ID
|
||||
view.setUint8(5, frame_obj.reason); // Packet size
|
||||
break;
|
||||
default:
|
||||
dbg_log("Client tried to send unknown packet: " + frame_obj.type, LOG_NET);
|
||||
|
||||
}
|
||||
this.send_packet(full_packet, frame_obj.type, frame_obj.stream_id);
|
||||
};
|
||||
|
||||
WispNetworkAdapter.prototype.destroy = function()
|
||||
{
|
||||
if(this.wispws) {
|
||||
this.wispws.onmessage = null;
|
||||
this.wispws.onclose = null;
|
||||
this.wispws.close();
|
||||
this.wispws = null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {Uint8Array} data
|
||||
*/
|
||||
WispNetworkAdapter.prototype.send = function(data)
|
||||
{
|
||||
let packet = {};
|
||||
parse_eth(data, packet);
|
||||
|
||||
if(packet.tcp) {
|
||||
let reply = {};
|
||||
reply.eth = { ethertype: ETHERTYPE_IPV4, src: this.router_mac, dest: packet.eth.src };
|
||||
reply.ipv4 = {
|
||||
proto: IPV4_PROTO_TCP,
|
||||
src: packet.ipv4.dest,
|
||||
dest: packet.ipv4.src
|
||||
};
|
||||
|
||||
let tuple = [
|
||||
packet.ipv4.src.join("."),
|
||||
packet.tcp.sport,
|
||||
packet.ipv4.dest.join("."),
|
||||
packet.tcp.dport
|
||||
].join(":");
|
||||
|
||||
if(packet.tcp.syn) {
|
||||
if(this.tcp_conn[tuple]) {
|
||||
dbg_log("SYN to already opened port", LOG_FETCH);
|
||||
}
|
||||
const tcp_conn = new TCPConnection();
|
||||
|
||||
tcp_conn.state = TCP_STATE_SYN_RECEIVED;
|
||||
tcp_conn.net = this;
|
||||
tcp_conn.tuple = tuple;
|
||||
tcp_conn.stream_id = this.last_stream++;
|
||||
this.tcp_conn[tuple] = tcp_conn;
|
||||
|
||||
tcp_conn.on_data = (data) => {
|
||||
if(data.length !== 0) {
|
||||
this.send_wisp_frame({
|
||||
type: "DATA",
|
||||
stream_id: tcp_conn.stream_id,
|
||||
data: data
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
this.send_wisp_frame({
|
||||
type: "CONNECT",
|
||||
stream_id: tcp_conn.stream_id,
|
||||
hostname: packet.ipv4.dest.join("."),
|
||||
port: packet.tcp.dport,
|
||||
data_callback: (data) => {
|
||||
tcp_conn.write(data);
|
||||
},
|
||||
close_callback: (data) => {
|
||||
tcp_conn.close();
|
||||
}
|
||||
});
|
||||
|
||||
tcp_conn.accept(packet);
|
||||
return;
|
||||
}
|
||||
|
||||
if(!this.tcp_conn[tuple]) {
|
||||
dbg_log(`I dont know about ${tuple}, so restting`, LOG_FETCH);
|
||||
let bop = packet.tcp.ackn;
|
||||
if(packet.tcp.fin || packet.tcp.syn) bop += 1;
|
||||
reply.tcp = {
|
||||
sport: packet.tcp.dport,
|
||||
dport: packet.tcp.sport,
|
||||
seq: bop,
|
||||
ackn: packet.tcp.seq + (packet.tcp.syn ? 1: 0),
|
||||
winsize: packet.tcp.winsize,
|
||||
rst: true,
|
||||
ack: packet.tcp.syn
|
||||
};
|
||||
this.receive(make_packet(reply));
|
||||
return;
|
||||
}
|
||||
|
||||
this.tcp_conn[tuple].process(packet);
|
||||
}
|
||||
|
||||
if(packet.arp && packet.arp.oper === 1 && packet.arp.ptype === ETHERTYPE_IPV4) {
|
||||
arp_whohas(packet, this);
|
||||
}
|
||||
|
||||
if(packet.dns) {
|
||||
// TODO: remove when this wisp client supports udp
|
||||
(async () => {
|
||||
let reply = {};
|
||||
reply.eth = { ethertype: ETHERTYPE_IPV4, src: this.router_mac, dest: packet.eth.src };
|
||||
reply.ipv4 = {
|
||||
proto: IPV4_PROTO_UDP,
|
||||
src: this.router_ip,
|
||||
dest: packet.ipv4.src,
|
||||
};
|
||||
reply.udp = { sport: 53, dport: packet.udp.sport };
|
||||
const result = await ((await fetch(`https://${this.doh_server}/dns-query`, {method: "POST", headers: [["content-type", "application/dns-message"]], body: packet.udp.data})).arrayBuffer());
|
||||
reply.udp.data = new Uint8Array(result);
|
||||
this.receive(make_packet(reply));
|
||||
})();
|
||||
}
|
||||
|
||||
if(packet.ntp) {
|
||||
// TODO: remove when this wisp client supports udp
|
||||
handle_fake_ntp(packet, this);
|
||||
return;
|
||||
}
|
||||
|
||||
if(packet.dhcp) {
|
||||
handle_fake_dhcp(packet, this);
|
||||
return;
|
||||
}
|
||||
|
||||
if(packet.udp && packet.udp.dport === 8) {
|
||||
// TODO: remove when this wisp client supports udp
|
||||
handle_udp_echo(packet, this);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {Uint8Array} data
|
||||
*/
|
||||
WispNetworkAdapter.prototype.receive = function(data)
|
||||
{
|
||||
this.bus.send("net" + this.id + "-receive", new Uint8Array(data));
|
||||
};
|
||||
312
tests/devices/wisp_network.js
Executable file
312
tests/devices/wisp_network.js
Executable file
|
|
@ -0,0 +1,312 @@
|
|||
#!/usr/bin/env -S node --experimental-websocket
|
||||
"use strict";
|
||||
|
||||
process.on("unhandledRejection", exn => { throw exn; });
|
||||
|
||||
const TEST_RELEASE_BUILD = +process.env.TEST_RELEASE_BUILD;
|
||||
|
||||
const V86 = require(`../../build/${TEST_RELEASE_BUILD ? "libv86" : "libv86-debug"}.js`).V86;
|
||||
|
||||
const assert = require("assert").strict;
|
||||
const SHOW_LOGS = false;
|
||||
const STOP_ON_FIRST_FAILURE = false;
|
||||
|
||||
function log_pass(msg, ...args)
|
||||
{
|
||||
console.log(`\x1b[92m[+] ${msg}\x1b[0m`, ...args);
|
||||
}
|
||||
|
||||
function log_warn(msg, ...args)
|
||||
{
|
||||
console.error(`\x1b[93m[!] ${msg}\x1b[0m`, ...args);
|
||||
}
|
||||
|
||||
function log_fail(msg, ...args)
|
||||
{
|
||||
console.error(`\x1b[91m[-] ${msg}\x1b[0m`, ...args);
|
||||
}
|
||||
|
||||
const tests =
|
||||
[
|
||||
{
|
||||
name: "DHCP",
|
||||
timeout: 60,
|
||||
start: () =>
|
||||
{
|
||||
emulator.serial0_send("udhcpc\n");
|
||||
emulator.serial0_send("echo -e done\\\\tudhcpc\n");
|
||||
},
|
||||
end_trigger: "done\tudhcpc",
|
||||
end: (capture) =>
|
||||
{
|
||||
assert(/lease of 192.168.86.100 obtained/.test(capture), "lease of 192.168.86.100 obtained");
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ifconfig",
|
||||
timeout: 60,
|
||||
start: () =>
|
||||
{
|
||||
emulator.serial0_send("ifconfig\n");
|
||||
emulator.serial0_send("echo -e done\\\\tifconfig\n");
|
||||
},
|
||||
end_trigger: "done\tifconfig",
|
||||
end: (capture) =>
|
||||
{
|
||||
assert(/192.168.86.100/.test(capture), "192.168.86.100");
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "route",
|
||||
timeout: 60,
|
||||
start: () =>
|
||||
{
|
||||
emulator.serial0_send("ip route\n");
|
||||
emulator.serial0_send("echo -e done\\\\troute\n");
|
||||
},
|
||||
end_trigger: "done\troute",
|
||||
end: (capture) =>
|
||||
{
|
||||
assert(/192.168.86.1/.test(capture), "192.168.86.100");
|
||||
},
|
||||
},
|
||||
//{
|
||||
// name: "arp -a",
|
||||
// timeout: 60,
|
||||
// start: () =>
|
||||
// {
|
||||
// emulator.serial0_send("arp -a\n");
|
||||
// emulator.serial0_send("echo -e done\\\\tarp\n");
|
||||
// },
|
||||
// end_trigger: "done\tarp",
|
||||
// end: (capture) =>
|
||||
// {
|
||||
// assert(/.192.168.86.1. at 52:54:00:01:02:03 \[ether\] {2}on eth0/.test(capture), "(192.168.86.1) at 52:54:00:01:02:03 [ether] on eth0");
|
||||
// },
|
||||
//},
|
||||
{
|
||||
name: "Curl example.org",
|
||||
timeout: 60,
|
||||
allow_failure: true,
|
||||
start: () =>
|
||||
{
|
||||
emulator.serial0_send("wget -T 10 -O - example.org\n");
|
||||
emulator.serial0_send("echo -e done\\\\texample.org\n");
|
||||
},
|
||||
end_trigger: "done\texample.org",
|
||||
end: (capture) =>
|
||||
{
|
||||
assert(/This domain is for use in illustrative examples in documents/.test(capture), "got example.org text");
|
||||
},
|
||||
},
|
||||
|
||||
];
|
||||
|
||||
let test_num = 0;
|
||||
let test_timeout = 0;
|
||||
const failed_tests = [];
|
||||
|
||||
const emulator = new V86({
|
||||
bios: { url: __dirname + "/../../bios/seabios.bin" },
|
||||
vga_bios: { url: __dirname + "/../../bios/vgabios.bin" },
|
||||
cdrom: { url: __dirname + "/../../images/linux4.iso" },
|
||||
autostart: true,
|
||||
memory_size: 64 * 1024 * 1024,
|
||||
disable_jit: +process.env.DISABLE_JIT,
|
||||
network_relay_url: "wisps://wisp.mercurywork.shop/",
|
||||
log_level: SHOW_LOGS ? 0x400000 : 0,
|
||||
});
|
||||
|
||||
emulator.add_listener("emulator-ready", function () {
|
||||
|
||||
});
|
||||
|
||||
let ran_command = false;
|
||||
let line = "";
|
||||
let capturing = false;
|
||||
let capture = "";
|
||||
let next_trigger;
|
||||
let next_trigger_handler;
|
||||
|
||||
function start_timeout()
|
||||
{
|
||||
if(tests[test_num].timeout)
|
||||
{
|
||||
test_timeout = setTimeout(() =>
|
||||
{
|
||||
log_fail("Test #%d (%s) took longer than %s sec. Timing out and terminating.", test_num, tests[test_num].name, tests[test_num].timeout);
|
||||
process.exit(1);
|
||||
}, tests[test_num].timeout * 1000);
|
||||
}
|
||||
}
|
||||
|
||||
function begin()
|
||||
{
|
||||
start_timeout();
|
||||
|
||||
console.log("\nPreparing test #%d: %s", test_num, tests[test_num].name);
|
||||
start_test();
|
||||
}
|
||||
|
||||
function start_test()
|
||||
{
|
||||
console.log("Starting test #%d: %s", test_num, tests[test_num].name);
|
||||
|
||||
capture = "";
|
||||
|
||||
tests[test_num].start();
|
||||
|
||||
if(tests[test_num].capture_trigger)
|
||||
{
|
||||
next_trigger = tests[test_num].capture_trigger;
|
||||
next_trigger_handler = start_capture;
|
||||
}
|
||||
else
|
||||
{
|
||||
next_trigger = tests[test_num].end_trigger;
|
||||
next_trigger_handler = end_test;
|
||||
}
|
||||
start_capture();
|
||||
}
|
||||
|
||||
function start_capture()
|
||||
{
|
||||
console.log("Capturing...");
|
||||
capture = "";
|
||||
capturing = true;
|
||||
|
||||
next_trigger = tests[test_num].end_trigger;
|
||||
next_trigger_handler = end_test;
|
||||
}
|
||||
|
||||
function end_test()
|
||||
{
|
||||
capturing = false;
|
||||
|
||||
if(tests[test_num].timeout)
|
||||
{
|
||||
clearTimeout(test_timeout);
|
||||
}
|
||||
|
||||
let test_has_failed = false;
|
||||
|
||||
try {
|
||||
tests[test_num].end(capture);
|
||||
} catch(e) {
|
||||
console.log(e);
|
||||
test_has_failed = true;
|
||||
}
|
||||
|
||||
if(!test_has_failed)
|
||||
{
|
||||
log_pass("Test #%d passed: %s", test_num, tests[test_num].name);
|
||||
}
|
||||
else
|
||||
{
|
||||
if(tests[test_num].allow_failure)
|
||||
{
|
||||
log_warn("Test #%d failed: %s (failure allowed)", test_num, tests[test_num].name);
|
||||
}
|
||||
else
|
||||
{
|
||||
log_fail("Test #%d failed: %s", test_num, tests[test_num].name);
|
||||
|
||||
if(STOP_ON_FIRST_FAILURE)
|
||||
{
|
||||
finish_tests();
|
||||
}
|
||||
}
|
||||
test_has_failed = false;
|
||||
}
|
||||
|
||||
test_num++;
|
||||
|
||||
if(test_num < tests.length)
|
||||
{
|
||||
begin();
|
||||
}
|
||||
else
|
||||
{
|
||||
finish_tests();
|
||||
}
|
||||
}
|
||||
|
||||
function finish_tests()
|
||||
{
|
||||
emulator.stop();
|
||||
emulator.destroy();
|
||||
|
||||
console.log("\nTests finished.");
|
||||
if(failed_tests.length === 0)
|
||||
{
|
||||
console.log("All tests passed");
|
||||
}
|
||||
else
|
||||
{
|
||||
let unallowed_failure = false;
|
||||
|
||||
console.error("Failed %d out of %d tests:", failed_tests.length, tests.length);
|
||||
for(const num of failed_tests)
|
||||
{
|
||||
if(tests[num].allow_failure)
|
||||
{
|
||||
log_warn("#%d %s (failure allowed)", num, tests[num].name);
|
||||
}
|
||||
else
|
||||
{
|
||||
unallowed_failure = true;
|
||||
log_fail("#%d %s", num, tests[num].name);
|
||||
}
|
||||
}
|
||||
if(unallowed_failure)
|
||||
{
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
emulator.bus.register("emulator-started", function()
|
||||
{
|
||||
console.error("Booting now, please stand by");
|
||||
});
|
||||
|
||||
emulator.add_listener("serial0-output-byte", function(byte)
|
||||
{
|
||||
const chr = String.fromCharCode(byte);
|
||||
if(chr < " " && chr !== "\n" && chr !== "\t" || chr > "~")
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
let new_line = "";
|
||||
let is_new_line = false;
|
||||
if(chr === "\n")
|
||||
{
|
||||
is_new_line = true;
|
||||
new_line = line;
|
||||
line = "";
|
||||
}
|
||||
else
|
||||
{
|
||||
line += chr;
|
||||
}
|
||||
|
||||
if(!ran_command && line.endsWith("~% "))
|
||||
{
|
||||
ran_command = true;
|
||||
begin();
|
||||
}
|
||||
else if(new_line === next_trigger)
|
||||
{
|
||||
next_trigger_handler();
|
||||
}
|
||||
else if(is_new_line && capturing)
|
||||
{
|
||||
capture += new_line + "\n";
|
||||
console.log(" Captured: %s", new_line);
|
||||
}
|
||||
else if(is_new_line)
|
||||
{
|
||||
console.log(" Serial: %s", new_line);
|
||||
}
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue