icrc-cbor 0.1.0

Cbor encoders and decoders commonly used by the ICRC ledger types.
Documentation
use ethnum::u256;
use minicbor::data::Tag;
use minicbor::decode::{Decoder, Error};
use minicbor::encode::{Encoder, Write};

const U32_MAX: u256 = u256::new(u32::MAX as u128);
const U64_MAX: u256 = u256::new(u64::MAX as u128);

pub fn decode<Ctx>(d: &mut Decoder<'_>, _ctx: &mut Ctx) -> Result<u256, Error> {
    let pos = d.position();
    match d.u64() {
        Ok(n) => return Ok(u256::from(n)),
        Err(e) if e.is_type_mismatch() => {
            d.set_position(pos);
        }
        Err(e) => return Err(e),
    }

    let tag: Tag = d.tag()?;
    if tag != Tag::PosBignum {
        return Err(Error::message(
            "failed to parse u256: expected a PosBignum tag",
        ));
    }
    let bytes = d.bytes()?;
    if bytes.len() > 32 {
        return Err(Error::message(format!(
            "failed to parse u256: expected at most 32 bytes, got: {}",
            bytes.len()
        )));
    }
    let mut be_bytes = [0u8; 32];
    be_bytes[32 - bytes.len()..32].copy_from_slice(bytes);
    Ok(u256::from_be_bytes(be_bytes))
}

pub fn encode<Ctx, W: Write>(
    v: &u256,
    e: &mut Encoder<W>,
    _ctx: &mut Ctx,
) -> Result<(), minicbor::encode::Error<W::Error>> {
    if v <= &U32_MAX {
        e.u32(v.as_u32())?;
    } else if v <= &U64_MAX {
        e.u64(v.as_u64())?;
    } else {
        let be_bytes = v.to_be_bytes();
        let non_zero_pos = be_bytes
            .iter()
            .position(|x| *x != 0)
            .unwrap_or(be_bytes.len());
        e.tag(Tag::PosBignum)?.bytes(&be_bytes[non_zero_pos..])?;
    }
    Ok(())
}