wolfcose 0.1.0

Safe Rust API for wolfSSL wolfCOSE.
use crate::error::{Error, Result};
use crate::raw;
use crate::types::CborMajorType;
use core::marker::PhantomData;
use core::ptr;
use core::slice;

/// Zero-copy CBOR item decoded from an input buffer.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct CborItem<'a> {
    /// Major type.
    pub major_type: CborMajorType,
    /// Numeric value, length, or item count.
    pub value: u64,
    /// Borrowed byte/text payload for byte and text strings.
    pub data: Option<&'a [u8]>,
}

impl<'a> CborItem<'a> {
    fn from_raw(item: raw::WOLFCOSE_CBOR_ITEM) -> Self {
        let data = if item.data.is_null() {
            None
        } else {
            // SAFETY: wolfCOSE returns a pointer into the decoder input valid
            // for `dataLen`; the lifetime is tied to the decoder input.
            Some(unsafe { slice::from_raw_parts(item.data, item.dataLen) })
        };
        Self {
            major_type: CborMajorType::from_raw(item.majorType).unwrap_or(CborMajorType::SIMPLE),
            value: item.val,
            data,
        }
    }
}

/// CBOR encoder over a caller-provided output buffer.
pub struct CborEncoder<'a> {
    ctx: raw::WOLFCOSE_CBOR_CTX,
    _marker: PhantomData<&'a mut [u8]>,
}

impl<'a> CborEncoder<'a> {
    /// Create an encoder over `out`.
    pub fn new(out: &'a mut [u8]) -> Self {
        Self {
            ctx: raw::WOLFCOSE_CBOR_CTX {
                buf: out.as_mut_ptr(),
                cbuf: ptr::null(),
                bufSz: out.len(),
                idx: 0,
            },
            _marker: PhantomData,
        }
    }

    /// Number of bytes written.
    pub fn len(&self) -> usize {
        self.ctx.idx
    }

    /// Whether no bytes have been written.
    pub fn is_empty(&self) -> bool {
        self.len() == 0
    }

    /// Return the bytes written so far.
    pub fn as_written(&self) -> &'a [u8] {
        // SAFETY: the encoder exclusively owns `buf` for `'a`, and `idx` never
        // exceeds `bufSz` when wolfCOSE reports success.
        unsafe { slice::from_raw_parts(self.ctx.buf, self.ctx.idx) }
    }

    /// Encode an unsigned integer.
    pub fn encode_u64(&mut self, value: u64) -> Result<()> {
        // SAFETY: `ctx` points to the live encoder buffer.
        Error::from_code(unsafe { raw::wc_CBOR_EncodeUint(&mut self.ctx, value) })
    }

    /// Encode a signed integer.
    pub fn encode_i64(&mut self, value: i64) -> Result<()> {
        // SAFETY: `ctx` points to the live encoder buffer.
        Error::from_code(unsafe { raw::wc_CBOR_EncodeInt(&mut self.ctx, value) })
    }

    /// Encode a byte string.
    pub fn encode_bstr(&mut self, data: &[u8]) -> Result<()> {
        let ptr = if data.is_empty() {
            ptr::null()
        } else {
            data.as_ptr()
        };
        // SAFETY: `ptr` is null for empty input or valid for `data.len()`.
        Error::from_code(unsafe { raw::wc_CBOR_EncodeBstr(&mut self.ctx, ptr, data.len()) })
    }

    /// Encode a UTF-8 text string.
    pub fn encode_tstr(&mut self, text: &str) -> Result<()> {
        let bytes = text.as_bytes();
        let ptr = if bytes.is_empty() {
            ptr::null()
        } else {
            bytes.as_ptr()
        };
        // SAFETY: `ptr` is null for empty input or valid for `bytes.len()`.
        Error::from_code(unsafe { raw::wc_CBOR_EncodeTstr(&mut self.ctx, ptr, bytes.len()) })
    }

    /// Encode an array header.
    pub fn encode_array_start(&mut self, count: usize) -> Result<()> {
        // SAFETY: `ctx` points to the live encoder buffer.
        Error::from_code(unsafe { raw::wc_CBOR_EncodeArrayStart(&mut self.ctx, count) })
    }

    /// Encode a map header.
    pub fn encode_map_start(&mut self, count: usize) -> Result<()> {
        // SAFETY: `ctx` points to the live encoder buffer.
        Error::from_code(unsafe { raw::wc_CBOR_EncodeMapStart(&mut self.ctx, count) })
    }

    /// Encode a semantic tag.
    pub fn encode_tag(&mut self, tag: u64) -> Result<()> {
        // SAFETY: `ctx` points to the live encoder buffer.
        Error::from_code(unsafe { raw::wc_CBOR_EncodeTag(&mut self.ctx, tag) })
    }

    /// Encode CBOR true.
    pub fn encode_true(&mut self) -> Result<()> {
        // SAFETY: `ctx` points to the live encoder buffer.
        Error::from_code(unsafe { raw::wc_CBOR_EncodeTrue(&mut self.ctx) })
    }

    /// Encode CBOR false.
    pub fn encode_false(&mut self) -> Result<()> {
        // SAFETY: `ctx` points to the live encoder buffer.
        Error::from_code(unsafe { raw::wc_CBOR_EncodeFalse(&mut self.ctx) })
    }

    /// Encode CBOR null.
    pub fn encode_null(&mut self) -> Result<()> {
        // SAFETY: `ctx` points to the live encoder buffer.
        Error::from_code(unsafe { raw::wc_CBOR_EncodeNull(&mut self.ctx) })
    }

    /// Encode an IEEE 754 single-precision float.
    ///
    /// Returns [`Error::Unsupported`](crate::Error::Unsupported) unless
    /// `wolfcose-sys` was built with the `float` feature.
    pub fn encode_f32(&mut self, value: f32) -> Result<()> {
        // SAFETY: `ctx` points to the live encoder buffer. The sys shim
        // returns Unsupported when the C float API is not compiled in.
        Error::from_code(unsafe { raw::rb_wc_CBOR_EncodeFloat(&mut self.ctx, value) })
    }

    /// Encode an IEEE 754 double-precision float.
    ///
    /// Returns [`Error::Unsupported`](crate::Error::Unsupported) unless
    /// `wolfcose-sys` was built with the `float` feature.
    pub fn encode_f64(&mut self, value: f64) -> Result<()> {
        // SAFETY: `ctx` points to the live encoder buffer. The sys shim
        // returns Unsupported when the C float API is not compiled in.
        Error::from_code(unsafe { raw::rb_wc_CBOR_EncodeDouble(&mut self.ctx, value) })
    }
}

