///|
// MD5 (RFC 1321), for the index's term anchors: asciidoctor-pdf names the
// destination of an index entry `__indextermdef-` and the MD5 hex digest of
// the term.
///|
let md5_shifts : Array[Int] = [
7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 5, 9, 14, 20, 5, 9,
14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23,
4, 11, 16, 23, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21,
]
///|
/// `floor(abs(sin(i + 1)) * 2^32)`
let md5_constants : Array[UInt] = Array::makei(64, i => {
let v = (@math.sin((i + 1).to_double()).abs() * 4294967296.0).floor()
v.to_uint64().to_uint()
})
///|
fn md5_rotl(x : UInt, n : Int) -> UInt {
(x << n) | (x >> (32 - n))
}
///|
/// The MD5 digest of `data` as 32 lowercase hex digits.
fn md5_hex(data : Bytes) -> String {
let len = data.length()
// message, 0x80, zero padding to 56 mod 64, then the bit length (LE)
let padded_len = (len + 8) / 64 * 64 + 64
let msg = FixedArray::make(padded_len, b'\x00')
for i in 0..> (8 * i)).to_byte()
}
let mut a0 : UInt = 0x67452301U
let mut b0 : UInt = 0xefcdab89U
let mut c0 : UInt = 0x98badcfeU
let mut d0 : UInt = 0x10325476U
let m = FixedArray::make(16, 0U)
for chunk = 0; chunk < padded_len; chunk = chunk + 64 {
for j in 0..<16 {
let p = chunk + j * 4
m[j] = msg[p].to_uint() |
(msg[p + 1].to_uint() << 8) |
(msg[p + 2].to_uint() << 16) |
(msg[p + 3].to_uint() << 24)
}
let mut a = a0
let mut b = b0
let mut c = c0
let mut d = d0
for i in 0..<64 {
let (f, g) = if i < 16 {
((b & c) | (b.lnot() & d), i)
} else if i < 32 {
((d & b) | (d.lnot() & c), (5 * i + 1) % 16)
} else if i < 48 {
(b ^ c ^ d, (3 * i + 5) % 16)
} else {
(c ^ (b | d.lnot()), 7 * i % 16)
}
let f2 = f + a + md5_constants[i] + m[g]
a = d
d = c
c = b
b = b + md5_rotl(f2, md5_shifts[i])
}
a0 = a0 + a
b0 = b0 + b
c0 = c0 + c
d0 = d0 + d
}
let sb = StringBuilder()
for word in [a0, b0, c0, d0] {
for k in 0..<4 {
let byte = ((word >> (8 * k)) & 0xFFU).reinterpret_as_int()
sb.write_char("0123456789abcdef"[byte >> 4].unsafe_to_char())
sb.write_char("0123456789abcdef"[byte & 15].unsafe_to_char())
}
}
sb.to_string()
}
///|
test "md5 digests" {
inspect(md5_hex(b""), content="d41d8cd98f00b204e9800998ecf8427e")
inspect(
md5_hex(b"The quick brown fox jumps over the lazy dog"),
content="9e107d9d372bb6826bd81d3542a419d6",
)
inspect(
md5_hex(
@utf8.encode(
"a string of exactly sixty-four bytes, to pad a whole block....",
),
),
content="ddc5f921ce15fea94fa53489b6fca8ef",
)
}