Logo
x logo

箱庭飛行場Ex

1-2.Packet、アドレスの実装

前回は、本誌で出てきたPacketとアドレスを、Rustの構造体として定義しました。

  • イーサネットフレーム
  • MACアドレス
  • IPv4アドレス
  • IPv6アドレス
  • ARP Packet

今回は、これらを順番にWebAssembly化するために必要な機能の実装をしていきましょう。 具体的には、データを表現する構造体をただWebAssemblyにしてもつまらないのですので、各構造体の定義だけではなく、Javascript側からPacketを作って、今中身がどうなっているか?が見えるようにするための機構を付け加えたいと思います。

WebAssembly化のためのRust実装

先週、WebAssemblyの開発環境の構築とHello WebAssemblyをやってみましたが、まだ開発環境を構築されていない方は「開発環境の構築と「Hello, WebAssembly!」を参照してください。

まず、今回のプロジェクトの雛形を作ります。(packet-pilotは私たちがつけたプロジェクトの名前です。ご自身の好きな名前に変えてください。)

cargo new --lib packet-pilot

そして、WebAssemblyにするための依存関係をCargo.tomlに追加します。 packet-pilotのディレクトリの中にあります。

[dependencies]
wasm-bindgen = "0.2"
serde = { version = "1.0", features = ["derive"] }
serde-wasm-bindgen = "0.4"
rand = "0.8"

[lib]
crate-type = ["cdylib"]

さて、ソースの構成は、せっかくなのでネットワークの階層に従って作っていってみましょう。今後本誌で紹介されるいろいろなプロトコルをどんどん追加していけるようにしておきましょう。

[packet-pilot]
├── Cargo.toml
├── lib.rs
├── src
|   ├── common 
|   └── layer1 
|   └── layer2 
|   └── layer3 
|   └── layer4 
|   └── layer5 
|   └── layer6 
    └── layer7 

このような構成で、

  • lib.rs WebAssemblyとしてjsとやりとりする部分。
  • common 共通で使うであろう構造体や関数を入れる。
  • layer1 物理層(L1)に関する PhysicalLayerFrame を入れる。
  • layer2 データリンク層(L2)に関するPacket,Addressを入れる。
  • layer3 ネットワーク層(L3)に関するPacket,Addressを入れる。
  • layer4以降は、今後本誌で紹介されてきたら追加していく予定です。
イーサーネットフレーム

Javascriptから確認できるように、今回はそれぞれの構造体に、

  • fmt::Displayを実装してパケットの中身、アドレスの設定値が見えるように
  • new(),from_string(),from_array()といった構造体のインスタンス生成関数

を基本構成として作っていきます。

まずはイーサーネットフレームから、

use serde::{Deserialize, Serialize};
use std::fmt;

use crate::layer2::address::mac_address::MacAddress;

#[derive(Clone, Default, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct EthernetFrame {
    pub dst_mac: MacAddress,  // 宛先MACアドレス (6バイト)
    pub src_mac: MacAddress,  // 送信元MACアドレス (6バイト)
    pub ethertype: u16,       // イーサータイプ (2バイト)
    pub data: Vec<u8>,        // データリンク層のペイロード
}

impl fmt::Display for EthernetFrame {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let hex_bytes: Vec<String> = self
            .data
            .iter()
            .map(|byte| format!("{:02X}", byte))
            .collect();
        let formatted_data = hex_bytes.join(" ");
        write!(
            f,
            "#dst_mac     : {}\n\
             #src_mac     : {}\n\
             #ethertype   : {:04X}\n\
             #data        : [{}]\n",
            self.dst_mac,
            self.src_mac,
            self.ethertype,
            formatted_data,
        )
    }
}

この impl fmt::Display for EthernetFrame は、 println!("frameの中身は、{}", ethernetframe);
format!("frameの中身は、{}",ethernetframe);
といった感じで表現しようとした時に、必要になってきます。Rustの標準の型(プリミティブ)であれば、そのまま表示できますが、 自分で定義した構造体の場合、Rustは、その値をどのように文字列として表示すべきかを知らないのです。 中身をどのような形式で表示したいか、プログラマが明示的に定義する必要があるということです。 今回作る構造体にはfmt::Dispay for xxxを標準で入れていくようにします。

