Full Code of sile/erl_dist for AI

main 77ceda01d05c cached
20 files
150.1 KB
38.0k tokens
401 symbols
1 requests
Download .txt
Repository: sile/erl_dist
Branch: main
Commit: 77ceda01d05c
Files: 20
Total size: 150.1 KB

Directory structure:
gitextract_hgwcid3u/

├── .github/
│   └── workflows/
│       ├── ci.yml
│       └── release.yml
├── .gitignore
├── Cargo.toml
├── LICENSE
├── README.md
├── examples/
│   ├── epmd_cli.rs
│   ├── handshake.rs
│   ├── recv_msg.rs
│   └── send_msg.rs
└── src/
    ├── channel.rs
    ├── eetf_ext.rs
    ├── epmd.rs
    ├── flags.rs
    ├── handshake.rs
    ├── io.rs
    ├── lib.rs
    ├── message.rs
    ├── node.rs
    └── term.rs

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/workflows/ci.yml
================================================
name: CI

on: [push]

jobs:
  check:
    name: Check
    runs-on: ubuntu-latest
    strategy:
      matrix:
        toolchain: [stable, beta, nightly]
    steps:
      - name: Checkout sources
        uses: actions/checkout@v6
      - run: rustup update ${{ matrix.toolchain }}
      - run: cargo check --all

  test:
    name: Test Suite
    runs-on: ubuntu-latest
    strategy:
      matrix:
        otp: ['26.0', '27.0', '28.0']
        toolchain: [stable]
    steps:
      - name: Checkout sources
        uses: actions/checkout@v6
      - uses: erlef/setup-beam@v1
        with:
          otp-version: ${{matrix.otp}}
      - run: rustup update ${{ matrix.toolchain }}
      - run: cargo test --all

  lints:
    name: Lints
    runs-on: ubuntu-latest
    strategy:
      matrix:
        toolchain: [stable, beta, nightly]
    steps:
      - name: Checkout sources
        uses: actions/checkout@v6
      - run: rustup update ${{ matrix.toolchain }}
      - run: cargo fmt --all -- --check
      - run: cargo clippy --all -- -D warnings



================================================
FILE: .github/workflows/release.yml
================================================
name: Create GitHub Release and Publish to crates.io

on:
  push:
    tags: ['v*']

jobs:
  github-release:
    runs-on: ubuntu-latest
    permissions:
      contents: write
    steps:
      - uses: actions/checkout@v6

      - id: create-release
        run: gh release create ${{ github.ref_name }} --title "${{ github.ref_name }}" --generate-notes
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

  publish:
    runs-on: ubuntu-latest
    # environment: release  # Optional: for enhanced security
    permissions:
      id-token: write
    steps:
    - uses: actions/checkout@v6
    - uses: rust-lang/crates-io-auth-action@v1
      id: auth
    - run: cargo publish
      env:
        CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }}


================================================
FILE: .gitignore
================================================
target
Cargo.lock


================================================
FILE: Cargo.toml
================================================
[package]
name = "erl_dist"
version = "0.8.0"
authors = ["Takeru Ohta <phjgt308@gmail.com>"]
description = "Rust Implementation of Erlang Distribution Protocol"
homepage = "https://github.com/sile/erl_dist"
repository = "https://github.com/sile/erl_dist"
readme = "README.md"
keywords = ["erlang"]
license = "MIT"
edition = "2024"

[dependencies]
eetf = "0.11"
futures = "0.3"
md5 = "0.8"
rand = "0.10"

[dev-dependencies]
noargs = "0.4.1"
nojson = "0.3"
smol = "2"


================================================
FILE: LICENSE
================================================
The MIT License

Copyright (c) 2016 Takeru Ohta <phjgt308@gmail.com>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.


================================================
FILE: README.md
================================================
erl_dist
========

[![erl_dist](https://img.shields.io/crates/v/erl_dist.svg)](https://crates.io/crates/erl_dist)
[![Documentation](https://docs.rs/erl_dist/badge.svg)](https://docs.rs/erl_dist)
[![Actions Status](https://github.com/sile/erl_dist/workflows/CI/badge.svg)](https://github.com/sile/erl_dist/actions)
[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)

Rust Implementation of [Erlang Distribution Protocol](http://erlang.org/doc/apps/erts/erl_dist_protocol.html).

The distribution protocol is used to communicate with distributed erlang nodes.

Examples
---------

Gets a node entry from EPMD:
```rust
use erl_dist::epmd::{DEFAULT_EPMD_PORT, EpmdClient};

// Connect to the local EPMD.
let connection = TcpStream::connect(("localhost", DEFAULT_EPMD_PORT)).await?;
let client = EpmdClient::new(connection);

// Get the information of a node.
let node_name = "foo";
if let Some(node) = client.get_node(node_name).await? {
    println!("Found: {:?}", node);
} else {
    println!("Not found");
}
```

Sends a message to an Erlang node:
```rust
use erl_dist::LOWEST_DISTRIBUTION_PROTOCOL_VERSION;
use erl_dist::node::{Creation, LocalNode};
use erl_dist::handshake::ClientSideHandshake;
use erl_dist::term::{Atom, Pid};
use erl_dist::message::{channel, Message};

// Connect to a peer node.
let peer_host = "localhost";
let peer_port = 7483;  // NOTE: Usually, port number is retrieved from EPMD.
let connection = TcpStream::connect((peer_host, peer_port)).await?;

// Local node information.
let creation = Creation::random();
let local_node = LocalNode::new("foo@localhost".parse()?, creation);

// Do handshake.
let mut handshake = ClientSideHandshake::new(connection, local_node.clone(), "cookie");
let _status = handshake.execute_send_name(LOWEST_DISTRIBUTION_PROTOCOL_VERSION).await?;
let (connection, peer_node) = handshake.execute_rest(true).await?;

// Create a channel.
let capability_flags = local_node.flags & peer_node.flags;
let (mut tx, _) = channel(connection, capability_flags);

// Send a message.
let from_pid = Pid::new(local_node.name.to_string(), 0, 0, local_node.creation.get());
let to_name = Atom::from("bar");
let msg = Message::reg_send(from_pid, to_name, Atom::from("hello").into());
tx.send(msg).await?;
```

Example commands:
- EPMD Client Example: [epmd_cli.rs](examples/epmd_cli.rs)
- Client Node Example: [send_msg.rs](examples/send_msg.rs)
- Server Node Example: [recv_msg.rs](examples/recv_msg.rs)

Related Crates
--------------

- [erl_rpc](https://github.com/sile/erl_rpc): Erlang RPC client for Rust
- [erldash](https://github.com/sile/erldash): Terminal-based Erlang dashboard


================================================
FILE: examples/epmd_cli.rs
================================================
//! EPMD client example.
//!
//! # Usage Examples
//!
//! ```bash
//! $ cargo run --example epmd_cli -- --help
//! $ cargo run --example epmd_cli names
//! $ cargo run --example epmd_cli node_entry foo
//! ```
use erl_dist::epmd::{EpmdClient, NodeEntry};

fn main() -> noargs::Result<()> {
    let mut args = noargs::raw_args();
    args.metadata_mut().app_name = "epmd_cli";
    args.metadata_mut().app_description = "EPMD client utility";
    noargs::HELP_FLAG.take_help(&mut args);

    let epmd_host: String = noargs::opt("host")
        .short('h')
        .default("127.0.0.1")
        .doc("EPMD host")
        .take(&mut args)
        .then(|a| a.value().parse())?;
    let epmd_port: u16 = noargs::opt("port")
        .short('p')
        .default("4369")
        .doc("EPMD port")
        .take(&mut args)
        .then(|a| a.value().parse())?;

    // Handle subcommands
    if noargs::cmd("names")
        .doc("List registered nodes")
        .take(&mut args)
        .is_present()
    {
        if let Some(help) = args.finish()? {
            print!("{help}");
            return Ok(());
        }

        smol::block_on(async {
            let stream =
                smol::net::TcpStream::connect(format!("{}:{}", epmd_host, epmd_port)).await?;
            let client = EpmdClient::new(stream);

            let names = client.get_names().await?;
            let result = nojson::json(|f| {
                f.set_indent_size(2);
                f.set_spacing(true);
                f.array(|f| {
                    for (name, port) in &names {
                        f.element(nojson::json(|f| {
                            f.object(|f| {
                                f.member("name", name.as_str())?;
                                f.member("port", *port)
                            })
                        }))?;
                    }
                    Ok(())
                })
            });
            println!("{result}");
            Ok::<(), Box<dyn std::error::Error>>(())
        })?;
    } else if noargs::cmd("dump")
        .doc("Dump all registered nodes")
        .take(&mut args)
        .is_present()
    {
        if let Some(help) = args.finish()? {
            print!("{help}");
            return Ok(());
        }

        smol::block_on(async {
            let stream =
                smol::net::TcpStream::connect(format!("{}:{}", epmd_host, epmd_port)).await?;
            let client = EpmdClient::new(stream);

            let result = client.dump().await?;
            println!("{}", result);
            Ok::<(), Box<dyn std::error::Error>>(())
        })?;
    } else if noargs::cmd("node_entry")
        .doc("Get node entry information")
        .take(&mut args)
        .is_present()
    {
        let node: String = noargs::arg("<NODE>")
            .doc("Node name to query")
            .take(&mut args)
            .then(|a| a.value().parse())?;

        if let Some(help) = args.finish()? {
            print!("{help}");
            return Ok(());
        }

        smol::block_on(async {
            let stream =
                smol::net::TcpStream::connect(format!("{}:{}", epmd_host, epmd_port)).await?;
            let client = EpmdClient::new(stream);

            let node_info = client.get_node(&node).await?.ok_or("node not found")?;
            let result = nojson::json(|f| {
                f.set_indent_size(2);
                f.set_spacing(true);
                f.object(|f| {
                    f.member("name", node_info.name.as_str())?;
                    f.member("port", node_info.port)?;
                    f.member(
                        "node_type",
                        format!(
                            "{:?} ({})",
                            node_info.node_type,
                            u8::from(node_info.node_type)
                        )
                        .as_str(),
                    )?;
                    f.member(
                        "protocol",
                        format!(
                            "{:?} ({})",
                            node_info.protocol,
                            u8::from(node_info.protocol)
                        )
                        .as_str(),
                    )?;
                    f.member("highest_version", node_info.highest_version)?;
                    f.member("lowest_version", node_info.lowest_version)?;
                    f.member("extra", node_info.extra.as_slice())
                })
            });
            println!("{result}");
            Ok::<(), Box<dyn std::error::Error>>(())
        })?;
    } else if noargs::cmd("kill")
        .doc("Kill EPMD daemon")
        .take(&mut args)
        .is_present()
    {
        if let Some(help) = args.finish()? {
            print!("{help}");
            return Ok(());
        }

        smol::block_on(async {
            let stream =
                smol::net::TcpStream::connect(format!("{}:{}", epmd_host, epmd_port)).await?;
            let client = EpmdClient::new(stream);

            let result = client.kill().await?;
            let result = nojson::json(|f| {
                f.set_indent_size(2);
                f.set_spacing(true);
                f.object(|f| f.member("result", result.as_str()))
            });
            println!("{result}");
            Ok::<(), Box<dyn std::error::Error>>(())
        })?;
    } else if noargs::cmd("register")
        .doc("Register a node with EPMD")
        .take(&mut args)
        .is_present()
    {
        let name: String = noargs::arg("<NAME>")
            .doc("Node name to register")
            .take(&mut args)
            .then(|a| a.value().parse())?;
        let port: u16 = noargs::opt("port")
            .default("3000")
            .doc("Port number")
            .take(&mut args)
            .then(|a| a.value().parse())?;
        let hidden: bool = noargs::flag("hidden")
            .doc("Register as hidden node")
            .take(&mut args)
            .is_present();

        if let Some(help) = args.finish()? {
            print!("{help}");
            return Ok(());
        }

        smol::block_on(async {
            let stream =
                smol::net::TcpStream::connect(format!("{}:{}", epmd_host, epmd_port)).await?;
            let client = EpmdClient::new(stream);

            let node = if hidden {
                NodeEntry::new_hidden(&name, port)
            } else {
                NodeEntry::new(&name, port)
            };
            let (_, creation) = client.register(node).await?;
            let result = nojson::json(|f| {
                f.set_indent_size(2);
                f.set_spacing(true);
                f.object(|f| f.member("creation", creation.get()))
            });
            println!("{result}");
            Ok::<(), Box<dyn std::error::Error>>(())
        })?;
    } else if let Some(help) = args.finish()? {
        print!("{help}");
        return Ok(());
    }

    Ok(())
}


================================================
FILE: examples/handshake.rs
================================================
//! Client side handshake example.
//!
//! # Usage Examples
//!
//! ```bash
//! $ cargo run --example handshake -- --help
//! $ cargo run --example handshake -- --peer foo --self bar@localhost --cookie erlang_cookie
//! ```

fn main() -> noargs::Result<()> {
    let mut args = noargs::raw_args();
    args.metadata_mut().app_name = "handshake";
    args.metadata_mut().app_description = "Client side handshake example";
    noargs::HELP_FLAG.take_help(&mut args);

    let local_node: erl_dist::node::NodeName = noargs::opt("self")
        .default("bar@localhost")
        .doc("Local node name")
        .take(&mut args)
        .then(|a| a.value().parse())?;
    let peer_node: erl_dist::node::NodeName = noargs::opt("peer")
        .default("foo@localhost")
        .doc("Peer node name")
        .take(&mut args)
        .then(|a| a.value().parse())?;
    let cookie: String = noargs::opt("cookie")
        .default("WPKYDIOSJIMJUURLRUHV")
        .doc("Erlang cookie")
        .take(&mut args)
        .then(|a| a.value().parse())?;

    if let Some(help) = args.finish()? {
        print!("{help}");
        return Ok(());
    }

    smol::block_on(async {
        let peer_node_info = {
            let addr = (peer_node.host(), erl_dist::epmd::DEFAULT_EPMD_PORT);
            let stream = smol::net::TcpStream::connect(addr).await?;
            let epmd_client = erl_dist::epmd::EpmdClient::new(stream);
            epmd_client
                .get_node(&peer_node.name())
                .await?
                .ok_or("peer node not found")?
        };
        println!("Got peer node info: {:?}", peer_node_info);

        let dummy_listening_port = 3333;
        let local_node_entry =
            erl_dist::epmd::NodeEntry::new_hidden(local_node.name(), dummy_listening_port);

        let (keepalive_connection, creation) = {
            let addr = (local_node.host(), erl_dist::epmd::DEFAULT_EPMD_PORT);
            let stream = smol::net::TcpStream::connect(addr).await?;
            let epmd_client = erl_dist::epmd::EpmdClient::new(stream);
            epmd_client.register(local_node_entry).await?
        };
        println!("Registered self node: creation={:?}", creation);

        let stream = smol::net::TcpStream::connect((peer_node.host(), peer_node_info.port)).await?;
        let mut handshake = erl_dist::handshake::ClientSideHandshake::new(
            stream,
            erl_dist::node::LocalNode::new(local_node.clone(), creation),
            &cookie,
        );
        let _status = handshake
            .execute_send_name(erl_dist::LOWEST_DISTRIBUTION_PROTOCOL_VERSION)
            .await?;
        let (_, peer_node) = handshake.execute_rest(true).await?;
        println!("Handshake finished: peer={:?}", peer_node);

        std::mem::drop(keepalive_connection);
        Ok::<(), Box<dyn std::error::Error>>(())
    })?;

    Ok(())
}


================================================
FILE: examples/recv_msg.rs
================================================
//! Server Node Example.
//!
//! The node registers specified name to the EPMD and waits messages from other (connected) nodes.
//! If this node receives a message, it will print the message and discard it.
//!
//! # Usage Examples
//!
//! ```bash
//! $ cargo run --example recv_msg -- --help
//! $ cargo run --example recv_msg -- --local bar@localhost --cookie erlang_cookie
//!
//! # On another shell
//! $ erl -sname foo
//! > {bar, bar@localhost} ! hello.
//! ```
use futures::stream::StreamExt;

fn main() -> noargs::Result<()> {
    let mut args = noargs::raw_args();
    args.metadata_mut().app_name = "recv_msg";
    args.metadata_mut().app_description = "Receive messages from Erlang nodes";
    noargs::HELP_FLAG.take_help(&mut args);

    let local_node: erl_dist::node::NodeName = noargs::opt("local")
        .default("bar@localhost")
        .doc("Local node name")
        .take(&mut args)
        .then(|a| a.value().parse())?;
    let cookie: String = noargs::opt("cookie")
        .default("WPKYDIOSJIMJUURLRUHV")
        .doc("Erlang cookie")
        .take(&mut args)
        .then(|a| a.value().parse())?;
    let published: bool = noargs::flag("published")
        .doc("Add PUBLISHED distribution flag to the node (otherwise, the node runs as a hidden node)")
        .take(&mut args)
        .is_present();

    if let Some(help) = args.finish()? {
        print!("{help}");
        return Ok(());
    }

    smol::block_on(async {
        let listener = smol::net::TcpListener::bind("0.0.0.0:0").await?;
        let listening_port = listener.local_addr()?.port();
        println!("Listening port: {}", listening_port);

        let local_node_entry = if published {
            erl_dist::epmd::NodeEntry::new(local_node.name(), listening_port)
        } else {
            erl_dist::epmd::NodeEntry::new_hidden(local_node.name(), listening_port)
        };

        let epmd_addr = (local_node.host(), erl_dist::epmd::DEFAULT_EPMD_PORT);
        let stream = smol::net::TcpStream::connect(epmd_addr).await?;
        let epmd_client = erl_dist::epmd::EpmdClient::new(stream);
        let (keepalive_connection, creation) = epmd_client.register(local_node_entry).await?;
        println!("Registered self node: creation={:?}", creation);

        let mut incoming = listener.incoming();
        while let Some(stream) = incoming.next().await.transpose()? {
            let mut local_node = erl_dist::node::LocalNode::new(local_node.clone(), creation);
            if published {
                local_node.flags |= erl_dist::DistributionFlags::PUBLISHED;
            }
            let cookie = cookie.clone();
            smol::spawn(async move {
                match handle_client(local_node, cookie, stream).await {
                    Ok(()) => {
                        println!("Client disconnected");
                    }
                    Err(e) => {
                        println!("Error: {}", e);
                    }
                };
            })
            .detach();
        }

        std::mem::drop(keepalive_connection);
        Ok::<(), Box<dyn std::error::Error>>(())
    })?;

    Ok(())
}

async fn handle_client(
    local_node: erl_dist::node::LocalNode,
    cookie: String,
    stream: smol::net::TcpStream,
) -> Result<(), Box<dyn std::error::Error>> {
    let mut handshake =
        erl_dist::handshake::ServerSideHandshake::new(stream, local_node.clone(), &cookie);
    let status = if handshake.execute_recv_name().await?.is_some() {
        erl_dist::handshake::HandshakeStatus::Ok
    } else {
        // Dynamic name.
        erl_dist::handshake::HandshakeStatus::Named {
            name: "generated_name".to_owned(),
            creation: erl_dist::node::Creation::random(),
        }
    };
    let (stream, peer_node) = handshake.execute_rest(status).await?;
    println!("Connected: {:?}", peer_node);

    let (mut tx, rx) = erl_dist::message::channel(stream, local_node.flags & peer_node.flags);
    let mut timer = smol::Timer::after(std::time::Duration::from_secs(30));
    let mut msg_future = Box::pin(rx.recv_owned());
    loop {
        let result = futures::future::select(
            msg_future,
            smol::Timer::after(std::time::Duration::from_secs(10)),
        )
        .await;
        match result {
            futures::future::Either::Left((result, _)) => {
                let (msg, rx) = result?;
                println!("Recv: {:?}", msg);
                msg_future = Box::pin(rx.recv_owned());
            }
            futures::future::Either::Right((_, f)) => {
                msg_future = f;
            }
        }

        if smol::future::poll_once(&mut timer).await.is_some() {
            tx.send(erl_dist::message::Message::Tick).await?;
            timer.set_after(std::time::Duration::from_secs(30));
        }
    }
}


================================================
FILE: examples/send_msg.rs
================================================
//! Client Node Example.
//!
//! The node sends a message (atom) to the specified erlang node.
//!
//! # Usage Examples
//!
//! ```bash
//! $ cargo run --example send_msg -- --help
//! $ cargo run --example send_msg -- --peer foo@localhost --destination foo --cookie erlang_cookie -m hello
//! ```

fn main() -> noargs::Result<()> {
    let mut args = noargs::raw_args();
    args.metadata_mut().app_name = "send_msg";
    args.metadata_mut().app_description = "Send a message to an Erlang node";
    noargs::HELP_FLAG.take_help(&mut args);

    let local_node: erl_dist::node::NodeName = noargs::opt("local")
        .default("bar@localhost")
        .doc("Local node name")
        .take(&mut args)
        .then(|a| a.value().parse())?;
    let peer_node: erl_dist::node::NodeName = noargs::opt("peer")
        .default("foo@localhost")
        .doc("Peer node name")
        .take(&mut args)
        .then(|a| a.value().parse())?;
    let cookie: String = noargs::opt("cookie")
        .default("WPKYDIOSJIMJUURLRUHV")
        .doc("Erlang cookie")
        .take(&mut args)
        .then(|a| a.value().parse())?;
    let destination: String = noargs::opt("destination")
        .short('d')
        .default("foo")
        .doc("Destination process name")
        .take(&mut args)
        .then(|a| a.value().parse())?;
    let message: String = noargs::opt("message")
        .short('m')
        .default("hello_world")
        .doc("Message to send")
        .take(&mut args)
        .then(|a| a.value().parse())?;

    if let Some(help) = args.finish()? {
        print!("{help}");
        return Ok(());
    }

    smol::block_on(async {
        let peer_node_info = {
            let addr = (peer_node.host(), erl_dist::epmd::DEFAULT_EPMD_PORT);
            let stream = smol::net::TcpStream::connect(addr).await?;
            let epmd_client = erl_dist::epmd::EpmdClient::new(stream);
            epmd_client
                .get_node(&peer_node.name())
                .await?
                .ok_or("peer node not found")?
        };
        println!("Got peer node info: {:?}", peer_node_info);

        let creation = erl_dist::node::Creation::random();
        let stream = smol::net::TcpStream::connect((peer_node.host(), peer_node_info.port)).await?;
        let local_node = erl_dist::node::LocalNode::new(local_node, creation);
        let mut handshake =
            erl_dist::handshake::ClientSideHandshake::new(stream, local_node.clone(), &cookie);
        let _status = handshake
            .execute_send_name(erl_dist::LOWEST_DISTRIBUTION_PROTOCOL_VERSION)
            .await?;
        let (connection, peer_node) = handshake.execute_rest(true).await?;
        println!("Handshake finished: peer={:?}", peer_node);

        let (mut tx, _) =
            erl_dist::message::channel(connection, local_node.flags & peer_node.flags);
        let pid = eetf::Pid::new(local_node.name.to_string(), 0, 0, creation.get());
        let msg = erl_dist::message::Message::reg_send(
            pid,
            eetf::Atom::from(destination),
            eetf::Atom::from(message).into(),
        );
        tx.send(msg).await?;

        Ok::<(), Box<dyn std::error::Error>>(())
    })?;

    Ok(())
}


================================================
FILE: src/channel.rs
================================================
use crate::DistributionFlags;
#[cfg(doc)]
use crate::handshake;
use crate::io::Connection;
use crate::message::Message;
use futures::io::{AsyncRead, AsyncWrite};

/// Makes a channel to send/received messages to/from a connected node.
///
/// Please ensure that the [`handshake`] has been completed using the `connection` before creating a channel.
///
/// `flags` should be an intersection of distribution flags of both nodes.
/// Note that the current implementation doesn't consider the distribution flags.
///
/// Note that, to keep the connection established, you need to send `Message::Tick` periodically.
/// Please see [the official `net_ticktime` doc](https://www.erlang.org/doc/man/kernel_app.html#net_ticktime) for more details.
pub fn channel<T>(connection: T, flags: DistributionFlags) -> (Sender<T>, Receiver<T>)
where
    T: AsyncRead + AsyncWrite + Unpin + Clone,
{
    let _ = flags;
    (Sender::new(connection.clone()), Receiver::new(connection))
}

const TYPE_TAG: u8 = 112;

/// Sender of a message channel.
#[derive(Debug)]
pub struct Sender<T> {
    connection: Connection<T>,
}

impl<T> Sender<T>
where
    T: AsyncRead + AsyncWrite + Unpin,
{
    fn new(connection: T) -> Self {
        Self {
            connection: Connection::new(connection),
        }
    }

    /// Sends a message.
    pub async fn send(&mut self, message: Message) -> Result<(), SendError> {
        if matches!(message, Message::Tick) {
            self.connection.write_u32(0).await?;
        } else {
            let mut buf = Vec::new();
            message.write_into(&mut buf)?;

            self.connection.write_u32(1 + buf.len() as u32).await?;
            self.connection.write_u8(TYPE_TAG).await?;
            self.connection.write_all(&buf).await?;
            self.connection.flush().await?;
        }
        Ok(())
    }
}

/// Receiver of a message channel.
#[derive(Debug)]
pub struct Receiver<T> {
    connection: Connection<T>,
}

impl<T> Receiver<T>
where
    T: AsyncRead + AsyncWrite + Unpin,
{
    fn new(connection: T) -> Self {
        Self {
            connection: Connection::new(connection),
        }
    }

    /// Receives a message.
    pub async fn recv(&mut self) -> Result<Message, RecvError> {
        let size = match self.connection.read_u32().await {
            Ok(size) => size as usize,
            Err(e) => {
                if e.kind() == std::io::ErrorKind::UnexpectedEof {
                    return Err(RecvError::Closed);
                } else {
                    return Err(e.into());
                }
            }
        };
        if size == 0 {
            return Ok(Message::Tick);
        }

        let tag = self.connection.read_u8().await?;
        if tag != TYPE_TAG {
            return Err(RecvError::UnexpectedTypeTag { tag });
        }

        let mut buf = vec![0; size - 1];
        self.connection.read_exact(&mut buf).await?;
        Message::read_from(&mut buf.as_slice())
    }

    /// Receives a message (owned version).
    pub async fn recv_owned(mut self) -> Result<(Message, Self), RecvError> {
        let msg = self.recv().await?;
        Ok((msg, self))
    }
}

/// Possible errors during sending messages.
#[derive(Debug)]
#[non_exhaustive]
#[allow(missing_docs)]
pub enum SendError {
    /// Encode error.
    Encode(eetf::EncodeError),

    /// I/O error.
    Io(std::io::Error),
}

impl std::fmt::Display for SendError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Encode(error) => write!(f, "{error}"),
            Self::Io(error) => write!(f, "{error}"),
        }
    }
}

impl std::error::Error for SendError {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        match self {
            Self::Encode(error) => Some(error),
            Self::Io(error) => Some(error),
        }
    }
}

impl From<std::io::Error> for SendError {
    fn from(value: std::io::Error) -> Self {
        Self::Io(value)
    }
}

impl From<eetf::EncodeError> for SendError {
    fn from(value: eetf::EncodeError) -> Self {
        Self::Encode(value)
    }
}

/// Possible errors during receiving messages.
#[derive(Debug)]
#[non_exhaustive]
#[allow(missing_docs)]
pub enum RecvError {
    /// Connection was closed by the peer.
    Closed,

    /// Unsupported distributed operation.
    UnsupportedOp { op: i32 },

    /// Unexpected type tag.
    UnexpectedTypeTag { tag: u8 },

    /// Decode error.
    Decode(eetf::DecodeError),

    /// I/O error.
    Io(std::io::Error),
}

impl std::fmt::Display for RecvError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Closed => write!(f, "connection was closed by the peer"),
            Self::UnsupportedOp { op } => write!(f, "unsupported distributed operation {op}"),
            Self::UnexpectedTypeTag { tag } => {
                write!(f, "expected type tag {TYPE_TAG} but got {tag}")
            }
            Self::Decode(error) => write!(f, "{error}"),
            Self::Io(error) => write!(f, "{error}"),
        }
    }
}

impl std::error::Error for RecvError {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        match self {
            Self::Decode(e) => Some(e),
            Self::Io(e) => Some(e),
            _ => None,
        }
    }
}

impl From<std::io::Error> for RecvError {
    fn from(value: std::io::Error) -> Self {
        Self::Io(value)
    }
}

impl From<eetf::DecodeError> for RecvError {
    fn from(value: eetf::DecodeError) -> Self {
        Self::Decode(value)
    }
}


================================================
FILE: src/eetf_ext.rs
================================================
use crate::term::{Mfa, PidOrAtom};
use eetf::{Atom, DecodeError, FixInteger, List, Pid, Reference, Term, Tuple};

pub fn nil() -> Term {
    eetf::List::nil().into()
}

pub fn check_tuple_len(tuple: &Tuple, n: usize) -> Result<(), DecodeError> {
    if tuple.elements.len() != n {
        Err(DecodeError::UnexpectedType {
            value: tuple.clone().into(),
            expected: format!("{} elements tuple", n),
        })
    } else {
        Ok(())
    }
}

pub fn try_from_tagged_tuple3<T0, T1>(mut tuple: Tuple) -> Result<(T0, T1), DecodeError>
where
    T0: TryFromTerm,
    T1: TryFromTerm,
{
    check_tuple_len(&tuple, 3)?;
    Ok((
        T0::try_from_term(std::mem::replace(&mut tuple.elements[1], nil()))?,
        T1::try_from_term(std::mem::replace(&mut tuple.elements[2], nil()))?,
    ))
}

pub fn try_from_tagged_tuple4<T0, T1, T2>(mut tuple: Tuple) -> Result<(T0, T1, T2), DecodeError>
where
    T0: TryFromTerm,
    T1: TryFromTerm,
    T2: TryFromTerm,
{
    check_tuple_len(&tuple, 4)?;
    Ok((
        T0::try_from_term(std::mem::replace(&mut tuple.elements[1], nil()))?,
        T1::try_from_term(std::mem::replace(&mut tuple.elements[2], nil()))?,
        T2::try_from_term(std::mem::replace(&mut tuple.elements[3], nil()))?,
    ))
}

pub fn try_from_tagged_tuple5<T0, T1, T2, T3>(
    mut tuple: Tuple,
) -> Result<(T0, T1, T2, T3), DecodeError>
where
    T0: TryFromTerm,
    T1: TryFromTerm,
    T2: TryFromTerm,
    T3: TryFromTerm,
{
    check_tuple_len(&tuple, 5)?;
    Ok((
        T0::try_from_term(std::mem::replace(&mut tuple.elements[1], nil()))?,
        T1::try_from_term(std::mem::replace(&mut tuple.elements[2], nil()))?,
        T2::try_from_term(std::mem::replace(&mut tuple.elements[3], nil()))?,
        T3::try_from_term(std::mem::replace(&mut tuple.elements[4], nil()))?,
    ))
}

pub fn try_from_tagged_tuple6<T0, T1, T2, T3, T4>(
    mut tuple: Tuple,
) -> Result<(T0, T1, T2, T3, T4), DecodeError>
where
    T0: TryFromTerm,
    T1: TryFromTerm,
    T2: TryFromTerm,
    T3: TryFromTerm,
    T4: TryFromTerm,
{
    check_tuple_len(&tuple, 6)?;
    Ok((
        T0::try_from_term(std::mem::replace(&mut tuple.elements[1], nil()))?,
        T1::try_from_term(std::mem::replace(&mut tuple.elements[2], nil()))?,
        T2::try_from_term(std::mem::replace(&mut tuple.elements[3], nil()))?,
        T3::try_from_term(std::mem::replace(&mut tuple.elements[4], nil()))?,
        T4::try_from_term(std::mem::replace(&mut tuple.elements[5], nil()))?,
    ))
}

pub fn try_from_tagged_tuple7<T0, T1, T2, T3, T4, T5>(
    mut tuple: Tuple,
) -> Result<(T0, T1, T2, T3, T4, T5), DecodeError>
where
    T0: TryFromTerm,
    T1: TryFromTerm,
    T2: TryFromTerm,
    T3: TryFromTerm,
    T4: TryFromTerm,
    T5: TryFromTerm,
{
    check_tuple_len(&tuple, 7)?;
    Ok((
        T0::try_from_term(std::mem::replace(&mut tuple.elements[1], nil()))?,
        T1::try_from_term(std::mem::replace(&mut tuple.elements[2], nil()))?,
        T2::try_from_term(std::mem::replace(&mut tuple.elements[3], nil()))?,
        T3::try_from_term(std::mem::replace(&mut tuple.elements[4], nil()))?,
        T4::try_from_term(std::mem::replace(&mut tuple.elements[5], nil()))?,
        T5::try_from_term(std::mem::replace(&mut tuple.elements[6], nil()))?,
    ))
}

pub trait TryFromTerm: Sized {
    fn try_from_term(term: Term) -> Result<Self, DecodeError>;
}

impl TryFromTerm for Term {
    fn try_from_term(term: Term) -> Result<Self, DecodeError> {
        Ok(term)
    }
}

impl TryFromTerm for Pid {
    fn try_from_term(term: Term) -> Result<Self, DecodeError> {
        try_from_term(term, "pid")
    }
}

impl TryFromTerm for Atom {
    fn try_from_term(term: Term) -> Result<Self, DecodeError> {
        try_from_term(term, "atom")
    }
}

impl TryFromTerm for Reference {
    fn try_from_term(term: Term) -> Result<Self, DecodeError> {
        try_from_term(term, "reference")
    }
}

impl TryFromTerm for FixInteger {
    fn try_from_term(term: Term) -> Result<Self, DecodeError> {
        try_from_term(term, "integer")
    }
}

impl TryFromTerm for List {
    fn try_from_term(term: Term) -> Result<Self, DecodeError> {
        try_from_term(term, "list")
    }
}

impl TryFromTerm for PidOrAtom {
    fn try_from_term(term: Term) -> Result<Self, DecodeError> {
        term.try_into()
            .map(Self::Pid)
            .or_else(|term| term.try_into().map(Self::Atom))
            .map_err(|value| DecodeError::UnexpectedType {
                value,
                expected: "pid or atom".to_owned(),
            })
    }
}

impl TryFromTerm for Mfa {
    fn try_from_term(term: Term) -> Result<Self, DecodeError> {
        let mut tuple = try_from_term(term, "tuple")?;
        check_tuple_len(&tuple, 3)?;
        Ok(Self {
            module: TryFromTerm::try_from_term(std::mem::replace(&mut tuple.elements[0], nil()))?,
            function: TryFromTerm::try_from_term(std::mem::replace(&mut tuple.elements[1], nil()))?,
            arity: TryFromTerm::try_from_term(std::mem::replace(&mut tuple.elements[2], nil()))?,
        })
    }
}

pub fn try_from_term<T>(term: Term, expected: &str) -> Result<T, DecodeError>
where
    Term: TryInto<T, Error = Term>,
{
    term.try_into()
        .map_err(|value| DecodeError::UnexpectedType {
            value,
            expected: expected.to_owned(),
        })
}


================================================
FILE: src/epmd.rs
================================================
//! EPMD client and other EPMD related components.
//!
//! "EPMD" stands for "Erlang Port Mapper Daemon" and
//! it provides name resolution functionalities for distributed erlang nodes.
//!
//! See [EPMD Protocol (Erlang Official Doc)](https://www.erlang.org/doc/apps/erts/erl_dist_protocol.html#epmd-protocol)
//! for more details.
use crate::io::Connection;
use crate::node::Creation;
#[cfg(doc)]
use crate::node::NodeName;
use crate::{HIGHEST_DISTRIBUTION_PROTOCOL_VERSION, LOWEST_DISTRIBUTION_PROTOCOL_VERSION};
use futures::io::{AsyncRead, AsyncWrite};
use std::str::FromStr;

/// Default EPMD listening port.
pub const DEFAULT_EPMD_PORT: u16 = 4369;

const TAG_DUMP_REQ: u8 = 100;
const TAG_KILL_REQ: u8 = 107;
const TAG_NAMES_REQ: u8 = 110;
const TAG_ALIVE2_X_RESP: u8 = 118;
const TAG_PORT2_RESP: u8 = 119;
const TAG_ALIVE2_REQ: u8 = 120;
const TAG_ALIVE2_RESP: u8 = 121;
const TAG_PORT_PLEASE2_REQ: u8 = 122;

/// Entry of a node registered in EPMD.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct NodeEntry {
    /// Node name.
    ///
    /// Note that it differs from [`NodeName`] as this name doesn't contain the host part.
    pub name: String,

    /// Port number on which this node accepts connection requests.
    pub port: u16,

    /// Node type.
    pub node_type: NodeType,

    /// Transport protocol to communicate with this node.
    pub protocol: TransportProtocol,

    /// Highest distribution protocol version that this node can handle.
    pub highest_version: u16,

    /// Lowest distribution protocol version that this node can handle.
    pub lowest_version: u16,

    /// Extra field.
    pub extra: Vec<u8>,
}

