netcode-official 1.0.0

The official Rust implementation of the netcode network protocol
Documentation
//! Wire compatibility lock against the reference C implementation.
//!
//! The `tests/vectors/*.bin` files are golden test vectors generated by the reference
//! C implementation (https://github.com/mas-bandwidth/netcode) from fixed inputs.
//! These tests assert that, given the same inputs, this implementation produces
//! byte-identical output — and that it reads the C-produced bytes back to the same
//! values. Together they pin the token layouts, packet framing, sequence encoding,
//! endianness, AEAD construction (cipher choice, nonce layout, associated data) to
//! the reference implementation.
//!
//! If one of these tests fails, the change breaks interoperability with every other
//! netcode implementation. Do not update the vectors to match new output unless the
//! netcode standard itself has changed.
//!
//! The vectors are produced by a small C program that drives the reference
//! implementation's writers with the fixed inputs below; regenerate them only against
//! an upstream netcode release.

use std::net::SocketAddr;

use crate::packet::{self, AllowedPackets, Packet};
use crate::token::{
    self, CHALLENGE_TOKEN_BYTES, CONNECT_TOKEN_NONCE_BYTES, CONNECT_TOKEN_PRIVATE_BYTES,
    ChallengeToken, ConnectToken, PrivateConnectToken,
};
use crate::{CONNECT_TOKEN_BYTES, KEY_BYTES, Key, MAX_PACKET_BYTES, USER_DATA_BYTES, UserData};

const VECTOR_PROTOCOL_ID: u64 = 0x1122334455667788;
const VECTOR_CLIENT_ID: u64 = 0x0102030405060708;
const VECTOR_TIMEOUT_SECONDS: i32 = 15;
const VECTOR_CREATE_TIMESTAMP: u64 = 1720000000;
const VECTOR_EXPIRE_TIMESTAMP: u64 = 1720000100;
const VECTOR_CHALLENGE_SEQUENCE: u64 = 1000;
const VECTOR_PACKET_SEQUENCE: u64 = 0x1122334455;

const VECTOR_PRIVATE_KEY: Key = [
    0x60, 0x6a, 0xbe, 0x6e, 0xc9, 0x19, 0x10, 0xea, 0x9a, 0x65, 0x62, 0xf6, 0x6f, 0x2b, 0x30, 0xe4,
    0x43, 0x71, 0xd6, 0x2c, 0xd1, 0x99, 0x27, 0x26, 0x6b, 0x3c, 0x60, 0xf4, 0xb7, 0x15, 0xab, 0xa1,
];

const PRIVATE_TOKEN_PLAINTEXT: &[u8; CONNECT_TOKEN_PRIVATE_BYTES] =
    include_bytes!("../tests/vectors/private_connect_token_plaintext.bin");
const PRIVATE_TOKEN_ENCRYPTED: &[u8; CONNECT_TOKEN_PRIVATE_BYTES] =
    include_bytes!("../tests/vectors/private_connect_token_encrypted.bin");
const CONNECT_TOKEN: &[u8; CONNECT_TOKEN_BYTES] =
    include_bytes!("../tests/vectors/connect_token.bin");
const CHALLENGE_TOKEN_ENCRYPTED: &[u8; CHALLENGE_TOKEN_BYTES] =
    include_bytes!("../tests/vectors/challenge_token_encrypted.bin");
const PACKET_REQUEST: &[u8] = include_bytes!("../tests/vectors/packet_request.bin");
const PACKET_DENIED: &[u8] = include_bytes!("../tests/vectors/packet_denied.bin");
const PACKET_CHALLENGE: &[u8] = include_bytes!("../tests/vectors/packet_challenge.bin");
const PACKET_RESPONSE: &[u8] = include_bytes!("../tests/vectors/packet_response.bin");
const PACKET_KEEP_ALIVE: &[u8] = include_bytes!("../tests/vectors/packet_keep_alive.bin");
const PACKET_PAYLOAD: &[u8] = include_bytes!("../tests/vectors/packet_payload.bin");
const PACKET_DISCONNECT: &[u8] = include_bytes!("../tests/vectors/packet_disconnect.bin");

