Skip to content

Commit c3e6d3f

Browse files
authored
Merge pull request #19 from zamderax/fix/ipv4-loopback-byte-order
Fix IPv4Address.loopback byte order issue and modernize test suite
2 parents 4737651 + d46da6b commit c3e6d3f

File tree

3 files changed

+121
-60
lines changed

3 files changed

+121
-60
lines changed

Package.swift

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ var package = Package(
2525
.package(
2626
url: "https://github.com/apple/swift-system",
2727
from: "1.5.0"
28+
),
29+
.package(
30+
url: "https://github.com/apple/swift-log.git",
31+
from: "1.0.0"
2832
)
2933
],
3034
targets: [
@@ -43,7 +47,13 @@ var package = Package(
4347
),
4448
.testTarget(
4549
name: "SocketTests",
46-
dependencies: ["Socket"]
50+
dependencies: [
51+
"Socket",
52+
.product(
53+
name: "Logging",
54+
package: "swift-log"
55+
)
56+
]
4757
)
4858
]
4959
)

Sources/Socket/System/Constants.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ internal var _INADDR_ANY: CInterop.IPv4Address { CInterop.IPv4Address(s_addr: nu
174174
#endif
175175

176176
@_alwaysEmitIntoClient
177-
internal var _INADDR_LOOPBACK: CInterop.IPv4Address { CInterop.IPv4Address(s_addr: numericCast(INADDR_LOOPBACK)) }
177+
internal var _INADDR_LOOPBACK: CInterop.IPv4Address { CInterop.IPv4Address(s_addr: UInt32(networkOrder: numericCast(INADDR_LOOPBACK))) }
178178

179179
@_alwaysEmitIntoClient
180180
internal var _INADDR6_ANY: CInterop.IPv6Address { in6addr_any }

Tests/SocketTests/SocketTests.swift

Lines changed: 109 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,27 @@
11
import Foundation
2-
import XCTest
2+
import Testing
33
import SystemPackage
4+
import Logging
45
@testable import Socket
56

6-
final class SocketTests: XCTestCase {
7+
@Suite("Socket Tests")
8+
struct SocketTests {
9+
10+
static let logger = Logger(label: "logger") { label in
11+
var handler = StreamLogHandler.standardOutput(label: label)
12+
// handler.logLevel = .debug
13+
return handler
14+
}
715

816
#if os(Linux)
9-
func _testUnixSocket() async throws {
17+
@Test("Unix Socket Communication")
18+
func testUnixSocket() async throws {
1019
let address = UnixSocketAddress(path: FilePath("/tmp/testsocket.sock"))
11-
NSLog("Using path \(address.path.description)")
20+
Self.logger.info("Using path \(address.path.description)")
1221
let socketA = try await Socket(
1322
UnixProtocol.raw
1423
)
15-
NSLog("Created socket A")
24+
Self.logger.info("Created socket A")
1625
let option: GenericSocketOption.ReuseAddress = true
1726
try socketA.fileDescriptor.setSocketOption(option)
1827
do { try socketA.fileDescriptor.bind(address) }
@@ -22,57 +31,58 @@ final class SocketTests: XCTestCase {
2231
let socketB = try await Socket(
2332
UnixProtocol.raw
2433
)
25-
NSLog("Created socket B")
34+
Self.logger.info("Created socket B")
2635
try socketB.fileDescriptor.setSocketOption(option)
2736
try socketB.fileDescriptor.bind(address)
2837
defer { Task { await socketB.close() } }
2938

3039
let data = Data("Test \(UUID())".utf8)
3140

3241
try await socketA.write(data)
33-
NSLog("Socket A wrote data")
42+
Self.logger.info("Socket A wrote data")
3443
let read = try await socketB.read(data.count)
35-
NSLog("Socket B read data")
36-
XCTAssertEqual(data, read)
44+
Self.logger.info("Socket B read data")
45+
#expect(data == read)
3746
}
3847
#endif
3948

49+
@Test("IPv4 TCP Socket Communication")
4050
func testIPv4TCPSocket() async throws {
4151
let port = UInt16.random(in: 8080 ..< .max)
42-
print("Using port \(port)")
52+
Self.logger.info("Using port \(port)")
4353
let address = IPv4SocketAddress(address: .any, port: port)
4454
let data = Data("Test \(UUID())".utf8)
4555
let server = try await Socket(
4656
IPv4Protocol.tcp,
4757
bind: address
4858
)
4959
let newConnectionTask = Task {
50-
XCTAssertEqual(try server.fileDescriptor.address(IPv4SocketAddress.self), address)
51-
NSLog("Server: Created server socket \(server.fileDescriptor)")
60+
#expect(try server.fileDescriptor.address(IPv4SocketAddress.self) == address)
61+
Self.logger.info("Server: Created server socket \(server.fileDescriptor)")
5262
try await server.listen()
5363

54-
NSLog("Server: Waiting on incoming connection")
64+
Self.logger.info("Server: Waiting on incoming connection")
5565
let newConnection = try await server.accept()
56-
NSLog("Server: Got incoming connection \(newConnection.fileDescriptor)")
57-
XCTAssertEqual(try newConnection.fileDescriptor.address(IPv4SocketAddress.self).address.rawValue, "127.0.0.1")
66+
Self.logger.info("Server: Got incoming connection \(newConnection.fileDescriptor)")
67+
#expect(try newConnection.fileDescriptor.address(IPv4SocketAddress.self).address.rawValue == "127.0.0.1")
5868
let eventsTask = Task {
5969
var events = [Socket.Event]()
6070
for try await event in newConnection.event {
6171
events.append(event)
62-
NSLog("Server Connection: \(event)")
72+
Self.logger.info("Server Connection: \(event)")
6373
}
6474
return events
6575
}
6676
try await Task.sleep(nanoseconds: 10_000_000)
6777
let _ = try await newConnection.write(data)
68-
NSLog("Server: Wrote outgoing data")
78+
Self.logger.info("Server: Wrote outgoing data")
6979
return try await eventsTask.value
7080
}
7181
let serverEventsTask = Task {
7282
var events = [Socket.Event]()
7383
for try await event in server.event {
7484
events.append(event)
75-
NSLog("Server: \(event)")
85+
Self.logger.info("Server: \(event)")
7686
}
7787
return events
7888
}
@@ -84,39 +94,40 @@ final class SocketTests: XCTestCase {
8494
var events = [Socket.Event]()
8595
for try await event in client.event {
8696
events.append(event)
87-
NSLog("Client: \(event)")
97+
Self.logger.info("Client: \(event)")
8898
}
8999
return events
90100
}
91-
XCTAssertEqual(try client.fileDescriptor.address(IPv4SocketAddress.self).address, .any)
92-
NSLog("Client: Created client socket \(client.fileDescriptor)")
101+
#expect(try client.fileDescriptor.address(IPv4SocketAddress.self).address == .any)
102+
Self.logger.info("Client: Created client socket \(client.fileDescriptor)")
93103

94-
NSLog("Client: Will connect to server")
104+
Self.logger.info("Client: Will connect to server")
95105
do { try await client.connect(to: address) }
96106
catch Errno.socketIsConnected { }
97-
NSLog("Client: Connected to server")
98-
XCTAssertEqual(try client.fileDescriptor.address(IPv4SocketAddress.self).address.rawValue, "127.0.0.1")
99-
XCTAssertEqual(try client.fileDescriptor.peerAddress(IPv4SocketAddress.self).address.rawValue, "127.0.0.1")
107+
Self.logger.info("Client: Connected to server")
108+
#expect(try client.fileDescriptor.address(IPv4SocketAddress.self).address.rawValue == "127.0.0.1")
109+
#expect(try client.fileDescriptor.peerAddress(IPv4SocketAddress.self).address.rawValue == "127.0.0.1")
100110
let read = try await client.read(data.count)
101-
NSLog("Client: Read incoming data")
102-
XCTAssertEqual(data, read)
111+
Self.logger.info("Client: Read incoming data")
112+
#expect(data == read)
103113
try await Task.sleep(nanoseconds: 2_000_000_000)
104114
await client.close()
105115
let clientEvents = try await clientEventsTask.value
106-
XCTAssertEqual(clientEvents.count, 4)
107-
XCTAssertEqual("\(clientEvents)", "[Socket.Socket.Event.write, Socket.Socket.Event.read, Socket.Socket.Event.didRead(41), Socket.Socket.Event.close]")
116+
#expect(clientEvents.count == 4)
117+
#expect("\(clientEvents)" == "[Socket.Socket.Event.write, Socket.Socket.Event.read, Socket.Socket.Event.didRead(41), Socket.Socket.Event.close]")
108118
await server.close()
109119
let serverEvents = try await serverEventsTask.value
110-
XCTAssertEqual(serverEvents.count, 2)
111-
XCTAssertEqual("\(serverEvents)", "[Socket.Socket.Event.connection, Socket.Socket.Event.close]")
120+
#expect(serverEvents.count == 2)
121+
#expect("\(serverEvents)" == "[Socket.Socket.Event.connection, Socket.Socket.Event.close]")
112122
let newConnectionEvents = try await newConnectionTask.value
113-
XCTAssertEqual(newConnectionEvents.count, 5)
114-
XCTAssertEqual("\(newConnectionEvents)", "[Socket.Socket.Event.write, Socket.Socket.Event.didWrite(41), Socket.Socket.Event.write, Socket.Socket.Event.read, Socket.Socket.Event.close]")
123+
#expect(newConnectionEvents.count == 5)
124+
#expect("\(newConnectionEvents)" == "[Socket.Socket.Event.write, Socket.Socket.Event.didWrite(41), Socket.Socket.Event.write, Socket.Socket.Event.read, Socket.Socket.Event.close]")
115125
}
116126

127+
@Test("IPv4 UDP Socket Communication")
117128
func testIPv4UDPSocket() async throws {
118129
let port = UInt16.random(in: 8080 ..< .max)
119-
print("Using port \(port)")
130+
Self.logger.info("Using port \(port)")
120131
let address = IPv4SocketAddress(
121132
address: .any,
122133
port: port
@@ -129,78 +140,114 @@ final class SocketTests: XCTestCase {
129140
bind: address
130141
)
131142
defer { Task { await server.close() } }
132-
NSLog("Server: Created server socket \(server.fileDescriptor)")
143+
Self.logger.info("Server: Created server socket \(server.fileDescriptor)")
133144

134145
do {
135-
NSLog("Server: Waiting to receive incoming message")
146+
Self.logger.info("Server: Waiting to receive incoming message")
136147
let (read, clientAddress) = try await server.receiveMessage(data.count, fromAddressOf: type(of: address))
137-
NSLog("Server: Received incoming message")
138-
XCTAssertEqual(data, read)
148+
Self.logger.info("Server: Received incoming message")
149+
#expect(data == read)
139150

140-
NSLog("Server: Waiting to send outgoing message")
151+
Self.logger.info("Server: Waiting to send outgoing message")
141152
try await server.sendMessage(data, to: clientAddress)
142-
NSLog("Server: Sent outgoing message")
153+
Self.logger.info("Server: Sent outgoing message")
143154
} catch {
144-
print("Server:", error)
145-
XCTFail("\(error)")
155+
Self.logger.error("Server error: \(error)")
156+
Issue.record("Server error: \(error)")
146157
}
147158
}
148159

149160
let client = try await Socket(
150161
IPv4Protocol.udp
151162
)
152163
defer { Task { await client.close() } }
153-
NSLog("Client: Created client socket \(client.fileDescriptor)")
164+
Self.logger.info("Client: Created client socket \(client.fileDescriptor)")
154165

155-
NSLog("Client: Waiting to send outgoing message")
166+
Self.logger.info("Client: Waiting to send outgoing message")
156167
try await client.sendMessage(data, to: address)
157-
NSLog("Client: Sent outgoing message")
168+
Self.logger.info("Client: Sent outgoing message")
158169

159-
NSLog("Client: Waiting to receive incoming message")
170+
Self.logger.info("Client: Waiting to receive incoming message")
160171
let (read, _) = try await client.receiveMessage(data.count, fromAddressOf: type(of: address))
161-
NSLog("Client: Received incoming message")
162-
XCTAssertEqual(data, read)
172+
Self.logger.info("Client: Received incoming message")
173+
#expect(data == read)
163174
}
164175

176+
@Test("Network Interface IPv4 Enumeration")
165177
func testNetworkInterfaceIPv4() throws {
166178
let interfaces = try NetworkInterface<IPv4SocketAddress>.interfaces
167179
if !isRunningInCI {
168-
XCTAssert(interfaces.isEmpty == false)
180+
#expect(!interfaces.isEmpty)
169181
}
170182
for interface in interfaces {
171-
print("\(interface.id.index). \(interface.id.name)")
172-
print("\(interface.address.address) \(interface.address.port)")
183+
Self.logger.info("\(interface.id.index). \(interface.id.name)")
184+
Self.logger.info("\(interface.address.address) \(interface.address.port)")
173185
if let netmask = interface.netmask {
174-
print("\(netmask.address) \(netmask.port)")
186+
Self.logger.info("\(netmask.address) \(netmask.port)")
175187
}
176188
}
177189
}
178190

191+
@Test("Network Interface IPv6 Enumeration")
179192
func testNetworkInterfaceIPv6() throws {
180193
let interfaces = try NetworkInterface<IPv6SocketAddress>.interfaces
181194
if !isRunningInCI {
182-
XCTAssert(interfaces.isEmpty == false)
195+
#expect(!interfaces.isEmpty)
183196
}
184197
for interface in interfaces {
185-
print("\(interface.id.index). \(interface.id.name)")
186-
print("\(interface.address.address) \(interface.address.port)")
198+
Self.logger.info("\(interface.id.index). \(interface.id.name)")
199+
Self.logger.info("\(interface.address.address) \(interface.address.port)")
187200
if let netmask = interface.netmask {
188-
print("\(netmask.address) \(netmask.port)")
201+
Self.logger.info("\(netmask.address) \(netmask.port)")
189202
}
190203
}
191204
}
192205

193206
#if canImport(Darwin) || os(Linux)
207+
@Test("Network Interface Link Layer Enumeration")
194208
func testNetworkInterfaceLinkLayer() throws {
195209
let interfaces = try NetworkInterface<LinkLayerSocketAddress>.interfaces
196210
for interface in interfaces {
197-
print("\(interface.id.index). \(interface.id.name)")
198-
print(interface.address.address)
211+
Self.logger.info("\(interface.id.index). \(interface.id.name)")
212+
Self.logger.info("\(interface.address.address)")
199213
assert(interface.id.index == numericCast(interface.address.index))
200214
}
201215
}
202216
#endif
203217

218+
@Test("IPv4 Loopback Address Byte Order Fix", .tags(.bugfix))
219+
func testIPv4LoopbackAddress() async throws {
220+
// Test the loopback address byte order issue from GitHub issue #18
221+
let loopback = IPv4Address.loopback
222+
#expect(loopback.rawValue == "127.0.0.1", "IPv4Address.loopback should return '127.0.0.1', not '1.0.0.127'")
223+
224+
// Test that loopback is equivalent to manually constructed 127.0.0.1
225+
let manualLoopback = IPv4Address(127, 0, 0, 1)
226+
#expect(loopback == manualLoopback, "IPv4Address.loopback should equal manually constructed IPv4Address(127, 0, 0, 1)")
227+
228+
// Test that loopback is equivalent to string-constructed address
229+
let stringLoopback = IPv4Address(rawValue: "127.0.0.1")!
230+
#expect(loopback == stringLoopback, "IPv4Address.loopback should equal string-constructed address")
231+
232+
// Test that we can actually bind to loopback for TCP
233+
// This should not throw "Can't assign requested address" error
234+
let tcpAddress = IPv4SocketAddress(address: .loopback, port: 0)
235+
let tcpSocket = try await Socket(IPv4Protocol.tcp, bind: tcpAddress)
236+
defer { Task { await tcpSocket.close() } }
237+
238+
// Test that we can also bind to loopback for UDP
239+
let udpAddress = IPv4SocketAddress(address: .loopback, port: 0)
240+
let udpSocket = try await Socket(IPv4Protocol.udp, bind: udpAddress)
241+
defer { Task { await udpSocket.close() } }
242+
243+
// Verify the bound addresses are actually loopback
244+
let boundTcpAddress = try tcpSocket.fileDescriptor.address(IPv4SocketAddress.self)
245+
#expect(boundTcpAddress.address.rawValue == "127.0.0.1", "Bound TCP socket should be on loopback address")
246+
247+
let boundUdpAddress = try udpSocket.fileDescriptor.address(IPv4SocketAddress.self)
248+
#expect(boundUdpAddress.address.rawValue == "127.0.0.1", "Bound UDP socket should be on loopback address")
249+
}
250+
204251
func testTCPNoDelay() async throws {
205252
// Create a TCP socket
206253
let socket = try await Socket(IPv4Protocol.tcp)
@@ -294,3 +341,7 @@ var isRunningInCI: Bool {
294341
}
295342
return false
296343
}
344+
345+
extension Tag {
346+
@Tag static var bugfix: Self
347+
}

0 commit comments

Comments
 (0)