impl NodeEntry {
    /// Makes a [`NodeEntry`] instance for a normal node.
    pub fn new(name: &str, port: u16) -> Self {
        Self {
            name: name.to_owned(),
            port,
            node_type: NodeType::Normal,
            protocol: TransportProtocol::TcpIpV4,
            highest_version: HIGHEST_DISTRIBUTION_PROTOCOL_VERSION,
            lowest_version: LOWEST_DISTRIBUTION_PROTOCOL_VERSION,
            extra: Vec::new(),
        }
    }

    /// Makes a [`NodeEntry`] instance for a hidden node.
    pub fn new_hidden(name: &str, port: u16) -> Self {
        Self {
            name: name.to_owned(),
            port,
            node_type: NodeType::Hidden,
            protocol: TransportProtocol::TcpIpV4,
            highest_version: HIGHEST_DISTRIBUTION_PROTOCOL_VERSION,
            lowest_version: LOWEST_DISTRIBUTION_PROTOCOL_VERSION,
            extra: Vec::new(),
        }
    }

    fn bytes_len(&self) -> usize {
        2 + self.name.len() + // name
        2 + // port
        1 + // node_type
        1 + // protocol
        2 + // highest_version
        2 + // lowest_version
        2 + self.extra.len() // extra
    }
}

/// Possible errors.
#[derive(Debug)]
#[non_exhaustive]
#[allow(missing_docs)]
pub enum EpmdError {
    /// Unknown response tag.
    UnknownResponseTag { request: &'static str, tag: u8 },

    /// Too long request.
    TooLongRequest { size: usize },

    /// `PORT_PLEASE2_REQ` request failure.
    GetNodeEntryError { code: u8 },

    /// `ALIVE2_REQ` request failure.
    RegisterNodeError { code: u8 },

    /// Malformed `NAMES_RESP` line.
    MalformedNamesResponse { line: String },

    /// I/O error.
    Io(std::io::Error),
}

impl std::fmt::Display for EpmdError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::UnknownResponseTag { request, tag } => {
                write!(
                    f,
                    "received an unknown tag {tag} as the response of {request}"
                )
            }
            Self::TooLongRequest { size } => {
                write!(
                    f,
                    "request byte size must be less than 0xFFFF, but got {size} bytes"
                )
            }
            Self::GetNodeEntryError { code } => {
                write!(
                    f,
                    "EPMD responded an error code {code} against a PORT_PLEASE2_REQ request"
                )
            }
            Self::RegisterNodeError { code } => {
                write!(
                    f,
                    "EPMD responded an error code {code} against an ALIVE2_REQ request"
                )
            }
            Self::MalformedNamesResponse { line } => {
                write!(
                    f,
                    "found a malformed NAMES_RESP line: expected_format=\"name {{NAME}} at port {{PORT}}\", actual_line={line:?}"
                )
            }
            Self::Io(error) => write!(f, "{error}"),
        }
    }
}

impl std::error::Error for EpmdError {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        if let Self::Io(error) = self {
            Some(error)
        } else {
            None
        }
    }
}

impl From<std::io::Error> for EpmdError {
    fn from(value: std::io::Error) -> Self {
        Self::Io(value)
    }
}

/// EPMD client.
#[derive(Debug)]
pub struct EpmdClient<T> {
    connection: Connection<T>,
}

impl<T> EpmdClient<T>
where
    T: AsyncRead + AsyncWrite + Unpin,
{
    /// Makes a new [`EpmdClient`] instance.
    ///
    /// `connection` is a connection to communicate with the target EPMD server.
    pub fn new(connection: T) -> Self {
        Self {
            connection: Connection::new(connection),
        }
    }

    /// Registers a node in EPMD.
    ///
    /// The connection created to the EPMD must be kept as long as the node is a distributed node.
    /// When the connection is closed, the node is automatically unregistered from the EPMD.
    pub async fn register(mut self, node: NodeEntry) -> Result<(T, Creation), EpmdError> {
        // Request.
        let size = 1 + node.bytes_len();
        let size = u16::try_from(size).map_err(|_| EpmdError::TooLongRequest { size })?;
        self.connection.write_u16(size).await?;
        self.connection.write_u8(TAG_ALIVE2_REQ).await?;
        self.connection.write_u16(node.port).await?;
        self.connection.write_u8(node.node_type.into()).await?;
        self.connection.write_u8(node.protocol.into()).await?;
        self.connection.write_u16(node.highest_version).await?;
        self.connection.write_u16(node.lowest_version).await?;
        self.connection.write_u16(node.name.len() as u16).await?;
        self.connection.write_all(node.name.as_bytes()).await?;
        self.connection.write_u16(node.extra.len() as u16).await?;
        self.connection.write_all(&node.extra).await?;
        self.connection.flush().await?;

        // Response.
        match self.connection.read_u8().await? {
            TAG_ALIVE2_RESP => {
                match self.connection.read_u8().await? {
                    0 => {}
                    code => return Err(EpmdError::RegisterNodeError { code }),
                }

                let creation = Creation::new(u32::from(self.connection.read_u16().await?));
                Ok((self.connection.into_inner(), creation))
            }
            TAG_ALIVE2_X_RESP => {
                match self.connection.read_u8().await? {
                    0 => {}
                    code => return Err(EpmdError::RegisterNodeError { code }),
                }

                let creation = Creation::new(self.connection.read_u32().await?);
                Ok((self.connection.into_inner(), creation))
            }
            tag => Err(EpmdError::UnknownResponseTag {
                request: "ALIVE2_REQ",
                tag,
            }),
        }
    }

    /// Gets all registered nodes (name and port pairs) from EPMD.
    pub async fn get_names(mut self) -> Result<Vec<(String, u16)>, EpmdError> {
        // Request.
        self.connection.write_u16(1).await?; // Length
        self.connection.write_u8(TAG_NAMES_REQ).await?;
        self.connection.flush().await?;

        // Response.
        let _epmd_port = self.connection.read_u32().await?;
        let node_info_text = self.connection.read_string().await?;

        node_info_text
            .split('\n')
            .filter(|s| !s.is_empty())
            .map(|line| NodeNameAndPort::from_str(line).map(|x| (x.name, x.port)))
            .collect()
    }

    /// Queries the node which has the given name to EPMD.
    ///
    /// If the node has not been registered in the connected EPMD, this method will return `None`.
    pub async fn get_node(mut self, node_name: &str) -> Result<Option<NodeEntry>, EpmdError> {
        // Request.
        let size = 1 + node_name.len();
        let size = u16::try_from(size).map_err(|_| EpmdError::TooLongRequest { size })?;
        self.connection.write_u16(size).await?;
        self.connection.write_u8(TAG_PORT_PLEASE2_REQ).await?;
        self.connection.write_all(node_name.as_bytes()).await?;
        self.connection.flush().await?;

        // Response.
        let tag = self.connection.read_u8().await?;
        if tag != TAG_PORT2_RESP {
            return Err(EpmdError::UnknownResponseTag {
                request: "NAMES_REQ",
                tag,
            });
        }

        match self.connection.read_u8().await? {
            0 => {}
            1 => {
                return Ok(None);
            }
            code => {
                return Err(EpmdError::GetNodeEntryError { code });
            }
        }

        Ok(Some(NodeEntry {
            port: self.connection.read_u16().await?,
            node_type: self.connection.read_u8().await?.into(),
            protocol: self.connection.read_u8().await?.into(),
            highest_version: self.connection.read_u16().await?,
            lowest_version: self.connection.read_u16().await?,
            name: self.connection.read_u16_string().await?,
            extra: self.connection.read_u16_bytes().await?,
        }))
    }

    /// Kills EPMD.
    ///
    /// This request kills the running EPMD.
    /// It is almost never used.
    ///
    /// If EPMD is killed, this method returns `"OK"`.
    pub async fn kill(mut self) -> Result<String, EpmdError> {
        // Request.
        self.connection.write_u16(1).await?;
        self.connection.write_u8(TAG_KILL_REQ).await?;
        self.connection.flush().await?;

        // Response.
        let result = self.connection.read_string().await?;
        Ok(result)
    }

    /// Dumps all data from EPMD.
    ///
    /// This request is not really used, it is to be regarded as a debug feature.
    ///
    /// The result value is a string written for each node kept in the connected EPMD.
    ///
    /// The format of each entry is
    ///
    /// ```shell
    /// "active name ${NODE_NAME} at port ${PORT}, fd = ${FD}\n"
    /// ```
    ///
    /// or
    ///
    /// ```shell
    /// "old/unused name ${NODE_NAME} at port ${PORT}, fd = ${FD}\n"
    /// ```
    pub async fn dump(mut self) -> Result<String, EpmdError> {
        // Request.
        self.connection.write_u16(1).await?;
        self.connection.write_u8(TAG_DUMP_REQ).await?;
        self.connection.flush().await?;

        // Response.
        let _epmd_port = self.connection.read_u32().await?;
        let info = self.connection.read_string().await?;
        Ok(info)
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
struct NodeNameAndPort {
    name: String,
    port: u16,
}

impl FromStr for NodeNameAndPort {
    type Err = EpmdError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let error = || EpmdError::MalformedNamesResponse { line: s.to_owned() };

        if !s.starts_with("name ") {
            return Err(error());
        }

        let s = &s["name ".len()..];
        let pos = s.find(" at port ").ok_or_else(error)?;
        let name = s[..pos].to_string();
        let port = s[pos + " at port ".len()..].parse().map_err(|_| error())?;
        Ok(Self { name, port })
    }
}

/// Protocol for communicating with a distributed node.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum TransportProtocol {
    /// TCP/IPv4.
    TcpIpV4,

    /// Other protocol.
    Other(u8),
}

impl From<u8> for TransportProtocol {
    fn from(v: u8) -> Self {
        match v {
            0 => Self::TcpIpV4,
            _ => Self::Other(v),
        }
    }
}

impl From<TransportProtocol> for u8 {
    fn from(v: TransportProtocol) -> Self {
        match v {
            TransportProtocol::TcpIpV4 => 0,
            TransportProtocol::Other(v) => v,
        }
    }
}

/// Type of a distributed node.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum NodeType {
    /// Hidden node (C-node).
    Hidden,

    /// Normal Erlang node.
    Normal,

    /// Other node.
    Other(u8),
}

impl From<u8> for NodeType {
    fn from(v: u8) -> Self {
        match v {
            72 => Self::Hidden,
            77 => Self::Normal,
            _ => Self::Other(v),
        }
    }
}

