ReadOnlySpanUtilities

Class/Module Overview

The ReadOnlySpanUtilities class provides utility functions for analyzing and manipulating ReadOnlySpan<byte> data. These methods complement the type conversion operations with analytical, statistical, and transformation capabilities designed for data inspection, quality analysis, and manipulation scenarios.

Purpose

  • Data analysis: Calculate entropy, analyze byte distributions, and gather statistics
  • Pattern detection: Find occurrences and analyze byte patterns
  • Data validation: Check for specific conditions (all zeros, all equal bytes)
  • Data transformation: Reverse, XOR, and other operations
  • Quality assurance: Entropy calculation for randomness testing

Capabilities

  • Shannon entropy calculation
  • Byte frequency distribution analysis
  • Occurrence counting and index finding
  • Equality and zero-checking operations
  • Byte reversal and XOR operations

API Documentation

Analysis Methods

CalculateEntropy

public static double CalculateEntropy(this ReadOnlySpan<byte> span)

Calculates the Shannon entropy of the ReadOnlySpan<byte>. Entropy is a measure of randomness/unpredictability in the data.

Returns: A value between 0.0 (perfectly ordered) and 8.0 (maximum entropy for bytes)

Performance: O(n) where n is span length

Example:

// Maximum entropy (all byte values present once)
var bytes = new byte[256];
for (var i = 0; i < 256; i++) bytes[i] = (byte)i;
ReadOnlySpan<byte> span = bytes;
double entropy = span.CalculateEntropy(); // Returns: 8.0

// No entropy (all same value)
ReadOnlySpan<byte> uniform = new byte[] { 5, 5, 5, 5 };
double noEntropy = uniform.CalculateEntropy(); // Returns: 0.0

Use Cases:

  • Testing random number generators
  • Validating encryption output
  • Detecting compression opportunities
  • Quality assurance for randomized data

AnalyzeDistribution

public static Dictionary<byte, int> AnalyzeDistribution(this ReadOnlySpan<byte> span)

Analyzes the distribution of byte values in the ReadOnlySpan<byte>.

Returns: A dictionary mapping each byte value to its frequency count

Performance: O(n) where n is span length

Example:

ReadOnlySpan<byte> span = new byte[] { 1, 2, 1, 3, 2, 1 };
var distribution = span.AnalyzeDistribution();
// Result: { 1: 3, 2: 2, 3: 1 }

foreach (var kvp in distribution)
{
    Console.WriteLine($"Byte {kvp.Key}: {kvp.Value} occurrences");
}

Use Cases:

  • Histogram generation
  • Data profiling
  • Compression analysis
  • Pattern detection

Search and Count Methods

CountOccurrences

public static int CountOccurrences(this ReadOnlySpan<byte> span, byte value)

Counts the occurrences of a specific byte value in the ReadOnlySpan<byte>.

Performance: O(n) single-pass

Example:

ReadOnlySpan<byte> span = new byte[] { 1, 2, 3, 2, 4, 2, 5 };
int count = span.CountOccurrences(2); // Returns: 3

FindAllIndices

public static int[] FindAllIndices(this ReadOnlySpan<byte> span, byte value)

Finds all indices where a specific byte value occurs in the ReadOnlySpan<byte>.

Returns: An array of indices where the byte value was found

Performance: O(n) with potential allocation for results array

Example:

ReadOnlySpan<byte> span = new byte[] { 1, 2, 3, 2, 4, 2, 5 };
int[] indices = span.FindAllIndices(2); // Returns: [1, 3, 5]

foreach (var index in indices)
{
    Console.WriteLine($"Found at position {index}");
}

Validation Methods

AllBytesEqual

public static bool AllBytesEqual(this ReadOnlySpan<byte> span)

Checks if all bytes in the ReadOnlySpan<byte> have the same value.

Performance: O(n) best case O(1), worst case O(n)

Example:

ReadOnlySpan<byte> uniform = new byte[] { 5, 5, 5, 5 };
bool allSame = uniform.AllBytesEqual(); // Returns: true

ReadOnlySpan<byte> mixed = new byte[] { 5, 5, 6, 5 };
bool notAllSame = mixed.AllBytesEqual(); // Returns: false

Use Cases:

  • Validating initialization
  • Detecting padding
  • Quality checks

IsAllZeros

public static bool IsAllZeros(this ReadOnlySpan<byte> span)

Checks if the ReadOnlySpan<byte> contains only zero bytes.

Performance: O(n) best case O(1), worst case O(n)

Example:

ReadOnlySpan<byte> zeros = new byte[100]; // Default-initialized to zeros
bool isZero = zeros.IsAllZeros(); // Returns: true

ReadOnlySpan<byte> hasData = new byte[] { 0, 0, 1, 0 };
bool notZero = hasData.IsAllZeros(); // Returns: false

Use Cases:

  • Validating cleared buffers
  • Detecting empty/initialized regions
  • Security checks for sensitive data clearing

Transformation Methods

Reverse

public static byte[] Reverse(this ReadOnlySpan<byte> span)

Reverses the bytes in a copy of the ReadOnlySpan<byte>. Note: This creates a new array since ReadOnlySpan is immutable.

Returns: A new byte array with reversed byte order

Performance: O(n) with allocation

Example:

ReadOnlySpan<byte> span = new byte[] { 1, 2, 3, 4, 5 };
byte[] reversed = span.Reverse(); // Returns: [5, 4, 3, 2, 1]

