From 642a51dcf944825cbae7d31c6e71370a951334fd Mon Sep 17 00:00:00 2001 From: Davide Date: Sun, 26 Jul 2026 00:32:46 +0200 Subject: [PATCH 1/8] Move WireGuard internals --- zig/src/testing.zig | 11 +++++++---- zig/src/wireguard/connection.zig | 4 ++-- zig/src/wireguard/exports.zig | 2 +- zig/src/wireguard/{ => internal}/adapter.zig | 4 ++-- zig/src/wireguard/{ => internal}/backend.zig | 6 +++--- zig/src/wireguard/{ => internal}/resolver.zig | 4 ++-- zig/src/wireguard/{ => internal}/tunnel_info.zig | 2 +- zig/src/wireguard/{ => internal}/uapi.zig | 2 +- zig/tests/wireguard/connection.zig | 9 +++++---- 9 files changed, 24 insertions(+), 20 deletions(-) rename zig/src/wireguard/{ => internal}/adapter.zig (99%) rename zig/src/wireguard/{ => internal}/backend.zig (97%) rename zig/src/wireguard/{ => internal}/resolver.zig (99%) rename zig/src/wireguard/{ => internal}/tunnel_info.zig (99%) rename zig/src/wireguard/{ => internal}/uapi.zig (98%) diff --git a/zig/src/testing.zig b/zig/src/testing.zig index bbf510d5..af9e6ba4 100644 --- a/zig/src/testing.zig +++ b/zig/src/testing.zig @@ -55,11 +55,14 @@ pub const openvpn_internal = if (openvpn_enabled) struct { } else struct {}; pub const partout = @import("partout.zig"); pub const wireguard_enabled = build_options.wireguard; -pub const wireguard_adapter = if (wireguard_enabled) @import("wireguard/adapter.zig") else struct {}; -pub const wireguard_backend = if (wireguard_enabled) @import("wireguard/backend.zig") else struct {}; pub const wireguard_connection = if (wireguard_enabled) @import("wireguard/connection.zig") else struct {}; pub const wireguard_exports = if (wireguard_enabled) @import("wireguard/exports.zig") else struct {}; pub const wireguard_parser = if (wireguard_enabled) @import("wireguard/parser.zig") else struct {}; pub const wireguard_serializer = if (wireguard_enabled) @import("wireguard/serializer.zig") else struct {}; -pub const wireguard_tunnel_info = if (wireguard_enabled) @import("wireguard/tunnel_info.zig") else struct {}; -pub const wireguard_uapi = if (wireguard_enabled) @import("wireguard/uapi.zig") else struct {}; +pub const wireguard_internal = if (wireguard_enabled) struct { + pub const adapter = @import("wireguard/internal/adapter.zig"); + pub const backend = @import("wireguard/internal/backend.zig"); + pub const resolver = @import("wireguard/internal/resolver.zig"); + pub const tunnel_info = @import("wireguard/internal/tunnel_info.zig"); + pub const uapi = @import("wireguard/internal/uapi.zig"); +} else struct {}; diff --git a/zig/src/wireguard/connection.zig b/zig/src/wireguard/connection.zig index 120ebf09..0546628e 100644 --- a/zig/src/wireguard/connection.zig +++ b/zig/src/wireguard/connection.zig @@ -9,8 +9,8 @@ const net = @import("../net/exports.zig"); const api = core.api; const log = core.logging; -const adapter_mod = @import("adapter.zig"); -const impl = @import("backend.zig"); +const adapter_mod = @import("internal/adapter.zig"); +const impl = @import("internal/backend.zig"); const WireGuardAdapter = adapter_mod.WireGuardAdapter; diff --git a/zig/src/wireguard/exports.zig b/zig/src/wireguard/exports.zig index 73623184..ca3786b6 100644 --- a/zig/src/wireguard/exports.zig +++ b/zig/src/wireguard/exports.zig @@ -12,7 +12,7 @@ const std = @import("std"); const build_options = @import("build_options"); -const backend = @import("backend.zig"); +const backend = @import("internal/backend.zig"); const connection = @import("connection.zig"); const core = @import("../core/exports.zig"); const net = @import("../net/exports.zig"); diff --git a/zig/src/wireguard/adapter.zig b/zig/src/wireguard/internal/adapter.zig similarity index 99% rename from zig/src/wireguard/adapter.zig rename to zig/src/wireguard/internal/adapter.zig index 34471a19..f25a7572 100644 --- a/zig/src/wireguard/adapter.zig +++ b/zig/src/wireguard/internal/adapter.zig @@ -5,8 +5,8 @@ const std = @import("std"); const builtin = @import("builtin"); -const core = @import("../core/exports.zig"); -const net = @import("../net/exports.zig"); +const core = @import("../../core/exports.zig"); +const net = @import("../../net/exports.zig"); const api = core.api; const log = core.logging; diff --git a/zig/src/wireguard/backend.zig b/zig/src/wireguard/internal/backend.zig similarity index 97% rename from zig/src/wireguard/backend.zig rename to zig/src/wireguard/internal/backend.zig index 494b1391..0d4b14cc 100644 --- a/zig/src/wireguard/backend.zig +++ b/zig/src/wireguard/internal/backend.zig @@ -4,9 +4,9 @@ const std = @import("std"); -const c_common = @import("../c/exports.zig").common; -const core = @import("../core/exports.zig"); -const net = @import("../net/exports.zig"); +const c_common = @import("../../c/exports.zig").common; +const core = @import("../../core/exports.zig"); +const net = @import("../../net/exports.zig"); const log = core.logging; const util = core.util; diff --git a/zig/src/wireguard/resolver.zig b/zig/src/wireguard/internal/resolver.zig similarity index 99% rename from zig/src/wireguard/resolver.zig rename to zig/src/wireguard/internal/resolver.zig index 8b904400..a5b75423 100644 --- a/zig/src/wireguard/resolver.zig +++ b/zig/src/wireguard/internal/resolver.zig @@ -4,8 +4,8 @@ const std = @import("std"); -const core = @import("../core/exports.zig"); -const net = @import("../net/exports.zig"); +const core = @import("../../core/exports.zig"); +const net = @import("../../net/exports.zig"); const api = core.api; const log = core.logging; diff --git a/zig/src/wireguard/tunnel_info.zig b/zig/src/wireguard/internal/tunnel_info.zig similarity index 99% rename from zig/src/wireguard/tunnel_info.zig rename to zig/src/wireguard/internal/tunnel_info.zig index 3ace8288..98a3e98a 100644 --- a/zig/src/wireguard/tunnel_info.zig +++ b/zig/src/wireguard/internal/tunnel_info.zig @@ -5,7 +5,7 @@ const std = @import("std"); const builtin = @import("builtin"); -const core = @import("../core/exports.zig"); +const core = @import("../../core/exports.zig"); const api = core.api; const util = core.util; diff --git a/zig/src/wireguard/uapi.zig b/zig/src/wireguard/internal/uapi.zig similarity index 98% rename from zig/src/wireguard/uapi.zig rename to zig/src/wireguard/internal/uapi.zig index 93fc326b..b114a5c5 100644 --- a/zig/src/wireguard/uapi.zig +++ b/zig/src/wireguard/internal/uapi.zig @@ -4,7 +4,7 @@ const std = @import("std"); -const core = @import("../core/exports.zig"); +const core = @import("../../core/exports.zig"); const api = core.api; const resolver = @import("resolver.zig"); diff --git a/zig/tests/wireguard/connection.zig b/zig/tests/wireguard/connection.zig index 756a6501..ca86c12d 100644 --- a/zig/tests/wireguard/connection.zig +++ b/zig/tests/wireguard/connection.zig @@ -5,15 +5,16 @@ const std = @import("std"); const builtin = @import("builtin"); -const adapter = @import("source").wireguard_adapter; -const backend_mod = @import("source").wireguard_backend; +const wireguard_internal = @import("source").wireguard_internal; +const adapter = wireguard_internal.adapter; +const backend_mod = wireguard_internal.backend; const connection = @import("source").wireguard_connection; const conn = @import("source").net_connection; const core = @import("source").core; const io = @import("source").net_io; const sandbox = @import("source").net_sandbox; -const tunnel_info = @import("source").wireguard_tunnel_info; -const uapi = @import("source").wireguard_uapi; +const tunnel_info = wireguard_internal.tunnel_info; +const uapi = wireguard_internal.uapi; const api = core.api; const AtomicBool = std.atomic.Value(bool); From e30a10dac68742c8a3dcec4c365f7d60fd6d1d06 Mon Sep 17 00:00:00 2001 From: Davide Date: Sun, 26 Jul 2026 09:32:12 +0200 Subject: [PATCH 2/8] Assign FIXMEs --- zig/src/core/logging.zig | 2 +- zig/src/openvpn/serializer.zig | 2 +- zig/src/testing/mock.zig | 2 +- zig/src/wireguard/connection.zig | 2 +- zig/src/wireguard/internal/resolver.zig | 2 +- zig/src/wireguard/serializer.zig | 2 +- zig/tests/core/api.zig | 2 +- zig/tests/wireguard/connection.zig | 6 +++--- 8 files changed, 10 insertions(+), 10 deletions(-) diff --git a/zig/src/core/logging.zig b/zig/src/core/logging.zig index 7aadaa18..e70d1710 100644 --- a/zig/src/core/logging.zig +++ b/zig/src/core/logging.zig @@ -31,7 +31,7 @@ var mutex: concurrency.Mutex = .{}; var logs_private_data: bool = false; var external_logger: Callback = null; -// ZIGME: Suppress until only Zig ABI +// FIXME: #527, Suppress until only Zig ABI /// C ABI entry point used by foreign callers to forward a log message. // pub export fn partout_log( // level: c_int, diff --git a/zig/src/openvpn/serializer.zig b/zig/src/openvpn/serializer.zig index aa252bec..bea23d80 100644 --- a/zig/src/openvpn/serializer.zig +++ b/zig/src/openvpn/serializer.zig @@ -13,7 +13,7 @@ pub fn serializeModule( module: *const api.TaggedModule, _: ?*anyopaque, ) core.SerializeError![]u8 { - // ZIGME: Make Configuration non-optional in OpenAPI and remove .IncompleteModule + // FIXME: #525, Make Configuration non-optional in OpenAPI and remove .IncompleteModule const configuration = switch (module.*) { .OpenVPN => |*openvpn| blk: { const value = if (openvpn.configuration) |*configuration| diff --git a/zig/src/testing/mock.zig b/zig/src/testing/mock.zig index 665a951b..00a8d329 100644 --- a/zig/src/testing/mock.zig +++ b/zig/src/testing/mock.zig @@ -108,7 +108,7 @@ pub const MockConnectionEnvironment = struct { fn onLooperFinish(_: ?*anyopaque, _: ?net.Looper.Failure) void {} }; -// ZIGME: Hardcode until only Zig ABI +// FIXME: #527, Hardcode until only Zig ABI pub export fn partout_log(_: i32, message: [*:0]const u8) void { std.debug.print("{s}\n", .{message}); } diff --git a/zig/src/wireguard/connection.zig b/zig/src/wireguard/connection.zig index 0546628e..9a852837 100644 --- a/zig/src/wireguard/connection.zig +++ b/zig/src/wireguard/connection.zig @@ -63,7 +63,7 @@ const WireGuardConnection = struct { module: net.ConnectionModule, sandbox: net.Sandbox, ) net.ConnectionCreateError!net.Connection { - // ZIGME: Make Configuration non-optional in OpenAPI and remove .IncompleteModule + // FIXME: #525, Make Configuration non-optional in OpenAPI and remove .IncompleteModule const base_configuration = switch (module.module.*) { .WireGuard => |*wireguard| blk: { const configuration = if (wireguard.configuration) |*value| diff --git a/zig/src/wireguard/internal/resolver.zig b/zig/src/wireguard/internal/resolver.zig index a5b75423..d13180e7 100644 --- a/zig/src/wireguard/internal/resolver.zig +++ b/zig/src/wireguard/internal/resolver.zig @@ -149,7 +149,7 @@ pub const PeerEndpointResolver = struct { const reachability = if (self.factory) |factory| factory.currentReachability() else null; var failures: usize = 0; - // ZIGME: Swift resolves peer hostnames concurrently with a task group. + // FIXME: #526, Swift resolves peer hostnames concurrently with a task group. // This simpler loop makes DNS timeouts additive when several peers are // unreachable; use bounded concurrent resolution if that becomes a // measurable startup problem. diff --git a/zig/src/wireguard/serializer.zig b/zig/src/wireguard/serializer.zig index bdc9fe65..5d29c9e2 100644 --- a/zig/src/wireguard/serializer.zig +++ b/zig/src/wireguard/serializer.zig @@ -13,7 +13,7 @@ pub fn serializeModule( module: *const api.TaggedModule, _: ?*anyopaque, ) core.SerializeError![]u8 { - // ZIGME: Make Configuration non-optional in OpenAPI and remove .IncompleteModule + // FIXME: #525, Make Configuration non-optional in OpenAPI and remove .IncompleteModule const configuration = switch (module.*) { .WireGuard => |*wireguard| blk: { const value = if (wireguard.configuration) |*configuration| diff --git a/zig/tests/core/api.zig b/zig/tests/core/api.zig index 087b0b9a..3b7ea9ef 100644 --- a/zig/tests/core/api.zig +++ b/zig/tests/core/api.zig @@ -64,7 +64,7 @@ test "reports generated JSON error keys" { ); try std.testing.expectEqualStrings("privateKey", info.key orelse return error.TestUnexpectedResult); - // ZIGME: Make Configuration non-optional in OpenAPI and remove .IncompleteModule + // FIXME: #525, Make Configuration non-optional in OpenAPI and remove .IncompleteModule // info.key = "stale"; // try std.testing.expectError( // error.InvalidModel, diff --git a/zig/tests/wireguard/connection.zig b/zig/tests/wireguard/connection.zig index ca86c12d..00125f07 100644 --- a/zig/tests/wireguard/connection.zig +++ b/zig/tests/wireguard/connection.zig @@ -64,7 +64,7 @@ test "WireGuard connection builds tunnel info with IP and DNS modules" { else => unreachable, }; - // ZIGME: Make Configuration non-optional in OpenAPI and remove .IncompleteModule + // FIXME: #525, Make Configuration non-optional in OpenAPI and remove .IncompleteModule var info = try tunnel_info.TunnelRemoteInfoBuilder.init( allocator, &profile, @@ -89,7 +89,7 @@ test "WireGuard connection builds tunnel info with IP and DNS modules" { else => return error.TestUnexpectedResult, }; try std.testing.expect(core.isGeneratedId(ip.id[0..])); - // ZIGME: Make Configuration non-optional in OpenAPI and remove .IncompleteModule + // FIXME: #525, Make Configuration non-optional in OpenAPI and remove .IncompleteModule try std.testing.expectEqual(configuration.?.interface.dns.?.id, dns.id); try std.testing.expectEqual(@as(?i32, 1420), ip.mtu); try std.testing.expectEqualStrings("1.1.1.1", dns.servers[0].raw); @@ -131,7 +131,7 @@ test "WireGuard connection folds active IP and VPN DNS routes into every peer" { else => unreachable, }; - // ZIGME: Make Configuration non-optional in OpenAPI and remove .IncompleteModule + // FIXME: #525, Make Configuration non-optional in OpenAPI and remove .IncompleteModule var merged = try connection.testing.configurationWithActiveModules( allocator, &source_configuration.?, From 32455e46c498cbaa00954ada3010bd44d3482630 Mon Sep 17 00:00:00 2001 From: Davide Date: Sun, 26 Jul 2026 09:32:35 +0200 Subject: [PATCH 3/8] Add .passphraseRequired code --- .../OpenAPI/Codegen/PartoutErrorCode.swift | 23 ++++++++++--------- .../io/partout/models/PartoutErrorCode.kt | 5 +++- scripts/openapi.yaml | 2 ++ zig/src/core/api.zig | 7 ++++-- zig/src/core/api_generated.zig | 3 +++ zig/tests/openvpn/exports.zig | 9 ++++---- 6 files changed, 30 insertions(+), 19 deletions(-) diff --git a/Sources/PartoutCore/OpenAPI/Codegen/PartoutErrorCode.swift b/Sources/PartoutCore/OpenAPI/Codegen/PartoutErrorCode.swift index a122a8ad..82e82280 100644 --- a/Sources/PartoutCore/OpenAPI/Codegen/PartoutErrorCode.swift +++ b/Sources/PartoutCore/OpenAPI/Codegen/PartoutErrorCode.swift @@ -75,27 +75,28 @@ public enum PartoutErrorCode: String, Sendable, Codable, CaseIterable { /// Keychain item not found. case keychainItemNotFound = "keychainItemNotFound" /// Compression settings mismatch. - case openVPNCompressionMismatch = "OpenVPN.compressionMismatch" + case passphraseRequired = "passphraseRequired" /// Connection failure. - case openVPNConnectionFailure = "OpenVPN.connectionFailure" + case openVPNCompressionMismatch = "OpenVPN.compressionMismatch" /// No routing configuration. - case openVPNNoRouting = "OpenVPN.noRouting" + case openVPNConnectionFailure = "OpenVPN.connectionFailure" /// One-time password is required. - case openVPNOTPRequired = "OpenVPN.otpRequired" + case openVPNNoRouting = "OpenVPN.noRouting" /// Passphrase is required. - case openVPNPassphraseRequired = "OpenVPN.passphraseRequired" + case openVPNOTPRequired = "OpenVPN.otpRequired" /// Authentication can be retried. - case openVPNRecoverableAuthentication = "OpenVPN.recoverableAuthentication" + case openVPNPassphraseRequired = "OpenVPN.passphraseRequired" /// Server requested shutdown. - case openVPNServerShutdown = "OpenVPN.serverShutdown" + case openVPNRecoverableAuthentication = "OpenVPN.recoverableAuthentication" /// TLS failure. - case openVPNTLSFailure = "OpenVPN.tlsFailure" + case openVPNServerShutdown = "OpenVPN.serverShutdown" /// Algorithm is unsupported. - case openVPNUnsupportedAlgorithm = "OpenVPN.unsupportedAlgorithm" + case openVPNTLSFailure = "OpenVPN.tlsFailure" /// Compression setting is unsupported. - case openVPNUnsupportedCompression = "OpenVPN.unsupportedCompression" + case openVPNUnsupportedAlgorithm = "OpenVPN.unsupportedAlgorithm" /// Option is unsupported. - case openVPNUnsupportedOption = "OpenVPN.unsupportedOption" + case openVPNUnsupportedCompression = "OpenVPN.unsupportedCompression" /// Configuration has no peers. + case openVPNUnsupportedOption = "OpenVPN.unsupportedOption" case wireGuardEmptyPeers = "WireGuard.emptyPeers" } diff --git a/cross/android/io/partout/models/PartoutErrorCode.kt b/cross/android/io/partout/models/PartoutErrorCode.kt index 956a8e7b..d45ef4ce 100644 --- a/cross/android/io/partout/models/PartoutErrorCode.kt +++ b/cross/android/io/partout/models/PartoutErrorCode.kt @@ -31,7 +31,7 @@ import kotlinx.serialization.Serializable /** * * - * Values: cached,notFound,operationCancelled,releasedObject,scriptException,timeout,unhandled,incompatibleModules,incompleteModule,noActiveModules,nonFinalModules,requiredImplementation,unexpectedModuleType,unknownImportedModule,unknownModuleHandler,authentication,crypto,dnsFailure,exhaustedEndpoints,fdUnavailable,ioFailure,linkNotActive,networkChanged,networkUnreachable,socketConfiguration,tunNotActive,tunNotAvailable,decoding,encoding,invalidField,invalidValue,parsing,keychainAddItem,keychainItemNotFound,openVPNCompressionMismatch,openVPNConnectionFailure,openVPNNoRouting,openVPNOTPRequired,openVPNPassphraseRequired,openVPNRecoverableAuthentication,openVPNServerShutdown,openVPNTLSFailure,openVPNUnsupportedAlgorithm,openVPNUnsupportedCompression,openVPNUnsupportedOption,wireGuardEmptyPeers + * Values: cached,notFound,operationCancelled,releasedObject,scriptException,timeout,unhandled,incompatibleModules,incompleteModule,noActiveModules,nonFinalModules,requiredImplementation,unexpectedModuleType,unknownImportedModule,unknownModuleHandler,authentication,crypto,dnsFailure,exhaustedEndpoints,fdUnavailable,ioFailure,linkNotActive,networkChanged,networkUnreachable,socketConfiguration,tunNotActive,tunNotAvailable,decoding,encoding,invalidField,invalidValue,parsing,keychainAddItem,keychainItemNotFound,passphraseRequired,openVPNCompressionMismatch,openVPNConnectionFailure,openVPNNoRouting,openVPNOTPRequired,openVPNPassphraseRequired,openVPNRecoverableAuthentication,openVPNServerShutdown,openVPNTLSFailure,openVPNUnsupportedAlgorithm,openVPNUnsupportedCompression,openVPNUnsupportedOption,wireGuardEmptyPeers */ @Serializable enum class PartoutErrorCode(val value: kotlin.String) { @@ -138,6 +138,9 @@ enum class PartoutErrorCode(val value: kotlin.String) { @SerialName(value = "keychainItemNotFound") keychainItemNotFound("keychainItemNotFound"), + @SerialName(value = "passphraseRequired") + passphraseRequired("passphraseRequired"), + @SerialName(value = "OpenVPN.compressionMismatch") openVPNCompressionMismatch("OpenVPN.compressionMismatch"), diff --git a/scripts/openapi.yaml b/scripts/openapi.yaml index 7108df53..a6605076 100644 --- a/scripts/openapi.yaml +++ b/scripts/openapi.yaml @@ -791,6 +791,7 @@ components: - parsing - keychainAddItem - keychainItemNotFound + - passphraseRequired - OpenVPN.compressionMismatch - OpenVPN.connectionFailure - OpenVPN.noRouting @@ -839,6 +840,7 @@ components: - parsing - keychainAddItem - keychainItemNotFound + - passphraseRequired - openVPNCompressionMismatch - openVPNConnectionFailure - openVPNNoRouting diff --git a/zig/src/core/api.zig b/zig/src/core/api.zig index a88d4943..a05bad65 100644 --- a/zig/src/core/api.zig +++ b/zig/src/core/api.zig @@ -99,6 +99,9 @@ pub const parseModule = extensions.parseModule; pub const typeBuildsConnection = extensions.typeBuildsConnection; // ZIGME: Map errors to code enum (LLM: don't touch this) -pub fn codeForError(_: anyerror) PartoutErrorCode { - return .unhandled; +pub fn codeForError(err: anyerror) PartoutErrorCode { + return switch (err) { + error.PassphraseRequired => .passphraseRequired, + else => .unhandled, + }; } diff --git a/zig/src/core/api_generated.zig b/zig/src/core/api_generated.zig index 36384d23..5fc9ceaf 100644 --- a/zig/src/core/api_generated.zig +++ b/zig/src/core/api_generated.zig @@ -2346,6 +2346,7 @@ pub const PartoutErrorCode = enum { parsing, keychainAddItem, keychainItemNotFound, + passphraseRequired, openVPNCompressionMismatch, openVPNConnectionFailure, openVPNNoRouting, @@ -2399,6 +2400,7 @@ pub const PartoutErrorCode = enum { if (std.mem.eql(u8, raw_value, "parsing")) return .parsing; if (std.mem.eql(u8, raw_value, "keychainAddItem")) return .keychainAddItem; if (std.mem.eql(u8, raw_value, "keychainItemNotFound")) return .keychainItemNotFound; + if (std.mem.eql(u8, raw_value, "passphraseRequired")) return .passphraseRequired; if (std.mem.eql(u8, raw_value, "OpenVPN.compressionMismatch")) return .openVPNCompressionMismatch; if (std.mem.eql(u8, raw_value, "OpenVPN.connectionFailure")) return .openVPNConnectionFailure; if (std.mem.eql(u8, raw_value, "OpenVPN.noRouting")) return .openVPNNoRouting; @@ -2450,6 +2452,7 @@ pub const PartoutErrorCode = enum { .parsing => "parsing", .keychainAddItem => "keychainAddItem", .keychainItemNotFound => "keychainItemNotFound", + .passphraseRequired => "passphraseRequired", .openVPNCompressionMismatch => "OpenVPN.compressionMismatch", .openVPNConnectionFailure => "OpenVPN.connectionFailure", .openVPNNoRouting => "OpenVPN.noRouting", diff --git a/zig/tests/openvpn/exports.zig b/zig/tests/openvpn/exports.zig index 5cfc34c7..0f5bb3be 100644 --- a/zig/tests/openvpn/exports.zig +++ b/zig/tests/openvpn/exports.zig @@ -147,11 +147,10 @@ test "OpenVPN module importer reports passphrase requirement" { ); try std.testing.expectEqual(api.ModuleType.OpenVPN, recognized_type); - // ZIGME: Restore after mapping code - // try std.testing.expectEqual( - // api.PartoutErrorCode.openVPNPassphraseRequired, - // api.codeForError(error.PassphraseRequired), - // ); + try std.testing.expectEqual( + api.PartoutErrorCode.passphraseRequired, + api.codeForError(error.PassphraseRequired), + ); } test "OpenVPN module importer decrypts legacy PKCS#1 client keys" { From 40d7c1fca576752a1b94e856152d45417c9ffab9 Mon Sep 17 00:00:00 2001 From: Davide Date: Sun, 26 Jul 2026 09:39:26 +0200 Subject: [PATCH 4/8] Merge looper stopping conditions --- zig/src/net/looper.zig | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/zig/src/net/looper.zig b/zig/src/net/looper.zig index 5eed6b6e..8083ece9 100644 --- a/zig/src/net/looper.zig +++ b/zig/src/net/looper.zig @@ -371,7 +371,7 @@ pub const Looper = struct { self.lock.unlock(); return error.Cancelled; }, - .stopping => { + .stopping, .stopped => { while (self.state == .stopping) { self.condition.wait(&self.lock); } @@ -381,13 +381,6 @@ pub const Looper = struct { if (failed) return error.TerminalFailure; return; }, - .stopped => { - const failed = self.terminal_failure != null; - self.lock.unlock(); - self.joinWorker(); - if (failed) return error.TerminalFailure; - return; - }, } const node = self.createCommandNode(.stop) catch |err| { self.lock.unlock(); From 8cd3cc6f04808bf90cf31d520f938d863fde917c Mon Sep 17 00:00:00 2001 From: Davide Date: Sun, 26 Jul 2026 10:07:10 +0200 Subject: [PATCH 5/8] Rename and test looper termination --- zig/src/net/connection.zig | 10 +++++----- zig/src/net/daemon.zig | 25 +++++++++++++------------ zig/src/openvpn/connection.zig | 10 +++++----- zig/src/openvpn/internal/session.zig | 4 ++-- zig/tests/net/daemon.zig | 24 +++++++++++++++++++++--- 5 files changed, 46 insertions(+), 27 deletions(-) diff --git a/zig/src/net/connection.zig b/zig/src/net/connection.zig index 6a6f3728..e5ee441e 100644 --- a/zig/src/net/connection.zig +++ b/zig/src/net/connection.zig @@ -170,9 +170,9 @@ pub const Connection = struct { network_change: *const fn (*anyopaque, io.ReachabilityInfo, Events) void, better_path: *const fn (*anyopaque, Events) void, deinit: *const fn (*anyopaque) void, - /// Called synchronously from the daemon-owned looper's terminal - /// callback. Most protocols can leave this unset. - looper_finish: ?*const fn (*anyopaque, ?looper.Looper.Failure) void = null, + /// Called synchronously when the shared daemon-owned looper + /// terminates, before runtime recovery or connection destruction. + looper_terminated: ?*const fn (*anyopaque, ?looper.Looper.Failure) void = null, }; pub fn start(self: Connection, events: Events) StartError!bool { @@ -200,11 +200,11 @@ pub const Connection = struct { self.vtable.better_path(self.ptr, events); } - pub fn looperDidFinish( + pub fn looperDidTerminate( self: Connection, failure: ?looper.Looper.Failure, ) void { - const block = self.vtable.looper_finish orelse return; + const block = self.vtable.looper_terminated orelse return; block(self.ptr, failure); } diff --git a/zig/src/net/daemon.zig b/zig/src/net/daemon.zig index 55c556bf..7ce17dd5 100644 --- a/zig/src/net/daemon.zig +++ b/zig/src/net/daemon.zig @@ -254,7 +254,7 @@ pub const Daemon = struct { onConnectionLastError: api.PartoutErrorCode, onConnectionDataCount: api.DataCount, onConnectionCancel: ?api.PartoutErrorCode, - onLooperFinish: ?Looper.Failure, + onLooperTerminated: ?Looper.Failure, recoverConnection, onConnectionBlock: struct { ptr: *anyopaque, @@ -275,7 +275,7 @@ pub const Daemon = struct { .onConnectionLastError => |code| self.handleLastError(code), .onConnectionDataCount => |count| self.handleDataCount(count), .onConnectionCancel => |code| self.handleConnectionCancel(code), - .onLooperFinish => |failure| self.handleLooperFinish(failure), + .onLooperTerminated => |failure| self.handleLooperTermination(failure), .recoverConnection => self.recoverConnectionRuntime(), .onConnectionBlock => |payload| self.handleConnectionBlock(payload.ptr, payload.block), } @@ -406,7 +406,7 @@ pub const Daemon = struct { self.state = .started; log.write(.notice, "Start daemon"); - self.clearEvents(); + self.clearEnvironment(); // Establish settings-only tunnel if no connection if (self.isSettingsOnly()) { @@ -522,7 +522,7 @@ pub const Daemon = struct { fn finishStop(self: *Daemon) void { self.state = .stopped; - if (self.stop_mode == .clear_environment) self.clearEvents(); + if (self.stop_mode == .clear_environment) self.clearEnvironment(); log.write(.notice, "Daemon stopped successfully"); } @@ -664,7 +664,7 @@ pub const Daemon = struct { self.emitRemove(.data_count); } - fn handleLooperFinish( + fn handleLooperTermination( self: *Daemon, failure: ?Looper.Failure, ) void { @@ -743,7 +743,7 @@ pub const Daemon = struct { if (self.options.events) |e| e.remove_key(e.ctx, key); } - fn clearEvents(self: *Daemon) void { + fn clearEnvironment(self: *Daemon) void { log.write(.notice, "Clear connection events"); self.snapshot_publisher.clearEnvironment(); self.emitRemove(.connection_status); @@ -774,7 +774,7 @@ pub const Daemon = struct { looper.* = Looper.init(self.allocator, .{ .on_finish = .{ .context = self, - .callback = onLooperFinish, + .callback = onLooperTerminate, }, }) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, @@ -799,7 +799,8 @@ pub const Daemon = struct { ); errdefer connection.deinit(); - // Publish a complete runtime before the looper can invoke onFinish. + // Publish a complete runtime before the looper can invoke its + // terminal callback. self.connection_runtime = .{ .connection = connection, .looper = looper, @@ -838,7 +839,7 @@ pub const Daemon = struct { // Keep the borrowed looper object alive until the connection has // released every Session that refers to it, but first join its worker - // so onFinish cannot race connection deinitialization. + // so the terminal callback cannot race connection deinitialization. runtime.looper.stop() catch |err| switch (err) { error.TerminalFailure => {}, else => log.writef(.debug, "Unable to stop connection looper: {s}", .{@errorName(err)}), @@ -849,7 +850,7 @@ pub const Daemon = struct { self.connection_runtime = null; } - fn onLooperFinish( + fn onLooperTerminate( ctx: ?*anyopaque, failure: ?Looper.Failure, ) void { @@ -857,9 +858,9 @@ pub const Daemon = struct { if (self.connection_runtime) |runtime| { // Session teardown must stay on the looper queue. It may enqueue // connection work first; actor FIFO preserves that ordering. - runtime.connection.looperDidFinish(failure); + runtime.connection.looperDidTerminate(failure); } - self.actor.schedule(.{ .onLooperFinish = failure }) catch |err| { + self.actor.schedule(.{ .onLooperTerminated = failure }) catch |err| { log.writef(.debug, "Ignore terminal looper after actor shutdown: {s}", .{@errorName(err)}); }; } diff --git a/zig/src/openvpn/connection.zig b/zig/src/openvpn/connection.zig index c97b9aab..004f6ab6 100644 --- a/zig/src/openvpn/connection.zig +++ b/zig/src/openvpn/connection.zig @@ -300,12 +300,12 @@ const OpenVPNConnection = struct { }; } - fn looperDidFinish( + fn looperDidTerminate( self: *OpenVPNConnection, failure: ?net.Looper.Failure, ) void { const session = self.current_session orelse return; - session.looperDidFinish(failure); + session.looperDidTerminate(failure); } fn setupLink( @@ -958,7 +958,7 @@ const openvpn_connection_vtable = net.Connection.VTable{ .network_change = networkChange, .better_path = betterPath, .deinit = deinit, - .looper_finish = looperDidFinish, + .looper_terminated = looperDidTerminate, }; fn start( @@ -992,12 +992,12 @@ fn betterPath(ptr: *anyopaque, events: net.Connection.Events) void { self.betterPath(events); } -fn looperDidFinish( +fn looperDidTerminate( ptr: *anyopaque, failure: ?net.Looper.Failure, ) void { const self: *OpenVPNConnection = @ptrCast(@alignCast(ptr)); - self.looperDidFinish(failure); + self.looperDidTerminate(failure); } fn deinit(ptr: *anyopaque) void { diff --git a/zig/src/openvpn/internal/session.zig b/zig/src/openvpn/internal/session.zig index 3bb605b7..574a2c22 100644 --- a/zig/src/openvpn/internal/session.zig +++ b/zig/src/openvpn/internal/session.zig @@ -382,7 +382,7 @@ pub const Session = struct { prepareShutdownOnQueue, ) catch |err| { // A terminal looper has already serialized final state; its owner - // routes `OnFinish` through `looperDidFinish` while Session lives. + // routes `OnFinish` through `looperDidTerminate` while Session lives. if (err == error.Cancelled or err == error.TerminalFailure) return; log.writef(.err, "Unable to shut down session on looper queue: {s}", .{ @errorName(err), @@ -521,7 +521,7 @@ pub const Session = struct { /// Routes the externally owned looper's terminal callback into the /// session. The owner must call this synchronously from `Looper.OnFinish` /// while the Session is alive, and must stop forwarding before `destroy`. - pub fn looperDidFinish(self: *Session, failure: ?net_mod.Looper.Failure) void { + pub fn looperDidTerminate(self: *Session, failure: ?net_mod.Looper.Failure) void { std.debug.assert(self.looper.isOnQueue()); if (failure) |value| switch (value) { .user => |cause| log.writef(.err, "Session looper finished with error: {s}", .{ diff --git a/zig/tests/net/daemon.zig b/zig/tests/net/daemon.zig index 9ccd9901..8cb3ad01 100644 --- a/zig/tests/net/daemon.zig +++ b/zig/tests/net/daemon.zig @@ -401,7 +401,10 @@ test "connection daemon replaces a terminal looper and reconnects" { const allocator = std.testing.allocator; const mock = mock_mod; - var capture = SandboxCapture{ .queue_work_on_stop = true }; + var capture = SandboxCapture{ + .queue_work_on_stop = true, + .queue_work_on_looper_termination = true, + }; var implementations = [_]net.ConnectionImplementation{capture.implementation()}; var registry = try net.ConnectionRegistry.init(allocator, &implementations); defer registry.deinit(allocator); @@ -431,14 +434,16 @@ test "connection daemon replaces a terminal looper and reconnects" { const terminal_looper = capture.looper orelse return error.TestUnexpectedResult; try terminal_looper.stop(); - // Flush onLooperFinish and its queued FIFO recovery barrier. + // Flush onLooperTerminated and its queued FIFO recovery barrier. try std.testing.expectError(error.AlreadyStarted, sut.start()); try std.testing.expectError(error.AlreadyStarted, sut.start()); try std.testing.expectEqual(@as(usize, 2), capture.create_count); try std.testing.expectEqual(@as(usize, 1), capture.deinit_count); try std.testing.expectEqual(@as(usize, 2), capture.start_count); try std.testing.expectEqual(@as(usize, 1), capture.stop_count); - try std.testing.expectEqual(@as(usize, 1), capture.serialized_count); + try std.testing.expectEqual(@as(usize, 1), capture.looper_termination_count); + try std.testing.expect(capture.looper_termination_before_deinit); + try std.testing.expectEqual(@as(usize, 2), capture.serialized_count); try std.testing.expect(capture.serialized_before_deinit); const recovered_looper = capture.looper orelse return error.TestUnexpectedResult; try std.testing.expect(try recovered_looper.perform( @@ -798,13 +803,16 @@ const SandboxCapture = struct { disconnect_on_start: bool = false, cancel_on_start: ?api.PartoutErrorCode = null, queue_work_on_stop: bool = false, + queue_work_on_looper_termination: bool = false, cache_dir: []const u8 = "", create_count: usize = 0, start_count: usize = 0, stop_count: usize = 0, + looper_termination_count: usize = 0, deinit_count: usize = 0, serialized_count: usize = 0, serialized_before_deinit: bool = false, + looper_termination_before_deinit: bool = false, fn implementation(self: *SandboxCapture) net.ConnectionImplementation { return .{ @@ -878,6 +886,15 @@ const SandboxCapture = struct { fn betterPath(_: *anyopaque, _: net.Connection.Events) void {} + fn looperTerminated(ptr: *anyopaque, _: ?net.Looper.Failure) void { + const self: *SandboxCapture = @ptrCast(@alignCast(ptr)); + self.looper_termination_count += 1; + self.looper_termination_before_deinit = self.deinit_count == 0; + if (self.queue_work_on_looper_termination) { + self.serialized_executor.?.run(self, onSerialized); + } + } + fn deinit(ptr: *anyopaque) void { const self: *SandboxCapture = @ptrCast(@alignCast(ptr)); self.deinit_count += 1; @@ -889,6 +906,7 @@ const SandboxCapture = struct { .network_change = networkChange, .better_path = betterPath, .deinit = deinit, + .looper_terminated = looperTerminated, }; const implementation_vtable = net.ConnectionImplementation.VTable{ From 7468ad18c42172554238ace0fd11eb71489e0981 Mon Sep 17 00:00:00 2001 From: Davide Date: Sun, 26 Jul 2026 10:11:17 +0200 Subject: [PATCH 6/8] Normalize WireGuard allocators --- zig/src/wireguard/connection.zig | 32 ++++++++++++-------------------- 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/zig/src/wireguard/connection.zig b/zig/src/wireguard/connection.zig index 9a852837..53a7f5db 100644 --- a/zig/src/wireguard/connection.zig +++ b/zig/src/wireguard/connection.zig @@ -128,7 +128,6 @@ const WireGuardConnection = struct { fn start( self: *WireGuardConnection, - allocator: std.mem.Allocator, events: net.Connection.Events, ) net.ConnectionStartError!bool { if (!self.adapter.isStopped()) { @@ -141,7 +140,7 @@ const WireGuardConnection = struct { events.status(events.ctx, .connecting); errdefer events.status(events.ctx, .disconnected); - self.adapter.start(allocator) catch |err| { + self.adapter.start(self.allocator) catch |err| { switch (err) { error.CannotLocateTunnelFileDescriptor => { log.write( @@ -175,7 +174,7 @@ const WireGuardConnection = struct { self.adapter.interfaceName() orelse "unknown", }); events.status(events.ctx, .connected); - self.reportDataCount(allocator, events); + self.reportDataCount(events); self.startDataCountTimer() catch |err| { log.writef(.err, "Unable to start data count timer: {s}", .{@errorName(err)}); }; @@ -184,7 +183,6 @@ const WireGuardConnection = struct { fn stop( self: *WireGuardConnection, - allocator: std.mem.Allocator, timeout_ms: u32, events: net.Connection.Events, ) void { @@ -200,18 +198,17 @@ const WireGuardConnection = struct { self.stopDataCountTimer(); self.cancelTemporaryShutdownRetry(); events.status(events.ctx, .disconnecting); - self.adapter.stop(allocator); + self.adapter.stop(self.allocator); events.status(events.ctx, .disconnected); } fn networkChange( self: *WireGuardConnection, - allocator: std.mem.Allocator, reachability: net.ReachabilityInfo, events: net.Connection.Events, ) void { self.cancelTemporaryShutdownRetry(); - switch (self.adapter.didUpdateReachable(allocator, reachability.reachable)) { + switch (self.adapter.didUpdateReachable(self.allocator, reachability.reachable)) { .unchanged => {}, .resumed => events.status(events.ctx, .connected), .retry => self.scheduleTemporaryShutdownRetry(), @@ -220,7 +217,6 @@ const WireGuardConnection = struct { fn betterPath( _: *WireGuardConnection, - _: std.mem.Allocator, _: net.Connection.Events, ) void { log.write(.debug, "Better path notification ignored"); @@ -228,17 +224,13 @@ const WireGuardConnection = struct { fn reportDataCount( self: *const WireGuardConnection, - allocator: std.mem.Allocator, events: net.Connection.Events, ) void { - events.data_count(events.ctx, self.readDataCount(allocator) orelse return); + events.data_count(events.ctx, self.readDataCount() orelse return); } - fn readDataCount( - self: *const WireGuardConnection, - allocator: std.mem.Allocator, - ) ?api.DataCount { - return self.adapter.dataCountFromRuntimeConfig(allocator) catch |err| { + fn readDataCount(self: *const WireGuardConnection) ?api.DataCount { + return self.adapter.dataCountFromRuntimeConfig(self.allocator) catch |err| { log.writef(.debug, "Unable to fetch runtime configuration: {s}", .{@errorName(err)}); return null; }; @@ -275,7 +267,7 @@ const WireGuardConnection = struct { if (!self.data_count_timer_active) return; const events = self.events orelse return; - self.reportDataCount(self.allocator, events); + self.reportDataCount(events); if (!self.data_count_timer_active) return; self.data_count_timer.init(self.data_count_interval_ms, onDataCountTimer, self) catch |err| { log.writef(.err, "Unable to reschedule data count timer: {s}", .{@errorName(err)}); @@ -463,7 +455,7 @@ const wireguard_connection_vtable = net.Connection.VTable{ fn start(ptr: *anyopaque, events: net.Connection.Events) net.ConnectionStartError!bool { const self: *WireGuardConnection = @ptrCast(@alignCast(ptr)); - return self.start(self.allocator, events); + return self.start(events); } fn stop( @@ -472,7 +464,7 @@ fn stop( events: net.Connection.Events, ) void { const self: *WireGuardConnection = @ptrCast(@alignCast(ptr)); - self.stop(self.allocator, timeout_ms, events); + self.stop(timeout_ms, events); } fn networkChange( @@ -481,12 +473,12 @@ fn networkChange( events: net.Connection.Events, ) void { const self: *WireGuardConnection = @ptrCast(@alignCast(ptr)); - self.networkChange(self.allocator, reachability, events); + self.networkChange(reachability, events); } fn betterPath(ptr: *anyopaque, events: net.Connection.Events) void { const self: *WireGuardConnection = @ptrCast(@alignCast(ptr)); - self.betterPath(self.allocator, events); + self.betterPath(events); } fn deinit(ptr: *anyopaque) void { From 621f0a48bd4fd1765b90cd0463818a5034dd4a67 Mon Sep 17 00:00:00 2001 From: Davide Date: Sun, 26 Jul 2026 10:37:46 +0200 Subject: [PATCH 7/8] Fix direct import --- zig/src/net/looper_queue.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zig/src/net/looper_queue.zig b/zig/src/net/looper_queue.zig index 79c5ff5d..fc4e9e41 100644 --- a/zig/src/net/looper_queue.zig +++ b/zig/src/net/looper_queue.zig @@ -4,7 +4,7 @@ const std = @import("std"); -const concurrency = @import("../core/concurrency.zig"); +const core = @import("../core/exports.zig"); const io = @import("io.zig"); /// Single binary data packet. @@ -216,7 +216,7 @@ pub const Command = union(enum) { /// an optional one-shot timer for deferred scheduling. pub const CommandNode = struct { command: Command, - timer: concurrency.RunAfter.Scheduled = .{}, + timer: core.RunAfter.Scheduled = .{}, // Intrusive command queue linkage. next: ?*CommandNode = null, From f5d9d2f070229787f636538cbeb10d12bce982f0 Mon Sep 17 00:00:00 2001 From: Davide Date: Sun, 26 Jul 2026 10:19:24 +0200 Subject: [PATCH 8/8] Use sentinel-terminated slices at the C boundaries --- zig/src/abi/helpers.zig | 2 +- zig/src/core/api_generated.zig | 32 ++++++------- zig/src/core/api_manual.zig | 4 +- zig/src/core/logging.zig | 56 +++++++++++++--------- zig/src/core/util.zig | 19 ++++---- zig/src/net/platform_dns.zig | 6 +-- zig/src/openvpn/internal/auth.zig | 6 +-- zig/src/openvpn/internal/data.zig | 6 +-- zig/src/openvpn/internal/serialization.zig | 17 ++----- zig/src/openvpn/internal/tls.zig | 5 +- zig/src/partout.zig | 4 +- zig/src/wireguard/internal/adapter.zig | 6 +-- zig/src/wireguard/internal/backend.zig | 31 +++++------- zig/src/wireguard/internal/uapi.zig | 8 ++-- zig/tests/core/logging.zig | 36 ++++++++++++++ zig/tests/core/util.zig | 23 +++++++++ zig/tests/net/platform_dns.zig | 2 +- zig/tests/wireguard/connection.zig | 5 +- zig/tools/openapi_codegen.zig | 2 +- 19 files changed, 161 insertions(+), 109 deletions(-) diff --git a/zig/src/abi/helpers.zig b/zig/src/abi/helpers.zig index 9e16e53f..121fa0e8 100644 --- a/zig/src/abi/helpers.zig +++ b/zig/src/abi/helpers.zig @@ -105,7 +105,7 @@ fn boundEventsBinding(ptr: *anyopaque) ?c.partout_daemon_events { return self.binding; } -fn eventKeyString(key: net.DaemonEventKey) []const u8 { +fn eventKeyString(key: net.DaemonEventKey) [:0]const u8 { return switch (key) { .connection_status => "connectionStatus", .data_count => "dataCount", diff --git a/zig/src/core/api_generated.zig b/zig/src/core/api_generated.zig index 5fc9ceaf..5de5b837 100644 --- a/zig/src/core/api_generated.zig +++ b/zig/src/core/api_generated.zig @@ -379,7 +379,7 @@ pub const ConnectionStatus = enum { return null; } - pub fn raw(self: @This()) []const u8 { + pub fn raw(self: @This()) [:0]const u8 { return switch (self) { .disconnected => "disconnected", .connecting => "connecting", @@ -498,7 +498,7 @@ pub const DNSModuleDomainPolicy = enum { return null; } - pub fn raw(self: @This()) []const u8 { + pub fn raw(self: @This()) [:0]const u8 { return switch (self) { .match => "match", .matchAndSearch => "matchAndSearch", @@ -736,7 +736,7 @@ pub const DNSProtocol = enum { return null; } - pub fn raw(self: @This()) []const u8 { + pub fn raw(self: @This()) [:0]const u8 { return switch (self) { .cleartext => "cleartext", .https => "https", @@ -1014,7 +1014,7 @@ pub const IPSocketType = enum { return null; } - pub fn raw(self: @This()) []const u8 { + pub fn raw(self: @This()) [:0]const u8 { return switch (self) { .udp => "UDP", .tcp => "TCP", @@ -1061,7 +1061,7 @@ pub const ModuleType = enum { return null; } - pub fn raw(self: @This()) []const u8 { + pub fn raw(self: @This()) [:0]const u8 { return switch (self) { .Custom => "Custom", .DNS => "DNS", @@ -1155,7 +1155,7 @@ pub const OnDemandModuleOtherNetwork = enum { return null; } - pub fn raw(self: @This()) []const u8 { + pub fn raw(self: @This()) [:0]const u8 { return switch (self) { .mobile => "mobile", .ethernet => "ethernet", @@ -1184,7 +1184,7 @@ pub const OnDemandModulePolicy = enum { return null; } - pub fn raw(self: @This()) []const u8 { + pub fn raw(self: @This()) [:0]const u8 { return switch (self) { .any => "any", .including => "including", @@ -1220,7 +1220,7 @@ pub const OpenVPNCipher = enum { return null; } - pub fn raw(self: @This()) []const u8 { + pub fn raw(self: @This()) [:0]const u8 { return switch (self) { .aes128cbc => "AES-128-CBC", .aes192cbc => "AES-192-CBC", @@ -1709,7 +1709,7 @@ pub const OpenVPNCredentialsOTPMethod = enum { return null; } - pub fn raw(self: @This()) []const u8 { + pub fn raw(self: @This()) [:0]const u8 { return switch (self) { .none => "none", .append => "append", @@ -1743,7 +1743,7 @@ pub const OpenVPNDigest = enum { return null; } - pub fn raw(self: @This()) []const u8 { + pub fn raw(self: @This()) [:0]const u8 { return switch (self) { .sha1 => "SHA1", .sha224 => "SHA224", @@ -2034,7 +2034,7 @@ pub const OpenVPNPullMask = enum { return null; } - pub fn raw(self: @This()) []const u8 { + pub fn raw(self: @This()) [:0]const u8 { return switch (self) { .routes => "routes", .dns => "dns", @@ -2064,7 +2064,7 @@ pub const OpenVPNRoutingPolicy = enum { return null; } - pub fn raw(self: @This()) []const u8 { + pub fn raw(self: @This()) [:0]const u8 { return switch (self) { .IPv4 => "IPv4", .IPv6 => "IPv6", @@ -2232,7 +2232,7 @@ pub const OpenVPNTLSWrapStrategy = enum { return null; } - pub fn raw(self: @This()) []const u8 { + pub fn raw(self: @This()) [:0]const u8 { return switch (self) { .auth => "auth", .crypt => "crypt", @@ -2416,7 +2416,7 @@ pub const PartoutErrorCode = enum { return null; } - pub fn raw(self: @This()) []const u8 { + pub fn raw(self: @This()) [:0]const u8 { return switch (self) { .cached => "cached", .notFound => "notFound", @@ -2675,7 +2675,7 @@ pub const SocketType = enum { return null; } - pub fn raw(self: @This()) []const u8 { + pub fn raw(self: @This()) [:0]const u8 { return switch (self) { .udp => "UDP", .tcp => "TCP", @@ -3071,7 +3071,7 @@ pub const TunnelStatus = enum { return null; } - pub fn raw(self: @This()) []const u8 { + pub fn raw(self: @This()) [:0]const u8 { return switch (self) { .inactive => "inactive", .activating => "activating", diff --git a/zig/src/core/api_manual.zig b/zig/src/core/api_manual.zig index 10a4337f..08444c2e 100644 --- a/zig/src/core/api_manual.zig +++ b/zig/src/core/api_manual.zig @@ -31,8 +31,8 @@ pub const Address = struct { }; } - fn ofZ(c_address: [*:0]const u8) Family { - return ofRaw(std.mem.span(c_address)); + fn ofZ(c_address: [:0]const u8) Family { + return ofRaw(c_address); } fn isValidPrefixLength(self: Family, prefix_length: u8) bool { diff --git a/zig/src/core/logging.zig b/zig/src/core/logging.zig index e70d1710..e6241b22 100644 --- a/zig/src/core/logging.zig +++ b/zig/src/core/logging.zig @@ -43,17 +43,9 @@ var external_logger: Callback = null; // return; // }; // mutex.unlock(); -// dispatch(logger, level, message); +// dispatchCString(logger, level, message); // } -fn dispatch( - logger: Logger, - level: c_int, - message: [*:0]const u8, -) void { - logger(level, message); -} - /// Configures global logging state. /// /// `private_data` records whether callers permit sensitive values in logs. @@ -97,24 +89,15 @@ pub fn sensitive(value: anytype) SensitiveValue(@TypeOf(value)) { /// Writes a core log message. /// -/// Messages are dropped when no logger is installed or when allocating the -/// zero-terminated copy fails. -pub fn write(level: Level, message: []const u8) void { +/// The borrowed message remains valid for the duration of the callback. +pub fn write(level: Level, message: [:0]const u8) void { mutex.lock(); const logger = external_logger orelse { mutex.unlock(); return; }; mutex.unlock(); - writeTo(logger, level, message); -} - -fn writeTo(logger: Logger, level: Level, message: []const u8) void { - const allocator = std.heap.c_allocator; - var c_message: util.TemporaryCString = .{}; - c_message.init(allocator, message) catch return; - defer c_message.deinit(); - dispatch(logger, @intFromEnum(level), c_message.ptr()); + dispatchSlice(logger, @intFromEnum(level), message); } /// Formats and writes a core log message. @@ -139,8 +122,35 @@ pub fn writef(level: Level, comptime fmt: []const u8, args: anytype) void { args, private_data, ) catch return; - const message = std.fmt.allocPrint(allocator, fmt, prepared) catch return; - writeTo(logger, level, message); + const message = std.fmt.allocPrintSentinel(allocator, fmt, prepared, 0) catch return; + dispatchSlice(logger, @intFromEnum(level), message); +} + +/// Forwards a borrowed C string directly to the configured C logger. +pub fn writeCString(level: Level, message: [*:0]const u8) void { + mutex.lock(); + const logger = external_logger orelse { + mutex.unlock(); + return; + }; + mutex.unlock(); + dispatchCString(logger, @intFromEnum(level), message); +} + +fn dispatchSlice( + logger: Logger, + level: c_int, + message: [:0]const u8, +) void { + dispatchCString(logger, level, message.ptr); +} + +fn dispatchCString( + logger: Logger, + level: c_int, + message: [*:0]const u8, +) void { + logger(level, message); } /// Writes a duration in seconds using a compact `h`, `m`, and `s` diff --git a/zig/src/core/util.zig b/zig/src/core/util.zig index 1e54c3c2..47dbba4b 100644 --- a/zig/src/core/util.zig +++ b/zig/src/core/util.zig @@ -62,8 +62,8 @@ pub fn appendOwned( try list.append(allocator, copy); } -/// Returns a slice from a C string. -pub fn borrowedCString(ptr: [*:0]const u8) []const u8 { +/// Returns a borrowed sentinel-terminated slice from a C string. +pub fn borrowedCString(ptr: [*:0]const u8) [:0]const u8 { return std.mem.span(ptr); } @@ -94,9 +94,9 @@ pub fn containsOnly(value: []const u8, allowed: []const u8) bool { /// Returns an allocator-owned path to the system temporary directory. pub fn defaultCacheDir(allocator: std.mem.Allocator) error{OutOfMemory}![]u8 { - const env_names = [_][*:0]const u8{ "TMPDIR", "TMP", "TEMP" }; + const env_names = [_][:0]const u8{ "TMPDIR", "TMP", "TEMP" }; for (env_names) |name| { - const value = std.c.getenv(name) orelse continue; + const value = std.c.getenv(name.ptr) orelse continue; const path = std.mem.span(value); if (path.len > 0) return allocator.dupe(u8, path); } @@ -249,14 +249,13 @@ pub fn trim(value: []const u8) []const u8 { return std.mem.trim(u8, value, " \r\t\n"); } -/// Runs a callback with a slice temporarily remapped to a C string. +/// Runs a callback with a borrowed, null-terminated slice. +/// +/// The callback must not retain the pointer after it returns. pub fn withCString( - value: []const u8, + value: [:0]const u8, callback: *const fn (?*anyopaque, [*c]const u8) callconv(.c) void, callback_ctx: ?*anyopaque, ) void { - var c_value: TemporaryCString = .{}; - c_value.init(std.heap.c_allocator, value) catch return; - defer c_value.deinit(); - callback(callback_ctx, c_value.ptr()); + callback(callback_ctx, value.ptr); } diff --git a/zig/src/net/platform_dns.zig b/zig/src/net/platform_dns.zig index df663103..dd0734a9 100644 --- a/zig/src/net/platform_dns.zig +++ b/zig/src/net/platform_dns.zig @@ -15,7 +15,7 @@ const log = core.logging; const DNSRecord = sandbox.DNSRecord; const DNSResolver = sandbox.DNSResolver; const ReachabilityInfo = io.ReachabilityInfo; -const ResolveFn = *const fn ([*:0]const u8, *const c.addrinfo, ?*const ReachabilityInfo, *[*c]c.addrinfo) c_int; +const ResolveFn = *const fn ([:0]const u8, *const c.addrinfo, ?*const ReachabilityInfo, *[*c]c.addrinfo) c_int; // Timed-out slots remain occupied until their uncancellable query returns. const max_pending_queries = 3; @@ -310,12 +310,12 @@ const Query = struct { }; fn resolveNative( - hostname: [*:0]const u8, + hostname: [:0]const u8, hints: *const c.addrinfo, reachability: ?*const ReachabilityInfo, result: *[*c]c.addrinfo, ) c_int { - return c.pp_dns_resolve(hostname, null, hints, reachability, result); + return c.pp_dns_resolve(hostname.ptr, null, hints, reachability, result); } fn resolveBlock( diff --git a/zig/src/openvpn/internal/auth.zig b/zig/src/openvpn/internal/auth.zig index ba18a756..ec696810 100644 --- a/zig/src/openvpn/internal/auth.zig +++ b/zig/src/openvpn/internal/auth.zig @@ -199,7 +199,7 @@ pub const PRF = struct { fn keysHash( allocator: std.mem.Allocator, functions: c_crypto.pp_crypto_fnt, - digest_name: [*:0]const u8, + digest_name: [:0]const u8, secret: []const u8, seed: []const u8, size: usize, @@ -231,7 +231,7 @@ pub const PRF = struct { fn hmac( allocator: std.mem.Allocator, functions: c_crypto.pp_crypto_fnt, - digest_name: [*:0]const u8, + digest_name: [:0]const u8, secret: []const u8, data: []const u8, ) !ZeroingData { @@ -241,7 +241,7 @@ pub const PRF = struct { var context = c_crypto.pp_hmac_ctx{ .dst = buffer.bytes.ptr, .dst_len = buffer.bytes.len, - .digest_name = digest_name, + .digest_name = digest_name.ptr, .secret = secret.ptr, .secret_len = secret.len, .data = data.ptr, diff --git a/zig/src/openvpn/internal/data.zig b/zig/src/openvpn/internal/data.zig index c5cb7c33..d534ba78 100644 --- a/zig/src/openvpn/internal/data.zig +++ b/zig/src/openvpn/internal/data.zig @@ -341,15 +341,13 @@ pub const DataPathWrapper = struct { const framing = nativeFraming(parameters.compression_framing); const cipher_name = if (parameters.cipher) |cipher| - try allocator.dupeZ(u8, cipher.raw()) + cipher.raw() else null; - defer if (cipher_name) |value| allocator.free(value); const digest_name = if (parameters.digest) |digest| - try allocator.dupeZ(u8, digest.raw()) + digest.raw() else null; - defer if (digest_name) |value| allocator.free(value); const mode: *c.openvpn_dp_mode = if (isAEAD(parameters.cipher)) blk: { const name = cipher_name orelse return error.UnsupportedAlgorithm; diff --git a/zig/src/openvpn/internal/serialization.zig b/zig/src/openvpn/internal/serialization.zig index 3fd0a4ca..85cebc22 100644 --- a/zig/src/openvpn/internal/serialization.zig +++ b/zig/src/openvpn/internal/serialization.zig @@ -194,10 +194,7 @@ const AuthSerializer = struct { defer keys.deinit(allocator); var bridge = try CryptoKeysBridge.init(allocator, &keys); defer bridge.deinit(); - var digest_name: core_mod.util.TemporaryCString = .{}; - try digest_name.init(allocator, digest.raw()); - defer digest_name.deinit(); - const cbc = functions.cbc_create.?(null, digest_name.ptr(), bridge.native()) orelse return error.UnsupportedAlgorithm; + const cbc = functions.cbc_create.?(null, digest.raw().ptr, bridge.native()) orelse return error.UnsupportedAlgorithm; const prefix_length = c.OpenVPNPacketOpcodeLength + c.OpenVPNPacketSessionIdLength; const hmac_length = c_crypto.pp_crypto_meta_of(cbc).digest_len; const auth_length = hmac_length + c.OpenVPNPacketReplayIdLength + c.OpenVPNPacketReplayTimestampLength; @@ -316,15 +313,11 @@ const CryptSerializer = struct { defer keys.deinit(allocator); var bridge = try CryptoKeysBridge.init(allocator, &keys); defer bridge.deinit(); - var cipher_name: core_mod.util.TemporaryCString = .{}; - try cipher_name.init(allocator, "AES-256-CTR"); - defer cipher_name.deinit(); - var digest_name: core_mod.util.TemporaryCString = .{}; - try digest_name.init(allocator, "SHA256"); - defer digest_name.deinit(); + const cipher_name: [:0]const u8 = "AES-256-CTR"; + const digest_name: [:0]const u8 = "SHA256"; const ctr = functions.ctr_create.?( - cipher_name.ptr(), - digest_name.ptr(), + cipher_name.ptr, + digest_name.ptr, ControlConstants.ctr_tag_length, ControlConstants.ctr_payload_length, bridge.native(), diff --git a/zig/src/openvpn/internal/tls.zig b/zig/src/openvpn/internal/tls.zig index 3a45d703..c7fe2a26 100644 --- a/zig/src/openvpn/internal/tls.zig +++ b/zig/src/openvpn/internal/tls.zig @@ -71,7 +71,7 @@ pub const TLSWrapper = struct { const create_tls = functions.create orelse return error.TLSFailure; const free_tls = functions.free orelse return error.TLSFailure; - const ca_path_plain = try std.fmt.allocPrint( + const ca_path = try std.fmt.allocPrintSentinel( allocator, "{s}{s}{s}", .{ @@ -79,9 +79,8 @@ pub const TLSWrapper = struct { if (std.mem.endsWith(u8, parameters.caches_directory, "/")) "" else "/", parameters.ca_filename, }, + 0, ); - defer allocator.free(ca_path_plain); - const ca_path = try allocator.dupeZ(u8, ca_path_plain); errdefer allocator.free(ca_path); try writeCA(ca_path, ca.pem); errdefer _ = c_common.remove(ca_path.ptr); diff --git a/zig/src/partout.zig b/zig/src/partout.zig index 7222a41a..c0b73d2f 100644 --- a/zig/src/partout.zig +++ b/zig/src/partout.zig @@ -24,14 +24,14 @@ const util = core.util; const allocator = std.heap.c_allocator; const identifier = "io.partout"; const version = "0.152.2"; -const version_identifier: [*:0]const u8 = std.fmt.comptimePrint("{s} {s}", .{ identifier, version }); +const version_identifier: [:0]const u8 = std.fmt.comptimePrint("{s} {s}", .{ identifier, version }); // const DaemonRuntime = if (builtin.is_test) @import("testing/mock.zig").MockRuntime else abi.DaemonRuntime; // var daemon_runtime = DaemonRuntime{}; var daemon_runtime: ?*abi.DaemonRuntime = null; pub export fn partout_version() callconv(.c) [*:0]const u8 { - return version_identifier; + return version_identifier.ptr; } pub export fn partout_init(args_pointer: ?*const c.partout_init_args) callconv(.c) void { diff --git a/zig/src/wireguard/internal/adapter.zig b/zig/src/wireguard/internal/adapter.zig index f25a7572..7511dae9 100644 --- a/zig/src/wireguard/internal/adapter.zig +++ b/zig/src/wireguard/internal/adapter.zig @@ -211,7 +211,7 @@ pub const WireGuardAdapter = struct { fn startBackend( self: *const WireGuardAdapter, allocator: std.mem.Allocator, - wg_config: []const u8, + wg_config: [:0]const u8, ) StartBackendError!i32 { log.write(.debug, "Start wg-go backend"); const handle = self.backend.turnOn(allocator, wg_config, .{ @@ -408,7 +408,7 @@ fn buildConfiguration( configuration: *const api.WireGuardConfiguration, endpoint_resolver: *PeerEndpointResolver, scope: WireGuardAdapter.ConfigurationScope, -) WireGuardAdapter.BuildConfigurationError![]u8 { +) WireGuardAdapter.BuildConfigurationError![:0]u8 { const resolved_endpoints = try endpoint_resolver.resolve( allocator, std.EnumSet(net.DNSResolver.Flag).initEmpty(), @@ -431,7 +431,7 @@ pub const testing = struct { allocator: std.mem.Allocator, configuration: *const api.WireGuardConfiguration, dns_resolver: net.DNSResolver, - ) WireGuardAdapter.BuildConfigurationError![]u8 { + ) WireGuardAdapter.BuildConfigurationError![:0]u8 { var endpoint_resolver = PeerEndpointResolver.init( configuration.peers, dns_resolver, diff --git a/zig/src/wireguard/internal/backend.zig b/zig/src/wireguard/internal/backend.zig index 0d4b14cc..a4197bb4 100644 --- a/zig/src/wireguard/internal/backend.zig +++ b/zig/src/wireguard/internal/backend.zig @@ -34,10 +34,10 @@ pub const Backend = struct { vtable: *const VTable, pub const VTable = struct { - turn_on: *const fn (?*anyopaque, std.mem.Allocator, []const u8, StartTunnel) Error!i32, + turn_on: *const fn (?*anyopaque, std.mem.Allocator, [:0]const u8, StartTunnel) Error!i32, turn_off: *const fn (?*anyopaque, i32) void, get_config: *const fn (?*anyopaque, std.mem.Allocator, i32) Error!?[]u8, - set_config: *const fn (?*anyopaque, std.mem.Allocator, i32, []const u8) Error!i64, + set_config: *const fn (?*anyopaque, std.mem.Allocator, i32, [:0]const u8) Error!i64, socket_descriptors: *const fn (?*anyopaque, std.mem.Allocator, i32) Error![]net.SocketDescriptor, bump_sockets: *const fn (?*anyopaque, i32, bool) void, disable_roaming: *const fn (?*anyopaque, i32) void, @@ -46,7 +46,7 @@ pub const Backend = struct { pub fn turnOn( self: Backend, allocator: std.mem.Allocator, - settings: []const u8, + settings: [:0]const u8, tunnel: StartTunnel, ) Error!i32 { return self.vtable.turn_on(self.ptr, allocator, settings, tunnel); @@ -68,7 +68,7 @@ pub const Backend = struct { self: Backend, allocator: std.mem.Allocator, handle: i32, - settings: []const u8, + settings: [:0]const u8, ) Error!i64 { return self.vtable.set_config(self.ptr, allocator, handle, settings); } @@ -107,16 +107,12 @@ const go_backend_vtable = Backend.VTable{ fn cTurnOn( _: ?*anyopaque, allocator: std.mem.Allocator, - settings: []const u8, + settings: [:0]const u8, tunnel: StartTunnel, ) Error!i32 { if (c.pp_wg_init() != 0) return error.BackendUnavailable; c.pp_wg_set_logger(cLog, null); - var c_settings: util.TemporaryCString = .{}; - try c_settings.init(allocator, settings); - defer c_settings.deinit(); - if (@import("builtin").os.tag == .windows) { // wireguard-go on Windows opens its own adapter by interface name; // Unix-family builds consume the already-created native TUN fd. @@ -124,11 +120,11 @@ fn cTurnOn( var c_ifname: util.TemporaryCString = .{}; try c_ifname.init(allocator, ifname); defer c_ifname.deinit(); - return c.pp_wg_turn_on(c_settings.ptr(), c_ifname.ptr()); + return c.pp_wg_turn_on(settings.ptr, c_ifname.ptr()); } const fd = tunnel.descriptor() orelse return error.CannotLocateTunnelFileDescriptor; - return c.pp_wg_turn_on(c_settings.ptr(), fd); + return c.pp_wg_turn_on(settings.ptr, fd); } fn cLog( @@ -137,8 +133,8 @@ fn cLog( message: [*c]const u8, ) callconv(.c) void { if (message == null) return; - const text = std.mem.trimEnd(u8, std.mem.span(message), "\r\n"); - log.write(if (level == 1) .err else .debug, text); + const message_z: [*:0]const u8 = @ptrCast(message); + log.writeCString(if (level == 1) .err else .debug, message_z); } fn cTurnOff(_: ?*anyopaque, handle: i32) void { @@ -157,14 +153,11 @@ fn cGetConfig( fn cSetConfig( _: ?*anyopaque, - allocator: std.mem.Allocator, + _: std.mem.Allocator, handle: i32, - settings: []const u8, + settings: [:0]const u8, ) Error!i64 { - var c_settings: util.TemporaryCString = .{}; - try c_settings.init(allocator, settings); - defer c_settings.deinit(); - return c.pp_wg_set_config(handle, c_settings.ptr()); + return c.pp_wg_set_config(handle, settings.ptr); } fn cSocketDescriptors( diff --git a/zig/src/wireguard/internal/uapi.zig b/zig/src/wireguard/internal/uapi.zig index b114a5c5..0f6bc387 100644 --- a/zig/src/wireguard/internal/uapi.zig +++ b/zig/src/wireguard/internal/uapi.zig @@ -15,7 +15,7 @@ pub fn buildConfiguration( allocator: std.mem.Allocator, configuration: *const api.WireGuardConfiguration, resolved_endpoints: []const resolver.ResolvedEndpoint, -) BuildConfigurationError![]u8 { +) BuildConfigurationError![:0]u8 { var aw: std.Io.Writer.Allocating = .init(allocator); errdefer aw.deinit(); const writer = &aw.writer; @@ -62,14 +62,14 @@ pub fn buildConfiguration( } } - return aw.toOwnedSlice(); + return aw.toOwnedSliceSentinel(0); } pub fn buildEndpointConfiguration( allocator: std.mem.Allocator, configuration: *const api.WireGuardConfiguration, resolved_endpoints: []const resolver.ResolvedEndpoint, -) BuildConfigurationError![]u8 { +) BuildConfigurationError![:0]u8 { var aw: std.Io.Writer.Allocating = .init(allocator); errdefer aw.deinit(); const writer = &aw.writer; @@ -86,7 +86,7 @@ pub fn buildEndpointConfiguration( try writer.print("endpoint={s}\n", .{endpoint_text}); } - return aw.toOwnedSlice(); + return aw.toOwnedSliceSentinel(0); } pub fn parseRuntimeDataCount(text: []const u8) ?api.DataCount { diff --git a/zig/tests/core/logging.zig b/zig/tests/core/logging.zig index a4a4bae3..6818db3b 100644 --- a/zig/tests/core/logging.zig +++ b/zig/tests/core/logging.zig @@ -84,6 +84,42 @@ test "external logger callback receives log messages" { try std.testing.expect(TestLogger.saw_message); } +test "sentinel log messages cross the C callback without copying" { + const TestLogger = struct { + var message_address: usize = 0; + + fn log(_: c_int, message: [*:0]const u8) callconv(.c) void { + message_address = @intFromPtr(message); + } + }; + + const message: [:0]const u8 = "borrowed"; + logging.init(false, TestLogger.log); + defer logging.deinit(); + + logging.write(.notice, message); + + try std.testing.expectEqual(@intFromPtr(message.ptr), TestLogger.message_address); +} + +test "C log messages are forwarded without scanning or copying" { + const TestLogger = struct { + var message_address: usize = 0; + + fn log(_: c_int, message: [*:0]const u8) callconv(.c) void { + message_address = @intFromPtr(message); + } + }; + + const message: [:0]const u8 = "borrowed"; + logging.init(false, TestLogger.log); + defer logging.deinit(); + + logging.writeCString(.notice, message.ptr); + + try std.testing.expectEqual(@intFromPtr(message.ptr), TestLogger.message_address); +} + test "duration helpers log compact time representations" { CapturingLogger.reset(); logging.init(false, CapturingLogger.log); diff --git a/zig/tests/core/util.zig b/zig/tests/core/util.zig index 0abcb5e9..c349161a 100644 --- a/zig/tests/core/util.zig +++ b/zig/tests/core/util.zig @@ -90,6 +90,29 @@ test "temporary C string reports fallback allocator failure" { try std.testing.expect(failing.has_induced_failure); } +test "passes sentinel slices to C callbacks without copying" { + const Callback = struct { + fn call(ctx: ?*anyopaque, value: [*c]const u8) callconv(.c) void { + const seen: *?[*c]const u8 = @ptrCast(@alignCast(ctx.?)); + seen.* = value; + } + }; + + const value: [:0]const u8 = "alpha"; + var seen: ?[*c]const u8 = null; + util.withCString(value, Callback.call, &seen); + + try std.testing.expectEqual(@intFromPtr(value.ptr), @intFromPtr(seen.?)); +} + +test "borrows C strings without dropping sentinel metadata" { + const value: [:0]const u8 = "alpha"; + const borrowed = util.borrowedCString(value.ptr); + + try std.testing.expectEqual(@intFromPtr(value.ptr), @intFromPtr(borrowed.ptr)); + try std.testing.expectEqual(@as(u8, 0), borrowed[borrowed.len]); +} + test "trims common ASCII whitespace" { try std.testing.expectEqualStrings("hello", util.trim(" \r\t\nhello \n\t")); try std.testing.expectEqualStrings("", util.trim(" \r\t\n")); diff --git a/zig/tests/net/platform_dns.zig b/zig/tests/net/platform_dns.zig index 0b3fdd42..2be8bfbc 100644 --- a/zig/tests/net/platform_dns.zig +++ b/zig/tests/net/platform_dns.zig @@ -31,7 +31,7 @@ test "DNS resolver times out and caps abandoned queries" { var release = std.atomic.Value(bool).init(false); fn resolve( - _: [*:0]const u8, + _: [:0]const u8, _: *const c.addrinfo, _: ?*const ReachabilityInfo, _: *[*c]c.addrinfo, diff --git a/zig/tests/wireguard/connection.zig b/zig/tests/wireguard/connection.zig index 00125f07..d7a6c7de 100644 --- a/zig/tests/wireguard/connection.zig +++ b/zig/tests/wireguard/connection.zig @@ -41,6 +41,7 @@ test "WireGuard connection builds UAPI configuration" { ); defer allocator.free(configuration_text); + try std.testing.expectEqual(@as(u8, 0), configuration_text[configuration_text.len]); try std.testing.expect(std.mem.indexOf(u8, configuration_text, "private_key=48ccbdcd1d0a520a98a99d297322f7b0998992636453c3c0e669ebf67877cd4b\n") != null); try std.testing.expect(std.mem.indexOf(u8, configuration_text, "listen_port=51820\n") != null); try std.testing.expect(std.mem.indexOf(u8, configuration_text, "replace_peers=true\n") != null); @@ -583,7 +584,7 @@ const fake_backend_vtable = backend_mod.Backend.VTable{ fn fakeTurnOn( ptr: ?*anyopaque, allocator: std.mem.Allocator, - settings: []const u8, + settings: [:0]const u8, _: backend_mod.StartTunnel, ) backend_mod.Error!i32 { const self: *FakeBackend = @ptrCast(@alignCast(ptr.?)); @@ -607,7 +608,7 @@ fn fakeGetConfig(_: ?*anyopaque, allocator: std.mem.Allocator, _: i32) backend_m ); } -fn fakeSetConfig(ptr: ?*anyopaque, allocator: std.mem.Allocator, _: i32, settings: []const u8) backend_mod.Error!i64 { +fn fakeSetConfig(ptr: ?*anyopaque, allocator: std.mem.Allocator, _: i32, settings: [:0]const u8) backend_mod.Error!i64 { const self: *FakeBackend = @ptrCast(@alignCast(ptr.?)); self.set_config_count += 1; if (self.last_set_config) |value| allocator.free(value); diff --git a/zig/tools/openapi_codegen.zig b/zig/tools/openapi_codegen.zig index df399cc4..b8675485 100644 --- a/zig/tools/openapi_codegen.zig +++ b/zig/tools/openapi_codegen.zig @@ -727,7 +727,7 @@ fn renderEnum(w: *std.Io.Writer, schema_item: Schema) WriterError!void { \\ return null; \\ } \\ - \\ pub fn raw(self: @This()) []const u8 { + \\ pub fn raw(self: @This()) [:0]const u8 { \\ return switch (self) { \\ );