octra-sqlite 0.5.2

Real SQLite inside an Octra Circle, with a Rust CLI and client library
Documentation
use super::error::{Error, Result};

pub const DOMAIN: &[u8] = b"octra-sqlite.osw1.v1\0";

pub fn frame(db_id: &[u8; 32], sequence: u64, method: &str, sql: &str) -> Result<Vec<u8>> {
    if method.is_empty() || method.len() > 16 {
        return Err(Error::new("OSW1 method must be 1..16 bytes"));
    }
    if sql.len() > u32::MAX as usize {
        return Err(Error::new("OSW1 SQL is too large"));
    }
    let mut message = Vec::with_capacity(DOMAIN.len() + 32 + 8 + 2 + method.len() + 4 + sql.len());
    message.extend_from_slice(DOMAIN);
    message.extend_from_slice(db_id);
    message.extend_from_slice(&sequence.to_be_bytes());
    message.extend_from_slice(&(method.len() as u16).to_be_bytes());
    message.extend_from_slice(method.as_bytes());
    message.extend_from_slice(&(sql.len() as u32).to_be_bytes());
    message.extend_from_slice(sql.as_bytes());
    Ok(message)
}

#[cfg(test)]
mod tests {
    use super::*;
    use sha2::{Digest, Sha256};

    #[test]
    fn osw1_frame_matches_golden_vector() {
        let digest = Sha256::digest(b"test-db-id");
        let mut db_id = [0u8; 32];
        db_id.copy_from_slice(&digest);
        let message = frame(&db_id, 42, "exec", "select 1;").unwrap();
        assert_eq!(
            hex::encode(message),
            "6f637472612d73716c6974652e6f7377312e7631001fce55ad53f355909514a6a349e2afb2a22cf3bca124d239a9ace46a4108c482000000000000002a0004657865630000000973656c65637420313b"
        );
    }
}