aboutsummaryrefslogtreecommitdiffstats
path: root/src/base32.zig
blob: b1400a5a234fa62d6cf7d2f9f6f89a73e838665a (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
const std = @import("std");

// TODO(rutgerbrf): simplify the code of the encoder & decoder?

pub const Encoder = struct {
    const Self = @This();

    out_off: u4 = 0,
    buf: u5 = 0,

    pub fn write(self: *Self, b: u8, out: []u8) usize {
        var i: usize = 0;
        var bits_left: u4 = 8;
        while (bits_left > 0) {
            var space_avail = @truncate(u3, 5 - self.out_off);
            var write_bits: u3 = if (bits_left < space_avail) @truncate(u3, bits_left) else space_avail;
            bits_left -= write_bits;
            var mask: u8 = (@as(u8, 0x01) << write_bits) - 1;
            var want: u8 = (b >> @truncate(u3, bits_left)) & mask;
            self.buf |= @truncate(u5, want << (space_avail - write_bits));
            self.out_off += write_bits;
            if (self.out_off == 5) {
                if (i >= out.len) break;
                out[i] = self.char();
                i += 1;
                self.out_off = 0;
                self.buf = 0;
            }
        }
        return i;
    }

    fn char(self: *const Self) u8 {
        return self.buf + (if (self.buf < 26) @as(u8, 'A') else '2' - 26);
    }
};

pub const DecodeError = error{CorruptInputError};

pub const Decoder = struct {
    const Self = @This();

    out_off: u4 = 0,
    buf: u8 = 0,

    pub fn read(self: *Self, c: u8) DecodeError!?u8 {
        var ret: ?u8 = null;
        var decoded_c = try decodeChar(c);
        var bits_left: u3 = 5;
        while (bits_left > 0) {
            var space_avail: u4 = 8 - self.out_off;
            var write_bits: u3 = if (bits_left < space_avail) bits_left else @truncate(u3, space_avail);
            bits_left -= write_bits;
            var mask: u8 = (@as(u8, 0x01) << write_bits) - 1;
            var want: u8 = (decoded_c >> bits_left) & mask;
            self.buf |= want << @truncate(u3, space_avail - write_bits);
            self.out_off += write_bits;
            if (self.out_off == 8) {
                ret = self.buf;
                self.out_off = 0;
                self.buf = 0;
            }
        }
        return ret;
    }

    fn decodeChar(p: u8) DecodeError!u5 {
        var value: u5 = 0;
        if (p >= 'A' and p <= 'Z') {
            value = @truncate(u5, p - @as(u8, 'A'));
        } else if (p >= '2' and p <= '9') {
            // '2' -> 26
            value = @truncate(u5, p - @as(u8, '2') + 26);
        } else {
            return error.CorruptInputError;
        }
        return value;
    }
};

pub fn encodedLen(src_len: usize) usize {
    const src_len_bits = src_len * 8;
    return src_len_bits / 5 + (if (src_len_bits % 5 > 0) @as(usize, 1) else 0);
}

pub fn decodedLen(enc_len: usize) usize {
    const enc_len_bits = enc_len * 5;
    return enc_len_bits / 8;
}

pub fn encode(bs: []const u8, out: []u8) usize {
    var e = Encoder{};
    var i: usize = 0;
    for (bs) |b| {
        if (i >= out.len) break;
        i += e.write(b, out[i..]);
    }
    if (e.out_off != 0 and i < out.len) {
        out[i] = e.char();
        i += 1;
    }
    return i; // amount of bytes processed
}

pub fn decode(ps: []const u8, out: []u8) DecodeError!usize {
    var d = Decoder{};
    var i: usize = 0;
    for (ps) |p| {
        if (i >= out.len) break;
        if (try d.read(p)) |b| {
            out[i] = b;
            i += 1;
        }
    }
    if (d.out_off != 0 and i < out.len) {
        out[i] = d.buf;
        i += 1;
    }
    return i; // amount of bytes processed
}