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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -135,3 +135,19 @@ jobs:
run: swift sdk install https://github.com/finagolfin/swift-android-sdk/releases/download/6.0.3/swift-6.0.3-RELEASE-android-24-0.1.artifactbundle.tar.gz --checksum 4566f23ae2d36dc5c02e915cd67d83b2af971faca4b2595fdd75cf0286acfac1
- name: Build
run: swift build --swift-sdk aarch64-unknown-linux-android24

windows_swift_6_0:
runs-on: windows-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install Swift
uses: SwiftyLab/setup-swift@latest
with:
swift-version: "6.0.3"
- name: Version
run: swift --version
- name: Build
run: swift build --build-tests
- name: Test
run: swift test --skip-build
8 changes: 8 additions & 0 deletions FlyingFox/Sources/HTTPServer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,15 @@ public final actor HTTPServer {

func makeSocketAndListen() throws -> Socket {
let socket = try Socket(domain: Int32(type(of: config.address).family))

#if canImport(WinSDK)
if config.address.family != AF_UNIX {
try socket.setValue(true, for: .exclusiveLocalAddressReuse)
}
#else
try socket.setValue(true, for: .localAddressReuse)
#endif

#if canImport(Darwin)
try socket.setValue(true, for: .noSIGPIPE)
#endif
Expand Down
11 changes: 8 additions & 3 deletions FlyingFox/Tests/HTTPServerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -340,8 +340,10 @@ actor HTTPServerTests {

@Test
func server_StartsOnUnixSocket() async throws {
let address = sockaddr_un.unix(path: #function)
let tempFile = FileManager.default.temporaryDirectory.appendingPathComponent("\(UUID().uuidString.prefix(8)).sock")
let address = sockaddr_un.unix(path: tempFile.path)
try? Socket.unlink(address)
defer { try? Socket.unlink(address) }
let server = HTTPServer.make(address: address)
await server.appendRoute("*") { _ in
return HTTPResponse.make(statusCode: .accepted)
Expand Down Expand Up @@ -375,6 +377,8 @@ actor HTTPServerTests {
)
}

#if !canImport(WinSDK)
// FIXME: This test fails non-deterministically on Windows
@Test
func server_AllowsExistingConnectionsToDisconnect_WhenStopped() async throws {
let server = HTTPServer.make()
Expand All @@ -390,11 +394,12 @@ actor HTTPServerTests {
try await Task.sleep(seconds: 0.1)
let taskStop = Task { await server.stop(timeout: 1) }

#expect(
try await socket.readResponse().statusCode == .ok
try await #expect(
socket.readResponse().statusCode == .ok
)
await taskStop.value
}
#endif

@Test
func server_DisconnectsWaitingRequests_WhenStopped() async throws {
Expand Down
5 changes: 2 additions & 3 deletions FlyingSocks/Sources/Socket+WinSock2.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ let F_SETFL = Int32(1)
let F_GETFL = Int32(1)
var errno: Int32 { WSAGetLastError() }
let EWOULDBLOCK = WSAEWOULDBLOCK
let EBADF = WSAENOTSOCK
let EINPROGRESS = WSAEINPROGRESS
let EISCONN = WSAEISCONN
public typealias sa_family_t = ADDRESS_FAMILY
Expand All @@ -63,6 +62,7 @@ extension Socket {
static let ipproto_ipv6 = Int32(IPPROTO_IPV6.rawValue)
static let ip_pktinfo = Int32(IP_PKTINFO)
static let ipv6_pktinfo = Int32(IPV6_PKTINFO)
static let ipv6_recvpktinfo = Int32(IPV6_PKTINFO)

static func makeAddressINET(port: UInt16) -> WinSDK.sockaddr_in {
WinSDK.sockaddr_in(
Expand Down Expand Up @@ -107,8 +107,7 @@ extension Socket {
}

static func fcntl(_ fd: FileDescriptorType, _ cmd: Int32) -> Int32 {
guard fd != INVALID_SOCKET else { return -1 }
return 0
return -1
}

static func fcntl(_ fd: FileDescriptorType, _ cmd: Int32, _ value: Int32) -> Int32 {
Expand Down
26 changes: 18 additions & 8 deletions FlyingSocks/Sources/Socket.swift
Original file line number Diff line number Diff line change
Expand Up @@ -126,10 +126,8 @@ public struct Socket: Sendable, Hashable {
switch domain {
case AF_INET:
try setValue(true, for: .packetInfoIP)
#if !canImport(WinSDK)
case AF_INET6:
try setValue(true, for: .packetInfoIPv6)
#endif
default:
return
}
Expand Down Expand Up @@ -221,7 +219,7 @@ public struct Socket: Sendable, Hashable {
Socket.connect(file.rawValue, $0, address.size)
}
guard result >= 0 || errno == EISCONN else {
if errno == EINPROGRESS {
if errno == EINPROGRESS || errno == EWOULDBLOCK {
throw SocketError.blocked
} else {
throw SocketError.makeFailed("Connect")
Expand All @@ -248,7 +246,7 @@ public struct Socket: Sendable, Hashable {
guard count > 0 else {
if errno == EWOULDBLOCK {
throw SocketError.blocked
} else if errno == EBADF || count == 0 {
} else if errnoSignalsDisconnected() || count == 0 {
throw SocketError.disconnected
} else {
throw SocketError.makeFailed("Read")
Expand All @@ -275,7 +273,7 @@ public struct Socket: Sendable, Hashable {
guard count > 0 else {
if errno == EWOULDBLOCK {
throw SocketError.blocked
} else if errno == EBADF || count == 0 {
} else if errnoSignalsDisconnected() || count == 0 {
throw SocketError.disconnected
} else {
throw SocketError.makeFailed("RecvFrom")
Expand Down Expand Up @@ -340,7 +338,7 @@ public struct Socket: Sendable, Hashable {
guard count > 0 else {
if errno == EWOULDBLOCK || errno == EAGAIN {
throw SocketError.blocked
} else if errno == EBADF || count == 0 {
} else if errnoSignalsDisconnected() || count == 0 {
throw SocketError.disconnected
} else {
throw SocketError.makeFailed("RecvMsg")
Expand All @@ -365,7 +363,7 @@ public struct Socket: Sendable, Hashable {
guard sent > 0 else {
if errno == EWOULDBLOCK {
throw SocketError.blocked
} else if errno == EBADF {
} else if errnoSignalsDisconnected() {
throw SocketError.disconnected
} else {
throw SocketError.makeFailed("Write")
Expand Down Expand Up @@ -568,10 +566,14 @@ public extension SocketOption where Self == BoolSocketOption {
BoolSocketOption(level: Socket.ipproto_ip, name: Socket.ip_pktinfo)
}

#if !canImport(WinSDK)
static var packetInfoIPv6: Self {
BoolSocketOption(level: Socket.ipproto_ipv6, name: Socket.ipv6_recvpktinfo)
}

#if canImport(WinSDK)
static var exclusiveLocalAddressReuse: Self {
BoolSocketOption(name: ~SO_REUSEADDR) // SO_EXCLUSIVEADDRUSE macro
}
#endif

#if canImport(Darwin)
Expand Down Expand Up @@ -627,6 +629,14 @@ package extension Socket {
}
}

fileprivate func errnoSignalsDisconnected() -> Bool {
#if canImport(WinSDK)
return errno == WSAENOTSOCK || errno == WSAENOTCONN || errno == WSAECONNRESET
#else
return errno == EBADF
#endif
}

#if !canImport(WinSDK)
fileprivate extension Socket {
// https://github.com/swiftlang/swift-evolution/blob/main/proposals/0138-unsaferawbufferpointer.md
Expand Down
6 changes: 4 additions & 2 deletions FlyingSocks/Sources/SocketPool+Poll.swift
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,9 @@ extension EventNotification {
let revents = POLLEvents(poll.revents)
let errors = Set<EventNotification.Error>.make(from: revents)

if events.contains(.write) && !errors.isEmpty {
let shouldSendErrors = !errors.isEmpty && (events.contains(.write) || (events.contains(.read) && !revents.intersects(with: .read)))

if shouldSendErrors {
return EventNotification(
file: .init(rawValue: poll.fd),
events: .init(events),
Expand Down Expand Up @@ -198,7 +200,7 @@ private extension Socket.Events {

init(_ events: POLLEvents) {
self = []
if events.contains(.read) {
if events.intersects(with: .read) {
self.insert(.read)
}
if events.contains(.write) {
Expand Down
11 changes: 9 additions & 2 deletions FlyingSocks/Tests/AsyncBufferedFileSequenceTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,16 @@ struct AsyncBufferedFileSequenceTests {

@Test
func fileSize() async throws {
#expect(
try AsyncBufferedFileSequence.fileSize(at: .jackOfHeartsRecital) == 299
#if os(Windows)
try #expect(
AsyncBufferedFileSequence.fileSize(at: .jackOfHeartsRecital) == 304
)
#else
try #expect(
AsyncBufferedFileSequence.fileSize(at: .jackOfHeartsRecital) == 299
)
#endif

#expect(throws: (any Error).self) {
try AsyncBufferedFileSequence.fileSize(at: URL(fileURLWithPath: "missing"))
}
Expand Down
25 changes: 20 additions & 5 deletions FlyingSocks/Tests/AsyncSocketTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -188,25 +188,34 @@ struct AsyncSocketTests {
)
}

#if canImport(WinSDK)
@Test
func datagramPairCreation_Throws() async throws {
await #expect(throws: SocketError.self) {
_ = try await AsyncSocket.makeDatagramPair()
}
}
#else
@Test
func datagramSocketReceivesChunk_WhenAvailable() async throws {
let (s1, s2, addr) = try await AsyncSocket.makeDatagramPair()

async let d2: (any SocketAddress, [UInt8]) = s2.receive(atMost: 100)
// TODO: calling send() on Darwin to an unconnected datagram domain
// socket returns EISCONN
#if canImport(Darwin)
#if canImport(Darwin)
try await s1.write("Swift".data(using: .utf8)!)
#else
#else
try await s1.send("Swift".data(using: .utf8)!, to: addr)
#endif
#endif
let v2 = try await d2
#expect(String(data: Data(v2.1), encoding: .utf8) == "Swift")

try s1.close()
try s2.close()
try? Socket.unlink(addr)
}
#endif

#if !canImport(WinSDK)
#if canImport(Darwin)
Expand Down Expand Up @@ -305,10 +314,16 @@ extension AsyncSocket {
}

static func makeListening(pool: some AsyncSocketPool) throws -> AsyncSocket {
let address = sockaddr_un.unix(path: #function)
let tempFile = FileManager.default.temporaryDirectory.appendingPathComponent("\(UUID().uuidString.prefix(8)).sock")
let address = sockaddr_un.unix(path: tempFile.path)
try? Socket.unlink(address)
defer { try? Socket.unlink(address) }
let socket = try Socket(domain: AF_UNIX, type: .stream)
try socket.setValue(true, for: .localAddressReuse)

#if !canImport(WinSDK)
try socket.setValue(true, for: .localAddressReuse)
#endif

try socket.bind(to: address)
try socket.listen()
return try AsyncSocket(socket: socket, pool: pool)
Expand Down
19 changes: 17 additions & 2 deletions FlyingSocks/Tests/SocketTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,11 @@ struct SocketTests {
}
}

#if !canImport(WinSDK)
// Not a good test on Windows, unfortunately:
// https://groups.google.com/g/microsoft.public.win32.programmer.networks/c/rBg0a8oERGQ/m/AvVOd-BIHhMJ
// "If necessary, Winsock can buffer significantly more than the SO_SNDBUF buffer size."

@Test
func socketWrite_ThrowsBlocked_WhenBufferIsFull() throws {
let (s1, s2) = try Socket.makeNonBlockingPair()
Expand All @@ -145,6 +150,7 @@ struct SocketTests {
try s1.close()
try s2.close()
}
#endif

@Test
func socketWrite_Throws_WhenSocketIsNotConnected() async throws {
Expand Down Expand Up @@ -193,6 +199,16 @@ struct SocketTests {
#expect(try socket.getValue(for: .localAddressReuse) == false)
}

#if canImport(WinSDK)
// Windows only supports setting O_NONBLOCK, and currently can't retrieve whether it's been set :)
@Test
func socket_Throws_On_Get_Flags() throws {
let socket = try Socket(domain: AF_UNIX, type: .stream)

try socket.setFlags(.append) // this is "OK", but actually won't set the flag
#expect(throws: SocketError.self) { try socket.flags.contains(.append) }
}
#else
@Test
func socket_Sets_And_Gets_Flags() throws {
let socket = try Socket(domain: AF_UNIX, type: .stream)
Expand All @@ -201,6 +217,7 @@ struct SocketTests {
try socket.setFlags(.append)
#expect(try socket.flags.contains(.append))
}
#endif

@Test
func socketAccept_ThrowsError_WhenInvalid() {
Expand Down Expand Up @@ -331,7 +348,6 @@ struct SocketTests {
)
}

#if !canImport(WinSDK)
@Test
func makes_datagram_ip6() throws {
let socket = try Socket(domain: Int32(sa_family_t(AF_INET6)), type: .datagram)
Expand All @@ -340,7 +356,6 @@ struct SocketTests {
try socket.getValue(for: .packetInfoIPv6) == true
)
}
#endif
}

extension Socket.Flags {
Expand Down
Loading