Skip to content
Open
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
30 changes: 0 additions & 30 deletions SRTHaishinKit/Sources/Extension/sockaddr_in+Extension.swift

This file was deleted.

45 changes: 30 additions & 15 deletions SRTHaishinKit/Sources/SRT/SRTSocket.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ final actor SRTSocket {

enum Error: Swift.Error {
case notConnected
case invalidArgument(_ message: String)
case rejected(_ reason: SRTRejectReason)
case illegalState(_ message: String)
}
Expand Down Expand Up @@ -128,28 +129,42 @@ final actor SRTSocket {
let status: Int32 = try {
switch url.mode {
case .caller:
guard var remote = url.remote else {
return SRT_ERROR
guard let remote = url.remote else {
throw Error.invalidArgument("missing remote url")
}
return try remote.resolve(AI_ADDRCONFIG) { name, length in
srt_connect(socket, name, length)
}
var remoteaddr = remote.makeSockaddr()
return srt_connect(socket, &remoteaddr, Int32(remote.size))
case .listener:
guard var local = url.local else {
return SRT_ERROR
guard let local = url.local else {
throw Error.invalidArgument("missing local url")
}
var localaddr = local.makeSockaddr()
let status = srt_bind(socket, &localaddr, Int32(local.size))
guard status != SRT_ERROR else {
throw makeSocketError()
let _: Int32 = try local.resolve(AI_PASSIVE) { name, length in
let status = srt_bind(socket, name, length)
if status == SRT_ERROR {
return nil
} else {
return status
}
}
return srt_listen(socket, 1)
case .rendezvous:
guard var remote = url.remote, var local = url.local else {
return SRT_ERROR
guard let remote = url.remote else {
throw Error.invalidArgument("missing remote url")
}
guard let local = url.local else {
throw Error.invalidArgument("missing local url")
}
return try remote.resolve(AI_PASSIVE | AI_ADDRCONFIG) { remotename, remotelen in
Copy link

Copilot AI Nov 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using AI_PASSIVE flag for remote address resolution in rendezvous mode is incorrect. AI_PASSIVE is intended for addresses that will be used with bind(), not connect() or rendezvous connections. The remote address should use AI_ADDRCONFIG only, while only the local address should use AI_PASSIVE.

Suggested change
return try remote.resolve(AI_PASSIVE | AI_ADDRCONFIG) { remotename, remotelen in
return try remote.resolve(AI_ADDRCONFIG) { remotename, remotelen in

Copilot uses AI. Check for mistakes.
return try local.resolve(AI_PASSIVE | AI_ADDRCONFIG) { localname, locallen in
let status = srt_rendezvous(socket, localname, locallen, remotename, remotelen)
Comment on lines +159 to +160
Copy link

Copilot AI Nov 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The parameters to srt_rendezvous are in the wrong order. According to SRT API documentation, the function signature should have local address parameters first, then remote address parameters. This line passes them as (socket, local, local_len, remote, remote_len) but based on the old code structure and typical SRT API patterns, verify the correct parameter order.

Copilot uses AI. Check for mistakes.
if status == SRT_ERROR {
return nil
} else {
return status
}
}
}
var remoteaddr = remote.makeSockaddr()
var localaddr = local.makeSockaddr()
return srt_rendezvous(socket, &remoteaddr, Int32(remote.size), &localaddr, Int32(local.size))
}
}()
guard status != SRT_ERROR else {
Expand Down
10 changes: 5 additions & 5 deletions SRTHaishinKit/Sources/SRT/SRTSocketURL.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,20 +27,20 @@ struct SRTSocketURL {
let mode: SRTMode
let options: [SRTSocketOption]

var remote: sockaddr_in? {
var remote: AddrInfo? {
guard let host = url.host else {
return nil
}
return .init(host, port: url.port ?? Self.defaultPort)
return AddrInfo(host: host, port: url.port ?? Self.defaultPort)
}

var local: sockaddr_in? {
var local: AddrInfo? {
let queryItems = Self.getQueryItems(url)
let adapter = queryItems["adapter"] ?? "0.0.0.0"
if let port = queryItems["port"] {
return .init(adapter, port: Int(port) ?? url.port ?? Self.defaultPort)
return AddrInfo(host: adapter, port: Int(port) ?? url.port ?? Self.defaultPort)
}
return .init(adapter, port: url.port ?? Self.defaultPort)
return AddrInfo(host: adapter, port: url.port ?? Self.defaultPort)
}

init?(_ url: URL?) {
Expand Down
56 changes: 56 additions & 0 deletions SRTHaishinKit/Sources/Util/AddrInfo.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import Foundation
import libsrt

struct AddrInfo {
enum Error: Swift.Error {
case failedToGetaddrinfo(_ code: Int)
case failedToResolve
}

let host: String
let port: Int

@discardableResult
func resolve<R>(_ flags: Int32, lambda: (UnsafePointer<sockaddr>, Int32) throws -> R?) throws -> R {
var hints = addrinfo(
ai_flags: flags,
ai_family: AF_UNSPEC,
Comment on lines +13 to +17
Copy link

Copilot AI Nov 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Setting ai_family to AF_UNSPEC allows both IPv4 and IPv6, but the flags parameter doesn't validate which protocols are actually requested. Consider documenting this behavior or adding a parameter to allow callers to specify protocol preferences (e.g., IPv4-only, IPv6-only, or both).

Suggested change
@discardableResult
func resolve<R>(_ flags: Int32, lambda: (UnsafePointer<sockaddr>, Int32) throws -> R?) throws -> R {
var hints = addrinfo(
ai_flags: flags,
ai_family: AF_UNSPEC,
/**
Resolves the host and port to a socket address.
- Parameters:
- flags: Flags for address resolution.
- family: Protocol family to use (AF_INET for IPv4, AF_INET6 for IPv6, AF_UNSPEC for both). Defaults to AF_UNSPEC.
- lambda: Closure to process each resolved address.
- Returns: The result of the closure for the first matching address.
- Throws: Error.failedToGetaddrinfo or Error.failedToResolve.
*/
@discardableResult
func resolve<R>(_ flags: Int32, family: Int32 = AF_UNSPEC, lambda: (UnsafePointer<sockaddr>, Int32) throws -> R?) throws -> R {
var hints = addrinfo(
ai_flags: flags,
ai_family: family,

Copilot uses AI. Check for mistakes.
ai_socktype: SOCK_DGRAM,
ai_protocol: 0,
ai_addrlen: 0,
ai_canonname: nil,
ai_addr: nil,
ai_next: nil
)
var result: UnsafeMutablePointer<addrinfo>?
let rv = getaddrinfo(host, String(port), &hints, &result)
guard rv == 0 else {
throw Error.failedToGetaddrinfo(Int(rv))
}
defer {
freeaddrinfo(result)
}
var addr = sockaddr_storage()
var rp = result
while rp != nil {
if let ai = rp?.pointee {
memcpy(&addr, ai.ai_addr, Int(ai.ai_addrlen))
let result = withUnsafePointer(to: &addr) {
$0.withMemoryRebound(to: sockaddr.self, capacity: 1) {
do {
return try lambda($0, Int32(ai.ai_addrlen))
} catch {
print("AddrInfo.resolve: lambda threw error for address \(host):\(port): \(error)")
return nil
}
}
}
if let result {
return result
}
}
rp = rp?.pointee.ai_next
}
throw Error.failedToResolve
}
}
Loading