A wrapped binary value whose maximum size is max_len.
This type must be used whenever a binary value is encoded in a PHC-formatted string.
This includes salt, hash, and any other binary parameters such as keys.
Once initialized, the actual value can be read with the constSlice() function.
max_len: usizebuf: [max_len]u8 = undefinedlen: usize = 0pub fn BinValue(comptime max_len: usize) type {
return struct {
const Self = @This();
const capacity = max_len;
const max_encoded_length = B64Encoder.calcSize(max_len);
buf: [max_len]u8 = undefined,
len: usize = 0,
/// Wrap an existing byte slice
pub fn fromSlice(slice: []const u8) Error!Self {
if (slice.len > capacity) return Error.NoSpaceLeft;
var bin_value: Self = undefined;
@memcpy(bin_value.buf[0..slice.len], slice);
bin_value.len = slice.len;
return bin_value;
}
/// Return the slice containing the actual value.
pub fn constSlice(self: *const Self) []const u8 {
return self.buf[0..self.len];
}
fn fromB64(self: *Self, str: []const u8) !void {
const len = B64Decoder.calcSizeForSlice(str) catch return Error.InvalidEncoding;
if (len > self.buf.len) return Error.NoSpaceLeft;
B64Decoder.decode(&self.buf, str) catch return Error.InvalidEncoding;
self.len = len;
}
fn toB64(self: *const Self, buf: []u8) ![]const u8 {
const value = self.constSlice();
const len = B64Encoder.calcSize(value.len);
if (len > buf.len) return Error.NoSpaceLeft;
return B64Encoder.encode(buf, value);
}
};
}anyerror means the error set is known only at runtime.
pub const Error = std.crypto.errors.EncodingError || error{NoSpaceLeft}pub fn deserialize(comptime HashResult: type, str: []const u8) Error!HashResultDeserialize a PHC-formatted string into a structure HashResult.
Required field in the HashResult structure:
alg_id: algorithm identifier
Optional, special fields:alg_version: algorithm version (unsigned integer)salt: salthash: output of the hash functionOther fields will also be deserialized from the function parameters section.
HashResult: typestr: []const u8pub fn deserialize(comptime HashResult: type, str: []const u8) Error!HashResult {
if (@hasField(HashResult, version_param_name)) {
@compileError("Field name '" ++ version_param_name ++ "'' is reserved for the algorithm version");
}
var out = mem.zeroes(HashResult);
var it = mem.splitScalar(u8, str, fields_delimiter_scalar);
var set_fields: usize = 0;
while (true) {
// Read the algorithm identifier
if ((it.next() orelse return Error.InvalidEncoding).len != 0) return Error.InvalidEncoding;
out.alg_id = it.next() orelse return Error.InvalidEncoding;
set_fields += 1;
// Read the optional version number
var field = it.next() orelse break;
if (kvSplit(field)) |opt_version| {
if (mem.eql(u8, opt_version.key, version_param_name)) {
if (@hasField(HashResult, "alg_version")) {
const value_type_info = switch (@typeInfo(@TypeOf(out.alg_version))) {
.optional => |opt| @typeInfo(opt.child),
else => |t| t,
};
out.alg_version = fmt.parseUnsigned(
@Type(value_type_info),
opt_version.value,
10,
) catch return Error.InvalidEncoding;
set_fields += 1;
}
field = it.next() orelse break;
}
} else |_| {}
// Read optional parameters
var has_params = false;
var it_params = mem.splitScalar(u8, field, params_delimiter_scalar);
while (it_params.next()) |params| {
const param = kvSplit(params) catch break;
var found = false;
inline for (comptime meta.fields(HashResult)) |p| {
if (mem.eql(u8, p.name, param.key)) {
switch (@typeInfo(p.type)) {
.int => @field(out, p.name) = fmt.parseUnsigned(
p.type,
param.value,
10,
) catch return Error.InvalidEncoding,
.pointer => |ptr| {
if (!ptr.is_const) @compileError("Value slice must be constant");
@field(out, p.name) = param.value;
},
.@"struct" => try @field(out, p.name).fromB64(param.value),
else => std.debug.panic(
"Value for [{s}] must be an integer, a constant slice or a BinValue",
.{p.name},
),
}
set_fields += 1;
found = true;
break;
}
}
if (!found) return Error.InvalidEncoding; // An unexpected parameter was found in the string
has_params = true;
}
// No separator between an empty parameters set and the salt
if (has_params) field = it.next() orelse break;
// Read an optional salt
if (@hasField(HashResult, "salt")) {
try out.salt.fromB64(field);
set_fields += 1;
} else {
return Error.InvalidEncoding;
}
// Read an optional hash
field = it.next() orelse break;
if (@hasField(HashResult, "hash")) {
try out.hash.fromB64(field);
set_fields += 1;
} else {
return Error.InvalidEncoding;
}
break;
}
// Check that all the required fields have been set, excluding optional values and parameters
// with default values
var expected_fields: usize = 0;
inline for (comptime meta.fields(HashResult)) |p| {
if (@typeInfo(p.type) != .optional and p.default_value_ptr == null) {
expected_fields += 1;
}
}
if (set_fields < expected_fields) return Error.InvalidEncoding;
return out;
}pub fn serialize(params: anytype, str: []u8) Error![]const u8Serialize parameters into a PHC string.
Required field for params:
alg_id: algorithm identifier
Optional, special fields:alg_version: algorithm version (unsigned integer)salt: salthash: output of the hash functionparams can also include any additional parameters.
str: []u8pub fn serialize(params: anytype, str: []u8) Error![]const u8 {
var buf = io.fixedBufferStream(str);
try serializeTo(params, buf.writer());
return buf.getWritten();
}pub fn calcSize(params: anytype) usizeCompute the number of bytes required to serialize params
pub fn calcSize(params: anytype) usize {
var buf = io.countingWriter(io.null_writer);
serializeTo(params, buf.writer()) catch unreachable;
return @as(usize, @intCast(buf.bytes_written));
}