impl EthernetFrame {
    pub fn new(
        dst_mac: Option<MacAddress>,
        src_mac: Option<MacAddress>,
        ethertype: Option<u16>,
        data: Option<Vec<u8>>,
    ) -> Self {
        Self {
            dst_mac: dst_mac.unwrap_or_else(|| MacAddress::get_broadcast_mac_addr()),
            src_mac: src_mac.unwrap_or_else(|| MacAddress::new()),
            ethertype: ethertype.unwrap_or(0x0800), // デフォルトはIPv4
            data: data.unwrap_or_default(),
        }
    }

    /// 指定したデータからフレームを生成する
    pub fn from_raw(
        dst_mac: [u8; 6],
        src_mac: [u8; 6],
        ethertype: u16,
        data: Vec<u8>,
    ) -> Self {
        Self {
            dst_mac: MacAddress(dst_mac),
            src_mac: MacAddress(src_mac),
            ethertype,
            data,
        }
    }
    /// フレーム全体のバイト長を計算する
    pub fn total_length(&self) -> usize {
        14 + self.data.len() // 14バイト(=dst_mac+src_mac+ethertype) + ペイロード長
    }

}

new()で構造体を作り出します。 何も指定されていなくても、デフォルトで構造体の各フィールドの値はセットするように

dst_mac: dst_mac.unwrap_or_else(|| MacAddress::get_broadcast_mac_addr()),
src_mac: src_mac.unwrap_or_else(|| MacAddress::new()),
ethertype: ethertype.unwrap_or(0x0800), // デフォルトはIPv4
data: data.unwrap_or_default(),

というふうにしています。 いろんなunwrapが出てきましたね。これらはそれぞれ、

  • unwrap_or_else()は、中身がNoneの場合、クロージャーを実行します。
  • unrap_or()は、中身がNoneの場合、この値をセットする。
  • unrap_or_default()は、中身がNoneの場合、その型のデフォルト値をセットする

となります。

from_raw()は、全て指定して、バイト配列を渡すことで生成できる関数です。 そして、total_length()で、イーサーネットフレームの全体のバイト長を計算します。 固定長である、dst_mac+src_mac+ethertyep=14bytesですので、可変長のdata部分をlen()で取得して足しています。

こういう風にして、そのデータ型を使ったメソッドを実装していきます。

では、PhysicalLayerFrameも同じように実装してみましょう。

use serde::{Deserialize, Serialize};
use std::fmt;
use crate::layer2::packets::EthernetFrame;


#[derive(Clone, Default, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct PhysicalLayerFrame {
    pub preamble: [u8; 7],             // プリアンブル (7バイト)
    pub sfd: u8,                       // スタートフレームデリミタ (1バイト)
    pub ethernet_frame: EthernetFrame, // データリンク層のイーサネットフレーム
}

impl fmt::Display for PhysicalLayerFrame {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(
            f,
            "#preamble       : {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X}\n\
             #sfd            : {:02X}\n\
             #ethernet_frame : {}\n",
            self.preamble[0],
            self.preamble[1],
            self.preamble[2],
            self.preamble[3],
            self.preamble[4],
            self.preamble[5],
            self.preamble[6],
            self.sfd,
            self.ethernet_frame,
        )
    }
}

packetのDisplayでは、各フィールド値は、#xxxxで表示するようにしています。 ここで、#ethernet_frame : {}\n",と{}だけで書いていますが、ここは先ほどEthernetFrameのところで実装したDisplayの形式で表示されます。 Displayを実装しておくとこういうことができるようになります。

impl PhysicalLayerFrame {
    /// 新しいフレームを生成
    pub fn new(frame: Option<EthernetFrame>) -> Self {
        Self {
            preamble: [0xAA; 7],
            sfd: 0xAB,
            ethernet_frame: frame.unwrap_or_else(EthernetFrame::default),
        }
    }

    /// RAWデータからPhysicalLayerFrameを構築
    pub fn from_raw(
        preamble: [u8; 7],
        sfd: u8,
        ethernet_frame: EthernetFrame,
    ) -> Self {
        Self {
            preamble,
            sfd,
            ethernet_frame,
        }
    }

    /// フレーム全体のバイト長を計算する
    pub fn total_length(&self) -> usize {
        8 + self.ethernet_frame.total_length() // プリアンブル + SFD + イーサネットフレーム長
    }

