Skip to content

Commit

Permalink
feat: convert to/from string (#7)
Browse files Browse the repository at this point in the history
* feat: convert to/from string

* feat: formatAlloc, rename toString to formatBuf

* chore: add constant for string length

* chore: add docs

* chore: update version

* chore: implement a format function

* chore: update examples to use format
  • Loading branch information
weskoerber authored Sep 12, 2024
1 parent 6a20b5c commit 668ffc6
Show file tree
Hide file tree
Showing 4 changed files with 135 additions and 3 deletions.
2 changes: 1 addition & 1 deletion build.zig.zon
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
.{
.name = "mac_address",
.version = "0.0.1",
.version = "0.1.0",
.minimum_zig_version = "0.12.0",
.dependencies = .{
.zigwin32 = .{
Expand Down
2 changes: 1 addition & 1 deletion examples/print_all.zig
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,6 @@ pub fn main() !void {

const addrs = try mac_address.getAll(allocator);
for (addrs) |addr| {
debug.print("{x:0>2}\n", .{addr.data});
debug.print("{}\n", .{addr});
}
}
2 changes: 1 addition & 1 deletion examples/print_first_no_loopback.zig
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,5 @@ pub fn main() void {
return;
};

debug.print("{x:0>2}\n", .{addr.data});
debug.print("{}\n", .{addr});
}
132 changes: 132 additions & 0 deletions lib/MacAddress.zig
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,135 @@ is_loopback: bool,

/// Individual bytes of the MAC address.
data: [6]u8,

pub const ParseError = error{
InvalidInput,
};

pub const FormatError = error{
NoSpaceLeft,
};

/// Parse a string into a `MacAddress`.
///
/// The `is_loopback` field is set to `false` but does not mean the parsed
/// value is not a loopback interface. This function does not make any
/// syscalls, so it knows nothing about the interface that's identified by the
/// value -- it's purely for display.
pub fn parse(buf: []const u8) !Self {
var data: [6]u8 = undefined;
var iter = std.mem.tokenizeScalar(u8, buf, ':');
var i: usize = 0;
while (iter.next()) |group| : (i += 1) {
if (i >= 6) return ParseError.InvalidInput;

data[i] = std.fmt.parseUnsigned(u8, group, 16) catch return ParseError.InvalidInput;
}

if (i < 6) return ParseError.InvalidInput;

return Self{
.is_loopback = false,
.data = data,
};
}

/// Format a `MacAddress` as a string, with each byte separated by colons. The
/// buffer must be at least 17 bytes long.
pub fn formatBuf(self: Self, buf: []u8) ![]u8 {
return std.fmt.bufPrint(buf, "{x:0>2}:{x:0>2}:{x:0>2}:{x:0>2}:{x:0>2}:{x:0>2}", .{
self.data[0],
self.data[1],
self.data[2],
self.data[3],
self.data[4],
self.data[5],
}) catch return FormatError.NoSpaceLeft;
}

/// Format a `MacAddress` as a string, with each byte separated by colons. The
/// caller owns the returned memory.
pub fn formatAlloc(self: Self, allocator: std.mem.Allocator) ![]u8 {
const buf = try allocator.alloc(u8, MAC_STR_LEN);
return self.formatBuf(buf);
}

/// Formats a MAC address to a string.
///
/// Note: The `fmt` and `options` arguments are required to work with
/// `std.fmt.format`, but are not used in `MacAddress`'s implementation.
pub fn format(self: Self, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
_ = options;
_ = fmt;

var buf: [MAC_STR_LEN]u8 = undefined;
const str = try self.formatBuf(&buf);

try writer.writeAll(str);
}

const std = @import("std");
const testing = std.testing;

const Self = @This();

// The max length of a MAC address, formatted as a string with each byte
// separated with a colon.
const MAC_STR_LEN = 17;

test "parse_success" {
const str = "00:11:22:33:44:55";
const expected = Self{ .is_loopback = false, .data = .{ 0x00, 0x11, 0x22, 0x33, 0x44, 0x55 } };
const addr = try Self.parse(str);

try testing.expect(std.meta.eql(expected, addr));
}

test "parse_error_too_short" {
const str = "00:11:22:33:44";
try testing.expectError(ParseError.InvalidInput, Self.parse(str));
}

test "parse_error_too_long" {
const str = "00:11:22:33:44:55:66";
try testing.expectError(ParseError.InvalidInput, Self.parse(str));
}

test "parse_error_empty" {
const str = "";
try testing.expectError(ParseError.InvalidInput, Self.parse(str));
}

test "parse_error_malformed" {
const str = "00:11:22:33:4:455";
try testing.expectError(ParseError.InvalidInput, Self.parse(str));
}

test "parse_error_malformed2" {
const str = ":0011:22:33::4455";
try testing.expectError(ParseError.InvalidInput, Self.parse(str));
}

test "format_buf_success" {
const addr = Self{ .is_loopback = false, .data = .{ 0x00, 0x11, 0x22, 0x33, 0x44, 0x55 } };
const expected = "00:11:22:33:44:55";
var buf: [MAC_STR_LEN]u8 = undefined;

try testing.expectEqualSlices(u8, expected, try addr.formatBuf(&buf));
}

test "format_buf_too_small" {
const addr = Self{ .is_loopback = false, .data = .{ 0x00, 0x11, 0x22, 0x33, 0x44, 0x55 } };
var buf: [16]u8 = undefined;

try testing.expectError(FormatError.NoSpaceLeft, addr.formatBuf(&buf));
}

test "format_alloc_success" {
const addr = Self{ .is_loopback = false, .data = .{ 0x00, 0x11, 0x22, 0x33, 0x44, 0x55 } };
const expected = "00:11:22:33:44:55";
const buf = try addr.formatAlloc(testing.allocator);
defer testing.allocator.free(buf);

try testing.expectEqualSlices(u8, expected, buf);
}

0 comments on commit 668ffc6

Please sign in to comment.