Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ jobs:
- name: Setup Zig for OpenHarmony
uses: openharmony-zig/setup-zig-ohos@v0.1.0
id: setup-zig
with:
tag: "0.16.0"

- name: Build
run: |
Expand Down
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
.DS_Store
.zig-cache
zig-pkg/
zig-out
dist/

package.har
package/libs/
package/libs/
7 changes: 7 additions & 0 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,11 @@ pub fn build(b: *std.Build) !void {
if (result.x64) |x64| {
x64.root_module.addImport("napi", napi);
}

const dts = try napi_build.generateTypeDefinition(b, .{
.root_source_file = b.path("./src/lib.zig"),
.output = b.path("package/index.d.ts"),
.napi_module = napi,
});
b.getInstallStep().dependOn(&dts.step);
}
4 changes: 2 additions & 2 deletions build.zig.zon
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@

// Tracks the earliest Zig version that the package considers to be a
// supported use case.
.minimum_zig_version = "0.15.1",
.minimum_zig_version = "0.16.0",

.dependencies = .{ .@"zig-napi" = .{ .url = "https://github.com/openharmony-zig/zig-addon/archive/refs/tags/0.0.1-beta.2.tar.gz", .hash = "zig_napi-0.0.1-beta.2-H6OwawaxAgDMitwMH0m5ZJZoIpdc5LU2C34P_msTfEQo" } },
.dependencies = .{ .@"zig-napi" = .{ .url = "https://github.com/openharmony-zig/zig-napi/archive/refs/tags/0.1.0.tar.gz", .hash = "zig_napi-0.1.0-H6Owa7sDBgBLhd-ooFFJIqt3CAGATY4sIYHopmSYkRDP" } },

.paths = .{
"build.zig",
Expand Down
27 changes: 14 additions & 13 deletions package/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
/* auto-generated by zig-addon */
/* eslint-disable */

export interface PingOption {
count?: number;
interval_ms?: number;
timeout_ms?: number;
ip_version?: string;
count?: number
interval_ms?: number
timeout_ms?: number
ip_version?: string
}

export interface PingResult {
sequence: number;
rtt_ms: number;
success: boolean;
error_msg?: string;
ip_addr: string;
sequence: number
rtt_ms: number
success: boolean
error_msg?: string
ip_addr: string
}

export declare function ping(
host: string,
config?: PingOption
): Promise<PingResult[]>;

export declare function ping(host: string, config: PingOption | undefined | null): Promise<Array<PingResult>>
81 changes: 56 additions & 25 deletions src/domain.zig
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
const std = @import("std");
const napi = @import("napi");
const net = std.net;
const net = std.Io.net;
const posix = std.posix;
const Allocator = std.mem.Allocator;

const PosixAddress = extern union {
any: posix.sockaddr,
in: posix.sockaddr.in,
in6: posix.sockaddr.in6,
};

pub const IPResolverError = error{
InvalidIPFormat,
ResolutionFailed,
Expand All @@ -17,67 +23,92 @@ pub const IPFamily = enum {
};

pub fn isValidIP(ip_str: []const u8) bool {
if (net.Address.parseIp4(ip_str, 0)) |_| {
if (net.IpAddress.parseIp4(ip_str, 0)) |_| {
return true;
} else |_| {}

if (net.Address.parseIp6(ip_str, 0)) |_| {
if (net.IpAddress.parseIp6(ip_str, 0)) |_| {
return true;
} else |_| {}

return false;
}

fn formatAddress(allocator: Allocator, addr: std.net.Address) ![]const u8 {
fn formatSockaddr(allocator: Allocator, addr: *const posix.sockaddr) ![]const u8 {
var buf: [64]u8 = undefined;
var writer = std.Io.Writer.fixed(&buf);
const storage: *const PosixAddress = @ptrCast(@alignCast(addr));

switch (addr.any.family) {
switch (addr.family) {
posix.AF.INET => {
const bytes = std.mem.asBytes(&addr.in.sa.addr);
const bytes: [4]u8 = @bitCast(storage.in.addr);
_ = try writer.print("{}.{}.{}.{}", .{ bytes[0], bytes[1], bytes[2], bytes[3] });
},
posix.AF.INET6 => {
_ = try addr.in6.format(&writer);
const ip6 = net.Ip6Address.Unresolved{
.bytes = storage.in6.addr,
.interface_name = null,
};
_ = try ip6.format(&writer);
},
else => return error.UnsupportedFamily,
}

return try allocator.dupe(u8, writer.buffered());
}

fn isAddressFamily(addr: std.net.Address, family: IPFamily) bool {
fn isAddressFamily(addr: *const posix.sockaddr, family: IPFamily) bool {
return switch (family) {
.ipv4 => addr.any.family == posix.AF.INET,
.ipv6 => addr.any.family == posix.AF.INET6,
.ipv4 => addr.family == posix.AF.INET,
.ipv6 => addr.family == posix.AF.INET6,
.auto => true,
};
}

/// Resolve hostname to IP address string
pub fn resolveHostname(allocator: Allocator, hostname: []const u8, prefer: IPFamily) ![]const u8 {
// Use std.net to resolve hostname
const address_list = std.net.getAddressList(allocator, hostname, 0) catch {
return napi.Error.fromReason("Failed to resolve hostname");
};
defer address_list.deinit();
const hostname_z = try allocator.dupeZ(u8, hostname);
defer allocator.free(hostname_z);

if (address_list.addrs.len == 0) {
return napi.Error.fromReason("Resolve hostname's result is empty");
}
const family: i32 = switch (prefer) {
.ipv4 => @intCast(posix.AF.INET),
.ipv6 => @intCast(posix.AF.INET6),
.auto => @intCast(posix.AF.UNSPEC),
};
const hints = std.c.addrinfo{
.flags = .{},
.family = family,
.socktype = @intCast(posix.SOCK.DGRAM),
.protocol = 0,
.addrlen = 0,
.addr = null,
.canonname = null,
.next = null,
};

// If auto mode, return the first address
if (prefer == .auto) {
return try formatAddress(allocator, address_list.addrs[0]);
var result: ?*std.c.addrinfo = null;
if (@intFromEnum(std.c.getaddrinfo(hostname_z, null, &hints, &result)) != 0) {
return napi.Error.fromReason("Failed to resolve hostname");
}

for (address_list.addrs) |addr| {
defer if (result) |res| std.c.freeaddrinfo(res);

var first_addr: ?*posix.sockaddr = null;
var item = result;
while (item) |info| : (item = info.next) {
const addr = info.addr orelse continue;
if (first_addr == null) {
first_addr = addr;
}
if (isAddressFamily(addr, prefer)) {
return try formatAddress(allocator, addr);
return try formatSockaddr(allocator, addr);
}
}

return try formatAddress(allocator, address_list.addrs[0]);
if (first_addr) |addr| {
return try formatSockaddr(allocator, addr);
} else {
return napi.Error.fromReason("Resolve hostname's result is empty");
}
}

/// Main function: get IP address string
Expand Down
Loading
Loading