    /// バイト配列に変換
    pub fn to_bytes(&self) -> Vec<u8> {
        let mut bytes = Vec::new();
        bytes.extend_from_slice(&self.preamble);
        bytes.push(self.sfd);
        bytes.extend_from_slice(&self.ethernet_frame.dst_mac.to_array());
        bytes.extend_from_slice(&self.ethernet_frame.src_mac.to_array());
        bytes.extend_from_slice(&self.ethernet_frame.ethertype.to_be_bytes());
        bytes.extend_from_slice(&self.ethernet_frame.data);
        bytes
    }
}

new()では、preambleとsfdはプロトコル上決まった値がありますので、データリンク層(L2)から渡された イーサーネットフレームをethernet_frameフィールドにセットするだけです。 from_raw()は、preambleやsfdの中身も指定できるようにしています。実際のネットワークに流すときは注意してください。 total_length()は、固定長のpreamble+sfdと、可変長のイーサーネットフレームの全体長を足したものになりますので、 EthernetFrameのtotal_length()を呼んでいます。

この2つは

  • EthernetFrame --> Layer2
  • PhysicalLayerFrame --> Layer1 に入れておきましょう。
[packet-pilot]
├── Cargo.lock
├── Cargo.toml
├── src
│   ├── layer1
│   │   ├── mod.rs
│   │   └── packets
│   │       ├── mod.rs
│   │       └── physical_layer_frame.rs
│   ├── layer2
│   │   ├── mod.rs
│   │   ├── address
│   │   └── packets
│   │       ├── ethernet_frame.rs
│   │       └── mod.rs
│   └── lib.rs

mod.rsの説明がまだでしたね。 どういう風に表現しようかと考えたのですが、mod.rsは、 本の構造に例えると:

  • lib.rs → 本全体の目次
  • layer1/mod.rs → 第1章の冒頭の目次
  • layer1/packets/mod.rs → 1.1節の冒頭の目次

mod.rsの役割は「この章に何が含まれているか」を示すことになります。

layer1/mod.rsは、layer1(ディレクトリ)の中に何が含まれているのか?を示し、

pub(crate) mod packets;

こうなっています。layer1には、packetsというものがあるよ。と表しています。

そして、layer1/packets/mod.rsは、

pub(crate) mod physical_layer_frame;
pub use physical_layer_frame::PhysicalLayerFrame;

となっており、このpacketsの中では、physical_layer_frameが定義されていますよ。 pub use physical_layer_frame::PhysicalLayerFrame;は、packets内で定義している構造体を全て書く必要はなく、 よく使う型を再エクスポートしておくものです。

例えば、上記のPhysicalLayerFrameのソースの最初の方で、

use crate::layer2::packets::EthernetFrame;

としていますが、これは、layer2/packets/mod.rsで、

pub(crate) mod ethernet_frame;
pub use ethernet_frame::EthernetFrame;

という風に、EthernetFrameをこの目次で再エクスポートしているから、こういう書き方ができるのです。 他のモジュールで、EthernetFrameを使うとき

//再エクスポートしているのでこういう書き方ができるのです。
use crate::layer2::packets::EthernetFrame;

//再エクスポートしていないと、そこまでのパスを全て書き冗長になります。
use crate::layer2::packets::ethernet_frame::EthernetFrame;

データリンク層(layer2)のパケットである、EthernetFrameを使う。 というこちらの方が全然わかりやすいですね。

mod.rsで目次を書くことでこのライブラリのコンパイル対象にもなります。 ここに書かれていないと、ファイルがあってもコンパイルされません。

ここで各mod.rsも提示しておきます。 [layer1]

(layer1/mod.rs)
pub(crate) mod packets;

(layer1/packets/mod.rs)
pub(crate) mod physical_layer_frame;
pub use physical_layer_frame::PhysicalLayerFrame;

[layer2]

(layer2/mod.rs)
pub(crate) mod packets;

(layer2/packets/mod.rs)
pub(crate) mod ethernet_frame;
pub use ethernet_frame::EthernetFrame;

そして、大元の本全体の目次である、lib.rsも入れておかないといけないですね。 [lib.rs]

pub(crate) mod layer1;
pub(crate) mod layer2;

これで全ての目次も揃いました。

この段階で、ビルドもできます。

cargo build    // デバッグビルド
cargo build --release // リリースビルド

cargo buildするとtarget/debugの下に色々出来上がります。

ls -l target/debug/deps/libpacket_pilot*

としてみてください。 libpacket_pilot-[ハッシュ値].rmeta がいくつか出来上がっていますね。

