rndc 0.1.5

BIND9 RNDC protocol implementation for Rust
Documentation
use byteorder::{BigEndian, ReadBytesExt};
use indexmap::IndexMap;
use std::io::{Cursor, Read};

use crate::error::RndcError;
use crate::internal::constants::{MSGTYPE_BINARYDATA, MSGTYPE_LIST, MSGTYPE_STRING, MSGTYPE_TABLE};

#[allow(dead_code)]
#[derive(Debug, Clone)]
pub(crate) enum RNDCPayload {
    String(String),
    Binary(Vec<u8>),
    Table(IndexMap<String, RNDCPayload>),
    List(Vec<RNDCPayload>),
}

fn binary_fromwire(cursor: &mut Cursor<&[u8]>, len: usize) -> Result<RNDCPayload, RndcError> {
    let mut buf = vec![0u8; len];
    cursor
        .read_exact(&mut buf)
        .map_err(|e| RndcError::DecodingError(e.to_string()))?;

    match String::from_utf8(buf.clone()) {
        Ok(s) => Ok(RNDCPayload::String(s)),
        Err(_) => Ok(RNDCPayload::Binary(buf)),
    }
}

fn key_fromwire(cursor: &mut Cursor<&[u8]>) -> Result<String, RndcError> {
    let len = cursor
        .read_u8()
        .map_err(|e| RndcError::DecodingError(e.to_string()))? as usize;
    let mut buf = vec![0u8; len];
    cursor
        .read_exact(&mut buf)
        .map_err(|e| RndcError::DecodingError(e.to_string()))?;
    String::from_utf8(buf).map_err(|e| RndcError::DecodingError(e.to_string()))
}

fn value_fromwire(cursor: &mut Cursor<&[u8]>) -> Result<RNDCPayload, RndcError> {
    let typ = cursor
        .read_u8()
        .map_err(|e| RndcError::DecodingError(e.to_string()))?;
    let len = cursor
        .read_u32::<BigEndian>()
        .map_err(|e| RndcError::DecodingError(e.to_string()))? as usize;
    let pos = cursor.position() as usize;

    let slice = &cursor.get_ref()[pos..pos + len];
    let mut sub_cursor = Cursor::new(slice);

    let result = match typ {
        MSGTYPE_STRING | MSGTYPE_BINARYDATA => binary_fromwire(&mut sub_cursor, len),
        MSGTYPE_TABLE => table_fromwire(&mut sub_cursor).map(RNDCPayload::Table),
        MSGTYPE_LIST => list_fromwire(&mut sub_cursor).map(RNDCPayload::List),
        _ => Err(RndcError::DecodingError(format!(
            "Unknown RNDC message type: {}",
            typ
        ))),
    };

    cursor.set_position((pos + len) as u64);
    result
}

fn table_fromwire(cursor: &mut Cursor<&[u8]>) -> Result<IndexMap<String, RNDCPayload>, RndcError> {
    let mut map = IndexMap::new();
    while (cursor.position() as usize) < cursor.get_ref().len() {
        let key = key_fromwire(cursor)?;
        let value = value_fromwire(cursor)?;
        map.insert(key, value);
    }
    Ok(map)
}

fn list_fromwire(cursor: &mut Cursor<&[u8]>) -> Result<Vec<RNDCPayload>, RndcError> {
    let mut list = Vec::new();
    while (cursor.position() as usize) < cursor.get_ref().len() {
        let value = value_fromwire(cursor)?;
        list.push(value);
    }
    Ok(list)
}

pub(crate) fn decode(buf: &[u8]) -> Result<IndexMap<String, RNDCPayload>, RndcError> {
    let mut cursor = Cursor::new(buf);

    let len = cursor
        .read_u32::<BigEndian>()
        .map_err(|e| RndcError::DecodingError(e.to_string()))? as usize;
    if len != buf.len() - 4 {
        return Err(RndcError::DecodingError(
            "RNDC buffer length mismatch".to_string(),
        ));
    }

    let version = cursor
        .read_u32::<BigEndian>()
        .map_err(|e| RndcError::DecodingError(e.to_string()))?;
    if version != 1 {
        return Err(RndcError::DecodingError(format!(
            "Unknown RNDC protocol version: {}",
            version
        )));
    }

    let res = table_fromwire(&mut cursor)?;

    Ok(res)
}