fn client_to_server_key() -> Key {
    std::array::from_fn(|i| i as u8)
}

fn server_to_client_key() -> Key {
    std::array::from_fn(|i| 255 - i as u8)
}

fn challenge_key() -> Key {
    std::array::from_fn(|i| (i * 2) as u8)
}

fn packet_key() -> Key {
    std::array::from_fn(|i| (i + 17) as u8)
}

fn user_data() -> UserData {
    std::array::from_fn(|i| i as u8)
}

fn connect_token_nonce() -> [u8; CONNECT_TOKEN_NONCE_BYTES] {
    std::array::from_fn(|i| (100 + i) as u8)
}

fn server_addresses() -> Vec<SocketAddr> {
    vec!["127.0.0.1:40000".parse().unwrap(), "[::1]:50000".parse().unwrap()]
}

fn private_token() -> PrivateConnectToken {
    PrivateConnectToken {
        client_id: VECTOR_CLIENT_ID,
        timeout_seconds: VECTOR_TIMEOUT_SECONDS,
        server_addresses: server_addresses(),
        client_to_server_key: client_to_server_key(),
        server_to_client_key: server_to_client_key(),
        user_data: user_data(),
    }
}

#[test]
fn private_connect_token_write_matches_c() {
    let mut buffer = [0u8; CONNECT_TOKEN_PRIVATE_BYTES];
    private_token().write(&mut buffer);
    assert_eq!(&buffer, PRIVATE_TOKEN_PLAINTEXT);
}

#[test]
fn private_connect_token_encrypt_matches_c() {
    let mut buffer = *PRIVATE_TOKEN_PLAINTEXT;
    token::encrypt_connect_token_private(
        &mut buffer,
        VECTOR_PROTOCOL_ID,
        VECTOR_EXPIRE_TIMESTAMP,
        &connect_token_nonce(),
        &VECTOR_PRIVATE_KEY,
    )
    .unwrap();
    assert_eq!(&buffer, PRIVATE_TOKEN_ENCRYPTED);
}

#[test]
fn private_connect_token_reads_c_vector() {
    let mut buffer = *PRIVATE_TOKEN_ENCRYPTED;
    token::decrypt_connect_token_private(
        &mut buffer,
        VECTOR_PROTOCOL_ID,
        VECTOR_EXPIRE_TIMESTAMP,
        &connect_token_nonce(),
        &VECTOR_PRIVATE_KEY,
    )
    .unwrap();

    let output = PrivateConnectToken::read(&buffer[..]).unwrap();
    let expected = private_token();
    assert_eq!(output.client_id, expected.client_id);
    assert_eq!(output.timeout_seconds, expected.timeout_seconds);
    assert_eq!(output.server_addresses, expected.server_addresses);
    assert_eq!(output.client_to_server_key, expected.client_to_server_key);
    assert_eq!(output.server_to_client_key, expected.server_to_client_key);
    assert_eq!(output.user_data, expected.user_data);
}

fn connect_token() -> ConnectToken {
    ConnectToken {
        protocol_id: VECTOR_PROTOCOL_ID,
        create_timestamp: VECTOR_CREATE_TIMESTAMP,
        expire_timestamp: VECTOR_EXPIRE_TIMESTAMP,
        nonce: connect_token_nonce(),
        private_data: Box::new(*PRIVATE_TOKEN_ENCRYPTED),
        timeout_seconds: VECTOR_TIMEOUT_SECONDS,
        server_addresses: server_addresses(),
        client_to_server_key: client_to_server_key(),
        server_to_client_key: server_to_client_key(),
    }
}