「.rmetaファイルとは」:

  • Rustのメタデータファイル
  • コンパイラが使用する中間生成物
  • 高速な増分コンパイルを可能にする

ここで、複数の.rmetaファイルが生成されるのは、テスト用とライブラリ用で別々に生成されていたり、異なるコンパイル設定用(例:デバッグ情報あり/なし)によるものということでした。

そして、cargo build --releaseして、

ls -l target/release/deps/libpacket_pilot*

target/release/deps/libpacket_pilot.dylibというのが出来上がっているのがわかります。 これはなんでしょう?

file target/release/deps/libpacket_pilot.dylib

でファイルの情報を見ると、

target/release/deps/libpacket_pilot.dylib: Mach-O 64-bit dynamically linked shared library arm64

と表示されます。 Mac上でbuildしたので Mach-Oと表示されていますね。64-bitは64bit対応、dynamically linkedは動的リンク用ライブラリ、arm64用。 ということになります。

こういう風にしてRustでビルドされるのですね。 ビルドの流れを本の構造に例えると:

  • ソースコード = 本の原稿
  • ビルド = 本の印刷
  • .dylib = 製本された本
  • ライブラリの利用 = 本を読むこと という感じですかね。

あれ?なんか疑問が出てきたのですが、
なぜ、そもそも、Cargo.tmolで、cdylibとダイナミックリンクの指定してるのでしょう?
ビルドしたら、dynamicall linkedなファイルが出来上がっているのはわかるのですが。なぜ? これは、調査班にまた任せてしまいましょう。

では、つづけます。

アドレス

データリンク層(L2)のアドレス=MACアドレス。 ネットワーク層(L3)のアドレス=IPv4アドレス、IPv6アドレス がありますね。 これらを実装していきましょう。

│   ├── layer2
│   │   ├── mod.rs
│   │   ├── address
│   │   |   ├── mac_address.rs
│   │   |   └── mod.rs
│   │   └── packets
│   │       ├── ethernet_frame.rs
│   │       └── mod.rs

layer2/address/にmac_address.rsを追加します。 addressにmac_address.rsを追加するので、目次であるmod.rsも忘れずに追加しておきましょう。再エクスポートもして、 layer2::address::MacAddress で使えるようにしておきます。 そして、layer2/mod.rsにも目次を追加して、これで、layer2はPacketとaddressが揃いました。

[layer2]

(layer2/mod.rs)
pub(crate) mod packets;
pub(crate) mod address;

(layer2/address/mod.rs)
pub(crate) mod mac_address;

pub use mac_address::MacAddress;

MacAddressの実装も、Packetに合わせて、

  • Displayの実装、
  • 生成用のランダム、文字列から、バイト列から、インスタンスの生成
  • 表示用のto_string() をベースに作っていきます。 さらに、今度はMacAddress独自の機能を考えてみましょう。 ARPする時に必要な?
  • broadcast用のMAC Addressを取得する関数
  • ARP時の宛先MAC Addressを取得する関数 の2つを追加しておきます。 broadcastするときのMACアドレスは、FF:FF:FF:FF:FF:FFと全てがFFでしたね。 ARP時の宛先MACアドレスは、不明なので、00:00:00:00:00:00と全てが00でした。

これらを実装すると、

use rand::Rng;
use serde::{Deserialize, Serialize};
use std::fmt;

#[derive(Clone, Copy, Default, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct MacAddress(pub [u8; 6]);

impl fmt::Display for MacAddress {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(
            f,
            "{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}",
            self.0[0], self.0[1], self.0[2], self.0[3], self.0[4], self.0[5]
        )
    }
}

impl MacAddress {
    /// ランダムにMACアドレスを生成するs
    pub fn new() -> Self {
        let mut rng = rand::thread_rng();
        let mut addr = [0u8; 6];
        rng.fill(&mut addr);
        
        // MACアドレス生成ルールのU/Lビットの扱い
        // ローカル管理アドレス (LAA) を示すためにU/Lビットを1にしておく
        addr[0] |= 0x02;  // ローカル管理アドレス 00000010にしておく
        //addr[0] &= 0xFE; // グローバルユニキャストの場合は、00000000

        MacAddress(addr)
    }
    /// ":"区切りの文字列からMACアドレスを生成する関数
    pub fn from_string(mac_str: &str) -> Result<[u8; 6], &'static str> {
        let bytes: Vec<u8> = mac_str.split(':')
                                    .map(|s| u8::from_str_radix(s, 16))
                                    .collect::<Result<Vec<u8>, _>>()
                                    .map_err(|_| "Invalid MAC address format")?;
    
