11use anyhow:: { anyhow, Result } ;
22use cbor4ii:: core:: {
3- dec:: { self , Decode , Read } ,
4- major, marker, types ,
3+ dec:: { self , Read } ,
4+ major, marker,
55} ;
66use pyo3:: { ffi, prelude:: * , types:: * , BoundObject } ;
77
@@ -28,6 +28,55 @@ fn map_key_cmp(a: &[u8], b: &[u8]) -> std::cmp::Ordering {
2828 }
2929}
3030
31+ // Argument of the already-peeked header byte: low bits 0..=0x17 are the value
32+ // itself; 0x18..=0x1b mean 1/2/4/8 following big-endian bytes. Consumes the
33+ // header. Rejects indefinite-length and reserved arguments (0x1c..=0x1f),
34+ // which DAG-CBOR forbids.
35+ #[ inline]
36+ fn decode_arg < ' de , R : dec:: Read < ' de > > ( r : & mut R , byte : u8 ) -> Result < u64 >
37+ where
38+ R :: Error : Send + Sync ,
39+ {
40+ r. advance ( 1 ) ;
41+ let low = byte & 0x1f ;
42+ if low <= 0x17 {
43+ return Ok ( low as u64 ) ;
44+ }
45+ let n = match low {
46+ 0x18 => 1 ,
47+ 0x19 => 2 ,
48+ 0x1a => 4 ,
49+ 0x1b => 8 ,
50+ _ => return Err ( anyhow ! ( "Indefinite or reserved header argument" ) ) ,
51+ } ;
52+ let buf = r. fill ( n) ?;
53+ let s = buf. as_ref ( ) ;
54+ if s. len ( ) < n {
55+ return Err ( anyhow ! ( "end of data" ) ) ;
56+ }
57+ let mut be = [ 0u8 ; 8 ] ;
58+ be[ 8 - n..] . copy_from_slice ( & s[ ..n] ) ;
59+ r. advance ( n) ;
60+ Ok ( u64:: from_be_bytes ( be) )
61+ }
62+
63+ // Definite-length bytes/string payload, zero-copy from the input.
64+ #[ inline]
65+ fn decode_seg < ' de , R : dec:: Read < ' de > > ( r : & mut R , byte : u8 ) -> Result < & ' de [ u8 ] >
66+ where
67+ R :: Error : Send + Sync ,
68+ {
69+ let len = usize:: try_from ( decode_arg ( r, byte) ?) ?;
70+ match r. fill ( len) ? {
71+ dec:: Reference :: Long ( s) if s. len ( ) >= len => {
72+ let s = & s[ ..len] ;
73+ r. advance ( len) ;
74+ Ok ( s)
75+ }
76+ _ => Err ( anyhow ! ( "end of data" ) ) ,
77+ }
78+ }
79+
3180pub ( crate ) fn to_pyobject < ' de , R : dec:: Read < ' de > > (
3281 py : Python ,
3382 r : & mut R ,
@@ -48,24 +97,27 @@ where
4897
4998 let byte = peek_one ( r) ?;
5099 Ok ( match dec:: if_major ( byte) {
51- major:: UNSIGNED => u64:: decode ( r) ?. into_pyobject ( py) ?. into ( ) ,
52- major:: NEGATIVE => i128:: decode ( r) ?. into_pyobject ( py) ?. into ( ) ,
53- major:: BYTES => PyBytes :: new ( py, <types:: Bytes < & [ u8 ] > >:: decode ( r) ?. 0 )
100+ major:: UNSIGNED => decode_arg ( r, byte) ?. into_pyobject ( py) ?. into ( ) ,
101+ major:: NEGATIVE => {
102+ let v = decode_arg ( r, byte) ?;
103+ // `-1 - v` fits an i64 for v < 2^63; the i128 fallback covers the
104+ // rest and costs a `_PyLong_FromByteArray`-style conversion.
105+ if v <= i64:: MAX as u64 {
106+ ( -1i64 - v as i64 ) . into_pyobject ( py) ?. into ( )
107+ } else {
108+ ( -1i128 - v as i128 ) . into_pyobject ( py) ?. into ( )
109+ }
110+ }
111+ major:: BYTES => PyBytes :: new ( py, decode_seg ( r, byte) ?)
54112 . into_pyobject ( py) ?
55113 . into ( ) ,
56114 major:: STRING => {
57115 // ASCII fast path inside the helper; non-ASCII falls through to
58116 // `PyUnicode_DecodeUTF8`, which is where the spec validation lives.
59- from_bytes (
60- py,
61- <types:: UncheckedStr < & [ u8 ] > >:: decode ( r)
62- . map_err ( |_| anyhow ! ( "Cannot decode as bytes" ) ) ?
63- . 0 ,
64- ) ?
65- . into ( )
117+ from_bytes ( py, decode_seg ( r, byte) ?) ?. into ( )
66118 }
67119 major:: ARRAY => {
68- let len = types :: Array :: len ( r ) ? . ok_or_else ( || anyhow ! ( "Array must contain length" ) ) ?;
120+ let len = usize :: try_from ( decode_arg ( r , byte ) ? ) ?;
69121 // Every element costs at least one byte; reject a claimed length
70122 // beyond the remaining input before allocating for it.
71123 if r. fill ( len) ?. as_ref ( ) . len ( ) < len {
94146 }
95147 }
96148 major:: MAP => {
97- let len = types :: Map :: len ( r ) ? . ok_or_else ( || anyhow ! ( "Map must contain length" ) ) ?;
149+ let len = usize :: try_from ( decode_arg ( r , byte ) ? ) ?;
98150 // Every entry costs at least two bytes (key + value); reject a
99151 // claimed length beyond the remaining input before presizing.
100152 let need = len. saturating_mul ( 2 ) ;
@@ -114,9 +166,11 @@ where
114166 for _ in 0 ..len {
115167 // DAG-CBOR keys are always strings. Python does the UTF-8 validation when creating
116168 // the string.
117- let key = <types:: UncheckedStr < & [ u8 ] > >:: decode ( r)
118- . map_err ( |_| anyhow ! ( "Map keys must be strings" ) ) ?
119- . 0 ;
169+ let key_byte = peek_one ( r) ?;
170+ if dec:: if_major ( key_byte) != major:: STRING {
171+ return Err ( anyhow ! ( "Map keys must be strings" ) ) ;
172+ }
173+ let key = decode_seg ( r, key_byte) ?;
120174
121175 if let Some ( prev_key) = prev_key {
122176 // it cares about duplicated keys too thanks to Ordering::Equal
@@ -146,12 +200,16 @@ where
146200 dict. into_pyobject ( py) ?. into ( )
147201 }
148202 major:: TAG => {
149- let value = types :: Tag :: tag ( r ) ?;
203+ let value = decode_arg ( r , byte ) ?;
150204 if value != 42 {
151205 return Err ( anyhow ! ( "Non-42 tags are not supported" ) ) ;
152206 }
153207
154- let cid = <types:: Bytes < & [ u8 ] > >:: decode ( r) ?. 0 ;
208+ let content_byte = peek_one ( r) ?;
209+ if dec:: if_major ( content_byte) != major:: BYTES {
210+ return Err ( anyhow ! ( "Invalid CID" ) ) ;
211+ }
212+ let cid = decode_seg ( r, content_byte) ?;
155213
156214 // we expect CIDs to have a leading zero byte
157215 if cid. len ( ) <= 1 || cid[ 0 ] != 0 {
@@ -182,7 +240,7 @@ where
182240 py. None ( )
183241 }
184242 marker:: F32 => {
185- let value = f32:: decode ( r ) ? ;
243+ let value = f32:: from_bits ( decode_arg ( r , byte ) ? as u32 ) ;
186244 if !value. is_finite ( ) {
187245 return Err ( anyhow ! (
188246 "Number out of range for f32 (NaNs are forbidden)" . to_string( )
@@ -191,7 +249,7 @@ where
191249 value. into_pyobject ( py) ?. into ( )
192250 }
193251 marker:: F64 => {
194- let value = f64:: decode ( r ) ? ;
252+ let value = f64:: from_bits ( decode_arg ( r , byte ) ? ) ;
195253 if !value. is_finite ( ) {
196254 return Err ( anyhow ! (
197255 "Number out of range for f64 (NaNs are forbidden)" . to_string( )
0 commit comments