| #include "header.h" |
| |
| namespace sframe { |
| |
| static size_t |
| uint_size(uint64_t val) |
| { |
| if (val < 0x08) { |
| // Fits in the config byte |
| return 0; |
| } |
| |
| for (unsigned int i = 0; i < 8; i += 1) { |
| if ((val >> (8 * i)) == 0) { |
| return i; |
| } |
| } |
| |
| return 8; |
| } |
| |
| void |
| encode_uint(uint64_t val, output_bytes buffer) |
| { |
| size_t size = buffer.size(); |
| for (size_t i = 0; i < size && i < 8; i++) { |
| buffer[size - i - 1] = uint8_t(val >> (8 * i)); |
| } |
| } |
| |
| static uint64_t |
| decode_uint(input_bytes data) |
| { |
| if (!data.empty() && data[0] == 0) { |
| throw invalid_parameter_error("Integer is not minimally encoded"); |
| } |
| |
| uint64_t val = 0; |
| for (size_t i = 0; i < data.size(); i++) { |
| val = (val << 8) + static_cast<uint64_t>(data[i]); |
| } |
| return val; |
| } |
| |
| struct ValueOrLength |
| { |
| bool is_length = false; |
| uint8_t value_or_length = 0; |
| |
| static ValueOrLength for_u64(uint64_t value) |
| { |
| if (value >= 0x08) { |
| return { true, static_cast<uint8_t>(uint_size(value) - 1) }; |
| } else { |
| return { false, static_cast<uint8_t>(value) }; |
| } |
| } |
| |
| static ValueOrLength decode(uint8_t encoded) |
| { |
| const auto is_length = (encoded & 0x08) != 0; |
| const auto value_or_length = (encoded & 0x07); |
| return { is_length, static_cast<uint8_t>(value_or_length) }; |
| } |
| |
| uint8_t encode() const |
| { |
| return (((is_length) ? 1 : 0) << 3) | (value_or_length & 0x07); |
| } |
| |
| size_t value_size() const |
| { |
| if (!is_length) { |
| return 0; |
| } |
| |
| return value_or_length + 1; |
| } |
| |
| std::tuple<uint64_t, input_bytes> read(input_bytes data) const |
| { |
| if (!is_length) { |
| // Nothing to read; value is already in config byte |
| return { value_or_length, data }; |
| } |
| |
| const auto size = value_size(); |
| const auto value = decode_uint(data.subspan(0, size)); |
| const auto remaining = data.subspan(size); |
| return { value, remaining }; |
| } |
| |
| private: |
| ValueOrLength(bool is_length_in, uint8_t value_or_length_in) |
| : is_length(is_length_in) |
| , value_or_length(value_or_length_in) |
| { |
| } |
| }; |
| |
| struct ConfigByte |
| { |
| ValueOrLength kid; |
| ValueOrLength ctr; |
| |
| ConfigByte(uint64_t kid_in, uint64_t ctr_in) |
| : kid(ValueOrLength::for_u64(kid_in)) |
| , ctr(ValueOrLength::for_u64(ctr_in)) |
| { |
| } |
| |
| explicit ConfigByte(uint8_t encoded) |
| : kid(ValueOrLength::decode(encoded >> 4)) |
| , ctr(ValueOrLength::decode(encoded & 0x0f)) |
| { |
| } |
| |
| size_t encoded_size() const |
| { |
| return 1 + kid.value_size() + ctr.value_size(); |
| } |
| |
| uint8_t encode() const { return (kid.encode() << 4) | ctr.encode(); } |
| }; |
| |
| Header::Header(KeyID key_id_in, Counter counter_in) |
| : key_id(key_id_in) |
| , counter(counter_in) |
| { |
| const auto cfg = ConfigByte{ key_id, counter }; |
| |
| _encoded[0] = cfg.encode(); |
| _encoded.resize(cfg.encoded_size()); |
| |
| const auto encoded = output_bytes(_encoded); |
| const auto after_cfg = encoded.subspan(1); |
| encode_uint(key_id, after_cfg.subspan(0, cfg.kid.value_size())); |
| |
| const auto after_kid = after_cfg.subspan(cfg.kid.value_size()); |
| encode_uint(counter, after_kid.subspan(0, cfg.ctr.value_size())); |
| } |
| |
| Header |
| Header::parse(input_bytes buffer) |
| { |
| if (buffer.size() < Header::min_size) { |
| throw buffer_too_small_error("Ciphertext too small to decode header"); |
| } |
| |
| const auto cfg = ConfigByte{ buffer[0] }; |
| const auto after_cfg = buffer.subspan(1); |
| const auto [key_id, after_kid] = cfg.kid.read(after_cfg); |
| const auto [counter, _] = cfg.ctr.read(after_kid); |
| const auto encoded = buffer.subspan(0, cfg.encoded_size()); |
| |
| return Header(key_id, counter, encoded); |
| } |
| |
| Header::Header(KeyID key_id_in, Counter counter_in, input_bytes encoded_in) |
| : key_id(key_id_in) |
| , counter(counter_in) |
| , _encoded(encoded_in) |
| { |
| } |
| |
| #if 0 |
| std::tuple<Header, input_bytes> |
| decode(input_bytes buffer) |
| { |
| if (buffer.size() < Header::min_size) { |
| throw buffer_too_small_error("Ciphertext too small to decode header"); |
| } |
| |
| const auto cfg = ConfigByte{ buffer[0] }; |
| const auto after_cfg = buffer.subspan(1); |
| const auto [kid, after_kid] = cfg.kid.read(after_cfg); |
| const auto [ctr, after_ctr] = cfg.ctr.read(after_kid); |
| const auto header = Header{ KeyID(kid), Counter(ctr) }; |
| |
| return { header, after_ctr }; |
| } |
| |
| size_t |
| Header::encode(output_bytes buffer) const |
| { |
| if (buffer.size() < size()) { |
| throw buffer_too_small_error("Buffer too small to encode header"); |
| } |
| |
| const auto cfg = ConfigByte{ key_id, counter }; |
| buffer[0] = cfg.encode(); |
| |
| const auto after_cfg = buffer.subspan(1); |
| encode_uint(key_id, after_cfg.subspan(0, cfg.kid.size())); |
| |
| const auto after_kid = after_cfg.subspan(cfg.kid.size()); |
| encode_uint(counter, after_kid.subspan(0, cfg.ctr.size())); |
| |
| return size(); |
| } |
| #endif |
| |
| } // namespace sframe |