feature. See also
. The project being documented here (as the example) is the Zig library itself.
scrypt.crypt_format
const crypt_format = struct
File
Code
const crypt_format = struct {
pub const prefix = "$7$";
pub fn HashResult(comptime crypt_max_hash_len: usize) type {
return struct {
ln: u6,
r: u30,
p: u30,
salt: []const u8,
hash: BinValue(crypt_max_hash_len),
};
}
const Codec = CustomB64Codec("./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".*);
pub fn BinValue(comptime max_len: usize) type {
return struct {
const Self = @This();
const capacity = max_len;
const max_encoded_length = Codec.encodedLen(max_len);
buf: [max_len]u8 = undefined,
len: usize = 0,
pub fn fromSlice(slice: []const u8) EncodingError!Self {
if (slice.len > capacity) return EncodingError.NoSpaceLeft;
var bin_value: Self = undefined;
@memcpy(bin_value.buf[0..slice.len], slice);
bin_value.len = slice.len;
return bin_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 = Codec.decodedLen(str.len);
if (len > self.buf.len) return EncodingError.NoSpaceLeft;
try Codec.decode(self.buf[0..len], str);
self.len = len;
}
fn toB64(self: *const Self, buf: []u8) ![]const u8 {
const value = self.constSlice();
const len = Codec.encodedLen(value.len);
if (len > buf.len) return EncodingError.NoSpaceLeft;
const encoded = buf[0..len];
Codec.encode(encoded, value);
return encoded;
}
};
}
pub fn saltFromBin(comptime len: usize, salt: [len]u8) [Codec.encodedLen(len)]u8 {
var buf: [Codec.encodedLen(len)]u8 = undefined;
Codec.encode(&buf, &salt);
return buf;
}
pub fn deserialize(comptime T: type, str: []const u8) EncodingError!T {
var out: T = undefined;
if (str.len < 16) return EncodingError.InvalidEncoding;
if (!mem.eql(u8, prefix, str[0..3])) return EncodingError.InvalidEncoding;
out.ln = try Codec.intDecode(u6, str[3..4]);
out.r = try Codec.intDecode(u30, str[4..9]);
out.p = try Codec.intDecode(u30, str[9..14]);
var it = mem.splitScalar(u8, str[14..], '$');
const salt = it.first();
if (@hasField(T, "salt")) out.salt = salt;
const hash_str = it.next() orelse return EncodingError.InvalidEncoding;
if (@hasField(T, "hash")) try out.hash.fromB64(hash_str);
return out;
}
pub fn serialize(params: anytype, str: []u8) EncodingError![]const u8 {
var w: std.Io.Writer = .fixed(str);
serializeTo(params, &w) catch |err| switch (err) {
error.WriteFailed => return error.NoSpaceLeft,
else => |e| return e,
};
return w.buffered();
}
pub fn calcSize(params: anytype) usize {
var trash: [128]u8 = undefined;
var d: std.Io.Writer.Discarding = .init(&trash);
serializeTo(params, &d.writer) catch unreachable;
return @intCast(d.fullCount());
}
fn serializeTo(params: anytype, w: *std.Io.Writer) !void {
var header: [14]u8 = undefined;
header[0..3].* = prefix.*;
Codec.intEncode(header[3..4], params.ln);
Codec.intEncode(header[4..9], params.r);
Codec.intEncode(header[9..14], params.p);
try w.writeAll(&header);
try w.writeAll(params.salt);
try w.writeAll("$");
var buf: [@TypeOf(params.hash).max_encoded_length]u8 = undefined;
const hash_str = try params.hash.toB64(&buf);
try w.writeAll(hash_str);
}
fn CustomB64Codec(comptime map: [64]u8) type {
return struct {
const map64 = map;
fn encodedLen(len: usize) usize {
return (len * 4 + 2) / 3;
}
fn decodedLen(len: usize) usize {
return len / 4 * 3 + (len % 4) * 3 / 4;
}
fn intEncode(dst: []u8, src: anytype) void {
var n = src;
for (dst) |*x| {
x.* = map64[@as(u6, @truncate(n))];
n = math.shr(@TypeOf(src), n, 6);
}
}
fn intDecode(comptime T: type, src: *const [(@bitSizeOf(T) + 5) / 6]u8) !T {
var v: T = 0;
for (src, 0..) |x, i| {
const vi = mem.findScalar(u8, &map64, x) orelse return EncodingError.InvalidEncoding;
v |= @as(T, @intCast(vi)) << @as(math.Log2Int(T), @intCast(i * 6));
}
return v;
}
fn decode(dst: []u8, src: []const u8) !void {
std.debug.assert(dst.len == decodedLen(src.len));
var i: usize = 0;
while (i < src.len / 4) : (i += 1) {
mem.writeInt(u24, dst[i * 3 ..][0..3], try intDecode(u24, src[i * 4 ..][0..4]), .little);
}
const leftover = src[i * 4 ..];
var v: u24 = 0;
for (leftover, 0..) |_, j| {
v |= @as(u24, try intDecode(u6, leftover[j..][0..1])) << @as(u5, @intCast(j * 6));
}
for (dst[i * 3 ..], 0..) |*x, j| {
x.* = @as(u8, @truncate(v >> @as(u5, @intCast(j * 8))));
}
}
fn encode(dst: []u8, src: []const u8) void {
std.debug.assert(dst.len == encodedLen(src.len));
var i: usize = 0;
while (i < src.len / 3) : (i += 1) {
intEncode(dst[i * 4 ..][0..4], mem.readInt(u24, src[i * 3 ..][0..3], .little));
}
const leftover = src[i * 3 ..];
var v: u24 = 0;
for (leftover, 0..) |x, j| {
v |= @as(u24, x) << @as(u5, @intCast(j * 8));
}
intEncode(dst[i * 4 ..], v);
}
};
}
}