struct hex [src]

Alias for std.crypto.codecs.base64_hex_ct.hex

(best-effort) constant time hexadecimal encoding and decoding.

Members

Source

pub const hex = struct { /// Encodes a binary buffer into a hexadecimal string. /// The output buffer must be twice the size of the input buffer. pub fn encode(encoded: []u8, bin: []const u8, comptime case: std.fmt.Case) error{SizeMismatch}!void { if (encoded.len / 2 != bin.len) { return error.SizeMismatch; } for (bin, 0..) |v, i| { const b: u16 = v >> 4; const c: u16 = v & 0xf; const off = if (case == .upper) 32 else 0; const x = ((87 - off + c + (((c -% 10) >> 8) & ~@as(u16, 38 - off))) & 0xff) << 8 | ((87 - off + b + (((b -% 10) >> 8) & ~@as(u16, 38 - off))) & 0xff); encoded[i * 2] = @truncate(x); encoded[i * 2 + 1] = @truncate(x >> 8); } } /// Decodes a hexadecimal string into a binary buffer. /// The output buffer must be half the size of the input buffer. pub fn decode(bin: []u8, encoded: []const u8) error{ SizeMismatch, InvalidCharacter, InvalidPadding }!void { if (encoded.len % 2 != 0) { return error.InvalidPadding; } if (bin.len < encoded.len / 2) { return error.SizeMismatch; } _ = decodeAny(bin, encoded, null) catch |err| { switch (err) { error.InvalidCharacter => return error.InvalidCharacter, error.InvalidPadding => return error.InvalidPadding, else => unreachable, } }; } /// A decoder that ignores certain characters. /// The decoder will skip any characters that are in the ignore list. pub const DecoderWithIgnore = struct { /// The characters to ignore. ignored_chars: StaticBitSet(256) = undefined, /// Decodes a hexadecimal string into a binary buffer. /// The output buffer must be half the size of the input buffer. pub fn decode( self: DecoderWithIgnore, bin: []u8, encoded: []const u8, ) error{ NoSpaceLeft, InvalidCharacter, InvalidPadding }![]const u8 { return decodeAny(bin, encoded, self.ignored_chars); } /// Returns the decoded length of a hexadecimal string, ignoring any characters in the ignore list. /// This operation does not run in constant time, but it aims to avoid leaking information about the underlying hexadecimal string. pub fn decodedLenForSlice(decoder: DecoderWithIgnore, encoded: []const u8) !usize { var hex_len = encoded.len; for (encoded) |c| { if (decoder.ignored_chars.isSet(c)) hex_len -= 1; } if (hex_len % 2 != 0) { return error.InvalidPadding; } return hex_len / 2; } /// Returns the maximum possible decoded size for a given input length after skipping ignored characters. pub fn decodedLenUpperBound(hex_len: usize) usize { return hex_len / 2; } }; /// Creates a new decoder that ignores certain characters. /// The decoder will skip any characters that are in the ignore list. /// The ignore list must not contain any valid hexadecimal characters. pub fn decoderWithIgnore(ignore_chars: []const u8) error{InvalidCharacter}!DecoderWithIgnore { var ignored_chars = StaticBitSet(256).initEmpty(); for (ignore_chars) |c| { switch (c) { '0'...'9', 'a'...'f', 'A'...'F' => return error.InvalidCharacter, else => if (ignored_chars.isSet(c)) return error.InvalidCharacter, } ignored_chars.set(c); } return DecoderWithIgnore{ .ignored_chars = ignored_chars }; } fn decodeAny( bin: []u8, encoded: []const u8, ignored_chars: ?StaticBitSet(256), ) error{ NoSpaceLeft, InvalidCharacter, InvalidPadding }![]const u8 { var bin_pos: usize = 0; var state: bool = false; var c_acc: u8 = 0; for (encoded) |c| { const c_num = c ^ 48; const c_num0: u8 = @truncate((@as(u16, c_num) -% 10) >> 8); const c_alpha: u8 = (c & ~@as(u8, 32)) -% 55; const c_alpha0: u8 = @truncate(((@as(u16, c_alpha) -% 10) ^ (@as(u16, c_alpha) -% 16)) >> 8); if ((c_num0 | c_alpha0) == 0) { if (ignored_chars) |set| { if (set.isSet(c)) { continue; } } return error.InvalidCharacter; } const c_val = (c_num0 & c_num) | (c_alpha0 & c_alpha); if (bin_pos >= bin.len) { return error.NoSpaceLeft; } if (!state) { c_acc = c_val << 4; } else { bin[bin_pos] = c_acc | c_val; bin_pos += 1; } state = !state; } if (state) { return error.InvalidPadding; } return bin[0..bin_pos]; } }