radixdb-plugin 1.1.0

Safe authoring SDK and local test host for RadixDB native plugins
Documentation
use radixdb_plugin::prelude::*;

#[derive(Debug, Default, Clone, RadixType)]
#[radix_type(
    id = "fixed_fields",
    name = "fixed_fields",
    codec = 1,
    semantic_revision = 1,
    storage = "fixed",
    max_bytes = 21
)]
struct FixedFields {
    #[radix_field(codec = "i32-le")]
    signed: i32,
    #[radix_field(codec = "f64-le")]
    floating: f64,
    #[radix_field(codec = "u16-le")]
    fixed: [u16; 4],
    #[radix_field(codec = "bool-u8")]
    enabled: bool,
}

#[derive(Debug, Default, Clone, RadixType)]
#[radix_type(
    id = "bounded_sequence",
    name = "bounded_sequence",
    codec = 1,
    semantic_revision = 1,
    storage = "variable",
    max_bytes = 36
)]
struct BoundedSequence {
    #[radix_field(codec = "u32-le", max_items = 8, max_bytes = 36)]
    values: Vec<u32>,
}

#[derive(Debug, Clone)]
struct ManualShape {
    tag: u8,
    payload: Vec<u8>,
}

struct ManualShapeCodec;

impl ManualCodec<ManualShape> for ManualShapeCodec {
    fn encode(value: &ManualShape, output: &mut CodecWriter) -> PluginResult<()> {
        if value.payload.len() > 27 {
            return Err(PluginError::limit_exceeded("manual payload exceeds bound"));
        }
        output.write(&[value.tag, value.payload.len() as u8])?;
        output.write(&value.payload)
    }

    fn decode(input: &mut CodecReader<'_>) -> PluginResult<ManualShape> {
        let header = input.read(2)?;
        let length = header[1] as usize;
        if length > 27 {
            return Err(PluginError::limit_exceeded("manual payload exceeds bound"));
        }
        Ok(ManualShape {
            tag: header[0],
            payload: input.read(length)?.to_vec(),
        })
    }

    fn corpus() -> Vec<ManualShape> {
        vec![
            ManualShape {
                tag: 0,
                payload: Vec::new(),
            },
            ManualShape {
                tag: u8::MAX,
                payload: vec![0, 1, 2, u8::MAX],
            },
        ]
    }
}

#[derive(Debug, Clone, RadixType)]
#[radix_type(
    id = "manual_shape",
    name = "manual_shape",
    codec = 1,
    semantic_revision = 1,
    storage = "variable",
    max_bytes = 29,
    manual = ManualShapeCodec
)]
struct ManualShapeType {
    tag: u8,
    payload: Vec<u8>,
}

impl ManualCodec<ManualShapeType> for ManualShapeCodec {
    fn encode(value: &ManualShapeType, output: &mut CodecWriter) -> PluginResult<()> {
        <Self as ManualCodec<ManualShape>>::encode(
            &ManualShape {
                tag: value.tag,
                payload: value.payload.clone(),
            },
            output,
        )
    }

    fn decode(input: &mut CodecReader<'_>) -> PluginResult<ManualShapeType> {
        let value = <Self as ManualCodec<ManualShape>>::decode(input)?;
        Ok(ManualShapeType {
            tag: value.tag,
            payload: value.payload,
        })
    }

    fn corpus() -> Vec<ManualShapeType> {
        <Self as ManualCodec<ManualShape>>::corpus()
            .into_iter()
            .map(|value| ManualShapeType {
                tag: value.tag,
                payload: value.payload,
            })
            .collect()
    }
}

#[derive(Debug, Default, Clone, RadixType)]
#[radix_type(
    id = "layout_probe",
    name = "layout_probe_i32",
    codec = 1,
    semantic_revision = 1,
    storage = "fixed",
    max_bytes = 4
)]
struct LayoutI32 {
    #[radix_field(codec = "i32-le")]
    value: i32,
}

#[derive(Debug, Default, Clone, RadixType)]
#[radix_type(
    id = "layout_probe",
    name = "layout_probe_i64",
    codec = 1,
    semantic_revision = 1,
    storage = "fixed",
    max_bytes = 8
)]
struct LayoutI64 {
    #[radix_field(codec = "i64-le")]
    value: i64,
}

#[test]
fn generated_and_manual_codecs_share_the_same_gate() {
    let fixed = radixdb_plugin::testing::check_type::<FixedFields>().unwrap();
    let sequence = radixdb_plugin::testing::check_type::<BoundedSequence>().unwrap();
    let manual = radixdb_plugin::testing::check_type::<ManualShapeType>().unwrap();
    assert!(fixed.corpus_values > 1);
    assert!(sequence.corpus_values > 1);
    assert_eq!(manual.corpus_values, 2);
}

#[test]
fn float_field_codec_preserves_exact_ieee_bits() {
    let report = radixdb_plugin::testing::check_type::<FixedFields>().unwrap();
    assert!(report.codec_vectors.iter().any(|bytes| {
        let bits = u64::from_le_bytes(bytes[4..12].try_into().unwrap());
        bits == 0x7ff8_0000_0000_0001
    }));
    assert!(report.codec_vectors.iter().any(|bytes| {
        let bits = u64::from_le_bytes(bytes[4..12].try_into().unwrap());
        bits == (-0.0_f64).to_bits()
    }));
}

#[test]
fn incompatible_layout_changes_codec_fingerprint() {
    assert_ne!(LayoutI32::CODEC_FINGERPRINT, LayoutI64::CODEC_FINGERPRINT);
}

#[test]
fn malformed_manual_and_sequence_payloads_fail_closed() {
    let malformed: &[&[u8]] = &[&[], &[1], &[1, 30], &[0, 0, 0, 9]];
    assert!(
        radixdb_plugin::testing::fuzz_malformed_external_bytes::<ManualShapeType>(malformed) >= 3
    );
    assert!(
        radixdb_plugin::testing::fuzz_malformed_external_bytes::<BoundedSequence>(malformed) >= 3
    );
}