Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
216 commits
Select commit Hold shift + click to select a range
cf6f4bc
Refactor CLI into command files
JKamsker Feb 25, 2026
bd26c8a
Refactor Node internals via composition
JKamsker Feb 25, 2026
e30ee51
Refactor ZeroTierSockets sample into commands
JKamsker Feb 25, 2026
a1518de
Extract ZeroTierSocket persistence helpers
JKamsker Feb 25, 2026
9199dd9
Split network config client helpers
JKamsker Feb 25, 2026
ee3922f
Extract ZeroTierIpv4Link parsing helpers
JKamsker Feb 25, 2026
98730ab
Extract OS UDP peer discovery protocol
JKamsker Feb 25, 2026
6d7c15d
Deduplicate ZeroTier error formatting
JKamsker Feb 25, 2026
b7318d6
Extract EventLoop timer queue
JKamsker Feb 25, 2026
eec702a
Extract dataplane runtime bootstrap from ZeroTierSocket
JKamsker Feb 25, 2026
1a1dbad
Refactor UserSpaceTcpClient into sender/receiver components
JKamsker Feb 25, 2026
5c0e4b5
Refactor UserSpaceTcpServerConnection into shared components
JKamsker Feb 25, 2026
e6238c0
Extract dataplane route registry
JKamsker Feb 25, 2026
172a7e2
Extract dataplane root query client
JKamsker Feb 25, 2026
9c6b69a
Extract peer security from dataplane runtime
JKamsker Feb 25, 2026
63ae9f0
Extract dataplane packet handling
JKamsker Feb 25, 2026
7033d8d
Extract TCP connect helper from ZeroTierSocket
JKamsker Feb 25, 2026
caa5fb2
Split StoreAndNodeTests into focused test files
JKamsker Feb 25, 2026
2adba43
Extract direct endpoint manager from ZeroTierIpv4Link
JKamsker Feb 25, 2026
c84e76f
Extract ZeroTier network config test harness
JKamsker Feb 25, 2026
dab453f
Split UserSpaceTcpClient tests and helpers
JKamsker Feb 25, 2026
16a5805
Extract ZeroTierSocket factory
JKamsker Feb 25, 2026
32f5bfb
Extract OverlayTcpStream
JKamsker Feb 25, 2026
38faf02
Deduplicate overlay TCP framing codec
JKamsker Feb 25, 2026
57f5b93
Extract HELLO OK parsing helper
JKamsker Feb 25, 2026
42133b5
Deduplicate root key derivation
JKamsker Feb 25, 2026
3c200d5
Extract OS UDP socket factory
JKamsker Feb 25, 2026
9bf0833
Extract network config request metadata builder
JKamsker Feb 25, 2026
5e23ba5
Extract HELLO packet builder
JKamsker Feb 25, 2026
5b02981
Extract overlay TCP incoming buffer
JKamsker Feb 25, 2026
35e6907
Extract TCP RST sender from IP handler
JKamsker Feb 25, 2026
b66b1a0
Extract ZeroTierSocket runtime bootstrapper
JKamsker Feb 25, 2026
f722489
Extract OS UDP peer registry
JKamsker Feb 25, 2026
3ef74ed
Extract OS UDP receive loop
JKamsker Feb 25, 2026
6fcfda8
Extract ManagedSocket endpoint normalizer
JKamsker Feb 25, 2026
e8bd33c
Extract decrypting packet receiver
JKamsker Feb 25, 2026
40703aa
Extract EventLoop work queue
JKamsker Feb 25, 2026
53e9bee
Extract ZeroTierSocket bind and timeout helpers
JKamsker Feb 25, 2026
ccdd3cc
Extract WHOIS client from network config protocol
JKamsker Feb 25, 2026
7184996
Refactor ManagedSocket into TCP/UDP backends
JKamsker Feb 25, 2026
f179679
Use shared packet ID generator in dataplane runtime
JKamsker Feb 25, 2026
9e20713
Extract peer datagram processing from dataplane runtime
JKamsker Feb 25, 2026
a30e20f
Extract dataplane RX loops from runtime
JKamsker Feb 25, 2026
16e26d2
Use shared packet ID generator in IPv4 link
JKamsker Feb 25, 2026
60d0e86
Split IPv4 link into sender/receiver components
JKamsker Feb 25, 2026
c51f8c7
Extract AES-GMAC-SIV from packet crypto
JKamsker Feb 25, 2026
c30051a
Decompose UserSpaceTcpSender into helpers
JKamsker Feb 25, 2026
3c833c0
Extract owned overlay stream from HTTP handler
JKamsker Feb 25, 2026
d9b3e3f
Move OverlayHttpMessageHandlerOptions to separate file
JKamsker Feb 25, 2026
611e673
Centralize packet layout offsets in ZeroTierPacketHeader
JKamsker Feb 25, 2026
acdc8c0
Use ZeroTierPacketHeader offsets in packet crypto
JKamsker Feb 25, 2026
7aa61d1
Add NodeId.TryParse and reuse in overlay HTTP handler
JKamsker Feb 25, 2026
617b862
Reuse NodeId.TryParse in CLI parsing
JKamsker Feb 25, 2026
726cc52
Centralize ARP ether type in ZeroTierFrameCodec
JKamsker Feb 25, 2026
dc8652c
Use ZeroTierPacketHeader.IndexPayload in control protocols
JKamsker Feb 25, 2026
2c0a033
Use ZeroTierPacketHeader.IndexPayload for payload slicing
JKamsker Feb 25, 2026
31505d9
Remove ExposeCommand partial by extracting forwarder
JKamsker Feb 25, 2026
cf4490c
Delete obsolete ExposeCommand.Server partial
JKamsker Feb 25, 2026
49244e3
Remove ListenCommand partial by extracting HTTP server
JKamsker Feb 25, 2026
788c219
Delete obsolete ListenCommand.Server partial
JKamsker Feb 25, 2026
02052c1
CLI: centralize numeric argument parsing
JKamsker Feb 25, 2026
fcabf55
CLI: centralize temp state path
JKamsker Feb 25, 2026
908ca09
CLI: extract ConsoleCancellation helper
JKamsker Feb 25, 2026
c4b1fc5
Persisted IDs: parse invariantly
JKamsker Feb 25, 2026
1c09b89
NodePeerService: use unambiguous peers prefix
JKamsker Feb 25, 2026
4e62688
NodeNetworkService: list networks with slash prefix
JKamsker Feb 25, 2026
27f6c72
State keys: add peers directory prefix
JKamsker Feb 25, 2026
fa3b820
Samples: centralize default state path
JKamsker Feb 25, 2026
7f6e8ee
Samples: reuse port parsing helper
JKamsker Feb 25, 2026
7831441
Tests: centralize temp state paths
JKamsker Feb 25, 2026
1f7165b
Samples: make ConsoleCancellation disposable
JKamsker Feb 25, 2026
fe9686d
Root client: extract request send helper
JKamsker Feb 25, 2026
84da395
NetE2E sample: centralize temp state path
JKamsker Feb 25, 2026
41ca79e
ZeroTier: reuse shared packet id generator
JKamsker Feb 25, 2026
2c60f46
ZeroTier: centralize uint40 encoding
JKamsker Feb 25, 2026
3c98c1c
Protocol: reuse uint40 writer helper
JKamsker Feb 25, 2026
c649070
Protocol: centralize uint40 decoding
JKamsker Feb 25, 2026
9585991
StateStore: normalize ListAsync prefixes
JKamsker Feb 25, 2026
6d6337d
ZeroTier: reuse centralized error formatting
JKamsker Feb 25, 2026
763aec1
Protocol: reuse uint40 reader in multicast gather
JKamsker Feb 25, 2026
2f33a21
StateStore: centralize planet/roots aliases
JKamsker Feb 25, 2026
47f29f2
Whois: use packet payload index constant
JKamsker Feb 25, 2026
88e6eb1
Hello: remove magic indices and use header constants
JKamsker Feb 25, 2026
c30f4c8
Decrypting receiver: use IndexVerb constant
JKamsker Feb 25, 2026
efb50b0
StateStore: centralize key normalization
JKamsker Feb 25, 2026
40f33c1
Tests: extract StreamTestHelpers.ReadExactAsync
JKamsker Feb 25, 2026
afcf0f4
ZeroTierIdentity: reuse uint40 writer helper
JKamsker Feb 25, 2026
ca410cb
Tests: unify userspace TCP ReadExact helpers
JKamsker Feb 25, 2026
60251cf
StateStore: explicit segment validation
JKamsker Feb 25, 2026
3dc5d8c
Tests: dedupe userspace TCP ReadExact
JKamsker Feb 25, 2026
b3ba904
Protocol: centralize packet header indices
JKamsker Feb 25, 2026
17f5a80
OverlayTcpClient: collapse expected dispose exceptions
JKamsker Feb 25, 2026
9ccd786
EventLoop: collapse cancellation/dispose catches
JKamsker Feb 25, 2026
43379e0
UserSpaceTcp streams: collapse expected dispose exceptions
JKamsker Feb 25, 2026
c397f21
ZeroTierTrace: collapse expected write exceptions
JKamsker Feb 25, 2026
a4c23a7
OverlayTcpPortForwarder: collapse expected shutdown exceptions
JKamsker Feb 25, 2026
031ca8e
Tests: collapse cleanup exceptions in persistence tests
JKamsker Feb 25, 2026
7385928
Tests: collapse cleanup exceptions in world codec tests
JKamsker Feb 25, 2026
00a54ed
PlanetLoader: collapse decode exception catches
JKamsker Feb 25, 2026
e8e5e9b
Tests: make cancellation catches conditional
JKamsker Feb 25, 2026
af13874
UserSpaceTcpClient: simplify DisposeAsync catch blocks
JKamsker Feb 25, 2026
8681904
UserSpaceTcpServerConnection: simplify DisposeAsync catch blocks
JKamsker Feb 25, 2026
dff1b5c
Tests: avoid Uri host normalization for NodeId
JKamsker Feb 25, 2026
d40d640
UserSpaceTcpWindowUpdateTrigger: collapse send-ack catches
JKamsker Feb 25, 2026
3866099
ZeroTierUdpTransport: collapse expected exception catches
JKamsker Feb 25, 2026
eee257a
OsUdpSocketFactory: collapse expected exception catches
JKamsker Feb 25, 2026
258aac5
OsUdpNodeTransport: simplify expected exception catches
JKamsker Feb 25, 2026
67c8e89
DirectEndpointManager: collapse expected send exceptions
JKamsker Feb 25, 2026
3ffa8ef
DataplanePeerSecurity: collapse expected send exceptions
JKamsker Feb 25, 2026
825dad8
OverlayTcpPortForwarder: streamline connection cleanup
JKamsker Feb 25, 2026
644ae05
NodeNetworkService: collapse expected leave exceptions
JKamsker Feb 25, 2026
6914c7d
ZeroTierTcpListener: simplify shutdown/accept exception catches
JKamsker Feb 25, 2026
224d5b1
Cli: collapse stream copy cleanup catches
JKamsker Feb 25, 2026
2662e3b
Cli: collapse acceptor exception catches
JKamsker Feb 25, 2026
4f1747c
DataplaneRuntime: make dispose cancellation catches conditional
JKamsker Feb 25, 2026
4861466
SocketBindings: make UDP ephemeral bind intent explicit
JKamsker Feb 25, 2026
2a6aa6d
Cli: DRY UDP socket dispose cleanup
JKamsker Feb 25, 2026
74d4a80
Cli: DRY UDP send socket dispose cleanup
JKamsker Feb 25, 2026
0614ae4
Cli: simplify ExposePortForwarder exception handling
JKamsker Feb 25, 2026
cb00fab
Cli: DRY listener dispose cleanup
JKamsker Feb 25, 2026
79f8526
Cli: DRY ExposeCommand listener dispose cleanup
JKamsker Feb 25, 2026
c003267
Cli: remove redundant CTS dispose guard
JKamsker Feb 25, 2026
eac7745
Cli: remove redundant CTS dispose guard (Expose)
JKamsker Feb 25, 2026
10cdd1b
Cli: remove redundant bridge CTS dispose guard
JKamsker Feb 25, 2026
318b366
Cli: simplify ListenHttpServer stream disposal
JKamsker Feb 25, 2026
6c8b418
Cli: streamline ExposePortForwarder disposal
JKamsker Feb 25, 2026
031d883
Cli: let acceptor handle socket connect failures
JKamsker Feb 25, 2026
87f9edf
Samples: use await using for accepted TCP echo sockets
JKamsker Feb 25, 2026
78351dc
Cli: remove redundant UDP dispose exception handling
JKamsker Feb 25, 2026
24799d6
Cli: remove redundant UDP dispose helper
JKamsker Feb 25, 2026
e896775
Cli: remove redundant listener dispose catch
JKamsker Feb 25, 2026
b7413b3
Cli: remove redundant listener dispose helper (Expose)
JKamsker Feb 25, 2026
1230704
EventLoop: remove redundant ObjectDisposedException catch
JKamsker Feb 25, 2026
fba259f
OverlayTcpPortForwarder: make shutdown paths explicit
JKamsker Feb 25, 2026
fe2b2af
Cli: make UDP send error handling explicit
JKamsker Feb 25, 2026
e95ba34
OverlayTcpPortForwarder: make quiet dispose explicit
JKamsker Feb 25, 2026
192cc61
DataplaneRuntime: rely on transport disposal without extra catch
JKamsker Feb 25, 2026
a483d6c
ZeroTierUdpTransport: dedupe endpoint normalization
JKamsker Feb 25, 2026
b7ae912
ZeroTierUdpTransport: reuse OsUdpSocketFactory
JKamsker Feb 25, 2026
b52cea8
Transport: centralize UDP endpoint normalization
JKamsker Feb 25, 2026
563ed4a
Cli: use using for TcpClient in ExposePortForwarder
JKamsker Feb 25, 2026
af72b06
Cli: centralize node/managed IP output
JKamsker Feb 25, 2026
765fec2
Cli: reuse CliOutput for node-mode commands
JKamsker Feb 25, 2026
e219df9
Cli: print host-safe NodeId form
JKamsker Feb 25, 2026
4cfec6b
Samples: explicit socket disposal to satisfy CA2007
JKamsker Feb 25, 2026
6455579
NodeId: add ToHexString helper
JKamsker Feb 25, 2026
48ad72d
Use NodeId.ToHexString in identity output
JKamsker Feb 25, 2026
8cdef4e
Prefer NodeId.ToHexString for formatting
JKamsker Feb 25, 2026
bdc683e
OverlayTcpPortForwarder: encapsulate stream bridging
JKamsker Feb 25, 2026
1569b68
Cli: encapsulate duplex stream bridging
JKamsker Feb 25, 2026
275ca13
Cli: simplify HTTP header terminator search
JKamsker Feb 25, 2026
84b42ad
OverlayTcpPortForwarder: track active connections
JKamsker Feb 25, 2026
dd3cc95
ZeroTierTcpListener: track active connections
JKamsker Feb 25, 2026
80bcb73
Internal: share active task tracking helper
JKamsker Feb 25, 2026
f260484
ZeroTierTcpListener: reuse ZeroTierTimeouts for accept
JKamsker Feb 25, 2026
4a2615a
ZeroTierUdpSocket: reuse ZeroTierTimeouts for receive
JKamsker Feb 25, 2026
745bb69
ZeroTierUdpTransport: reuse ZeroTierTimeouts for receive
JKamsker Feb 25, 2026
32d1c6c
ZeroTierWhoisClient: use ZeroTierTimeouts
JKamsker Feb 25, 2026
b6e5730
ZeroTierMulticastGatherClient: use ZeroTierTimeouts
JKamsker Feb 25, 2026
8481eb0
ZeroTierNetworkConfigProtocol: use ZeroTierTimeouts
JKamsker Feb 25, 2026
7a6aa23
ZeroTierDataplaneRootClient: reuse ZeroTierTimeouts
JKamsker Feb 25, 2026
9c8d54c
ZeroTierHelloClient: use ZeroTierTimeouts
JKamsker Feb 25, 2026
a7b3dc5
Build: treat warnings as errors
JKamsker Feb 25, 2026
e66c919
fix bugs
JKamsker Feb 25, 2026
7ad060c
Add comprehensive plan for fixing correctness and security gaps in ma…
JKamsker Feb 25, 2026
c5beb5a
chore(docs): normalize Fixes1 plan formatting
JKamsker Feb 25, 2026
5ed8406
chore(docs): add checkboxes for phases 0-2
JKamsker Feb 25, 2026
a3acbd2
chore(docs): add checkboxes for phases 3-5
JKamsker Feb 25, 2026
d0a7916
chore(docs): add checkboxes for phases 6-7
JKamsker Feb 25, 2026
db46d60
chore(tasks): record phase 0 baseline
JKamsker Feb 25, 2026
c571908
chore(tasks): add fix-to-test tracking checklist
JKamsker Feb 25, 2026
43cbc25
fix(persistence): harden state store key normalization
JKamsker Feb 26, 2026
b2b1d80
fix(persistence): confine FileStateStore paths to root
JKamsker Feb 26, 2026
92fcccb
fix(persistence): implement planet/roots alias semantics
JKamsker Feb 26, 2026
230b595
fix(persistence): write state store files atomically
JKamsker Feb 26, 2026
065ca8f
fix(persistence): harden identity store IO
JKamsker Feb 26, 2026
11f2d88
fix(persistence): make network state persistence atomic
JKamsker Feb 26, 2026
7975ea3
fix(persistence): make atomic writes resilient
JKamsker Feb 26, 2026
5c57741
test(persistence): add state store hardening coverage
JKamsker Feb 26, 2026
aaf1db2
fix(tcp): stabilize ACK wait and handle wrap-around
JKamsker Feb 26, 2026
da4c787
fix(tcp): use Pipe receiver and trim out-of-order
JKamsker Feb 26, 2026
d98ed47
test(tcp): cover ACK timeout, overlap trim, and RST drain
JKamsker Feb 26, 2026
bb1550e
fix(tcp): validate checksum on receive
JKamsker Feb 26, 2026
2212b75
test(tcp): drop segments with invalid checksum
JKamsker Feb 26, 2026
4ec2d4c
fix(tcp): negotiate MSS from SYN options
JKamsker Feb 26, 2026
d3bf1a0
perf(dataplane): avoid UDP payload copies
JKamsker Feb 26, 2026
d97493b
fix(dataplane): bound queues and drop oldest
JKamsker Feb 26, 2026
198f33a
fix(dataplane): keep peer loop alive on faults
JKamsker Feb 26, 2026
ede797b
fix(http): resolve numeric node id hosts
JKamsker Feb 26, 2026
e95e09b
fix(dataplane): avoid WHOIS head-of-line blocking
JKamsker Feb 26, 2026
64a8acd
fix(dataplane): harden HELLO processing
JKamsker Feb 26, 2026
5aa4fa7
fix(dataplane): filter datagrams by root endpoint
JKamsker Feb 26, 2026
8e6ca8c
fix(dataplane): cache ResolveNodeId results
JKamsker Feb 26, 2026
4b60260
fix(dataplane): reject scoped IPv6 route keys
JKamsker Feb 26, 2026
dc61fff
chore(tasks): mark Phase 2 tests done
JKamsker Feb 26, 2026
ff5c3dd
test(dataplane): add phase 3 resilience tests
JKamsker Feb 26, 2026
5c59cd5
fix(managed): socket lifecycle semantics
JKamsker Feb 26, 2026
50f8be2
fix(listener): make dispose idempotent
JKamsker Feb 26, 2026
f2589a5
test(managed): add Phase 4 lifecycle coverage
JKamsker Feb 26, 2026
8b16833
fix(overlay): wait for connections on dispose
JKamsker Feb 26, 2026
2f98b73
fix(planet): harden planet loading and signatures
JKamsker Feb 26, 2026
ebb53e0
fix(network-config): cap config total length
JKamsker Feb 26, 2026
942d501
fix(crypto): reject invalid X25519 agreement
JKamsker Feb 26, 2026
5eca6cd
fix(protocol): clamp pushed direct paths
JKamsker Feb 26, 2026
b11c37e
fix(node): serialize operations with lifecycle
JKamsker Feb 26, 2026
c9a084f
fix(os-udp): survive subscriber callback faults
JKamsker Feb 26, 2026
31b65df
fix(os-udp): harden peer discovery parsing
JKamsker Feb 26, 2026
d723675
fix(overlay-tcp): bound queues and buffer early data
JKamsker Feb 26, 2026
b621d55
fix(ztudp): add v2 directed framing
JKamsker Feb 26, 2026
4b2db25
test(active-task-set): cover wait semantics
JKamsker Feb 26, 2026
f85f49f
fix(codec): validate decoded inputs
JKamsker Feb 26, 2026
1f24d46
fix(event-loop): cap cancellation tracking
JKamsker Feb 26, 2026
066ea78
docs: update persistence and udp v2 notes
JKamsker Feb 26, 2026
26c8c4f
chore(tasks): mark Phase 7 validation
JKamsker Feb 26, 2026
9570805
chore(tasks): track CI matrix status
JKamsker Feb 26, 2026
4023f75
chore(tasks): mark CI green
JKamsker Feb 26, 2026
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
5 changes: 5 additions & 0 deletions Directory.Build.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<Project>
<PropertyGroup>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
</Project>
28 changes: 28 additions & 0 deletions ZTSharp.Tests/ActiveTaskSetTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using ZTSharp.Internal;

