From df9d981e6f1f3bf1e13e5112cf60a9261f90a2b0 Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Wed, 25 Mar 2020 14:16:35 -0700 Subject: [PATCH] Bigint append (#596) * Bigint append as binary implementation Co-authored-by: Bret Ambrose --- include/aws/common/bigint.h | 12 ++++ source/bigint.c | 113 ++++++++++++++++++++++++++++++++++-- tests/CMakeLists.txt | 1 + tests/bigint_test.c | 91 +++++++++++++++++++++++++++++ 4 files changed, 211 insertions(+), 6 deletions(-) diff --git a/include/aws/common/bigint.h b/include/aws/common/bigint.h index 1d76423c3..bacbc1fd2 100644 --- a/include/aws/common/bigint.h +++ b/include/aws/common/bigint.h @@ -70,6 +70,18 @@ struct aws_bigint *aws_bigint_new_from_cursor(struct aws_allocator *allocator, s AWS_COMMON_API int aws_bigint_bytebuf_debug_output(const struct aws_bigint *bigint, struct aws_byte_buf *buffer); +/** + * Writes a bigint to a buffer as a big endian sequence of octets. + * + * If minimum_length is non-zero, then leading zero-bytes will pad the output as necessary. Otherwise only the minimum + * number of bytes will be written. + */ +AWS_COMMON_API +int aws_bigint_bytebuf_append_as_big_endian( + const struct aws_bigint *bigint, + struct aws_byte_buf *buffer, + size_t minimum_length); + /** * Returns true if this integer is negative, false otherwise. */ diff --git a/source/bigint.c b/source/bigint.c index 5330f7810..0a95d629b 100644 --- a/source/bigint.c +++ b/source/bigint.c @@ -454,6 +454,110 @@ int aws_bigint_bytebuf_debug_output(const struct aws_bigint *bigint, struct aws_ return AWS_OP_SUCCESS; } +static size_t s_aws_bigint_byte_length(const struct aws_bigint *bigint) { + size_t digit_count = aws_array_list_length(&bigint->digits); + + uint32_t high_digit = 0; + aws_array_list_get_at(&bigint->digits, &high_digit, digit_count - 1); + + size_t non_zero_high_digit_bytes = 4; + if (high_digit < (1U << BITS_PER_BYTE)) { + non_zero_high_digit_bytes = 1; + } else if (high_digit < (1U << (2 * BITS_PER_BYTE))) { + non_zero_high_digit_bytes = 2; + } else if (high_digit < (1U << (3 * BITS_PER_BYTE))) { + non_zero_high_digit_bytes = 3; + } + + return (digit_count - 1) * BYTES_PER_BASE_DIGIT + non_zero_high_digit_bytes; +} + +#if _MSC_VER +# pragma warning(push) +# pragma warning(disable : 4221) /* allow struct member to reference format_buffer */ +# pragma warning(disable : 4204) /* non-constant aggregate initializer */ +#endif + +int aws_bigint_bytebuf_append_as_big_endian( + const struct aws_bigint *bigint, + struct aws_byte_buf *buffer, + size_t minimum_length) { + + AWS_PRECONDITION(aws_bigint_is_valid(bigint)); + AWS_PRECONDITION(aws_byte_buf_is_valid(buffer)); + + size_t digit_count = aws_array_list_length(&bigint->digits); + size_t bigint_bytes = s_aws_bigint_byte_length(bigint); + size_t padding_bytes = 0; + if (bigint_bytes < minimum_length) { + padding_bytes = minimum_length - bigint_bytes; + } + + int result = AWS_OP_ERR; + + size_t required_capacity = bigint_bytes + padding_bytes; + if (aws_byte_buf_reserve_relative(buffer, required_capacity)) { + goto done; + } + + if (padding_bytes > 0) { + uint8_t padding = 0; + struct aws_byte_cursor padding_cursor = { + .ptr = &padding, + .len = 1, + }; + + for (size_t i = 0; i < padding_bytes; ++i) { + if (aws_byte_buf_append(buffer, &padding_cursor)) { + goto done; + } + } + } + + uint8_t digit_byte = 0; + struct aws_byte_cursor digit_bytes_cursor = { + .ptr = &digit_byte, + .len = 1, + }; + + bool should_write = false; + + for (size_t i = 0; i < digit_count; ++i) { + size_t digit_index = digit_count - i - 1; + + uint32_t current_digit = 0; + aws_array_list_get_at(&bigint->digits, ¤t_digit, digit_index); + + for (size_t j = 0; j < BYTES_PER_BASE_DIGIT; ++j) { + digit_byte = (uint8_t)(current_digit >> (3 * BITS_PER_BYTE)) & ((1U << BITS_PER_BYTE) - 1); + current_digit <<= BITS_PER_BYTE; + + if (!should_write) { + should_write = digit_byte > 0 || j + 1 == BYTES_PER_BASE_DIGIT; + } + + if (should_write) { + if (aws_byte_buf_append(buffer, &digit_bytes_cursor)) { + goto done; + } + } + } + } + + result = AWS_OP_SUCCESS; + +done: + + AWS_POSTCONDITION(aws_bigint_is_valid(bigint)); + AWS_POSTCONDITION(aws_byte_buf_is_valid(buffer)); + + return result; +} + +#if _MSC_VER +# pragma warning(pop) +#endif + bool aws_bigint_is_negative(const struct aws_bigint *bigint) { AWS_PRECONDITION(aws_bigint_is_valid(bigint)); @@ -678,7 +782,6 @@ static int s_aws_bigint_subtract_magnitudes( const struct aws_bigint *larger = lhs; const struct aws_bigint *smaller = rhs; - if (ordering == AWS_BI_LESS_THAN) { larger = rhs; smaller = lhs; @@ -723,7 +826,6 @@ static int s_aws_bigint_subtract_magnitudes( } int aws_bigint_add(struct aws_bigint *output, const struct aws_bigint *lhs, const struct aws_bigint *rhs) { - AWS_PRECONDITION(aws_bigint_is_valid(output)); AWS_PRECONDITION(aws_bigint_is_valid(lhs)); AWS_PRECONDITION(aws_bigint_is_valid(rhs)); @@ -768,7 +870,6 @@ int aws_bigint_add(struct aws_bigint *output, const struct aws_bigint *lhs, cons } int aws_bigint_subtract(struct aws_bigint *output, const struct aws_bigint *lhs, const struct aws_bigint *rhs) { - AWS_PRECONDITION(aws_bigint_is_valid(output)); AWS_PRECONDITION(aws_bigint_is_valid(lhs)); AWS_PRECONDITION(aws_bigint_is_valid(rhs)); @@ -1087,8 +1188,8 @@ static int s_aws_bigint_divide_by_single_digit( aws_array_list_get_at(÷nd->digits, ¤t_dividend_digit, quotient_length - 1 - i); const uint64_t two_digit_dividend = (current_remainder << BASE_BITS) + current_dividend_digit; - const uint32_t quotient_digit = (uint32_t)(two_digit_dividend / wide_divisor); + aws_array_list_set_at(&temp_quotient->digits, "ient_digit, quotient_length - 1 - i); current_remainder = two_digit_dividend % wide_divisor; @@ -1100,6 +1201,7 @@ static int s_aws_bigint_divide_by_single_digit( quotient->sign = 1; const uint32_t final_remainder = (uint32_t)current_remainder; + aws_array_list_clear(&remainder->digits); aws_array_list_push_back(&remainder->digits, &final_remainder); remainder->sign = 1; @@ -1203,7 +1305,6 @@ static int s_aws_bigint_normalized_divide( for (size_t i = 0; i < quotient_digit_count; ++i) { const size_t lhs_index = lhs_digit_count - i - 1; - AWS_FATAL_ASSERT(lhs_index >= 2); uint32_t highest_digit = 0; @@ -1238,7 +1339,6 @@ static int s_aws_bigint_normalized_divide( aws_array_list_get_at(&lhs->digits, ¤t_digit, lhs_index - rhs_digit_count + j); const uint64_t product_digit = q_guess * divisor_digit; - const uint64_t difference = (uint64_t)current_digit - product_digit - borrow; current_digit = (uint32_t)(difference & LOWER_32_BIT_MASK); @@ -1261,6 +1361,7 @@ static int s_aws_bigint_normalized_divide( aws_array_list_get_at(&lhs->digits, ¤t_digit, lhs_index - rhs_digit_count + j); const uint64_t digit_sum = (uint64_t)divisor_digit + (uint64_t)current_digit + carry; + carry = digit_sum >> BASE_BITS; current_digit = (uint32_t)(digit_sum & LOWER_32_BIT_MASK); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 2cd6846c3..86ca76bf5 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -409,6 +409,7 @@ add_test_case(test_bigint_divide_error) add_test_case(test_bigint_divide_edge) add_test_case(test_bigint_divide_single_digit_divisor) add_test_case(test_bigint_divide_general) +add_test_case(test_bigint_append_binary) generate_test_driver(${PROJECT_NAME}-tests) diff --git a/tests/bigint_test.c b/tests/bigint_test.c index fa6572aa8..62b8853bb 100644 --- a/tests/bigint_test.c +++ b/tests/bigint_test.c @@ -1957,3 +1957,94 @@ static int s_test_bigint_divide_general(struct aws_allocator *allocator, void *c } AWS_TEST_CASE(test_bigint_divide_general, s_test_bigint_divide_general) + +struct bigint_append_binary_test { + const char *input_hex; + size_t minimum_length; + const char *expected_binary_data; + size_t expected_length; +}; + +static struct bigint_append_binary_test s_append_binary_cases[] = { + { + .input_hex = "0x0", + .minimum_length = 0, + .expected_binary_data = "\x00", + .expected_length = 1, + }, + { + .input_hex = "0xff", + .minimum_length = 0, + .expected_binary_data = "\xFF", + .expected_length = 1, + }, + { + .input_hex = "0x3aff", + .minimum_length = 0, + .expected_binary_data = "\x3a\xFF", + .expected_length = 2, + }, + { + .input_hex = "0x3a78ff3483637", + .minimum_length = 0, + .expected_binary_data = "\x03\xa7\x8f\xf3\x48\x36\x37", + .expected_length = 7, + }, + { + .input_hex = "fd3758b8a20010baa583fde3e7bb8532f4abd", + .minimum_length = 0, + .expected_binary_data = "\x0f\xd3\x75\x8b\x8a\x20\x01\x0b\xaa\x58\x3f\xde\x3e\x7b\xb8\x53\x2f\x4a\xbd", + .expected_length = 19, + }, + { + .input_hex = "0xff", + .minimum_length = 5, + .expected_binary_data = "\x00\x00\x00\x00\xFF", + .expected_length = 5, + }, + { + .input_hex = "0xffaabb88", + .minimum_length = 5, + .expected_binary_data = "\x00\xff\xaa\xbb\x88", + .expected_length = 5, + }, + { + .input_hex = "940eb4747e656dd3f0c2679dca69c64baf7ea", + .minimum_length = 32, + .expected_binary_data = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x09\x40\xeb\x47\x47\xe6\x56\xdd" + "\x3f\x0c\x26\x79\xdc\xa6\x9c\x64\xba\xf7\xea", + .expected_length = 32, + }, + { + .input_hex = "10000000000000000000000000000000000000001", + .minimum_length = 32, + .expected_binary_data = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01", + .expected_length = 32, + }, +}; + +static int s_test_bigint_append_binary(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + for (size_t i = 0; i < AWS_ARRAY_SIZE(s_append_binary_cases); ++i) { + struct aws_byte_buf buffer; + aws_byte_buf_init(&buffer, allocator, 1); + + struct bigint_append_binary_test *testcase = &s_append_binary_cases[i]; + + struct aws_bigint *test = aws_bigint_new_from_hex(allocator, aws_byte_cursor_from_c_str(testcase->input_hex)); + ASSERT_NOT_NULL(test); + + ASSERT_SUCCESS(aws_bigint_bytebuf_append_as_big_endian(test, &buffer, testcase->minimum_length)); + ASSERT_TRUE(buffer.len == testcase->expected_length); + ASSERT_BIN_ARRAYS_EQUALS(testcase->expected_binary_data, testcase->expected_length, buffer.buffer, buffer.len); + + aws_bigint_destroy(test); + aws_byte_buf_clean_up(&buffer); + } + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE(test_bigint_append_binary, s_test_bigint_append_binary)