enlace 0.2.3

Encrypted mailbox and latest-value slot fan-out.
Documentation
use std::sync::Arc;

use crate::config::Config;
use crate::coordinator::Coordinator;
use crate::crypto;
use crate::kdf::{ChannelKind, channel_aead_key};
use crate::state::State;
use crate::transports::{
    HealthTracker, decode_empty_response, decode_mailbox_recv_response, decode_slot_get_response,
};

use reqwest::StatusCode;
use reqwest::header::{HeaderMap, HeaderName, HeaderValue};

const SEED: [u8; 32] = [0xA5; 32];
const NAME: &str = "fuzz";
const MAX_PLAINTEXT_BYTES: usize = 65_536;

pub fn open_mailbox(data: &[u8]) {
    let config = Config {
        dedup_buffer: 0,
        max_plaintext_bytes: MAX_PLAINTEXT_BYTES,
        ..Config::default()
    };
    let coordinator = Coordinator::new(
        &SEED,
        Vec::new(),
        &config,
        State::memory().store(),
        Arc::new(HealthTracker::from_config(&config)),
    );
    let Some((&mode, bytes)) = data.split_first() else {
        coordinator.open_for_fuzz(ChannelKind::Mailbox, NAME, data);
        return;
    };

    if mode & 1 == 0 {
        coordinator.open_for_fuzz(ChannelKind::Mailbox, NAME, bytes);
        return;
    }

    let Ok(key) = channel_aead_key(&SEED, ChannelKind::Mailbox, NAME) else {
        return;
    };
    let sealed = crypto::seal(&key, &mailbox_aad(), bytes);
    coordinator.open_for_fuzz(ChannelKind::Mailbox, NAME, &sealed);
}

pub fn http_relay_client_response(data: &[u8]) {
    let Some((operation, status, headers, body)) =
        parse_http_response(data).or_else(|| parse_compact_response(data))
    else {
        return;
    };

    match operation % 4 {
        0 | 1 => {
            let _ = decode_empty_response(status);
        }
        2 => {
            let _ = decode_mailbox_recv_response(status, body);
        }
        _ => {
            let _ = decode_slot_get_response(status, &headers, body);
        }
    }
}

fn mailbox_aad() -> Vec<u8> {
    let mut aad = Vec::with_capacity(b"enlace/v1/aead/mailbox/".len() + NAME.len());
    aad.extend_from_slice(b"enlace/v1/aead/mailbox/");
    aad.extend_from_slice(NAME.as_bytes());
    aad
}

fn parse_http_response(data: &[u8]) -> Option<(u8, StatusCode, HeaderMap, Vec<u8>)> {
    let (&operation, response) = data.split_first()?;
    let split_at = response
        .windows(4)
        .position(|window| window == b"\r\n\r\n")?;
    let (head, body) = response.split_at(split_at);
    let body = body.get(4..)?.to_vec();

    let mut lines = head.split(|byte| *byte == b'\n');
    let status = parse_status_line(lines.next()?)?;
    let mut headers = HeaderMap::new();

    for line in lines.take(32) {
        let line = line.strip_suffix(b"\r").unwrap_or(line);
        let Some(colon) = line.iter().position(|byte| *byte == b':') else {
            continue;
        };
        let (name, value) = line.split_at(colon);
        let value = trim_ascii(value.get(1..)?);
        let (Ok(name), Ok(value)) = (HeaderName::from_bytes(name), HeaderValue::from_bytes(value))
        else {
            continue;
        };
        headers.append(name, value);
    }

    Some((operation, status, headers, body))
}

fn parse_compact_response(data: &[u8]) -> Option<(u8, StatusCode, HeaderMap, Vec<u8>)> {
    let (&operation, rest) = data.split_first()?;
    let status_bytes: [u8; 2] = rest.get(..2)?.try_into().ok()?;
    let code = 100 + (u16::from_be_bytes(status_bytes) % 500);
    let status = StatusCode::from_u16(code).ok()?;
    let rest = rest.get(2..)?;
    let mut headers = HeaderMap::new();

    let body = if let Some((&version_len, rest)) = rest.split_first() {
        let version_len = usize::from(version_len).min(rest.len());
        let (version, body) = rest.split_at(version_len);
        if let Ok(value) = HeaderValue::from_bytes(version) {
            headers.insert(HeaderName::from_static("x-enlace-version"), value);
        }
        body.to_vec()
    } else {
        Vec::new()
    };

    Some((operation, status, headers, body))
}

fn parse_status_line(line: &[u8]) -> Option<StatusCode> {
    let line = line.strip_suffix(b"\r").unwrap_or(line);
    let mut parts = line
        .split(u8::is_ascii_whitespace)
        .filter(|part| !part.is_empty());
    let _version = parts.next()?;
    let code = std::str::from_utf8(parts.next()?).ok()?.parse().ok()?;
    StatusCode::from_u16(code).ok()
}

fn trim_ascii(mut value: &[u8]) -> &[u8] {
    while let Some((&first, rest)) = value.split_first() {
        if !first.is_ascii_whitespace() {
            break;
        }
        value = rest;
    }
    while let Some((&last, rest)) = value.split_last() {
        if !last.is_ascii_whitespace() {
            break;
        }
        value = rest;
    }
    value
}