Skip to main content

StreamHeader

Struct StreamHeader 

pub struct StreamHeader {
    pub offset: u32,
    pub size: u32,
    pub name: String,
}
Expand description

ECMA-335 compliant stream header providing metadata stream location and identification.

A stream header describes a single metadata stream within a .NET assembly’s metadata directory. Each header contains the stream’s file offset, size, and identifying name according to ECMA-335 Section II.24.2.2. The structure enables runtime and analysis tools to locate and validate specific metadata streams (strings, blobs, GUIDs, tables) within assembly files.

§Binary Layout

Stream headers have a variable-length binary format:

[0-3]   u32     Stream offset from metadata root (4-byte aligned)
[4-7]   u32     Stream size in bytes (must be multiple of 4)  
[8-N]   CStr    Null-terminated ASCII stream name (max 32 chars)

§Standard Stream Names

The ECMA-335 specification defines six standard stream names:

  • #Strings: UTF-8 identifier strings (type/member names)
  • #US: UTF-16 user string literals (string constants)
  • #Blob: Variable-length binary data (signatures, attributes)
  • #GUID: 128-bit globally unique identifiers
  • #~: Compressed metadata tables (modern assemblies)
  • #-: Uncompressed metadata tables (legacy format)

§Validation Requirements

All stream headers must meet ECMA-335 compliance requirements:

  • Stream size must be 4-byte aligned (divisible by 4)
  • Offset and size must not exceed 0x7FFFFFFF (2GB limit)
  • Stream name must match one of the six standard names
  • Name must be null-terminated ASCII within 32 characters

§Thread Safety

StreamHeader instances are immutable after construction and safe for concurrent access. All fields contain owned data with no shared references or interior mutability.

§Examples

§Parsing a Metadata Table Stream Header

use dotscope::metadata::streams::StreamHeader;

// Binary data for "#~" (compressed tables) stream header
#[rustfmt::skip]
let header_data = [
    0x6C, 0x00, 0x00, 0x00,  // Offset: 0x6C (108 bytes)
    0xA4, 0x45, 0x00, 0x00,  // Size: 0x45A4 (17,828 bytes, 4-byte aligned)
    0x23, 0x7E, 0x00,        // Name: "#~\0" (compressed metadata tables)
];

let header = StreamHeader::from(&header_data)?;

assert_eq!(header.offset, 0x6C);
assert_eq!(header.size, 0x45A4);
assert_eq!(header.name, "#~");

println!("Compressed metadata tables at offset 0x{:X}, {} bytes",
         header.offset, header.size);

§Processing String Stream Headers

use dotscope::metadata::streams::StreamHeader;

// String stream header with null-terminated name
#[rustfmt::skip]
let strings_header = [
    0x20, 0x00, 0x00, 0x00,           // Offset: 0x20 (32 bytes)
    0x48, 0x12, 0x00, 0x00,           // Size: 0x1248 (4,680 bytes)
    0x23, 0x53, 0x74, 0x72, 0x69,     // "#Strings" name
    0x6E, 0x67, 0x73, 0x00,           // null-terminated
];

let header = StreamHeader::from(&strings_header)?;

assert_eq!(header.name, "#Strings");
assert!(header.size % 4 == 0); // ECMA-335 alignment requirement

match header.name.as_str() {
    "#Strings" => println!("Found identifier strings stream"),
    "#US" => println!("Found user strings stream"),
    "#Blob" => println!("Found binary data stream"),
    "#GUID" => println!("Found GUID stream"),
    "#~" => println!("Found compressed metadata tables"),
    "#-" => println!("Found uncompressed metadata tables"),
    _ => unreachable!("Invalid stream names are rejected during parsing"),
}

§Error Handling for Invalid Headers

use dotscope::metadata::streams::StreamHeader;

// Invalid header with unaligned size
#[rustfmt::skip]
let invalid_size = [
    0x6C, 0x00, 0x00, 0x00,  // Valid offset
    0xA5, 0x45, 0x00, 0x00,  // Invalid size (not divisible by 4)
    0x23, 0x7E, 0x00,        // Valid name
];

assert!(StreamHeader::from(&invalid_size).is_err());

// Invalid header with unknown stream name
#[rustfmt::skip]
let invalid_name = [
    0x6C, 0x00, 0x00, 0x00,  // Valid offset
    0xA4, 0x45, 0x00, 0x00,  // Valid size
    0x24, 0x58, 0x00,        // Invalid name "$X\0"
];