namespace ZTSharp.Tests;

public sealed class ActiveTaskSetTests
{
[Fact]
public async Task WaitAsync_WaitsForTasksTrackedDuringWait()
{
var set = new ActiveTaskSet();

var first = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
var second = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);

set.Track(first.Task);
var waitTask = set.WaitAsync();

set.Track(second.Task);
first.TrySetResult(true);

await Task.Delay(10);
Assert.False(waitTask.IsCompleted);

second.TrySetResult(true);
await waitTask.WaitAsync(TimeSpan.FromSeconds(1));
}
}

55 changes: 55 additions & 0 deletions ZTSharp.Tests/CodecValidationTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
using System.Buffers.Binary;
using System.Net;

namespace ZTSharp.Tests;

public sealed class CodecValidationTests
{
[Fact]
public void NetworkAddressCodec_TryDecode_RejectsInvalidV4Prefix()
{
Span<byte> encoded = stackalloc byte[11];
encoded[0] = 1; // version
BinaryPrimitives.WriteInt32LittleEndian(encoded.Slice(1, 4), 1); // count
encoded[5] = 4; // tag v4
encoded[6] = 33; // invalid prefix
encoded[7] = 1;
encoded[8] = 2;
encoded[9] = 3;
encoded[10] = 4;

Assert.False(NetworkAddressCodec.TryDecode(encoded, out _));
}

[Fact]
public void NetworkAddressCodec_TryDecode_RejectsInvalidV6Prefix()
{
var encoded = new byte[31];
encoded[0] = 1; // version
BinaryPrimitives.WriteInt32LittleEndian(encoded.AsSpan(1, 4), 1); // count
encoded[5] = 6; // tag v6
encoded[6] = 129; // invalid prefix

var address = IPAddress.Parse("fd00::1").GetAddressBytes();
Assert.Equal(16, address.Length);
address.CopyTo(encoded.AsSpan(7, 16));

Assert.False(NetworkAddressCodec.TryDecode(encoded, out _));
}

[Fact]
public void PeerEndpointCodec_TryDecode_RejectsPortZero()
{
Span<byte> encoded = stackalloc byte[8];
encoded[0] = 1; // version
encoded[1] = 4; // tag v4
BinaryPrimitives.WriteUInt16BigEndian(encoded.Slice(2, 2), 0);
encoded[4] = 1;
encoded[5] = 2;
encoded[6] = 3;
encoded[7] = 4;

Assert.False(PeerEndpointCodec.TryDecode(encoded, out _));
}
}

