@@ -13,10 +13,10 @@ use crate::haystack::val::{
1313
1414use super :: consts:: { FANTOM_EPOCH_UNIX_SECS , get_const} ;
1515use super :: encode:: {
16- CTRL_COORD , CTRL_DATE , CTRL_DATETIME_I4 , CTRL_DATETIME_I8 , CTRL_DICT , CTRL_DICT_EMPTY ,
17- CTRL_FALSE , CTRL_GRID , CTRL_LIST , CTRL_LIST_EMPTY , CTRL_MARKER , CTRL_NA , CTRL_NULL ,
18- CTRL_NUMBER_F8 , CTRL_NUMBER_I2 , CTRL_NUMBER_I4 , CTRL_REF_I8 , CTRL_REF_STR , CTRL_REMOVE ,
19- CTRL_STR , CTRL_SYMBOL , CTRL_TIME , CTRL_TRUE , CTRL_URI , CTRL_XSTR ,
16+ CTRL_BUF , CTRL_COORD , CTRL_DATE , CTRL_DATETIME_I4 , CTRL_DATETIME_I8 , CTRL_DICT ,
17+ CTRL_DICT_EMPTY , CTRL_FALSE , CTRL_GRID , CTRL_LIST , CTRL_LIST_EMPTY , CTRL_MARKER , CTRL_NA ,
18+ CTRL_NULL , CTRL_NUMBER_F8 , CTRL_NUMBER_I2 , CTRL_NUMBER_I4 , CTRL_REF_I8 , CTRL_REF_STR ,
19+ CTRL_REMOVE , CTRL_STR , CTRL_SYMBOL , CTRL_TIME , CTRL_TRUE , CTRL_URI , CTRL_XSTR ,
2020} ;
2121
2222// ---------------------------------------------------------------------------
@@ -243,6 +243,24 @@ fn datetime_from_nanos(fantom_nanos: i64, tz: &str) -> Result<DateTime> {
243243// Private payload decoders (ctrl byte already consumed by caller)
244244// ---------------------------------------------------------------------------
245245
246+ /// Decode a Haxall binary buffer (`CTRL_BUF`/0x13) payload: `varint(size)` + raw bytes.
247+ ///
248+ /// libhaystack has no `Bin` value kind, so the buffer is surfaced as
249+ /// `XStr("Bin", "<lowercase hex>")`, preserving all bytes without data loss and
250+ /// remaining compatible with Haystack's XStr encoding convention for binary data.
251+ fn decode_buf_as_xstr < R : Read > ( reader : & mut R ) -> Result < XStr > {
252+ let size = decode_varint ( reader) ?;
253+ if size < 0 {
254+ return Err ( Error :: Message ( "Negative Buf size" . into ( ) ) ) ;
255+ }
256+ let mut bytes = vec ! [ 0u8 ; size as usize ] ;
257+ reader
258+ . read_exact ( & mut bytes)
259+ . map_err ( |e| Error :: Message ( e. to_string ( ) ) ) ?;
260+ let hex: String = bytes. iter ( ) . map ( |b| format ! ( "{b:02x}" ) ) . collect ( ) ;
261+ Ok ( XStr :: make ( "Bin" , & hex) )
262+ }
263+
246264/// Decode a non-empty Dict payload: `'{' varint(count) (key value)* '}'`
247265fn decode_dict_payload < R : Read > ( reader : & mut R ) -> Result < Dict > {
248266 let marker = read_u8 ( reader) ?;
@@ -474,6 +492,7 @@ impl FromBrio for Value {
474492 & decode_str ( reader) ?,
475493 ) ) ) ,
476494 CTRL_SYMBOL => Ok ( Value :: from ( Symbol :: make ( & decode_str ( reader) ?) ) ) ,
495+ CTRL_BUF => decode_buf_as_xstr ( reader) . map ( Value :: from) ,
477496 CTRL_DICT_EMPTY => Ok ( Value :: from ( Dict :: default ( ) ) ) ,
478497 CTRL_DICT => decode_dict_payload ( reader) . map ( Value :: from) ,
479498 CTRL_LIST_EMPTY => Ok ( Value :: from ( List :: default ( ) ) ) ,
@@ -501,8 +520,23 @@ pub fn from_brio<R: Read>(reader: &mut R) -> Result<Value> {
501520
502521fn make_number ( v : f64 , unit : & str ) -> Number {
503522 if unit. is_empty ( ) {
523+ return Number :: make ( v) ;
524+ }
525+ // Haxall `BrioReader.consumeUnit` calls `Number.loadUnit(str, checked:false)` which
526+ // returns `null` for unrecognised unit strings (the number becomes unitless). Match
527+ // that semantic when units-db is available. Without units-db, fall back to the
528+ // DEFAULT_UNIT sentinel so unit information is at least structurally preserved.
529+ #[ cfg( feature = "units-db" ) ]
530+ {
531+ use crate :: units:: get_unit;
532+ if let Some ( u) = get_unit ( unit) {
533+ return Number :: make_with_unit ( v, u) ;
534+ }
535+ // Unit string present but not in database → unitless (matches Haxall null).
504536 Number :: make ( v)
505- } else {
537+ }
538+ #[ cfg( not( feature = "units-db" ) ) ]
539+ {
506540 use crate :: units:: get_unit_or_default;
507541 Number :: make_with_unit ( v, get_unit_or_default ( unit) )
508542 }
0 commit comments