Skip to content

Commit c32f3c9

Browse files
committed
fix: use allocating deserializers when scratch space exceeded
Issue #32 describes a class of errors where `deserialize_str` (and `deserialize_bytes`) have match expressions that fall through to an error when the `Text` (`Bytes`) lengths are longer than can fit in the scratch array. This patch works around that limitation by delegating to the corresponding allocating methods `deserialize_string` (`deserialize_byte_buf`) in case the scratch space is too small. This should address the issue even for types whose `Deserialize` visitor does not implement `visit_string`, since the default implementation of that method delegates back to `visit_str` once the fully-deserialized `String` is in hand. This addition raises a question to me about the stance of the `ciborium` crate on `no_std`/`alloc` support. This workaround depends on being able to use `String` and `Vec`, which rules out `no_std`. But these allocating deserializers already exist in the code and don't appear to be guarded by `cfg` directives, so I don't believe this patch changes the level of support provided for these environments. Adds regression tests for slightly-too-long string and byte sequence roundtripping.
1 parent 109c371 commit c32f3c9

File tree

2 files changed

+123
-0
lines changed

2 files changed

+123
-0
lines changed

ciborium/src/de/mod.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,12 @@ where
333333
}
334334
}
335335

336+
// Longer strings require alloaction; delegate to `deserialize_string`
337+
item @ Header::Text(_) => {
338+
self.decoder.push(item);
339+
self.deserialize_string(visitor)
340+
}
341+
336342
header => Err(header.expected("str")),
337343
};
338344
}
@@ -371,6 +377,12 @@ where
371377
visitor.visit_bytes(&self.scratch[..len])
372378
}
373379

380+
// Longer byte sequences require alloaction; delegate to `deserialize_byte_buf`
381+
item @ Header::Bytes(_) => {
382+
self.decoder.push(item);
383+
self.deserialize_byte_buf(visitor)
384+
}
385+
374386
Header::Array(len) => self.recurse(|me| {
375387
let access = Access(me, len);
376388
visitor.visit_seq(access)

ciborium/tests/codec.rs

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@ extern crate std;
55
use std::collections::{BTreeMap, HashMap};
66
use std::convert::TryFrom;
77
use std::fmt::Debug;
8+
use std::io::Cursor;
89

910
use ciborium::value::Value;
1011
use ciborium::{cbor, de::from_reader, ser::into_writer};
1112

1213
use rstest::rstest;
14+
use serde::de::Visitor;
1315
use serde::{de::DeserializeOwned, Deserialize, Serialize};
1416

1517
macro_rules! val {
@@ -416,3 +418,112 @@ fn byte_vec_serde_bytes_compatibility(input: Vec<u8>) {
416418
let bytes: Vec<u8> = from_reader(&buf[..]).unwrap();
417419
assert_eq!(input, bytes);
418420
}
421+
422+
// Regression test for #32 where strings and bytes longer than 4096 bytes previously failed to
423+
// roundtrip if `deserialize_str` and `deserialize_bytes` (and not their owned equivalents) are used
424+
// in the deserializers.
425+
426+
#[derive(Clone, Debug, PartialEq, Eq)]
427+
struct LongString {
428+
s: String,
429+
}
430+
431+
impl Serialize for LongString {
432+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
433+
where
434+
S: serde::Serializer,
435+
{
436+
serializer.serialize_str(self.s.as_str())
437+
}
438+
}
439+
440+
impl<'de> Deserialize<'de> for LongString {
441+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
442+
where
443+
D: serde::Deserializer<'de>,
444+
{
445+
deserializer.deserialize_str(LongStringVisitor)
446+
}
447+
}
448+
449+
struct LongStringVisitor;
450+
451+
impl<'de> Visitor<'de> for LongStringVisitor {
452+
type Value = LongString;
453+
454+
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
455+
write!(formatter, "string")
456+
}
457+
458+
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
459+
where
460+
E: serde::de::Error,
461+
{
462+
Ok(LongString { s: v.to_owned() })
463+
}
464+
}
465+
466+
#[test]
467+
fn long_string_roundtrips() {
468+
let s = String::from_utf8(vec![b'A'; 5000]).unwrap();
469+
let long_string = LongString { s };
470+
471+
let mut buf = vec![];
472+
into_writer(&long_string, Cursor::new(&mut buf)).unwrap();
473+
let long_string_de = from_reader(Cursor::new(&buf)).unwrap();
474+
475+
assert_eq!(long_string, long_string_de);
476+
}
477+
478+
#[derive(Clone, Debug, PartialEq, Eq)]
479+
struct LongBytes {
480+
v: Vec<u8>,
481+
}
482+
483+
impl Serialize for LongBytes {
484+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
485+
where
486+
S: serde::Serializer,
487+
{
488+
serializer.serialize_bytes(self.v.as_slice())
489+
}
490+
}
491+
492+
impl<'de> Deserialize<'de> for LongBytes {
493+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
494+
where
495+
D: serde::Deserializer<'de>,
496+
{
497+
deserializer.deserialize_bytes(LongBytesVisitor)
498+
}
499+
}
500+
501+
struct LongBytesVisitor;
502+
503+
impl<'de> Visitor<'de> for LongBytesVisitor {
504+
type Value = LongBytes;
505+
506+
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
507+
write!(formatter, "bytes")
508+
}
509+
510+
fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
511+
where
512+
E: serde::de::Error,
513+
{
514+
Ok(LongBytes { v: v.to_vec() })
515+
}
516+
}
517+
518+
#[test]
519+
fn long_bytes_roundtrips() {
520+
let long_bytes = LongBytes {
521+
v: vec![b'A'; 5000],
522+
};
523+
524+
let mut buf = vec![];
525+
into_writer(&long_bytes, Cursor::new(&mut buf)).unwrap();
526+
let long_bytes_de = from_reader(Cursor::new(&buf)).unwrap();
527+
528+
assert_eq!(long_bytes, long_bytes_de);
529+
}

0 commit comments

Comments
 (0)