First attempt, using C converted to Rust online includes references to libc
Code shared from the Rust Playground · GitHub .
Second attempt, using this code Native Messaging - Rust · GitHub throws
The sender sent an invalid JSON message; message ignored.
The code also does not echo standard input.
My guess is you're sending the length as 8 bytes, not 4.
Try:
let len = msg.len();
let len = if len > 1024 * 1024 {
let msg = format!("Message was too large, length: {}", len);
return Err(io::Error::other(msg));
} else {
len as u32
};
You should also use read_exact
and write_all
for the length, like you are for the message.
2 Likes
That sends the message back. Then the host exists.
It should remain running, inside some kind of loop.
Additionally, instead of sending back a predefined message, I'm trying to read input, and echo back the message, as I do with other hosts GitHub - guest271314/NativeMessagingHosts: Native Messaging hosts .
fn main() -> Result<(), Box<dyn std::error::Error>> {
+ loop {
let msg = read_input()?;
eprintln!("{:?}", std::str::from_utf8(&msg));
write_output("{\"msg\":\"pang\"}")?;
- Ok(())
+ }
}
guest271314:
Additionally, instead of sending back a predefined message, I'm trying to read input, and echo back the message, as I do with other hosts
Oh, just send the received message back? (I thought you were trying to print in a terminal despite, presumably, piping stdin and stdout.)
Sending the received message back:
loop {
let msg = read_input()?;
write_output(&msg)?;
}
pub fn write_output(msg: &[u8]) -> io::Result<()> {
// ...
outstream.write_all(msg)?;
2 Likes
The host exits.
There is no terminal or TTY involved. You can test for yourself following these instructions NativeMessagingHosts/README.md at main · guest271314/NativeMessagingHosts · GitHub .
Create a local folder named native-messaging-rust
.
Include the following files.
manifest.json
{
"name": "nm-rust",
"short_name": "nm_rust",
"version": "1.0",
"manifest_version": 3,
"description": "Rust Native Messaging host",
"permissions": ["nativeMessaging"],
"background": {
"service_worker": "background.js",
"type": "module"
},
"action": {}
}
background.js
globalThis.name = chrome.runtime.getManifest().short_name;
globalThis.port = chrome.runtime.connectNative(globalThis.name);
port.onMessage.addListener((message) => {
console.log(message);
});
port.onDisconnect.addListener((p) => console.log(chrome.runtime.lastError));
port.postMessage(new Array(209715));
chrome.runtime.onInstalled.addListener((reason) => {
console.log(reason);
});
nm_rust.json
{
"name": "nm_rust",
"description": "Rust Native Messaging Host",
"path": "/absolute/path/to/native-messaging-rust/nm_rust.rs",
"type": "stdio",
"allowed_origins": [
"chrome-extension://<ID>"
]
}
You'll get the generated ID in chrome://extensions
when you install the unpacked extension.
nm_rust.rs
#!/usr/bin/env -S /home/user/.cargo/bin/cargo -Zscript
use std::io::{self, Read, Write};
fn main() {
loop {
let msg = read_input()?;
write_output(&msg)?;
}
}
pub fn read_input() -> io::Result<Vec<u8>> {
let mut instream = io::stdin();
let mut length = [0; 4];
instream.read(&mut length)?;
let mut buffer = vec![0; u32::from_ne_bytes(length) as usize];
instream.read_exact(&mut buffer)?;
Ok(buffer)
}
pub fn write_output(msg: &str) -> io::Result<()> {
let mut outstream = io::stdout();
let len = msg.len();
let len = if len > 1024 * 1024 {
let msg = format!("Message was too large, length: {}", len);
return Err(io::Error::other(msg));
} else {
len as u32
};
outstream.write(&len.to_ne_bytes())?;
outstream.write_all(msg.as_bytes())?;
outstream.flush()?;
Ok(())
}
What the C++ version looks like
// C++ Native Messaging host
// https://browserext.github.io/native-messaging/
// https://developer.chrome.com/docs/apps/nativeMessaging/
// https://www.reddit.com/user/Eternal_Weeb/
// guest271314, 2022
#include <iostream>
#include <vector>
using namespace std;
vector<uint8_t> getMessage() {
uint32_t length = 0;
size_t size = fread(&length, sizeof(length), 1, stdin);
vector<uint8_t> message(length);
size = fread(message.data(), sizeof(*message.data()), message.size(), stdin);
return message;
}
void sendMessage(const vector<uint8_t> &message) {
const uint32_t length = message.size();
fwrite(&length, sizeof(length), 1, stdout);
fwrite(message.data(), message.size(), sizeof(*message.data()), stdout);
fflush(stdout);
}
int main() {
while (true) {
sendMessage(getMessage());
}
return 0;
}
If we are going to send that error message back to the client, it need to be formatted, too, e.g., using QuickJS to read standard input to V8's d8
shell in a subprocess, because d8
's readline()
is expecting text to execute we have to work around that and use a different means to read stdin
to Goolge V8's d8
shell NativeMessagingHosts/nm_d8.js at main · guest271314/NativeMessagingHosts · GitHub
} catch (e) {
const json = JSON.stringify({error:e.message});
std.out.write(Uint32Array.of(json.length).buffer, 0, 4);
Now I don't know Rust, though if we do
loop {
let msg = read_input()?;
write_output(&msg)?;
}
don't we have to change the type from msg: &str)
to this type io::Result<Vec<u8>>
?
How to echo standard input?
Unfortunately that is exiting.
@quinedot
The code in Rust Playground works to echo input. Thanks.
Native Messaging doesn't handle standard error.
How to send the fomatted string as JSON to the client here, then just exit?
let len = if len > 1024 * 1024 {
let msg = format!("Message was too large, length: {}", len);
return Err(io::Error::other(msg));
If you want a type-based/structured way to create JSON, with proper escaping and all of that, you'll need some third-party crate.
Otherwise, you can just make the formatted message valid json. The formatting documentation may be useful. In particular, {
and }
must be escaped as {{
and }}
.
Sloppy example:
let msg = format!("{{ \"error\": \"Message was too large, length: {}\" }}", len);
outstream.write_all(&msg.len().to_ne_bytes())?;
outstream.write_all(msg.as_bytes())?;
outstream.flush()?;
return Err(io::Error::other(msg));
Slightly less sloppy example using the serde
and serde_json
crates:
use serde::Serialize;
#[derive(Serialize)]
struct JsonError<'msg> {
error: &'msg str,
}
let msg = format!("Message was too large, length: {}", len);
let err = JsonError { error: &msg };
let json = serde_json::to_string(&err)?;
outstream.write_all(&json.len().to_ne_bytes())?;
outstream.write_all(json.as_bytes())?;
outstream.flush()?;
return Err(io::Error::other(msg));
Minimized serde
/serde_json
example.
1 Like
The first example throws in the client
The sender sent an invalid JSON message; message ignored.
This is the exact code I'm running
pub fn write_output(msg: &[u8]) -> io::Result<()> {
let mut outstream = io::stdout();
let len = msg.len();
let len = if len > 1024 * 1024 {
let msg = format!("{{ \"error\": \"Message was too large, length: {}\" }}", len);
outstream.write_all(&msg.len().to_ne_bytes())?;
outstream.write_all(msg.as_bytes())?;
outstream.flush()?;
return Err(io::Error::other(msg));
} else {
len as u32
};
outstream.write_all(&len.to_ne_bytes())?;
outstream.write_all(msg)?;
outstream.flush()?;
Ok(())
}
Meaning the formatted message is never sent to the client from the host when 1024*1024 is exceeded from client.
Possibly I messed up my ad-hoc JSON or possibly only certain JSON messages are supported.
An example of a basic JSON string will work for now, e.g., "Message length exceeds 1024*1024"
, including double quotes.
Just play around with something that prints to stdout until you get the JSON you want. You don't need me to debug JSON.
1 Like
Alright. Thanks. I'll probably publish the code so far on GitHub with this as the comments
// Rust Native Messaging Host
// https://gist.github.com/TotallyNotChase/c747c55d4a965954f49a7fa5c3f344e0
// https://users.rust-lang.org/t/how-to-implement-a-native-messaging-host-using-only-rust-standard-library/115603/13
// 8-7-2024
1 Like
@quinedot Do you know the source of the following error message?
Error: Error { kind: UnexpectedEof, message: "failed to fill whole buffer" }