Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions crates/asn1-parser/src/string/bit_string.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,10 @@ impl BitString<'_> {
}

pub fn bits_amount(&self) -> usize {
(self.octets.as_ref().len() - 1) * 8 - usize::from(self.octets.as_ref()[0])
let data_len = self.octets.len() - 1;
let padding = usize::from(self.octets[0]);

(data_len * 8).saturating_sub(padding)
}

/// Creates a new [BitString] from amount of bits and actual bits buffer
Expand Down Expand Up @@ -100,7 +103,12 @@ impl<'data> Asn1ValueDecoder<'data> for BitString<'data> {
fn decode(_: Tag, reader: &mut Reader<'data>) -> Asn1Result<Self> {
let data = reader.read_remaining();

let inner = if !data.is_empty() {
if data.is_empty() {
return Err(Error::from("BitString must have at least one byte (unused bits)"));
}

let inner = if data.len() > 1 {
// Check len > 1 since first byte is unused bits
let mut inner_reader = Reader::new(&data[1..]);
inner_reader.set_next_id(reader.next_id());
inner_reader.set_offset(reader.full_offset() - data.len());
Expand Down
51 changes: 50 additions & 1 deletion crates/asn1-parser/tests/decode_encode.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::sync::Once;

use asn1_parser::{Asn1, Asn1Decoder, Asn1Encoder, Asn1Type, MetaInfo, ObjectIdentifier, Taggable};
use asn1_parser::{Asn1, Asn1Decoder, Asn1Encoder, Asn1Type, BitString, MetaInfo, ObjectIdentifier, Tag, Taggable};
use prop_strategies::any_asn1_type;
use proptest::proptest;

Expand Down Expand Up @@ -305,3 +305,52 @@ fn test_2() {
let asn1 = Asn1::decode_buff(raw).unwrap();
println!("{:?}", asn1);
}

#[test]
fn empty_bitstring() {
init_logging();

let raw = [0x03, 0x01, 0x00];
let asn1 = Asn1::decode_buff(&raw).expect("Failed to decode empty BitString");

assert_eq!(asn1.inner_asn1().tag(), Tag::from(3), "Tag should be 0x03");

if let Asn1Type::BitString(bitstring) = asn1.inner_asn1() {
assert_eq!(bitstring.raw_bits(), &[0], "Raw bits should be [0]");
assert_eq!(bitstring.bits_amount(), 0, "Bits amount should be 0");
assert!(bitstring.inner().is_none(), "Inner should be None");
} else {
panic!("Expected BitString type");
}

// Test encoding back
let mut encoded = vec![0; asn1.needed_buf_size()];
asn1.encode_buff(&mut encoded)
.expect("Failed to encode empty BitString");
assert_eq!(encoded, raw, "Encoded bytes should match original");

// Test creating an empty BitString directly
let empty_bits = BitString::from_raw_vec(0, vec![]).expect("Failed to create empty BitString");
assert_eq!(empty_bits.raw_bits(), &[0], "Created raw bits should be [0]");
assert_eq!(empty_bits.bits_amount(), 0, "Created bits amount should be 0");
assert!(empty_bits.inner().is_none(), "Created inner should be None");

// Test invalid decoding: BitString with length 0 (invalid in DER)
let invalid_raw = [0x03, 0x00];
assert!(
Asn1::decode_buff(&invalid_raw).is_err(),
"Decoding length 0 should fail"
);

// Test invalid creation: too many bits
assert!(
BitString::from_raw_vec(1, vec![]).is_err(),
"Creating with too many bits should fail"
);

// Test invalid creation: too many unused bits
assert!(
BitString::from_raw_vec(0, vec![0]).is_err(),
"Creating with excess unused bits should fail"
);
}
25 changes: 16 additions & 9 deletions src/asn1/scheme/strings.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::fmt::Write;

use asn1_parser::{
OwnedBitString, OwnedBmpString, OwnedGeneralString, OwnedIA5String, OwnedNumericString, OwnedOctetString,
OwnedPrintableString, OwnedRawAsn1EntityData, OwnedUtf8String, OwnedVisibleString,
Expand Down Expand Up @@ -68,15 +70,20 @@ pub struct BitStringNodeProps {

#[function_component(BitStringNode)]
pub fn bit_string(props: &BitStringNodeProps) -> Html {
let bits = props.node.raw_bits()[1..]
.iter()
.map(|byte| format!("{:08b}", byte))
.fold(String::new(), |mut ac, new| {
ac.push_str(&new);
ac
});
let raw_bits = props.node.raw_bits();
let bits_amount = props.node.bits_amount();
let bits = &bits[0..bits_amount];

let bits = if raw_bits.len() > 1 {
let mut bits = String::with_capacity((raw_bits.len() - 1) * 8);
for byte in &raw_bits[1..] {
write!(bits, "{:08b}", byte).unwrap();
}
bits
} else {
String::new()
};

let display_bits = &bits[0..bits_amount.min(bits.len())];

let offset = props.meta.tag_position();
let length_len = props.meta.length_range().len();
Expand All @@ -99,7 +106,7 @@ pub fn bit_string(props: &BitStringNodeProps) -> Html {
<div class="terminal-asn1-node">
<NodeOptions node_bytes={RcSlice::from(props.meta.raw_bytes())} {offset} {length_len} {data_len} name={String::from("BitString")} />
<span class="asn1-node-info-label">{format!("({} bits)", bits_amount)}</span>
<span class="asn-simple-value">{bits}</span>
<span class="asn-simple-value">{display_bits}</span>
</div>
}
}
Expand Down