        if bytes.len() == 6 {
            let mut mac_array = [0u8; 6];
            mac_array.copy_from_slice(&bytes);
            Ok(mac_array)
        } else {
            Err("MAC address must contain exactly 6 bytes")
        }
    }
    /// バイト配列からMACアドレスを生成する関数
    pub fn from_array(bytes: [u8; 6]) -> Self {
        MacAddress(bytes)
    }

    /// MACアドレスをバイトスライスとして取得
    pub fn as_slice(&self) -> &[u8] {
        &self.0
    }
    /// MACアドレスをバイト配列として取得
    pub fn to_array(&self) -> [u8; 6] {
        self.0
    }
    /// MACアドレスを文字列に変換
    pub fn to_string(mac: &[u8; 6]) -> String {
        mac.iter()
           .map(|byte| format!("{:02X}", byte))
           .collect::<Vec<String>>()
           .join(":")
    }

    /// broadcast用のMAC Addressを取得する関数
    pub fn get_broadcast_mac_addr() -> MacAddress {
        // IPv4では全てFFにすることでブロードキャストアドレスになる
        let mac:MacAddress = MacAddress([0xFF;6]);
        mac
    }

    /// ARP時の宛先MAC Addressを取得する関数
    pub fn get_arp_target_mac_addr() -> MacAddress {
        // ARPコマンドを送る時targetのMACアドレスは全て00にする
        let mac:MacAddress = MacAddress([0x00;6]);
        mac
    }
}

このようになりました。今後ここにMACアドレス固有の機能をimplを追加していくことになります。

さて、次は、ネットワーク層(L3)のアドレスであるIPv4アドレスと、IPv6アドレスです。

│   ├── layer3
│   │   ├── mod.rs
│   │   ├── address
│   │   |   ├── ipv4_address.rs
│   │   |   ├── ipv6_address.rs
│   │   |   └── mod.rs

まだネットワーク層(L3)は、addressだけですね。

まずは目次の追加です。 [layer3]

(layer3/mod.rs)
pub(crate) mod address;

(layer3/address/mod.rs)
pub(crate) mod ipv4_address;
pub(crate) mod ipv6_address;

pub use ipv4_address::IPv4Address;
pub use ipv6_address::IPv6Address;

layer3::address::IPv6Addressという風にアクセスできるように再エクスポートもしておきます。

では、IPv4Addressからいきます。

use rand::Rng;
use serde::{Deserialize, Serialize};
use std::fmt;

#[derive(Clone, Copy, Default, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct IPv4Address(pub [u8; 4]);

impl fmt::Display for IPv4Address {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(
            f,
            "{}.{}.{}.{}",
            self.0[0], self.0[1], self.0[2], self.0[3]
        )
    }
}

impl IPv4Address {

    /// 192.168.0.xのIPv4アドレスをランダムに生成
    pub fn new() -> IPv4Address {
        let mut rng = rand::thread_rng();
        let mut addr = [0;4];

        addr = [192, 168, 0, rng.gen_range(1..=254)];
        IPv4Address(addr)
    }
    /// "."区切りの文字列からMACアドレスを生成する関数
    pub fn from_string(s: &str) -> Result<IPv4Address, &'static str> {
        let parts: Vec<&str> = s.split('.').collect();
        if parts.len() != 4 {
            return Err("Invalid IPv4 address format");
        }

        let mut addr = [0u8; 4];
        for i in 0..4 {
            addr[i] = match parts[i].parse::<u8>() {
                Ok(num) => num,
                Err(_) => return Err("Invalid number in IPv4 address"),
            };
        }

        Ok(IPv4Address(addr))
    }
    /// バイト配列からMACアドレスを生成する関数
    pub fn from_array(array: [u8; 4]) -> IPv4Address {
        IPv4Address(array)
    }
    /// IPv4アドレスをバイトスライスとして取得
    pub fn as_slice(&self) -> &[u8] {
        &self.0
    }
    /// IPv4アドレスをバイト配列として取得
    pub fn to_array(&self) -> [u8; 4] {
        self.0
    }
    /// IPv4アドレスを文字列に変換
    pub fn to_string(&self) -> String {
        format!("{}.{}.{}.{}", self.0[0], self.0[1], self.0[2], self.0[3])
    }
}