assert!(StreamHeader::from(&invalid_name).is_err());

§References

Fields§

§offset: u32

Byte offset from the metadata root to the start of this stream’s data.

The offset is relative to the beginning of the metadata section and must point to a valid location within the assembly file. Values must not exceed 0x7FFFFFFF (2GB limit) to prevent integer overflow attacks.

§ECMA-335 Compliance

  • Must be a valid file offset within the metadata section
  • Subject to reasonable bounds checking (≤ 2GB)
  • Points to 4-byte aligned stream data (implementation detail)
§size: u32

Size of this stream’s data in bytes, must be a multiple of 4 per ECMA-335.

The size represents the exact byte count of stream data. ECMA-335 Section II.24.2.2 states sizes should be 4-byte aligned, but many real-world tools produce unaligned sizes which are tolerated for compatibility.

§Validation Rules

  • Should be divisible by 4 per spec (unaligned values are tolerated)
  • Must not exceed 0x7FFFFFFF (2GB limit)
  • Zero size is valid for empty streams

§Security Considerations

  • Bounds checked to prevent integer overflow attacks
  • Combined with offset validation to prevent out-of-bounds access
§name: String

Stream identifier name, null-terminated ASCII string (maximum 32 characters).

The name uniquely identifies the stream type and must match one of the six standard stream names defined in ECMA-335. Names are case-sensitive and must be properly null-terminated within the 32-character limit.

§Valid Stream Names

  • #Strings: UTF-8 identifier strings (namespaces, type names, member names)
  • #US: UTF-16 user string literals (string constants from IL code)
  • #Blob: Variable-length binary data (method signatures, custom attributes)
  • #GUID: 128-bit globally unique identifiers (assembly correlation)
  • #~: Compressed metadata tables (modern .NET assemblies, space-efficient)
  • #-: Uncompressed metadata tables (legacy format, rarely used)

§Security and Validation

  • Only standard ECMA-335 names are accepted to prevent malformed metadata
  • ASCII encoding enforced during parsing to prevent encoding attacks
  • Maximum length limit prevents buffer overflow vulnerabilities

Implementations§

§

impl StreamHeader

pub fn from(data: &[u8]) -> Result<StreamHeader>

Parse a stream header from binary data according to ECMA-335 specification.

Creates a StreamHeader by parsing the binary format defined in ECMA-335 Section II.24.2.2. The method performs comprehensive validation to ensure compliance with the specification and protect against malformed metadata that could cause security vulnerabilities.

§Binary Format Parsed
Offset | Size | Field       | Description
-------|------|-------------|------------------------------------------
0      | 4    | Offset      | Stream data offset from metadata root (LE)
4      | 4    | Size        | Stream size in bytes (LE, 4-byte aligned)
8      | N+1  | Name        | Null-terminated ASCII name (max 32 chars)
§Validation Performed

This method enforces all ECMA-335 compliance requirements:

  1. Minimum size: Data must contain at least 9 bytes (8-byte header + 1 name byte)
  2. 4-byte alignment: Stream size must be divisible by 4
  3. Range limits: Offset and size must not exceed 0x7FFFFFFF (2GB)
  4. Valid stream names: Name must match one of six standard ECMA-335 stream identifiers
  5. Null termination: Name must be properly null-terminated within 32 characters
  6. ASCII encoding: Name must contain only valid ASCII characters
§Standard Stream Names
  • #Strings: UTF-8 identifier strings
  • #US: UTF-16 user string literals
  • #Blob: Variable-length binary data
  • #GUID: 128-bit globally unique identifiers
  • #~: Compressed metadata tables
  • #-: Uncompressed metadata tables
§Arguments
  • data - Binary data slice containing the stream header to parse
§Returns
  • Ok(StreamHeader) - Successfully parsed and validated stream header
  • Err(Error) - Parsing failed due to insufficient data or validation errors
§Errors

Returns crate::Error in the following cases:

  • [crate::Error::OutOfBounds]: Data slice too short (< 9 bytes)
  • Malformed data: Stream size not 4-byte aligned (ECMA-335 violation)
  • Range error: Offset or size exceeds 0x7FFFFFFF (integer overflow protection)
  • Invalid name: Stream name doesn’t match standard ECMA-335 identifiers
  • Format error: Name not properly null-terminated or contains non-ASCII
§Examples
§Parsing Valid Stream Headers
use dotscope::metadata::streams::StreamHeader;

