Browse Source

refactor: code structure optimization

master
Dnomd343 8 months ago
parent
commit
cefbcc4639
  1. 75
      src/impl/algorithm.inc
  2. 94
      src/impl/constexpr.inc
  3. 57
      src/impl/core.cc
  4. 8
      src/impl/inline.inc
  5. 33
      src/impl/value.inc
  6. 11
      src/impl/wrapper.cc
  7. 23
      src/md5.h
  8. 6
      test/assert.cc

75
src/impl/algorithm.inc

@ -1,75 +0,0 @@
#pragma once
#define MD5_R1 A, B, C, D
#define MD5_R2 D, A, B, C
#define MD5_R3 C, D, A, B
#define MD5_R4 B, C, D, A
#define MD5_F(x, y, z) (z ^ (x & (y ^ z)))
#define MD5_G(x, y, z) (y ^ (z & (x ^ y)))
#define MD5_H(x, y, z) (x ^ y ^ z)
#define MD5_I(x, y, z) (y ^ (x | ~z))
#define MD5_CYC(w, s) (w = (w << s) | (w >> (32 - s)))
#define MD5_STEP(i, f, a, b, c, d) \
do { \
a += f(b, c, d) + block[K(i)] + T(i); \
MD5_CYC(a, S(i)); \
a += b; \
} while (0)
#define MD5_FF(i, ...) MD5_STEP((0x00 | i), MD5_F, __VA_ARGS__)
#define MD5_GG(i, ...) MD5_STEP((0x10 | i), MD5_G, __VA_ARGS__)
#define MD5_HH(i, ...) MD5_STEP((0x20 | i), MD5_H, __VA_ARGS__)
#define MD5_II(i, ...) MD5_STEP((0x30 | i), MD5_I, __VA_ARGS__)
#define MD5_ROUND(OP) \
OP(0x0, MD5_R1); OP(0x1, MD5_R2); OP(0x2, MD5_R3); OP(0x3, MD5_R4); \
OP(0x4, MD5_R1); OP(0x5, MD5_R2); OP(0x6, MD5_R3); OP(0x7, MD5_R4); \
OP(0x8, MD5_R1); OP(0x9, MD5_R2); OP(0xa, MD5_R3); OP(0xb, MD5_R4); \
OP(0xc, MD5_R1); OP(0xd, MD5_R2); OP(0xe, MD5_R3); OP(0xf, MD5_R4);
#define MD5_UPDATE \
MD5_ROUND(MD5_FF) MD5_ROUND(MD5_GG) MD5_ROUND(MD5_HH) MD5_ROUND(MD5_II)
#include "value.inc"
namespace md5::impl {
struct md5_ctx {
uint32_t A = value::kA;
uint32_t B = value::kB;
uint32_t C = value::kC;
uint32_t D = value::kD;
uint64_t size = 0; // processed size in byte
};
/// MD5 data block index, input between 0 and 63.
constexpr int K(const int i) {
constexpr int step[4] = {1, 5, 3, 7};
constexpr int begin[4] = {0, 1, 5, 0};
return (begin[i >> 4] + step[i >> 4] * i) & 0b1111;
}
/// MD5 circular shift times, input between 0 and 63.
constexpr int S(const int i) {
constexpr int shift[4][4] = {
{7, 12, 17, 22},
{5, 9, 14, 20},
{4, 11, 16, 23},
{6, 10, 15, 21},
};
return shift[i >> 4][i & 0b11];
}
/// MD5 T-table constant, input between 0 and 63.
constexpr uint32_t T(const int i) {
return value::kT[i];
}
static_assert(K(0) != K(63), "invalid constexpr");
static_assert(S(0) != S(63), "invalid constexpr");
static_assert(T(0) != T(63), "invalid constexpr");
} // namespace md5::impl

94
src/impl/constexpr.inc

