PCSC sample in Zig

I continue the list of PC/SC wrappers I started in 2010 with PC/SC sample in different languages. I am now presenting a new sample code in Zig.

pcsc-z

The wrapper is available at https://github.com/kofi-q/pcsc-z and is compatible for GNU/Linux, macOS and Windows.

The author is kofi-q.

The licence is MIT.

The API documentation can be found at https://kofi-q.github.io/pcsc-z/#pcsc.

Zig

From Wikipedia Zig article:

Zig is a system programming language designed to be a general-purpose improvement to the C programming language. It is free and open-source software, released under an MIT License.

Differences with C relate to control flow, function calls, library imports, variable declaration and Unicode support. The language makes no use of macros or preprocessor instructions. Features adopted from modern languages include the addition of compile time generic programming data types, allowing functions to work on a variety of data, along with a small set of new compiler directives to allow access to the information about those types using reflection. Zig requires manual memory management, but attempts to improve memory safety through option types and a unit testing framework. Features for low-level programming include packed structs, arbitrary-width integers and multiple pointer types.

Zig was designed by Andrew Kelly and first announced in 2016. Development is funded by the Zig Software Foundation (ZSF).

Installation

$ zig fetch --save=pcsc "git+https://github.com/kofi-q/pcsc-z.git"

I had to use an older version of the Zig-PC/SC wrapper because I used Zig 0.15.2 (stable version) instead of the development version 0.16.

$ zig fetch --save=pcsc "git+https://github.com/kofi-q/pcsc-z.git#zig-0.15"
info: resolved ref 'zig-0.15' to commit b347fd008c003ceb92041654a050c48d02468ce3
warning: overwriting existing dependency named 'pcsc'

Source code

To get started, you can use the zig init command to generate a minimal zig project.

$ zig init
info: created build.zig
info: created build.zig.zon
info: created src/main.zig
info: created src/root.zig
info: see `zig build --help` for a menu of options

You then need to update 2 files: src/main.zig and build.zig.

//! src/main.zig

const std = @import("std");
const pcsc = @import("pcsc");

pub fn main() !void {
    const client = try pcsc.Client.init(.SYSTEM);
    defer client.deinit() catch |err| std.debug.print(
        "Unable to release client: {t}",
        .{err},
    );

    // Connect to 1st reader
    var reader_names = try client.readerNames();
    if (reader_names.next()) |reader_name| {
        std.debug.print("Using: {s}\n", .{reader_name});

        // Connect to an inserted card:
        const card = try client.connect(reader_name, .SHARED, .ANY);
        defer card.disconnect(.LEAVE) catch |err| std.debug.print(
            "Unable to disconnect card: {t}\n",
            .{err},
        );

        std.debug.print("Card connected with protocol {f}\n", .{card.protocol});

        var buf_response: [pcsc.max_buffer_len]u8 = undefined;

        // SELECT command
        const select = [_]u8{ 0x00, 0xA4, 0x04, 0x00, 0x0A, 0xA0, 0x00,
            0x00, 0x00, 0x62, 0x03, 0x01, 0x0C, 0x06, 0x01 };

        std.debug.print("Transmitting APDU: {x}\n", .{select});

        var response = try card.transmit(&select, &buf_response);
        std.debug.print("Received response: {x}\n", .{response});

        // TEST command
        const command = [_]u8{ 0x00, 0x00, 0x00, 0x00 };

        std.debug.print("Transmitting APDU: {x}\n", .{command});

        response = try card.transmit(&command, &buf_response);
        std.debug.print("Received response: {x}\n", .{response});

        // Truncate the 2 last bytes: status word
        const text = response[0..response.len-2];
        std.debug.print("Text: {s}\n", .{text});
    } else {
        std.debug.print("No reader found\n", .{});
    }
}
//! build.zig

const std = @import("std");

pub fn build(b: *std.Build) !void {
    const target = b.standardTargetOptions(.{});
    const mode = b.standardOptimizeOption(.{});

    const pcsc_dep = b.dependency("pcsc", .{
        .optimize = mode,
        .target = target,
    });

    const pcsc_mod = pcsc_dep.module("pcsc");

    const exe = b.addExecutable(.{
        .name = "pcsc-demo",
        .root_module = b.createModule(.{
            .imports = &.{
                .{ .name = "pcsc", .module = pcsc_mod },
            },
            .optimize = mode,
            .root_source_file = b.path("src/main.zig"),
            .target = target,
        }),
    });

    const demo_run = b.addRunArtifact(exe);
    const demo_step = b.step("demo", "Run PCSC demo");
    demo_step.dependOn(&demo_run.step);
}

Output

$ zig build demo
Using: Gemalto USB SmartCard Reader
Card connected with protocol T=1
Transmitting APDU: 00a404000aa00000006203010c0601
Received response: 9000
Transmitting APDU: 00000000
Received response: 48656c6c6f20776f726c64219000
Text: Hello world!

Conclusion

Zig is a relatively new programming language. We can already find a PC/SC wrapper for it. That is great!

If you are working on a Free Software PC/SC wrapper that is not yet on my list please let me know.