Skip to main content

Crate osal_rs_serde

Crate osal_rs_serde 

Source
Expand description

§OSAL-RS-Serde - Serialization/Deserialization Framework

A lightweight, extensible serialization framework inspired by Serde, designed for embedded systems and no-std environments.

§Overview

This library provides a flexible serialization/deserialization framework that:

  • No-std compatible: Works perfectly in bare-metal embedded environments
  • Memory-efficient: Optimized for resource-constrained systems
  • Derive macro support: #[derive(Serialize, Deserialize)] for automatic implementation
  • Extensible: Create custom serializers for any format (binary, JSON, MessagePack, etc.)
  • Type-safe: Leverages Rust’s type system for compile-time guarantees
  • Standalone: Can be used in any project, not just with osal-rs

§Supported Types

  • Primitives: bool, u8, i8, u16, i16, u32, i32, u64, i64, u128, i128, f32, f64
  • Compound types: Arrays [T; N], tuples (T1, T2, T3) (up to 3 elements), Option<T>
  • Collections: Vec<T>, byte slices, strings (with alloc feature)
  • Custom types: Any struct implementing Serialize/Deserialize
  • Nested structs: Full support for struct composition

§Memory Layout

The default ByteSerializer uses little-endian binary format:

  • Primitives: Native sizes (1, 2, 4, 8, or 16 bytes)
  • bool: 1 byte (0 or 1)
  • Option<T>: 1 byte tag + sizeof(T) if Some, 1 byte if None
  • Arrays [T; N]: sizeof(T) * N (no length prefix)
  • Tuples: concatenation of all elements
  • Structs: concatenation of all fields in declaration order

§Quick Start

§Basic Struct Example
use osal_rs_serde::{Serialize, Deserialize, to_bytes, from_bytes};

#[derive(Serialize, Deserialize)]
struct SensorData {
    temperature: i16,
    humidity: u8,
    pressure: u32,
}

fn main() {
    let data = SensorData {
        temperature: 25,
        humidity: 60,
        pressure: 1013,
    };

    // Serialize
    let mut buffer = [0u8; 32];
    let len = to_bytes(&data, &mut buffer).unwrap();
    println!("Serialized {} bytes", len);

    // Deserialize
    let read_data: SensorData = from_bytes(&buffer[..len]).unwrap();
    println!("Temperature: {}", read_data.temperature);
}
§Struct with Optional Fields
use osal_rs_serde::{Serialize, Deserialize, to_bytes, from_bytes};

#[derive(Serialize, Deserialize)]
struct Config {
    device_id: u32,
    name: Option<u8>,      // Optional device name code
    enabled: bool,
    timeout: Option<u16>,  // Optional timeout in ms
}

fn main() {
    let config = Config {
        device_id: 100,
        name: Some(42),
        enabled: true,
        timeout: None,
    };

    let mut buffer = [0u8; 64];
    let len = to_bytes(&config, &mut buffer).unwrap();
    let decoded: Config = from_bytes(&buffer[..len]).unwrap();
}
§Struct with Arrays and Tuples
use osal_rs_serde::{Serialize, Deserialize, to_bytes, from_bytes};

#[derive(Serialize, Deserialize)]
struct TelemetryPacket {
    timestamp: u64,
    coordinates: (i32, i32, i32),  // x, y, z
    samples: [u16; 8],              // 8 sensor readings
    status: u8,
}

fn main() {
    let packet = TelemetryPacket {
        timestamp: 1642857600,
        coordinates: (100, 200, 50),
        samples: [10, 20, 30, 40, 50, 60, 70, 80],
        status: 0xFF,
    };

    let mut buffer = [0u8; 128];
    let len = to_bytes(&packet, &mut buffer).unwrap();
    println!("Telemetry packet: {} bytes", len);
}
§Nested Structs
use osal_rs_serde::{Serialize, Deserialize, to_bytes, from_bytes};

#[derive(Serialize, Deserialize)]
struct Location {
    latitude: i32,
    longitude: i32,
}

#[derive(Serialize, Deserialize)]
struct Device {
    id: u32,
    battery: u8,
    location: Location,
    active: bool,
}

fn main() {
    let device = Device {
        id: 42,
        battery: 85,
        location: Location {
            latitude: 45500000,
            longitude: 9200000,
        },
        active: true,
    };

    let mut buffer = [0u8; 64];
    let len = to_bytes(&device, &mut buffer).unwrap();
    let decoded: Device = from_bytes(&buffer[..len]).unwrap();
    println!("Device at {}, {}", 
             decoded.location.latitude, 
             decoded.location.longitude);
}
§Complex Embedded System Example
use osal_rs_serde::{Serialize, Deserialize, to_bytes, from_bytes};

#[derive(Serialize, Deserialize, Debug, PartialEq)]
struct MotorControl {
    motor_id: u8,
    speed: i16,        // -1000 to 1000
    direction: bool,   // true = forward, false = reverse
    current: u16,      // mA
}

#[derive(Serialize, Deserialize, Debug, PartialEq)]
struct RobotState {
    timestamp: u64,
    motors: [MotorControl; 4],  // 4 motors
    battery_voltage: u16,        // mV
    temperature: i8,             // °C
    error_flags: u32,
}