35 changes: 6 additions & 29 deletions ZTSharp.Tests/ExternalZtNetTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,14 +61,14 @@ public async Task net_NetworkCreate_SpawnTwoClients_And_Communicate_E2E()

await using var node1 = new Node(new NodeOptions
{
StateRootPath = Path.Combine(Path.GetTempPath(), "zt-e2e-node-" + Guid.NewGuid()),
StateRootPath = TestTempPaths.CreateGuidSuffixed("zt-e2e-node-"),
StateStore = node1Store,
TransportMode = TransportMode.OsUdp
});

await using var node2 = new Node(new NodeOptions
{
StateRootPath = Path.Combine(Path.GetTempPath(), "zt-e2e-node-" + Guid.NewGuid()),
StateRootPath = TestTempPaths.CreateGuidSuffixed("zt-e2e-node-"),
StateStore = node2Store,
TransportMode = TransportMode.OsUdp
});
Expand All @@ -81,8 +81,8 @@ public async Task net_NetworkCreate_SpawnTwoClients_And_Communicate_E2E()

var node1Identity = await node1.GetIdentityAsync();
var node2Identity = await node2.GetIdentityAsync();
var node1Id = node1Identity.NodeId.Value.ToString("x10", CultureInfo.InvariantCulture);
var node2Id = node2Identity.NodeId.Value.ToString("x10", CultureInfo.InvariantCulture);
var node1Id = node1Identity.NodeId.ToHexString();
var node2Id = node2Identity.NodeId.ToHexString();