impl From<NodeType> for u8 {
    fn from(v: NodeType) -> Self {
        match v {
            NodeType::Hidden => 72,
            NodeType::Normal => 77,
            NodeType::Other(v) => v,
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn epmd_client_works() {
        let node_name = "epmd_client_works";
        smol::block_on(async {
            let erl_node = crate::tests::TestErlangNode::new(node_name)
                .await
                .expect("failed to run a test erlang node");

            // Get the information of an existing Erlang node.
            let node = crate::tests::epmd_client()
                .await
                .get_node(node_name)
                .await
                .expect("failed to get node");
            let node = node.expect("no such node");
            assert_eq!(node.name, node_name);

            // Register a new node.
            let client = crate::tests::epmd_client().await;
            let new_node_name = "erl_dist_test_new_node";
            let new_node = NodeEntry::new_hidden(new_node_name, 3000);
            let (stream, _creation) = client
                .register(new_node)
                .await
                .expect("failed to register a new node");

            // Get the information of the newly added Erlang node.
            let node = crate::tests::epmd_client()
                .await
                .get_node(new_node_name)
                .await
                .expect("failed to get node");
            let node = node.expect("no such node");
            assert_eq!(node.name, new_node_name);

            // Deregister the node.
            std::mem::drop(stream);
            std::thread::sleep(std::time::Duration::from_millis(100));

            let node = crate::tests::epmd_client()
                .await
                .get_node(new_node_name)
                .await
                .expect("failed to get node");
            assert!(node.is_none());

            std::mem::drop(erl_node);
        });
    }
}


================================================
FILE: src/flags.rs
================================================
/// Distribution flags.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct DistributionFlags(u64);

impl DistributionFlags {
    /// The node is to be published and part of the global namespace.
    pub const PUBLISHED: Self = Self(0x01);

    /// The node implements an atom cache (obsolete).
    pub const ATOM_CACHE: Self = Self(0x02);

    /// The node implements extended (3 × 32 bits) references (required).
    ///
    /// [NOTE] This flag is mandatory. If not present, the connection is refused.
    pub const EXTENDED_REFERENCES: Self = Self(0x04);

    /// The node implements distributed process monitoring.
    ///
    /// [NOTE] This flag will become mandatory in OTP 25.
    pub const DIST_MONITOR: Self = Self(0x08);

    /// The node uses separate tag for funs (lambdas) in the distribution protocol.
    pub const FUN_TAGS: Self = Self(0x10);

    /// The node implements distributed named process monitoring.
    pub const DIST_MONITOR_NAME: Self = Self(0x20);

    /// The (hidden) node implements atom cache (obsolete).
    pub const HIDDEN_ATOM_CACHE: Self = Self(0x40);

    /// The node understands new fun tags.
    ///
    /// [NOTE] This flag is mandatory. If not present, the connection is refused.
    pub const NEW_FUN_TAGS: Self = Self(0x80);

    /// The node can handle extended pids and ports (required).
    ///
    /// [NOTE] This flag is mandatory. If not present, the connection is refused.
    pub const EXTENDED_PIDS_PORTS: Self = Self(0x100);

    /// This node understands `EXPORT_EXT` tag.
    ///
    /// [NOTE] This flag will become mandatory in OTP 25.
    pub const EXPORT_PTR_TAG: Self = Self(0x200);

    /// The node understands bit binaries.
    ///
    /// [NOTE] This flag will become mandatory in OTP 25.
    pub const BIT_BINARIES: Self = Self(0x400);

    /// The node understandss new float format.
    ///
    /// [NOTE] This flag will become mandatory in OTP 25.
    pub const NEW_FLOATS: Self = Self(0x800);

    /// This node allows unicode characters in I/O operations.
    pub const UNICODE_IO: Self = Self(0x1000);

    /// The node implements atom cache in distribution header.
    ///
    /// Note that currently `erl_dist` can not handle distribution headers.
    pub const DIST_HDR_ATOM_CACHE: Self = Self(0x2000);

    /// The node understands the `SMALL_ATOM_EXT` tag.
    pub const SMALL_ATOM_TAGS: Self = Self(0x4000);

    /// The node understands UTF-8 encoded atoms.
    ///
    /// [NOTE] This flag is mandatory. If not present, the connection is refused.
    pub const UTF8_ATOMS: Self = Self(0x10000);

    /// The node understands maps.
    ///
    /// [NOTE] This flag will become mandatory in OTP 25.
    pub const MAP_TAGS: Self = Self(0x20000);

    /// The node understands big node creation tags `NEW_PID_EXT`, `NEW_PORT_EXT` and `NEWER_REFERENCE_EXT`.
    ///
    /// [NOTE] This flag is mandatory. If not present, the connection is refused.
    pub const BIG_CREATION: Self = Self(0x40000);

    /// Use the `SEND_SENDER` control message instead of the `SEND` control message and use the `SEND_SENDER_TT` control message instead of the `SEND_TT` control message.
    pub const SEND_SENDER: Self = Self(0x80000);

    /// The node understands any term as the seqtrace label.
    pub const BIG_SEQTRACE_LABELS: Self = Self(0x100000);

    /// Use the `PAYLOAD_EXIT`, `PAYLOAD_EXIT_TT`, `PAYLOAD_EXIT2`, `PAYLOAD_EXIT2_TT` and `PAYLOAD_MONITOR_P_EXIT` control messages instead of the non-PAYLOAD variants.
    pub const EXIT_PAYLOAD: Self = Self(0x400000);

    /// Use fragmented distribution messages to send large messages.
    pub const FRAGMENTS: Self = Self(0x800000);

    /// The node supports the new connection setup handshake (version 6) introduced in OTP 23.
    pub const HANDSHAKE_23: Self = Self(0x1000000);

    /// Use the new link protocol.
    ///
    /// Unless both nodes have set the `UNLINK_ID` flag, the old link protocol will be used as a fallback.
    ///
    /// [NOTE] This flag will become mandatory in OTP 25.
    pub const UNLINK_ID: Self = Self(0x2000000);

    /// Set if the `SPAWN_REQUEST`, `SPAWN_REQUEST_TT`, `SPAWN_REPLY`, `SPAWN_REPLY_TT` control messages are supported.
    pub const SPAWN: Self = Self(1 << 32);

    /// Dynamic node name.
    ///
    /// This is not a capability but rather used as a request from the connecting node
    /// to receive its node name from the accepting node as part of the handshake.
    pub const NAME_ME: Self = Self(1 << 33);

    /// The node accepts a larger amount of data in pids, ports and references (node container types version 4).
    ///
    /// In the pid case full 32-bit `ID` and `Serial` fields in `NEW_PID_EXT`,
    /// in the port case a 64-bit integer in `V4_PORT_EXT`, and in the reference case up to 5 32-bit ID words are
    /// now accepted in `NEWER_REFERENCE_EXT`.
    /// Introduced in OTP 24.
    ///
    /// [NOTE] This flag will become mandatory in OTP 26.
    pub const V4_NC: Self = Self(1 << 34);

    /// The node supports process alias and can by this handle the `ALIAS_SEND` and `ALIAS_SEND_TT` control messages.
    ///
    /// Introduced in OTP 24.
    pub const ALIAS: Self = Self(1 << 35);

    /// The node supports all capabilities that are mandatory in OTP 25.
    ///
    /// Introduced in OTP 25.
    /// [NOTE] This flag will become mandatory in OTP 27.
    pub const MANDATORY_25_DIGEST: Self = Self(1 << 36);

    /// Returns the raw bits value.
    pub const fn bits(self) -> u64 {
        self.0
    }

    /// Creates flags from raw bits.
    pub const fn from_bits_truncate(bits: u64) -> Self {
        Self(bits)
    }

    /// Returns `true` if all flags in `other` are contained within `self`.
    pub const fn contains(self, other: Self) -> bool {
        (self.0 & other.0) == other.0
    }

    /// Makes a new [`DistributionFlags`] with the default flags.
    ///
    /// This is equivalent to the following code:
    ///
    /// ```
    /// # use erl_dist::DistributionFlags;
    /// DistributionFlags::mandatory();
    /// ```
    pub fn new() -> Self {
        Self::mandatory()
    }

    /// Gets the mandatory flags (in OTP 26).
    pub fn mandatory() -> Self {
        Self::EXTENDED_REFERENCES
            | Self::FUN_TAGS
            | Self::NEW_FUN_TAGS
            | Self::EXTENDED_PIDS_PORTS
            | Self::EXPORT_PTR_TAG
            | Self::BIT_BINARIES
            | Self::NEW_FLOATS
            | Self::UTF8_ATOMS
            | Self::MAP_TAGS
            | Self::BIG_CREATION
            | Self::HANDSHAKE_23
            | Self::MANDATORY_25_DIGEST
            | Self::UNLINK_ID
            | Self::V4_NC
    }
}

impl Default for DistributionFlags {
    fn default() -> Self {
        Self::new()
    }
}

impl std::ops::BitOr for DistributionFlags {
    type Output = Self;

    fn bitor(self, rhs: Self) -> Self {
        Self(self.0 | rhs.0)
    }
}

impl std::ops::BitOrAssign for DistributionFlags {
    fn bitor_assign(&mut self, rhs: Self) {
        self.0 |= rhs.0;
    }
}

impl std::ops::BitAnd for DistributionFlags {
    type Output = Self;

    fn bitand(self, rhs: Self) -> Self {
        Self(self.0 & rhs.0)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn flag_bit_values() {
        assert_eq!(DistributionFlags::PUBLISHED.bits(), 0x01);
        assert_eq!(DistributionFlags::EXTENDED_REFERENCES.bits(), 0x04);
        assert_eq!(DistributionFlags::UTF8_ATOMS.bits(), 0x10000);
        assert_eq!(DistributionFlags::HANDSHAKE_23.bits(), 0x1000000);
        assert_eq!(DistributionFlags::SPAWN.bits(), 1 << 32);
        assert_eq!(DistributionFlags::NAME_ME.bits(), 1 << 33);
        assert_eq!(DistributionFlags::V4_NC.bits(), 1 << 34);
        assert_eq!(DistributionFlags::ALIAS.bits(), 1 << 35);
        assert_eq!(DistributionFlags::MANDATORY_25_DIGEST.bits(), 1 << 36);
    }

    #[test]
    fn from_bits_truncate_roundtrip() {
        let flags = DistributionFlags::mandatory();
        let bits = flags.bits();
        assert_eq!(DistributionFlags::from_bits_truncate(bits), flags);
    }

    #[test]
    fn from_bits_truncate_preserves_unknown_bits() {
        let raw = 0xFFFF_FFFF_FFFF_FFFF;
        assert_eq!(DistributionFlags::from_bits_truncate(raw).bits(), raw);
    }

    #[test]
    fn contains_single_flag() {
        let flags = DistributionFlags::PUBLISHED | DistributionFlags::UTF8_ATOMS;
        assert!(flags.contains(DistributionFlags::PUBLISHED));
        assert!(flags.contains(DistributionFlags::UTF8_ATOMS));
        assert!(!flags.contains(DistributionFlags::SPAWN));
    }

    #[test]
    fn contains_multiple_flags() {
        let flags = DistributionFlags::mandatory();
        let subset = DistributionFlags::EXTENDED_REFERENCES | DistributionFlags::UTF8_ATOMS;
        assert!(flags.contains(subset));
    }

    #[test]
    fn contains_empty_is_always_true() {
        let empty = DistributionFlags::from_bits_truncate(0);
        assert!(DistributionFlags::PUBLISHED.contains(empty));
        assert!(empty.contains(empty));
    }

    #[test]
    fn bitor_combines_flags() {
        let a = DistributionFlags::PUBLISHED;
        let b = DistributionFlags::UTF8_ATOMS;
        let combined = a | b;
        assert_eq!(combined.bits(), 0x01 | 0x10000);
        assert!(combined.contains(a));
        assert!(combined.contains(b));
    }

    #[test]
    fn bitor_assign_combines_flags() {
        let mut flags = DistributionFlags::PUBLISHED;
        flags |= DistributionFlags::NAME_ME;
        assert!(flags.contains(DistributionFlags::PUBLISHED));
        assert!(flags.contains(DistributionFlags::NAME_ME));
    }

    #[test]
    fn bitand_intersects_flags() {
        let a = DistributionFlags::PUBLISHED | DistributionFlags::UTF8_ATOMS;
        let b = DistributionFlags::UTF8_ATOMS | DistributionFlags::SPAWN;
        let intersection = a & b;
        assert!(intersection.contains(DistributionFlags::UTF8_ATOMS));
        assert!(!intersection.contains(DistributionFlags::PUBLISHED));
        assert!(!intersection.contains(DistributionFlags::SPAWN));
    }

    #[test]
    fn default_equals_mandatory() {
        assert_eq!(DistributionFlags::default(), DistributionFlags::mandatory());
        assert_eq!(DistributionFlags::new(), DistributionFlags::mandatory());
    }

    #[test]
    fn mandatory_contains_expected_flags() {
        let m = DistributionFlags::mandatory();
        assert!(m.contains(DistributionFlags::EXTENDED_REFERENCES));
        assert!(m.contains(DistributionFlags::FUN_TAGS));
        assert!(m.contains(DistributionFlags::NEW_FUN_TAGS));
        assert!(m.contains(DistributionFlags::EXTENDED_PIDS_PORTS));
        assert!(m.contains(DistributionFlags::EXPORT_PTR_TAG));
        assert!(m.contains(DistributionFlags::BIT_BINARIES));
        assert!(m.contains(DistributionFlags::NEW_FLOATS));
        assert!(m.contains(DistributionFlags::UTF8_ATOMS));
        assert!(m.contains(DistributionFlags::MAP_TAGS));
        assert!(m.contains(DistributionFlags::BIG_CREATION));
        assert!(m.contains(DistributionFlags::HANDSHAKE_23));
        assert!(m.contains(DistributionFlags::MANDATORY_25_DIGEST));
        assert!(m.contains(DistributionFlags::UNLINK_ID));
        assert!(m.contains(DistributionFlags::V4_NC));

        // Not in mandatory set.
        assert!(!m.contains(DistributionFlags::PUBLISHED));
        assert!(!m.contains(DistributionFlags::NAME_ME));
        assert!(!m.contains(DistributionFlags::SPAWN));
        assert!(!m.contains(DistributionFlags::ALIAS));
    }

    #[test]
    fn high_and_low_bits_coexist() {
        // Verify that flags spanning both u32 halves work correctly together.
        let flags = DistributionFlags::PUBLISHED | DistributionFlags::SPAWN; // bit 0 + bit 32
        assert_eq!(flags.bits(), 0x01 | (1 << 32));
        assert!(flags.contains(DistributionFlags::PUBLISHED));
        assert!(flags.contains(DistributionFlags::SPAWN));

        // Truncating to u32 should lose high bits.
        let low = flags.bits() as u32;
        assert_eq!(low, 0x01);
    }

    #[test]
    fn clone_and_copy() {
        let a = DistributionFlags::mandatory();
        let b = a;
        let c = a.clone();
        assert_eq!(a, b);
        assert_eq!(a, c);
    }

    #[test]
    fn debug_format() {
        // Just ensure it doesn't panic.
        let _ = format!("{:?}", DistributionFlags::mandatory());
    }

    #[test]
    fn hash_consistent() {
        use std::collections::HashSet;

        let mut set = HashSet::new();
        set.insert(DistributionFlags::PUBLISHED);
        set.insert(DistributionFlags::PUBLISHED);
        assert_eq!(set.len(), 1);

        set.insert(DistributionFlags::UTF8_ATOMS);
        assert_eq!(set.len(), 2);
    }
}


================================================
FILE: src/handshake.rs
================================================
//! Distribution Handshake.
//!
//! This handshake is used by an Erlang node for connecting to another one.
//!
//! See
//! [Distribution Handshake (Erlang Official Doc)](https://www.erlang.org/doc/apps/erts/erl_dist_protocol.html#distribution-handshake)
//! for more details.
use crate::io::Connection;
use crate::node::{Creation, LocalNode, NodeName, NodeNameError, PeerNode};
use crate::{DistributionFlags, LOWEST_DISTRIBUTION_PROTOCOL_VERSION};
use futures::io::{AsyncRead, AsyncWrite};

const PROTOCOL_VERSION: u16 = LOWEST_DISTRIBUTION_PROTOCOL_VERSION;
const NODE_NAME_VERSION: u16 = 5;

/// Client-side handshake.
#[derive(Debug)]
pub struct ClientSideHandshake<T> {
    local_node: LocalNode,
    local_challenge: Challenge,
    cookie: String,
    connection: Connection<T>,
    send_name_status: Option<HandshakeStatus>,
    may_need_complement: bool,
}

impl<T> ClientSideHandshake<T>
where
    T: AsyncRead + AsyncWrite + Unpin,
{
    /// Makes a new [`ClientSideHandshake`] instance.
    pub fn new(connection: T, local_node: LocalNode, cookie: &str) -> Self {
        Self {
            local_node,
            local_challenge: Challenge::new(),
            cookie: cookie.to_owned(),
            connection: Connection::new(connection),
            send_name_status: None,
            may_need_complement: false,
        }
    }

    /// Executes the first part of the handshake protocol.
    ///
    /// To complete the handshake, you then need to call [`ClientSideHandshake::execute_rest()`] method
    /// taking into account the [`HandshakeStatus`] replied from the peer node.
    pub async fn execute_send_name(
        &mut self,
        protocol_version: u16,
    ) -> Result<HandshakeStatus, HandshakeError> {
        self.send_name(protocol_version).await?;
        let status = self.recv_status().await?;
        self.send_name_status = Some(status.clone());
        Ok(status)
    }

    /// Executes the rest part of the handshake protocol.
    ///
    /// You must need to have called [`ClientSideHandshake::execute_send_name()`] before this method.
    /// In a case where [`HandshakeStatus`] of the first part is [`HandshakeStatus::Alive`],
    /// the `do_continue` argument is used to indicate whether this handshake should continue or not
    /// (otherwise the argument is ignored).
    ///
    /// If the [`HandshakeStatus`] returned is a non-ok status, this method call fails immediately.
    pub async fn execute_rest(
        mut self,
        do_continue: bool,
    ) -> Result<(T, PeerNode), HandshakeError> {
        match self.send_name_status {
            None => {
                return Err(HandshakeError::PhaseError {
                    current: "ClientSideHandshake::execute_rest()",
                    depends_on: "ClientSideHandshake::execute_send_name()",
                });
            }
            Some(HandshakeStatus::Nok) => return Err(HandshakeError::OngoingHandshake),
            Some(HandshakeStatus::NotAllowed) => return Err(HandshakeError::NotAllowed),
            Some(HandshakeStatus::Alive) => {
                self.send_status(if do_continue { "true" } else { "false" })
                    .await?;
                if !do_continue {
                    return Err(HandshakeError::AlreadyActive);
                }
            }
            _ => {}
        }

        let (peer_node, peer_challenge) = self.recv_challenge().await?;
        if self.may_need_complement && peer_node.creation.is_some() {
            self.send_complement().await?;
        }
        self.send_challenge_reply(peer_challenge).await?;
        self.recv_challenge_ack().await?;

        let connection = self.connection.into_inner();
        Ok((connection, peer_node))
    }

    async fn send_name(&mut self, protocol_version: u16) -> Result<(), HandshakeError> {
        let mut writer = self.connection.handshake_message_writer();
        match protocol_version {
            PROTOCOL_VERSION => {
                writer.write_u8(b'N')?;
                writer.write_u64(self.local_node.flags.bits())?;
                writer.write_u32(self.local_node.creation.get())?;
                if self.local_node.flags.contains(DistributionFlags::NAME_ME) {
                    writer.write_u16(self.local_node.name.host().len() as u16)?;
                    writer.write_all(self.local_node.name.host().as_bytes())?;
                } else {
                    writer.write_u16(self.local_node.name.len() as u16)?;
                    writer.write_all(self.local_node.name.to_string().as_bytes())?;
                }
            }
            value => {
                return Err(HandshakeError::UnknownProtocolVersion { value });
            }
        }
        writer.finish().await?;
        Ok(())
    }

    async fn recv_status(&mut self) -> Result<HandshakeStatus, HandshakeError> {
        let mut reader = self.connection.handshake_message_reader().await?;
        let tag = reader.read_u8().await?;
        if tag != b's' {
            return Err(HandshakeError::UnexpectedTag {
                message: "STATUS",
                tag,
            });
        }
        let status = reader.read_bytes().await?;
        let status = match status.as_slice() {
            b"ok" => HandshakeStatus::Ok,
            b"ok_simultaneous" => HandshakeStatus::OkSimultaneous,
            b"nok" => HandshakeStatus::Nok,
            b"not_allowed" => HandshakeStatus::NotAllowed,
            b"alive" => HandshakeStatus::Alive,
            _ => {
                if status.starts_with(b"named:") {
                    let bytes = &status["named:".len()..];
                    if bytes.len() < 2 {
                        return Err(HandshakeError::Io(std::io::Error::new(
                            std::io::ErrorKind::UnexpectedEof,
                            "unexpected eof",
                        )));
                    }
                    let n = u16::from_be_bytes([bytes[0], bytes[1]]) as usize;
                    let bytes = &bytes[2..];
                    if bytes.len() < n + 4 {
                        return Err(HandshakeError::Io(std::io::Error::new(
                            std::io::ErrorKind::UnexpectedEof,
                            "unexpected eof",
                        )));
                    }
                    let name = std::str::from_utf8(&bytes[..n])
                        .map_err(|_| {
                            std::io::Error::new(
                                std::io::ErrorKind::InvalidData,
                                "invalid UTF-8 in node name",
                            )
                        })?
                        .to_owned();
                    let bytes = &bytes[n..];
                    let node_name: NodeName = name.parse()?;
                    let name = node_name.name().to_owned();

                    let creation =
                        Creation::new(u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]));
                    HandshakeStatus::Named { name, creation }
                } else {
                    let status = String::from_utf8_lossy(&status).to_string();
                    return Err(HandshakeError::UnknownStatus { status });
                }
            }
        };
        reader.finish().await?;
        Ok(status)
    }

    async fn send_status(&mut self, status: &str) -> Result<(), HandshakeError> {
        let mut writer = self.connection.handshake_message_writer();
        writer.write_u8(b's')?;
        writer.write_all(status.as_bytes())?;
        Ok(())
    }

    async fn recv_challenge(&mut self) -> Result<(PeerNode, Challenge), HandshakeError> {
        let mut reader = self.connection.handshake_message_reader().await?;
        let (node, challenge) = match reader.read_u8().await? {
            b'n' => {
                let version = reader.read_u16().await?;
                if version != NODE_NAME_VERSION {
                    return Err(HandshakeError::InvalidVersionValue { value: version });
                }
                let flags =
                    DistributionFlags::from_bits_truncate(u64::from(reader.read_u32().await?));
                let challenge = Challenge(reader.read_u32().await?);
                let name = reader.read_string().await?.parse()?;
                let node = PeerNode {
                    name,
                    flags,
                    creation: None,
                };
                (node, challenge)
            }
            b'N' => {
                let flags = DistributionFlags::from_bits_truncate(reader.read_u64().await?);
                let challenge = Challenge(reader.read_u32().await?);
                let creation = Creation::new(reader.read_u32().await?);
                let name = reader.read_u16_string().await?.parse()?;
                let node = PeerNode {
                    name,
                    flags,
                    creation: Some(creation),
                };
                (node, challenge)
            }
            tag => {
                return Err(HandshakeError::UnexpectedTag {
                    message: "CHALLENGE",
                    tag,
                });
            }
        };
        reader.finish().await?;
        Ok((node, challenge))
    }

    async fn send_complement(&mut self) -> Result<(), HandshakeError> {
        let mut writer = self.connection.handshake_message_writer();
        writer.write_u8(b'c')?;
        writer.write_u32((self.local_node.flags.bits() >> 32) as u32)?;
        writer.write_u32(self.local_node.creation.get())?;
        writer.finish().await?;
        Ok(())
    }

    async fn send_challenge_reply(
        &mut self,
        peer_challenge: Challenge,
    ) -> Result<(), HandshakeError> {
        let mut writer = self.connection.handshake_message_writer();
        writer.write_u8(b'r')?;
        writer.write_u32(self.local_challenge.0)?;
        writer.write_all(&peer_challenge.digest(&self.cookie).0)?;
        writer.finish().await?;
        Ok(())
    }

    async fn recv_challenge_ack(&mut self) -> Result<(), HandshakeError> {
        let mut reader = self.connection.handshake_message_reader().await?;
        let tag = reader.read_u8().await?;
        if tag != b'a' {
            return Err(HandshakeError::UnexpectedTag {
                message: "CHALLENGE_ACK",
                tag,
            });
        }

        let mut digest = [0; 16];
        reader.read_exact(&mut digest).await?;
        if digest != self.local_challenge.digest(&self.cookie).0 {
            return Err(HandshakeError::CookieMismatch);
        }
        reader.finish().await?;

        Ok(())
    }
}

/// Server-side handshake.
#[derive(Debug)]
pub struct ServerSideHandshake<T> {
    local_node: LocalNode,
    local_challenge: Challenge,
    cookie: String,
    connection: Connection<T>,
    peer_node: Option<PeerNode>,
}

impl<T> ServerSideHandshake<T>
where
    T: AsyncRead + AsyncWrite + Unpin,
{
    /// Makes a new [`ServerSideHandshake`] instance.
    pub fn new(connection: T, local_node: LocalNode, cookie: &str) -> Self {
        Self {
            local_node,
            local_challenge: Challenge::new(),
            cookie: cookie.to_owned(),
            connection: Connection::new(connection),
            peer_node: None,
        }
    }

    /// Executes the first part of the handshake protocol.
    ///
    /// To complete the handshake, you then need to call [`ServerSideHandshake::execute_rest()`] method
    /// taking into account the [`NodeName`] sent from the peer node.
    ///
    /// Note that the return value becomes `None` if the peer requested a dynamic node name.
    /// In the case, if you want to continue the handshake, you need to use [`HandshakeStatus::Named`] for the reply.
    pub async fn execute_recv_name(&mut self) -> Result<Option<NodeName>, HandshakeError> {
        let mut reader = self.connection.handshake_message_reader().await?;
        let tag = reader.read_u8().await?;
        let node = match tag {
            b'n' => {
                let version = reader.read_u16().await?;
                if version != NODE_NAME_VERSION {
                    return Err(HandshakeError::InvalidVersionValue { value: version });
                }
                let flags =
                    DistributionFlags::from_bits_truncate(u64::from(reader.read_u32().await?));
                let name = reader.read_string().await?.parse()?;
                PeerNode {
                    name,
                    flags,
                    creation: None,
                }
            }
            b'N' => {
                let flags = DistributionFlags::from_bits_truncate(reader.read_u64().await?);
                let creation = Creation::new(reader.read_u32().await?);
                let name = if flags.contains(DistributionFlags::NAME_ME) {
                    let host = reader.read_u16_string().await?;
                    NodeName::new("nonode", &host)?
                } else {
                    reader.read_u16_string().await?.parse()?
                };
                PeerNode {
                    name,
                    flags,
                    creation: Some(creation),
                }
            }
            _ => {
                return Err(HandshakeError::UnexpectedTag {
                    message: "NAME",
                    tag,
                });
            }
        };
        reader.finish().await?;

        let name = node.name.clone();
        let is_dynamic = node.flags.contains(DistributionFlags::NAME_ME);
        self.peer_node = Some(node);
        if is_dynamic { Ok(None) } else { Ok(Some(name)) }
    }

    /// Executes the rest part of the handshake protocol.
    ///
    /// You must need to have called [`ServerSideHandshake::execute_recv_name()`] before this method.
    ///
    /// Note that if the [`HandshakeStatus`] is a non-ok status, this method call fails just
    /// after sending the status to the peer node.
    pub async fn execute_rest(
        mut self,
        status: HandshakeStatus,
    ) -> Result<(T, PeerNode), HandshakeError> {
        let (peer_flags, peer_creation) = if let Some(peer) = &self.peer_node {
            (peer.flags, peer.creation)
        } else {
            return Err(HandshakeError::PhaseError {
                current: "ServerSideHandshake::execute_rest()",
                depends_on: "ServerSideHandshake::execute_recv_name()",
            });
        };

        self.send_status(status).await?;

        self.send_challenge(peer_flags).await?;

        if peer_flags.contains(DistributionFlags::HANDSHAKE_23) && peer_creation.is_none() {
            self.recv_complement().await?;
        }

        let peer_challenge = self.recv_challenge_reply().await?;
        self.send_challenge_ack(peer_challenge).await?;

        let peer_node = self.peer_node.take().expect("unreachable");
        let connection = self.connection.into_inner();
        Ok((connection, peer_node))
    }

    async fn send_status(&mut self, status: HandshakeStatus) -> Result<(), HandshakeError> {
        let mut writer = self.connection.handshake_message_writer();
        writer.write_u8(b's')?;
        match &status {
            HandshakeStatus::Ok => writer.write_all(b"ok")?,
            HandshakeStatus::OkSimultaneous => writer.write_all(b"ok_simultaneous")?,
            HandshakeStatus::Nok => writer.write_all(b"nok")?,
            HandshakeStatus::NotAllowed => writer.write_all(b"not_allowed")?,
            HandshakeStatus::Alive => writer.write_all(b"alive")?,
            HandshakeStatus::Named { name, creation } => {
                let peer_node = self.peer_node.as_mut().expect("unreachable");
                let node_name = NodeName::new(name, peer_node.name.host())?;
                writer.write_all(b"named:")?;
                writer.write_u16(node_name.len() as u16)?;
                writer.write_all(node_name.to_string().as_bytes())?;
                writer.write_u32(creation.get())?;

                peer_node.name = node_name;
                peer_node.creation = Some(*creation);
                self.local_node.flags |= DistributionFlags::NAME_ME;
            }
        }
        writer.finish().await?;

        match status {
            HandshakeStatus::Nok => Err(HandshakeError::OngoingHandshake),
            HandshakeStatus::NotAllowed => Err(HandshakeError::NotAllowed),
            _ => Ok(()),
        }
    }

    async fn send_challenge(
        &mut self,
        peer_flags: DistributionFlags,
    ) -> Result<(), HandshakeError> {
        let mut writer = self.connection.handshake_message_writer();
        if peer_flags.contains(DistributionFlags::HANDSHAKE_23) {
            writer.write_u8(b'N')?;
            writer.write_u64(self.local_node.flags.bits())?;
            writer.write_u32(self.local_challenge.0)?;
            writer.write_u32(self.local_node.creation.get())?;
            writer.write_u16(self.local_node.name.len() as u16)?;
            writer.write_all(self.local_node.name.to_string().as_bytes())?;
        } else {
            writer.write_u8(b'n')?;
            writer.write_u16(5)?;
            writer.write_u32(self.local_node.flags.bits() as u32)?;
            writer.write_u32(self.local_challenge.0)?;
            writer.write_all(self.local_node.name.to_string().as_bytes())?;
        }
        writer.finish().await?;
        Ok(())
    }

    async fn recv_complement(&mut self) -> Result<(), HandshakeError> {
        let mut reader = self.connection.handshake_message_reader().await?;
        let tag = reader.read_u8().await?;
        if tag != b'c' {
            return Err(HandshakeError::UnexpectedTag {
                message: "send_complement",
                tag,
            });
        }
        let flags_high =
            DistributionFlags::from_bits_truncate(u64::from(reader.read_u32().await?) << 32);
        let creation = Creation::new(reader.read_u32().await?);
        reader.finish().await?;

        let peer = self.peer_node.as_mut().expect("unreachable");
        peer.flags |= flags_high;
        peer.creation = Some(creation);

        Ok(())
    }

    async fn recv_challenge_reply(&mut self) -> Result<Challenge, HandshakeError> {
        let mut reader = self.connection.handshake_message_reader().await?;
        let tag = reader.read_u8().await?;
        if tag != b'r' {
            return Err(HandshakeError::UnexpectedTag {
                message: "challenge_reply",
                tag,
            });
        }
        let peer_challenge = Challenge(reader.read_u32().await?);
        let mut digest = Digest([0; 16]);
        reader.read_exact(&mut digest.0).await?;
        reader.finish().await?;

        if self.local_challenge.digest(&self.cookie) != digest {
            return Err(HandshakeError::CookieMismatch);
        }

        Ok(peer_challenge)
    }

    async fn send_challenge_ack(
        &mut self,
        peer_challenge: Challenge,
    ) -> Result<(), HandshakeError> {
        let mut writer = self.connection.handshake_message_writer();
        writer.write_u8(b'a')?;
        writer.write_all(&peer_challenge.digest(&self.cookie).0)?;
        writer.finish().await?;
        Ok(())
    }
}

/// Handshake status.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum HandshakeStatus {
    /// The handshake will continue.
    Ok,