/// CBOR decoder over an input buffer.
pub struct CborDecoder<'a> {
    ctx: raw::WOLFCOSE_CBOR_CTX,
    _marker: PhantomData<&'a [u8]>,
}

impl<'a> CborDecoder<'a> {
    /// Create a decoder over `input`.
    pub fn new(input: &'a [u8]) -> Self {
        Self {
            ctx: raw::WOLFCOSE_CBOR_CTX {
                buf: ptr::null_mut(),
                cbuf: input.as_ptr(),
                bufSz: input.len(),
                idx: 0,
            },
            _marker: PhantomData,
        }
    }

    /// Current decoder cursor.
    pub fn position(&self) -> usize {
        self.ctx.idx
    }

    /// Total input length.
    pub fn len(&self) -> usize {
        self.ctx.bufSz
    }

    /// Whether the decoder input is empty.
    pub fn is_empty(&self) -> bool {
        self.len() == 0
    }

    /// Whether all input bytes have been consumed.
    pub fn is_finished(&self) -> bool {
        self.position() == self.len()
    }

    /// Remaining undecoded byte count.
    pub fn remaining(&self) -> usize {
        self.len().saturating_sub(self.position())
    }

    /// Peek at the next CBOR major type.
    pub fn peek_type(&self) -> Option<CborMajorType> {
        if self.ctx.idx >= self.ctx.bufSz {
            None
        } else {
            // SAFETY: bounds were checked above and `ctx` points to this decoder.
            CborMajorType::from_raw(unsafe { raw::wc_CBOR_PeekType_Rust(&self.ctx) })
        }
    }

    /// Peek at the raw initial byte of the next item.
    pub fn peek_initial_byte(&self) -> Option<u8> {
        if self.ctx.idx >= self.ctx.bufSz {
            None
        } else {
            // SAFETY: bounds were checked above and `cbuf` points to decoder input.
            Some(unsafe { *self.ctx.cbuf.add(self.ctx.idx) })
        }
    }

