Lines
98.28 %
Functions
51.43 %
Branches
100 %
#![cfg_attr(not(feature = "std"), no_std)]
use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
pub const MAGIC_NOMI: u32 = 0x4E4F_4D49;
pub const MAGIC_OUTP: u32 = 0x4F55_5450;
pub const BASE_OFFSET: u32 = 0x1000;
pub const FORMAT_VERSION: u16 = 1;
macro_rules! impl_try_from_u8 {
($name:ident { $($variant:ident = $val:expr),* $(,)? }) => {
impl TryFrom<u8> for $name {
type Error = ();
fn try_from(v: u8) -> Result<Self, ()> {
match v {
$($val => Ok(Self::$variant),)*
_ => Err(()),
}
};
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ContextType {
EntityCreate = 0,
EntityUpdate = 1,
EntityDelete = 2,
BatchProcess = 3,
impl_try_from_u8!(ContextType {
});
pub enum EntityType {
Transaction = 0,
Split = 1,
Tag = 2,
Account = 3,
Commodity = 4,
Price = 5,
Budget = 6,
Lot = 7,
impl_try_from_u8!(EntityType {
pub enum Operation {
Nop = 0,
Create = 1,
Update = 2,
Delete = 3,
Link = 4,
Unlink = 5,
impl_try_from_u8!(Operation {
pub struct EntityFlags;
impl EntityFlags {
#[must_use]
pub fn is_primary(flags: u8) -> bool {
flags & 0x01 != 0
pub fn is_context(flags: u8) -> bool {
flags & 0x02 != 0
pub fn make(is_primary: bool, is_context: bool) -> u8 {
u8::from(is_primary) | (u8::from(is_context) << 1)
// Include generated struct definitions from org document
include!("generated.rs");
impl GlobalHeader {
pub fn new(
context_type: ContextType,
primary_entity_type: EntityType,
input_entity_count: u32,
primary_entity_idx: u32,
) -> Self {
Self {
magic: MAGIC_NOMI,
version: FORMAT_VERSION,
context_type: context_type as u8,
primary_entity_type: primary_entity_type as u8,
input_entity_count,
entities_offset: 0,
strings_pool_offset: 0,
strings_pool_size: 0,
output_offset: 0,
output_size: 0,
primary_entity_idx,
reserved: [0; 28],
pub fn as_bytes(&self) -> &[u8] {
<Self as IntoBytes>::as_bytes(self)
impl EntityHeader {
entity_type: EntityType,
operation: Operation,
flags: u8,
id: [u8; 16],
parent_idx: i32,
data_offset: u32,
data_size: u32,
entity_type: entity_type as u8,
operation: operation as u8,
flags,
reserved: [0],
id,
parent_idx,
data_offset,
data_size,
impl OutputHeader {
pub fn new(output_start_idx: u32) -> Self {
magic: MAGIC_OUTP,
output_entity_count: 0,
output_start_idx,
strings_offset: 0,
next_write_pos: 0,
reserved: [0; 4],
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_size_constants() {
assert_eq!(GLOBAL_HEADER_SIZE, 64);
assert_eq!(ENTITY_HEADER_SIZE, 32);
assert_eq!(OUTPUT_HEADER_SIZE, 24);
assert_eq!(TRANSACTION_DATA_SIZE, 48);
assert_eq!(SPLIT_DATA_SIZE, 64);
assert_eq!(TAG_DATA_SIZE, 16);
assert_eq!(ACCOUNT_DATA_SIZE, 48);
assert_eq!(COMMODITY_DATA_SIZE, 32);
fn test_global_header_roundtrip() {
let header = GlobalHeader::new(ContextType::EntityCreate, EntityType::Transaction, 5, 0);
let bytes = header.as_bytes();
let parsed = GlobalHeader::from_bytes(bytes).unwrap();
assert_eq!(parsed.magic, MAGIC_NOMI);
assert_eq!(parsed.input_entity_count, 5);
fn test_entity_header_roundtrip() {
let id = [1u8; 16];
let header = EntityHeader::new(EntityType::Split, Operation::Create, 0, id, -1, 100, 64);
let bytes = header.to_bytes();
let parsed = EntityHeader::from_bytes(&bytes).unwrap();
assert_eq!(parsed.entity_type, EntityType::Split as u8);
assert_eq!(parsed.parent_idx, -1);
assert_eq!(parsed.data_offset, 100);
fn test_output_header_roundtrip() {
let header = OutputHeader::new(10);
let parsed = OutputHeader::from_bytes(&bytes).unwrap();
assert_eq!(parsed.magic, MAGIC_OUTP);
assert_eq!(parsed.output_start_idx, 10);
fn test_tag_data_roundtrip() {
let tag = TagData {
name_offset: 100,
value_offset: 105,
name_len: 5,
value_len: 10,
let bytes = tag.to_bytes();
let parsed = TagData::from_bytes(&bytes).unwrap();
assert_eq!(parsed.name_offset, 100);
assert_eq!(parsed.value_offset, 105);
assert_eq!(parsed.name_len, 5);
assert_eq!(parsed.value_len, 10);