var node1Endpoint = node1.LocalTransportEndpoint;
var node2Endpoint = node2.LocalTransportEndpoint;
Expand Down Expand Up @@ -134,13 +134,13 @@ public async Task net_NetworkCreate_SpawnTwoClients_And_Communicate_E2E()

await clientStream.WriteAsync(ping);
var serverBuffer = new byte[ping.Length];
var serverRead = await ReadExactAsync(serverStream, serverBuffer, ping.Length, CancellationToken.None);
var serverRead = await StreamTestHelpers.ReadExactAsync(serverStream, serverBuffer, ping.Length, CancellationToken.None);
Assert.Equal(ping.Length, serverRead);
Assert.True(serverBuffer.AsSpan().SequenceEqual(ping));

await serverStream.WriteAsync(pong);
var clientBuffer = new byte[pong.Length];
var clientRead = await ReadExactAsync(clientStream, clientBuffer, pong.Length, CancellationToken.None);
var clientRead = await StreamTestHelpers.ReadExactAsync(clientStream, clientBuffer, pong.Length, CancellationToken.None);
Assert.Equal(pong.Length, clientRead);
Assert.True(clientBuffer.AsSpan().SequenceEqual(pong));
}
Expand All @@ -151,29 +151,6 @@ public async Task net_NetworkCreate_SpawnTwoClients_And_Communicate_E2E()
}
}

