Deserialize 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.
pub fn deserialize(comptime HashResult: type, str: []const u8) Error!HashResult
pub 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 ValueType = switch (@typeInfo(@TypeOf(out.alg_version))) {
.optional => |opt| opt.child,
else => @TypeOf(out.alg_version),
};
out.alg_version = fmt.parseUnsigned(
ValueType,
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;
const info = @typeInfo(HashResult).@"struct";
inline for (info.field_names, info.field_types) |p_name, p_type| {
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.attrs.@"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;
const info = @typeInfo(HashResult).@"struct";
inline for (info.field_types, info.field_attrs) |p_type, p_attrs| {
if (@typeInfo(p_type) != .optional and p_attrs.default_value_ptr == null) {
expected_fields += 1;
}
}
if (set_fields < expected_fields) return Error.InvalidEncoding;
return out;
}