@ -1,87 +1,89 @@
#pragma once
namespace md5::impl {
namespace md5::ce {
struct md5_ce_ctx {
struct md5_ctx {
uint32_t A = value::kA;
uint32_t B = value::kB;
uint32_t C = value::kC;
uint32_t D = value::kD;
};
struct md5_ce {
const char *data;
uint64_t data_len, padded_len;
struct md5_data {
const char *ptr;
uint64_t len, padded_len;
constexpr md5_ce(const char *data, const uint64_t len)
: data(data), data_len(len), padded_len((len + 64 + 8) & ~0b111111ULL) {}
constexpr md5_data(const char *data, const uint64_t len)
: ptr(data), len(len), padded_len((len + 64 + 8) & ~0b111111ULL) {}
};
using Block = std::array<uint32_t, 16>; // single md5 block with 64 bytes
/// Get the data and padding byte of the specified index.
constexpr uint8_t GetByte(const md5_ce *ctx, const uint64_t index) {
if (index < ctx->data_len) // message data
return ctx->data[index];
if (index == ctx->data_len) // padding flag
constexpr uint8_t GetByte(const md5_data &data, const uint64_t index) {
if (index < data.len) // message data
return data.ptr[index];
if (index == data.len) // padding flag
return 0x80;
if (index < ctx->padded_len - 8) // padding content
if (index < data.padded_len - 8) // padding content
return 0x00;
const auto offset = (index + 8 - ctx->padded_len) * 8;
return static_cast<uint8_t>(0xff & (ctx->data_len * 8) >> offset);
const auto offset = (index + 8 - data.padded_len) * 8;
return static_cast<uint8_t>(0xff & (data.len * 8) >> offset);
}
/// Get the MD5 block content at the specified index.
constexpr Block GetBlock(const md5_ce *ctx, const uint64_t index) {
constexpr Block GetBlock(const md5_data &data, const uint64_t index) {
Block block {};
for (int i = 0; i < 16; ++i) {
const auto offset = index + i * 4;
(block[i] <<= 8) |= GetByte(ctx, offset + 3);
(block[i] <<= 8) |= GetByte(ctx, offset + 2);
(block[i] <<= 8) |= GetByte(ctx, offset + 1);
(block[i] <<= 8) |= GetByte(ctx, offset + 0);
(block[i] <<= 8) |= GetByte(data, offset + 3);
(block[i] <<= 8) |= GetByte(data, offset + 2);
(block[i] <<= 8) |= GetByte(data, offset + 1);
(block[i] <<= 8) |= GetByte(data, offset + 0);
}
return block;
}
/// Convert origin MD5 integers to hexadecimal character array.
constexpr std::array<char, 32> DigestCE(const std::array<uint32_t, 4> &ctx) {
std::array<char, 32> result {};
for (uint32_t i = 0, val = 0; i < 32; val >>= 8) {
if (!(i & 0b111))
val = ctx[i >> 3];
result[i++] = value::HexTable[(val >> 4) & 0b1111];
result[i++] = value::HexTable[val & 0b1111];
}
return result;
}
constexpr uint32_t Calc(const md5_ce_ctx &ctx, const int i) {
/// Apply MD5 round process with 64 times calculate.
constexpr md5_ctx Round(const Block &block, md5_ctx ctx) {
constexpr auto calc = [](const md5_ctx &c, const int i) {
if (i < 0x10)
return ctx.D ^ (ctx.B & (ctx.C ^ ctx.D));
return c.D ^ (c.B & (c.C ^ c.D));
if (i < 0x20)
return ctx.C ^ (ctx.D & (ctx.B ^ ctx.C));
return c.C ^ (c.D & (c.B ^ c.C));
if (i < 0x30)
return ctx.B ^ ctx.C ^ ctx.D;
return ctx.C ^ (ctx.B | ~ctx.D);
}
return c.B ^ c.C ^ c.D;
return c.C ^ (c.B | ~c.D);
};
constexpr md5_ce_ctx Round(const Block &block, md5_ce_ctx ctx) {
for (int i = 0; i < 64; ++i) {
const auto a = ctx.A + Calc(ctx, i) + block[K(i)] + T(i);
const auto a = ctx.A + calc(ctx, i) + block[value::K(i)] + value::T(i);
ctx.A = ctx.D;
ctx.D = ctx.C;
ctx.C = ctx.B;
ctx.B += a << S(i) | a >> (32 - S(i));
ctx.B += a << value::S(i) | a >> (32 - value::S(i));
}
return ctx;
}
constexpr std::array<char, 32> MD5::HashCE(const char *data, const uint64_t len) {
md5_ce_ctx ctx;
const md5_ce md5(data, len);
/// Convert origin MD5 integers to hexadecimal character array.
constexpr std::array<char, 32> DigestCE(const std::array<uint32_t, 4> &ctx) {
std::array<char, 32> result {};
for (uint32_t i = 0, val = 0; i < 32; val >>= 8) {
if (!(i & 0b111))
val = ctx[i >> 3];
result[i++] = value::HexTable[(val >> 4) & 0b1111];
result[i++] = value::HexTable[val & 0b1111];
}
return result;
}
/// MD5 hash implement based on constexpr.
constexpr std::array<char, 32> Hash(const char *data, const uint64_t len) {
md5_ctx ctx;
const md5_data md5(data, len);
for (uint32_t index = 0; index < md5.padded_len; index += 64) {
const auto [A, B, C, D] = Round(GetBlock(&md5, index), ctx);
const auto [A, B, C, D] = Round(GetBlock(md5, index), ctx);
ctx.A += A;
ctx.B += B;
ctx.C += C;
@ -90,6 +92,6 @@ constexpr std::array<char, 32> MD5::HashCE(const char *data, const uint64_t len)
return DigestCE({ctx.A, ctx.B, ctx.C, ctx.D});
}
static_assert(MD5::HashCE("")[0] == 'd');
static_assert(Hash("", 0)[0] == 'd');
} // namespace md5::impl
} // namespace md5::ce

57
src/impl/core.cc

@ -1,8 +1,38 @@
#include "md5.h"
#include <cstring>
#include "md5.h"
using md5::MD5;
using md5::value::K;
using md5::value::S;
using md5::value::T;
#define R1 A, B, C, D
#define R2 D, A, B, C
#define R3 C, D, A, B
#define R4 B, C, D, A
#define F(x, y, z) (z ^ (x & (y ^ z)))
#define G(x, y, z) (y ^ (z & (x ^ y)))
#define H(x, y, z) (x ^ y ^ z)
#define I(x, y, z) (y ^ (x | ~z))
#define STEP(i, f, a, b, c, d) \
do { \
a += f(b, c, d) + block[K(i)] + T(i); \
a = a << S(i) | a >> (32 - S(i)); \
a += b; \
} while (0)
#define FF(i, ...) STEP((0x00 | i), F, __VA_ARGS__)
#define GG(i, ...) STEP((0x10 | i), G, __VA_ARGS__)
#define HH(i, ...) STEP((0x20 | i), H, __VA_ARGS__)
#define II(i, ...) STEP((0x30 | i), I, __VA_ARGS__)
using ::md5::impl::MD5;
#define MD5_UPDATE(OP) \
OP(0x0, R1); OP(0x1, R2); OP(0x2, R3); OP(0x3, R4); \
OP(0x4, R1); OP(0x5, R2); OP(0x6, R3); OP(0x7, R4); \
OP(0x8, R1); OP(0x9, R2); OP(0xa, R3); OP(0xb, R4); \
OP(0xc, R1); OP(0xd, R2); OP(0xe, R3); OP(0xf, R4);
static constexpr unsigned char Padding[64] { 0x80, /* 0x00, ... */ };
@ -16,11 +46,14 @@ const void* MD5::UpdateImpl(const void *data, uint64_t len) {
auto D = ctx_.D;
while (block < limit) {
auto A_ = A;
auto B_ = B;
auto C_ = C;
auto D_ = D;
MD5_UPDATE
const auto A_ = A;
const auto B_ = B;
const auto C_ = C;
const auto D_ = D;
MD5_UPDATE(FF)
MD5_UPDATE(GG)
MD5_UPDATE(HH)
MD5_UPDATE(II)
A += A_;
B += B_;
C += C_;
@ -43,16 +76,16 @@ void MD5::FinalImpl(const void *data, uint64_t len) {
}
unsigned char buffer[128]; // 2 blocks
::std::memcpy(buffer, data, len);
std::memcpy(buffer, data, len);
const uint64_t total = (ctx_.size + len) << 3; // total number in bit
if (len < 56) { // len -> [0, 56)
::std::memcpy(buffer + len, Padding, 56 - len);
::std::memcpy(buffer + 56, &total, 8);
std::memcpy(buffer + len, Padding, 56 - len);
std::memcpy(buffer + 56, &total, 8);
UpdateImpl(buffer, 64); // update 1 block
} else { // len -> [56, 64 + 56)
::std::memcpy(buffer + len, Padding, 120 - len);
::std::memcpy(buffer + 120, &total, 8);
std::memcpy(buffer + len, Padding, 120 - len);
std::memcpy(buffer + 120, &total, 8);
UpdateImpl(buffer, 128); // update 2 blocks
}
}

8
src/impl/inline.inc

@ -1,6 +1,6 @@
#pragma once
namespace md5::impl {
namespace md5 {
inline MD5& MD5::Reset() {
ctx_.A = value::kA;
@ -35,4 +35,8 @@ constexpr std::array<char, 32> MD5::HashCE(const std::string_view &data) {
return HashCE(data.data(), data.size());
}
} // namespace md5::impl
constexpr std::array<char, 32> MD5::HashCE(const char *data, const uint64_t len) {
return ce::Hash(data, len);
}
} // namespace md5

33
src/impl/value.inc

@ -16,14 +16,28 @@ constexpr uint32_t kB = 0xefcdab89;
constexpr uint32_t kC = 0x98badcfe;
constexpr uint32_t kD = 0x10325476;
// In order to be compatible with C++17, the `consteval` keyword cannot be used
// here. The MD5 T-table constants will be macro-expanded and calculated.
/// MD5 data block index, input between 0 and 63.
constexpr int K(const int i) {
constexpr int step[4] = {1, 5, 3, 7};
constexpr int begin[4] = {0, 1, 5, 0};
return (begin[i >> 4] + step[i >> 4] * i) & 0b1111;
}
static_assert(K(0) != K(63));
constexpr uint32_t TCal(const int i) {
const auto val = math::sin(i + 1);
return static_cast<uint32_t>(math::abs(val) * 0x100000000);
/// MD5 circular shift times, input between 0 and 63.
constexpr int S(const int i) {
constexpr int shift[4][4] = {
{7, 12, 17, 22},
{5, 9, 14, 20},
{4, 11, 16, 23},
{6, 10, 15, 21},
};
return shift[i >> 4][i & 0b11];
}
static_assert(S(0) != S(63));
/// In order to be compatible with C++17, the `consteval` keyword cannot be used
/// here. The MD5 T-table constants will be macro-expanded and calculated.
#define MD5_TT \
MD5_T(00) MD5_T(01) MD5_T(02) MD5_T(03) MD5_T(04) MD5_T(05) MD5_T(06) MD5_T(07) \
MD5_T(08) MD5_T(09) MD5_T(0a) MD5_T(0b) MD5_T(0c) MD5_T(0d) MD5_T(0e) MD5_T(0f) \
@ -34,15 +48,20 @@ constexpr uint32_t TCal(const int i) {
MD5_T(30) MD5_T(31) MD5_T(32) MD5_T(33) MD5_T(34) MD5_T(35) MD5_T(36) MD5_T(37) \
MD5_T(38) MD5_T(39) MD5_T(3a) MD5_T(3b) MD5_T(3c) MD5_T(3d) MD5_T(3e) MD5_T(3f)
#define MD5_T(x) constexpr auto kT_##x = TCal(0x##x);
#define MD5_T(x) constexpr auto kT_##x = static_cast<uint32_t>(math::abs(math::sin(0x##x + 1)) * 0x100000000);
MD5_TT
#undef MD5_T
#define MD5_T(x) kT_##x,
/// MD5 T-table constant array.
constexpr std::array kT = {MD5_TT};
#undef MD5_T
#undef MD5_TT
/// MD5 T-table constant, input between 0 and 63.
constexpr uint32_t T(const int i) {
return kT[i];
}
static_assert(T(0) != T(63));
} // namespace md5::value

11
src/impl/wrapper.cc

@ -1,8 +1,7 @@
#include <cstring>
#include "md5.h"
#include <cstring>
using ::md5::impl::MD5;
using md5::MD5;
std::string MD5::Digest() const {
std::string result(32, 0x00);
@ -17,13 +16,13 @@ std::string MD5::Digest() const {
MD5& MD5::Update(const void *data, uint64_t len) {
if (buffer_size_ != 0) {
if (buffer_size_ + len < 64) { // buffer not filled
::std::memcpy(buffer_ + buffer_size_, data, len);
std::memcpy(buffer_ + buffer_size_, data, len);
buffer_size_ += len;
return *this; // save into buffer and return
}
const auto size = 64 - buffer_size_;
::std::memcpy(buffer_ + buffer_size_, data, size);
std::memcpy(buffer_ + buffer_size_, data, size);
UpdateImpl(buffer_, 64); // fill and update with buffer
data = static_cast<const char*>(data) + size;
buffer_size_ = 0;
@ -33,7 +32,7 @@ MD5& MD5::Update(const void *data, uint64_t len) {
data = UpdateImpl(data, len);
len &= 0b111111; // len -> [0, 64)
if (len != 0) {
::std::memcpy(buffer_, data, len); // save remain data into buffer
std::memcpy(buffer_, data, len); // save remain data into buffer
buffer_size_ = len;
}
return *this;

23
src/md5.h

@ -10,9 +10,10 @@ static_assert(sizeof(uintptr_t) == 8,
static_assert(__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__,
"Project only works on little-endian architecture.");
#include "impl/algorithm.inc"
#include "impl/value.inc"
#include "impl/constexpr.inc"
namespace md5::impl {
namespace md5 {
class MD5 {
public:
@ -33,7 +34,6 @@ public:
/// Get the string result of md5.
[[nodiscard]] std::string Digest() const;
public:
/// Calculate the md5 hash value of the specified data.
static std::string Hash(const std::string_view &data);
@ -47,6 +47,14 @@ public:
static constexpr std::array<char, 32> HashCE(const char *data, uint64_t len);
private:
struct md5_ctx {
uint32_t A = value::kA;
uint32_t B = value::kB;
uint32_t C = value::kC;
uint32_t D = value::kD;
uint64_t size = 0; // processed size in byte
};
md5_ctx ctx_;
char buffer_[64] {};
uint64_t buffer_size_ = 0; // size < 64
@ -58,13 +66,6 @@ private:
void FinalImpl(const void *data, uint64_t len);
};
} // namespace md5::impl
} // namespace md5
#include "impl/inline.inc"
#include "impl/constexpr.inc"
namespace md5 {
using MD5 = ::md5::impl::MD5;
} // namespace md5

6
test/assert.cc

@ -1,8 +1,8 @@
#include "md5.h"
using md5::impl::K;
using md5::impl::S;
using md5::impl::T;
using md5::value::K;
using md5::value::S;
using md5::value::T;
static_assert(K(0x00) == 0);
static_assert(K(0x01) == 1);

Loading…
Cancel
Save