private static async Task<int> ReadExactAsync(
Stream stream,
byte[] buffer,
int length,
CancellationToken cancellationToken)
{
var readTotal = 0;
while (readTotal < length)
{
var read = await stream.ReadAsync(
buffer.AsMemory(readTotal, length - readTotal),
cancellationToken).ConfigureAwait(false);
if (read == 0)
{
return readTotal;
}

readTotal += read;
}

return readTotal;
}

private static string? ParseNetworkId(string output)
{
var match = Regex.Match(output, @"(?im)^[\s:]*nwid:\s*([0-9a-f]+)\s*$");
Expand Down
148 changes: 148 additions & 0 deletions ZTSharp.Tests/FileStateStoreSecurityTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
using System.IO;

namespace ZTSharp.Tests;

public sealed class FileStateStoreSecurityTests
{
[WindowsFact]
public async Task WriteAsync_Rejects_WindowsDriveRootedPaths()
{
var path = TestTempPaths.CreateGuidSuffixed("zt-store-sec-");
try
{
var store = new FileStateStore(path);

await Assert.ThrowsAsync<ArgumentException>(() => store.WriteAsync("C:/Windows/Temp/pwn", new byte[] { 1, 2, 3 }));

Assert.Empty(Directory.EnumerateFileSystemEntries(path, "*", SearchOption.AllDirectories));
}
finally
{
Directory.Delete(path, recursive: true);
}
}

[WindowsFact]
public async Task WriteAsync_Rejects_NtfsAds()
{
var path = TestTempPaths.CreateGuidSuffixed("zt-store-sec-");
try
{
var store = new FileStateStore(path);

await Assert.ThrowsAsync<ArgumentException>(() => store.WriteAsync("planet:ads", new byte[] { 1, 2, 3 }));

Assert.Empty(Directory.EnumerateFileSystemEntries(path, "*", SearchOption.AllDirectories));
}
finally
{
Directory.Delete(path, recursive: true);
}
}

[Fact]
public async Task WriteAsync_Rejects_RootedPaths()
{
var path = TestTempPaths.CreateGuidSuffixed("zt-store-sec-");
try
{
var store = new FileStateStore(path);

await Assert.ThrowsAsync<ArgumentException>(() => store.WriteAsync("/etc/passwd", new byte[] { 1, 2, 3 }));

Assert.Empty(Directory.EnumerateFileSystemEntries(path, "*", SearchOption.AllDirectories));
}
finally
{
Directory.Delete(path, recursive: true);
}
}

[Fact]
public async Task ReadAsync_PlanetFallsBackToRoots_AndRootListContainsBothAliases()
{
var path = TestTempPaths.CreateGuidSuffixed("zt-store-alias-");
try
{
var store = new FileStateStore(path);

File.WriteAllBytes(Path.Combine(path, "roots"), new byte[] { 1, 2, 3, 4 });

var readViaPlanet = await store.ReadAsync("planet");
var listed = await store.ListAsync();

Assert.NotNull(readViaPlanet);
Assert.True(readViaPlanet!.Value.Span.SequenceEqual(new byte[] { 1, 2, 3, 4 }));
Assert.Contains("planet", listed);
Assert.Contains("roots", listed);
}
finally
{
Directory.Delete(path, recursive: true);
}
}

[WindowsFact]
public async Task ListAsync_Rejects_WindowsDriveRootedPrefixes()
{
var path = TestTempPaths.CreateGuidSuffixed("zt-store-sec-");
try
{
var store = new FileStateStore(path);

await Assert.ThrowsAsync<ArgumentException>(() => store.ListAsync("C:/"));
}
finally
{
Directory.Delete(path, recursive: true);
}
}

[Fact]
public async Task WriteAsync_IsAtomicReplace_BestEffort()
{
var path = TestTempPaths.CreateGuidSuffixed("zt-store-atomic-");
try
{
var store = new FileStateStore(path);

var oldBytes = Enumerable.Repeat((byte)0xAA, 256 * 1024).ToArray();
var newBytes = Enumerable.Repeat((byte)0x55, 256 * 1024).ToArray();
await store.WriteAsync("foo", oldBytes);

using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
var writer = Task.Run(async () =>
{
for (var i = 0; i < 50; i++)
{
var bytes = i % 2 == 0 ? newBytes : oldBytes;
await store.WriteAsync("foo", bytes, cts.Token);
}
}, cts.Token);

var reader = Task.Run(async () =>
{
for (var i = 0; i < 250; i++)
{
var read = await store.ReadAsync("foo", cts.Token);
if (read is null)
{
continue;
}

var span = read.Value.Span;
var matchesOld = span.SequenceEqual(oldBytes);
var matchesNew = span.SequenceEqual(newBytes);
Assert.True(matchesOld || matchesNew);
}
}, cts.Token);

await Task.WhenAll(writer, reader);
}
finally
{
Directory.Delete(path, recursive: true);
}
}
}

3 changes: 1 addition & 2 deletions ZTSharp.Tests/NetworkAddressTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public async Task NetworkAddresses_RoundTrip()

await using var node = new Node(new NodeOptions
{
StateRootPath = Path.Combine(Path.GetTempPath(), "zt-node-" + Guid.NewGuid()),
StateRootPath = TestTempPaths.CreateGuidSuffixed("zt-node-"),
StateStore = new MemoryStateStore()
});

Expand All @@ -32,4 +32,3 @@ public async Task NetworkAddresses_RoundTrip()
Assert.Equal(expected[1], actual[1]);
}
}

Loading