consensus_encoding: add more encode/decode tests
What changed, and why it matters
This commit only adds new unit tests for encoding and decoding routines in the rust-bitcoin consensus_encoding crate. It does not change any production code, fix bugs, or alter behavior. The tests cover corner cases such as zero-length arrays, empty slices, boundary values for compact-size integers, and state transitions in combined encoders/decoders.
No security action needed. Treat as routine test coverage improvement.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The diff is limited to two test files (consensus_encoding/tests/decode.rs and consensus_encoding/tests/encode.rs). It imports additional test helpers and adds roughly 220 lines of new test cases exercising ArrayDecoder/Encoder, Decoder2/Encoder2, CompactSizeDecoder/Encoder, ByteVecDecoder, SliceEncoder, and cast_to_usize_if_valid. No library source files are modified, and no runtime behavior changes.
Changed components
consensus_encoding/tests/decode.rsconsensus_encoding/tests/encode.rsInspect captured patch +224 / −3
diff --git a/consensus_encoding/tests/decode.rs b/consensus_encoding/tests/decode.rs
index 1f0c5cff..958411a9 100644
--- a/consensus_encoding/tests/decode.rs
+++ b/consensus_encoding/tests/decode.rs
@@ -2,7 +2,9 @@
//! Integration tests for decode module.
-use bitcoin_consensus_encoding::{ArrayDecoder, Decoder, UnexpectedEofError};
+use bitcoin_consensus_encoding::{
+ ArrayDecoder, CompactSizeDecoder, Decoder, Decoder2, UnexpectedEofError,
+};
const EMPTY: &[u8] = &[];
@@ -52,3 +54,143 @@ fn decode_array_insufficient_data_error() {
let err = decoder.end().unwrap_err();
assert!(matches!(err, UnexpectedEofError { .. }));
}
+
+#[test]
+fn decode_array_zero_size() {
+ // Test zero-sized array decoder which doesn't consume any bytes.
+ let mut decoder = ArrayDecoder::<0>::new();
+ let mut data = &[0x01, 0x02, 0x03][..];
+
+ let needs_more = decoder.push_bytes(&mut data).unwrap();
+ assert!(!needs_more, "zero-sized ArrayDecoder should not need data");
+ assert_eq!(data, &[0x01, 0x02, 0x03]);
+ let result = decoder.end().unwrap();
+ assert_eq!(result, [0u8; 0]);
+
+ // read_limit should be 0.
+ let decoder = ArrayDecoder::<0>::new();
+ assert_eq!(decoder.read_limit(), 0);
+}
+
+#[test]
+fn decode_array_empty_slice_push() {
+ // Test pushing empty slices to ArrayDecoder.
+ let mut decoder = ArrayDecoder::<3>::new();
+ let mut empty_data = &[][..];
+
+ let needs_more = decoder.push_bytes(&mut empty_data).unwrap();
+ assert!(needs_more, "decoder should still need data after empty push");
+ assert_eq!(empty_data, &[0u8; 0]);
+ assert_eq!(decoder.read_limit(), 3);
+}
+
+#[test]
+fn decode_decoder2_state_transitions() {
+ // Test the state transition point boundry in Decoder2.
+ let mut decoder: Decoder2<ArrayDecoder<2>, ArrayDecoder<3>> =
+ Decoder2::new(ArrayDecoder::<2>::new(), ArrayDecoder::<3>::new());
+
+ assert_eq!(decoder.read_limit(), 5);
+ let mut data = &[0x01, 0x02][..];
+ let needs_more = decoder.push_bytes(&mut data).unwrap();
+ assert!(needs_more, "should need more data for second decoder");
+ assert_eq!(data.len(), 0, "all data should be consumed");
+
+ assert_eq!(decoder.read_limit(), 3);
+ let mut more_data = &[0x03, 0x04, 0x05][..];
+ let needs_more = decoder.push_bytes(&mut more_data).unwrap();
+ assert!(!needs_more, "should not need more data after completing both decoders");
+ assert_eq!(more_data.len(), 0, "all data should be consumed");
+
+ assert_eq!(decoder.read_limit(), 0);
+ let (first_result, second_result) = decoder.end().unwrap();
+ assert_eq!(first_result, [0x01, 0x02]);
+ assert_eq!(second_result, [0x03, 0x04, 0x05]);
+}
+
+#[test]
+fn decode_decoder2_read_limit_with_exhausted() {
+ // Test read_limit calculation when first decoder needs 0 bytes.
+ let decoder1: Decoder2<ArrayDecoder<0>, ArrayDecoder<5>> =
+ Decoder2::new(ArrayDecoder::<0>::new(), ArrayDecoder::<5>::new());
+ assert_eq!(decoder1.read_limit(), 5);
+
+ let mut decoder2: Decoder2<ArrayDecoder<2>, ArrayDecoder<3>> =
+ Decoder2::new(ArrayDecoder::<2>::new(), ArrayDecoder::<3>::new());
+ let mut data = &[0x01, 0x02][..];
+ let needs_more = decoder2.push_bytes(&mut data).unwrap();
+ assert!(needs_more, "should need more data for second decoder");
+ assert_eq!(data.len(), 0, "all data should be consumed");
+ assert_eq!(decoder2.read_limit(), 3);
+}
+
+#[test]
+fn decode_compact_size_read_limit_transitions() {
+ // Test read_limit behavior during compact size decoding.
+ let mut decoder = CompactSizeDecoder::default();
+
+ assert_eq!(decoder.read_limit(), 1);
+ let mut data = &[0xFD][..];
+ let needs_more = decoder.push_bytes(&mut data).unwrap();
+ assert!(needs_more, "should need more data after seeing 0xFD");
+ assert_eq!(data.len(), 0, "all data should be consumed");
+
+ assert_eq!(decoder.read_limit(), 2);
+ let mut data = &[0x00][..];
+ let needs_more = decoder.push_bytes(&mut data).unwrap();
+ assert!(needs_more, "should still need one more byte");
+ assert_eq!(data.len(), 0, "all data should be consumed");
+ assert_eq!(decoder.read_limit(), 1);
+
+ let mut data = &[0x01][..];
+ let needs_more = decoder.push_bytes(&mut data).unwrap();
+ assert!(!needs_more, "should not need more data");
+ assert_eq!(data.len(), 0, "all data should be consumed");
+ assert_eq!(decoder.read_limit(), 0);
+
+ let result = decoder.end().unwrap();
+ assert_eq!(result, 256);
+}
+
+#[test]
+fn decode_compact_size_single_byte_read_limit() {
+ // Test read_limit for single-byte compact size.
+ let mut decoder = CompactSizeDecoder::default();
+
+ assert_eq!(decoder.read_limit(), 1);
+ let mut data = &[0x42][..];
+ let needs_more = decoder.push_bytes(&mut data).unwrap();
+ assert!(!needs_more, "single-byte value should be complete");
+ assert_eq!(data.len(), 0, "all data should be consumed");
+ assert_eq!(decoder.read_limit(), 0);
+ let result = decoder.end().unwrap();
+ assert_eq!(result, 66);
+}
+
+#[cfg(feature = "alloc")]
+#[test]
+fn decode_cast_to_usize_boundary_conditions() {
+ // Test the 4MB boundary and some edge cases.
+ use bitcoin_consensus_encoding::cast_to_usize_if_valid;
+
+ assert!(cast_to_usize_if_valid(4_000_000).is_ok());
+ assert!(cast_to_usize_if_valid(4_000_001).is_err());
+ assert!(cast_to_usize_if_valid(u64::MAX).is_err());
+ assert_eq!(cast_to_usize_if_valid(0).unwrap(), 0);
+}
+
+#[cfg(feature = "alloc")]
+#[test]
+fn decode_byte_vec_decoder_empty() {
+ // Test decoding empty byte vector, with length prefix of 0.
+ use bitcoin_consensus_encoding::{ByteVecDecoder, Decoder};
+
+ let mut decoder = ByteVecDecoder::new();
+ let mut data = &[0x00][..];
+ let needs_more = decoder.push_bytes(&mut data).unwrap();
+ assert!(!needs_more, "decoder should not need more data for empty vector");
+ assert_eq!(data.len(), 0, "all data should be consumed");
+ assert_eq!(decoder.read_limit(), 0);
+ let result = decoder.end().unwrap();
+ assert!(result.is_empty());
+}
diff --git a/consensus_encoding/tests/encode.rs b/consensus_encoding/tests/encode.rs
index d8f08d06..0b9846ba 100644
--- a/consensus_encoding/tests/encode.rs
+++ b/consensus_encoding/tests/encode.rs
@@ -5,9 +5,11 @@
#[cfg(feature = "std")]
use std::io::{Cursor, Write};
+use bitcoin_consensus_encoding::{
+ ArrayEncoder, BytesEncoder, CompactSizeEncoder, Encoder, Encoder2,
+};
#[cfg(feature = "alloc")]
-use bitcoin_consensus_encoding::Encodable;
-use bitcoin_consensus_encoding::{ArrayEncoder, BytesEncoder, Encoder};
+use bitcoin_consensus_encoding::{Encodable, SliceEncoder};
// Simple test type that implements Encodable.
#[cfg(feature = "alloc")]
@@ -119,3 +121,80 @@ fn encode_newtype_lifetime_flexibility() {
assert_eq!(custom_encoder.current_chunk(), test_data.as_slice());
assert_eq!(no_lifetime_encoder.current_chunk(), &[1, 2, 3, 4][..]);
}
+
+#[cfg(feature = "alloc")]
+#[test]
+fn encode_slice_encoder_mixed_empty_and_data() {
+ // Test SliceEncoder behavior with mixed empty and non-empty elements.
+ struct TestBytes(Vec<u8>);
+
+ impl Encodable for TestBytes {
+ type Encoder<'s>
+ = BytesEncoder<'s>
+ where
+ Self: 's;
+
+ fn encoder(&self) -> Self::Encoder<'_> { BytesEncoder::without_length_prefix(&self.0) }
+ }
+
+ let slice = &[TestBytes(vec![]), TestBytes(vec![1, 2]), TestBytes(vec![]), TestBytes(vec![3])];
+
+ let mut encoder = SliceEncoder::without_length_prefix(slice);
+
+ assert!(encoder.current_chunk().is_empty());
+ assert!(encoder.advance());
+ assert_eq!(encoder.current_chunk(), &[1, 2]);
+ assert!(encoder.advance());
+ assert_eq!(encoder.current_chunk(), &[3]);
+ assert!(!encoder.advance());
+ assert!(encoder.current_chunk().is_empty());
+}
+
+#[test]
+fn encode_compact_size_boundary_values() {
+ // Test CompactSizeEncoder with boundary values.
+ let mut encoder = CompactSizeEncoder::new(252u32);
+ assert_eq!(encoder.current_chunk(), &[252]);
+ assert!(!encoder.advance());
+
+ let mut encoder = CompactSizeEncoder::new(253u32);
+ assert_eq!(encoder.current_chunk(), &[0xFD, 253, 0]);
+ assert!(!encoder.advance());
+
+ let mut encoder = CompactSizeEncoder::new(0x10000u32);
+ assert_eq!(encoder.current_chunk(), &[0xFE, 0, 0, 1, 0]);
+ assert!(!encoder.advance());
+
+ let mut encoder = CompactSizeEncoder::new(0u32);
+ assert_eq!(encoder.current_chunk(), &[0]);
+ assert!(!encoder.advance());
+}
+
+#[test]
+fn encode_encoder2_with_first_empty_encoder() {
+ // Test Encoder2 when first encoder produces no data.
+ let enc1 = ArrayEncoder::<0>::without_length_prefix([]);
+ let enc2 = ArrayEncoder::<3>::without_length_prefix([1, 2, 3]);
+
+ let mut encoder = Encoder2::new(enc1, enc2);
+
+ assert!(encoder.current_chunk().is_empty());
+ assert!(encoder.advance());
+ assert_eq!(encoder.current_chunk(), &[1, 2, 3]);
+ assert!(!encoder.advance());
+ assert!(encoder.current_chunk().is_empty());
+}
+
+#[test]
+fn encode_encoder_advance_multiple_times_when_exhausted() {
+ // Test that calling advance() multiple times on exhausted encoder is safe.
+ let mut encoder = ArrayEncoder::<2>::without_length_prefix([10, 20]);
+
+ assert_eq!(encoder.current_chunk(), &[10, 20]);
+ assert!(!encoder.advance());
+ assert!(encoder.current_chunk().is_empty());
+ assert!(!encoder.advance());
+ assert!(!encoder.advance());
+ assert!(!encoder.advance());
+ assert!(encoder.current_chunk().is_empty());
+}
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.