IPv6Addressは、

use rand::Rng;
use serde::{Deserialize, Serialize};
use std::fmt;

#[derive(Clone, Copy, Default, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct IPv6Address(pub [u8; 16]);

impl fmt::Display for IPv6Address {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(
            f,
            "{:02X}{:02X}:{:02X}{:02X}:{:02X}{:02X}:{:02X}{:02X}:\
             {:02X}{:02X}:{:02X}{:02X}:{:02X}{:02X}:{:02X}{:02X}",
            self.0[0], self.0[1], self.0[2], self.0[3],
            self.0[4], self.0[5], self.0[6], self.0[7],
            self.0[8], self.0[9], self.0[10], self.0[11],
            self.0[12], self.0[13], self.0[14], self.0[15],
        )
    }
}

impl IPv6Address {
    /// "2001:0db8:xx:xx:xx:xx:xx:xx"のIPv6アドレスをランダムに生成
    pub fn new() -> IPv6Address {
        let mut rng = rand::thread_rng();
        let mut address = [0u8; 16];
        address[0] = 0x20;
        address[1] = 0x01;
        address[2] = 0x0d;
        address[3] = 0xb8;
        
        for i in 4..16 {
            address[i] = rng.gen();
        }
        
        IPv6Address(address)
    }

    /// ":"区切りの文字列からIPv6アドレスを生成する関数
    pub fn from_string(s: &str) -> Result<IPv6Address, &'static str> {
        let parts: Vec<&str> = s.split(':').collect();
        if parts.len() != 8 {
            return Err("Invalid IPv6 address format");
        }

        let mut addr = [0u8; 16];
        for (i, part) in parts.iter().enumerate() {
            if part.len() > 4 {
                return Err("Invalid segment in IPv6 address");
            }
            let value = match u16::from_str_radix(part, 16) {
                Ok(num) => num,
                Err(_) => return Err("Invalid number in IPv6 address"),
            };
            addr[i * 2] = (value >> 8) as u8; // 高位バイト
            addr[i * 2 + 1] = (value & 0xFF) as u8; // 低位バイト
        }

        Ok(IPv6Address(addr))
    }

    /// バイト配列からIPv6アドレスを生成する関数
    pub fn from_array(array: [u8; 16]) -> IPv6Address {
        IPv6Address(array)
    }

    /// IPv6アドレスをバイトスライスとして取得
    pub fn as_slice(&self) -> &[u8] {
        &self.0
    }

    /// IPv6アドレスをバイト配列として取得
    pub fn to_array(&self) -> [u8; 16] {
        self.0
    }

    /// IPv6アドレスを文字列に変換
    pub fn to_string(&self) -> String {
        self.to_string_with_separator(':')
    }
    
    /// セパレータを指定してIPv6アドレスを文字列に変換
    pub fn to_string_with_separator(&self, separator: char) -> String {
        format!(
            "{:02X}{:02X}{}{:02X}{:02X}{}{:02X}{:02X}{}{:02X}{:02X}{}\
             {:02X}{:02X}{}{:02X}{:02X}{}{:02X}{:02X}{}{:02X}{:02X}",
            self.0[0], self.0[1], separator,
            self.0[2], self.0[3], separator,
            self.0[4], self.0[5], separator,
            self.0[6], self.0[7], separator,
            self.0[8], self.0[9], separator,
            self.0[10], self.0[11], separator,
            self.0[12], self.0[13], separator,
            self.0[14], self.0[15]
        )
    }

}

さて、buildしますか?いえいえ、慌てないでください。 本全体の目次である、lib.rsに、layer3を追加しないとIPアドレス系がビルドされません。

[lib.rs]

pub(crate) mod layer1;
pub(crate) mod layer2;
pub(crate) mod layer3;

はい。これで、

cargo build

ができるようになります。

これでRustとしての実装は揃いました。これでビルドしたものはクレートとして他のRustプロジェクトやC言語から使えるようになります。 しかし、我々が作ろうとしているのはWebAssemblyですので、これをWebAssemblyにするとなると、それぞれの構造体と関数をJavascriptから使えるように してあげないといけないですね。

次は、Packet,AddressをWebAssemblyとして使えるようにするには?を進めていきます。

その前に、Packet、アドレスのことを再度復習したい方は、本誌Vol.1で復習しておいてくだださい。

ネットワークを流れるPacketをプログラムから操作できるようになりたいという人には必見のマガジンです。