Compare commits

..

No commits in common. "105962593321522f29005f004ebda4325bf1d5dc" and "607bf5021e1a577acad43ed656d8a9794fd9e8a2" have entirely different histories.

3 changed files with 120 additions and 139 deletions

View File

@ -5,19 +5,9 @@ A base64 encoder & decoder in Zig
## Usage ## Usage
```shell ```shell
# Print help
# Encoding # Encoding
> baze64 "hello" echo -n "hello" | baze64
# Decoding with -D or -d # Decoding
> baze64 -D "aGVsbG8=" echo -n "aGVsbG8=" | baze64
# Print help
> baze64 -H
Usage: baze64 {-d/-D} {-h/-H} input
With no arguments, encodes input into a base64 string
Args:
-d -D: decodes a base64 string
-h -H: print this help
``` ```

View File

@ -5,103 +5,105 @@ pub const err = error{
IndexNotFound, IndexNotFound,
}; };
fn char_at(index: u8) u8 { pub const Base64 = struct {
const table: *const [64]u8 = comptime "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; table: *const [64]u8,
return table[index];
}
pub fn encode(allocator: std.mem.Allocator, input: []const u8) ![]u8 { pub const init: Base64 = .{
if (input.len == 0) { .table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
return ""; };
fn char_at(self: Base64, index: u8) u8 {
return self.table[index];
} }
const out_sz = try calc_encode_length(input); pub fn encode(self: Base64, allocator: std.mem.Allocator, input: []const u8) ![]u8 {
var out = try allocator.alloc(u8, out_sz); if (input.len == 0) {
var buf = [3]u8{0, 0, 0}; return "";
var count: u16 = 0;
var outc: u16 = 0;
for (input) |b| {
buf[count] = b;
count += 1;
if (count == 3) {
out[outc] = char_at(buf[0] >> 2);
out[outc + 1] = char_at(((buf[0] & 0x03) << 4) + (buf[1] >> 4));
out[outc + 2] = char_at(((buf[1] & 0x0F) << 2) + (buf[2] >> 6));
out[outc + 3] = char_at(buf[2] & 0x3F);
count = 0;
outc += 4;
} }
}
if (count == 2) { const encode_length = try calc_encode_length(input);
out[outc] = char_at(buf[0] >> 2); var out = try allocator.alloc(u8, encode_length);
out[outc + 1] = char_at((buf[0] & 0x03) << 4) + (buf[1] >> 4); var buf = [3]u8{0, 0, 0};
out[outc + 2] = char_at((buf[1] & 0x0F) << 2); var count: u16 = 0;
out[outc + 3] = '='; var outc: u16 = 0;
} else if (count == 1) {
out[outc] = char_at(buf[0] >> 2);
out[outc + 1] = char_at((buf[0] & 0x03) << 4);
out[outc + 2] = '=';
out[outc + 3] = '=';
}
return out; for (input) |b| {
} buf[count] = b;
count += 1;
fn decode_index(char: u8) err!u8 { if (count == 3) {
if (char == '=') { out[outc] = self.char_at(buf[0] >> 2);
return 64; out[outc + 1] = self.char_at(((buf[0] & 0x03) << 4) + (buf[1] >> 4));
} out[outc + 2] = self.char_at(((buf[1] & 0x0F) << 2) + (buf[2] >> 6));
out[outc + 3] = self.char_at(buf[2] & 0x3F);
if (char >= 'A' and char <= 'Z') { count = 0;
return char - 'A'; outc += 4;
} else if (char >= 'a' and char <= 'z') {
return char - 'a' + 26;
} else if (char >= '0' and char <= '9') {
return char - '0' + 52;
}
switch (char) {
'+' => { return 62; },
'/' => { return 63; },
'=' => { return 64; },
else => { return err.IndexNotFound; }
}
}
pub fn decode(allocator: std.mem.Allocator, input: []const u8) ![]u8 {
if (input.len == 0) {
return "";
}
const out_sz = try calc_decode_length(input);
var out = try allocator.alloc(u8, out_sz);
var count: u8 = 0;
var iout: u64 = 0;
var buf = [4]u8{ 0, 0, 0, 0 };
var cutoff :u8 = 0;
for (0..input.len) |i| {
buf[count] = try decode_index(input[i]);
count += 1;
if (count == 4) {
out[iout] = (buf[0] << 2) + (buf[1] >> 4);
if (buf[2] != 64) {
out[iout + 1] = (buf[1] << 4) + (buf[2] >> 2);
cutoff = 2;
} }
if (buf[3] != 64) {
out[iout + 2] = (buf[2] << 6) + buf[3];
cutoff = 1;
}
iout += 3;
count = 0;
} }
if (count == 2) {
out[outc] = self.char_at(buf[0] >> 2);
out[outc + 1] = self.char_at((buf[0] & 0x03) << 4) + (buf[1] >> 4);
out[outc + 2] = self.char_at((buf[1] & 0x0F) << 2);
out[outc + 3] = '=';
} else if (count == 1) {
out[outc] = self.char_at(buf[0] >> 2);
out[outc + 1] = self.char_at((buf[0] & 0x03) << 4);
out[outc + 2] = '=';
out[outc + 3] = '=';
}
return out;
} }
return out[0..out.len - cutoff + 1]; fn decode_index(self: Base64, char: u8) err!u8 {
} if (char == '=') {
return 64;
}
if (char >= 'A' and char <= 'Z') {
return char - 'A';
} else if (char >= 'a' and char <= 'z') {
return char - 'a' + 26;
} else {
for (52..64) |i| {
const idx: u8 = @intCast(i);
if (self.char_at(idx) == char) {
return idx;
}
}
}
return err.IndexNotFound;
}
pub fn decode(self: Base64, allocator: std.mem.Allocator, input: []const u8) ![]u8 {
if (input.len == 0) {
return "";
}
const output_sz = try calc_decode_length(input);
var output = try allocator.alloc(u8, output_sz);
var count: u8 = 0;
var iout: u64 = 0;
var buf = [4]u8{ 0, 0, 0, 0 };
for (0..input.len) |i| {
buf[count] = try self.decode_index(input[i]);
count += 1;
if (count == 4) {
output[iout] = (buf[0] << 2) + (buf[1] >> 4);
if (buf[2] != 64) {
output[iout + 1] = (buf[1] << 4) + (buf[2] >> 2);
}
if (buf[3] != 64) {
output[iout + 2] = (buf[2] << 6) + buf[3];
}
iout += 3;
count = 0;
}
}
return output;
}
};
fn calc_encode_length(input: []const u8) !usize { fn calc_encode_length(input: []const u8) !usize {
if (input.len < 3) { if (input.len < 3) {
@ -120,51 +122,57 @@ fn calc_decode_length(input: []const u8) !usize {
} }
test "encode hello" { test "encode hello" {
var b = Base64.init;
var gpa = std.heap.GeneralPurposeAllocator(.{}).init; var gpa = std.heap.GeneralPurposeAllocator(.{}).init;
const allocator = gpa.allocator(); const allocator = gpa.allocator();
const encoded = try encode(allocator, "hello"); const encoded = try b.encode(allocator, "hello");
try std.testing.expect(std.mem.eql(u8, encoded, "aGVsbG8=")); try std.testing.expect(std.mem.eql(u8, encoded, "aGVsbG8="));
} }
test "encode long" { test "encode long" {
var b = Base64.init;
var gpa = std.heap.GeneralPurposeAllocator(.{}).init; var gpa = std.heap.GeneralPurposeAllocator(.{}).init;
const allocator = gpa.allocator(); const allocator = gpa.allocator();
const encoded = try encode(allocator, "Hey, it's me. I'm the problem. It's me"); const encoded = try b.encode(allocator, "Hey, it's me. I'm the problem. It's me");
try std.testing.expect(std.mem.eql(u8, encoded, "SGV5LCBpdCdzIG1lLiBJJ20gdGhlIHByb2JsZW0uIEl0J3MgbWU=")); try std.testing.expect(std.mem.eql(u8, encoded, "SGV5LCBpdCdzIG1lLiBJJ20gdGhlIHByb2JsZW0uIEl0J3MgbWU="));
} }
test "decode hello" { test "decode hello" {
var b = Base64.init;
var gpa = std.heap.GeneralPurposeAllocator(.{}).init; var gpa = std.heap.GeneralPurposeAllocator(.{}).init;
const allocator = gpa.allocator(); const allocator = gpa.allocator();
const encoded = try decode(allocator, "aGVsbG8="); const encoded = try b.decode(allocator, "aGVsbG8=");
try stdout.print("{s}\n", .{encoded}); try stdout.print("{s}\n", .{encoded});
try std.testing.expect(std.mem.eql(u8, encoded, "hello")); try std.testing.expect(std.mem.eql(u8, encoded, "hello"));
} }
test "decode long" { test "decode long" {
var b = Base64.init;
var gpa = std.heap.GeneralPurposeAllocator(.{}).init; var gpa = std.heap.GeneralPurposeAllocator(.{}).init;
const allocator = gpa.allocator(); const allocator = gpa.allocator();
const encoded = try decode(allocator, "SGV5LCBpdCdzIG1lLiBJJ20gdGhlIHByb2JsZW0uIEl0J3MgbWU="); const encoded = try b.decode(allocator, "SGV5LCBpdCdzIG1lLiBJJ20gdGhlIHByb2JsZW0uIEl0J3MgbWU=");
try stdout.print("{s}\n", .{encoded}); try stdout.print("{s}\n", .{encoded});
try std.testing.expect(std.mem.eql(u8, encoded, "Hey, it's me. I'm the problem. It's me")); try std.testing.expect(std.mem.eql(u8, encoded, "Hey, it's me. I'm the problem. It's me"));
} }
test "decode_index" { test "decode_index" {
try std.testing.expectError(err.IndexNotFound, decode_index('{')); var b = Base64.init;
try std.testing.expectError(err.IndexNotFound, b.decode_index('{'));
var r :u8 = 0; var r :u8 = 0;
r = try decode_index('A'); r = try b.decode_index('A');
try std.testing.expect(r == 0); try std.testing.expect(r == 0);
r = try decode_index('a'); r = try b.decode_index('a');
try std.testing.expect(r == 26); try std.testing.expect(r == 26);
r = try decode_index('0'); r = try b.decode_index('0');
try std.testing.expect(r == 52); try std.testing.expect(r == 52);
r = try decode_index('/'); r = try b.decode_index('/');
try std.testing.expect(r == 63); try std.testing.expect(r == 63);
r = try decode_index('='); r = try b.decode_index('=');
try std.testing.expect(r == 64); try std.testing.expect(r == 64);
} }

View File

@ -1,14 +1,12 @@
const std = @import("std"); const std = @import("std");
const stdout = std.io.getStdOut().writer(); const stdout = std.io.getStdOut().writer();
const Base64 = @import("base64.zig"); const b64 = @import("base64.zig");
const usageText = const usageText =
\\Usage: baze64 {-d/-D} {-h/-H} input \\Usage: baze64 {-d/-D} input
\\With no arguments, encodes input into a base64 string
\\Args: \\Args:
\\ -d -D: decodes a base64 string \\ -d -D: decode instead of encode
\\ -h -H: print this help
; ;
@ -18,37 +16,22 @@ pub fn main() !void {
var args = try std.process.argsWithAllocator(allocator); var args = try std.process.argsWithAllocator(allocator);
defer args.deinit(); defer args.deinit();
var decodeSwitch = false; var b = b64.Base64.init;
var input: []const u8 = undefined;
// Skip arg[0] // Skip arg[0]
_ = args.skip(); _ = args.skip();
while (true) { var a = args.next();
const argVal = args.next(); if (a) |arg| {
var arg: []const u8 = undefined;
if (argVal) |a| {
arg = a;
} else {
break;
}
if (std.mem.eql(u8, arg, "-d") or std.mem.eql(u8, arg, "-D")) { if (std.mem.eql(u8, arg, "-d") or std.mem.eql(u8, arg, "-D")) {
decodeSwitch = true; a = args.next();
} else if (std.mem.eql(u8, arg, "-h") or std.mem.eql(u8, arg, "-H")) { const decoded = try b.decode(allocator, arg);
try stdout.print("{s}\n", .{usageText}); try stdout.print("{s}", .{decoded});
std.process.exit(0);
} else { } else {
input = arg; const encoded = try b.decode(allocator, arg);
break; try stdout.print("{s}", .{encoded});
} }
}
if (decodeSwitch) {
const decoded = try Base64.decode(allocator, input);
try stdout.print("{s}", .{decoded});
} else { } else {
const encoded = try Base64.encode(allocator, input); try stdout.print("{s}", .{usageText});
try stdout.print("{s}", .{encoded});
} }
} }