    /// The handshake will continue.
    ///
    /// The client-side node is informed that the server-side node has another ongoing connection attempt
    /// that will be shut down (simultaneous connect where the client-side node's name is
    /// greater than the server-side node's name, compared literally).
    OkSimultaneous,

    /// The handshake will not continue.
    ///
    /// The client-side already has an ongoing handshake, which it itself has initiated
    /// (simultaneous connect where the server-side node's name is greater than the client-side node's).
    Nok,

    /// The connection is disallowed for some (unspecified) security reason.
    NotAllowed,

    /// A connection to the node is already active.
    ///
    /// This either means that node the client-side node is confused or
    /// that the TCP connection breakdown of a previous node with this name has not yet reached the server-side node.
    ///
    /// The client-side node then decides whether to continue or not the handshake process.
    Alive,

    /// The handshake willl continue, but the client-side node requested a dynamic node name.
    Named {
        /// Dynamic node name that the server-side node generated.
        ///
        /// Note that this name doesn't contain the host part.
        name: String,

        /// Node creation that the server-side node generated for the client node.
        creation: Creation,
    },
}

#[derive(Debug, Clone, Copy)]
struct Challenge(u32);

impl Challenge {
    fn new() -> Self {
        Self(rand::random())
    }

    fn digest(self, cookie: &str) -> Digest {
        Digest(md5::compute(format!("{}{}", cookie, self.0)).0)
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
struct Digest([u8; 16]);

/// Possible errors during handshake.
#[derive(Debug)]
#[non_exhaustive]
#[allow(missing_docs)]
pub enum HandshakeError {
    /// Peer already has an ongoing handshake with this node.
    OngoingHandshake,

    /// Connection is disallowed for some (unspecified) security reason.
    NotAllowed,

    /// Connection to the node is already active.
    AlreadyActive,

    /// Unknown distribution protocol version.
    UnknownProtocolVersion { value: u16 },

    /// Unknown status.
    UnknownStatus { status: String },

    /// Unexpected tag.
    UnexpectedTag { message: &'static str, tag: u8 },

    /// Cookie mismatch.
    CookieMismatch,

    /// Invalid version.
    InvalidVersionValue { value: u16 },

    /// Unexpected handshake phase.
    PhaseError {
        current: &'static str,
        depends_on: &'static str,
    },

    /// Node name error.
    NodeNameError(NodeNameError),

    /// I/O error.
    Io(std::io::Error),
}

impl std::fmt::Display for HandshakeError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::OngoingHandshake => {
                write!(f, "peer already has an ongoing handshake with this node")
            }
            Self::NotAllowed => write!(
                f,
                "the connection is disallowed for some (unspecified) security reason"
            ),
            Self::AlreadyActive => write!(f, "a connection to the node is already active"),
            Self::UnknownProtocolVersion { value } => {
                write!(f, "unknown distribution protocol version {value:?}")
            }
            Self::UnknownStatus { status } => {
                write!(f, "received an unknown status {status:?}")
            }
            Self::UnexpectedTag { message, tag } => {
                write!(f, "received an unexpected tag {tag} for {message:?}")
            }
            Self::CookieMismatch => write!(f, "cookie mismatch"),
            Self::InvalidVersionValue { value } => {
                write!(
                    f,
                    "the 'version' value of an old 'send_name' message must be {NODE_NAME_VERSION}, but got {value}"
                )
            }
            Self::PhaseError {
                current,
                depends_on,
            } => {
                write!(
                    f,
                    "{current:?} was unexpectedly executed before {depends_on:?}"
                )
            }
            Self::NodeNameError(error) => write!(f, "{error}"),
            Self::Io(error) => write!(f, "{error}"),
        }
    }
}

impl std::error::Error for HandshakeError {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        match self {
            Self::NodeNameError(error) => Some(error),
            Self::Io(error) => Some(error),
            _ => None,
        }
    }
}

impl From<std::io::Error> for HandshakeError {
    fn from(value: std::io::Error) -> Self {
        Self::Io(value)
    }
}

impl From<NodeNameError> for HandshakeError {
    fn from(value: NodeNameError) -> Self {
        Self::NodeNameError(value)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use futures::StreamExt;

    #[test]
    fn client_side_handshake_works() {
        let peer_name = "client_side_handshake_works";
        smol::block_on(async {
            let erl_node = crate::tests::TestErlangNode::new(peer_name)
                .await
                .expect("failed to run a test erlang node");

            let peer_entry = crate::tests::epmd_client()
                .await
                .get_node(peer_name)
                .await
                .expect("failed to get node");
            let peer_entry = peer_entry.expect("no such node");

            let connection = smol::net::TcpStream::connect(("localhost", peer_entry.port))
                .await
                .expect("failed to connect");
            let local_node = LocalNode::new("foo@localhost".parse().unwrap(), Creation::random());
            let mut handshake =
                ClientSideHandshake::new(connection, local_node, crate::tests::COOKIE);
            let status = handshake
                .execute_send_name(crate::LOWEST_DISTRIBUTION_PROTOCOL_VERSION)
                .await
                .expect("failed to execute send name");
            assert_eq!(status, HandshakeStatus::Ok);
            let (_, peer_node) = handshake
                .execute_rest(true)
                .await
                .expect("failed to execute handshake");
            assert_eq!(peer_entry.name, peer_node.name.name());

            std::mem::drop(erl_node);
        });
    }

    #[test]
    fn server_side_handshake_works() {
        smol::block_on(async {
            let listener = smol::net::TcpListener::bind("0.0.0.0:0").await.unwrap();
            let listening_port = listener.local_addr().unwrap().port();
            let (tx, rx) = futures::channel::oneshot::channel();
            let connection = smol::net::TcpStream::connect(("localhost", listening_port))
                .await
                .unwrap();

            smol::spawn(async move {
                let local_node =
                    LocalNode::new("foo@localhost".parse().unwrap(), Creation::random());
                let mut handshake =
                    ClientSideHandshake::new(connection, local_node, crate::tests::COOKIE);
                let _status = handshake
                    .execute_send_name(crate::LOWEST_DISTRIBUTION_PROTOCOL_VERSION)
                    .await
                    .unwrap();
                let (connection, _) = handshake.execute_rest(true).await.unwrap();
                let _ = tx.send(connection);
            })
            .detach();

            let mut incoming = listener.incoming();
            if let Some(connection) = incoming.next().await {
                let local_node =
                    LocalNode::new("bar@localhost".parse().unwrap(), Creation::random());
                let mut handshake =
                    ServerSideHandshake::new(connection.unwrap(), local_node, crate::tests::COOKIE);
                let peer_name = handshake.execute_recv_name().await.unwrap();
                assert!(peer_name.is_some());

                handshake.execute_rest(HandshakeStatus::Ok).await.unwrap();
            }
            let _ = rx.await;
        })
    }
}


================================================
FILE: src/io.rs
================================================
use eetf::{DecodeError, EncodeError, FixInteger, Term, Tuple};
use futures::io::{AsyncRead, AsyncReadExt as _, AsyncWrite, AsyncWriteExt as _};
use std::io::{Read, Write};

#[derive(Debug)]
pub struct Connection<T> {
    inner: T,
}

impl<T> Connection<T>
where
    T: AsyncRead + AsyncWrite + Unpin,
{
    pub fn new(inner: T) -> Self {
        Self { inner }
    }

    pub fn into_inner(self) -> T {
        self.inner
    }

    pub fn handshake_message_writer(&mut self) -> HandshakeMessageWriter<'_, T> {
        HandshakeMessageWriter {
            connection: self,
            buf: Vec::new(),
        }
    }

    #[allow(clippy::needless_lifetimes)]
    pub async fn handshake_message_reader<'a>(
        &'a mut self,
    ) -> std::io::Result<HandshakeMessageReader<'a, T>> {
        let size = self.read_u16().await? as usize;
        Ok(HandshakeMessageReader {
            connection: self,
            size,
        })
    }

    pub async fn write_u8(&mut self, v: u8) -> std::io::Result<()> {
        self.inner.write_all(&[v]).await
    }

    pub async fn write_u16(&mut self, v: u16) -> std::io::Result<()> {
        self.inner.write_all(&v.to_be_bytes()).await
    }

    pub async fn write_u32(&mut self, v: u32) -> std::io::Result<()> {
        self.inner.write_all(&v.to_be_bytes()).await
    }

    pub async fn write_all(&mut self, buf: &[u8]) -> std::io::Result<()> {
        self.inner.write_all(buf).await
    }

    pub async fn flush(&mut self) -> std::io::Result<()> {
        self.inner.flush().await
    }

    pub async fn read_u8(&mut self) -> std::io::Result<u8> {
        let mut buf = [0; 1];
        self.inner.read_exact(&mut buf).await?;
        Ok(buf[0])
    }

    pub async fn read_u16(&mut self) -> std::io::Result<u16> {
        let mut buf = [0; 2];
        self.inner.read_exact(&mut buf).await?;
        Ok(u16::from_be_bytes(buf))
    }

    pub async fn read_u32(&mut self) -> std::io::Result<u32> {
        let mut buf = [0; 4];
        self.inner.read_exact(&mut buf).await?;
        Ok(u32::from_be_bytes(buf))
    }

    pub async fn read_u64(&mut self) -> std::io::Result<u64> {
        let mut buf = [0; 8];
        self.inner.read_exact(&mut buf).await?;
        Ok(u64::from_be_bytes(buf))
    }

    pub async fn read_exact(&mut self, buf: &mut [u8]) -> std::io::Result<()> {
        self.inner.read_exact(buf).await
    }

    pub async fn read_string(&mut self) -> std::io::Result<String> {
        let mut buf = String::new();
        self.inner.read_to_string(&mut buf).await?;
        Ok(buf)
    }

    pub async fn read_stringn(&mut self, size: usize) -> std::io::Result<String> {
        let mut buf = vec![0; size];
        self.inner.read_exact(&mut buf).await?;
        String::from_utf8(buf).map_err(|_| {
            std::io::Error::new(
                std::io::ErrorKind::InvalidData,
                "stream did not contain valid UTF-8",
            )
        })
    }

    pub async fn read_u16_bytes(&mut self) -> std::io::Result<Vec<u8>> {
        let mut buf = vec![0; usize::from(self.read_u16().await?)];
        self.inner.read_exact(&mut buf).await?;
        Ok(buf)
    }

    pub async fn read_u16_string(&mut self) -> std::io::Result<String> {
        let buf = self.read_u16_bytes().await?;
        String::from_utf8(buf).map_err(|_| {
            std::io::Error::new(
                std::io::ErrorKind::InvalidData,
                "stream did not contain valid UTF-8",
            )
        })
    }
}

#[derive(Debug)]
pub struct HandshakeMessageWriter<'a, T> {
    connection: &'a mut Connection<T>,
    buf: Vec<u8>,
}

impl<T> HandshakeMessageWriter<'_, T>
where
    T: AsyncRead + AsyncWrite + Unpin,
{
    pub async fn finish(self) -> std::io::Result<()> {
        if self.buf.len() > u16::MAX as usize {
            return Err(std::io::Error::new(
                std::io::ErrorKind::InvalidData,
                format!(
                    "too large bytes: expected less then {}, but got {} bytes",
                    u16::MAX as usize + 1,
                    self.buf.len()
                ),
            ));
        }
        self.connection.write_u16(self.buf.len() as u16).await?;
        self.connection.write_all(&self.buf).await?;
        self.connection.flush().await?;
        Ok(())
    }

    pub fn write_u8(&mut self, v: u8) -> std::io::Result<()> {
        self.buf.push(v);
        Ok(())
    }

    pub fn write_u16(&mut self, v: u16) -> std::io::Result<()> {
        self.buf.extend_from_slice(&v.to_be_bytes());
        Ok(())
    }

    pub fn write_u32(&mut self, v: u32) -> std::io::Result<()> {
        self.buf.extend_from_slice(&v.to_be_bytes());
        Ok(())
    }

    pub fn write_u64(&mut self, v: u64) -> std::io::Result<()> {
        self.buf.extend_from_slice(&v.to_be_bytes());
        Ok(())
    }

    pub fn write_all(&mut self, bytes: &[u8]) -> std::io::Result<()> {
        self.buf.extend_from_slice(bytes);
        Ok(())
    }
}

#[derive(Debug)]
pub struct HandshakeMessageReader<'a, T> {
    connection: &'a mut Connection<T>,
    size: usize,
}

impl<T> HandshakeMessageReader<'_, T>
where
    T: AsyncRead + AsyncWrite + Unpin,
{
    pub async fn read_u8(&mut self) -> std::io::Result<u8> {
        self.size = self.size.checked_sub(1).ok_or_else(|| {
            std::io::Error::new(std::io::ErrorKind::UnexpectedEof, "unexpected eof")
        })?;
        self.connection.read_u8().await
    }

    pub async fn read_u16(&mut self) -> std::io::Result<u16> {
        self.size = self.size.checked_sub(2).ok_or_else(|| {
            std::io::Error::new(std::io::ErrorKind::UnexpectedEof, "unexpected eof")
        })?;
        self.connection.read_u16().await
    }

    pub async fn read_u32(&mut self) -> std::io::Result<u32> {
        self.size = self.size.checked_sub(4).ok_or_else(|| {
            std::io::Error::new(std::io::ErrorKind::UnexpectedEof, "unexpected eof")
        })?;
        self.connection.read_u32().await
    }

    pub async fn read_u64(&mut self) -> std::io::Result<u64> {
        self.size = self.size.checked_sub(8).ok_or_else(|| {
            std::io::Error::new(std::io::ErrorKind::UnexpectedEof, "unexpected eof")
        })?;
        self.connection.read_u64().await
    }

    pub async fn read_string(&mut self) -> std::io::Result<String> {
        let n = self.size;
        self.size = 0;
        self.connection.read_stringn(n).await
    }

    pub async fn read_bytes(&mut self) -> std::io::Result<Vec<u8>> {
        let n = self.size;
        let mut buf = vec![0; n];
        self.read_exact(&mut buf).await?;
        Ok(buf)
    }

    pub async fn read_exact(&mut self, buf: &mut [u8]) -> std::io::Result<()> {
        let n = buf.len();
        self.size = self.size.checked_sub(n).ok_or_else(|| {
            std::io::Error::new(std::io::ErrorKind::UnexpectedEof, "unexpected eof")
        })?;
        self.connection.read_exact(buf).await
    }

    pub async fn read_u16_string(&mut self) -> std::io::Result<String> {
        let n = self.read_u16().await? as usize;
        self.size = self.size.checked_sub(n).ok_or_else(|| {
            std::io::Error::new(std::io::ErrorKind::UnexpectedEof, "unexpected eof")
        })?;
        self.connection.read_stringn(n).await
    }

    pub async fn consume_remaining_bytes(&mut self) -> std::io::Result<()> {
        let mut buf = vec![0; self.size];
        self.size = 0;
        self.connection.read_exact(&mut buf).await?;
        Ok(())
    }

    pub async fn finish(mut self) -> std::io::Result<()> {
        self.consume_remaining_bytes().await
    }
}

pub trait ReadTermExt: Read {
    fn read_tuple(&mut self) -> Result<Tuple, DecodeError> {
        let term = self.read_term()?;
        term.try_into()
            .map_err(|value| DecodeError::UnexpectedType {
                value,
                expected: "Tuple".to_owned(),
            })
    }

    fn read_term(&mut self) -> Result<Term, DecodeError> {
        Term::decode(self)
    }
}

impl<T: Read> ReadTermExt for T {}

pub trait WriteTermExt: Write {
    fn write_tagged_tuple1(&mut self, tag: i32) -> Result<(), EncodeError> {
        let tuple = Tuple {
            elements: vec![Term::from(FixInteger { value: tag })],
        };
        self.write_term(tuple)
    }

    fn write_tagged_tuple3<T0, T1>(
        &mut self,
        tag: i32,
        term0: T0,
        term1: T1,
    ) -> Result<(), EncodeError>
    where
        Term: From<T0>,
        Term: From<T1>,
    {
        let tuple = Tuple {
            elements: vec![
                Term::from(FixInteger { value: tag }),
                Term::from(term0),
                Term::from(term1),
            ],
        };
        self.write_term(tuple)
    }

    fn write_tagged_tuple4<T0, T1, T2>(
        &mut self,
        tag: i32,
        term0: T0,
        term1: T1,
        term2: T2,
    ) -> Result<(), EncodeError>
    where
        Term: From<T0>,
        Term: From<T1>,
        Term: From<T2>,
    {
        let tuple = Tuple {
            elements: vec![
                Term::from(FixInteger { value: tag }),
                Term::from(term0),
                Term::from(term1),
                Term::from(term2),
            ],
        };
        self.write_term(tuple)
    }

    fn write_tagged_tuple5<T0, T1, T2, T3>(
        &mut self,
        tag: i32,
        term0: T0,
        term1: T1,
        term2: T2,
        term3: T3,
    ) -> Result<(), EncodeError>
    where
        Term: From<T0>,
        Term: From<T1>,
        Term: From<T2>,
        Term: From<T3>,
    {
        let tuple = Tuple {
            elements: vec![
                Term::from(FixInteger { value: tag }),
                Term::from(term0),
                Term::from(term1),
                Term::from(term2),
                Term::from(term3),
            ],
        };
        self.write_term(tuple)
    }

    fn write_tagged_tuple6<T0, T1, T2, T3, T4>(
        &mut self,
        tag: i32,
        term0: T0,
        term1: T1,
        term2: T2,
        term3: T3,
        term4: T4,
    ) -> Result<(), EncodeError>
    where
        Term: From<T0>,
        Term: From<T1>,
        Term: From<T2>,
        Term: From<T3>,
        Term: From<T4>,
    {
        let tuple = Tuple {
            elements: vec![
                Term::from(FixInteger { value: tag }),
                Term::from(term0),
                Term::from(term1),
                Term::from(term2),
                Term::from(term3),
                Term::from(term4),
            ],
        };
        self.write_term(tuple)
    }

    #[allow(clippy::too_many_arguments)]
    fn write_tagged_tuple7<T0, T1, T2, T3, T4, T5>(
        &mut self,
        tag: i32,
        term0: T0,
        term1: T1,
        term2: T2,
        term3: T3,
        term4: T4,
        term5: T5,
    ) -> Result<(), EncodeError>
    where
        Term: From<T0>,
        Term: From<T1>,
        Term: From<T2>,
        Term: From<T3>,
        Term: From<T4>,
        Term: From<T5>,
    {
        let tuple = Tuple {
            elements: vec![
                Term::from(FixInteger { value: tag }),
                Term::from(term0),
                Term::from(term1),
                Term::from(term2),
                Term::from(term3),
                Term::from(term4),
                Term::from(term5),
            ],
        };
        self.write_term(tuple)
    }

    fn write_term<T>(&mut self, term: T) -> Result<(), EncodeError>
    where
        Term: From<T>,
    {
        Term::from(term).encode(self)
    }
}

impl<T: Write> WriteTermExt for T {}