Use Cases:

  • Endianness conversion
  • String reversal for byte-encoded text
  • Protocol data manipulation

Xor

public static byte[] Xor(this ReadOnlySpan<byte> span1, ReadOnlySpan<byte> span2)

Performs an XOR operation between two ReadOnlySpan<byte> instances. If spans have different lengths, operates on the shorter length.

Returns: A new byte array containing the XOR result

Performance: O(min(n,m)) where n and m are span lengths

Example:

ReadOnlySpan<byte> key = new byte[] { 0xFF, 0x00, 0xAA };
ReadOnlySpan<byte> data = new byte[] { 0x0F, 0xFF, 0x55 };
byte[] xored = key.Xor(data); // Returns: [0xF0, 0xFF, 0xFF]

// XOR again to decrypt
byte[] original = xored.AsSpan().Xor(key); // Returns: [0x0F, 0xFF, 0x55]

Use Cases:

  • Simple encryption/decryption
  • Checksum calculation
  • Data masking
  • Bitwise operations

Practical Examples

Data Quality Analysis

public class DataQualityReport
{
    public static void AnalyzeData(byte[] data)
    {
        ReadOnlySpan<byte> span = data;

        // Calculate entropy
        double entropy = span.CalculateEntropy();
        Console.WriteLine($"Entropy: {entropy:F2} bits");

        // Check for patterns
        if (span.IsAllZeros())
        {
            Console.WriteLine("WARNING: Data is all zeros!");
        }
        else if (span.AllBytesEqual())
        {
            Console.WriteLine($"WARNING: All bytes are {span[0]}");
        }

        // Analyze distribution
        var distribution = span.AnalyzeDistribution();
        Console.WriteLine($"Unique bytes: {distribution.Count}");

        // Find most common byte
        var mostCommon = distribution.OrderByDescending(kvp => kvp.Value).First();
        Console.WriteLine($"Most common: {mostCommon.Key} ({mostCommon.Value} times)");
    }
}

Simple XOR Cipher

public class XorCipher
{
    public static byte[] Encrypt(ReadOnlySpan<byte> data, ReadOnlySpan<byte> key)
    {
        // Expand key to match data length
        var expandedKey = new byte[data.Length];
        for (int i = 0; i < data.Length; i++)
        {
            expandedKey[i] = key[i % key.Length];
        }

        return data.Xor(expandedKey);
    }

    public static byte[] Decrypt(ReadOnlySpan<byte> encrypted, ReadOnlySpan<byte> key)
    {
        // XOR is symmetric
        return Encrypt(encrypted, key);
    }
}

// Usage
ReadOnlySpan<byte> message = Encoding.UTF8.GetBytes("Secret");
ReadOnlySpan<byte> key = new byte[] { 0xAB, 0xCD, 0xEF };
byte[] encrypted = XorCipher.Encrypt(message, key);
byte[] decrypted = XorCipher.Decrypt(encrypted, key);

Finding Delimiter Positions

public static List<(int start, int length)> FindRecords(
    ReadOnlySpan<byte> data,
    byte delimiter)
{
    var records = new List<(int, int)>();
    var indices = data.FindAllIndices(delimiter);

    int lastIndex = 0;
    foreach (var index in indices)
    {
        var length = index - lastIndex;
        if (length > 0)
        {
            records.Add((lastIndex, length));
        }
        lastIndex = index + 1;
    }

    // Add final record if exists
    if (lastIndex < data.Length)
    {
        records.Add((lastIndex, data.Length - lastIndex));
    }

    return records;
}

Performance Characteristics

Time Complexity

  • CalculateEntropy: O(n) - single pass with 256-element frequency array
  • AnalyzeDistribution: O(n) - single pass with dictionary operations
  • CountOccurrences: O(n) - single pass
  • FindAllIndices: O(n) - single pass with list growth
  • AllBytesEqual: O(n) - best case O(1) if first bytes differ
  • IsAllZeros: O(n) - best case O(1) if first byte non-zero
  • Reverse: O(n) - single pass with allocation
  • Xor: O(min(n,m)) - single pass, operates on shorter span

Memory Usage

  • Analysis methods: O(1) for CountOccurrences, AllBytesEqual, IsAllZeros
  • AnalyzeDistribution: O(unique bytes) - typically O(256) maximum
  • FindAllIndices: O(matches) - allocates array for match positions
  • Reverse/Xor: O(n) - creates new array with results

Optimization Notes

  • Entropy and distribution calculations are single-pass
  • Early-exit optimizations for equality checks
  • Dictionary uses TryGetValue for optimal performance
  • Array.Reverse is highly optimized in .NET

Best Practices

Use Appropriate Methods

// DO: Use specific check for zeros
if (span.IsAllZeros()) { }

// DON'T: Use distribution for simple checks
if (span.AnalyzeDistribution().Count == 1 &&
    span.AnalyzeDistribution().ContainsKey(0)) { }

Reuse Distribution Results

// DO: Calculate once, use multiple times
var distribution = span.AnalyzeDistribution();
var uniqueCount = distribution.Count;
var mostCommon = distribution.MaxBy(kvp => kvp.Value);

// DON'T: Recalculate repeatedly
var uniqueCount = span.AnalyzeDistribution().Count;
var mostCommon = span.AnalyzeDistribution().MaxBy(kvp => kvp.Value);

Cross-References