// Compressed metadata tables stream header
#[rustfmt::skip]
let tables_header = [
    0x6C, 0x00, 0x00, 0x00,  // Offset: 0x6C
    0xA4, 0x45, 0x00, 0x00,  // Size: 0x45A4 (4-byte aligned)
    0x23, 0x7E, 0x00,        // Name: "#~\0"
];

let header = StreamHeader::from(&tables_header)?;
assert_eq!(header.name, "#~");
assert_eq!(header.offset, 0x6C);
assert_eq!(header.size, 0x45A4);
assert!(header.size % 4 == 0); // ECMA-335 alignment verified

// Strings stream header with longer name
#[rustfmt::skip]
let strings_header = [
    0x20, 0x00, 0x00, 0x00,           // Offset: 0x20
    0x48, 0x12, 0x00, 0x00,           // Size: 0x1248
    0x23, 0x53, 0x74, 0x72, 0x69,     // "#Strings"
    0x6E, 0x67, 0x73, 0x00,           // null-terminated
];

let header = StreamHeader::from(&strings_header)?;
assert_eq!(header.name, "#Strings");
§Error Cases and Validation
use dotscope::metadata::streams::StreamHeader;

// Error: Data too short
let too_short = [0x6C, 0x00, 0x00, 0x00, 0xA4]; // Only 5 bytes
assert!(StreamHeader::from(&too_short).is_err());

// Error: Size not 4-byte aligned
#[rustfmt::skip]
let unaligned = [
    0x6C, 0x00, 0x00, 0x00,  // Valid offset
    0xA5, 0x45, 0x00, 0x00,  // Size 0x45A5 (not divisible by 4)
    0x23, 0x7E, 0x00,        // Valid name
];
assert!(StreamHeader::from(&unaligned).is_err());

// Error: Invalid stream name
#[rustfmt::skip]
let invalid_name = [
    0x6C, 0x00, 0x00, 0x00,  // Valid offset
    0xA4, 0x45, 0x00, 0x00,  // Valid size
    0x24, 0x58, 0x00,        // Invalid name "$X\0"
];
assert!(StreamHeader::from(&invalid_name).is_err());

// Error: Offset too large (> 2GB)
#[rustfmt::skip]
let large_offset = [
    0xFF, 0xFF, 0xFF, 0xFF,  // Offset: 0xFFFFFFFF (> 0x7FFFFFFF)
    0xA4, 0x45, 0x00, 0x00,  // Valid size
    0x23, 0x7E, 0x00,        // Valid name
];
assert!(StreamHeader::from(&large_offset).is_err());
§Processing Multiple Headers
use dotscope::metadata::streams::StreamHeader;

// Directory with multiple stream headers
let directory_data = [
    // First header: "#~" stream
    0x6C, 0x00, 0x00, 0x00, 0xA4, 0x45, 0x00, 0x00, 0x23, 0x7E, 0x00, 0x00,
    // Second header would follow at proper alignment...
];

let mut offset = 0;
let mut streams = Vec::new();

while offset < directory_data.len() {
    match StreamHeader::from(&directory_data[offset..]) {
        Ok(header) => {
            println!("Found stream: {}", header.name);
            streams.push(header);
            // Calculate next header offset (implementation specific)
            break; // For this example
        }
        Err(_) => break,
    }
}
§ECMA-335 Compliance

This implementation fully complies with ECMA-335 Partition II, Section 24.2.2 including all validation requirements and format specifications.

§See Also

pub fn write_to<W: Write>(&self, writer: &mut W) -> Result<()>

Writes this stream header to a writer in ECMA-335 binary format.

Serializes the stream header including offset, size, and null-terminated name with proper 4-byte alignment padding according to ECMA-335 Section II.24.2.2.

§Binary Format Written
Offset | Size | Field  | Description
-------|------|--------|------------------------------------------
0      | 4    | Offset | Stream data offset from metadata root (LE)
4      | 4    | Size   | Stream size in bytes (LE)
8      | N    | Name   | Null-terminated ASCII name
8+N    | P    | Pad    | Zero padding to 4-byte boundary
§Arguments
§Returns
  • Ok(()) - Header written successfully
  • Err(Error) - Write operation failed
§Examples
use dotscope::metadata::streams::StreamHeader;

let header = StreamHeader {
    offset: 0x6C,
    size: 0x1000,
    name: "#~".to_string(),
};

let mut buffer = Vec::new();
header.write_to(&mut buffer)?;

