-
Notifications
You must be signed in to change notification settings - Fork 121
Added IPAddress
#64
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Added IPAddress
#64
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
/* | ||
This source file is part of the Swift System open source project | ||
|
||
Copyright (c) 2021 Apple Inc. and the Swift System project authors | ||
Licensed under Apache License v2.0 with Runtime Library Exception | ||
|
||
See https://swift.org/LICENSE.txt for license information | ||
*/ | ||
|
||
@usableFromInline | ||
internal protocol CInternetAddress { | ||
|
||
static var stringLength: Int { get } | ||
|
||
static var family: CInt { get } | ||
|
||
init() | ||
} | ||
|
||
internal extension CInternetAddress { | ||
|
||
@usableFromInline | ||
init?(_ string: String) { | ||
self.init() | ||
/** | ||
inet_pton() returns 1 on success (network address was successfully converted). 0 is returned if src does not contain a character string representing a valid network address in the specified address family. If af does not contain a valid address family, -1 is returned and errno is set to EAFNOSUPPORT. | ||
*/ | ||
let result = string.withCString { | ||
system_inet_pton(Self.family, $0, &self) | ||
} | ||
guard result == 1 else { | ||
assert(result != -1, "Invalid address family") | ||
return nil | ||
} | ||
} | ||
} | ||
|
||
internal extension String { | ||
|
||
@usableFromInline | ||
init<T: CInternetAddress>(_ cInternetAddress: T) throws { | ||
let cString = UnsafeMutablePointer<CChar>.allocate(capacity: T.stringLength) | ||
defer { cString.deallocate() } | ||
let success = withUnsafePointer(to: cInternetAddress) { | ||
system_inet_ntop( | ||
T.family, | ||
$0, | ||
cString, | ||
numericCast(T.stringLength) | ||
) != nil | ||
} | ||
guard success else { | ||
throw Errno.current | ||
} | ||
|
||
self.init(cString: cString) | ||
} | ||
} | ||
|
||
extension CInterop.IPv4Address: CInternetAddress { | ||
|
||
@usableFromInline | ||
static var stringLength: Int { return numericCast(_INET_ADDRSTRLEN) } | ||
|
||
@usableFromInline | ||
static var family: CInt { _AF_INET } | ||
} | ||
|
||
extension CInterop.IPv6Address: CInternetAddress { | ||
|
||
@usableFromInline | ||
static var stringLength: Int { return numericCast(_INET6_ADDRSTRLEN) } | ||
|
||
@usableFromInline | ||
static var family: CInt { _AF_INET6 } | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -123,3 +123,20 @@ internal func system_pipe(_ fds: UnsafeMutablePointer<Int32>) -> CInt { | |
return pipe(fds) | ||
} | ||
#endif | ||
|
||
internal func system_inet_pton( | ||
_ family: Int32, | ||
_ cString: UnsafePointer<CInterop.PlatformChar>, | ||
_ address: UnsafeMutableRawPointer) -> Int32 { | ||
#if ENABLE_MOCKING | ||
if mockingEnabled { return _mock(family, cString, address) } | ||
#endif | ||
return inet_pton(family, cString, address) | ||
} | ||
|
||
internal func system_inet_ntop(_ family: Int32, _ pointer : UnsafeRawPointer, _ string: UnsafeMutablePointer<CChar>, _ length: UInt32) -> UnsafePointer<CChar>? { | ||
#if ENABLE_MOCKING | ||
//if mockingEnabled { return _mock(family, pointer, string, length) } | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this commented out on purpose? |
||
#endif | ||
return inet_ntop(family, pointer, string, length) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,208 @@ | ||
/* | ||
This source file is part of the Swift System open source project | ||
|
||
Copyright (c) 2021 Apple Inc. and the Swift System project authors | ||
Licensed under Apache License v2.0 with Runtime Library Exception | ||
|
||
See https://swift.org/LICENSE.txt for license information | ||
*/ | ||
|
||
/// Internet Protocol Address | ||
@frozen | ||
public enum IPAddress: Equatable, Hashable, Codable { | ||
|
||
/// IPv4 | ||
case v4(IPv4Address) | ||
|
||
/// IPv6 | ||
case v6(IPv6Address) | ||
} | ||
|
||
public extension IPAddress { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The access qualifiers for extension members aren’t being placed consistently in this patch. I don’t know what the project style is, but either they should all go on the extension members or they should all go on the extension itself. |
||
|
||
@_alwaysEmitIntoClient | ||
func withUnsafeBytes<Result>(_ body: ((UnsafeRawBufferPointer) -> (Result))) -> Result { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we should provide a mutable version of this function as well. |
||
switch self { | ||
case let .v4(address): | ||
return address.withUnsafeBytes(body) | ||
case let .v6(address): | ||
return address.withUnsafeBytes(body) | ||
} | ||
} | ||
} | ||
|
||
extension IPAddress: RawRepresentable { | ||
|
||
public init?(rawValue: String) { | ||
|
||
if let address = IPv4Address(rawValue: rawValue) { | ||
self = .v4(address) | ||
} else if let address = IPv6Address(rawValue: rawValue) { | ||
self = .v6(address) | ||
} else { | ||
return nil | ||
} | ||
} | ||
|
||
public var rawValue: String { | ||
switch self { | ||
case let .v4(address): return address.rawValue | ||
case let .v6(address): return address.rawValue | ||
} | ||
} | ||
} | ||
|
||
extension IPAddress: CustomStringConvertible, CustomDebugStringConvertible { | ||
|
||
public var description: String { | ||
return rawValue | ||
} | ||
|
||
public var debugDescription: String { | ||
return description | ||
} | ||
} | ||
|
||
/// IPv4 Socket Address | ||
@frozen | ||
public struct IPv4Address: Equatable, Hashable, Codable { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we consider making this a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In either case we should definitely let you construct these with a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think it makes sense to make IP addresses collections of UInt8; they are technically defined as 32/128-bit identifiers. That you can slice the bits up and view, say, an IPv4 address as (4xUInt8) or (2xUInt16) or (1xUInt32) is interesting, and can make some manipulation easier, but it isn't fundamental to what an IP address is. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't inherently disagree with that, but we either have to have a useful way to project them to a number or to bytes, otherwise getting them as a sequence of bytes is a pain in the neck. There are lots of reasons to want to be able to do that, most notably when working as network protocols, and having a safe way to do it is useful. Given that projecting an IPv6 address as an integer is not possible in Swift without swift-numerics (an unreasonable dependency for this project), I'm inclined to want to still offer it. |
||
|
||
@usableFromInline | ||
internal let bytes: CInterop.IPv4Address | ||
|
||
@_alwaysEmitIntoClient | ||
public init(_ bytes: CInterop.IPv4Address) { | ||
self.bytes = bytes | ||
} | ||
|
||
@_alwaysEmitIntoClient | ||
public func withUnsafeBytes<Result>(_ body: ((UnsafeRawBufferPointer) -> (Result))) -> Result { | ||
Swift.withUnsafeBytes(of: bytes, body) | ||
} | ||
} | ||
|
||
public extension IPv4Address { | ||
|
||
/// Initialize with raw bytes. | ||
@_alwaysEmitIntoClient | ||
init(_ byte0: UInt8, _ byte1: UInt8, _ byte2: UInt8, _ byte3: UInt8) { | ||
self.init(unsafeBitCast((byte0, byte1, byte2, byte3), to: CInterop.IPv4Address.self)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don’t think we have any reason to need |
||
} | ||
} | ||
|
||
public extension IPAddress { | ||
|
||
/// Initialize with a IP v4 address. | ||
@_alwaysEmitIntoClient | ||
init(_ byte0: UInt8, _ byte1: UInt8, _ byte2: UInt8, _ byte3: UInt8) { | ||
self = .v4(IPv4Address(byte0, byte1, byte2, byte3)) | ||
} | ||
} | ||
|
||
public extension IPv4Address { | ||
|
||
@_alwaysEmitIntoClient | ||
static var any: IPv4Address { IPv4Address(_INADDR_ANY) } | ||
|
||
@_alwaysEmitIntoClient | ||
static var loopback: IPv4Address { IPv4Address(_INADDR_LOOPBACK) } | ||
} | ||
|
||
extension IPv4Address: RawRepresentable { | ||
|
||
@_alwaysEmitIntoClient | ||
public init?(rawValue: String) { | ||
guard let bytes = CInterop.IPv4Address(rawValue) else { | ||
return nil | ||
} | ||
self.init(bytes) | ||
} | ||
|
||
@_alwaysEmitIntoClient | ||
public var rawValue: String { | ||
return try! String(bytes) | ||
} | ||
} | ||
|
||
extension IPv4Address: CustomStringConvertible, CustomDebugStringConvertible { | ||
|
||
public var description: String { | ||
return rawValue | ||
} | ||
|
||
public var debugDescription: String { | ||
return description | ||
} | ||
} | ||
|
||
/// IPv6 Socket Address | ||
@frozen | ||
public struct IPv6Address: Equatable, Hashable, Codable { | ||
|
||
@usableFromInline | ||
internal let bytes: CInterop.IPv6Address | ||
|
||
@_alwaysEmitIntoClient | ||
public init(_ bytes: CInterop.IPv6Address) { | ||
self.bytes = bytes | ||
} | ||
|
||
@_alwaysEmitIntoClient | ||
public func withUnsafeBytes<Result>(_ body: ((UnsafeRawBufferPointer) -> (Result))) -> Result { | ||
Swift.withUnsafeBytes(of: bytes, body) | ||
} | ||
} | ||
|
||
public extension IPv6Address { | ||
|
||
/// Initialize with bytes | ||
@_alwaysEmitIntoClient | ||
init(_ byte0: UInt16, _ byte1: UInt16, _ byte2: UInt16, _ byte3: UInt16, _ byte4: UInt16, _ byte5: UInt16, _ byte6: UInt16, _ byte7: UInt16) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. UInt16s aren’t bytes. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. They could be - "byte" is an ambiguous term. This is why internet standards (and WebURL's IP-address types) use the term "octet". When slicing a 128-bit IPv6 address in to 16-bit units, the terms I've seen most often are "segments" and "pieces" (WebURL uses the latter). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is technically true, but I'm going to argue that it's the least useful kind of technically true. The number of developers who see the word "byte" and think "8 bits" overwhelmingly outnumbers the number of people who see the word "byte" and think "I don't know how big this is". Calling these bytes invites confusion (I guarantee that some time down the road we'll get a developer wanting to reword this initializer if we don't do it ourselves), so we can just not use that word. |
||
self.init(unsafeBitCast((byte0.bigEndian, byte1.bigEndian, byte2.bigEndian, byte3.bigEndian, byte4.bigEndian, byte5.bigEndian, byte6.bigEndian, byte7.bigEndian), to: CInterop.IPv6Address.self)) | ||
} | ||
} | ||
|
||
public extension IPAddress { | ||
|
||
/// Initialize with a IP v6 address. | ||
@_alwaysEmitIntoClient | ||
init(_ byte0: UInt16, _ byte1: UInt16, _ byte2: UInt16, _ byte3: UInt16, _ byte4: UInt16, _ byte5: UInt16, _ byte6: UInt16, _ byte7: UInt16) { | ||
self = .v6(IPv6Address(byte0, byte1, byte2, byte3, byte4, byte5, byte6, byte7)) | ||
} | ||
} | ||
|
||
public extension IPv6Address { | ||
|
||
@_alwaysEmitIntoClient | ||
static var any: IPv6Address { IPv6Address(_INADDR6_ANY) } | ||
|
||
@_alwaysEmitIntoClient | ||
static var loopback: IPv6Address { IPv6Address(_INADDR6_LOOPBACK) } | ||
} | ||
|
||
extension IPv6Address: RawRepresentable { | ||
|
||
@_alwaysEmitIntoClient | ||
public init?(rawValue: String) { | ||
guard let bytes = CInterop.IPv6Address(rawValue) else { | ||
return nil | ||
} | ||
self.init(bytes) | ||
} | ||
|
||
@_alwaysEmitIntoClient | ||
public var rawValue: String { | ||
return try! String(bytes) | ||
} | ||
} | ||
|
||
extension IPv6Address: CustomStringConvertible, CustomDebugStringConvertible { | ||
|
||
public var description: String { | ||
return rawValue | ||
} | ||
|
||
public var debugDescription: String { | ||
return description | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don’t think we need this, we could safely use
String(unsafeUninitializedCapacity:initializingWith:)
.