Skip to content

Commit 29a67d1

Browse files
committed
auto merge of #8272 : DaGenix/rust/digest-md5-impl-not-unrolled, r=cmr
An MD5 implementation was originally included in #8097, but, since there are a couple different implementations of that digest algorithm (@alco mentioned his implementation on the mailing list just before I opened that PR), it was suggested that I remove it from that PR and open up a new PR to discuss the different implementations and the best way forward. If anyone wants to discuss a different implementation, feel free to present it here and discuss and compare it to this one. I'll just discuss my implementation and I'll leave it to others to present details of theirs. This implementation relies on the FixedBuffer struct from cryptoutil.rs for managing the input buffer, just like the Sha1 and Sha2 digest implementations do. I tried manually unrolling the loops in the compression function, but I got slightly worse performance when I did that. Outside of the #[test]s, I also tested the implementation by generating 1,000 inputs of up to 10MB in size and checking the MD5 digest calculated by this code against the MD5 digest calculated by Java's implementation. On my computer, I'm getting the following performance: ``` test md5::bench::md5_10 ... bench: 52 ns/iter (+/- 1) = 192 MB/s test md5::bench::md5_1k ... bench: 2819 ns/iter (+/- 44) = 363 MB/s test md5::bench::md5_64k ... bench: 178566 ns/iter (+/- 4927) = 367 MB/s ```
2 parents 1942a7a + b00aa12 commit 29a67d1

File tree

5 files changed

+471
-42
lines changed

5 files changed

+471
-42
lines changed

src/libextra/crypto/cryptoutil.rs

+134-36
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
// option. This file may not be copied, modified, or distributed
99
// except according to those terms.
1010

11-
use std::num::One;
11+
use std::num::{One, Zero, CheckedAdd};
1212
use std::vec::bytes::{MutableByteVector, copy_memory};
1313

1414

@@ -36,6 +36,18 @@ pub fn write_u32_be(dst: &mut[u8], input: u32) {
3636
}
3737
}
3838

39+
/// Write a u32 into a vector, which must be 4 bytes long. The value is written in little-endian
40+
/// format.
41+
pub fn write_u32_le(dst: &mut[u8], input: u32) {
42+
use std::cast::transmute;
43+
use std::unstable::intrinsics::to_le32;
44+
assert!(dst.len() == 4);
45+
unsafe {
46+
let x: *mut i32 = transmute(dst.unsafe_mut_ref(0));
47+
*x = to_le32(input as i32);
48+
}
49+
}
50+
3951
/// Read a vector of bytes into a vector of u64s. The values are read in big-endian format.
4052
pub fn read_u64v_be(dst: &mut[u64], input: &[u8]) {
4153
use std::cast::transmute;
@@ -68,51 +80,90 @@ pub fn read_u32v_be(dst: &mut[u32], input: &[u8]) {
6880
}
6981
}
7082

83+
/// Read a vector of bytes into a vector of u32s. The values are read in little-endian format.
84+
pub fn read_u32v_le(dst: &mut[u32], input: &[u8]) {
85+
use std::cast::transmute;
86+
use std::unstable::intrinsics::to_le32;
87+
assert!(dst.len() * 4 == input.len());
88+
unsafe {
89+
let mut x: *mut i32 = transmute(dst.unsafe_mut_ref(0));
90+
let mut y: *i32 = transmute(input.unsafe_ref(0));
91+
do dst.len().times() {
92+
*x = to_le32(*y);
93+
x = x.offset(1);
94+
y = y.offset(1);
95+
}
96+
}
97+
}
98+
7199