================================================
FILE: src/lib.rs
================================================
//! Rust Implementation of Erlang Distribution Protocol.
//!
//! Distribution protocol is used to communicate with distributed erlang nodes.
//!
//! Reference: [Distribution Protocol](http://erlang.org/doc/apps/erts/erl_dist_protocol.html)
//!
//! # Examples
//!
//! Gets a node entry from EPMD:
//! ```no_run
//! # use smol::net::TcpStream;
//! use erl_dist::epmd::{DEFAULT_EPMD_PORT, EpmdClient};
//!
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
//! # smol::block_on(async {
//! // Connect to the local EPMD.
//! let connection = TcpStream::connect(("localhost", DEFAULT_EPMD_PORT)).await?;
//! let client = EpmdClient::new(connection);
//!
//! // Get the information of a node.
//! let node_name = "foo";
//! if let Some(node) = client.get_node(node_name).await? {
//!     println!("Found: {:?}", node);
//! } else {
//!     println!("Not found");
//! }
//! # Ok(())
//! # })
//! # }
//! ```
//!
//! Sends a message to an Erlang node:
//! ```no_run
//! # use smol::net::TcpStream;
//! use erl_dist::LOWEST_DISTRIBUTION_PROTOCOL_VERSION;
//! use erl_dist::node::{Creation, LocalNode};
//! use erl_dist::handshake::ClientSideHandshake;
//! use erl_dist::term::{Atom, Pid};
//! use erl_dist::message::{channel, Message};
//!
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
//! # smol::block_on(async {
//! // Connect to a peer node.
//! let peer_host = "localhost";
//! let peer_port = 7483;  // NOTE: Usually, port number is retrieved from EPMD.
//! let connection = TcpStream::connect((peer_host, peer_port)).await?;
//!
//! // Local node information.
//! let creation = Creation::random();
//! let local_node = LocalNode::new("foo@localhost".parse()?, creation);
//!
//! // Do handshake.
//! let mut handshake = ClientSideHandshake::new(connection, local_node.clone(), "cookie");
//! let _status = handshake.execute_send_name(LOWEST_DISTRIBUTION_PROTOCOL_VERSION).await?;
//! let (connection, peer_node) = handshake.execute_rest(true).await?;
//!
//! // Create a channel.
//! let capability_flags = local_node.flags & peer_node.flags;
//! let (mut tx, _) = channel(connection, capability_flags);
//!
//! // Send a message.
//! let from_pid = Pid::new(local_node.name.to_string(), 0, 0, local_node.creation.get());
//! let to_name = Atom::from("bar");
//! let msg = Message::reg_send(from_pid, to_name, Atom::from("hello").into());
//! tx.send(msg).await?;
//! # Ok(())
//! # })
//! # }
//! ```
//!
//! Example commands:
//! - EPMD Client Example: [send_msg.rs](https://github.com/sile/erl_dist/blob/master/examples/epmd_cli.rs)
//! - Client Node Example: [send_msg.rs](https://github.com/sile/erl_dist/blob/master/examples/send_msg.rs)
//! - Server Node Example: [recv_msg.rs](https://github.com/sile/erl_dist/blob/master/examples/recv_msg.rs)
#![warn(missing_docs)]
pub mod epmd;
pub mod handshake;
pub mod message;
pub mod node;
pub mod term;

mod channel;
mod eetf_ext;
mod flags;
mod io;

pub use self::flags::DistributionFlags;

/// The lowest distribution protocol version this crate can handle.
pub const LOWEST_DISTRIBUTION_PROTOCOL_VERSION: u16 = 6;

/// The highest distribution protocol version this crate can handle.
pub const HIGHEST_DISTRIBUTION_PROTOCOL_VERSION: u16 = 6;

#[cfg(test)]
mod tests {
    use std::process::{Child, Command};

    type BoxError = Box<dyn std::error::Error + Send + Sync>;

    pub const COOKIE: &str = "test-cookie";

    #[derive(Debug)]
    pub struct TestErlangNode {
        child: Child,
    }

    impl TestErlangNode {
        pub async fn new(name: &str) -> Result<Self, BoxError> {
            let child = Command::new("erl")
                .args(&["-sname", name, "-noshell", "-setcookie", COOKIE])
                .spawn()?;
            let start = std::time::Instant::now();
            loop {
                if let Ok(client) = try_epmd_client().await {
                    if client.get_node(name).await?.is_some() {
                        break;
                    }
                }
                std::thread::sleep(std::time::Duration::from_millis(500));
                if start.elapsed() > std::time::Duration::from_secs(10) {
                    break;
                }
            }
            Ok(Self { child })
        }
    }

    impl Drop for TestErlangNode {
        fn drop(&mut self) {
            let _ = self.child.kill();
        }
    }

    pub async fn try_epmd_client() -> Result<crate::epmd::EpmdClient<smol::net::TcpStream>, BoxError>
    {
        let client = smol::net::TcpStream::connect(("127.0.0.1", crate::epmd::DEFAULT_EPMD_PORT))
            .await
            .map(crate::epmd::EpmdClient::new)?;
        Ok(client)
    }

    pub async fn epmd_client() -> crate::epmd::EpmdClient<smol::net::TcpStream> {
        try_epmd_client().await.unwrap()
    }
}


================================================
FILE: src/message.rs
================================================
//! Messages passed between distributed nodes, and channels for those messages.
//!
//! Reference: [Protocol between Connected Nodes](https://www.erlang.org/doc/apps/erts/erl_dist_protocol.html#protocol-between-connected-nodes)
//!
//! Note that distribution headers are not supported in the current version.
#[cfg(doc)]
use crate::DistributionFlags;
use crate::eetf_ext;
use crate::io::{ReadTermExt, WriteTermExt};
use crate::term::{Atom, FixInteger, List, Mfa, Pid, PidOrAtom, Reference, Term, Tuple};
use eetf::{DecodeError, EncodeError};
use std::io::{Read, Write};

pub use crate::channel::{Receiver, RecvError, SendError, Sender, channel};

trait DistributionMessage: Sized {
    const OP: i32;
    fn write_into<W: Write>(self, writer: &mut W) -> Result<(), EncodeError>;
    fn read_from<R: Read>(reader: &mut R, ctrl_msg: Tuple) -> Result<Self, DecodeError>;
}

/// This signal is sent by `from_pid` in order to create a link between `from_pid` and `to_pid`.
#[derive(Debug, Clone, PartialEq)]
#[allow(missing_docs)]
pub struct Link {
    pub from_pid: Pid,
    pub to_pid: Pid,
}

impl DistributionMessage for Link {
    const OP: i32 = 1;

    fn write_into<W: Write>(self, writer: &mut W) -> Result<(), EncodeError> {
        writer.write_tagged_tuple3(Self::OP, self.from_pid, self.to_pid)?;
        Ok(())
    }

    fn read_from<R: Read>(_reader: &mut R, ctrl_msg: Tuple) -> Result<Self, DecodeError> {
        let (from_pid, to_pid) = eetf_ext::try_from_tagged_tuple3(ctrl_msg)?;
        Ok(Self { from_pid, to_pid })
    }
}

/// This signal is used to send a message to `to_pid`.
#[derive(Debug, Clone, PartialEq)]
#[allow(missing_docs)]
pub struct Send {
    pub to_pid: Pid,
    pub message: Term,
}

impl DistributionMessage for Send {
    const OP: i32 = 2;

    fn write_into<W: Write>(self, writer: &mut W) -> Result<(), EncodeError> {
        writer.write_tagged_tuple3(Self::OP, Tuple::nil(), self.to_pid)?;
        writer.write_term(self.message)?;
        Ok(())
    }

    fn read_from<R: Read>(reader: &mut R, ctrl_msg: Tuple) -> Result<Self, DecodeError> {
        let (_, to_pid): (Term, _) = eetf_ext::try_from_tagged_tuple3(ctrl_msg)?;
        let message = reader.read_term()?;
        Ok(Self { to_pid, message })
    }
}

/// This signal is sent when a link has been broken.
#[derive(Debug, Clone, PartialEq)]
#[allow(missing_docs)]
pub struct Exit {
    pub from_pid: Pid,
    pub to_pid: Pid,
    pub reason: Term,
}

impl DistributionMessage for Exit {
    const OP: i32 = 3;

    fn write_into<W: Write>(self, writer: &mut W) -> Result<(), EncodeError> {
        writer.write_tagged_tuple4(Self::OP, self.from_pid, self.to_pid, self.reason)?;
        Ok(())
    }

    fn read_from<R: Read>(_reader: &mut R, ctrl_msg: Tuple) -> Result<Self, DecodeError> {
        let (from_pid, to_pid, reason) = eetf_ext::try_from_tagged_tuple4(ctrl_msg)?;
        Ok(Self {
            from_pid,
            to_pid,
            reason,
        })
    }
}

/// **DEPRECATED** This signal is sent by `from_pid` in order to remove a link between `from_pid` and `to_pid`.
///
/// This signal has been deprecated and will not be supported in OTP 26.
/// For more information see the documentation of the [new link protocol](https://www.erlang.org/doc/apps/erts/erl_dist_protocol.html#new_link_protocol).
#[derive(Debug, Clone, PartialEq)]
#[allow(missing_docs)]
pub struct Unlink {
    pub from_pid: Pid,
    pub to_pid: Pid,
}

impl DistributionMessage for Unlink {
    const OP: i32 = 4;

    fn write_into<W: Write>(self, writer: &mut W) -> Result<(), EncodeError> {
        writer.write_tagged_tuple3(Self::OP, self.from_pid, self.to_pid)?;
        Ok(())
    }

    fn read_from<R: Read>(_reader: &mut R, ctrl_msg: Tuple) -> Result<Self, DecodeError> {
        let (from_pid, to_pid) = eetf_ext::try_from_tagged_tuple3(ctrl_msg)?;
        Ok(Self { from_pid, to_pid })
    }
}

/// Node link.
#[derive(Debug, Clone, PartialEq)]
pub struct NodeLink;

impl DistributionMessage for NodeLink {
    const OP: i32 = 5;

    fn write_into<W: Write>(self, writer: &mut W) -> Result<(), EncodeError> {
        writer.write_tagged_tuple1(Self::OP)?;
        Ok(())
    }

    fn read_from<R: Read>(_reader: &mut R, ctrl_msg: Tuple) -> Result<Self, DecodeError> {
        eetf_ext::check_tuple_len(&ctrl_msg, 1)?;
        Ok(Self {})
    }
}

/// This signal is used to send a message to a named process.
#[derive(Debug, Clone, PartialEq)]
#[allow(missing_docs)]
pub struct RegSend {
    pub from_pid: Pid,
    pub to_name: Atom,
    pub message: Term,
}

impl DistributionMessage for RegSend {
    const OP: i32 = 6;

    fn write_into<W: Write>(self, writer: &mut W) -> Result<(), EncodeError> {
        writer.write_tagged_tuple4(Self::OP, self.from_pid, Tuple::nil(), self.to_name)?;
        writer.write_term(self.message)?;
        Ok(())
    }

    fn read_from<R: Read>(reader: &mut R, ctrl_msg: Tuple) -> Result<Self, DecodeError> {
        let (from_pid, _, to_name): (_, Term, _) = eetf_ext::try_from_tagged_tuple4(ctrl_msg)?;
        let message = reader.read_term()?;
        Ok(Self {
            from_pid,
            to_name,
            message,
        })
    }
}

/// Group leader.
#[derive(Debug, Clone, PartialEq)]
#[allow(missing_docs)]
pub struct GroupLeader {
    pub from_pid: Pid,
    pub to_pid: Pid,
}

impl DistributionMessage for GroupLeader {
    const OP: i32 = 7;

    fn write_into<W: Write>(self, writer: &mut W) -> Result<(), EncodeError> {
        writer.write_tagged_tuple3(Self::OP, self.from_pid, self.to_pid)?;
        Ok(())
    }

    fn read_from<R: Read>(_reader: &mut R, ctrl_msg: Tuple) -> Result<Self, DecodeError> {
        let (from_pid, to_pid) = eetf_ext::try_from_tagged_tuple3(ctrl_msg)?;
        Ok(Self { from_pid, to_pid })
    }
}

/// This signal is sent by a call to the `erlang:exit/2` bif.
#[derive(Debug, Clone, PartialEq)]
#[allow(missing_docs)]
pub struct Exit2 {
    pub from_pid: Pid,
    pub to_pid: Pid,
    pub reason: Term,
}

impl DistributionMessage for Exit2 {
    const OP: i32 = 8;

    fn write_into<W: Write>(self, writer: &mut W) -> Result<(), EncodeError> {
        writer.write_tagged_tuple4(Self::OP, self.from_pid, self.to_pid, self.reason)?;
        Ok(())
    }

    fn read_from<R: Read>(_reader: &mut R, ctrl_msg: Tuple) -> Result<Self, DecodeError> {
        let (from_pid, to_pid, reason) = eetf_ext::try_from_tagged_tuple4(ctrl_msg)?;
        Ok(Self {
            from_pid,
            to_pid,
            reason,
        })
    }
}

/// [`Send`] with a trace token.
#[derive(Debug, Clone, PartialEq)]
#[allow(missing_docs)]
pub struct SendTt {
    pub to_pid: Pid,
    pub trace_token: Term,
    pub message: Term,
}

impl DistributionMessage for SendTt {
    const OP: i32 = 12;

    fn write_into<W: Write>(self, writer: &mut W) -> Result<(), EncodeError> {
        writer.write_tagged_tuple4(Self::OP, Tuple::nil(), self.to_pid, self.trace_token)?;
        writer.write_term(self.message)?;
        Ok(())
    }

    fn read_from<R: Read>(reader: &mut R, ctrl_msg: Tuple) -> Result<Self, DecodeError> {
        let (_, trace_token, to_pid): (Term, _, _) = eetf_ext::try_from_tagged_tuple4(ctrl_msg)?;
        let message = reader.read_term()?;
        Ok(Self {
            to_pid,
            trace_token,
            message,
        })
    }
}

/// [`Exit`] with a trace token.
#[derive(Debug, Clone, PartialEq)]
#[allow(missing_docs)]
pub struct ExitTt {
    pub from_pid: Pid,
    pub to_pid: Pid,
    pub trace_token: Term,
    pub reason: Term,
}

impl DistributionMessage for ExitTt {
    const OP: i32 = 13;

    fn write_into<W: Write>(self, writer: &mut W) -> Result<(), EncodeError> {
        writer.write_tagged_tuple5(
            Self::OP,
            self.from_pid,
            self.to_pid,
            self.trace_token,
            self.reason,
        )?;
        Ok(())
    }

    fn read_from<R: Read>(_reader: &mut R, ctrl_msg: Tuple) -> Result<Self, DecodeError> {
        let (from_pid, to_pid, trace_token, reason) = eetf_ext::try_from_tagged_tuple5(ctrl_msg)?;
        Ok(Self {
            from_pid,
            to_pid,
            trace_token,
            reason,
        })
    }
}

/// [`RegSend`] with a trace token.
#[derive(Debug, Clone, PartialEq)]
#[allow(missing_docs)]
pub struct RegSendTt {
    pub from_pid: Pid,
    pub to_name: Atom,
    pub trace_token: Term,
    pub message: Term,
}

impl DistributionMessage for RegSendTt {
    const OP: i32 = 16;

    fn write_into<W: Write>(self, writer: &mut W) -> Result<(), EncodeError> {
        writer.write_tagged_tuple5(
            Self::OP,
            self.from_pid,
            Tuple::nil(),
            self.to_name,
            self.trace_token,
        )?;
        writer.write_term(self.message)?;
        Ok(())
    }

    fn read_from<R: Read>(reader: &mut R, ctrl_msg: Tuple) -> Result<Self, DecodeError> {
        let (from_pid, _, to_name, trace_token): (_, Term, _, _) =
            eetf_ext::try_from_tagged_tuple5(ctrl_msg)?;
        let message = reader.read_term()?;
        Ok(Self {
            from_pid,
            to_name,
            trace_token,
            message,
        })
    }
}

/// [`Exit2`] with a trace token.
#[derive(Debug, Clone, PartialEq)]
#[allow(missing_docs)]
pub struct Exit2Tt {
    pub from_pid: Pid,
    pub to_pid: Pid,
    pub trace_token: Term,
    pub reason: Term,
}

impl DistributionMessage for Exit2Tt {
    const OP: i32 = 18;

    fn write_into<W: Write>(self, writer: &mut W) -> Result<(), EncodeError> {
        writer.write_tagged_tuple5(
            Self::OP,
            self.from_pid,
            self.to_pid,
            self.trace_token,
            self.reason,
        )?;
        Ok(())
    }

    fn read_from<R: Read>(_reader: &mut R, ctrl_msg: Tuple) -> Result<Self, DecodeError> {
        let (from_pid, to_pid, trace_token, reason) = eetf_ext::try_from_tagged_tuple5(ctrl_msg)?;
        Ok(Self {
            from_pid,
            to_pid,
            trace_token,
            reason,
        })
    }
}

/// `from_pid` = monitoring process and `to_proc` = monitored process pid or name (atom).
#[derive(Debug, Clone, PartialEq)]
#[allow(missing_docs)]
pub struct MonitorP {
    pub from_pid: Pid,
    pub to_proc: PidOrAtom,
    pub reference: Reference,
}

impl DistributionMessage for MonitorP {
    const OP: i32 = 19;

    fn write_into<W: Write>(self, writer: &mut W) -> Result<(), EncodeError> {
        writer.write_tagged_tuple4(Self::OP, self.from_pid, self.to_proc, self.reference)?;
        Ok(())
    }

    fn read_from<R: Read>(_reader: &mut R, ctrl_msg: Tuple) -> Result<Self, DecodeError> {
        let (from_pid, to_proc, reference) = eetf_ext::try_from_tagged_tuple4(ctrl_msg)?;
        Ok(Self {
            from_pid,
            to_proc,
            reference,
        })
    }
}

/// `from_pid` = monitoring process and `to_proc` = monitored process pid or name (atom).
///
/// We include `from_pid` just in case we want to trace this.
#[derive(Debug, Clone, PartialEq)]
#[allow(missing_docs)]
pub struct DemonitorP {
    pub from_pid: Pid,
    pub to_proc: PidOrAtom,
    pub reference: Reference,
}

impl DistributionMessage for DemonitorP {
    const OP: i32 = 20;

    fn write_into<W: Write>(self, writer: &mut W) -> Result<(), EncodeError> {
        writer.write_tagged_tuple4(Self::OP, self.from_pid, self.to_proc, self.reference)?;
        Ok(())
    }

    fn read_from<R: Read>(_reader: &mut R, ctrl_msg: Tuple) -> Result<Self, DecodeError> {
        let (from_pid, to_proc, reference) = eetf_ext::try_from_tagged_tuple4(ctrl_msg)?;
        Ok(Self {
            from_pid,
            to_proc,
            reference,
        })
    }
}

/// `from_proc` = monitored process pid or name (atom), `to_pid` = monitoring process, and `reason` = exit reason for the monitored process.
#[derive(Debug, Clone, PartialEq)]
#[allow(missing_docs)]
pub struct MonitorPExit {
    pub from_proc: PidOrAtom,
    pub to_pid: Pid,
    pub reference: Reference,
    pub reason: Term,
}

impl DistributionMessage for MonitorPExit {
    const OP: i32 = 21;

    fn write_into<W: Write>(self, writer: &mut W) -> Result<(), EncodeError> {
        writer.write_tagged_tuple5(
            Self::OP,
            self.from_proc,
            self.to_pid,
            self.reference,
            self.reason,
        )?;
        Ok(())
    }

    fn read_from<R: Read>(_reader: &mut R, ctrl_msg: Tuple) -> Result<Self, DecodeError> {
        let (from_proc, to_pid, reference, reason) = eetf_ext::try_from_tagged_tuple5(ctrl_msg)?;
        Ok(Self {
            from_proc,
            to_pid,
            reference,
            reason,
        })
    }
}

/// This control message replaces the [`Send`] control message and will be sent when the distribution flag `DistributionFlags::SEND_SENDER` has been negotiated in the connection setup handshake.
#[derive(Debug, Clone, PartialEq)]
#[allow(missing_docs)]
pub struct SendSender {
    pub from_pid: Pid,
    pub to_pid: Pid,
    pub message: Term,
}

impl DistributionMessage for SendSender {
    const OP: i32 = 22;

    fn write_into<W: Write>(self, writer: &mut W) -> Result<(), EncodeError> {
        writer.write_tagged_tuple3(Self::OP, self.from_pid, self.to_pid)?;
        writer.write_term(self.message)?;
        Ok(())
    }

    fn read_from<R: Read>(reader: &mut R, ctrl_msg: Tuple) -> Result<Self, DecodeError> {
        let (from_pid, to_pid) = eetf_ext::try_from_tagged_tuple3(ctrl_msg)?;
        let message = reader.read_term()?;
        Ok(Self {
            from_pid,
            to_pid,
            message,
        })
    }
}

/// [`SendSender`] with a trace token.
#[derive(Debug, Clone, PartialEq)]
#[allow(missing_docs)]
pub struct SendSenderTt {
    pub from_pid: Pid,
    pub to_pid: Pid,
    pub trace_token: Term,
    pub message: Term,
}

impl DistributionMessage for SendSenderTt {
    const OP: i32 = 23;

    fn write_into<W: Write>(self, writer: &mut W) -> Result<(), EncodeError> {
        writer.write_tagged_tuple4(Self::OP, self.from_pid, self.to_pid, self.trace_token)?;
        writer.write_term(self.message)?;
        Ok(())
    }

    fn read_from<R: Read>(reader: &mut R, ctrl_msg: Tuple) -> Result<Self, DecodeError> {
        let (from_pid, to_pid, trace_token) = eetf_ext::try_from_tagged_tuple4(ctrl_msg)?;
        let message = reader.read_term()?;
        Ok(Self {
            from_pid,
            to_pid,
            trace_token,
            message,
        })
    }
}

/// This control message replaces the [`Exit`] control message and will be sent when the distribution flag [`DistributionFlags::EXIT_PAYLOAD`] has been negotiated in the connection setup handshake.
#[derive(Debug, Clone, PartialEq)]
#[allow(missing_docs)]
pub struct PayloadExit {
    pub from_pid: Pid,
    pub to_pid: Pid,
    pub reason: Term,
}

impl DistributionMessage for PayloadExit {
    const OP: i32 = 24;

    fn write_into<W: Write>(self, writer: &mut W) -> Result<(), EncodeError> {
        writer.write_tagged_tuple3(Self::OP, self.from_pid, self.to_pid)?;
        writer.write_term(self.reason)?;
        Ok(())
    }

    fn read_from<R: Read>(reader: &mut R, ctrl_msg: Tuple) -> Result<Self, DecodeError> {
        let (from_pid, to_pid) = eetf_ext::try_from_tagged_tuple3(ctrl_msg)?;
        let reason = reader.read_term()?;
        Ok(Self {
            from_pid,
            to_pid,
            reason,
        })
    }
}

/// [`PayloadExit`] with a trace token.
#[derive(Debug, Clone, PartialEq)]
#[allow(missing_docs)]
pub struct PayloadExitTt {
    pub from_pid: Pid,
    pub to_pid: Pid,
    pub trace_token: Term,
    pub reason: Term,
}

impl DistributionMessage for PayloadExitTt {
    const OP: i32 = 25;

    fn write_into<W: Write>(self, writer: &mut W) -> Result<(), EncodeError> {
        writer.write_tagged_tuple4(Self::OP, self.from_pid, self.to_pid, self.trace_token)?;
        writer.write_term(self.reason)?;
        Ok(())
    }

    fn read_from<R: Read>(reader: &mut R, ctrl_msg: Tuple) -> Result<Self, DecodeError> {
        let (from_pid, to_pid, trace_token) = eetf_ext::try_from_tagged_tuple4(ctrl_msg)?;
        let reason = reader.read_term()?;
        Ok(Self {
            from_pid,
            to_pid,
            trace_token,
            reason,
        })
    }
}

/// This control message replaces the [`Exit2`] control message and will be sent when the distribution flag [`DistributionFlags::EXIT_PAYLOAD`] has been negotiated in the connection setup handshake.
#[derive(Debug, Clone, PartialEq)]
#[allow(missing_docs)]
pub struct PayloadExit2 {
    pub from_pid: Pid,
    pub to_pid: Pid,
    pub reason: Term,
}

impl DistributionMessage for PayloadExit2 {
    const OP: i32 = 26;

    fn write_into<W: Write>(self, writer: &mut W) -> Result<(), EncodeError> {
        writer.write_tagged_tuple3(Self::OP, self.from_pid, self.to_pid)?;
        writer.write_term(self.reason)?;
        Ok(())
    }

    fn read_from<R: Read>(reader: &mut R, ctrl_msg: Tuple) -> Result<Self, DecodeError> {
        let (from_pid, to_pid) = eetf_ext::try_from_tagged_tuple3(ctrl_msg)?;
        let reason = reader.read_term()?;
        Ok(Self {
            from_pid,
            to_pid,
            reason,
        })
    }
}

/// [`PayloadExit2`] with a trace token.
#[derive(Debug, Clone, PartialEq)]
#[allow(missing_docs)]
pub struct PayloadExit2Tt {
    pub from_pid: Pid,
    pub to_pid: Pid,
    pub trace_token: Term,
    pub reason: Term,
}

impl DistributionMessage for PayloadExit2Tt {
    const OP: i32 = 27;

