Skip to content

Commit 6eb21bb

Browse files
committed
Add decode option
1 parent 9da34f0 commit 6eb21bb

File tree

3 files changed

+131
-24
lines changed

3 files changed

+131
-24
lines changed

README.md

+14
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,17 @@
11
# Zig Base32
22

33
> Base32 library
4+
5+
## Usage
6+
7+
1. Encode
8+
```
9+
$ zbase32 Hello
10+
JBSWY3DP
11+
```
12+
13+
2. Decode
14+
```
15+
$ zbase32 -d JBSWY3DP
16+
Hello
17+
```

src/base32.zig

+91-12
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,9 @@ const Error = error{
99
OutOfMemory,
1010
};
1111

12-
// 2-7: 50-55
13-
// A-Z: 65-90
1412
pub const standard_alphabet_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567".*;
1513
pub const standard_alphabet_values = [32]u8{
16-
0b00_00000, // A
14+
0b00_00000,
1715
0b00_00001,
1816
0b00_00010,
1917
0b00_00011,
@@ -39,12 +37,12 @@ pub const standard_alphabet_values = [32]u8{
3937
0b00_10111,
4038
0b00_11000,
4139
0b00_11001,
42-
0b00_11010, // 2
43-
0b00_11011, // 3
44-
0b00_11100, // 4
45-
0b00_11101, // 5
46-
0b00_11110, // 6
47-
0b00_11111, // 7
40+
0b00_11010,
41+
0b00_11011,
42+
0b00_11100,
43+
0b00_11101,
44+
0b00_11110,
45+
0b00_11111,
4846
};
4947

5048
pub const Base32Encoder = struct {
@@ -62,9 +60,7 @@ pub const Base32Encoder = struct {
6260
const rem: u8 = @intCast(u8, text.len % wsize);
6361
const n: u8 = @intCast(u8, text.len / wsize);
6462
var buf: [9]u8 = .{0} ** 9;
65-
66-
const allocator = self.allocator;
67-
var list = std.ArrayList(u8).init(allocator);
63+
var list = std.ArrayList(u8).init(self.allocator);
6864

6965
for (0..n) |i| {
7066
for (wsize * i..wsize * (i + 1)) |j| {
@@ -122,6 +118,67 @@ pub const Base32Encoder = struct {
122118

123119
return Error.InvalidCharacter;
124120
}
121+
122+
fn lookup_v(b: u8) Error!u8 {
123+
for (standard_alphabet_chars, 0..) |x, i| {
124+
if (b == x) return standard_alphabet_values[i];
125+
}
126+
127+
return Error.InvalidCharacter;
128+
}
129+
130+
pub fn decode(self: *Self, text: []const u8) Error![]u8 {
131+
if (text.len % 8 != 0) return Error.InvalidPadding;
132+
const wsize = 8;
133+
const n: u8 = @intCast(u8, text.len / wsize);
134+
var buf: [9]u8 = .{0} ** 9;
135+
var list = std.ArrayList(u8).init(self.allocator);
136+
137+
for (0..n) |i| {
138+
for (wsize * i..wsize * (i + 1)) |j| {
139+
if (text[j] == "="[0]) break;
140+
buf[buf[8]] = text[j];
141+
buf[8] += 1;
142+
}
143+
144+
const spit = try spit_decoded(buf);
145+
try list.appendSlice(spit[0..spit[8]]);
146+
buf = .{0} ** 9;
147+
}
148+
149+
return list.items;
150+
}
151+
152+
fn spit_decoded(src: [9]u8) Error![9]u8 {
153+
var dest: [9]u8 = .{0} ** 9;
154+
var lut = [_]u8{0} ** 8;
155+
inline for (0..8) |i| {
156+
lut[i] = Base32Encoder.lookup_v(src[i]) catch 0;
157+
}
158+
159+
if (src[8] > 1) {
160+
dest[0] = lut[0] << 3 | lut[1] >> 2;
161+
dest[8] = 1;
162+
}
163+
if (src[8] > 3) {
164+
dest[1] = lut[1] << 6 | lut[2] << 1 | lut[3] >> 4;
165+
dest[8] = 2;
166+
}
167+
if (src[8] > 4) {
168+
dest[2] = lut[3] << 4 | lut[4] >> 1;
169+
dest[8] = 3;
170+
}
171+
if (src[8] > 6) {
172+
dest[3] = lut[4] << 7 | lut[5] << 2 | lut[6] >> 3;
173+
dest[8] = 4;
174+
}
175+
if (src[8] == 8) {
176+
dest[4] = lut[6] << 5 | lut[7];
177+
dest[8] = 5;
178+
}
179+
180+
return dest;
181+
}
125182
};
126183

127184
const TestPair = struct {
@@ -150,3 +207,25 @@ test "encode string" {
150207
try testing.expect(std.mem.eql(u8, res, t.expect));
151208
}
152209
}
210+
211+
test "decode string" {
212+
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
213+
defer arena.deinit();
214+
const allocator = arena.allocator();
215+
var b32 = Base32Encoder.init(allocator);
216+
const testCases = [_]TestPair{
217+
.{ .arg = "JA======", .expect = "H" },
218+
.{ .arg = "JBSQ====", .expect = "He" },
219+
.{ .arg = "JBSWY===", .expect = "Hel" },
220+
.{ .arg = "JBSWY3A=", .expect = "Hell" },
221+
.{ .arg = "JBSWY3DP", .expect = "Hello" },
222+
.{ .arg = "JBSWY3DPEE======", .expect = "Hello!" },
223+
.{ .arg = "GEZDGNBVGY3TQOI=", .expect = "123456789" },
224+
.{ .arg = "GEZDGNBVGY3TQOJQGEZDGNBV", .expect = "123456789012345" },
225+
};
226+
227+
for (testCases) |t| {
228+
const res = try b32.decode(t.arg);
229+
try testing.expect(std.mem.eql(u8, res, t.expect));
230+
}
231+
}

src/cli.zig

+26-12
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,41 @@
11
const std = @import("std");
22
const mem = std.mem;
3+
const process = std.process;
34
const lib = @import("base32.zig");
45

6+
fn start_cli(args: *process.ArgIterator, a: []const u8) !void {
7+
if (mem.eql(u8, a, "-h")) {
8+
println(help);
9+
std.process.exit(0);
10+
}
11+
12+
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
13+
defer arena.deinit();
14+
const allocator = arena.allocator();
15+
var encoder = lib.Base32Encoder.init(allocator);
16+
17+
var output: []const u8 = undefined;
18+
if (mem.eql(u8, a, "-d")) {
19+
if (args.next()) |encoded_in| {
20+
output = try encoder.decode(encoded_in);
21+
}
22+
} else {
23+
output = try encoder.encode(a);
24+
}
25+
26+
try write_stdout(output);
27+
}
28+
529
pub fn main() !void {
6-
var args = std.process.args();
30+
var args = process.args();
731
if (args.inner.count <= 1) {
832
println(help);
933
goodbye("Missing arguments", .{});
1034
}
1135
_ = args.next();
1236

1337
if (args.next()) |a| {
14-
if (mem.eql(u8, a, "-h")) {
15-
println(help);
16-
std.process.exit(0);
17-
}
18-
19-
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
20-
defer arena.deinit();
21-
const allocator = arena.allocator();
22-
var encoder = lib.Base32Encoder.init(allocator);
23-
const encoded = try encoder.encode(a);
24-
try write_stdout(encoded);
38+
try start_cli(&args, a);
2539
}
2640
}
2741

0 commit comments

Comments
 (0)