Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
Original file line number Diff line number Diff line change
Expand Up @@ -136,47 +136,78 @@ package enum PeerAddress: Equatable {
// - ipv4:<host>:<port> for ipv4 addresses
// - ipv6:[<host>]:<port> for ipv6 addresses
// - unix:<uds-pathname> for UNIX domain sockets
let addressUTF8View = address.utf8

// First get the first component so that we know what type of address we're dealing with
let addressComponents = address.split(separator: ":", maxSplits: 1)
let firstColonIndex = addressUTF8View.firstIndex(of: UInt8(ascii: ":"))

guard addressComponents.count > 1 else {
guard let firstColonIndex else {
// This is some unexpected/unknown format
return nil
}

let addressType = addressUTF8View[..<firstColonIndex]

var addressWithoutType = addressUTF8View[firstColonIndex...]
_ = addressWithoutType.popFirst()

// Check what type the transport is...
switch addressComponents[0] {
case "ipv4":
let ipv4AddressComponents = addressComponents[1].split(separator: ":")
if ipv4AddressComponents.count == 2, let port = Int(ipv4AddressComponents[1]) {
self = .ipv4(address: String(ipv4AddressComponents[0]), port: port)
} else {
if addressType.elementsEqual("ipv4".utf8) {
guard let addressColon = addressWithoutType.firstIndex(of: UInt8(ascii: ":")) else {
// This is some unexpected/unknown format
return nil
}

case "ipv6":
if addressComponents[1].first == "[" {
// At this point, we are looking at an address with format: [<address>]:<port>
// We drop the first character ('[') and split by ']:' to keep two components: the address
// and the port.
let ipv6AddressComponents = addressComponents[1].dropFirst().split(separator: "]:")
if ipv6AddressComponents.count == 2, let port = Int(ipv6AddressComponents[1]) {
self = .ipv6(address: String(ipv6AddressComponents[0]), port: port)
} else {
return nil
}
let hostComponent = addressWithoutType[..<addressColon]
var portComponent = addressWithoutType[addressColon...]
_ = portComponent.popFirst()

if let host = String(hostComponent), let port = Int(utf8View: portComponent) {
self = .ipv4(address: host, port: port)
} else {
return nil
}
} else if addressType.elementsEqual("ipv6".utf8) {
guard let lastColonIndex = addressWithoutType.lastIndex(of: UInt8(ascii: ":")) else {
// This is some unexpected/unknown format
return nil
}

case "unix":
// Whatever comes after "unix:" is the <pathname>
self = .unixDomainSocket(path: String(addressComponents[1]))
var hostComponent = addressWithoutType[..<lastColonIndex]
var portComponent = addressWithoutType[lastColonIndex...]
_ = portComponent.popFirst()

default:
if let firstBracket = hostComponent.popFirst(), let lastBracket = hostComponent.popLast(),
firstBracket == UInt8(ascii: "["), lastBracket == UInt8(ascii: "]"),
let host = String(hostComponent), let port = Int(utf8View: portComponent)
{
self = .ipv6(address: host, port: port)
} else {
// This is some unexpected/unknown format
return nil
}
} else if addressType.elementsEqual("unix".utf8) {
// Whatever comes after "unix:" is the <pathname>
self = .unixDomainSocket(path: String(addressWithoutType) ?? "")
} else {
// This is some unexpected/unknown format
return nil
}
}
}

extension Int {
package init?(utf8View: Substring.UTF8View.SubSequence) {
var value = 0
for utf8Element in utf8View {
value &*= 10
let elementValue = Int(utf8Element - 48) // ASCII code for 0 is 48
guard elementValue >= 0, elementValue <= 9 else {
// non-digit character
return nil
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

At the moment if utf8Element is e.g. 47 then we'll underflow and crash when creating elementValue.

We should validate the UInt8 value and then do the conversion. It also means that the subtraction in the conversion can be unchecked.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I thought maybe using a range instead was clearer so I've done, but I'm actually unsure if the range allocates or if it's all nicely optimised by the compiler.

value &+= elementValue
}
self = value
}
}
5 changes: 5 additions & 0 deletions Tests/GRPCOTelTracingInterceptorsTests/PeerAddressTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,9 @@ struct PeerAddressTests {
let address = PeerAddress(address)
#expect(address == nil)
}

@Test("Int.init(utf8View:)")
func testIntInitFromUTF8View() async throws {
#expect(54321 == Int(utf8View: "54321".utf8[...]))
}
}