#[test]
fn connect_token_write_matches_c() {
    let mut buffer = [0u8; CONNECT_TOKEN_BYTES];
    connect_token().write(&mut buffer);
    assert_eq!(&buffer, CONNECT_TOKEN);
}

#[test]
fn connect_token_reads_c_vector() {
    let output = ConnectToken::read(CONNECT_TOKEN).unwrap();
    let expected = connect_token();
    assert_eq!(output.protocol_id, expected.protocol_id);
    assert_eq!(output.create_timestamp, expected.create_timestamp);
    assert_eq!(output.expire_timestamp, expected.expire_timestamp);
    assert_eq!(output.nonce, expected.nonce);
    assert_eq!(output.private_data, expected.private_data);
    assert_eq!(output.timeout_seconds, expected.timeout_seconds);
    assert_eq!(output.server_addresses, expected.server_addresses);
    assert_eq!(output.client_to_server_key, expected.client_to_server_key);
    assert_eq!(output.server_to_client_key, expected.server_to_client_key);
}

#[test]
fn challenge_token_matches_c() {
    let challenge_token = ChallengeToken { client_id: VECTOR_CLIENT_ID, user_data: user_data() };

    let mut buffer = [0u8; CHALLENGE_TOKEN_BYTES];
    challenge_token.write(&mut buffer);
    token::encrypt_challenge_token(&mut buffer, VECTOR_CHALLENGE_SEQUENCE, &challenge_key())
        .unwrap();
    assert_eq!(&buffer, CHALLENGE_TOKEN_ENCRYPTED);

    let mut buffer = *CHALLENGE_TOKEN_ENCRYPTED;
    token::decrypt_challenge_token(&mut buffer, VECTOR_CHALLENGE_SEQUENCE, &challenge_key())
        .unwrap();
    let output = ChallengeToken::read(&buffer);
    assert_eq!(output.client_id, VECTOR_CLIENT_ID);
    assert_eq!(output.user_data, user_data());
}

/// Writes the packet and asserts byte equality with the C vector, then reads the C
/// vector back and returns the parsed packet for per-type field checks.
fn packet_round_trip_c(packet: &Packet, vector: &[u8], allowed_packets: AllowedPackets) -> Packet {
    let mut buffer = [0u8; MAX_PACKET_BYTES];
    let written = packet::write_packet(
        packet,
        &mut buffer,
        VECTOR_PACKET_SEQUENCE,
        &packet_key(),
        VECTOR_PROTOCOL_ID,
    )
    .unwrap();
    assert_eq!(&buffer[..written], vector, "written packet bytes differ from the C vector");

    let mut vector_copy = vector.to_vec();
    let (output, sequence) = packet::read_packet(
        &mut vector_copy,
        Some(&packet_key()),
        VECTOR_PROTOCOL_ID,
        VECTOR_CREATE_TIMESTAMP,
        Some(&VECTOR_PRIVATE_KEY),
        allowed_packets,
        None,
    )
    .expect("the C packet vector failed to read");

    if !matches!(output, Packet::Request { .. }) {
        assert_eq!(sequence, VECTOR_PACKET_SEQUENCE);
    }

    output
}

#[test]
fn connection_request_packet_matches_c() {
    let packet = Packet::Request {
        protocol_id: VECTOR_PROTOCOL_ID,
        expire_timestamp: VECTOR_EXPIRE_TIMESTAMP,
        nonce: connect_token_nonce(),
        private_data: Box::new(*PRIVATE_TOKEN_ENCRYPTED),
    };
    let output = packet_round_trip_c(&packet, PACKET_REQUEST, AllowedPackets::SERVER);
    match output {
        Packet::Request { protocol_id, expire_timestamp, nonce, private_data } => {
            assert_eq!(protocol_id, VECTOR_PROTOCOL_ID);
            assert_eq!(expire_timestamp, VECTOR_EXPIRE_TIMESTAMP);
            assert_eq!(nonce, connect_token_nonce());
            // the private data comes back decrypted, with the original HMAC in the
            // trailing 16 bytes
            assert_eq!(private_data[..1008], PRIVATE_TOKEN_PLAINTEXT[..1008]);
            assert_eq!(private_data[1008..], PRIVATE_TOKEN_ENCRYPTED[1008..]);
        }
        _ => panic!("wrong packet type"),
    }
}

