///|
/// Shannon entropy of a byte range, in bits per byte (0.0 to 8.0).
///
/// This is the cheapest useful signal for "is this section packed or
/// encrypted": compiled code and data usually land between 4 and 6.5, while
/// compressed or encrypted payloads cluster near 8.
///|
pub fn byte_entropy(bytes : Bytes) -> Double? {
if bytes.length() == 0 {
return None
}
let counts : Array[Int32] = []
let mut i = 0
while i < 256 {
counts.push(0)
i = i + 1
}
let mut j = 0
while j < bytes.length() {
let value = bytes[j].to_int()
if value >= 0 && value < 256 {
counts[value] = counts[value] + 1
}
j = j + 1
}
let total = bytes.length().to_double()
let mut entropy = 0.0
let mut k = 0
while k < 256 {
let count = counts[k]
if count > 0 {
let p = count.to_double() / total
entropy = entropy - p * @math.log2(p)
}
k = k + 1
}
Some(entropy)
}
///|
/// Entropy of a section's file-backed bytes.
///
/// The range is scanned in place: a packed section can be hundreds of
/// megabytes, and copying it just to count bytes would dominate the scan.
pub fn Pe::section_entropy(self : Pe, section : Section) -> Double? {
if section.raw_size <= 0 {
return None
}
let start = section.raw_pointer
let end = start + section.raw_size
if start < 0 || end > self.bytes.length() {
return None
}
Some(entropy_range(self.bytes, start, end))
}
///|
/// Entropy over `bytes[start..end)`.
pub fn entropy_range(bytes : Bytes, start : Int32, end : Int32) -> Double {
let counts : Array[Int32] = []
let mut i = 0
while i < 256 {
counts.push(0)
i = i + 1
}
let limit = if end > bytes.length() { bytes.length() } else { end }
let mut j = start
while j < limit && j >= 0 {
let value = bytes[j].to_int()
if value >= 0 && value < 256 {
counts[value] = counts[value] + 1
}
j = j + 1
}
let total = (limit - start).to_double()
if total <= 0.0 {
return 0.0
}
let mut entropy = 0.0
let mut k = 0
while k < 256 {
let count = counts[k]
if count > 0 {
let p = count.to_double() / total
entropy = entropy - p * @math.log2(p)
}
k = k + 1
}
entropy
}