Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stream comunication between JavaScript and guest OS #530

Open
nwtgck opened this issue Sep 24, 2021 · 8 comments
Open

Stream comunication between JavaScript and guest OS #530

nwtgck opened this issue Sep 24, 2021 · 8 comments

Comments

@nwtgck
Copy link
Contributor

nwtgck commented Sep 24, 2021

Hi, thank you so much for the fantastic project!

I'd like to comunicate or stream data between JavaScript and guest OS. I know emulator.serial0_send() and "serial0-output-line" event, using in the Lua intepreter, but the output might have noises.

For example, when we have device files such as /dev/jsread and /dev/jswrite, we can compress in a guest OS and use in JavaScript by using cat /dev/jsread | gzip | /dev/jswrite. You can also do cat /dev/jsread | lua | /dev/jswrite.

Another my idea is to use named pipes (FIFO). I tried to use them, but unfortunelly they don't use FS.prototype.Read orFS.prototype.Write, so I could not give or obtain data to the named pipe from JavaScript.

Could you tell me how to comunicate between JavaScript and guest OS streamingly?

poor workaround

The script as follows create a large file/root/myjsread and emulator.fs9p.Read is modified to give arbitrary data to guest OS from JavaScript.

    var emulator = new V86Starter({...});

    const rootId = emulator.fs9p.SearchPath("/root").id;
    const filename = "myjsread";
    const parentid = rootId;
    const x = emulator.fs9p.CreateInode();
    x.uid = emulator.fs9p.inodes[parentid].uid;
    x.gid = emulator.fs9p.inodes[parentid].gid;
    x.qid.type = S_IFREG >> 8;
    x.mode = (emulator.fs9p.inodes[parentid].mode & 0x1B6) | S_IFREG;
    emulator.fs9p.PushInode(x, parentid, filename);
    emulator.fs9p.NotifyListeners(emulator.fs9p.inodes.length-1, 'newfile');
    var id = emulator.fs9p.inodes.length-1;
    x.size = Number.MAX_SAFE_INTEGER;

    const originalRead = emulator.fs9p.Read.bind(emulator.fs9p);

    console.log("ID", id);
    emulator.fs9p.Read = async function (nodeid, offset, count) {
        if (id === nodeid) {
            console.log("!! READ HOOK !! reading ", arguments);
            // You can return bytes what you want, but I just streams infinite "A"
            return new Uint8Array(Array(count).fill(65));
        }
        return originalRead(nodeid, offset, count);
    };

An example usage in a guest OS is as follows.

$ cat myjsread
AAAAAAAAAAAA...
AAAAAAAAAAAA...
...

In my experiment, reading the /root/myjsread stopped arround 2^32 bytes.

@nwtgck
Copy link
Contributor Author

nwtgck commented Sep 25, 2021

I finally made it, but it has a problem ! I specified uartN: true bellow, then we can read/write /dev/ttyS1 and/dev/ttyS2 in guest OS.

var emulator = new V86Starter({
        ...
        uart1: true,
        uart2: true,
        uart3: true,
        ...
});

JavaScript → guest OS

//  JavaScript → guest OS
setInterval(() => {
        console.log("send now!");
        emulator.bus.send("serial1-input", 65);
}, 1000);
stty raw < /dev/ttyS1
cat /dev/ttyS1

guest OS → JavaScript

stty raw < /dev/ttyS2
echo hello > /dev/ttyS2
    emulator.add_listener("serial2-output-char", function(char)
    {
        console.log("outchar2", char);
    });

The problem is the line buffer.

@ZXMushroom63
Copy link

I found this very, very useful. Thanks!

@nwtgck
Copy link
Contributor Author

nwtgck commented Sep 30, 2023

I'm glad to hear that!

@farhan-ct
Copy link

This is very helpful, thank you for sharing!

@coderofsalvation
Copy link

this is what I wanted.
I'm so happy somebody pointed me in #1140 to this issue.
Yesterday I gave up hacking on .Read() as I got weird javascript errors about incorrect size/offsets.
Seeing this example makes me realize I was unaware of multiple ttys, as well as using a real inode with a large size ❤

@SuperMaxusa
Copy link
Contributor

SuperMaxusa commented Sep 5, 2024

Small update about the serial port method: the serialX-output-char event is deprecated (4928c91), so you need to use serialX-output-byte + String.fromCharCode/TextDecoder instead if you need to get a character:

    emulator.add_listener("serial2-output-byte", function(byte)
    {
        console.log("outchar2", String.fromCharCode(byte));
    });

Also you can use a virtio-console device (7660cbf), if you are using self-built Linux or Buildroot, you need to enable CONFIG_VIRTIO_CONSOLE in make menuconfig/linux-menuconfig (some Linux distributions have this module by default).

var emulator = new V86({
        ...
        virtio_console: true,
        ...
});

JavaScript → guest OS

//  JavaScript → guest OS
setInterval(() => {
    console.log("send now!");
    emulator.bus.send("virtio-console0-input-bytes", Uint8Array.from([65]));
}, 1000);
stty raw < /dev/hvc0
microcom /dev/hvc0

guest OS → JavaScript

// guest OS → JavaScript
emulator.add_listener("virtio-console1-output-bytes", function(bytes)
{
    console.log("out", new TextDecoder("utf-8").decode(bytes));
});
stty raw < /dev/hvc1
echo hello > /dev/hvc1

@Molnfront
Copy link

Could you have a node.js server in the guest OS listening for inputs from the external Javascript?

@SuperMaxusa
Copy link
Contributor

SuperMaxusa commented Nov 24, 2024

Could you have a node.js server in the guest OS listening for inputs from the external Javascript?

Currently, no, there is some implementation on a custom network stack: #1048 (reply in thread)

UPD: Look at tcpip.js: #1197 and #1048 (comment)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants