diff --git a/Package.swift b/Package.swift index f8a01bc..a185218 100644 --- a/Package.swift +++ b/Package.swift @@ -21,15 +21,10 @@ do { let package = Package( name: "swift-async-dns-resolver", - platforms: [ - .macOS("13.0"), - .iOS(.v13), - ], products: [ .library(name: "AsyncDNSResolver", targets: ["AsyncDNSResolver"]), ], dependencies: [ - .package(url: "https://github.com/apple/swift-nio", .upToNextMajor(from: "2.53.0")), .package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0"), ], targets: [ @@ -49,7 +44,6 @@ let package = Package( name: "AsyncDNSResolver", dependencies: [ "CAsyncDNSResolver", - .product(name: "NIOCore", package: "swift-nio"), ] ), diff --git a/Sources/AsyncDNSResolver/AsyncDNSResolver.swift b/Sources/AsyncDNSResolver/AsyncDNSResolver.swift index 60cbc7e..fb340e4 100644 --- a/Sources/AsyncDNSResolver/AsyncDNSResolver.swift +++ b/Sources/AsyncDNSResolver/AsyncDNSResolver.swift @@ -2,7 +2,7 @@ // // This source file is part of the SwiftAsyncDNSResolver open source project // -// Copyright (c) 2020-2023 Apple Inc. and the SwiftAsyncDNSResolver project authors +// Copyright (c) 2020-2024 Apple Inc. and the SwiftAsyncDNSResolver project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -15,6 +15,7 @@ // MARK: - Async DNS resolver API /// `AsyncDNSResolver` provides API for running asynchronous DNS queries. +@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) public struct AsyncDNSResolver { let underlying: DNSResolver @@ -184,21 +185,39 @@ enum QueryType { // MARK: - Query reply types public enum IPAddress: Sendable, Equatable, CustomStringConvertible { - case IPv4(String) - case IPv6(String) + case ipv4(IPv4) + case ipv6(IPv6) public var description: String { switch self { - case .IPv4(let address): - return address - case .IPv6(let address): - return address + case .ipv4(let address): + return String(describing: address) + case .ipv6(let address): + return String(describing: address) + } + } + + public struct IPv4: Sendable, Hashable, CustomStringConvertible { + public var address: String + public var description: String { self.address } + + public init(address: String) { + self.address = address + } + } + + public struct IPv6: Sendable, Hashable, CustomStringConvertible { + public var address: String + public var description: String { self.address } + + public init(address: String) { + self.address = address } } } public struct ARecord: Sendable, Equatable, CustomStringConvertible { - public let address: IPAddress + public let address: IPAddress.IPv4 public let ttl: Int32? public var description: String { @@ -207,7 +226,7 @@ public struct ARecord: Sendable, Equatable, CustomStringConvertible { } public struct AAAARecord: Sendable, Equatable, CustomStringConvertible { - public let address: IPAddress + public let address: IPAddress.IPv6 public let ttl: Int32? public var description: String { diff --git a/Sources/AsyncDNSResolver/Errors.swift b/Sources/AsyncDNSResolver/Errors.swift index ef4970d..7d1d930 100644 --- a/Sources/AsyncDNSResolver/Errors.swift +++ b/Sources/AsyncDNSResolver/Errors.swift @@ -2,7 +2,7 @@ // // This source file is part of the SwiftAsyncDNSResolver open source project // -// Copyright (c) 2020-2023 Apple Inc. and the SwiftAsyncDNSResolver project authors +// Copyright (c) 2020-2024 Apple Inc. and the SwiftAsyncDNSResolver project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -12,6 +12,7 @@ // //===----------------------------------------------------------------------===// +@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) extension AsyncDNSResolver { /// Possible ``AsyncDNSResolver/AsyncDNSResolver`` errors. public struct Error: Swift.Error, CustomStringConvertible { diff --git a/Sources/AsyncDNSResolver/c-ares/AresChannel.swift b/Sources/AsyncDNSResolver/c-ares/AresChannel.swift index 78b9d26..8f21422 100644 --- a/Sources/AsyncDNSResolver/c-ares/AresChannel.swift +++ b/Sources/AsyncDNSResolver/c-ares/AresChannel.swift @@ -2,7 +2,7 @@ // // This source file is part of the SwiftAsyncDNSResolver open source project // -// Copyright (c) 2020-2023 Apple Inc. and the SwiftAsyncDNSResolver project authors +// Copyright (c) 2020-2024 Apple Inc. and the SwiftAsyncDNSResolver project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -17,6 +17,7 @@ import Foundation // MARK: - ares_channel +@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) class AresChannel { let pointer: UnsafeMutablePointer let lock = NSLock() @@ -62,6 +63,7 @@ class AresChannel { } } +@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) private func checkAresResult(body: () -> Int32) throws { let result = body() guard result == ARES_SUCCESS else { diff --git a/Sources/AsyncDNSResolver/c-ares/AresOptions.swift b/Sources/AsyncDNSResolver/c-ares/AresOptions.swift index 3b78118..3200667 100644 --- a/Sources/AsyncDNSResolver/c-ares/AresOptions.swift +++ b/Sources/AsyncDNSResolver/c-ares/AresOptions.swift @@ -2,7 +2,7 @@ // // This source file is part of the SwiftAsyncDNSResolver open source project // -// Copyright (c) 2020-2023 Apple Inc. and the SwiftAsyncDNSResolver project authors +// Copyright (c) 2020-2024 Apple Inc. and the SwiftAsyncDNSResolver project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -16,6 +16,7 @@ import CAsyncDNSResolver // MARK: - Options for `CAresDNSResolver` +@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) extension CAresDNSResolver { /// Options for ``CAresDNSResolver``. public struct Options { @@ -88,6 +89,7 @@ extension CAresDNSResolver { } } +@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) extension CAresDNSResolver.Options { public struct Flags: OptionSet { public let rawValue: Int32 @@ -120,6 +122,7 @@ extension CAresDNSResolver.Options { } } +@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) extension CAresDNSResolver.Options { var aresOptions: AresOptions { let aresOptions = AresOptions() diff --git a/Sources/AsyncDNSResolver/c-ares/DNSResolver_c-ares.swift b/Sources/AsyncDNSResolver/c-ares/DNSResolver_c-ares.swift index 657dd4c..c2fc3a4 100644 --- a/Sources/AsyncDNSResolver/c-ares/DNSResolver_c-ares.swift +++ b/Sources/AsyncDNSResolver/c-ares/DNSResolver_c-ares.swift @@ -15,6 +15,7 @@ import CAsyncDNSResolver /// ``DNSResolver`` implementation backed by c-ares C library. +@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) public class CAresDNSResolver: DNSResolver { let options: Options let ares: Ares @@ -119,6 +120,7 @@ extension QueryType { // MARK: - c-ares query wrapper +@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) class Ares { typealias QueryCallback = @convention(c) (UnsafeMutableRawPointer?, CInt, CInt, UnsafeMutablePointer?, CInt) -> Void @@ -183,6 +185,7 @@ class Ares { } } +@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) extension Ares { // TODO: implement this more nicely using NIO EventLoop? // See: @@ -253,6 +256,7 @@ extension Ares { // MARK: - c-ares query reply handler +@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) extension Ares { struct QueryReplyHandler { private let _handler: (CInt, UnsafeMutablePointer?, CInt) -> Void @@ -291,6 +295,7 @@ protocol AresQueryReplyParser { func parse(buffer: UnsafeMutablePointer?, length: CInt) throws -> Reply } +@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) extension Ares { static let maxAddresses: Int = 32 @@ -574,32 +579,34 @@ private func toStringArray(_ arrayPointer: UnsafeMutablePointer( @@ -168,6 +169,7 @@ struct DNSSD { // MARK: - dnssd query reply handler +@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) extension DNSSD { struct QueryReplyHandler { private let _handleRecord: (DNSServiceErrorType, UnsafeRawPointer?, UInt16) -> Void @@ -209,6 +211,7 @@ protocol DNSSDQueryReplyHandler { func generateReply(records: [Record]) throws -> Reply } +@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) extension DNSSD { // Reference: https://github.com/orlandos-nl/DNSClient/blob/master/Sources/DNSClient/Messages/Message.swift @@ -227,7 +230,7 @@ extension DNSSD { var parsedAddressBytes = [CChar](repeating: 0, count: Int(INET_ADDRSTRLEN)) inet_ntop(AF_INET, ptr, &parsedAddressBytes, socklen_t(INET_ADDRSTRLEN)) let parsedAddress = String(cString: parsedAddressBytes) - return ARecord(address: .IPv4(parsedAddress), ttl: nil) + return ARecord(address: .init(address: parsedAddress), ttl: nil) } func generateReply(records: [ARecord]) throws -> [ARecord] { @@ -250,7 +253,7 @@ extension DNSSD { var parsedAddressBytes = [CChar](repeating: 0, count: Int(INET6_ADDRSTRLEN)) inet_ntop(AF_INET6, ptr, &parsedAddressBytes, socklen_t(INET6_ADDRSTRLEN)) let parsedAddress = String(cString: parsedAddressBytes) - return AAAARecord(address: .IPv6(parsedAddress), ttl: nil) + return AAAARecord(address: .init(address: parsedAddress), ttl: nil) } func generateReply(records: [AAAARecord]) throws -> [AAAARecord] { @@ -267,7 +270,7 @@ extension DNSSD { } let bufferPtr = UnsafeBufferPointer(start: ptr, count: Int(length)) - var buffer = ByteBuffer(bytes: bufferPtr) + var buffer = Array(bufferPtr)[...] guard let nameserver = self.readName(&buffer) else { throw AsyncDNSResolver.Error.badResponse("failed to read name") @@ -290,7 +293,7 @@ extension DNSSD { } let bufferPtr = UnsafeBufferPointer(start: ptr, count: Int(length)) - var buffer = ByteBuffer(bytes: bufferPtr) + var buffer = Array(bufferPtr)[...] guard let cname = self.readName(&buffer) else { throw AsyncDNSResolver.Error.badResponse("failed to read name") @@ -313,7 +316,7 @@ extension DNSSD { } let bufferPtr = UnsafeBufferPointer(start: ptr, count: Int(length)) - var buffer = ByteBuffer(bytes: bufferPtr) + var buffer = Array(bufferPtr)[...] guard let mname = self.readName(&buffer), let rname = self.readName(&buffer), @@ -350,7 +353,7 @@ extension DNSSD { } let bufferPtr = UnsafeBufferPointer(start: ptr, count: Int(length)) - var buffer = ByteBuffer(bytes: bufferPtr) + var buffer = Array(bufferPtr)[...] guard let name = self.readName(&buffer) else { throw AsyncDNSResolver.Error.badResponse("failed to read name") @@ -373,7 +376,7 @@ extension DNSSD { } let bufferPtr = UnsafeBufferPointer(start: ptr, count: Int(length)) - var buffer = ByteBuffer(bytes: bufferPtr) + var buffer = Array(bufferPtr)[...] guard let priority = buffer.readInteger(as: UInt16.self), let host = self.readName(&buffer) else { @@ -416,7 +419,7 @@ extension DNSSD { } let bufferPtr = UnsafeBufferPointer(start: ptr, count: Int(length)) - var buffer = ByteBuffer(bytes: bufferPtr) + var buffer = Array(bufferPtr)[...] guard let priority = buffer.readInteger(as: UInt16.self), let weight = buffer.readInteger(as: UInt16.self), @@ -440,16 +443,18 @@ extension DNSSD { } extension DNSSDQueryReplyHandler { - func readName(_ buffer: inout ByteBuffer) -> String? { + func readName(_ buffer: inout ArraySlice) -> String? { var parts: [String] = [] while let length = buffer.readInteger(as: UInt8.self), length > 0, let part = buffer.readString(length: Int(length)) { parts.append(part) } + return parts.isEmpty ? nil : parts.joined(separator: ".") } + @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) func ensureOne(records: [R]) throws -> R { guard records.count <= 1 else { throw AsyncDNSResolver.Error.badResponse("expected 1 record but got \(records.count)") @@ -460,4 +465,30 @@ extension DNSSDQueryReplyHandler { return record } } + +extension ArraySlice { + mutating func readInteger(as: T.Type = T.self) -> T? { + let size = MemoryLayout.size + guard self.count >= size else { return nil } + + let value = self.withUnsafeBytes { pointer in + var value = T.zero + Swift.withUnsafeMutableBytes(of: &value) { valuePointer in + valuePointer.copyMemory(from: UnsafeRawBufferPointer(rebasing: pointer[.. String? { + guard self.count >= length else { return nil } + + let prefix = self.prefix(length) + self = self.dropFirst(length) + return String(decoding: prefix, as: UTF8.self) + } +} #endif diff --git a/Tests/AsyncDNSResolverTests/dnssd/DNSDArraySliceTests.swift b/Tests/AsyncDNSResolverTests/dnssd/DNSDArraySliceTests.swift new file mode 100644 index 0000000..15a5a8a --- /dev/null +++ b/Tests/AsyncDNSResolverTests/dnssd/DNSDArraySliceTests.swift @@ -0,0 +1,55 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftAsyncDNSResolver open source project +// +// Copyright (c) 2024 Apple Inc. and the SwiftAsyncDNSResolver project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftAsyncDNSResolver project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +@testable import AsyncDNSResolver +import XCTest + +#if canImport(Darwin) +final class DNSDArraySliceTests: XCTestCase { + func testReadUnsignedInteger() { + // [UInt8(0), UInt16(.max), UInt32(0), UInt64(.max)] + let bytes: [UInt8] = [0, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255] + var slice = bytes[...] + + XCTAssertEqual(slice.readInteger(as: UInt8.self), 0) + XCTAssertEqual(slice.readInteger(as: UInt16.self), .max) + XCTAssertEqual(slice.readInteger(as: UInt32.self), 0) + XCTAssertEqual(slice.readInteger(as: UInt64.self), .max) + + XCTAssertNil(slice.readInteger(as: UInt8.self)) + } + + func testReadSignedInteger() { + // [Int8(0), Int16(-1), Int32(0), Int64(-1)] + let bytes: [UInt8] = [0, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255] + var slice = bytes[...] + + XCTAssertEqual(slice.readInteger(as: Int8.self), 0) + XCTAssertEqual(slice.readInteger(as: Int16.self), -1) + XCTAssertEqual(slice.readInteger(as: Int32.self), 0) + XCTAssertEqual(slice.readInteger(as: Int64.self), -1) + + XCTAssertNil(slice.readInteger(as: Int8.self)) + } + + func testReadString() { + let bytes = Array("hello, world!".utf8) + var slice = bytes[...] + + XCTAssertEqual(slice.readString(length: 13), "hello, world!") + XCTAssertEqual(slice.readString(length: 0), "") + XCTAssertNil(slice.readString(length: 1)) + } +} +#endif diff --git a/Tests/AsyncDNSResolverTests/dnssd/DNSSDDNSResolverTests.swift b/Tests/AsyncDNSResolverTests/dnssd/DNSSDDNSResolverTests.swift index acddf17..10aeb28 100644 --- a/Tests/AsyncDNSResolverTests/dnssd/DNSSDDNSResolverTests.swift +++ b/Tests/AsyncDNSResolverTests/dnssd/DNSSDDNSResolverTests.swift @@ -108,7 +108,7 @@ final class DNSSDDNSResolverTests: XCTestCase { let addrBytes: [UInt8] = [38, 32, 1, 73] try addrBytes.withUnsafeBufferPointer { let record = try DNSSD.AQueryReplyHandler.instance.parseRecord(data: $0.baseAddress, length: UInt16($0.count)) - XCTAssertEqual(record, ARecord(address: .IPv4("38.32.1.73"), ttl: nil)) + XCTAssertEqual(record, ARecord(address: .init(address: "38.32.1.73"), ttl: nil)) } } diff --git a/scripts/soundness.sh b/scripts/soundness.sh index a963865..efb44a4 100755 --- a/scripts/soundness.sh +++ b/scripts/soundness.sh @@ -18,7 +18,7 @@ here="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" function replace_acceptable_years() { # this needs to replace all acceptable forms with 'YEARS' - sed -e 's/202[012]-202[1234]/YEARS/' -e 's/202[01234]/YEARS/' + sed -e 's/202[0123]-202[1234]/YEARS/' -e 's/202[01234]/YEARS/' } if ! hash swiftformat &> /dev/null