celerity 0.1.0

Pure Rust sans-IO ZMTP 3.1 messaging core with Tokio TCP and Unix socket transports.
Documentation
use bytes::{Buf, Bytes};

use crate::wire::decode_metadata;
use crate::{CipherSuite, MetadataMap, ProtocolError};

#[derive(Debug)]
pub(super) struct Hello {
    pub(super) version: u8,
    pub(super) cipher_suite: CipherSuite,
    pub(super) client_eph_public: [u8; 32],
    pub(super) server_key_hash: [u8; 32],
    pub(super) client_nonce_seed: [u8; 8],
}

pub(super) fn parse_hello(mut payload: Bytes) -> Result<Hello, ProtocolError> {
    if payload.len() != 74 {
        return Err(ProtocolError::CurveHandshake("invalid HELLO payload"));
    }

    let version = payload.get_u8();
    let cipher_suite = CipherSuite::from_id(payload.get_u8())?;
    let client_eph_public = payload
        .split_to(32)
        .as_ref()
        .try_into()
        .expect("length already checked");
    let server_key_hash = payload
        .split_to(32)
        .as_ref()
        .try_into()
        .expect("length already checked");
    let client_nonce_seed = payload
        .split_to(8)
        .as_ref()
        .try_into()
        .expect("length already checked");

    Ok(Hello {
        version,
        cipher_suite,
        client_eph_public,
        server_key_hash,
        client_nonce_seed,
    })
}

#[derive(Debug)]
pub(super) struct WelcomeFrame {
    pub(super) cipher_suite: CipherSuite,
    pub(super) server_eph_public: [u8; 32],
    pub(super) ciphertext: Bytes,
}

pub(super) fn parse_welcome(mut payload: Bytes) -> Result<WelcomeFrame, ProtocolError> {
    if payload.len() < 33 + 16 {
        return Err(ProtocolError::CurveHandshake("invalid WELCOME payload"));
    }

    let cipher_suite = CipherSuite::from_id(payload.get_u8())?;
    let server_eph_public = payload
        .split_to(32)
        .as_ref()
        .try_into()
        .expect("length already checked");
    Ok(WelcomeFrame {
        cipher_suite,
        server_eph_public,
        ciphertext: payload,
    })
}

#[derive(Debug)]
pub(super) struct WelcomeBody {
    pub(super) server_static_public: [u8; 32],
    pub(super) cookie: [u8; 32],
    pub(super) server_nonce_seed: [u8; 8],
}

pub(super) fn decode_welcome_body(mut body: Bytes) -> Result<WelcomeBody, ProtocolError> {
    if body.len() != 73 {
        return Err(ProtocolError::CurveHandshake("invalid WELCOME body"));
    }

    let version = body.get_u8();
    if version != 1 {
        return Err(ProtocolError::CurveHandshake("unsupported WELCOME version"));
    }

    let server_static_public = body
        .split_to(32)
        .as_ref()
        .try_into()
        .expect("length already checked");
    let cookie = body
        .split_to(32)
        .as_ref()
        .try_into()
        .expect("length already checked");
    let server_nonce_seed = body
        .split_to(8)
        .as_ref()
        .try_into()
        .expect("length already checked");

    Ok(WelcomeBody {
        server_static_public,
        cookie,
        server_nonce_seed,
    })
}

#[derive(Debug)]
pub(super) struct Initiate {
    pub(super) client_static_public: [u8; 32],
    pub(super) ciphertext: Bytes,
}

pub(super) fn parse_initiate(mut payload: Bytes) -> Result<Initiate, ProtocolError> {
    if payload.len() < 32 + 16 {
        return Err(ProtocolError::CurveHandshake("invalid INITIATE payload"));
    }

    let client_static_public = payload
        .split_to(32)
        .as_ref()
        .try_into()
        .expect("length already checked");
    Ok(Initiate {
        client_static_public,
        ciphertext: payload,
    })
}

#[derive(Debug)]
pub(super) struct InitiateBody {
    pub(super) cookie: [u8; 32],
    pub(super) metadata: MetadataMap,
}

pub(super) fn decode_initiate_body(mut body: Bytes) -> Result<InitiateBody, ProtocolError> {
    if body.len() < 36 {
        return Err(ProtocolError::CurveHandshake("invalid INITIATE body"));
    }

    let cookie = body
        .split_to(32)
        .as_ref()
        .try_into()
        .expect("length already checked");
    let metadata_len = body.get_u32() as usize;
    if body.len() != metadata_len {
        return Err(ProtocolError::CurveHandshake("invalid INITIATE metadata"));
    }

    let metadata = decode_metadata(body)?;
    Ok(InitiateBody { cookie, metadata })
}

pub(super) fn append_transcript(transcript: &mut Vec<u8>, label: &[u8], payload: &[u8]) {
    transcript.extend_from_slice(&(label.len() as u16).to_be_bytes());
    transcript.extend_from_slice(label);
    transcript.extend_from_slice(&(payload.len() as u32).to_be_bytes());
    transcript.extend_from_slice(payload);
}