fn main() {
    let state = RobotState {
        timestamp: 1000000,
        motors: [
            MotorControl { motor_id: 0, speed: 500, direction: true, current: 1200 },
            MotorControl { motor_id: 1, speed: 500, direction: true, current: 1150 },
            MotorControl { motor_id: 2, speed: -300, direction: false, current: 800 },
            MotorControl { motor_id: 3, speed: -300, direction: false, current: 850 },
        ],
        battery_voltage: 12400,  // 12.4V
        temperature: 35,
        error_flags: 0,
    };

    let mut buffer = [0u8; 256];
    let len = to_bytes(&state, &mut buffer).unwrap();
    println!("Robot state serialized: {} bytes", len);
     
    // Deserialize and check
    let decoded: RobotState = from_bytes(&buffer[..len]).unwrap();
    assert_eq!(state, decoded);
    println!("Battery: {}mV, Temp: {}°C", 
             decoded.battery_voltage, 
             decoded.temperature);
}

§Manual Implementation (For Custom Behavior)

use osal_rs_serde::{Serialize, Deserialize, Serializer, Deserializer};

struct Point {
    x: i32,
    y: i32,
}

impl Serialize for Point {
    fn serialize<S: Serializer>(&self, serializer: &mut S) -> Result<(), S::Error> {
        serializer.serialize_i32("x", self.x)?;
        serializer.serialize_i32("y", self.y)?;
        Ok(())
    }
}

impl Deserialize for Point {
    fn deserialize<D: Deserializer>(deserializer: &mut D, _name: &str) -> Result<Self, D::Error> {
        Ok(Point {
            x: deserializer.deserialize_i32("x")?,
            y: deserializer.deserialize_i32("y")?,
        })
    }
}

§Integration with OSAL-RS

Perfect for inter-task communication using queues:

use osal_rs::os::{Queue, QueueFn};
use osal_rs_serde::{Serialize, Deserialize, to_bytes, from_bytes};

#[derive(Serialize, Deserialize)]
struct Message {
    command: u8,
    data: [u16; 4],
}

fn sender(queue: &Queue) {
    let msg = Message { command: 0x42, data: [1, 2, 3, 4] };
    let mut buffer = [0u8; 32];
    let len = to_bytes(&msg, &mut buffer).unwrap();
    queue.post(&buffer[..len], 100).unwrap();
}

fn receiver(queue: &Queue) {
    let mut buffer = [0u8; 32];
    queue.fetch(&mut buffer, 100).unwrap();
    let msg: Message = from_bytes(&buffer).unwrap();
}

§Supported Types

  • Primitives: All integer types (u8-u128, i8-i128), f32, f64, bool
  • Compound: Arrays [T; N], tuples (up to 3 elements), Option<T>
  • Collections: Vec<T>, String (requires alloc feature)
  • Custom: Any type implementing Serialize/Deserialize
  • Nested: Full support for nested structs

§Creating Custom Serializers

You can create custom serializers for different formats (JSON, MessagePack, CBOR, etc.) by implementing the Serializer and Deserializer traits:

use osal_rs_serde::{Serializer, Error};

struct JsonSerializer<'a> {
    buffer: &'a mut [u8],
    position: usize,
}

impl<'a> Serializer for JsonSerializer<'a> {
    type Error = Error;
     
    fn serialize_u32(&mut self, name: &str, v: u32) -> Result<(), Self::Error> {
        // Write JSON format: "name": value
        // Implementation here...
        Ok(())
    }
     
    // Implement other serialize_* methods...
}

See examples/custom_serializer.rs for a complete implementation example.

§Performance & Binary Size

  • Zero-copy: Reads/writes directly to/from buffers
  • No allocations: Works entirely with stack buffers (or Vec with alloc)
  • Predictable: Buffer size calculable at compile time
  • Small code size: Minimal overhead, optimized for embedded targets

§Features

  • default: Includes alloc feature
  • alloc: Enables Vec, String support
  • std: Enables standard library (error traits, etc.)
  • derive: Enables #[derive(Serialize, Deserialize)] macros (recommended)

§Examples

The examples/ directory contains complete working examples:

  • basic.rs - Simple struct serialization
  • with_derive.rs - Using derive macros
  • arrays_tuples.rs - Arrays and tuples
  • nested_structs.rs - Nested structures
  • optional_fields.rs - Optional fields with Option
  • robot_control.rs - Complex embedded system
  • custom_serializer.rs - Custom serializer implementation

Structs§

ByteDeserializer
A deserializer that reads data from a byte buffer in little-endian format.
ByteSerializer
A serializer that writes data to a byte buffer in little-endian format.

Enums§

Error
Error types that can occur during serialization or deserialization.

Traits§

Deserialize
Trait for types that can be deserialized.
Deserializer
Trait that defines how to deserialize various types.
Serialize
Trait for types that can be serialized.
Serializer
Trait that defines how to serialize various types.

Functions§

from_bytes
Deserialize a value from a byte buffer.
to_bytes
Serialize a value to a byte buffer.
to_dyn_bytes
Serialize a value to a dynamically sized byte vector.

Type Aliases§

Result
Result type for serialization/deserialization operations.