#[test]
fn connection_denied_packet_matches_c() {
    let output = packet_round_trip_c(&Packet::Denied, PACKET_DENIED, AllowedPackets::CLIENT);
    assert!(matches!(output, Packet::Denied));
}

#[test]
fn connection_challenge_packet_matches_c() {
    let packet = Packet::Challenge {
        challenge_token_sequence: VECTOR_CHALLENGE_SEQUENCE,
        challenge_token_data: *CHALLENGE_TOKEN_ENCRYPTED,
    };
    let output = packet_round_trip_c(&packet, PACKET_CHALLENGE, AllowedPackets::CLIENT);
    match output {
        Packet::Challenge { challenge_token_sequence, challenge_token_data } => {
            assert_eq!(challenge_token_sequence, VECTOR_CHALLENGE_SEQUENCE);
            assert_eq!(&challenge_token_data, CHALLENGE_TOKEN_ENCRYPTED);
        }
        _ => panic!("wrong packet type"),
    }
}

#[test]
fn connection_response_packet_matches_c() {
    let packet = Packet::Response {
        challenge_token_sequence: VECTOR_CHALLENGE_SEQUENCE,
        challenge_token_data: *CHALLENGE_TOKEN_ENCRYPTED,
    };
    let output = packet_round_trip_c(&packet, PACKET_RESPONSE, AllowedPackets::SERVER);
    match output {
        Packet::Response { challenge_token_sequence, challenge_token_data } => {
            assert_eq!(challenge_token_sequence, VECTOR_CHALLENGE_SEQUENCE);
            assert_eq!(&challenge_token_data, CHALLENGE_TOKEN_ENCRYPTED);
        }
        _ => panic!("wrong packet type"),
    }
}

#[test]
fn connection_keep_alive_packet_matches_c() {
    let packet = Packet::KeepAlive { client_index: 7, max_clients: 32 };
    let output = packet_round_trip_c(&packet, PACKET_KEEP_ALIVE, AllowedPackets::CLIENT);
    match output {
        Packet::KeepAlive { client_index, max_clients } => {
            assert_eq!(client_index, 7);
            assert_eq!(max_clients, 32);
        }
        _ => panic!("wrong packet type"),
    }
}

#[test]
fn connection_payload_packet_matches_c() {
    let payload: Vec<u8> = (0..crate::MAX_PAYLOAD_BYTES).map(|i| i as u8).collect();
    let packet = Packet::Payload(payload.clone());
    let output = packet_round_trip_c(&packet, PACKET_PAYLOAD, AllowedPackets::CLIENT);
    match output {
        Packet::Payload(data) => assert_eq!(data, payload),
        _ => panic!("wrong packet type"),
    }
}

#[test]
fn connection_disconnect_packet_matches_c() {
    let output =
        packet_round_trip_c(&Packet::Disconnect, PACKET_DISCONNECT, AllowedPackets::CLIENT);
    assert!(matches!(output, Packet::Disconnect));
}

#[test]
fn version_info_matches_c() {
    assert_eq!(&crate::VERSION_INFO, b"NETCODE 1.02\0");
    assert_eq!(KEY_BYTES, 32);
    assert_eq!(USER_DATA_BYTES, 256);
    assert_eq!(CONNECT_TOKEN_BYTES, 2048);
    assert_eq!(CONNECT_TOKEN_PRIVATE_BYTES, 1024);
    assert_eq!(CHALLENGE_TOKEN_BYTES, 300);
    assert_eq!(CONNECT_TOKEN_NONCE_BYTES, 24);
}