    /// Decode a raw CBOR item head.
    pub fn decode_head(&mut self) -> Result<CborItem<'a>> {
        let mut item = raw::WOLFCOSE_CBOR_ITEM::default();
        // SAFETY: output item and decoder context are valid.
        Error::from_code(unsafe { raw::wc_CBOR_DecodeHead(&mut self.ctx, &mut item) })?;
        Ok(CborItem::from_raw(item))
    }

    /// Decode CBOR null.
    pub fn decode_null(&mut self) -> Result<()> {
        let item = self.decode_head()?;
        if item.major_type == CborMajorType::SIMPLE
            && item.value == simple_value(raw::WOLFCOSE_CBOR_NULL)
        {
            Ok(())
        } else {
            Err(Error::CborType)
        }
    }

    /// Decode a CBOR boolean.
    pub fn decode_bool(&mut self) -> Result<bool> {
        let item = self.decode_head()?;
        if item.major_type != CborMajorType::SIMPLE {
            return Err(Error::CborType);
        }
        match item.value {
            value if value == simple_value(raw::WOLFCOSE_CBOR_TRUE) => Ok(true),
            value if value == simple_value(raw::WOLFCOSE_CBOR_FALSE) => Ok(false),
            _ => Err(Error::CborType),
        }
    }

    /// Decode an unsigned integer.
    pub fn decode_u64(&mut self) -> Result<u64> {
        let mut value = 0;
        // SAFETY: output value and decoder context are valid.
        Error::from_code(unsafe { raw::wc_CBOR_DecodeUint(&mut self.ctx, &mut value) })?;
        Ok(value)
    }

    /// Decode a signed integer.
    pub fn decode_i64(&mut self) -> Result<i64> {
        let mut value = 0;
        // SAFETY: output value and decoder context are valid.
        Error::from_code(unsafe { raw::wc_CBOR_DecodeInt(&mut self.ctx, &mut value) })?;
        Ok(value)
    }

    /// Decode a byte string.
    pub fn decode_bstr(&mut self) -> Result<&'a [u8]> {
        let mut data = ptr::null();
        let mut len = 0;
        // SAFETY: outputs and decoder context are valid.
        Error::from_code(unsafe { raw::wc_CBOR_DecodeBstr(&mut self.ctx, &mut data, &mut len) })?;
        if data.is_null() {
            Ok(&[])
        } else {
            // SAFETY: wolfCOSE returns a slice into the decoder input.
            Ok(unsafe { slice::from_raw_parts(data, len) })
        }
    }

    /// Decode a text string as bytes.
    pub fn decode_tstr_bytes(&mut self) -> Result<&'a [u8]> {
        let mut data = ptr::null();
        let mut len = 0;
        // SAFETY: outputs and decoder context are valid.
        Error::from_code(unsafe { raw::wc_CBOR_DecodeTstr(&mut self.ctx, &mut data, &mut len) })?;
        if data.is_null() {
            Ok(&[])
        } else {
            // SAFETY: wolfCOSE returns a slice into the decoder input.
            Ok(unsafe { slice::from_raw_parts(data, len) })
        }
    }

    /// Decode an array header.
    pub fn decode_array_start(&mut self) -> Result<usize> {
        let mut count = 0;
        // SAFETY: output count and decoder context are valid.
        Error::from_code(unsafe { raw::wc_CBOR_DecodeArrayStart(&mut self.ctx, &mut count) })?;
        Ok(count)
    }

    /// Decode a map header.
    pub fn decode_map_start(&mut self) -> Result<usize> {
        let mut count = 0;
        // SAFETY: output count and decoder context are valid.
        Error::from_code(unsafe { raw::wc_CBOR_DecodeMapStart(&mut self.ctx, &mut count) })?;
        Ok(count)
    }

    /// Decode a semantic tag.
    pub fn decode_tag(&mut self) -> Result<u64> {
        let mut tag = 0;
        // SAFETY: output tag and decoder context are valid.
        Error::from_code(unsafe { raw::wc_CBOR_DecodeTag(&mut self.ctx, &mut tag) })?;
        Ok(tag)
    }

    /// Skip the next complete CBOR item.
    pub fn skip(&mut self) -> Result<()> {
        // SAFETY: decoder context is valid.
        Error::from_code(unsafe { raw::wc_CBOR_Skip(&mut self.ctx) })
    }
}

fn simple_value(initial_byte: u32) -> u64 {
    (initial_byte & 0x1f) as u64
}