diff options
-rw-r--r-- | include/axolotl/message.hh | 63 | ||||
-rw-r--r-- | src/message.cpp | 171 | ||||
-rw-r--r-- | tests/test_message.cpp | 53 |
3 files changed, 287 insertions, 0 deletions
diff --git a/include/axolotl/message.hh b/include/axolotl/message.hh new file mode 100644 index 0000000..85579cf --- /dev/null +++ b/include/axolotl/message.hh @@ -0,0 +1,63 @@ +#include <cstddef> +#include <cstdint> + + +namespace axolotl { + +/** + * The length of the buffer needed to hold a message. + */ +std::size_t encode_message_length( + std::uint32_t counter, + std::size_t ratchet_key_length, + std::size_t ciphertext_length, + std::size_t mac_length +); + + +struct MessageWriter { + std::size_t body_length; + std::uint8_t * ratchet_key; + std::uint8_t * ciphertext; + std::uint8_t * mac; +}; + + +struct MessageReader { + std::size_t body_length; + std::uint8_t version; + std::uint32_t counter; + std::size_t ratchet_key_length; + std::size_t ciphertext_length; + std::size_t mac_length; + std::uint8_t const * ratchet_key; + std::uint8_t const * ciphertext; + std::uint8_t const * mac; +}; + + +/** + * Writes the message headers into the output buffer. + * Returns a writer struct populated with pointers into the output buffer. + */ +MessageWriter encode_message( + std::uint8_t version, + std::uint32_t counter, + std::size_t ratchet_key_length, + std::size_t ciphertext_length, + std::uint8_t * output +); + + +/** + * Reads the message headers from the input buffer. + * Returns a reader struct populated with pointers into the input buffer. + * On failure the returned body_length will be 0. + */ +MessageReader decode_message( + std::uint8_t const * input, std::size_t input_length, + std::size_t mac_length +); + + +} // namespace axolotl diff --git a/src/message.cpp b/src/message.cpp new file mode 100644 index 0000000..d0e45e0 --- /dev/null +++ b/src/message.cpp @@ -0,0 +1,171 @@ +#include "axolotl/message.hh" + +namespace { + +template<typename T> +std::size_t varint_length( + T value +) { + std::size_t result = 1; + while (value > 128U) { + ++result; + value >>= 7; + } + return result; +} + + +template<typename T> +std::uint8_t * varint_encode( + std::uint8_t * output, + T value +) { + while (value > 128U) { + *(output++) = (0x7F & value) | 0x80; + } + (*output++) = value; + return output; +} + + +template<typename T> +T varint_decode( + std::uint8_t const * varint_start, + std::uint8_t const * varint_end +) { + T value = 0; + do { + value <<= 7; + value |= 0x7F & *(--varint_end); + } while (varint_end != varint_start); + return value; +} + + +std::uint8_t const * varint_skip( + std::uint8_t const * input, + std::uint8_t const * input_end +) { + while (input != input_end) { + std::uint8_t tmp = *(input++); + if ((tmp & 0x80) == 0) { + return input; + } + } + return input; +} + + +std::size_t varstring_length( + std::size_t string_length +) { + return varint_length(string_length) + string_length; +} + +static std::size_t const VERSION_LENGTH = 1; +static std::uint8_t const RATCHET_KEY_TAG = 012; +static std::uint8_t const COUNTER_TAG = 020; +static std::uint8_t const CIPHERTEXT_TAG = 042; + +} // namespace + + +std::size_t axolotl::encode_message_length( + std::uint32_t counter, + std::size_t ratchet_key_length, + std::size_t ciphertext_length, + std::size_t mac_length +) { + std::size_t length = VERSION_LENGTH; + length += 1 + varstring_length(ratchet_key_length); + length += 1 + varint_length(counter); + length += 1 + varstring_length(ciphertext_length); + return length + mac_length; +} + + +axolotl::MessageWriter axolotl::encode_message( + std::uint8_t version, + std::uint32_t counter, + std::size_t ratchet_key_length, + std::size_t ciphertext_length, + std::uint8_t * output +) { + axolotl::MessageWriter result; + std::uint8_t * pos = output; + *(pos++) = version; + *(pos++) = COUNTER_TAG; + pos = varint_encode(pos, counter); + *(pos++) = RATCHET_KEY_TAG; + pos = varint_encode(pos, ratchet_key_length); + result.ratchet_key = pos; + pos += ratchet_key_length; + *(pos++) = CIPHERTEXT_TAG; + pos = varint_encode(pos, ciphertext_length); + result.ciphertext = pos; + pos += ciphertext_length; + result.body_length = pos - output; + result.mac = pos; + return result; +} + + +axolotl::MessageReader axolotl::decode_message( + std::uint8_t const * input, std::size_t input_length, + std::size_t mac_length +) { + axolotl::MessageReader result; + result.body_length = 0; + std::uint8_t const * pos = input; + std::uint8_t const * end = input + input_length - mac_length; + std::uint8_t flags = 0; + result.mac = end; + result.mac_length = mac_length; + if (pos == end) return result; + result.version = *(pos++); + while (pos != end) { + uint8_t tag = *(pos); + if (tag == COUNTER_TAG) { + ++pos; + std::uint8_t const * counter_start = pos; + pos = varint_skip(pos, end); + result.counter = varint_decode<std::uint32_t>(counter_start, pos); + flags |= 1; + } else if (tag == RATCHET_KEY_TAG) { + ++pos; + std::uint8_t const * len_start = pos; + pos = varint_skip(pos, end); + std::size_t len = varint_decode<std::size_t>(len_start, pos); + if (len > end - pos) return result; + result.ratchet_key_length = len; + result.ratchet_key = pos; + pos += len; + flags |= 2; + } else if (tag == CIPHERTEXT_TAG) { + ++pos; + std::uint8_t const * len_start = pos; + pos = varint_skip(pos, end); + std::size_t len = varint_decode<std::size_t>(len_start, pos); + if (len > end - pos) return result; + result.ciphertext_length = len; + result.ciphertext = pos; + pos += len; + flags |= 4; + } else if (tag & 0x7 == 0) { + pos = varint_skip(pos, end); + pos = varint_skip(pos, end); + } else if (tag & 0x7 == 2) { + std::uint8_t const * len_start = pos; + pos = varint_skip(pos, end); + std::size_t len = varint_decode<std::size_t>(len_start, pos); + if (len > end - pos) return result; + pos += len; + } else { + return result; + } + } + if (flags == 0x7) { + result.body_length = end - input; + } + return result; +} diff --git a/tests/test_message.cpp b/tests/test_message.cpp new file mode 100644 index 0000000..a09d7e7 --- /dev/null +++ b/tests/test_message.cpp @@ -0,0 +1,53 @@ +#include "axolotl/message.hh" +#include "unittest.hh" + +int main() { + +std::uint8_t message1[36] = "\x03\n\nratchetkey\x10\x01\"\nciphertexthmacsha2"; +std::uint8_t message2[36] = "\x03\x10\x01\n\nratchetkey\"\nciphertexthmacsha2"; +std::uint8_t ratchetkey[11] = "ratchetkey"; +std::uint8_t ciphertext[11] = "ciphertext"; +std::uint8_t hmacsha2[9] = "hmacsha2"; + +{ /* Message decode test */ + +TestCase test_case("Message decode test"); + +axolotl::MessageReader reader(axolotl::decode_message(message1, 35, 8)); + +assert_equals(std::size_t(27), reader.body_length); +assert_equals(std::uint8_t(3), reader.version); +assert_equals(std::uint32_t(1), reader.counter); +assert_equals(std::size_t(10), reader.ratchet_key_length); +assert_equals(std::size_t(10), reader.ciphertext_length); +assert_equals(std::size_t(8), reader.mac_length); + +assert_equals(ratchetkey, reader.ratchet_key, 10); +assert_equals(ciphertext, reader.ciphertext, 10); +assert_equals(hmacsha2, reader.mac, 8); + + +} /* Message decode test */ + +{ /* Message encode test */ + +TestCase test_case("Message encode test"); + +std::size_t length = axolotl::encode_message_length(1, 10, 10, 8); +assert_equals(std::size_t(35), length); + +std::uint8_t output[length]; + +axolotl::MessageWriter writer(axolotl::encode_message(3, 1, 10, 10, output)); +assert_equals(std::size_t(27), writer.body_length); + +std::memcpy(writer.ratchet_key, ratchetkey, 10); +std::memcpy(writer.ciphertext, ciphertext, 10); +std::memcpy(writer.mac, hmacsha2, 8); + +assert_equals(message2, output, 35); + + +} /* Message encode test */ + +} |