consensus_encoding: move compact size into its own module
What changed, and why it matters
This commit is a pure code reorganization: it moves the compact-size integer encoder and decoder into a new dedicated file/module. The public API is unchanged, the logic is unchanged, and no security-relevant behavior is modified.
No security action needed; treat as routine refactoring.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The change relocates CompactSizeEncoder, CompactSizeDecoder, and their error types from consensus_encoding/src/encode/encoders.rs and consensus_encoding/src/decode/decoders.rs into a new consensus_encoding/src/compact_size.rs. lib.rs re-exports the same symbols under the same names. The diff shows only moves, import adjustments, and formatting; no functional changes to encoding/decoding logic, limits, or error handling.
Changed components
consensus_encoding/src/compact_size.rs (new)consensus_encoding/src/decode/decoders.rsconsensus_encoding/src/encode/encoders.rsconsensus_encoding/src/lib.rsInspect captured patch +423 / −403
diff --git a/consensus_encoding/src/compact_size.rs b/consensus_encoding/src/compact_size.rs
new file mode 100644
index 00000000..2c9b9817
--- /dev/null
+++ b/consensus_encoding/src/compact_size.rs
@@ -0,0 +1,412 @@
+// SPDX-License-Identifier: CC0-1.0
+
+//! Compact size codec.
+//!
+//! Compact size is a variable-length integer encoding used throughout the
+//! Bitcoin consensus protocol to usually to encode collection lengths. However,
+//! there are also some unique non-length use cases.
+
+use internals::array_vec::ArrayVec;
+
+use crate::decode::Decoder;
+use crate::encode::{Encoder, ExactSizeEncoder};
+
+/// Maximum size, in bytes, of a vector we are allowed to decode.
+///
+/// This is also the default value limit that can be decoded with a decoder from
+/// [`CompactSizeDecoder::new`].
+pub(crate) const MAX_VEC_SIZE: usize = 4_000_000;
+
+/// The maximum length of a compact size encoding.
+const SIZE: usize = 9;
+
+/// Encoder for a compact size encoded integer.
+#[derive(Debug, Clone)]
+pub struct CompactSizeEncoder {
+ buf: Option<ArrayVec<u8, SIZE>>,
+}
+
+impl CompactSizeEncoder {
+ /// Constructs a new `CompactSizeEncoder`.
+ ///
+ /// Encodings are defined only for the range of u64. On systems where usize is
+ /// larger than u64, it will be possible to call this method with out-of-range
+ /// values. In such cases we will ignore the passed value and encode [`u64::MAX`].
+ /// But even on such exotic systems, we expect users to pass the length of an
+ /// in-memory object, meaning that such large values are impossible to obtain.
+ pub fn new(value: usize) -> Self { Self { buf: Some(Self::encode(value)) } }
+
+ /// Returns the number of bytes used to encode this `CompactSize` value.
+ ///
+ /// # Returns
+ ///
+ /// - 1 for 0..=0xFC
+ /// - 3 for 0xFD..=(2^16-1)
+ /// - 5 for 0x10000..=(2^32-1)
+ /// - 9 otherwise.
+ #[inline]
+ pub const fn encoded_size(value: usize) -> usize {
+ match value {
+ 0..=0xFC => 1,
+ 0xFD..=0xFFFF => 3,
+ 0x10000..=0xFFFF_FFFF => 5,
+ _ => 9,
+ }
+ }
+
+ /// Encodes `CompactSize` without allocating.
+ #[inline]
+ fn encode(value: usize) -> ArrayVec<u8, SIZE> {
+ let mut res = ArrayVec::<u8, SIZE>::new();
+ match value {
+ 0..=0xFC => {
+ res.push(value as u8); // Cast ok because of match.
+ }
+ 0xFD..=0xFFFF => {
+ let v = value as u16; // Cast ok because of match.
+ res.push(0xFD);
+ res.extend_from_slice(&v.to_le_bytes());
+ }
+ 0x10000..=0xFFFF_FFFF => {
+ let v = value as u32; // Cast ok because of match.
+ res.push(0xFE);
+ res.extend_from_slice(&v.to_le_bytes());
+ }
+ _ => {
+ res.push(0xFF);
+ res.extend_from_slice(&value.to_le_bytes());
+ }
+ }
+ res
+ }
+}
+
+impl Encoder for CompactSizeEncoder {
+ #[inline]
+ fn current_chunk(&self) -> &[u8] { self.buf.as_ref().map(|b| &b[..]).unwrap_or_default() }
+
+ #[inline]
+ fn advance(&mut self) -> bool {
+ self.buf = None;
+ false
+ }
+}
+
+impl ExactSizeEncoder for CompactSizeEncoder {
+ #[inline]
+ fn len(&self) -> usize { self.buf.map_or(0, |buf| buf.len()) }
+}
+
+/// Decodes a compact size encoded integer.
+///
+/// For more information about decoder see the documentation of the [`Decoder`] trait.
+#[derive(Debug, Clone)]
+pub struct CompactSizeDecoder {
+ buf: ArrayVec<u8, 9>,
+ limit: usize,
+}
+
+impl CompactSizeDecoder {
+ /// Constructs a new compact size decoder.
+ ///
+ /// Consensus encoded vectors can be up to 4,000,000 bytes long.
+ /// This is a theoretical max since block size is 4 meg wu and minimum vector element is one byte.
+ ///
+ /// The final call to [`CompactSizeDecoder::end`] on this decoder will fail if the
+ /// decoded value exceeds 4,000,000 or won't fit in a `usize`.
+ pub const fn new() -> Self {
+ Self { buf: ArrayVec::new(), limit: MAX_VEC_SIZE }
+ }
+
+ /// Constructs a new compact size decoder with encoded value limited to the provided usize.
+ ///
+ /// The final call to [`CompactSizeDecoder::end`] on this decoder will fail if the
+ /// decoded value exceeds `limit` or won't fit in a `usize`.
+ pub const fn new_with_limit(limit: usize) -> Self {
+ Self { buf: ArrayVec::new(), limit }
+ }
+}
+
+impl Default for CompactSizeDecoder {
+ fn default() -> Self { Self::new() }
+}
+
+impl Decoder for CompactSizeDecoder {
+ type Output = usize;
+ type Error = CompactSizeDecoderError;
+
+ fn push_bytes(&mut self, bytes: &mut &[u8]) -> Result<bool, Self::Error> {
+ if bytes.is_empty() {
+ return Ok(true);
+ }
+
+ if self.buf.is_empty() {
+ self.buf.push(bytes[0]);
+ *bytes = &bytes[1..];
+ }
+ let len = match self.buf[0] {
+ 0xFF => 9,
+ 0xFE => 5,
+ 0xFD => 3,
+ _ => 1,
+ };
+ let to_copy = bytes.len().min(len - self.buf.len());
+ self.buf.extend_from_slice(&bytes[..to_copy]);
+ *bytes = &bytes[to_copy..];
+
+ Ok(self.buf.len() != len)
+ }
+
+ fn end(self) -> Result<Self::Output, Self::Error> {
+ use CompactSizeDecoderErrorInner as E;
+
+ fn arr<const N: usize>(slice: &[u8]) -> Result<[u8; N], CompactSizeDecoderError> {
+ slice.try_into().map_err(|_| {
+ CompactSizeDecoderError(E::UnexpectedEof { required: N, received: slice.len() })
+ })
+ }
+
+ let (first, payload) = self
+ .buf
+ .split_first()
+ .ok_or(CompactSizeDecoderError(E::UnexpectedEof { required: 1, received: 0 }))?;
+
+ let dec_value = match *first {
+ 0xFF => {
+ let x = u64::from_le_bytes(arr(payload)?);
+ if x < 0x100_000_000 {
+ Err(CompactSizeDecoderError(E::NonMinimal { value: x }))
+ } else {
+ Ok(x)
+ }
+ }
+ 0xFE => {
+ let x = u32::from_le_bytes(arr(payload)?);
+ if x < 0x10000 {
+ Err(CompactSizeDecoderError(E::NonMinimal { value: x.into() }))
+ } else {
+ Ok(x.into())
+ }
+ }
+ 0xFD => {
+ let x = u16::from_le_bytes(arr(payload)?);
+ if x < 0xFD {
+ Err(CompactSizeDecoderError(E::NonMinimal { value: x.into() }))
+ } else {
+ Ok(x.into())
+ }
+ }
+ n => Ok(n.into()),
+ }?;
+
+ // This error is returned if dec_value is outside of the usize range, or
+ // if it is above the given limit.
+ let make_err = || {
+ CompactSizeDecoderError(E::ValueExceedsLimit(LengthPrefixExceedsMaxError {
+ value: dec_value,
+ limit: self.limit,
+ }))
+ };
+
+ usize::try_from(dec_value).map_err(|_| make_err()).and_then(|nsize| {
+ if nsize > self.limit {
+ Err(make_err())
+ } else {
+ Ok(nsize)
+ }
+ })
+ }
+
+ fn read_limit(&self) -> usize {
+ match self.buf.len() {
+ 0 => 1,
+ already_read => match self.buf[0] {
+ 0xFF => 9_usize.saturating_sub(already_read),
+ 0xFE => 5_usize.saturating_sub(already_read),
+ 0xFD => 3_usize.saturating_sub(already_read),
+ _ => 0,
+ },
+ }
+ }
+}
+
+/// An error consensus decoding a compact size encoded integer.
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct CompactSizeDecoderError(CompactSizeDecoderErrorInner);
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+enum CompactSizeDecoderErrorInner {
+ /// Returned when the decoder reaches end of stream (EOF).
+ UnexpectedEof {
+ /// How many bytes were required.
+ required: usize,
+ /// How many bytes were received.
+ received: usize,
+ },
+ /// Returned when the encoding is not minimal
+ NonMinimal {
+ /// The encoded value.
+ value: u64,
+ },
+ /// Returned when the encoded value exceeds the decoder's limit.
+ ValueExceedsLimit(LengthPrefixExceedsMaxError),
+}
+
+impl core::fmt::Display for CompactSizeDecoderError {
+ fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
+ use CompactSizeDecoderErrorInner as E;
+ use internals::write_err;
+
+ match self.0 {
+ E::UnexpectedEof { required: 1, received: 0 } => {
+ write!(f, "required at least one byte but the input is empty")
+ }
+ E::UnexpectedEof { required, received: 0 } => {
+ write!(f, "required at least {} bytes but the input is empty", required)
+ }
+ E::UnexpectedEof { required, received } => write!(
+ f,
+ "required at least {} bytes but only {} bytes were received",
+ required, received
+ ),
+ E::NonMinimal { value } => write!(f, "the value {} was not encoded minimally", value),
+ E::ValueExceedsLimit(ref e) => write_err!(f, "value exceeds limit"; e),
+ }
+ }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for CompactSizeDecoderError {
+ fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
+ use CompactSizeDecoderErrorInner as E;
+
+ match self {
+ Self(E::ValueExceedsLimit(ref e)) => Some(e),
+ _ => None,
+ }
+ }
+}
+
+/// The error returned when a compact size value exceeds a configured limit.
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct LengthPrefixExceedsMaxError {
+ /// The limit that was exceeded.
+ limit: usize,
+ /// The value that exceeded the limit.
+ value: u64,
+}
+
+impl core::fmt::Display for LengthPrefixExceedsMaxError {
+ fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
+ write!(f, "decoded length {} exceeds maximum allowed {}", self.value, self.limit)
+ }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for LengthPrefixExceedsMaxError {}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn encoded_value_1_byte() {
+ // Check lower bound, upper bound (and implicitly endian-ness).
+ for v in [0x00, 0x01, 0x02, 0xFA, 0xFB, 0xFC] {
+ let v = v as usize;
+ assert_eq!(CompactSizeEncoder::encoded_size(v), 1);
+ // Should be encoded as the value as a u8.
+ let want = [v as u8];
+ let got = CompactSizeEncoder::encode(v);
+ assert_eq!(got.as_slice().len(), 1); // sanity check
+ assert_eq!(got.as_slice(), want);
+ }
+ }
+
+ macro_rules! check_encode {
+ ($($test_name:ident, $size:expr, $value:expr, $want:expr);* $(;)?) => {
+ $(
+ #[test]
+ fn $test_name() {
+ let value = $value as usize; // Because default integer type is i32.
+ assert_eq!(CompactSizeEncoder::encoded_size(value), $size);
+ let got = CompactSizeEncoder::encode(value);
+ assert_eq!(got.as_slice().len(), $size); // sanity check
+ assert_eq!(got.as_slice(), &$want);
+ }
+ )*
+ }
+ }
+
+ check_encode! {
+ // 3 byte encoding.
+ encoded_value_3_byte_lower_bound, 3, 0xFD, [0xFD, 0xFD, 0x00]; // 0x00FD
+ encoded_value_3_byte_endianness, 3, 0xABCD, [0xFD, 0xCD, 0xAB];
+ encoded_value_3_byte_upper_bound, 3, 0xFFFF, [0xFD, 0xFF, 0xFF];
+ // 5 byte encoding.
+ encoded_value_5_byte_lower_bound, 5, 0x0001_0000, [0xFE, 0x00, 0x00, 0x01, 0x00];
+ encoded_value_5_byte_endianness, 5, 0x0123_4567, [0xFE, 0x67, 0x45, 0x23, 0x01];
+ encoded_value_5_byte_upper_bound, 5, 0xFFFF_FFFF, [0xFE, 0xFF, 0xFF, 0xFF, 0xFF];
+ }
+
+ // Only test on platforms with a usize that is 64 bits
+ #[cfg(target_pointer_width = "64")]
+ check_encode! {
+ // 9 byte encoding.
+ encoded_value_9_byte_lower_bound, 9, 0x0000_0001_0000_0000, [0xFF, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00];
+ encoded_value_9_byte_endianness, 9, 0x0123_4567_89AB_CDEF, [0xFF, 0xEF, 0xCD, 0xAB, 0x89, 0x67, 0x45, 0x23, 0x01];
+ encoded_value_9_byte_upper_bound, 9, u64::MAX, [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF];
+ }
+
+ #[test]
+ fn compact_size_new_values_too_large() {
+ use CompactSizeDecoderErrorInner as E;
+
+ const EXCESS_VEC_SIZE: u64 = (MAX_VEC_SIZE + 1) as u64; // can't use try_from for const
+
+ // MAX_VEC_SIZE should succeed for `new` constructor
+ let mut decoder = CompactSizeDecoder::new();
+ decoder.push_bytes(&mut [0xFE, 0x00, 0x09, 0x3D, 0x00].as_slice()).unwrap();
+ let got = decoder.end().unwrap();
+ assert_eq!(got, MAX_VEC_SIZE);
+
+ // MAX_VEC_SIZE + 1 should fail for `new` constructor
+ let mut decoder = CompactSizeDecoder::new();
+ decoder.push_bytes(&mut [0xFE, 0x01, 0x09, 0x3D, 0x00].as_slice()).unwrap();
+ let got = decoder.end().unwrap_err();
+ assert!(matches!(
+ got,
+ CompactSizeDecoderError(E::ValueExceedsLimit(
+ LengthPrefixExceedsMaxError {
+ limit: MAX_VEC_SIZE,
+ value: EXCESS_VEC_SIZE,
+ }
+ )),
+ ));
+ }
+
+ #[test]
+ fn compact_size_new_with_limit_values_too_large() {
+ use CompactSizeDecoderErrorInner as E;
+
+ // 240 should succeed for `new_with_limit` constructor
+ let mut decoder = CompactSizeDecoder::new_with_limit(240);
+ decoder.push_bytes(&mut [0xf0].as_slice()).unwrap();
+ let got = decoder.end().unwrap();
+ assert_eq!(got, 240);
+
+ // 241 should fail for `new_with_limit` constructor
+ let mut decoder = CompactSizeDecoder::new_with_limit(240);
+ decoder.push_bytes(&mut [0xf1].as_slice()).unwrap();
+ let got = decoder.end().unwrap_err();
+ assert!(matches!(
+ got,
+ CompactSizeDecoderError(E::ValueExceedsLimit(
+ LengthPrefixExceedsMaxError {
+ limit: 240,
+ value: 241,
+ }
+ )),
+ ));
+ }
+}
diff --git a/consensus_encoding/src/decode/decoders.rs b/consensus_encoding/src/decode/decoders.rs
index 36fba50e..4d85e112 100644
--- a/consensus_encoding/src/decode/decoders.rs
+++ b/consensus_encoding/src/decode/decoders.rs
@@ -14,11 +14,8 @@ use internals::write_err;
use super::Decodable;
use super::Decoder;
-/// Maximum size, in bytes, of a vector we are allowed to decode.
-///
-/// This is also the default value limit that can be decoded with a decoder from
-/// [`CompactSizeDecoder::new`].
-const MAX_VEC_SIZE: usize = 4_000_000;
+#[cfg(feature = "alloc")]
+use crate::compact_size::{CompactSizeDecoder, CompactSizeDecoderError};
/// Maximum amount of memory (in bytes) to allocate at once when deserializing vectors.
#[cfg(feature = "alloc")]
@@ -682,195 +679,6 @@ where
fn read_limit(&self) -> usize { self.inner.read_limit() }
}
-/// Decodes a compact size encoded integer.
-///
-/// For more information about decoder see the documentation of the [`Decoder`] trait.
-#[derive(Debug, Clone)]
-pub struct CompactSizeDecoder {
- buf: internals::array_vec::ArrayVec<u8, 9>,
- limit: usize,
-}
-
-impl CompactSizeDecoder {
- /// Constructs a new compact size decoder.
- ///
- /// Consensus encoded vectors can be up to 4,000,000 bytes long.
- /// This is a theoretical max since block size is 4 meg wu and minimum vector element is one byte.
- ///
- /// The final call to [`CompactSizeDecoder::end`] on this decoder will fail if the
- /// decoded value exceeds 4,000,000 or won't fit in a `usize`.
- pub const fn new() -> Self {
- Self { buf: internals::array_vec::ArrayVec::new(), limit: MAX_VEC_SIZE }
- }
-
- /// Constructs a new compact size decoder with encoded value limited to the provided usize.
- ///
- /// The final call to [`CompactSizeDecoder::end`] on this decoder will fail if the
- /// decoded value exceeds `limit` or won't fit in a `usize`.
- pub const fn new_with_limit(limit: usize) -> Self {
- Self { buf: internals::array_vec::ArrayVec::new(), limit }
- }
-}
-
-impl Default for CompactSizeDecoder {
- fn default() -> Self { Self::new() }
-}
-
-impl Decoder for CompactSizeDecoder {
- type Output = usize;
- type Error = CompactSizeDecoderError;
-
- fn push_bytes(&mut self, bytes: &mut &[u8]) -> Result<bool, Self::Error> {
- if bytes.is_empty() {
- return Ok(true);
- }
-
- if self.buf.is_empty() {
- self.buf.push(bytes[0]);
- *bytes = &bytes[1..];
- }
- let len = match self.buf[0] {
- 0xFF => 9,
- 0xFE => 5,
- 0xFD => 3,
- _ => 1,
- };
- let to_copy = bytes.len().min(len - self.buf.len());
- self.buf.extend_from_slice(&bytes[..to_copy]);
- *bytes = &bytes[to_copy..];
-
- Ok(self.buf.len() != len)
- }
-
- fn end(self) -> Result<Self::Output, Self::Error> {
- use CompactSizeDecoderErrorInner as E;
-
- fn arr<const N: usize>(slice: &[u8]) -> Result<[u8; N], CompactSizeDecoderError> {
- slice.try_into().map_err(|_| {
- CompactSizeDecoderError(E::UnexpectedEof { required: N, received: slice.len() })
- })
- }
-
- let (first, payload) = self
- .buf
- .split_first()
- .ok_or(CompactSizeDecoderError(E::UnexpectedEof { required: 1, received: 0 }))?;
-
- let dec_value = match *first {
- 0xFF => {
- let x = u64::from_le_bytes(arr(payload)?);
- if x < 0x100_000_000 {
- Err(CompactSizeDecoderError(E::NonMinimal { value: x }))
- } else {
- Ok(x)
- }
- }
- 0xFE => {
- let x = u32::from_le_bytes(arr(payload)?);
- if x < 0x10000 {
- Err(CompactSizeDecoderError(E::NonMinimal { value: x.into() }))
- } else {
- Ok(x.into())
- }
- }
- 0xFD => {
- let x = u16::from_le_bytes(arr(payload)?);
- if x < 0xFD {
- Err(CompactSizeDecoderError(E::NonMinimal { value: x.into() }))
- } else {
- Ok(x.into())
- }
- }
- n => Ok(n.into()),
- }?;
-
- // This error is returned if dec_value is outside of the usize range, or
- // if it is above the given limit.
- let make_err = || {
- CompactSizeDecoderError(E::ValueExceedsLimit(LengthPrefixExceedsMaxError {
- value: dec_value,
- limit: self.limit,
- }))
- };
-
- usize::try_from(dec_value).map_err(|_| make_err()).and_then(|nsize| {
- if nsize > self.limit {
- Err(make_err())
- } else {
- Ok(nsize)
- }
- })
- }
-
- fn read_limit(&self) -> usize {
- match self.buf.len() {
- 0 => 1,
- already_read => match self.buf[0] {
- 0xFF => 9_usize.saturating_sub(already_read),
- 0xFE => 5_usize.saturating_sub(already_read),
- 0xFD => 3_usize.saturating_sub(already_read),
- _ => 0,
- },
- }
- }
-}
-
-/// An error consensus decoding a compact size encoded integer.
-#[derive(Debug, Clone, PartialEq, Eq)]
-pub struct CompactSizeDecoderError(CompactSizeDecoderErrorInner);
-
-#[derive(Debug, Clone, PartialEq, Eq)]
-enum CompactSizeDecoderErrorInner {
- /// Returned when the decoder reaches end of stream (EOF).
- UnexpectedEof {
- /// How many bytes were required.
- required: usize,
- /// How many bytes were received.
- received: usize,
- },
- /// Returned when the encoding is not minimal
- NonMinimal {
- /// The encoded value.
- value: u64,
- },
- /// Returned when the encoded value exceeds the decoder's limit.
- ValueExceedsLimit(LengthPrefixExceedsMaxError),
-}
-
-impl fmt::Display for CompactSizeDecoderError {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- use CompactSizeDecoderErrorInner as E;
-
- match self.0 {
- E::UnexpectedEof { required: 1, received: 0 } => {
- write!(f, "required at least one byte but the input is empty")
- }
- E::UnexpectedEof { required, received: 0 } => {
- write!(f, "required at least {} bytes but the input is empty", required)
- }
- E::UnexpectedEof { required, received } => write!(
- f,
- "required at least {} bytes but only {} bytes were received",
- required, received
- ),
- E::NonMinimal { value } => write!(f, "the value {} was not encoded minimally", value),
- E::ValueExceedsLimit(ref e) => write_err!(f, "value exceeds limit"; e),
- }
- }
-}
-
-#[cfg(feature = "std")]
-impl std::error::Error for CompactSizeDecoderError {
- fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
- use CompactSizeDecoderErrorInner as E;
-
- match self {
- Self(E::ValueExceedsLimit(ref e)) => Some(e),
- _ => None,
- }
- }
-}
-
/// The error returned by the [`ByteVecDecoder`].
#[cfg(feature = "alloc")]
#[derive(Debug, Clone, PartialEq, Eq)]
@@ -967,24 +775,6 @@ where
}
}
-/// Length prefix exceeds the configured limit.
-#[derive(Debug, Clone, PartialEq, Eq)]
-pub struct LengthPrefixExceedsMaxError {
- /// Decoded value of the compact encoded length prefix.
- value: u64,
- /// The value limit that the length prefix exceeds.
- limit: usize,
-}
-
-impl core::fmt::Display for LengthPrefixExceedsMaxError {
- fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
- write!(f, "length prefix {} exceeds max value {}", self.value, self.limit)
- }
-}
-
-#[cfg(feature = "std")]
-impl std::error::Error for LengthPrefixExceedsMaxError {}
-
/// Not enough bytes given to decoder.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct UnexpectedEofError {
@@ -1102,60 +892,9 @@ mod tests {
#[cfg(feature = "alloc")]
use alloc::vec::Vec;
+ #[cfg(feature = "alloc")]
use super::*;
- #[test]
- fn compact_size_new_values_too_large() {
- use CompactSizeDecoderErrorInner as E;
-
- const EXCESS_VEC_SIZE: u64 = (MAX_VEC_SIZE + 1) as u64; // can't use try_from for const
-
- // MAX_VEC_SIZE should succeed for `new` constructor
- let mut decoder = CompactSizeDecoder::new();
- decoder.push_bytes(&mut [0xFE, 0x00, 0x09, 0x3D, 0x00].as_slice()).unwrap();
- let got = decoder.end().unwrap();
- assert_eq!(got, MAX_VEC_SIZE);
-
- // MAX_VEC_SIZE + 1 should fail for `new` constructor
- let mut decoder = CompactSizeDecoder::new();
- decoder.push_bytes(&mut [0xFE, 0x01, 0x09, 0x3D, 0x00].as_slice()).unwrap();
- let got = decoder.end().unwrap_err();
- assert!(matches!(
- got,
- CompactSizeDecoderError(E::ValueExceedsLimit(
- LengthPrefixExceedsMaxError {
- limit: MAX_VEC_SIZE,
- value: EXCESS_VEC_SIZE,
- }
- )),
- ));
- }
-
- #[test]
- fn compact_size_new_with_limit_values_too_large() {
- use CompactSizeDecoderErrorInner as E;
-
- // 240 should succeed for `new_with_limit` constructor
- let mut decoder = CompactSizeDecoder::new_with_limit(240);
- decoder.push_bytes(&mut [0xf0].as_slice()).unwrap();
- let got = decoder.end().unwrap();
- assert_eq!(got, 240);
-
- // 241 should fail for `new_with_limit` constructor
- let mut decoder = CompactSizeDecoder::new_with_limit(240);
- decoder.push_bytes(&mut [0xf1].as_slice()).unwrap();
- let got = decoder.end().unwrap_err();
- assert!(matches!(
- got,
- CompactSizeDecoderError(E::ValueExceedsLimit(
- LengthPrefixExceedsMaxError {
- limit: 240,
- value: 241,
- }
- )),
- ));
- }
-
#[test]
#[cfg(feature = "alloc")]
fn byte_vec_decoder_decode_empty_slice() {
diff --git a/consensus_encoding/src/encode/encoders.rs b/consensus_encoding/src/encode/encoders.rs
index 297d9e15..5549b22b 100644
--- a/consensus_encoding/src/encode/encoders.rs
+++ b/consensus_encoding/src/encode/encoders.rs
@@ -14,12 +14,8 @@
use core::fmt;
-use internals::array_vec::ArrayVec;
-
use super::{Encodable, Encoder, ExactSizeEncoder};
-
-/// The maximum length of a compact size encoding.
-const SIZE: usize = 9;
+pub use crate::compact_size::CompactSizeEncoder;
/// An encoder for a single byte slice.
#[derive(Debug, Clone)]
@@ -269,133 +265,4 @@ define_encoder_n! {
(3, D, enc_4), (4, E, enc_5), (5, F, enc_6),
}
-/// Encoder for a compact size encoded integer.
-#[derive(Debug, Clone)]
-pub struct CompactSizeEncoder {
- buf: Option<ArrayVec<u8, SIZE>>,
-}
-
-impl CompactSizeEncoder {
- /// Constructs a new `CompactSizeEncoder`.
- ///
- /// Encodings are defined only for the range of u64. On systems where usize is
- /// larger than u64, it will be possible to call this method with out-of-range
- /// values. In such cases we will ignore the passed value and encode [`u64::MAX`].
- /// But even on such exotic systems, we expect users to pass the length of an
- /// in-memory object, meaning that such large values are impossible to obtain.
- pub fn new(value: usize) -> Self { Self { buf: Some(Self::encode(value)) } }
-
- /// Returns the number of bytes used to encode this `CompactSize` value.
- ///
- /// # Returns
- ///
- /// - 1 for 0..=0xFC
- /// - 3 for 0xFD..=(2^16-1)
- /// - 5 for 0x10000..=(2^32-1)
- /// - 9 otherwise.
- #[inline]
- pub const fn encoded_size(value: usize) -> usize {
- match value {
- 0..=0xFC => 1,
- 0xFD..=0xFFFF => 3,
- 0x10000..=0xFFFF_FFFF => 5,
- _ => 9,
- }
- }
-
- /// Encodes `CompactSize` without allocating.
- #[inline]
- fn encode(value: usize) -> ArrayVec<u8, SIZE> {
- let mut res = ArrayVec::<u8, SIZE>::new();
- match value {
- 0..=0xFC => {
- res.push(value as u8); // Cast ok because of match.
- }
- 0xFD..=0xFFFF => {
- let v = value as u16; // Cast ok because of match.
- res.push(0xFD);
- res.extend_from_slice(&v.to_le_bytes());
- }
- 0x10000..=0xFFFF_FFFF => {
- let v = value as u32; // Cast ok because of match.
- res.push(0xFE);
- res.extend_from_slice(&v.to_le_bytes());
- }
- _ => {
- res.push(0xFF);
- res.extend_from_slice(&value.to_le_bytes());
- }
- }
- res
- }
-}
-
-impl Encoder for CompactSizeEncoder {
- #[inline]
- fn current_chunk(&self) -> &[u8] { self.buf.as_ref().map(|b| &b[..]).unwrap_or_default() }
- #[inline]
- fn advance(&mut self) -> bool {
- self.buf = None;
- false
- }
-}
-
-impl ExactSizeEncoder for CompactSizeEncoder {
- #[inline]
- fn len(&self) -> usize { self.buf.map_or(0, |buf| buf.len()) }
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- #[test]
- fn encoded_value_1_byte() {
- // Check lower bound, upper bound (and implicitly endian-ness).
- for v in [0x00, 0x01, 0x02, 0xFA, 0xFB, 0xFC] {
- let v = v as usize;
- assert_eq!(CompactSizeEncoder::encoded_size(v), 1);
- // Should be encoded as the value as a u8.
- let want = [v as u8];
- let got = CompactSizeEncoder::encode(v);
- assert_eq!(got.as_slice().len(), 1); // sanity check
- assert_eq!(got.as_slice(), want);
- }
- }
-
- macro_rules! check_encode {
- ($($test_name:ident, $size:expr, $value:expr, $want:expr);* $(;)?) => {
- $(
- #[test]
- fn $test_name() {
- let value = $value as usize; // Because default integer type is i32.
- assert_eq!(CompactSizeEncoder::encoded_size(value), $size);
- let got = CompactSizeEncoder::encode(value);
- assert_eq!(got.as_slice().len(), $size); // sanity check
- assert_eq!(got.as_slice(), &$want);
- }
- )*
- }
- }
-
- check_encode! {
- // 3 byte encoding.
- encoded_value_3_byte_lower_bound, 3, 0xFD, [0xFD, 0xFD, 0x00]; // 0x00FD
- encoded_value_3_byte_endianness, 3, 0xABCD, [0xFD, 0xCD, 0xAB];
- encoded_value_3_byte_upper_bound, 3, 0xFFFF, [0xFD, 0xFF, 0xFF];
- // 5 byte encoding.
- encoded_value_5_byte_lower_bound, 5, 0x0001_0000, [0xFE, 0x00, 0x00, 0x01, 0x00];
- encoded_value_5_byte_endianness, 5, 0x0123_4567, [0xFE, 0x67, 0x45, 0x23, 0x01];
- encoded_value_5_byte_upper_bound, 5, 0xFFFF_FFFF, [0xFE, 0xFF, 0xFF, 0xFF, 0xFF];
- }
-
- // Only test on platforms with a usize that is 64 bits
- #[cfg(target_pointer_width = "64")]
- check_encode! {
- // 9 byte encoding.
- encoded_value_9_byte_lower_bound, 9, 0x0000_0001_0000_0000, [0xFF, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00];
- encoded_value_9_byte_endianness, 9, 0x0123_4567_89AB_CDEF, [0xFF, 0xEF, 0xCD, 0xAB, 0x89, 0x67, 0x45, 0x23, 0x01];
- encoded_value_9_byte_upper_bound, 9, u64::MAX, [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF];
- }
-}
diff --git a/consensus_encoding/src/lib.rs b/consensus_encoding/src/lib.rs
index 53d4203f..1f3cb203 100644
--- a/consensus_encoding/src/lib.rs
+++ b/consensus_encoding/src/lib.rs
@@ -56,17 +56,19 @@ extern crate alloc;
#[cfg(feature = "std")]
extern crate std;
+mod compact_size;
mod decode;
mod encode;
-pub use self::decode::decoders::{
- ArrayDecoder, CompactSizeDecoder, CompactSizeDecoderError, Decoder2, Decoder2Error, Decoder3,
- Decoder3Error, Decoder4, Decoder4Error, Decoder6, Decoder6Error, UnexpectedEofError,
-};
+pub use self::compact_size::{CompactSizeDecoder, CompactSizeDecoderError};
#[cfg(feature = "alloc")]
+pub use self::compact_size::LengthPrefixExceedsMaxError;
pub use self::decode::decoders::{
- ByteVecDecoder, ByteVecDecoderError, LengthPrefixExceedsMaxError, VecDecoder, VecDecoderError,
+ ArrayDecoder, Decoder2, Decoder2Error, Decoder3, Decoder3Error, Decoder4, Decoder4Error,
+ Decoder6, Decoder6Error, UnexpectedEofError,
};
+#[cfg(feature = "alloc")]
+pub use self::decode::decoders::{ByteVecDecoder, ByteVecDecoderError, VecDecoder, VecDecoderError};
#[cfg(feature = "std")]
pub use self::decode::{
decode_from_read, decode_from_read_unbuffered, decode_from_read_unbuffered_with, ReadError,
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.