LoginSignup
5
5

More than 5 years have passed since last update.

Rust で Firefox Add-on と Native messaging

Last updated at Posted at 2019-02-11

環境

Firefox 65.0
toolchain: stable-i686-pc-windows-msvc
rustc 1.32.0 (9fda7c223 2019-01-16)

目的

MDN にある Native messaging の Python による ping_pong サンプルを Rust で実装してみます。Windows 環境での例です。

ネイティブアプリ側

MDN の説明にある通り標準入出力を使ってメッセージのやり取りを行います。それぞれのメッセージの先頭 4bytes がメッセージ長になってます。Windows の場合はリトルエンディアンなので下記のようなメッセージ構造になります。

0 1 2 3 4 5 6 7 8 9
6 0 0 0 " p i n g "

まずはメッセージ長を取得する処理です。vec![0u8; 4]で 4bytes 分のバッファを確保して、read_exactで読み込み、copy_nonoverlappingu32に変換という流れです。dst.to_le()は書かなくても良いですが、リトルエンディアンということが分かるように書いてます。

pub fn read_size() -> io::Result<usize> {
    let mut buf = vec![0u8; 4];
    io::stdin().read_exact(buf.as_mut_slice())?;
    let size = unsafe {
        let mut dst: u32 = 0;
        ptr::copy_nonoverlapping(buf.as_ptr(),
                                 &mut dst as *mut u32 as *mut u8, 4);
        dst.to_le() as usize
    };
    Ok(size)
}

メッセージ長が取得できたのでメッセージ本文を読み込みます。vec![0u8; size]でメッセージ長分のバッファを確保して読み込み、String::from_utf8_lossy()で Rust の世界の文字列に変換します。ここでは読み込んだ文字列をコールバック関数に渡しています。

pub fn read_content(callback: Option<CB>) -> io::Result<()> {
    let size = read_size()?;

    let mut buf = vec![0u8; size];
    io::stdin().read_exact(buf.as_mut_slice())?;

    let content = String::from_utf8_lossy(buf.as_slice());
    if callback.is_some() {
        (callback.unwrap())(content.as_ref());
    }
    Ok(())
}

コールバック関数の型定義とメインループです。

type CB = fn(value: &str);

pub fn main_loop(callback: Option<CB>) {
    loop {
        read_content(callback).unwrap();
    }
}

入力側の処理は以上です。

次は出力側の処理を書いていきます。アプリ側からの送信するメッセージも先頭 4bytes がメッセージ長で、それにメッセージ本文が続きます。

まずはメッセージ長を出力します。アプリ側からのメッセージ送信は 1MiB に制限されているので、それを超えていたらエラーを返すようにしています。その次がu32[u8; 4]に変換する処理です。

pub fn write_size(size: usize) -> io::Result<()> {
    if size > 1024 * 1024 {
        return Err(io::Error::from(io::ErrorKind::InvalidData));
    }
    let size: [u8; 4] = unsafe {
        *(&(size as u32).to_le() as *const u32 as *const [u8; 4])
    };
    let _ = io::stdout().write(&size)?;
    Ok(())
}

メッセージ本文を出力する処理です。

pub fn write_content(content: &str) -> io::Result<()> {
    let size = content.len();
    write_size(size)?;

    io::stdout().write(content.as_bytes())?;
    io::stdout().flush()?;
    Ok(())
}

以上を native_messaging module にして、main.rs から呼ぶようにしています。native_messaging.rs と main.rs をまとめてどうぞ。

native_messaging.rs
use std::io::{self, Read, Write};
use std::ptr;

type CB = fn(value: &str);

pub fn main_loop(callback: Option<CB>) {
    loop {
        read_content(callback).unwrap();
    }
}

pub fn read_content(callback: Option<CB>) -> io::Result<()> {
    let size = read_size()?;

    let mut buf = vec![0u8; size];
    io::stdin().read_exact(buf.as_mut_slice())?;

    let content = String::from_utf8_lossy(buf.as_slice());
    if callback.is_some() {
        (callback.unwrap())(content.as_ref());
    }
    Ok(())
}

pub fn read_size() -> io::Result<usize> {
    let mut buf = vec![0u8; 4];
    io::stdin().read_exact(buf.as_mut_slice())?;
    let size = unsafe {
        let mut dst: u32 = 0;
        ptr::copy_nonoverlapping(buf.as_ptr(),
                                 &mut dst as *mut u32 as *mut u8, 4);
        dst.to_le() as usize
    };
    Ok(size)
}

pub fn write_size(size: usize) -> io::Result<()> {
    if size > 1024 * 1024 {
        return Err(io::Error::from(io::ErrorKind::InvalidData));
    }
    let size: [u8; 4] = unsafe {
        *(&(size as u32).to_le() as *const u32 as *const [u8; 4])
    };
    let _ = io::stdout().write(&size)?;
    Ok(())
}

pub fn write_content(content: &str) -> io::Result<()> {
    let size = content.len();
    write_size(size)?;

    io::stdout().write(content.as_bytes())?;
    io::stdout().flush()?;
    Ok(())
}

main.rs はメインループを実行して、メッセージを受け取ったら、コールバック関数で "pong" というメッセージを返すだけです。

main.rs
mod native_messaging;
use native_messaging::{main_loop, write_content};

fn main() {
    main_loop(Some(callback));
}

fn callback(_content: &str) {
    write_content("pong").unwrap();
}

あくまでもサンプルなのでエラー処理はいい加減です。crates.io に chrome_native_messaging という crate があるのでそちらも参考にしてください。Firefox でも動くはず?

manifest.json

以下の内容の manifest ファイルを ping_pong.exe と同じフォルダに保存します。

{
  "name": "ping_pong",
  "description": "Example host for native messaging by Rust",
  "path": "ping_pong.exe",
  "type": "stdio",
  "allowed_extensions": [ "ping_pong@example.org" ]
}

この manifest ファイルへのパスを以下のようにレジストリに書き込みます。C:¥path¥to¥manifest.jsonのところは自分の環境に合わせて適宜書き換えてください。
ping_pong.png

アドオン側

MDNとまったく同じなので(略

5
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
5