// Buffer contains: offset (4) + size (4) + "#~\0" (3) + padding (1) = 12 bytes
assert_eq!(buffer.len(), 12);
§Errors

Returns an error if writing to the writer fails.

pub fn serialized_size(&self) -> usize

Returns the total serialized size of this stream header in bytes.

The size includes the fixed fields (offset + size = 8 bytes) plus the null-terminated name padded to a 4-byte boundary.

§Returns

The total size in bytes when written with write_to.

§Examples
use dotscope::metadata::streams::StreamHeader;

let header = StreamHeader {
    offset: 0x6C,
    size: 0x1000,
    name: "#~".to_string(),
};

// 8 (fixed) + ceil4("#~\0") = 8 + 4 = 12
assert_eq!(header.serialized_size(), 12);

let strings_header = StreamHeader {
    offset: 0x20,
    size: 0x500,
    name: "#Strings".to_string(),
};

// 8 (fixed) + ceil4("#Strings\0") = 8 + 12 = 20
assert_eq!(strings_header.serialized_size(), 20);

Trait Implementations§

§

impl Clone for StreamHeader

§

fn clone(&self) -> StreamHeader

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
§

impl Debug for StreamHeader

§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
§

impl Eq for StreamHeader

§

impl PartialEq for StreamHeader

§

fn eq(&self, other: &StreamHeader) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, and should not be overridden without very good reason.
§

impl StructuralPartialEq for StreamHeader

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> AsAny for T
where T: Any,

Source§

fn as_any(&self) -> &(dyn Any + 'static)

Source§

fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)

Source§

fn type_name(&self) -> &'static str

Gets the type name of self
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> Downcast for T
where T: AsAny + ?Sized,

Source§

fn is<T>(&self) -> bool
where T: AsAny,

Returns true if the boxed type is the same as T. Read more
Source§

fn downcast_ref<T>(&self) -> Option<&T>
where T: AsAny,

Forward to the method defined on the type Any.
Source§

fn downcast_mut<T>(&mut self) -> Option<&mut T>
where T: AsAny,

Forward to the method defined on the type Any.
Source§

impl<T> DynClone for T
where T: Clone,

Source§

fn __clone_box(&self, _: Private) -> *mut ()

Source§

impl<Q, K> Equivalent<K> for Q
where Q: Eq + ?Sized, K: Borrow<Q> + ?Sized,

Source§

fn equivalent(&self, key: &K) -> bool

Checks if this value is equivalent to the given key. Read more
Source§

impl<Q, K> Equivalent<K> for Q
where Q: Eq + ?Sized, K: Borrow<Q> + ?Sized,

Source§

fn equivalent(&self, key: &K) -> bool

Checks if this value is equivalent to the given key. Read more
Source§

impl<Q, K> Equivalent<K> for Q
where Q: Eq + ?Sized, K: Borrow<Q> + ?Sized,

Source§

fn equivalent(&self, key: &K) -> bool

Compare self to key and return true if they are equal.
Source§

impl<T> ErasedDestructor for T
where T: 'static,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, A> IntoAst<A> for T
where T: Into<A>, A: Ast,

Source§

fn into_ast(self, _a: &A) -> A

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

impl<F, T> IntoSample<T> for F
where T: FromSample<F>,

Source§

fn into_sample(self) -> T

Source§

impl<T> Pointable for T

Source§

const ALIGN: usize

The alignment of pointer.
Source§

type Init = T

The type for initializers.
Source§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
Source§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
Source§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
Source§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
Source§

impl<T> PolicyExt for T
where T: ?Sized,

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> Scalar for T
where T: 'static + Clone + PartialEq + Debug,

Source§

impl<SS, SP> SupersetOf<SS> for SP
where SS: SubsetOf<SP>,

Source§

fn to_subset(&self) -> Option<SS>

The inverse inclusion map: attempts to construct self from the equivalent element of its superset. Read more
Source§

fn is_in_subset(&self) -> bool

Checks if self is actually part of its subset T (and can be converted to it).
Source§

fn to_subset_unchecked(&self) -> SS

Use with care! Same as self.to_subset but without any property checks. Always succeeds.
Source§

fn from_subset(element: &SS) -> SP

The inclusion map: converts self to the equivalent element of its superset.
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T> TryClone for T
where T: Clone,

Source§

fn try_clone(&self) -> Result<T, Error>

Clones self, possibly returning an error.
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more