    fn write_into<W: Write>(self, writer: &mut W) -> Result<(), EncodeError> {
        writer.write_tagged_tuple4(Self::OP, self.from_pid, self.to_pid, self.trace_token)?;
        writer.write_term(self.reason)?;
        Ok(())
    }

    fn read_from<R: Read>(reader: &mut R, ctrl_msg: Tuple) -> Result<Self, DecodeError> {
        let (from_pid, to_pid, trace_token) = eetf_ext::try_from_tagged_tuple4(ctrl_msg)?;
        let reason = reader.read_term()?;
        Ok(Self {
            from_pid,
            to_pid,
            trace_token,
            reason,
        })
    }
}

/// This control message replaces the [`MonitorPExit`] control message and will be sent when the distribution flag [`DistributionFlags::EXIT_PAYLOAD`] has been negotiated in the connection setup handshake.
#[derive(Debug, Clone, PartialEq)]
#[allow(missing_docs)]
pub struct PayloadMonitorPExit {
    pub from_proc: PidOrAtom,
    pub to_pid: Pid,
    pub reference: Reference,
    pub reason: Term,
}

impl DistributionMessage for PayloadMonitorPExit {
    const OP: i32 = 28;

    fn write_into<W: Write>(self, writer: &mut W) -> Result<(), EncodeError> {
        writer.write_tagged_tuple4(Self::OP, self.from_proc, self.to_pid, self.reference)?;
        writer.write_term(self.reason)?;
        Ok(())
    }

    fn read_from<R: Read>(reader: &mut R, ctrl_msg: Tuple) -> Result<Self, DecodeError> {
        let (from_proc, to_pid, reference) = eetf_ext::try_from_tagged_tuple4(ctrl_msg)?;
        let reason = reader.read_term()?;
        Ok(Self {
            from_proc,
            to_pid,
            reference,
            reason,
        })
    }
}

/// This signal is sent by the `spawn_request()` BIF.
#[derive(Debug, Clone, PartialEq)]
#[allow(missing_docs)]
pub struct SpawnRequest {
    pub req_id: Reference,
    pub from_pid: Pid,
    pub group_leader: Pid,
    pub mfa: Mfa,
    pub opt_list: List,
    pub arg_list: List,
}

impl DistributionMessage for SpawnRequest {
    const OP: i32 = 29;

    fn write_into<W: Write>(self, writer: &mut W) -> Result<(), EncodeError> {
        writer.write_tagged_tuple6(
            Self::OP,
            self.req_id,
            self.from_pid,
            self.group_leader,
            self.mfa,
            self.opt_list,
        )?;
        writer.write_term(self.arg_list)?;
        Ok(())
    }

    fn read_from<R: Read>(reader: &mut R, ctrl_msg: Tuple) -> Result<Self, DecodeError> {
        let (req_id, from_pid, group_leader, mfa, opt_list) =
            eetf_ext::try_from_tagged_tuple6(ctrl_msg)?;
        let arg_list = eetf_ext::try_from_term(reader.read_term()?, "list")?;
        Ok(Self {
            req_id,
            from_pid,
            group_leader,
            mfa,
            opt_list,
            arg_list,
        })
    }
}

/// [`SpawnRequest`] with a trace token.
#[derive(Debug, Clone, PartialEq)]
#[allow(missing_docs)]
pub struct SpawnRequestTt {
    pub req_id: Reference,
    pub from_pid: Pid,
    pub group_leader: Pid,
    pub mfa: Mfa,
    pub opt_list: List,
    pub trace_token: Term,
    pub arg_list: List,
}

impl DistributionMessage for SpawnRequestTt {
    const OP: i32 = 30;

    fn write_into<W: Write>(self, writer: &mut W) -> Result<(), EncodeError> {
        writer.write_tagged_tuple7(
            Self::OP,
            self.req_id,
            self.from_pid,
            self.group_leader,
            self.mfa,
            self.opt_list,
            self.trace_token,
        )?;
        writer.write_term(self.arg_list)?;
        Ok(())
    }

    fn read_from<R: Read>(reader: &mut R, ctrl_msg: Tuple) -> Result<Self, DecodeError> {
        let (req_id, from_pid, group_leader, mfa, opt_list, trace_token) =
            eetf_ext::try_from_tagged_tuple7(ctrl_msg)?;
        let arg_list = eetf_ext::try_from_term(reader.read_term()?, "list")?;
        Ok(Self {
            req_id,
            from_pid,
            group_leader,
            mfa,
            opt_list,
            trace_token,
            arg_list,
        })
    }
}

/// This signal is sent as a reply to a process previously sending a [`SpawnRequest`] signal.
#[derive(Debug, Clone, PartialEq)]
#[allow(missing_docs)]
pub struct SpawnReply {
    pub req_id: Reference,
    pub to_pid: Pid,
    pub flags: FixInteger,
    pub result: PidOrAtom,
}

impl DistributionMessage for SpawnReply {
    const OP: i32 = 31;

    fn write_into<W: Write>(self, writer: &mut W) -> Result<(), EncodeError> {
        writer.write_tagged_tuple5(Self::OP, self.req_id, self.to_pid, self.flags, self.result)?;
        Ok(())
    }

    fn read_from<R: Read>(_reader: &mut R, ctrl_msg: Tuple) -> Result<Self, DecodeError> {
        let (req_id, to_pid, flags, result) = eetf_ext::try_from_tagged_tuple5(ctrl_msg)?;
        Ok(Self {
            req_id,
            to_pid,
            flags,
            result,
        })
    }
}

/// [`SpawnReply`] with a trace token.
#[derive(Debug, Clone, PartialEq)]
#[allow(missing_docs)]
pub struct SpawnReplyTt {
    pub req_id: Reference,
    pub to_pid: Pid,
    pub flags: FixInteger,
    pub result: PidOrAtom,
    pub trace_token: Term,
}

impl DistributionMessage for SpawnReplyTt {
    const OP: i32 = 32;

    fn write_into<W: Write>(self, writer: &mut W) -> Result<(), EncodeError> {
        writer.write_tagged_tuple6(
            Self::OP,
            self.req_id,
            self.to_pid,
            self.flags,
            self.result,
            self.trace_token,
        )?;
        Ok(())
    }

    fn read_from<R: Read>(_reader: &mut R, ctrl_msg: Tuple) -> Result<Self, DecodeError> {
        let (req_id, to_pid, flags, result, trace_token) =
            eetf_ext::try_from_tagged_tuple6(ctrl_msg)?;
        Ok(Self {
            req_id,
            to_pid,
            flags,
            result,
            trace_token,
        })
    }
}

/// This signal is sent by `from_pid` in order to remove a link between `from_pid` and `to_pid`.
///
/// This unlink signal replaces the [`Unlink`] signal.
/// This signal is only passed when the [new link protocol](https://www.erlang.org/doc/apps/erts/erl_dist_protocol.html#new_link_protocol) has been negotiated using the [`DistributionFlags::UNLINK_ID`] distribution flag.
#[derive(Debug, Clone, PartialEq)]
#[allow(missing_docs)]
pub struct UnlinkId {
    pub id: Term,
    pub from_pid: Pid,
    pub to_pid: Pid,
}

impl DistributionMessage for UnlinkId {
    const OP: i32 = 35;

    fn write_into<W: Write>(self, writer: &mut W) -> Result<(), EncodeError> {
        writer.write_tagged_tuple4(Self::OP, self.id, self.from_pid, self.to_pid)?;
        Ok(())
    }

    fn read_from<R: Read>(_reader: &mut R, ctrl_msg: Tuple) -> Result<Self, DecodeError> {
        let (id, from_pid, to_pid) = eetf_ext::try_from_tagged_tuple4(ctrl_msg)?;
        Ok(Self {
            id,
            from_pid,
            to_pid,
        })
    }
}

/// An unlink acknowledgement signal.
///
/// This signal is sent as an acknowledgement of the reception of an [`UnlinkId`] signal.
#[derive(Debug, Clone, PartialEq)]
#[allow(missing_docs)]
pub struct UnlinkIdAck {
    pub id: Term,
    pub from_pid: Pid,
    pub to_pid: Pid,
}

impl DistributionMessage for UnlinkIdAck {
    const OP: i32 = 36;

    fn write_into<W: Write>(self, writer: &mut W) -> Result<(), EncodeError> {
        writer.write_tagged_tuple4(Self::OP, self.id, self.from_pid, self.to_pid)?;
        Ok(())
    }

    fn read_from<R: Read>(_reader: &mut R, ctrl_msg: Tuple) -> Result<Self, DecodeError> {
        let (id, from_pid, to_pid) = eetf_ext::try_from_tagged_tuple4(ctrl_msg)?;
        Ok(Self {
            id,
            from_pid,
            to_pid,
        })
    }
}

/// This control message is used when sending the message Message to the process identified by the process alias `alias`.
///
/// Nodes that can handle this control message sets the distribution flag [`DistributionFlags::ALIAS`] in the connection setup handshake.
#[derive(Debug, Clone, PartialEq)]
#[allow(missing_docs)]
pub struct AliasSend {
    pub from_pid: Pid,
    pub alias: Reference,
    pub message: Term,
}

impl DistributionMessage for AliasSend {
    const OP: i32 = 33;

    fn write_into<W: Write>(self, writer: &mut W) -> Result<(), EncodeError> {
        writer.write_tagged_tuple3(Self::OP, self.from_pid, self.alias)?;
        writer.write_term(self.message)?;
        Ok(())
    }

    fn read_from<R: Read>(reader: &mut R, ctrl_msg: Tuple) -> Result<Self, DecodeError> {
        let (from_pid, alias) = eetf_ext::try_from_tagged_tuple3(ctrl_msg)?;
        let message = reader.read_term()?;
        Ok(Self {
            from_pid,
            alias,
            message,
        })
    }
}

/// [`AliasSend`] with a trace token.
#[derive(Debug, Clone, PartialEq)]
#[allow(missing_docs)]
pub struct AliasSendTt {
    pub from_pid: Pid,
    pub alias: Reference,
    pub trace_token: Term,
    pub message: Term,
}

impl DistributionMessage for AliasSendTt {
    const OP: i32 = 34;

    fn write_into<W: Write>(self, writer: &mut W) -> Result<(), EncodeError> {
        writer.write_tagged_tuple4(Self::OP, self.from_pid, self.alias, self.trace_token)?;
        writer.write_term(self.message)?;
        Ok(())
    }

    fn read_from<R: Read>(reader: &mut R, ctrl_msg: Tuple) -> Result<Self, DecodeError> {
        let (from_pid, alias, trace_token) = eetf_ext::try_from_tagged_tuple4(ctrl_msg)?;
        let message = reader.read_term()?;
        Ok(Self {
            from_pid,
            alias,
            trace_token,
            message,
        })
    }
}

/// Message.
#[derive(Debug, Clone, PartialEq)]
#[allow(missing_docs)]
pub enum Message {
    Link(Link),
    Send(Send),
    Exit(Exit),
    Unlink(Unlink), // deprecated
    NodeLink(NodeLink),
    RegSend(RegSend),
    GroupLeader(GroupLeader),
    Exit2(Exit2),
    SendTt(SendTt),
    ExitTt(ExitTt),
    RegSendTt(RegSendTt),
    Exit2Tt(Exit2Tt),
    MonitorP(MonitorP),
    DemonitorP(DemonitorP),
    MonitorPExit(MonitorPExit),
    SendSender(SendSender),
    SendSenderTt(SendSenderTt),
    PayloadExit(PayloadExit),
    PayloadExitTt(PayloadExitTt),
    PayloadExit2(PayloadExit2),
    PayloadExit2Tt(PayloadExit2Tt),
    PayloadMonitorPExit(PayloadMonitorPExit),
    SpawnRequest(SpawnRequest),
    SpawnRequestTt(SpawnRequestTt),
    SpawnReply(SpawnReply),
    SpawnReplyTt(SpawnReplyTt),
    UnlinkId(UnlinkId),
    UnlinkIdAck(UnlinkIdAck),
    AliasSend(AliasSend),
    AliasSendTt(AliasSendTt),

    /// Tick message used for keeping alive a connection.
    ///
    /// See also: [`net_ticktime` parameter](https://www.erlang.org/doc/man/kernel_app.html#net_ticktime)
    Tick,
}

impl Message {
    /// Makes a [`Link`] message.
    pub fn link(from_pid: Pid, to_pid: Pid) -> Self {
        Self::Link(Link { from_pid, to_pid })
    }

    /// Makes a [`Send`] message.
    pub fn send(to_pid: Pid, message: Term) -> Self {
        Self::Send(Send { to_pid, message })
    }

    /// Makes a [`Exit`] message.
    pub fn exit(from_pid: Pid, to_pid: Pid, reason: Term) -> Self {
        Self::Exit(Exit {
            from_pid,
            to_pid,
            reason,
        })
    }

    /// Makes a [`Unlink`] message.
    pub fn unlink(from_pid: Pid, to_pid: Pid) -> Self {
        Self::Unlink(Unlink { from_pid, to_pid })
    }

    /// Makes a [`NodeLink`] message.
    pub fn node_link() -> Self {
        Self::NodeLink(NodeLink)
    }

    /// Makes a [`RegSend`] message.
    pub fn reg_send(from_pid: Pid, to_name: Atom, message: Term) -> Self {
        Self::RegSend(RegSend {
            from_pid,
            to_name,
            message,
        })
    }

    /// Makes a [`GroupLeader`] message.
    pub fn group_leader(from_pid: Pid, to_pid: Pid) -> Self {
        Self::GroupLeader(GroupLeader { from_pid, to_pid })
    }

    /// Makes a [`Exit2`] message.
    pub fn exit2(from_pid: Pid, to_pid: Pid, reason: Term) -> Self {
        Self::Exit2(Exit2 {
            from_pid,
            to_pid,
            reason,
        })
    }

    /// Makes a [`SendTt`] message.
    pub fn send_tt(to_pid: Pid, message: Term, trace_token: Term) -> Self {
        Self::SendTt(SendTt {
            to_pid,
            trace_token,
            message,
        })
    }

    /// Makes a [`ExitTt`] message.
    pub fn exit_tt(from_pid: Pid, to_pid: Pid, reason: Term, trace_token: Term) -> Self {
        Self::ExitTt(ExitTt {
            from_pid,
            to_pid,
            trace_token,
            reason,
        })
    }

    /// Makes a [`RegSendTt`] message.
    pub fn reg_send_tt(from_pid: Pid, to_name: Atom, message: Term, trace_token: Term) -> Self {
        Self::RegSendTt(RegSendTt {
            from_pid,
            to_name,
            trace_token,
            message,
        })
    }

    /// Makes a [`Exit2Tt`] message.
    pub fn exit2_tt(from_pid: Pid, to_pid: Pid, reason: Term, trace_token: Term) -> Self {
        Self::Exit2Tt(Exit2Tt {
            from_pid,
            to_pid,
            trace_token,
            reason,
        })
    }

    /// Makes as [`MonitorP`] message.
    pub fn monitor_p(from_pid: Pid, to_proc: PidOrAtom, reference: Reference) -> Self {
        Self::MonitorP(MonitorP {
            from_pid,
            to_proc,
            reference,
        })
    }

    /// Makes as [`DemonitorP`] message.
    pub fn demonitor_p(from_pid: Pid, to_proc: PidOrAtom, reference: Reference) -> Self {
        Self::DemonitorP(DemonitorP {
            from_pid,
            to_proc,
            reference,
        })
    }

    /// Makes as [`MonitorPExit`] message.
    pub fn monitor_p_exit(
        from_proc: PidOrAtom,
        to_pid: Pid,
        reference: Reference,
        reason: Term,
    ) -> Self {
        Self::MonitorPExit(MonitorPExit {
            from_proc,
            to_pid,
            reference,
            reason,
        })
    }

    /// Makes a [`SendSender`] message.
    pub fn send_sender(from_pid: Pid, to_pid: Pid, message: Term) -> Self {
        Self::SendSender(SendSender {
            from_pid,
            to_pid,
            message,
        })
    }

    /// Makes a [`SendSenderTt`] message.
    pub fn send_sender_tt(from_pid: Pid, to_pid: Pid, message: Term, trace_token: Term) -> Self {
        Self::SendSenderTt(SendSenderTt {
            from_pid,
            to_pid,
            message,
            trace_token,
        })
    }

    /// Makes a [`PayloadExit`] message.
    pub fn payload_exit(from_pid: Pid, to_pid: Pid, reason: Term) -> Self {
        Self::PayloadExit(PayloadExit {
            from_pid,
            to_pid,
            reason,
        })
    }

    /// Makes a [`PayloadExitTt`] message.
    pub fn payload_exit_tt(from_pid: Pid, to_pid: Pid, reason: Term, trace_token: Term) -> Self {
        Self::PayloadExitTt(PayloadExitTt {
            from_pid,
            to_pid,
            reason,
            trace_token,
        })
    }

    /// Makes a [`PayloadExit2`] message.
    pub fn payload_exit2(from_pid: Pid, to_pid: Pid, reason: Term) -> Self {
        Self::PayloadExit2(PayloadExit2 {
            from_pid,
            to_pid,
            reason,
        })
    }

    /// Makes a [`PayloadExit2Tt`] message.
    pub fn payload_exit2_tt(from_pid: Pid, to_pid: Pid, reason: Term, trace_token: Term) -> Self {
        Self::PayloadExit2Tt(PayloadExit2Tt {
            from_pid,
            to_pid,
            reason,
            trace_token,
        })
    }

    /// Makes a [`PayloadMonitorPExit`] message.
    pub fn payload_monitor_p_exit(
        from_proc: PidOrAtom,
        to_pid: Pid,
        reference: Reference,
        reason: Term,
    ) -> Self {
        Self::PayloadMonitorPExit(PayloadMonitorPExit {
            from_proc,
            to_pid,
            reference,
            reason,
        })
    }

    /// Makes a [`SpawnRequest`] message.
    pub fn spawn_request(
        req_id: Reference,
        from_pid: Pid,
        group_leader: Pid,
        mfa: Mfa,
        opt_list: List,
        arg_list: List,
    ) -> Self {
        Self::SpawnRequest(SpawnRequest {
            req_id,
            from_pid,
            group_leader,
            mfa,
            opt_list,
            arg_list,
        })
    }

    /// Makes a [`SpawnRequestTt`] message.
    pub fn spawn_request_tt(
        req_id: Reference,
        from_pid: Pid,
        group_leader: Pid,
        mfa: Mfa,
        opt_list: List,
        arg_list: List,
        trace_token: Term,
    ) -> Self {
        Self::SpawnRequestTt(SpawnRequestTt {
            req_id,
            from_pid,
            group_leader,
            mfa,
            opt_list,
            arg_list,
            trace_token,
        })
    }

    /// Makes a [`SpawnReply`] message.
    pub fn spawn_reply(
        req_id: Reference,
        to_pid: Pid,
        flags: FixInteger,
        result: PidOrAtom,
    ) -> Self {
        Self::SpawnReply(SpawnReply {
            req_id,
            to_pid,
            flags,
            result,
        })
    }

    /// Makes a [`SpawnReplyTt`] message.
    pub fn spawn_reply_tt(
        req_id: Reference,
        to_pid: Pid,
        flags: FixInteger,
        result: PidOrAtom,
        trace_token: Term,
    ) -> Self {
        Self::SpawnReplyTt(SpawnReplyTt {
            req_id,
            to_pid,
            flags,
            result,
            trace_token,
        })
    }

    /// Makes a [`UnlinkId`] message.
    pub fn unlink_id(id: Term, from_pid: Pid, to_pid: Pid) -> Self {
        Self::UnlinkId(UnlinkId {
            id,
            from_pid,
            to_pid,
        })
    }

    /// Makes a [`UnlinkIdAck`] message.
    pub fn unlink_id_ack(id: Term, from_pid: Pid, to_pid: Pid) -> Self {
        Self::UnlinkIdAck(UnlinkIdAck {
            id,
            from_pid,
            to_pid,
        })
    }

    /// Makes a [`AliasSend`] message.
    pub fn alias_send(from_pid: Pid, alias: Reference, message: Term) -> Self {
        Self::AliasSend(AliasSend {
            from_pid,
            alias,
            message,
        })
    }

    /// Makes a [`AliasSendTt`] message.
    pub fn alias_send_tt(
        from_pid: Pid,
        alias: Reference,
        message: Term,
        trace_token: Term,
    ) -> Self {
        Self::AliasSendTt(AliasSendTt {
            from_pid,
            alias,
            message,
            trace_token,
        })
    }

    /// Serialize the [Message] into a byte buffer
    pub fn write_into<W: Write>(self, writer: &mut W) -> Result<(), crate::channel::SendError> {
        match self {
            Self::Link(x) => x.write_into(writer)?,
            Self::Send(x) => x.write_into(writer)?,
            Self::Exit(x) => x.write_into(writer)?,
            Self::Unlink(x) => x.write_into(writer)?,
            Self::NodeLink(x) => x.write_into(writer)?,
            Self::RegSend(x) => x.write_into(writer)?,
            Self::GroupLeader(x) => x.write_into(writer)?,
            Self::Exit2(x) => x.write_into(writer)?,
            Self::SendTt(x) => x.write_into(writer)?,
            Self::ExitTt(x) => x.write_into(writer)?,
            Self::RegSendTt(x) => x.write_into(writer)?,
            Self::Exit2Tt(x) => x.write_into(writer)?,
            Self::MonitorP(x) => x.write_into(writer)?,
            Self::DemonitorP(x) => x.write_into(writer)?,
            Self::MonitorPExit(x) => x.write_into(writer)?,
            Self::SendSender(x) => x.write_into(writer)?,
            Self::SendSenderTt(x) => x.write_into(writer)?,
            Self::PayloadExit(x) => x.write_into(writer)?,
            Self::PayloadExitTt(x) => x.write_into(writer)?,
            Self::PayloadExit2(x) => x.write_into(writer)?,
            Self::PayloadExit2Tt(x) => x.write_into(writer)?,
            Self::PayloadMonitorPExit(x) => x.write_into(writer)?,
            Self::SpawnRequest(x) => x.write_into(writer)?,
            Self::SpawnRequestTt(x) => x.write_into(writer)?,
            Self::SpawnReply(x) => x.write_into(writer)?,
            Self::SpawnReplyTt(x) => x.write_into(writer)?,
            Self::UnlinkId(x) => x.write_into(writer)?,
            Self::UnlinkIdAck(x) => x.write_into(writer)?,
            Self::AliasSend(x) => x.write_into(writer)?,
            Self::AliasSendTt(x) => x.write_into(writer)?,
            Self::Tick => unreachable!(),
        }
        Ok(())
    }

    /// Deserialize a given byte buffer into a [Message]. Returns [Err(_)] if the message
    /// is malformed
    pub fn read_from<R: Read>(reader: &mut R) -> Result<Self, crate::channel::RecvError> {
        let mut ctrl_msg = reader.read_tuple()?;
        if ctrl_msg.elements.is_empty() {
            return Err(DecodeError::UnexpectedType {
                value: ctrl_msg.into(),
                expected: "non empty tuple".to_owned(),
            }
            .into());
        }
        let op: FixInteger = eetf_ext::try_from_term(
            std::mem::replace(&mut ctrl_msg.elements[0], eetf_ext::nil()),
            "integer",
        )?;
        let msg = match op.value {
            Link::OP => Link::read_from(reader, ctrl_msg).map(Self::Link)?,
            Send::OP => Send::read_from(reader, ctrl_msg).map(Self::Send)?,
            Exit::OP => Exit::read_from(reader, ctrl_msg).map(Self::Exit)?,
            Unlink::OP => Unlink::read_from(reader, ctrl_msg).map(Self::Unlink)?,
            NodeLink::OP => NodeLink::read_from(reader, ctrl_msg).map(Self::NodeLink)?,
            RegSend::OP => RegSend::read_from(reader, ctrl_msg).map(Self::RegSend)?,
            GroupLeader::OP => GroupLeader::read_from(reader, ctrl_msg).map(Self::GroupLeader)?,
            Exit2::OP => Exit2::read_from(reader, ctrl_msg).map(Self::Exit2)?,
            SendTt::OP => SendTt::read_from(reader, ctrl_msg).map(Self::SendTt)?,
            ExitTt::OP => ExitTt::read_from(reader, ctrl_msg).map(Self::ExitTt)?,
            RegSendTt::OP => RegSendTt::read_from(reader, ctrl_msg).map(Self::RegSendTt)?,
            Exit2Tt::OP => Exit2Tt::read_from(reader, ctrl_msg).map(Self::Exit2Tt)?,
            MonitorP::OP => MonitorP::read_from(reader, ctrl_msg).map(Self::MonitorP)?,
            DemonitorP::OP => DemonitorP::read_from(reader, ctrl_msg).map(Self::DemonitorP)?,
            MonitorPExit::OP => {
                MonitorPExit::read_from(reader, ctrl_msg).map(Self::MonitorPExit)?
            }
            SendSender::OP => SendSender::read_from(reader, ctrl_msg).map(Self::SendSender)?,
            SendSenderTt::OP => {
                SendSenderTt::read_from(reader, ctrl_msg).map(Self::SendSenderTt)?
            }
            PayloadExit::OP => PayloadExit::read_from(reader, ctrl_msg).map(Self::PayloadExit)?,
            PayloadExitTt::OP => {
                PayloadExitTt::read_from(reader, ctrl_msg).map(Self::PayloadExitTt)?
            }
            PayloadExit2::OP => {
                PayloadExit2::read_from(reader, ctrl_msg).map(Self::PayloadExit2)?
            }
            PayloadExit2Tt::OP => {
                PayloadExit2Tt::read_from(reader, ctrl_msg).map(Self::PayloadExit2Tt)?
            }
            PayloadMonitorPExit::OP => {
                PayloadMonitorPExit::read_from(reader, ctrl_msg).map(Self::PayloadMonitorPExit)?
            }
            SpawnRequest::OP => {
                SpawnRequest::read_from(reader, ctrl_msg).map(Self::SpawnRequest)?
            }
            SpawnRequestTt::OP => {
                SpawnRequestTt::read_from(reader, ctrl_msg).map(Self::SpawnRequestTt)?
            }
            SpawnReply::OP => SpawnReply::read_from(reader, ctrl_msg).map(Self::SpawnReply)?,
            SpawnReplyTt::OP => {
                SpawnReplyTt::read_from(reader, ctrl_msg).map(Self::SpawnReplyTt)?
            }
            UnlinkId::OP => UnlinkId::read_from(reader, ctrl_msg).map(Self::UnlinkId)?,
            UnlinkIdAck::OP => UnlinkIdAck::read_from(reader, ctrl_msg).map(Self::UnlinkIdAck)?,
            AliasSend::OP => AliasSend::read_from(reader, ctrl_msg).map(Self::AliasSend)?,
            AliasSendTt::OP => AliasSendTt::read_from(reader, ctrl_msg).map(Self::AliasSendTt)?,
            op => return Err(crate::channel::RecvError::UnsupportedOp { op }),
        };
        Ok(msg)
    }
}


