You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

186 lines
6.7KB

  1. #pragma once
  2. #include <neo/buffer_algorithm.hpp>
  3. #include <neo/const_buffer.hpp>
  4. #include <array>
  5. #include <cassert>
  6. #include <cstddef>
  7. namespace browns {
  8. class md5 {
  9. public:
  10. using digest_type = std::array<std::byte, 16>;
  11. private:
  12. // clang-format off
  13. static constexpr std::array<std::uint32_t, 64> magic_numbers_sierra = {
  14. 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22,
  15. 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20,
  16. 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23,
  17. 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21,
  18. };
  19. static constexpr std::array<std::uint32_t, 64> magic_numbers_kilo = {
  20. 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee,
  21. 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501,
  22. 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be,
  23. 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821,
  24. 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa,
  25. 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8,
  26. 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed,
  27. 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a,
  28. 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c,
  29. 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70,
  30. 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05,
  31. 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665,
  32. 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039,
  33. 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1,
  34. 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1,
  35. 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391,
  36. };
  37. // clang-format on
  38. std::array<std::uint32_t, 4> _running_digest = {
  39. 0x67452301,
  40. 0xefcdab89,
  41. 0x98badcfe,
  42. 0x10325476,
  43. };
  44. constexpr static std::size_t bits_per_block = 512;
  45. constexpr static std::size_t bits_per_byte = 8;
  46. constexpr static std::size_t bytes_per_block = bits_per_block / bits_per_byte;
  47. using chunk_type = std::array<std::byte, bytes_per_block>;
  48. chunk_type _pending_blocks = {};
  49. std::size_t _write_offset = 0;
  50. std::uint64_t _msg_length = 0;
  51. std::size_t _num_pending_blocks = 0;
  52. constexpr void _consume_block() noexcept {
  53. _write_offset = 0;
  54. std::uint32_t alpha = _running_digest[0];
  55. std::uint32_t bravo = _running_digest[1];
  56. std::uint32_t charlie = _running_digest[2];
  57. std::uint32_t delta = _running_digest[3];
  58. const std::uint32_t* data
  59. = static_cast<const std::uint32_t*>(static_cast<const void*>(_pending_blocks.data()));
  60. for (int idx = 0; idx < 64; ++idx) {
  61. std::uint32_t F = 0;
  62. std::uint32_t g = 0;
  63. if (idx < 16) {
  64. F = (bravo & charlie) | ((~bravo) & delta);
  65. g = idx;
  66. } else if (idx < 32) {
  67. F = (delta & bravo) | ((~delta) & charlie);
  68. g = ((5 * idx) + 1) % 16;
  69. } else if (idx < 48) {
  70. F = bravo ^ charlie ^ delta;
  71. g = ((3 * idx) + 5) % 16;
  72. } else {
  73. F = charlie ^ (bravo | (~delta));
  74. g = (7 * idx) % 16;
  75. }
  76. F = F + alpha + magic_numbers_kilo[idx] + data[g];
  77. alpha = delta;
  78. delta = charlie;
  79. charlie = bravo;
  80. bravo = bravo
  81. + ((F << magic_numbers_sierra[idx]) | (F >> (32 - magic_numbers_sierra[idx])));
  82. }
  83. _running_digest[0] += alpha;
  84. _running_digest[1] += bravo;
  85. _running_digest[2] += charlie;
  86. _running_digest[3] += delta;
  87. }
  88. constexpr static std::byte* _le_copy(std::uint64_t n, std::byte* ptr) noexcept {
  89. auto n_ptr = neo::byte_pointer(&n);
  90. auto n_end = n_ptr + sizeof n;
  91. while (n_ptr != n_end) {
  92. *ptr++ = *n_ptr++;
  93. }
  94. return ptr;
  95. }
  96. public:
  97. constexpr md5() = default;
  98. constexpr void feed(neo::const_buffer buf) noexcept {
  99. feed(buf.data(), buf.data() + buf.size());
  100. }
  101. template <typename Iter, typename Sent>
  102. constexpr void feed(Iter it, const Sent s) noexcept {
  103. using source_val_type = typename std::iterator_traits<Iter>::value_type;
  104. static_assert(std::disjunction_v<std::is_same<source_val_type, char>,
  105. std::is_same<source_val_type, unsigned char>,
  106. std::is_same<source_val_type, signed char>,
  107. std::is_same<source_val_type, std::byte>>,
  108. "Type fed to hash must have be std::byte-sized");
  109. while (it != s) {
  110. auto write_head = std::next(_pending_blocks.begin(), _write_offset);
  111. const auto write_stop = _pending_blocks.end();
  112. while (write_head != write_stop && it != s) {
  113. *write_head++ = static_cast<std::byte>(*it);
  114. ++it;
  115. ++_write_offset;
  116. _msg_length += 1;
  117. }
  118. if (write_head == write_stop) {
  119. _consume_block();
  120. }
  121. }
  122. }
  123. constexpr void pad() noexcept {
  124. // Length is recored in bits
  125. const std::uint64_t len_nbits = _msg_length * 8;
  126. // Calc how many bytes of padding need to be inserted
  127. std::size_t n_to_next_boundary = bytes_per_block - _write_offset;
  128. if (n_to_next_boundary < sizeof(len_nbits)) {
  129. // We don't have enough room to the next boundary for the message
  130. // length. Go another entire block of padding
  131. n_to_next_boundary += bytes_per_block;
  132. }
  133. // Create an array for padding.
  134. std::array<std::byte, bytes_per_block * 2> pad = {};
  135. // Set the lead bit, as per spec
  136. pad[0] = std::byte{0b1000'0000};
  137. // Calc how many bytes from our pad object we should copy. We need
  138. // to leave room at the end for the message length integer.
  139. std::size_t n_to_copy = n_to_next_boundary - sizeof(_msg_length);
  140. // Feed the padding
  141. feed(neo::const_buffer(pad.data(), n_to_copy));
  142. // Now feed the message length integer
  143. feed(neo::const_buffer(neo::byte_pointer(&len_nbits), sizeof(len_nbits)));
  144. assert(_write_offset == 0);
  145. }
  146. constexpr digest_type digest() const noexcept {
  147. if (_write_offset != 0) {
  148. assert(false && "Requested digest of incomplete md5. Be sure you called pad()!");
  149. std::terminate();
  150. }
  151. digest_type ret = {};
  152. auto data = neo::byte_pointer(_running_digest.data());
  153. auto dest = ret.begin();
  154. auto dest_end = ret.end();
  155. while (dest != dest_end) {
  156. *dest++ = *data++;
  157. }
  158. return ret;
  159. }
  160. };
  161. } // namespace browns