Skip to content

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

Closed
wants to merge 1 commit into from
Closed
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
76 changes: 76 additions & 0 deletions Sources/System/Internals/CInternetAddress.swift
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)
Copy link

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:).

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 }
}
6 changes: 6 additions & 0 deletions Sources/System/Internals/CInterop.swift
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,10 @@ public enum CInterop {
/// on API.
public typealias PlatformUnicodeEncoding = UTF8
#endif

/// The C `in_addr` type
public typealias IPv4Address = in_addr

/// The C `in6_addr` type
public typealias IPv6Address = in6_addr
}
23 changes: 23 additions & 0 deletions Sources/System/Internals/Constants.swift
Original file line number Diff line number Diff line change
Expand Up @@ -529,3 +529,26 @@ internal var _SEEK_HOLE: CInt { SEEK_HOLE }
internal var _SEEK_DATA: CInt { SEEK_DATA }
#endif

@_alwaysEmitIntoClient
internal var _INET_ADDRSTRLEN: CInt { INET_ADDRSTRLEN }

@_alwaysEmitIntoClient
internal var _INET6_ADDRSTRLEN: CInt { INET6_ADDRSTRLEN }

@_alwaysEmitIntoClient
internal var _INADDR_ANY: CInterop.IPv4Address { CInterop.IPv4Address(s_addr: INADDR_ANY) }

@_alwaysEmitIntoClient
internal var _INADDR_LOOPBACK: CInterop.IPv4Address { CInterop.IPv4Address(s_addr: INADDR_LOOPBACK) }

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

@_alwaysEmitIntoClient
internal var _INADDR6_LOOPBACK: CInterop.IPv6Address { in6addr_loopback }

@_alwaysEmitIntoClient
internal var _AF_INET: CInt { AF_INET }

@_alwaysEmitIntoClient
internal var _AF_INET6: CInt { AF_INET6 }
17 changes: 17 additions & 0 deletions Sources/System/Internals/Syscalls.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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) }
Copy link

Choose a reason for hiding this comment

The 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)
}
208 changes: 208 additions & 0 deletions Sources/System/InternetProtocol.swift
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 {
Copy link

Choose a reason for hiding this comment

The 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 {
Copy link

Choose a reason for hiding this comment

The 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 {
Copy link

Choose a reason for hiding this comment

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

Should we consider making this a RandomAccessCollection, MutableCollection of UInt8?

Copy link

Choose a reason for hiding this comment

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

In either case we should definitely let you construct these with a Collection where Element == UInt8.

Copy link

Choose a reason for hiding this comment

The 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.

Copy link

Choose a reason for hiding this comment

The 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))
Copy link

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 have any reason to need unsafeBitCast here, this can be fairly straightforwardly written as a direct initialization.

}
}

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) {
Copy link

Choose a reason for hiding this comment

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

UInt16s aren’t bytes.

Copy link

Choose a reason for hiding this comment

The 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).

Copy link

Choose a reason for hiding this comment

The 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
}
}