commit 84024fb05a3fd3df614711e4932ff97d763458fc Author: Philippe PITTOLI Date: Fri Jun 21 01:07:12 2024 +0200 Libhexa: first commit. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..880cd5d --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +zig-out +.zig-cache diff --git a/build.zig b/build.zig new file mode 100644 index 0000000..57ef894 --- /dev/null +++ b/build.zig @@ -0,0 +1,112 @@ +const std = @import("std"); + +const VERSION = "0.1.0"; + +// Although this function looks imperative, note that its job is to +// declaratively construct a build graph that will be executed by an external +// runner. +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 static_lib = b.addStaticLibrary(.{ + .name = "hexa", + // In this case the main source file is merely a path, however, in more + // complicated build scripts, this could be a generated file. + .root_source_file = b.path("src/bindings.zig"), + .target = target, + .optimize = optimize, + }); + + // This declares intent for the library to be installed into the standard + // location when the user invokes the "install" step (the default step when + // running `zig build`). + b.installArtifact(static_lib); + + const shared_lib = b.addSharedLibrary(.{ + .name = "hexa", + .root_source_file = .{ .cwd_relative = "src/bindings.zig" }, + .version = comptime (try std.SemanticVersion.parse(VERSION)), + .target = target, + .optimize = optimize, + }); + shared_lib.linkLibC(); + b.installArtifact(shared_lib); + + const exe = b.addExecutable(.{ + .name = "hexa", + .root_source_file = b.path("src/application.zig"), + .target = target, + .optimize = optimize, + }); + + // 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); + + // Creates a step for unit testing. This only builds the test executable + // but does not run it. + const lib_unit_tests = b.addTest(.{ + .root_source_file = b.path("src/root.zig"), + .target = target, + .optimize = optimize, + }); + + const run_lib_unit_tests = b.addRunArtifact(lib_unit_tests); + + const exe_unit_tests = b.addTest(.{ + .root_source_file = b.path("src/main.zig"), + .target = target, + .optimize = optimize, + }); + + const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests); + + // Similar to creating the run step earlier, this exposes a `test` step to + // the `zig build --help` menu, providing a way for the user to request + // running the unit tests. + const test_step = b.step("test", "Run unit tests"); + test_step.dependOn(&run_lib_unit_tests.step); + test_step.dependOn(&run_exe_unit_tests.step); + + const install_static_lib = b.addInstallArtifact(static_lib, .{}); + const static_lib_step = b.step("static", "Compile LibIPC as a static library."); + static_lib_step.dependOn(&install_static_lib.step); + + const install_shared_lib = b.addInstallArtifact(shared_lib, .{}); + // b.getInstallStep().dependOn(&install_shared_lib.step); + const shared_lib_step = b.step("shared", "Compile LibIPC as a shared library."); + shared_lib_step.dependOn(&install_shared_lib.step); +} diff --git a/build.zig.zon b/build.zig.zon new file mode 100644 index 0000000..cb5bccd --- /dev/null +++ b/build.zig.zon @@ -0,0 +1,72 @@ +.{ + // This is the default name used by packages depending on this one. For + // example, when a user runs `zig fetch --save `, 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 = "libhexa", + + // 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", + + // This field is optional. + // This is currently advisory only; Zig does not yet do anything + // with this value. + //.minimum_zig_version = "0.11.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 ` 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. + // .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", + }, +} diff --git a/c-example/example.c b/c-example/example.c new file mode 100644 index 0000000..474f54d --- /dev/null +++ b/c-example/example.c @@ -0,0 +1,25 @@ +#include "../libhexa.h" + +#include +#include + +#define BUFFER_SIZE 2000 + +int main(void) +{ + char buffer[BUFFER_SIZE]; + char inp[] = "hello this is me, how are you?"; + char title[] = "this is my title"; + + uint32_t bufsize = BUFFER_SIZE; + uint32_t *bufsizep = &bufsize; + + const int ret = hexdump(title, strlen(title), inp, strlen(inp), buffer, bufsizep); + if(ret != 0) { + printf("something bad happened\n"); + return 1; + } + + printf("%.*s", bufsize, buffer); + return 0; +} diff --git a/libhexa.h b/libhexa.h new file mode 100644 index 0000000..ca55846 --- /dev/null +++ b/libhexa.h @@ -0,0 +1,13 @@ +#ifndef __LIBHEXA__ +#define __LIBHEXA__ + +#include + +// Write an hexadecimal dump of input data in the output buffer. +// A title can be given to the dump, center-aligned on the first line. +// In case the title is empty, this line won't be written at all. +int32_t hexdump(char* title, uint32_t title_len + , char* input_buffer, uint32_t input_buffer_len + , char* output_buffer, unsigned int* output_buffer_len); + +#endif diff --git a/makefile b/makefile new file mode 100644 index 0000000..eaf953f --- /dev/null +++ b/makefile @@ -0,0 +1,10 @@ +all: build + +build: + zig build + +build-test: build + gcc -o example c-example/example.c -L ./zig-out/lib/ -lhexa + +run-test: build + LD_LIBRARY_PATH=./zig-out/lib/ ./example diff --git a/src/application.zig b/src/application.zig new file mode 100644 index 0000000..c15cd69 --- /dev/null +++ b/src/application.zig @@ -0,0 +1,42 @@ +const std = @import("std"); +const hexa = @import("./libhexa.zig"); +const stdin = std.io.getStdIn().reader(); +const stdout = std.io.getStdOut().writer(); + +const line_size = 16; +const nlines = 32; + +/// Print some bytes' hexdump on standard output. +pub fn print_hex(title: []const u8, buffer : []const u8) !void +{ + var hexbuf: [line_size*nlines*10]u8 = undefined; + var hexfbs = std.io.fixedBufferStream(&hexbuf); + const hexwriter = hexfbs.writer(); + try hexa.hexdump(hexwriter, title, buffer); + try stdout.print("{s}", .{hexfbs.getWritten()}); +} + +/// The program is simple: print an hexadecimal dump of input. +/// Provided parameter will be shown as a title. +pub fn main() !void +{ + var title : []const u8 = ""; + + const args = try std.process.argsAlloc(std.heap.page_allocator); + if (args.len > 2) { + try stdout.print("usage: {s} [title]\n", .{args[0]}); + try stdout.print("example: {s} 'debug hexdump of blah'\n", .{args[0]}); + return; + } + + if (args.len == 2) { title = args[1]; } + + var input_buffer: [line_size*nlines]u8 = undefined; + var n : u64 = undefined; + n = try stdin.read(&input_buffer); + while(n > 0) + { + try print_hex(title, input_buffer[0..n]); + n = try stdin.read(&input_buffer); + } +} diff --git a/src/bindings.zig b/src/bindings.zig new file mode 100644 index 0000000..07d2bff --- /dev/null +++ b/src/bindings.zig @@ -0,0 +1,37 @@ +const std = @import("std"); +const hexa = @import("./libhexa.zig"); + +const log = std.log.scoped(.libhexa); +const stdout = std.io.getStdOut().writer(); + +/// Write an hexdump of input data in an output buffer. +export fn hexdump(title: [*]const u8, title_len: u32, + input_buffer: [*]const u8, input_buffer_len: u32, + output_buffer: [*]u8, output_buffer_len: *u32) callconv(.C) i32 +{ + var hexfbs = std.io.fixedBufferStream(output_buffer[0..output_buffer_len.*]); + const writer_output = hexfbs.writer(); + hexa.hexdump(writer_output, title[0..title_len], input_buffer[0..input_buffer_len]) catch |err| { + switch(err) { + error.DiskQuota => log.warn("error.DiskQuota", .{}), + error.FileTooBig => log.warn("error.FileTooBig", .{}), + error.InputOutput => log.warn("error.InputOutput", .{}), + error.NoSpaceLeft => log.warn("error.NoSpaceLeft", .{}), + error.DeviceBusy => log.warn("error.DeviceBusy", .{}), + error.InvalidArgument => log.warn("error.InvalidArgument", .{}), + error.AccessDenied => log.warn("error.AccessDenied", .{}), + error.BrokenPipe => log.warn("error.BrokenPipe", .{}), + error.SystemResources => log.warn("error.SystemResources", .{}), + error.OperationAborted => log.warn("error.OperationAborted", .{}), + error.NotOpenForWriting => log.warn("error.NotOpenForWriting", .{}), + error.LockViolation => log.warn("error.LockViolation", .{}), + error.WouldBlock => log.warn("error.WouldBlock", .{}), + error.ConnectionResetByPeer => log.warn("error.ConnectionResetByPeer", .{}), + error.Unexpected => log.warn("error.Unexpected", .{}), + } + return -1; + }; + + output_buffer_len.* = @intCast(hexfbs.pos); + return 0; +} diff --git a/src/libhexa.zig b/src/libhexa.zig new file mode 100644 index 0000000..bd6137f --- /dev/null +++ b/src/libhexa.zig @@ -0,0 +1,72 @@ +const std = @import("std"); + +pub fn hexdump(stream: anytype, header: []const u8, buffer: []const u8) std.posix.WriteError!void +{ + // Print a header. + if (header.len > 0) { + var hdr: [64]u8 = undefined; + const offset: usize = (hdr.len / 2) - ((header.len / 2) - 1); + + @memset(hdr[0..hdr.len], ' '); + std.mem.copyForwards(u8, hdr[offset..hdr.len], header); + + try stream.writeAll(hdr[0..hdr.len]); + try stream.writeAll("\n"); + } + + var hexb: u32 = 0; + var ascii: [16]u8 = undefined; + // First line, first left side (simple number). + try stream.print(" {d:0>4}: ", .{hexb}); + + // Loop on all values in the buffer (i from 0 to buffer.len). + var i: u32 = 0; + while (i < buffer.len) : (i += 1) { + // Print actual hexadecimal value. + try stream.print("{X:0>2} ", .{buffer[i]}); + + // What to print (simple ascii text, right side). + if (buffer[i] >= ' ' and buffer[i] <= '~') { + ascii[(i % 16)] = buffer[i]; + } else { + ascii[(i % 16)] = '.'; + } + + // Next input is a multiple of 8 = extra space. + if ((i + 1) % 8 == 0) { + try stream.writeAll(" "); + } + + // No next input: print the right amount of spaces. + if ((i + 1) == buffer.len) { + // Each line is 16 bytes to print, each byte takes 3 characters. + var missing_spaces = 3 * (15 - (i % 16)); + // Missing an extra space if the current index % 16 is less than 7. + if ((i % 16) < 7) { + missing_spaces += 1; + } + while (missing_spaces > 0) : (missing_spaces -= 1) { + try stream.writeAll(" "); + } + } + + // Every 16 bytes: print ascii text and line return. + + // Case 1: it's been 16 bytes AND it's the last byte to print. + if ((i + 1) % 16 == 0 and (i + 1) == buffer.len) { + try stream.print("{s}\n", .{ascii[0..ascii.len]}); + } + // Case 2: it's been 16 bytes but it's not the end of the buffer. + else if ((i + 1) % 16 == 0 and (i + 1) != buffer.len) { + try stream.print("{s}\n", .{ascii[0..ascii.len]}); + hexb += 16; + try stream.print(" {d:0>4}: ", .{hexb}); + } + // Case 3: not the end of the 16 bytes row but it's the end of the buffer. + else if ((i + 1) % 16 != 0 and (i + 1) == buffer.len) { + try stream.print(" {s}\n", .{ascii[0..((i + 1) % 16)]}); + } + // Case 4: not the end of the 16 bytes row and not the end of the buffer. + // Do nothing. + } +}