================================================
FILE: src/node.rs
================================================
//! Node related components.
use crate::DistributionFlags;

/// Local node information.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct LocalNode {
    /// Node name.
    pub name: NodeName,

    /// Distribution flags.
    pub flags: DistributionFlags,

    /// Incarnation identifier.
    pub creation: Creation,
}

impl LocalNode {
    /// Makes a new [`LocalNode`] instance with the default distribution flags.
    pub fn new(name: NodeName, creation: Creation) -> Self {
        Self {
            name,
            flags: Default::default(),
            creation,
        }
    }
}

/// Peer node information.
///
/// This is similar to [`LocalNode`] but the `creation` field can be `None` as older nodes may not provide that information during the handshake.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct PeerNode {
    /// Node name.
    pub name: NodeName,

    /// Distribution flags.
    pub flags: DistributionFlags,

    /// Incarnation identifier.
    pub creation: Option<Creation>,
}

/// Errors that can occur while parsing node names.
#[derive(Debug)]
#[non_exhaustive]
#[allow(missing_docs)]
pub enum NodeNameError {
    /// Node name length must be less than 256.
    TooLongName { size: usize },

    /// Name part of a node name is empty.
    EmptyName,

    /// Host part of a node name is empty.
    EmptyHost,

    /// Node name must contain an '@' character.
    MissingAtmark,
}

impl std::fmt::Display for NodeNameError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::TooLongName { size } => {
                write!(
                    f,
                    "node name length must be less than 256, but got {size} characters"
                )
            }
            Self::EmptyName => write!(f, "the name part of a node name is empty"),
            Self::EmptyHost => write!(f, "the host part of a node name is empty"),
            Self::MissingAtmark => write!(f, "node name must contain an '@' character"),
        }
    }
}

impl std::error::Error for NodeNameError {}

/// Full node name with the format "{NAME}@{HOST}".
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct NodeName {
    name: String,
    host: String,
}

impl NodeName {
    /// Makes a new [`NodeName`] instance.
    pub fn new(name: &str, host: &str) -> Result<Self, NodeNameError> {
        let size = name.len() + 1 + host.len();
        if size > 255 {
            Err(NodeNameError::TooLongName { size })
        } else if name.is_empty() {
            Err(NodeNameError::EmptyName)
        } else if host.is_empty() {
            Err(NodeNameError::EmptyHost)
        } else {
            Ok(Self {
                name: name.to_owned(),
                host: host.to_owned(),
            })
        }
    }

    /// Returns the name part.
    pub fn name(&self) -> &str {
        &self.name
    }

    /// Returns the host part.
    pub fn host(&self) -> &str {
        &self.host
    }

    /// Returns the name length.
    ///
    /// Note that the result will never be less than `3`.
    #[allow(clippy::len_without_is_empty)]
    pub fn len(&self) -> usize {
        self.name.len() + 1 + self.host.len()
    }
}

impl std::str::FromStr for NodeName {
    type Err = NodeNameError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let mut tokens = s.splitn(2, '@');
        if let (Some(name), Some(host)) = (tokens.next(), tokens.next()) {
            Self::new(name, host)
        } else {
            Err(NodeNameError::MissingAtmark)
        }
    }
}

impl std::fmt::Display for NodeName {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        write!(f, "{}@{}", self.name, self.host)
    }
}

/// Incarnation identifier of a node.
///
/// [`Creation`] is used by the node to create its pids, ports and references.
/// If the node restarts, the value of [`Creation`] will be changed.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Creation(u32);

impl Creation {
    /// Makes a new [`Creation`] instance.
    pub const fn new(n: u32) -> Self {
        Self(n)
    }

    /// Makes a new [`Creation`] instance having a random value.
    pub fn random() -> Self {
        Self(rand::random())
    }

    /// Gets the value.
    pub const fn get(self) -> u32 {
        self.0
    }
}


================================================
FILE: src/term.rs
================================================
//! Erlang terms.
pub use eetf::{
    Atom, BigInteger, Binary, BitBinary, ExternalFun, FixInteger, Float, ImproperList, InternalFun,
    List, Map, Pid, Port, Reference, Term, Tuple,
};

/// [`Pid`] or [`Atom`]
#[derive(Debug, Clone, PartialEq)]
#[allow(missing_docs)]
pub enum PidOrAtom {
    Pid(Pid),
    Atom(Atom),
}

impl From<PidOrAtom> for Term {
    fn from(v: PidOrAtom) -> Self {
        match v {
            PidOrAtom::Pid(v) => v.into(),
            PidOrAtom::Atom(v) => v.into(),
        }
    }
}

/// { [`Atom`], [`Atom`], [`FixInteger`] }
#[derive(Debug, Clone, PartialEq)]
#[allow(missing_docs)]
pub struct Mfa {
    pub module: Atom,
    pub function: Atom,
    pub arity: FixInteger,
}

impl From<Mfa> for Term {
    fn from(v: Mfa) -> Self {
        Tuple::from(vec![v.module.into(), v.function.into(), v.arity.into()]).into()
    }
}
Download .txt
gitextract_hgwcid3u/

├── .github/
│   └── workflows/
│       ├── ci.yml
│       └── release.yml
├── .gitignore
├── Cargo.toml
├── LICENSE
├── README.md
├── examples/
│   ├── epmd_cli.rs
│   ├── handshake.rs
│   ├── recv_msg.rs
│   └── send_msg.rs
└── src/
    ├── channel.rs
    ├── eetf_ext.rs
    ├── epmd.rs
    ├── flags.rs
    ├── handshake.rs
    ├── io.rs
    ├── lib.rs
    ├── message.rs
    ├── node.rs
    └── term.rs
Download .txt
SYMBOL INDEX (401 symbols across 14 files)