72-
/// Returns true if adding the two parameters will result in integer overflow
73-
pub fn will_add_overflow<T: Int + Unsigned>(x: T, y: T) -> bool {
74-
// This doesn't handle negative values! Don't copy this code elsewhere without considering if
75-
// negative values are important to you!
76-
let max: T = Bounded::max_value();
77-
return x > max - y;
100+
trait ToBits {
101+
/// Convert the value in bytes to the number of bits, a tuple where the 1st item is the
102+
/// high-order value and the 2nd item is the low order value.
103+
fn to_bits(self) -> (Self, Self);
78104
}
79105

80-
/// Shifts the second parameter and then adds it to the first. fails!() if there would be unsigned
81-
/// integer overflow.
82-
pub fn shift_add_check_overflow<T: Int + Unsigned + Clone>(x: T, mut y: T, shift: T) -> T {
83-
if y.leading_zeros() < shift {
84-
fail!("Could not add values - integer overflow.");
106+
impl ToBits for u64 {
107+
fn to_bits(self) -> (u64, u64) {
108+
return (self >> 61, self << 3);
85109
}
86-
y = y << shift;
110+
}
87111

88-
if will_add_overflow(x.clone(), y.clone()) {
89-
fail!("Could not add values - integer overflow.");
90-
}
112+
/// Adds the specified number of bytes to the bit count. fail!() if this would cause numeric
113+
/// overflow.
114+
pub fn add_bytes_to_bits<T: Int + CheckedAdd + ToBits>(bits: T, bytes: T) -> T {
115+
let (new_high_bits, new_low_bits) = bytes.to_bits();
91116

92-
return x + y;
93-
}
117+
if new_high_bits > Zero::zero() {
118+
fail!("Numeric overflow occured.")
119+
}
94120

95-
/// Shifts the second parameter and then adds it to the first, which is a tuple where the first
96-
/// element is the high order value. fails!() if there would be unsigned integer overflow.
97-
pub fn shift_add_check_overflow_tuple
98-
<T: Int + Unsigned + Clone>
99-
(x: (T, T), mut y: T, shift: T) -> (T, T) {
100-
if y.leading_zeros() < shift {
101-
fail!("Could not add values - integer overflow.");
121+
match bits.checked_add(&new_low_bits) {
122+
Some(x) => return x,
123+
None => fail!("Numeric overflow occured.")
102124
}
103-
y = y << shift;
125+
}
104126

105-
match x {
106-
(hi, low) => {
107-
let one: T = One::one();
108-
if will_add_overflow(low.clone(), y.clone()) {
109-
if will_add_overflow(hi.clone(), one.clone()) {
110-
fail!("Could not add values - integer overflow.");
111-
} else {
112-
return (hi + one, low + y);
113-
}
127+
/// Adds the specified number of bytes to the bit count, which is a tuple where the first element is
128+
/// the high order value. fail!() if this would cause numeric overflow.
129+
pub fn add_bytes_to_bits_tuple
130+
<T: Int + Unsigned + CheckedAdd + ToBits>
131+
(bits: (T, T), bytes: T) -> (T, T) {
132+
let (new_high_bits, new_low_bits) = bytes.to_bits();
133+
let (hi, low) = bits;
134+
135+
// Add the low order value - if there is no overflow, then add the high order values
136+
// If the addition of the low order values causes overflow, add one to the high order values
137+
// before adding them.
138+
match low.checked_add(&new_low_bits) {
139+
Some(x) => {
140+
if new_high_bits == Zero::zero() {
141+
// This is the fast path - every other alternative will rarely occur in practice
142+
// considering how large an input would need to be for those paths to be used.
143+
return (hi, x);
114144
} else {
115-
return (hi, low + y);
145+
match hi.checked_add(&new_high_bits) {
146+
Some(y) => return (y, x),
147+
None => fail!("Numeric overflow occured.")
148+
}
149+
}
150+
},
151+
None => {
152+
let one: T = One::one();
153+
let z = match new_high_bits.checked_add(&one) {
154+
Some(w) => w,
155+
None => fail!("Numeric overflow occured.")
156+
};
157+
match hi.checked_add(&z) {
158+
// This re-executes the addition that was already performed earlier when overflow
159+
// occured, this time allowing the overflow to happen. Technically, this could be
160+
// avoided by using the checked add intrinsic directly, but that involves using
161+
// unsafe code and is not really worthwhile considering how infrequently code will
162+
// run in practice. This is the reason that this function requires that the type T
163+
// be Unsigned - overflow is not defined for Signed types. This function could be
164+
// implemented for signed types as well if that were needed.
165+
Some(y) => return (y, low + new_low_bits),
166+
None => fail!("Numeric overflow occured.")
116167
}
117168
}
118169
}
@@ -300,6 +351,7 @@ mod test {
300351
use std::rand::RngUtil;
301352
use std::vec;
302353

354+
use cryptoutil::{add_bytes_to_bits, add_bytes_to_bits_tuple};
303355
use digest::Digest;
304356

305357
/// Feed 1,000,000 'a's into the digest with varying input sizes and check that the result is
@@ -324,4 +376,50 @@ mod test {
324376

325377
assert!(expected == result_str);
326378
}
379+
380+
// A normal addition - no overflow occurs
381+
#[test]
382+
fn test_add_bytes_to_bits_ok() {
383+
assert!(add_bytes_to_bits::<u64>(100, 10) == 180);
384+
}
385+
386+
// A simple failure case - adding 1 to the max value
387+
#[test]
388+
#[should_fail]
389+
fn test_add_bytes_to_bits_overflow() {
390+
add_bytes_to_bits::<u64>(Bounded::max_value(), 1);
391+
}
392+
393+
// A normal addition - no overflow occurs (fast path)
394+
#[test]
395+
fn test_add_bytes_to_bits_tuple_ok() {
396+
assert!(add_bytes_to_bits_tuple::<u64>((5, 100), 10) == (5, 180));
397+
}
398+
399+
// The low order value overflows into the high order value
400+
#[test]
401+
fn test_add_bytes_to_bits_tuple_ok2() {
402+
assert!(add_bytes_to_bits_tuple::<u64>((5, Bounded::max_value()), 1) == (6, 7));
403+
}
404+
405+
// The value to add is too large to be converted into bits without overflowing its type
406+
#[test]
407+
fn test_add_bytes_to_bits_tuple_ok3() {
408+
assert!(add_bytes_to_bits_tuple::<u64>((5, 0), 0x4000000000000001) == (7, 8));
409+
}
410+
411+
// A simple failure case - adding 1 to the max value
412+
#[test]
413+
#[should_fail]
414+
fn test_add_bytes_to_bits_tuple_overflow() {
415+
add_bytes_to_bits_tuple::<u64>((Bounded::max_value(), Bounded::max_value()), 1);
416+
}
417+
418+
// The value to add is too large to convert to bytes without overflowing its type, but the high
419+
// order value from this conversion overflows when added to the existing high order value
420+
#[test]
421+
#[should_fail]
422+
fn test_add_bytes_to_bits_tuple_overflow2() {
423+
add_bytes_to_bits_tuple::<u64>((Bounded::max_value::<u64>() - 1, 0), 0x8000000000000000);
424+
}
327425
}

0 commit comments

Comments
 (0)