Initial commit
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
zig-out/
|
||||
.zig-cache/
|
||||
@@ -0,0 +1,57 @@
|
||||
const std = @import("std");
|
||||
|
||||
pub fn build(b: *std.Build) void {
|
||||
// Standard target options allows the person running `zig build` to choose
|
||||
// what target to build for. Here we do not override the defaults, which
|
||||
// means any target is allowed, and the default is native. Other options
|
||||
// for restricting supported target set are available.
|
||||
const target = b.standardTargetOptions(.{});
|
||||
|
||||
// Standard optimization options allow the person running `zig build` to select
|
||||
// between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. Here we do not
|
||||
// set a preferred release mode, allowing the user to decide how to optimize.
|
||||
const optimize = b.standardOptimizeOption(.{});
|
||||
|
||||
const exe_mod = b.createModule(.{
|
||||
.root_source_file = if (target.result.cpu.arch == .wasm32) b.path("src/wasm.zig") else b.path("src/main.zig"),
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
});
|
||||
|
||||
exe_mod.export_symbol_names = &.{ "malloc", "free", "decodeStringWasm" };
|
||||
|
||||
const exe = b.addExecutable(.{
|
||||
.name = "zig_imb",
|
||||
.root_module = exe_mod,
|
||||
});
|
||||
|
||||
exe.entry = .disabled;
|
||||
|
||||
// This declares intent for the executable to be installed into the
|
||||
// standard location when the user invokes the "install" step (the default
|
||||
// step when running `zig build`).
|
||||
b.installArtifact(exe);
|
||||
|
||||
// This *creates* a Run step in the build graph, to be executed when another
|
||||
// step is evaluated that depends on it. The next line below will establish
|
||||
// such a dependency.
|
||||
const run_cmd = b.addRunArtifact(exe);
|
||||
|
||||
// By making the run step depend on the install step, it will be run from the
|
||||
// installation directory rather than directly from within the cache directory.
|
||||
// This is not necessary, however, if the application depends on other installed
|
||||
// files, this ensures they will be present and in the expected location.
|
||||
run_cmd.step.dependOn(b.getInstallStep());
|
||||
|
||||
// This allows the user to pass arguments to the application in the build
|
||||
// command itself, like this: `zig build run -- arg1 arg2 etc`
|
||||
if (b.args) |args| {
|
||||
run_cmd.addArgs(args);
|
||||
}
|
||||
|
||||
// This creates a build step. It will be visible in the `zig build --help` menu,
|
||||
// and can be selected like this: `zig build run`
|
||||
// This will evaluate the `run` step rather than the default, which is "install".
|
||||
const run_step = b.step("run", "Run the app");
|
||||
run_step.dependOn(&run_cmd.step);
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
.{
|
||||
// This is the default name used by packages depending on this one. For
|
||||
// example, when a user runs `zig fetch --save <url>`, this field is used
|
||||
// as the key in the `dependencies` table. Although the user can choose a
|
||||
// different name, most users will stick with this provided value.
|
||||
//
|
||||
// It is redundant to include "zig" in this name because it is already
|
||||
// within the Zig package namespace.
|
||||
.name = .zig_imb,
|
||||
|
||||
// This is a [Semantic Version](https://semver.org/).
|
||||
// In a future version of Zig it will be used for package deduplication.
|
||||
.version = "0.0.0",
|
||||
|
||||
// Together with name, this represents a globally unique package
|
||||
// identifier. This field is generated by the Zig toolchain when the
|
||||
// package is first created, and then *never changes*. This allows
|
||||
// unambiguous detection of one package being an updated version of
|
||||
// another.
|
||||
//
|
||||
// When forking a Zig project, this id should be regenerated (delete the
|
||||
// field and run `zig build`) if the upstream project is still maintained.
|
||||
// Otherwise, the fork is *hostile*, attempting to take control over the
|
||||
// original project's identity. Thus it is recommended to leave the comment
|
||||
// on the following line intact, so that it shows up in code reviews that
|
||||
// modify the field.
|
||||
.fingerprint = 0x761a5f11ac79281a, // Changing this has security and trust implications.
|
||||
|
||||
// Tracks the earliest Zig version that the package considers to be a
|
||||
// supported use case.
|
||||
.minimum_zig_version = "0.14.0",
|
||||
|
||||
// This field is optional.
|
||||
// Each dependency must either provide a `url` and `hash`, or a `path`.
|
||||
// `zig build --fetch` can be used to fetch all dependencies of a package, recursively.
|
||||
// Once all dependencies are fetched, `zig build` no longer requires
|
||||
// internet connectivity.
|
||||
.dependencies = .{
|
||||
// See `zig fetch --save <url>` for a command-line interface for adding dependencies.
|
||||
//.example = .{
|
||||
// // When updating this field to a new URL, be sure to delete the corresponding
|
||||
// // `hash`, otherwise you are communicating that you expect to find the old hash at
|
||||
// // the new URL. If the contents of a URL change this will result in a hash mismatch
|
||||
// // which will prevent zig from using it.
|
||||
// .url = "https://example.com/foo.tar.gz",
|
||||
//
|
||||
// // This is computed from the file contents of the directory of files that is
|
||||
// // obtained after fetching `url` and applying the inclusion rules given by
|
||||
// // `paths`.
|
||||
// //
|
||||
// // This field is the source of truth; packages do not come from a `url`; they
|
||||
// // come from a `hash`. `url` is just one of many possible mirrors for how to
|
||||
// // obtain a package matching this `hash`.
|
||||
// //
|
||||
// // Uses the [multihash](https://multiformats.io/multihash/) format.
|
||||
// .hash = "...",
|
||||
//
|
||||
// // When this is provided, the package is found in a directory relative to the
|
||||
// // build root. In this case the package's hash is irrelevant and therefore not
|
||||
// // computed. This field and `url` are mutually exclusive.
|
||||
// .path = "foo",
|
||||
//
|
||||
// // When this is set to `true`, a package is declared to be lazily
|
||||
// // fetched. This makes the dependency only get fetched if it is
|
||||
// // actually used.
|
||||
// .lazy = false,
|
||||
//},
|
||||
},
|
||||
|
||||
// Specifies the set of files and directories that are included in this package.
|
||||
// Only files and directories listed here are included in the `hash` that
|
||||
// is computed for this package. Only files listed here will remain on disk
|
||||
// when using the zig package manager. As a rule of thumb, one should list
|
||||
// files required for compilation plus any license(s).
|
||||
// Paths are relative to the build root. Use the empty string (`""`) to refer to
|
||||
// the build root itself.
|
||||
// A directory listed here means that all files within, recursively, are included.
|
||||
.paths = .{
|
||||
"build.zig",
|
||||
"build.zig.zon",
|
||||
"src",
|
||||
// For example...
|
||||
//"LICENSE",
|
||||
//"README.md",
|
||||
},
|
||||
}
|
||||
+287
@@ -0,0 +1,287 @@
|
||||
const std = @import("std");
|
||||
|
||||
const bar_positions = [_][4]u4{
|
||||
.{ 7, 2, 4, 3 },
|
||||
.{ 1, 10, 0, 0 },
|
||||
.{ 9, 12, 2, 8 },
|
||||
.{ 5, 5, 6, 11 },
|
||||
.{ 8, 9, 3, 1 },
|
||||
.{ 0, 1, 5, 12 },
|
||||
.{ 2, 5, 1, 8 },
|
||||
.{ 4, 4, 9, 11 },
|
||||
.{ 6, 3, 8, 10 },
|
||||
.{ 3, 9, 7, 6 },
|
||||
.{ 5, 11, 1, 4 },
|
||||
.{ 8, 5, 2, 12 },
|
||||
.{ 9, 10, 0, 2 },
|
||||
.{ 7, 1, 6, 7 },
|
||||
.{ 3, 6, 4, 9 },
|
||||
.{ 0, 3, 8, 6 },
|
||||
.{ 6, 4, 2, 7 },
|
||||
.{ 1, 1, 9, 9 },
|
||||
.{ 7, 10, 5, 2 },
|
||||
.{ 4, 0, 3, 8 },
|
||||
.{ 6, 2, 0, 4 },
|
||||
.{ 8, 11, 1, 0 },
|
||||
.{ 9, 8, 3, 12 },
|
||||
.{ 2, 6, 7, 7 },
|
||||
.{ 5, 1, 4, 10 },
|
||||
.{ 1, 12, 6, 9 },
|
||||
.{ 7, 3, 8, 0 },
|
||||
.{ 5, 8, 9, 7 },
|
||||
.{ 4, 6, 2, 10 },
|
||||
.{ 3, 4, 0, 5 },
|
||||
.{ 8, 4, 5, 7 },
|
||||
.{ 7, 11, 1, 9 },
|
||||
.{ 6, 0, 9, 6 },
|
||||
.{ 0, 6, 4, 8 },
|
||||
.{ 2, 1, 3, 2 },
|
||||
.{ 5, 9, 8, 12 },
|
||||
.{ 4, 11, 6, 1 },
|
||||
.{ 9, 5, 7, 4 },
|
||||
.{ 3, 3, 1, 2 },
|
||||
.{ 0, 7, 2, 0 },
|
||||
.{ 1, 3, 4, 1 },
|
||||
.{ 6, 10, 3, 5 },
|
||||
.{ 8, 7, 9, 4 },
|
||||
.{ 2, 11, 5, 6 },
|
||||
.{ 0, 8, 7, 12 },
|
||||
.{ 4, 2, 8, 1 },
|
||||
.{ 5, 10, 3, 0 },
|
||||
.{ 9, 3, 0, 9 },
|
||||
.{ 6, 5, 2, 4 },
|
||||
.{ 7, 8, 1, 7 },
|
||||
.{ 5, 0, 4, 5 },
|
||||
.{ 2, 3, 0, 10 },
|
||||
.{ 6, 12, 9, 2 },
|
||||
.{ 3, 11, 1, 6 },
|
||||
.{ 8, 8, 7, 9 },
|
||||
.{ 5, 4, 0, 11 },
|
||||
.{ 1, 5, 2, 2 },
|
||||
.{ 9, 1, 4, 12 },
|
||||
.{ 8, 3, 6, 6 },
|
||||
.{ 7, 0, 3, 7 },
|
||||
.{ 4, 7, 7, 5 },
|
||||
.{ 0, 12, 1, 11 },
|
||||
.{ 2, 9, 9, 0 },
|
||||
.{ 6, 8, 5, 3 },
|
||||
.{ 3, 10, 8, 2 },
|
||||
};
|
||||
|
||||
fn generateCharacterTable(n: u8, comptime len: usize) [len]u13 {
|
||||
@setEvalBranchQuota(150000);
|
||||
|
||||
var table: [len]u13 = undefined;
|
||||
|
||||
var lower_index = 0;
|
||||
var upper_index = len - 1;
|
||||
|
||||
for (0..0x2000) |i| {
|
||||
const b = bitsSet(u13, i);
|
||||
if (b != n) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const reverse = @bitReverse(@as(u13, i));
|
||||
|
||||
if (reverse < i) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (reverse == i) {
|
||||
table[upper_index] = i;
|
||||
upper_index -= 1;
|
||||
} else {
|
||||
table[lower_index] = i;
|
||||
lower_index += 1;
|
||||
table[lower_index] = reverse;
|
||||
lower_index += 1;
|
||||
}
|
||||
}
|
||||
|
||||
return table;
|
||||
}
|
||||
|
||||
const character_table_5 = generateCharacterTable(5, 1287);
|
||||
const character_table_2 = generateCharacterTable(2, 77);
|
||||
|
||||
const BarType = enum {
|
||||
descending,
|
||||
ascending,
|
||||
tracking,
|
||||
full,
|
||||
};
|
||||
|
||||
pub const BarcodeResult = struct {
|
||||
tracking_code: [20]u8,
|
||||
routing_code: [11]u8,
|
||||
};
|
||||
|
||||
pub const Error = error{
|
||||
InvalidCharacter,
|
||||
DecodingError,
|
||||
InternalError,
|
||||
InvalidChecksum,
|
||||
};
|
||||
|
||||
fn bitsSet(comptime T: type, value: T) u8 {
|
||||
var count: u8 = 0;
|
||||
|
||||
for (0..@typeInfo(T).int.bits) |i| {
|
||||
count += @intCast((value >> @intCast(i)) & 1);
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
fn findCodeword(character: u13) u13 {
|
||||
const b = bitsSet(u13, character);
|
||||
if (b == 2) {
|
||||
for (character_table_2, 0..) |item, codeword| {
|
||||
if (character == item) {
|
||||
return @intCast(codeword + 1287);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (character_table_5, 0..) |item, codeword| {
|
||||
if (character == item) {
|
||||
return @intCast(codeword);
|
||||
}
|
||||
}
|
||||
}
|
||||
unreachable;
|
||||
}
|
||||
|
||||
fn generateChecksum(data: [13]u8) u16 {
|
||||
const generator_polynomial: u16 = 0x0F35;
|
||||
var checksum: u16 = 0x07ff;
|
||||
|
||||
var byte: u16 = data[0] << 5;
|
||||
|
||||
for (2..8) |_| {
|
||||
if (((checksum ^ byte) & 0x400) != 0) {
|
||||
checksum = (checksum << 1) ^ generator_polynomial;
|
||||
} else {
|
||||
checksum <<= 1;
|
||||
}
|
||||
|
||||
checksum &= 0x7FF;
|
||||
byte <<= 1;
|
||||
}
|
||||
|
||||
for (data[1..]) |b| {
|
||||
byte = @as(u16, b) << 3;
|
||||
|
||||
for (0..8) |_| {
|
||||
if (((checksum ^ byte) & 0x400) != 0) {
|
||||
checksum = (checksum << 1) ^ generator_polynomial;
|
||||
} else {
|
||||
checksum <<= 1;
|
||||
}
|
||||
|
||||
checksum &= 0x7FF;
|
||||
byte <<= 1;
|
||||
}
|
||||
}
|
||||
|
||||
return checksum;
|
||||
}
|
||||
|
||||
fn decode(bars: [65]BarType) Error!BarcodeResult {
|
||||
var characters = [_]u13{0} ** 10;
|
||||
|
||||
for (bars, 0..) |bar, i| {
|
||||
const positions = bar_positions[i];
|
||||
if (bar == .descending or bar == .full) {
|
||||
characters[positions[0]] |= (@as(u13, 1) << positions[1]);
|
||||
}
|
||||
if (bar == .ascending or bar == .full) {
|
||||
characters[positions[2]] |= (@as(u13, 1) << positions[3]);
|
||||
}
|
||||
}
|
||||
|
||||
var checksum: u11 = 0;
|
||||
|
||||
for (&characters, 0..) |*character, i| {
|
||||
switch (bitsSet(u13, character.*)) {
|
||||
8, 11 => {
|
||||
character.* ^= 0b1111111111111;
|
||||
checksum |= (@as(u11, 1) << @intCast(i));
|
||||
checksum |= (@as(u11, 1) << @intCast(i));
|
||||
},
|
||||
2, 5 => {},
|
||||
else => return error.DecodingError,
|
||||
}
|
||||
|
||||
character.* = findCodeword(character.*);
|
||||
}
|
||||
|
||||
characters[9] /= 2;
|
||||
if (characters[0] >= 659) {
|
||||
characters[0] -= 659;
|
||||
checksum |= 0b10000000000;
|
||||
}
|
||||
|
||||
var bindata: u104 = 0;
|
||||
|
||||
bindata = @as(u104, characters[0]) * 1365 + characters[1];
|
||||
bindata = bindata * 1365 + characters[2];
|
||||
bindata = bindata * 1365 + characters[3];
|
||||
bindata = bindata * 1365 + characters[4];
|
||||
bindata = bindata * 1365 + characters[5];
|
||||
bindata = bindata * 1365 + characters[6];
|
||||
bindata = bindata * 1365 + characters[7];
|
||||
bindata = bindata * 1365 + characters[8];
|
||||
bindata = bindata * 636 + characters[9];
|
||||
|
||||
if (generateChecksum(@bitCast(@byteSwap(bindata))) != checksum) { // lol, only works on little-endian systems #WONTFIX
|
||||
return error.InvalidChecksum;
|
||||
}
|
||||
|
||||
var tracking_code: [20]u8 = undefined;
|
||||
var routing_code = [_]u8{0} ** 11;
|
||||
|
||||
var i: u8 = 19;
|
||||
while (i >= 2) : (i -= 1) {
|
||||
tracking_code[i] = std.fmt.digitToChar(@intCast(bindata % 10), .lower);
|
||||
bindata /= 10;
|
||||
}
|
||||
tracking_code[i] = std.fmt.digitToChar(@intCast(bindata % 5), .lower);
|
||||
bindata /= 5;
|
||||
i -= 1;
|
||||
tracking_code[i] = std.fmt.digitToChar(@intCast(bindata % 10), .lower);
|
||||
bindata /= 10;
|
||||
|
||||
switch (bindata) {
|
||||
1...100000 => {
|
||||
_ = std.fmt.bufPrint(&routing_code, "{d:05}", .{bindata - 1}) catch return error.InternalError;
|
||||
},
|
||||
100001...1000199999 => {
|
||||
_ = std.fmt.bufPrint(&routing_code, "{d:09}", .{bindata - 100000 - 1}) catch return error.InternalError;
|
||||
},
|
||||
else => {
|
||||
_ = std.fmt.bufPrint(&routing_code, "{d:011}", .{bindata - 1000000000 - 100000 - 1}) catch return error.InternalError;
|
||||
},
|
||||
}
|
||||
|
||||
return .{
|
||||
.tracking_code = tracking_code,
|
||||
.routing_code = routing_code,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn decodeString(str: *const [65:0]u8) Error!BarcodeResult {
|
||||
var bars: [65]BarType = undefined;
|
||||
for (&bars, 0..) |*pt, i| {
|
||||
pt.* = switch (str[i]) {
|
||||
'A', 'a' => .ascending,
|
||||
'D', 'd' => .descending,
|
||||
'F', 'f' => .full,
|
||||
'T', 't' => .tracking,
|
||||
else => return error.InvalidCharacter,
|
||||
};
|
||||
}
|
||||
const thing = try decode(bars);
|
||||
|
||||
return thing;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
const std = @import("std");
|
||||
const imb = @import("./imb.zig");
|
||||
|
||||
pub fn main() !void {
|
||||
const stuff = try imb.decodeString("AADTFFDFTDADTAADAATFDTDDAAADDTDTTDAFADADDDTFFFDDTTTADFAAADFTDAADA");
|
||||
std.log.info("{s}", .{stuff.tracking_code});
|
||||
std.log.info("{s}", .{stuff.routing_code});
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
const std = @import("std");
|
||||
const imb = @import("./imb.zig");
|
||||
|
||||
const allocator = std.heap.wasm_allocator;
|
||||
|
||||
export fn decodeStringWasm(str: *const [65:0]u8) usize {
|
||||
if (imb.decodeString(str)) |v| {
|
||||
const ptr = allocator.create([31]u8) catch unreachable;
|
||||
const result = v.tracking_code ++ v.routing_code;
|
||||
@memcpy(ptr, &result);
|
||||
return @intFromPtr(ptr);
|
||||
} else |e| switch (e) {
|
||||
error.InvalidCharacter => return 0,
|
||||
error.DecodingError => return 1,
|
||||
error.InternalError => return 2,
|
||||
error.InvalidChecksum => return 3,
|
||||
}
|
||||
}
|
||||
|
||||
export fn malloc(len: u8) [*]u8 {
|
||||
return (allocator.alloc(u8, len) catch unreachable).ptr;
|
||||
}
|
||||
|
||||
export fn free(ptr: [*]u8, len: u8) void {
|
||||
allocator.free(ptr[0..len]);
|
||||
}
|
||||
Executable
BIN
Binary file not shown.
+119
@@ -0,0 +1,119 @@
|
||||
<html>
|
||||
<head> </head>
|
||||
<body>
|
||||
<form id="form">
|
||||
<input
|
||||
type="text"
|
||||
name="thing"
|
||||
value="TATAFFADTTFDDFTFAFDFTFFATTFDDFTDFTDDTADAAFTTFAAADFFFDTDTTDFATDDDT"
|
||||
size="100"
|
||||
/>
|
||||
<button type="submit" disabled>Decode</button>
|
||||
</form>
|
||||
|
||||
<pre id="result"></pre>
|
||||
|
||||
<script>
|
||||
WebAssembly.instantiateStreaming(fetch("imb.wasm")).then(
|
||||
({ instance }) => {
|
||||
const { decodeStringWasm, malloc, free, memory } = instance.exports;
|
||||
|
||||
function allocString(str) {
|
||||
str += "\0"; // add null terminator
|
||||
|
||||
const encoder = new TextEncoder();
|
||||
const encoded = encoder.encode(str);
|
||||
|
||||
const ptr = instance.exports.malloc(encoded.length);
|
||||
|
||||
const mem = new Uint8Array(instance.exports.memory.buffer);
|
||||
|
||||
for (let i = 0; i < encoded.length; i++) {
|
||||
mem[ptr + i] = encoded[i];
|
||||
}
|
||||
|
||||
return [ptr, encoded.length];
|
||||
}
|
||||
|
||||
function decodeBarcode(str) {
|
||||
if (!/^[adft]{65}$/i.test(str)) throw new Error("invalid string");
|
||||
|
||||
const [string, stringLen] = allocString(str);
|
||||
|
||||
const result = decodeStringWasm(string);
|
||||
|
||||
free(string, stringLen);
|
||||
|
||||
if (result == 0) throw new Error("invalid character");
|
||||
if (result == 1) throw new Error("decoding error");
|
||||
if (result == 2) throw new Error("unknown error");
|
||||
if (result == 3) throw new Error("invalid checksum");
|
||||
|
||||
const resultString = new TextDecoder().decode(
|
||||
new Uint8Array(memory.buffer).subarray(result, result + 31)
|
||||
);
|
||||
|
||||
free(result, 31);
|
||||
|
||||
return parseDecoded(resultString);
|
||||
}
|
||||
|
||||
function parseDecoded(decoded) {
|
||||
const tracking_code = decoded.substring(0, 20);
|
||||
const routing_code = decoded.substring(20).replace(/[^0-9]/g, "");
|
||||
|
||||
let zip, mailerId, serialNumber;
|
||||
|
||||
if (routing_code.length == 11) {
|
||||
zip = `${routing_code.substring(0, 5)}-${routing_code.substring(
|
||||
5,
|
||||
9
|
||||
)}(${routing_code.substring(9, 11)})`;
|
||||
} else if (routing_code.length == 9) {
|
||||
zip = `${routing_code.substring(0, 5)}-${routing_code.substring(
|
||||
5,
|
||||
9
|
||||
)}`;
|
||||
} else {
|
||||
zip = routing_code;
|
||||
}
|
||||
|
||||
if (tracking_code[5] == 9) {
|
||||
mailerId = tracking_code.substring(5, 5 + 9);
|
||||
serialNumber = tracking_code.substring(14, 14 + 6);
|
||||
} else {
|
||||
mailerId = tracking_code.substring(5, 5 + 6);
|
||||
serialNumber = tracking_code.substring(11, 11 + 9);
|
||||
}
|
||||
|
||||
return {
|
||||
tracking_code: {
|
||||
barcodeId: tracking_code.substring(0, 2),
|
||||
serviceType: tracking_code.substring(2, 5),
|
||||
mailerId,
|
||||
serialNumber,
|
||||
},
|
||||
zip,
|
||||
};
|
||||
}
|
||||
|
||||
document.getElementById("form").addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
try {
|
||||
const result = decodeBarcode(e.target.thing.value);
|
||||
document.getElementById("result").innerText = JSON.stringify(
|
||||
result,
|
||||
null,
|
||||
2
|
||||
);
|
||||
} catch (e) {
|
||||
document.getElementById("result").innerText = e;
|
||||
}
|
||||
});
|
||||
|
||||
document.querySelector("button[type=submit]").disabled = false;
|
||||
}
|
||||
);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user