FILE: examples/epmd_cli.rs
  function main (line 12) | fn main() -> noargs::Result<()> {

FILE: examples/handshake.rs
  function main (line 10) | fn main() -> noargs::Result<()> {

FILE: examples/recv_msg.rs
  function main (line 18) | fn main() -> noargs::Result<()> {
  function handle_client (line 88) | async fn handle_client(

FILE: examples/send_msg.rs
  function main (line 12) | fn main() -> noargs::Result<()> {

FILE: src/channel.rs
  function channel (line 17) | pub fn channel<T>(connection: T, flags: DistributionFlags) -> (Sender<T>...
  constant TYPE_TAG (line 25) | const TYPE_TAG: u8 = 112;
  type Sender (line 29) | pub struct Sender<T> {
  function new (line 37) | fn new(connection: T) -> Self {
  function send (line 44) | pub async fn send(&mut self, message: Message) -> Result<(), SendError> {
  type Receiver (line 62) | pub struct Receiver<T> {
  function new (line 70) | fn new(connection: T) -> Self {
  function recv (line 77) | pub async fn recv(&mut self) -> Result<Message, RecvError> {
  function recv_owned (line 103) | pub async fn recv_owned(mut self) -> Result<(Message, Self), RecvError> {
  type SendError (line 113) | pub enum SendError {
    method fmt (line 122) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    method source (line 131) | fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
    method from (line 140) | fn from(value: std::io::Error) -> Self {
    method from (line 146) | fn from(value: eetf::EncodeError) -> Self {
  type RecvError (line 155) | pub enum RecvError {
    method fmt (line 173) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    method source (line 187) | fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
    method from (line 197) | fn from(value: std::io::Error) -> Self {
    method from (line 203) | fn from(value: eetf::DecodeError) -> Self {

FILE: src/eetf_ext.rs
  function nil (line 4) | pub fn nil() -> Term {
  function check_tuple_len (line 8) | pub fn check_tuple_len(tuple: &Tuple, n: usize) -> Result<(), DecodeErro...
  function try_from_tagged_tuple3 (line 19) | pub fn try_from_tagged_tuple3<T0, T1>(mut tuple: Tuple) -> Result<(T0, T...
  function try_from_tagged_tuple4 (line 31) | pub fn try_from_tagged_tuple4<T0, T1, T2>(mut tuple: Tuple) -> Result<(T...
  function try_from_tagged_tuple5 (line 45) | pub fn try_from_tagged_tuple5<T0, T1, T2, T3>(
  function try_from_tagged_tuple6 (line 63) | pub fn try_from_tagged_tuple6<T0, T1, T2, T3, T4>(
  function try_from_tagged_tuple7 (line 83) | pub fn try_from_tagged_tuple7<T0, T1, T2, T3, T4, T5>(
  type TryFromTerm (line 105) | pub trait TryFromTerm: Sized {
    method try_from_term (line 106) | fn try_from_term(term: Term) -> Result<Self, DecodeError>;
    method try_from_term (line 110) | fn try_from_term(term: Term) -> Result<Self, DecodeError> {
    method try_from_term (line 116) | fn try_from_term(term: Term) -> Result<Self, DecodeError> {
    method try_from_term (line 122) | fn try_from_term(term: Term) -> Result<Self, DecodeError> {
    method try_from_term (line 128) | fn try_from_term(term: Term) -> Result<Self, DecodeError> {
    method try_from_term (line 134) | fn try_from_term(term: Term) -> Result<Self, DecodeError> {
    method try_from_term (line 140) | fn try_from_term(term: Term) -> Result<Self, DecodeError> {
    method try_from_term (line 146) | fn try_from_term(term: Term) -> Result<Self, DecodeError> {
    method try_from_term (line 158) | fn try_from_term(term: Term) -> Result<Self, DecodeError> {
  function try_from_term (line 169) | pub fn try_from_term<T>(term: Term, expected: &str) -> Result<T, DecodeE...

FILE: src/epmd.rs
  constant DEFAULT_EPMD_PORT (line 17) | pub const DEFAULT_EPMD_PORT: u16 = 4369;
  constant TAG_DUMP_REQ (line 19) | const TAG_DUMP_REQ: u8 = 100;
  constant TAG_KILL_REQ (line 20) | const TAG_KILL_REQ: u8 = 107;
  constant TAG_NAMES_REQ (line 21) | const TAG_NAMES_REQ: u8 = 110;
  constant TAG_ALIVE2_X_RESP (line 22) | const TAG_ALIVE2_X_RESP: u8 = 118;
  constant TAG_PORT2_RESP (line 23) | const TAG_PORT2_RESP: u8 = 119;
  constant TAG_ALIVE2_REQ (line 24) | const TAG_ALIVE2_REQ: u8 = 120;
  constant TAG_ALIVE2_RESP (line 25) | const TAG_ALIVE2_RESP: u8 = 121;
  constant TAG_PORT_PLEASE2_REQ (line 26) | const TAG_PORT_PLEASE2_REQ: u8 = 122;
  type NodeEntry (line 30) | pub struct NodeEntry {
    method new (line 57) | pub fn new(name: &str, port: u16) -> Self {
    method new_hidden (line 70) | pub fn new_hidden(name: &str, port: u16) -> Self {
    method bytes_len (line 82) | fn bytes_len(&self) -> usize {
  type EpmdError (line 97) | pub enum EpmdError {
    method fmt (line 118) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    method source (line 156) | fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
    method from (line 166) | fn from(value: std::io::Error) -> Self {
  type EpmdClient (line 173) | pub struct EpmdClient<T> {
  function new (line 184) | pub fn new(connection: T) -> Self {
  function register (line 194) | pub async fn register(mut self, node: NodeEntry) -> Result<(T, Creation)...
  function get_names (line 239) | pub async fn get_names(mut self) -> Result<Vec<(String, u16)>, EpmdError> {
  function get_node (line 259) | pub async fn get_node(mut self, node_name: &str) -> Result<Option<NodeEn...
  function kill (line 304) | pub async fn kill(mut self) -> Result<String, EpmdError> {
  function dump (line 332) | pub async fn dump(mut self) -> Result<String, EpmdError> {
  type NodeNameAndPort (line 346) | struct NodeNameAndPort {
  type Err (line 352) | type Err = EpmdError;
  method from_str (line 354) | fn from_str(s: &str) -> Result<Self, Self::Err> {
  type TransportProtocol (line 372) | pub enum TransportProtocol {
    method from (line 381) | fn from(v: u8) -> Self {
  function from (line 390) | fn from(v: TransportProtocol) -> Self {
  type NodeType (line 401) | pub enum NodeType {
    method from (line 413) | fn from(v: u8) -> Self {
  function from (line 423) | fn from(v: NodeType) -> Self {
  function epmd_client_works (line 437) | fn epmd_client_works() {

FILE: src/flags.rs
  type DistributionFlags (line 3) | pub struct DistributionFlags(u64);
    constant PUBLISHED (line 7) | pub const PUBLISHED: Self = Self(0x01);
    constant ATOM_CACHE (line 10) | pub const ATOM_CACHE: Self = Self(0x02);
    constant EXTENDED_REFERENCES (line 15) | pub const EXTENDED_REFERENCES: Self = Self(0x04);
    constant DIST_MONITOR (line 20) | pub const DIST_MONITOR: Self = Self(0x08);
    constant FUN_TAGS (line 23) | pub const FUN_TAGS: Self = Self(0x10);
    constant DIST_MONITOR_NAME (line 26) | pub const DIST_MONITOR_NAME: Self = Self(0x20);
    constant HIDDEN_ATOM_CACHE (line 29) | pub const HIDDEN_ATOM_CACHE: Self = Self(0x40);
    constant NEW_FUN_TAGS (line 34) | pub const NEW_FUN_TAGS: Self = Self(0x80);
    constant EXTENDED_PIDS_PORTS (line 39) | pub const EXTENDED_PIDS_PORTS: Self = Self(0x100);
    constant EXPORT_PTR_TAG (line 44) | pub const EXPORT_PTR_TAG: Self = Self(0x200);
    constant BIT_BINARIES (line 49) | pub const BIT_BINARIES: Self = Self(0x400);
    constant NEW_FLOATS (line 54) | pub const NEW_FLOATS: Self = Self(0x800);
    constant UNICODE_IO (line 57) | pub const UNICODE_IO: Self = Self(0x1000);
    constant DIST_HDR_ATOM_CACHE (line 62) | pub const DIST_HDR_ATOM_CACHE: Self = Self(0x2000);
    constant SMALL_ATOM_TAGS (line 65) | pub const SMALL_ATOM_TAGS: Self = Self(0x4000);
    constant UTF8_ATOMS (line 70) | pub const UTF8_ATOMS: Self = Self(0x10000);
    constant MAP_TAGS (line 75) | pub const MAP_TAGS: Self = Self(0x20000);
    constant BIG_CREATION (line 80) | pub const BIG_CREATION: Self = Self(0x40000);
    constant SEND_SENDER (line 83) | pub const SEND_SENDER: Self = Self(0x80000);
    constant BIG_SEQTRACE_LABELS (line 86) | pub const BIG_SEQTRACE_LABELS: Self = Self(0x100000);
    constant EXIT_PAYLOAD (line 89) | pub const EXIT_PAYLOAD: Self = Self(0x400000);
    constant FRAGMENTS (line 92) | pub const FRAGMENTS: Self = Self(0x800000);
    constant HANDSHAKE_23 (line 95) | pub const HANDSHAKE_23: Self = Self(0x1000000);
    constant UNLINK_ID (line 102) | pub const UNLINK_ID: Self = Self(0x2000000);
    constant SPAWN (line 105) | pub const SPAWN: Self = Self(1 << 32);
    constant NAME_ME (line 111) | pub const NAME_ME: Self = Self(1 << 33);
    constant V4_NC (line 121) | pub const V4_NC: Self = Self(1 << 34);
    constant ALIAS (line 126) | pub const ALIAS: Self = Self(1 << 35);
    constant MANDATORY_25_DIGEST (line 132) | pub const MANDATORY_25_DIGEST: Self = Self(1 << 36);
    method bits (line 135) | pub const fn bits(self) -> u64 {
    method from_bits_truncate (line 140) | pub const fn from_bits_truncate(bits: u64) -> Self {
    method contains (line 145) | pub const fn contains(self, other: Self) -> bool {
    method new (line 157) | pub fn new() -> Self {
    method mandatory (line 162) | pub fn mandatory() -> Self {
    type Output (line 187) | type Output = Self;
    method bitor (line 189) | fn bitor(self, rhs: Self) -> Self {
    method bitor_assign (line 195) | fn bitor_assign(&mut self, rhs: Self) {
    type Output (line 201) | type Output = Self;
    method bitand (line 203) | fn bitand(self, rhs: Self) -> Self {
  method default (line 181) | fn default() -> Self {
  function flag_bit_values (line 213) | fn flag_bit_values() {
  function from_bits_truncate_roundtrip (line 226) | fn from_bits_truncate_roundtrip() {
  function from_bits_truncate_preserves_unknown_bits (line 233) | fn from_bits_truncate_preserves_unknown_bits() {
  function contains_single_flag (line 239) | fn contains_single_flag() {
  function contains_multiple_flags (line 247) | fn contains_multiple_flags() {
  function contains_empty_is_always_true (line 254) | fn contains_empty_is_always_true() {
  function bitor_combines_flags (line 261) | fn bitor_combines_flags() {
  function bitor_assign_combines_flags (line 271) | fn bitor_assign_combines_flags() {
  function bitand_intersects_flags (line 279) | fn bitand_intersects_flags() {
  function default_equals_mandatory (line 289) | fn default_equals_mandatory() {
  function mandatory_contains_expected_flags (line 295) | fn mandatory_contains_expected_flags() {
  function high_and_low_bits_coexist (line 320) | fn high_and_low_bits_coexist() {
  function clone_and_copy (line 333) | fn clone_and_copy() {
  function debug_format (line 342) | fn debug_format() {
  function hash_consistent (line 348) | fn hash_consistent() {

FILE: src/handshake.rs
  constant PROTOCOL_VERSION (line 13) | const PROTOCOL_VERSION: u16 = LOWEST_DISTRIBUTION_PROTOCOL_VERSION;
  constant NODE_NAME_VERSION (line 14) | const NODE_NAME_VERSION: u16 = 5;
  type ClientSideHandshake (line 18) | pub struct ClientSideHandshake<T> {
  function new (line 32) | pub fn new(connection: T, local_node: LocalNode, cookie: &str) -> Self {
  function execute_send_name (line 47) | pub async fn execute_send_name(
  function execute_rest (line 65) | pub async fn execute_rest(
  function send_name (line 99) | async fn send_name(&mut self, protocol_version: u16) -> Result<(), Hands...
  function recv_status (line 122) | async fn recv_status(&mut self) -> Result<HandshakeStatus, HandshakeErro...
  function send_status (line 180) | async fn send_status(&mut self, status: &str) -> Result<(), HandshakeErr...
  function recv_challenge (line 187) | async fn recv_challenge(&mut self) -> Result<(PeerNode, Challenge), Hand...
  function send_complement (line 229) | async fn send_complement(&mut self) -> Result<(), HandshakeError> {
  function send_challenge_reply (line 238) | async fn send_challenge_reply(
  function recv_challenge_ack (line 250) | async fn recv_challenge_ack(&mut self) -> Result<(), HandshakeError> {
  type ServerSideHandshake (line 273) | pub struct ServerSideHandshake<T> {
  function new (line 286) | pub fn new(connection: T, local_node: LocalNode, cookie: &str) -> Self {
  function execute_recv_name (line 303) | pub async fn execute_recv_name(&mut self) -> Result<Option<NodeName>, Ha...
  function execute_rest (line 357) | pub async fn execute_rest(
  function send_status (line 386) | async fn send_status(&mut self, status: HandshakeStatus) -> Result<(), H...
  function send_challenge (line 417) | async fn send_challenge(
  function recv_complement (line 440) | async fn recv_complement(&mut self) -> Result<(), HandshakeError> {
  function recv_challenge_reply (line 461) | async fn recv_challenge_reply(&mut self) -> Result<Challenge, HandshakeE...
  function send_challenge_ack (line 482) | async fn send_challenge_ack(
  type HandshakeStatus (line 496) | pub enum HandshakeStatus {
  type Challenge (line 537) | struct Challenge(u32);
    method new (line 540) | fn new() -> Self {
    method digest (line 544) | fn digest(self, cookie: &str) -> Digest {
  type Digest (line 550) | struct Digest([u8; 16]);
  type HandshakeError (line 556) | pub enum HandshakeError {
    method fmt (line 595) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    method source (line 637) | fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
    method from (line 647) | fn from(value: std::io::Error) -> Self {
    method from (line 653) | fn from(value: NodeNameError) -> Self {
  function client_side_handshake_works (line 664) | fn client_side_handshake_works() {
  function server_side_handshake_works (line 700) | fn server_side_handshake_works() {

FILE: src/io.rs
  type Connection (line 6) | pub struct Connection<T> {
  function new (line 14) | pub fn new(inner: T) -> Self {
  function into_inner (line 18) | pub fn into_inner(self) -> T {
  function handshake_message_writer (line 22) | pub fn handshake_message_writer(&mut self) -> HandshakeMessageWriter<'_,...
  function handshake_message_reader (line 30) | pub async fn handshake_message_reader<'a>(
  function write_u8 (line 40) | pub async fn write_u8(&mut self, v: u8) -> std::io::Result<()> {
  function write_u16 (line 44) | pub async fn write_u16(&mut self, v: u16) -> std::io::Result<()> {
  function write_u32 (line 48) | pub async fn write_u32(&mut self, v: u32) -> std::io::Result<()> {
  function write_all (line 52) | pub async fn write_all(&mut self, buf: &[u8]) -> std::io::Result<()> {
  function flush (line 56) | pub async fn flush(&mut self) -> std::io::Result<()> {
  function read_u8 (line 60) | pub async fn read_u8(&mut self) -> std::io::Result<u8> {
  function read_u16 (line 66) | pub async fn read_u16(&mut self) -> std::io::Result<u16> {
  function read_u32 (line 72) | pub async fn read_u32(&mut self) -> std::io::Result<u32> {
  function read_u64 (line 78) | pub async fn read_u64(&mut self) -> std::io::Result<u64> {
  function read_exact (line 84) | pub async fn read_exact(&mut self, buf: &mut [u8]) -> std::io::Result<()> {
  function read_string (line 88) | pub async fn read_string(&mut self) -> std::io::Result<String> {
  function read_stringn (line 94) | pub async fn read_stringn(&mut self, size: usize) -> std::io::Result<Str...
  function read_u16_bytes (line 105) | pub async fn read_u16_bytes(&mut self) -> std::io::Result<Vec<u8>> {
  function read_u16_string (line 111) | pub async fn read_u16_string(&mut self) -> std::io::Result<String> {
  type HandshakeMessageWriter (line 123) | pub struct HandshakeMessageWriter<'a, T> {
  function finish (line 132) | pub async fn finish(self) -> std::io::Result<()> {
  function write_u8 (line 149) | pub fn write_u8(&mut self, v: u8) -> std::io::Result<()> {
  function write_u16 (line 154) | pub fn write_u16(&mut self, v: u16) -> std::io::Result<()> {
  function write_u32 (line 159) | pub fn write_u32(&mut self, v: u32) -> std::io::Result<()> {
  function write_u64 (line 164) | pub fn write_u64(&mut self, v: u64) -> std::io::Result<()> {
  function write_all (line 169) | pub fn write_all(&mut self, bytes: &[u8]) -> std::io::Result<()> {
  type HandshakeMessageReader (line 176) | pub struct HandshakeMessageReader<'a, T> {
  function read_u8 (line 185) | pub async fn read_u8(&mut self) -> std::io::Result<u8> {
  function read_u16 (line 192) | pub async fn read_u16(&mut self) -> std::io::Result<u16> {
  function read_u32 (line 199) | pub async fn read_u32(&mut self) -> std::io::Result<u32> {
  function read_u64 (line 206) | pub async fn read_u64(&mut self) -> std::io::Result<u64> {
  function read_string (line 213) | pub async fn read_string(&mut self) -> std::io::Result<String> {
  function read_bytes (line 219) | pub async fn read_bytes(&mut self) -> std::io::Result<Vec<u8>> {
  function read_exact (line 226) | pub async fn read_exact(&mut self, buf: &mut [u8]) -> std::io::Result<()> {
  function read_u16_string (line 234) | pub async fn read_u16_string(&mut self) -> std::io::Result<String> {
  function consume_remaining_bytes (line 242) | pub async fn consume_remaining_bytes(&mut self) -> std::io::Result<()> {
  function finish (line 249) | pub async fn finish(mut self) -> std::io::Result<()> {
  type ReadTermExt (line 254) | pub trait ReadTermExt: Read {
    method read_tuple (line 255) | fn read_tuple(&mut self) -> Result<Tuple, DecodeError> {
    method read_term (line 264) | fn read_term(&mut self) -> Result<Term, DecodeError> {
  type WriteTermExt (line 271) | pub trait WriteTermExt: Write {
    method write_tagged_tuple1 (line 272) | fn write_tagged_tuple1(&mut self, tag: i32) -> Result<(), EncodeError> {
    method write_tagged_tuple3 (line 279) | fn write_tagged_tuple3<T0, T1>(
    method write_tagged_tuple4 (line 299) | fn write_tagged_tuple4<T0, T1, T2>(
    method write_tagged_tuple5 (line 322) | fn write_tagged_tuple5<T0, T1, T2, T3>(
    method write_tagged_tuple6 (line 348) | fn write_tagged_tuple6<T0, T1, T2, T3, T4>(
    method write_tagged_tuple7 (line 378) | fn write_tagged_tuple7<T0, T1, T2, T3, T4, T5>(
    method write_term (line 410) | fn write_term<T>(&mut self, term: T) -> Result<(), EncodeError>

FILE: src/lib.rs
  constant LOWEST_DISTRIBUTION_PROTOCOL_VERSION (line 90) | pub const LOWEST_DISTRIBUTION_PROTOCOL_VERSION: u16 = 6;
  constant HIGHEST_DISTRIBUTION_PROTOCOL_VERSION (line 93) | pub const HIGHEST_DISTRIBUTION_PROTOCOL_VERSION: u16 = 6;
  type BoxError (line 99) | type BoxError = Box<dyn std::error::Error + Send + Sync>;
  constant COOKIE (line 101) | pub const COOKIE: &str = "test-cookie";
  type TestErlangNode (line 104) | pub struct TestErlangNode {
    method new (line 109) | pub async fn new(name: &str) -> Result<Self, BoxError> {
  method drop (line 130) | fn drop(&mut self) {
  function try_epmd_client (line 135) | pub async fn try_epmd_client() -> Result<crate::epmd::EpmdClient<smol::n...
  function epmd_client (line 143) | pub async fn epmd_client() -> crate::epmd::EpmdClient<smol::net::TcpStre...

FILE: src/message.rs
  type DistributionMessage (line 16) | trait DistributionMessage: Sized {
    constant OP (line 17) | const OP: i32;
    method write_into (line 18) | fn write_into<W: Write>(self, writer: &mut W) -> Result<(), EncodeError>;
    method read_from (line 19) | fn read_from<R: Read>(reader: &mut R, ctrl_msg: Tuple) -> Result<Self,...
    constant OP (line 31) | const OP: i32 = 1;
    method write_into (line 33) | fn write_into<W: Write>(self, writer: &mut W) -> Result<(), EncodeErro...
    method read_from (line 38) | fn read_from<R: Read>(_reader: &mut R, ctrl_msg: Tuple) -> Result<Self...
    constant OP (line 53) | const OP: i32 = 2;
    method write_into (line 55) | fn write_into<W: Write>(self, writer: &mut W) -> Result<(), EncodeErro...
    method read_from (line 61) | fn read_from<R: Read>(reader: &mut R, ctrl_msg: Tuple) -> Result<Self,...
    constant OP (line 78) | const OP: i32 = 3;
    method write_into (line 80) | fn write_into<W: Write>(self, writer: &mut W) -> Result<(), EncodeErro...
    method read_from (line 85) | fn read_from<R: Read>(_reader: &mut R, ctrl_msg: Tuple) -> Result<Self...
    constant OP (line 107) | const OP: i32 = 4;
    method write_into (line 109) | fn write_into<W: Write>(self, writer: &mut W) -> Result<(), EncodeErro...
    method read_from (line 114) | fn read_from<R: Read>(_reader: &mut R, ctrl_msg: Tuple) -> Result<Self...
    constant OP (line 125) | const OP: i32 = 5;
    method write_into (line 127) | fn write_into<W: Write>(self, writer: &mut W) -> Result<(), EncodeErro...
    method read_from (line 132) | fn read_from<R: Read>(_reader: &mut R, ctrl_msg: Tuple) -> Result<Self...
    constant OP (line 148) | const OP: i32 = 6;
    method write_into (line 150) | fn write_into<W: Write>(self, writer: &mut W) -> Result<(), EncodeErro...
    method read_from (line 156) | fn read_from<R: Read>(reader: &mut R, ctrl_msg: Tuple) -> Result<Self,...
    constant OP (line 176) | const OP: i32 = 7;
    method write_into (line 178) | fn write_into<W: Write>(self, writer: &mut W) -> Result<(), EncodeErro...
    method read_from (line 183) | fn read_from<R: Read>(_reader: &mut R, ctrl_msg: Tuple) -> Result<Self...
    constant OP (line 199) | const OP: i32 = 8;
    method write_into (line 201) | fn write_into<W: Write>(self, writer: &mut W) -> Result<(), EncodeErro...
    method read_from (line 206) | fn read_from<R: Read>(_reader: &mut R, ctrl_msg: Tuple) -> Result<Self...
    constant OP (line 226) | const OP: i32 = 12;
    method write_into (line 228) | fn write_into<W: Write>(self, writer: &mut W) -> Result<(), EncodeErro...
    method read_from (line 234) | fn read_from<R: Read>(reader: &mut R, ctrl_msg: Tuple) -> Result<Self,...
    constant OP (line 256) | const OP: i32 = 13;
    method write_into (line 258) | fn write_into<W: Write>(self, writer: &mut W) -> Result<(), EncodeErro...
    method read_from (line 269) | fn read_from<R: Read>(_reader: &mut R, ctrl_msg: Tuple) -> Result<Self...
    constant OP (line 291) | const OP: i32 = 16;
    method write_into (line 293) | fn write_into<W: Write>(self, writer: &mut W) -> Result<(), EncodeErro...
    method read_from (line 305) | fn read_from<R: Read>(reader: &mut R, ctrl_msg: Tuple) -> Result<Self,...
    constant OP (line 329) | const OP: i32 = 18;
    method write_into (line 331) | fn write_into<W: Write>(self, writer: &mut W) -> Result<(), EncodeErro...
    method read_from (line 342) | fn read_from<R: Read>(_reader: &mut R, ctrl_msg: Tuple) -> Result<Self...
    constant OP (line 363) | const OP: i32 = 19;
    method write_into (line 365) | fn write_into<W: Write>(self, writer: &mut W) -> Result<(), EncodeErro...
    method read_from (line 370) | fn read_from<R: Read>(_reader: &mut R, ctrl_msg: Tuple) -> Result<Self...
    constant OP (line 392) | const OP: i32 = 20;
    method write_into (line 394) | fn write_into<W: Write>(self, writer: &mut W) -> Result<(), EncodeErro...
    method read_from (line 399) | fn read_from<R: Read>(_reader: &mut R, ctrl_msg: Tuple) -> Result<Self...
    constant OP (line 420) | const OP: i32 = 21;
    method write_into (line 422) | fn write_into<W: Write>(self, writer: &mut W) -> Result<(), EncodeErro...
    method read_from (line 433) | fn read_from<R: Read>(_reader: &mut R, ctrl_msg: Tuple) -> Result<Self...
    constant OP (line 454) | const OP: i32 = 22;
    method write_into (line 456) | fn write_into<W: Write>(self, writer: &mut W) -> Result<(), EncodeErro...
    method read_from (line 462) | fn read_from<R: Read>(reader: &mut R, ctrl_msg: Tuple) -> Result<Self,...
    constant OP (line 484) | const OP: i32 = 23;
    method write_into (line 486) | fn write_into<W: Write>(self, writer: &mut W) -> Result<(), EncodeErro...
    method read_from (line 492) | fn read_from<R: Read>(reader: &mut R, ctrl_msg: Tuple) -> Result<Self,...
    constant OP (line 514) | const OP: i32 = 24;
    method write_into (line 516) | fn write_into<W: Write>(self, writer: &mut W) -> Result<(), EncodeErro...
    method read_from (line 522) | fn read_from<R: Read>(reader: &mut R, ctrl_msg: Tuple) -> Result<Self,...
    constant OP (line 544) | const OP: i32 = 25;
    method write_into (line 546) | fn write_into<W: Write>(self, writer: &mut W) -> Result<(), EncodeErro...
    method read_from (line 552) | fn read_from<R: Read>(reader: &mut R, ctrl_msg: Tuple) -> Result<Self,...
    constant OP (line 574) | const OP: i32 = 26;
    method write_into (line 576) | fn write_into<W: Write>(self, writer: &mut W) -> Result<(), EncodeErro...
    method read_from (line 582) | fn read_from<R: Read>(reader: &mut R, ctrl_msg: Tuple) -> Result<Self,...
    constant OP (line 604) | const OP: i32 = 27;
    method write_into (line 606) | fn write_into<W: Write>(self, writer: &mut W) -> Result<(), EncodeErro...
    method read_from (line 612) | fn read_from<R: Read>(reader: &mut R, ctrl_msg: Tuple) -> Result<Self,...
    constant OP (line 635) | const OP: i32 = 28;
    method write_into (line 637) | fn write_into<W: Write>(self, writer: &mut W) -> Result<(), EncodeErro...
    method read_from (line 643) | fn read_from<R: Read>(reader: &mut R, ctrl_msg: Tuple) -> Result<Self,...
    constant OP (line 668) | const OP: i32 = 29;
    method write_into (line 670) | fn write_into<W: Write>(self, writer: &mut W) -> Result<(), EncodeErro...
    method read_from (line 683) | fn read_from<R: Read>(reader: &mut R, ctrl_msg: Tuple) -> Result<Self,...
    constant OP (line 712) | const OP: i32 = 30;
    method write_into (line 714) | fn write_into<W: Write>(self, writer: &mut W) -> Result<(), EncodeErro...
    method read_from (line 728) | fn read_from<R: Read>(reader: &mut R, ctrl_msg: Tuple) -> Result<Self,...
    constant OP (line 755) | const OP: i32 = 31;
    method write_into (line 757) | fn write_into<W: Write>(self, writer: &mut W) -> Result<(), EncodeErro...
    method read_from (line 762) | fn read_from<R: Read>(_reader: &mut R, ctrl_msg: Tuple) -> Result<Self...
    constant OP (line 785) | const OP: i32 = 32;
    method write_into (line 787) | fn write_into<W: Write>(self, writer: &mut W) -> Result<(), EncodeErro...
    method read_from (line 799) | fn read_from<R: Read>(_reader: &mut R, ctrl_msg: Tuple) -> Result<Self...
    constant OP (line 825) | const OP: i32 = 35;
    method write_into (line 827) | fn write_into<W: Write>(self, writer: &mut W) -> Result<(), EncodeErro...
    method read_from (line 832) | fn read_from<R: Read>(_reader: &mut R, ctrl_msg: Tuple) -> Result<Self...
    constant OP (line 854) | const OP: i32 = 36;
    method write_into (line 856) | fn write_into<W: Write>(self, writer: &mut W) -> Result<(), EncodeErro...
    method read_from (line 861) | fn read_from<R: Read>(_reader: &mut R, ctrl_msg: Tuple) -> Result<Self...
    constant OP (line 883) | const OP: i32 = 33;
    method write_into (line 885) | fn write_into<W: Write>(self, writer: &mut W) -> Result<(), EncodeErro...
    method read_from (line 891) | fn read_from<R: Read>(reader: &mut R, ctrl_msg: Tuple) -> Result<Self,...
    constant OP (line 913) | const OP: i32 = 34;
    method write_into (line 915) | fn write_into<W: Write>(self, writer: &mut W) -> Result<(), EncodeErro...
    method read_from (line 921) | fn read_from<R: Read>(reader: &mut R, ctrl_msg: Tuple) -> Result<Self,...
  type Link (line 25) | pub struct Link {
  type Send (line 47) | pub struct Send {
  type Exit (line 71) | pub struct Exit {
  type Unlink (line 101) | pub struct Unlink {
  type NodeLink (line 122) | pub struct NodeLink;
  type RegSend (line 141) | pub struct RegSend {
  type GroupLeader (line 170) | pub struct GroupLeader {
  type Exit2 (line 192) | pub struct Exit2 {
  type SendTt (line 219) | pub struct SendTt {
  type ExitTt (line 248) | pub struct ExitTt {
  type RegSendTt (line 283) | pub struct RegSendTt {
  type Exit2Tt (line 321) | pub struct Exit2Tt {
  type MonitorP (line 356) | pub struct MonitorP {
  type DemonitorP (line 385) | pub struct DemonitorP {
  type MonitorPExit (line 412) | pub struct MonitorPExit {
  type SendSender (line 447) | pub struct SendSender {
  type SendSenderTt (line 476) | pub struct SendSenderTt {
  type PayloadExit (line 507) | pub struct PayloadExit {
  type PayloadExitTt (line 536) | pub struct PayloadExitTt {
  type PayloadExit2 (line 567) | pub struct PayloadExit2 {
  type PayloadExit2Tt (line 596) | pub struct PayloadExit2Tt {
  type PayloadMonitorPExit (line 627) | pub struct PayloadMonitorPExit {
  type SpawnRequest (line 658) | pub struct SpawnRequest {
  type SpawnRequestTt (line 701) | pub struct SpawnRequestTt {
  type SpawnReply (line 747) | pub struct SpawnReply {
  type SpawnReplyTt (line 776) | pub struct SpawnReplyTt {
  type UnlinkId (line 818) | pub struct UnlinkId {
  type UnlinkIdAck (line 847) | pub struct UnlinkIdAck {
  type AliasSend (line 876) | pub struct AliasSend {
  type AliasSendTt (line 905) | pub struct AliasSendTt {
  type Message (line 936) | pub enum Message {
    method link (line 976) | pub fn link(from_pid: Pid, to_pid: Pid) -> Self {
    method send (line 981) | pub fn send(to_pid: Pid, message: Term) -> Self {
    method exit (line 986) | pub fn exit(from_pid: Pid, to_pid: Pid, reason: Term) -> Self {
    method unlink (line 995) | pub fn unlink(from_pid: Pid, to_pid: Pid) -> Self {
    method node_link (line 1000) | pub fn node_link() -> Self {
    method reg_send (line 1005) | pub fn reg_send(from_pid: Pid, to_name: Atom, message: Term) -> Self {
    method group_leader (line 1014) | pub fn group_leader(from_pid: Pid, to_pid: Pid) -> Self {
    method exit2 (line 1019) | pub fn exit2(from_pid: Pid, to_pid: Pid, reason: Term) -> Self {
    method send_tt (line 1028) | pub fn send_tt(to_pid: Pid, message: Term, trace_token: Term) -> Self {
    method exit_tt (line 1037) | pub fn exit_tt(from_pid: Pid, to_pid: Pid, reason: Term, trace_token: ...
    method reg_send_tt (line 1047) | pub fn reg_send_tt(from_pid: Pid, to_name: Atom, message: Term, trace_...
    method exit2_tt (line 1057) | pub fn exit2_tt(from_pid: Pid, to_pid: Pid, reason: Term, trace_token:...
    method monitor_p (line 1067) | pub fn monitor_p(from_pid: Pid, to_proc: PidOrAtom, reference: Referen...
    method demonitor_p (line 1076) | pub fn demonitor_p(from_pid: Pid, to_proc: PidOrAtom, reference: Refer...
    method monitor_p_exit (line 1085) | pub fn monitor_p_exit(
    method send_sender (line 1100) | pub fn send_sender(from_pid: Pid, to_pid: Pid, message: Term) -> Self {
    method send_sender_tt (line 1109) | pub fn send_sender_tt(from_pid: Pid, to_pid: Pid, message: Term, trace...
    method payload_exit (line 1119) | pub fn payload_exit(from_pid: Pid, to_pid: Pid, reason: Term) -> Self {
    method payload_exit_tt (line 1128) | pub fn payload_exit_tt(from_pid: Pid, to_pid: Pid, reason: Term, trace...
    method payload_exit2 (line 1138) | pub fn payload_exit2(from_pid: Pid, to_pid: Pid, reason: Term) -> Self {
    method payload_exit2_tt (line 1147) | pub fn payload_exit2_tt(from_pid: Pid, to_pid: Pid, reason: Term, trac...
    method payload_monitor_p_exit (line 1157) | pub fn payload_monitor_p_exit(
    method spawn_request (line 1172) | pub fn spawn_request(
    method spawn_request_tt (line 1191) | pub fn spawn_request_tt(
    method spawn_reply (line 1212) | pub fn spawn_reply(
    method spawn_reply_tt (line 1227) | pub fn spawn_reply_tt(
    method unlink_id (line 1244) | pub fn unlink_id(id: Term, from_pid: Pid, to_pid: Pid) -> Self {
    method unlink_id_ack (line 1253) | pub fn unlink_id_ack(id: Term, from_pid: Pid, to_pid: Pid) -> Self {
    method alias_send (line 1262) | pub fn alias_send(from_pid: Pid, alias: Reference, message: Term) -> S...
    method alias_send_tt (line 1271) | pub fn alias_send_tt(
    method write_into (line 1286) | pub fn write_into<W: Write>(self, writer: &mut W) -> Result<(), crate:...
    method read_from (line 1325) | pub fn read_from<R: Read>(reader: &mut R) -> Result<Self, crate::chann...

FILE: src/node.rs
  type LocalNode (line 6) | pub struct LocalNode {
    method new (line 19) | pub fn new(name: NodeName, creation: Creation) -> Self {
  type PeerNode (line 32) | pub struct PeerNode {
  type NodeNameError (line 47) | pub enum NodeNameError {
    method fmt (line 62) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  type NodeName (line 81) | pub struct NodeName {
    method new (line 88) | pub fn new(name: &str, host: &str) -> Result<Self, NodeNameError> {
    method name (line 105) | pub fn name(&self) -> &str {
    method host (line 110) | pub fn host(&self) -> &str {
    method len (line 118) | pub fn len(&self) -> usize {
    type Err (line 124) | type Err = NodeNameError;
    method from_str (line 126) | fn from_str(s: &str) -> Result<Self, Self::Err> {
    method fmt (line 137) | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
  type Creation (line 147) | pub struct Creation(u32);
    method new (line 151) | pub const fn new(n: u32) -> Self {
    method random (line 156) | pub fn random() -> Self {
    method get (line 161) | pub const fn get(self) -> u32 {

FILE: src/term.rs
  type PidOrAtom (line 10) | pub enum PidOrAtom {
  method from (line 16) | fn from(v: PidOrAtom) -> Self {
  type Mfa (line 27) | pub struct Mfa {
  method from (line 34) | fn from(v: Mfa) -> Self {
Condensed preview — 20 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (160K chars).
[
  {
    "path": ".github/workflows/ci.yml",
    "chars": 1043,
    "preview": "name: CI\n\non: [push]\n\njobs:\n  check:\n    name: Check\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        tool"
  },
  {
    "path": ".github/workflows/release.yml",
    "chars": 755,
    "preview": "name: Create GitHub Release and Publish to crates.io\n\non:\n  push:\n    tags: ['v*']\n\njobs:\n  github-release:\n    runs-on:"
  },
  {
    "path": ".gitignore",
    "chars": 18,
    "preview": "target\nCargo.lock\n"
  },
  {
    "path": "Cargo.toml",
    "chars": 466,
    "preview": "[package]\nname = \"erl_dist\"\nversion = \"0.8.0\"\nauthors = [\"Takeru Ohta <phjgt308@gmail.com>\"]\ndescription = \"Rust Impleme"
  },
  {
    "path": "LICENSE",
    "chars": 1093,
    "preview": "The MIT License\n\nCopyright (c) 2016 Takeru Ohta <phjgt308@gmail.com>\n\nPermission is hereby granted, free of charge, to a"
  },
  {
    "path": "README.md",
    "chars": 2652,
    "preview": "erl_dist\n========\n\n[![erl_dist](https://img.shields.io/crates/v/erl_dist.svg)](https://crates.io/crates/erl_dist)\n[![Doc"
  },
  {
    "path": "examples/epmd_cli.rs",
    "chars": 6965,
    "preview": "//! EPMD client example.\n//!\n//! # Usage Examples\n//!\n//! ```bash\n//! $ cargo run --example epmd_cli -- --help\n//! $ car"
  },
  {
    "path": "examples/handshake.rs",
    "chars": 2878,
    "preview": "//! Client side handshake example.\n//!\n//! # Usage Examples\n//!\n//! ```bash\n//! $ cargo run --example handshake -- --hel"
  },
  {
    "path": "examples/recv_msg.rs",
    "chars": 4833,
    "preview": "//! Server Node Example.\n//!\n//! The node registers specified name to the EPMD and waits messages from other (connected)"
  },
  {
    "path": "examples/send_msg.rs",
    "chars": 3214,
    "preview": "//! Client Node Example.\n//!\n//! The node sends a message (atom) to the specified erlang node.\n//!\n//! # Usage Examples\n"
  },
  {
    "path": "src/channel.rs",
    "chars": 5612,
    "preview": "use crate::DistributionFlags;\n#[cfg(doc)]\nuse crate::handshake;\nuse crate::io::Connection;\nuse crate::message::Message;\n"
  },
  {
    "path": "src/eetf_ext.rs",
    "chars": 5369,
    "preview": "use crate::term::{Mfa, PidOrAtom};\nuse eetf::{Atom, DecodeError, FixInteger, List, Pid, Reference, Term, Tuple};\n\npub fn"
  },
  {
    "path": "src/epmd.rs",
    "chars": 15007,
    "preview": "//! EPMD client and other EPMD related components.\n//!\n//! \"EPMD\" stands for \"Erlang Port Mapper Daemon\" and\n//! it prov"
  },
  {
    "path": "src/flags.rs",
    "chars": 12769,
    "preview": "/// Distribution flags.\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]\npub struct DistributionFlags(u64);\n\nimpl Dist"
  },
  {
    "path": "src/handshake.rs",
    "chars": 27510,
    "preview": "//! Distribution Handshake.\n//!\n//! This handshake is used by an Erlang node for connecting to another one.\n//!\n//! See\n"
  },
  {
    "path": "src/io.rs",
    "chars": 11613,
    "preview": "use eetf::{DecodeError, EncodeError, FixInteger, Term, Tuple};\nuse futures::io::{AsyncRead, AsyncReadExt as _, AsyncWrit"
  },
  {
    "path": "src/lib.rs",
    "chars": 4785,
    "preview": "//! Rust Implementation of Erlang Distribution Protocol.\n//!\n//! Distribution protocol is used to communicate with distr"
  },
  {
    "path": "src/message.rs",
    "chars": 41897,
    "preview": "//! Messages passed between distributed nodes, and channels for those messages.\n//!\n//! Reference: [Protocol between Con"
  },
  {
    "path": "src/node.rs",
    "chars": 4363,
    "preview": "//! Node related components.\nuse crate::DistributionFlags;\n\n/// Local node information.\n#[derive(Debug, Clone, PartialEq"
  },
  {
    "path": "src/term.rs",
    "chars": 859,
    "preview": "//! Erlang terms.\npub use eetf::{\n    Atom, BigInteger, Binary, BitBinary, ExternalFun, FixInteger, Float, ImproperList,"
  }
]

About this extraction

This page contains the full source code of the sile/erl_dist GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 20 files (150.1 KB), approximately 38.0k tokens, and a symbol index with 401 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!