Skip to content

Commit

Permalink
Merge pull request #3 from swift-libp2p/dev/garlic+tests
Browse files Browse the repository at this point in the history
Additional Protocol Support and Tests
  • Loading branch information
btoms20 committed Jul 21, 2023
2 parents 3ab4015 + d8040f9 commit cded294
Show file tree
Hide file tree
Showing 10 changed files with 687 additions and 187 deletions.
1 change: 0 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ let package = Package(
.package(url: "https://github.com/swift-libp2p/swift-multibase.git", .upToNextMajor(from: "0.0.1")),
.package(url: "https://github.com/swift-libp2p/swift-multihash.git", .upToNextMajor(from: "0.0.1")),
.package(url: "https://github.com/swift-libp2p/swift-cid.git", .upToNextMajor(from: "0.0.1"))
//.package(name: "PeerID", path: "../PeerID")
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
Expand Down
102 changes: 84 additions & 18 deletions Sources/Multiaddr/Address.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ import CID

public struct Address: Equatable {
let addrProtocol: MultiaddrProtocol
var address: String?
let address: String?

init(addrProtocol: MultiaddrProtocol, addressData: Data) {
self.addrProtocol = addrProtocol
guard !addressData.isEmpty else { self.address = nil; return }
self.address = try? unpackAddress(addressData)
self.address = try? Address.unpackAddress(addressData, for: addrProtocol)
}

init(addrProtocol: MultiaddrProtocol, address: String? = nil) throws {
Expand All @@ -27,25 +27,27 @@ public struct Address: Equatable {
case .p2p, .ipfs:
//Ensure addy is a valid CID or Multihash compliant String and store it as a b58 String if so...
guard let address = address, !address.isEmpty else { throw MultiaddrError.parseAddressFail }
guard let address = (try? CID(address).multihash.b58String) ?? (try? Multihash(multihash: address).b58String) else { throw MultiaddrError.parseAddressFail }
guard ((try? CID(address)) != nil) || ((try? Multihash(multihash: address)) != nil) else { throw MultiaddrError.parseAddressFail }
self.address = address
case .certhash:
// Ensure Certhash is a valid Multihash
guard let address = address, !address.isEmpty else { throw MultiaddrError.parseAddressFail }
guard (try? Multihash(multihash: address)) != nil else { throw MultiaddrError.parseAddressFail }
self.address = address
default:
if let address = address {
if var address = address {
if address.hasSuffix("/") { address.removeLast() }
if address.isEmpty { self.address = nil }
else { self.address = address }
} else {
self.address = nil
}
}
let _ = try Address.binaryPackedAddress(self.address, for: self.addrProtocol)
}

func binaryPacked() throws -> Data {
let bytes = [addrProtocol.packedCode(), try binaryPackedAddress()].compactMap{$0}.flatMap{$0}
let bytes = [addrProtocol.packedCode(), try Address.binaryPackedAddress(self.address, for: self.addrProtocol)].compactMap{$0}.flatMap{$0}
return Data(bytes: bytes, count: bytes.count)
}

Expand All @@ -58,6 +60,13 @@ public struct Address: Equatable {
} catch {
return false
}
case (.p2p, .p2p), (.ipfs, .ipfs), (.p2p, .ipfs), (.ipfs, .p2p):
do {
guard let leftAddress = lhs.address, let rightAddress = rhs.address else { return false }
return try CID(leftAddress).multihash == CID(rightAddress).multihash
} catch {
return false
}
default:
return lhs.addrProtocol == rhs.addrProtocol && lhs.address == rhs.address
}
Expand All @@ -66,25 +75,51 @@ public struct Address: Equatable {

extension Address {

private func unpackAddress(_ addressData: Data) throws -> String? {
static private func unpackAddress(_ addressData: Data, for addrProtocol:MultiaddrProtocol) throws -> String? {
switch addrProtocol {
case .tcp, .udp, .dccp, .sctp:
guard addressData.count == 2 else { throw MultiaddrError.parseAddressFail }
return String(addressData.uint16.bigEndian)
case .ip4:
return try IPv4.string(for: addressData)
case .ip6:
return try IPv6.string(for: addressData)
case .tcp, .udp, .dccp, .sctp:
guard addressData.count == 2 else { throw MultiaddrError.parseAddressFail }
return String(addressData.uint16.bigEndian)
case .ip6zone:
guard !addressData.isEmpty else { throw MultiaddrError.parseAddressFail }
let varInt = VarInt.uVarInt(addressData.bytes)
guard Int(varInt.value) + varInt.bytesRead == addressData.count else { throw MultiaddrError.parseAddressFail }
guard let address = String(data: Data(addressData.dropFirst(varInt.bytesRead)), encoding: .utf8) else { throw MultiaddrError.invalidFormat }
guard address.count > 0, !address.contains("/") else { throw MultiaddrError.invalidFormat }
return address
case .ipcidr:
guard addressData.count == 1 else { throw MultiaddrError.parseAddressFail }
let ipMask = addressData.asString(base: .base10)
return ipMask
case .onion:
return try Onion.string(for: addressData)
case .onion3:
return try Onion3.string(for: addressData)
case .garlic32:
guard !addressData.isEmpty else { throw MultiaddrError.parseAddressFail }
let varInt = VarInt.uVarInt(addressData.bytes)
guard Int(varInt.value) + varInt.bytesRead == addressData.count else { throw MultiaddrError.parseAddressFail }
return try Garlic32.string(for: addressData.dropFirst(varInt.bytesRead))
case .garlic64:
guard !addressData.isEmpty else { throw MultiaddrError.parseAddressFail }
let varInt = VarInt.uVarInt(addressData.bytes)
guard Int(varInt.value) + varInt.bytesRead == addressData.count else { throw MultiaddrError.parseAddressFail }
return try Garlic64.string(for: addressData.dropFirst(varInt.bytesRead))
case .p2p, .ipfs:
return try IPFS.string(for: addressData)
case .dns4, .dns6, .dnsaddr, .unix:
return try P2P.string(for: addressData)
case .dns, .dns4, .dns6, .dnsaddr, .sni, .unix:
return try DNS.string(for: addressData)
case .http, .https, .utp, .udt, .ws, .wss, .quic, .p2p_circuit:
guard addressData.isEmpty else { throw MultiaddrError.parseAddressFail }
return nil
//case .http, .https:
// if addressData.isEmpty { return nil }
// guard let str = String(data: addressData, encoding: .utf8) else { throw MultiaddrError.parseAddressFail }
// return str
case .certhash:
guard !addressData.isEmpty else { throw MultiaddrError.parseAddressFail }
let varInt = VarInt.uVarInt(addressData.bytes)
Expand All @@ -95,44 +130,75 @@ extension Address {
}
}

private func binaryPackedAddress() throws -> Data? {
guard let address = address else { return nil }
static private func binaryPackedAddress(_ address:String?, for addrProtocol:MultiaddrProtocol) throws -> Data? {
switch addrProtocol {
case .tcp, .udp, .dccp, .sctp:
guard let address = address else { throw MultiaddrError.parseAddressFail }
guard let port = UInt16(address) else { throw MultiaddrError.invalidPortValue }
var bigEndianPort = port.bigEndian
return Data(bytes: &bigEndianPort, count: MemoryLayout<UInt16>.size)
case .ip4:
guard let address = address else { throw MultiaddrError.parseAddressFail }
return try IPv4.data(for: address)
case .ip6:
guard let address = address else { throw MultiaddrError.parseAddressFail }
return try IPv6.data(for: address)
case .ip6zone:
guard let address = address else { throw MultiaddrError.parseAddressFail }
guard !address.contains("/") else { throw MultiaddrError.invalidFormat }
let data = Data(address.utf8)
return Data(putUVarInt(UInt64(data.count)) + data)
case .ipcidr:
guard let address = address else { throw MultiaddrError.parseAddressFail }
let ipMask = try Data(decoding: address, as: .base10)
guard ipMask.count == 1 else { throw MultiaddrError.parseAddressFail }
return ipMask
case .onion:
guard let address = address else { throw MultiaddrError.parseAddressFail }
return try Onion.data(for: address)
case .onion3:
guard let address = address else { throw MultiaddrError.parseAddressFail }
return try Onion3.data(for: address)
case .garlic32:
guard let address = address else { throw MultiaddrError.parseAddressFail }
return try Garlic32.data(for: address)
case .garlic64:
guard let address = address else { throw MultiaddrError.parseAddressFail }
return try Garlic64.data(for: address)
case .p2p, .ipfs:
return try IPFS.data(for: address)
case .dns4, .dns6, .dnsaddr, .unix:
guard let address = address else { throw MultiaddrError.parseAddressFail }
return try P2P.data(for: address)
case .dns, .dns4, .dns6, .dnsaddr, .sni, .unix:
guard let address = address else { throw MultiaddrError.parseAddressFail }
return DNS.data(for: address)
case .http, .https, .utp, .udt, .ws, .wss, .quic, .p2p_circuit:
guard address == nil else { throw MultiaddrError.parseAddressFail }
return nil
//case .http, .https:
// if let address = address {
// return Data(address.utf8)
// } else {
// return nil
// }
case .certhash:
guard let address = address else { throw MultiaddrError.parseAddressFail }
let mh = try Multihash(multihash: address)
return Data(VarInt.putUVarInt(UInt64(mh.value.count)) + mh.value)
default:
if address == nil { return nil }
throw MultiaddrError.parseAddressFail
}
}

static func byteSizeForAddress(_ proto: MultiaddrProtocol, buffer: [UInt8]) -> Int {
switch proto.size() {
case .zero:
return 0
case .fixed(let bits):
return bits / 8
case .variable:
case .variableLengthPrefixed:
let (sizeValue, bytesRead) = VarInt.uVarInt(buffer) //Varint.readUVarInt(from: buffer)
return Int(sizeValue) + bytesRead
case .zero:
return 0
}
}
}
Expand Down
3 changes: 2 additions & 1 deletion Sources/Multiaddr/Extensions/Error+Multiaddr.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//
// Error+Multiaddr.swift
//
//
// Created by Luke Reichold
// Modified by Brandon Toms on 5/1/22.
//
Expand All @@ -14,6 +14,7 @@ enum MultiaddrError: Error {
case parseIPv6AddressFail
case invalidPortValue
case invalidOnionHostAddress
case invalidGarlicAddress
case unknownProtocol
case ipfsAddressLengthConflict
case unknownCodec
Expand Down
13 changes: 13 additions & 0 deletions Sources/Multiaddr/Multiaddr.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,16 @@ public struct Multiaddr: Equatable {
public private(set) var addresses: [Address] = []

public init(_ string: String) throws {
guard !string.isEmpty else { throw MultiaddrError.invalidFormat }
addresses = try createAddresses(from: string)
guard !self.addresses.isEmpty else { throw MultiaddrError.parseAddressFail }
try validate()
}

public init(_ bytes: Data) throws {
guard !bytes.isEmpty else { throw MultiaddrError.invalidFormat }
self.addresses = try createAddresses(fromData: bytes)
guard !self.addresses.isEmpty else { throw MultiaddrError.parseAddressFail }
}

public init(_ proto: MultiaddrProtocol, address: String?) throws {
Expand Down Expand Up @@ -74,6 +78,7 @@ public struct Multiaddr: Equatable {
/// Returns a new `Multiaddr`, removing the last occurance of the protocol and all subsequent addresses.
public func decapsulate(_ other: String) -> Multiaddr {
let protoName = other.hasPrefix("/") ? String(other.dropFirst()) : other
//guard let codec = try? Codecs(protoName) else { return self }
if let lastMatch = addresses.lastIndex(where: { $0.addrProtocol.name == protoName }) {
return Multiaddr(Array(addresses[..<lastMatch]))
} else {
Expand Down Expand Up @@ -112,6 +117,14 @@ public struct Multiaddr: Equatable {
} else { return nil }
}

public func getFirstAddress(forCodec codec:MultiaddrProtocol) -> Address? {
return self.addresses.first(where: { $0.codec.isEqual(codec) })
}

public func getAddresses(forCodec codec:MultiaddrProtocol) -> [Address] {
return self.addresses.compactMap { if $0.codec.isEqual(codec) { return $0 } else { return nil } }
}

/// Returns a new Multiaddr replacing the Address associated with the specified codec
///
/// - Parameters:
Expand Down
46 changes: 46 additions & 0 deletions Sources/Multiaddr/Protocol Helpers/Garlic.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
//
// Garlic.swift
//
// Modified by Brandon Toms on 5/1/22.
//

import BaseX
import Foundation
import Multibase
import VarInt

struct Garlic32 {
static func data(for string: String) throws -> Data {
if string.count < 55 && string.count != 52 { throw MultiaddrError.invalidGarlicAddress }
let padded = string + String(repeating: "=", count: string.count % 8)
let decoded = try BaseEncoding.decode(padded, as: .base32Pad).data // BaseX.decode(string, as: .custom(Garlic32.alphabet))
if decoded.count < 35 && decoded.count != 32 { throw MultiaddrError.invalidGarlicAddress }
return Data(VarInt.putUVarInt(UInt64(decoded.count)) + decoded)
}

static func string(for data: Data) throws -> String {
if data.count < 35 && data.count != 32 { throw MultiaddrError.invalidGarlicAddress }
var encoded = data.asString(base: .base32Pad) //BaseX.encode(data, into: .custom(Garlic32.alphabet))
while encoded.last == "=" { encoded.removeLast() }
if encoded.count < 55 && encoded.count != 52 { throw MultiaddrError.invalidGarlicAddress }
return encoded
}
}

struct Garlic64 {
static let alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-~"

static func data(for string: String) throws -> Data {
guard string.count >= 516 && string.count <= 616 else { throw MultiaddrError.invalidGarlicAddress }
let decoded = try BaseX.decode(string, as: .custom(Garlic64.alphabet))
guard decoded.count >= 386 else { throw MultiaddrError.invalidGarlicAddress }
return Data(VarInt.putUVarInt(UInt64(decoded.count)) + decoded)
}

static func string(for data: Data) throws -> String {
guard data.count >= 386 else { throw MultiaddrError.invalidGarlicAddress }
let encoded = BaseX.encode(data, into: .custom(Garlic64.alphabet))
guard encoded.count >= 516 && encoded.count <= 616 else { throw MultiaddrError.invalidGarlicAddress }
return encoded
}
}
46 changes: 9 additions & 37 deletions Sources/Multiaddr/Protocol Helpers/IPFS.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,48 +5,20 @@
// Modified by Brandon Toms on 5/1/22.
//

import CID
import Foundation
import VarInt
import Multibase
import Multihash
import CID
import VarInt

struct IPFS {
struct P2P {
static func data(for address: String) throws -> Data {
// let raw = try? BaseEncoding.decode(address, as: .base58btc)
// print(raw)
// let raw2 = try? BaseEncoding.decode(address) //36 bytes
// print(raw2)
// print("Checking if address is a valid multihash: \(address)")
// let mh = try Multihash(multihash: address)
// print(mh.b58String)
// return Data(mh.digest!)

let addressBytes = Array(try BaseEncoding.decode(address, as: .base58btc).data) //Base58.bytesFromBase58(address)
let sizeBytes = UInt64(addressBytes.count).varIntData()
let combined = [Array(sizeBytes), addressBytes].flatMap { $0 }
return Data(bytes: combined, count: combined.count)
let multihash = try (try? CID(address).multihash) ?? Multihash(multihash: address)
return Data(putUVarInt(UInt64(multihash.value.count)) + multihash.value)
}

static func string(for data: Data) throws -> String {
// print("Checking if data is a valid multihash")
// return try Multihash(multihash: data).b58String

let buffer = Array(data)
let decodedVarint = VarInt.uVarInt(buffer) //Varint.readUVarInt(from: buffer)
//let expectedSize = decodedVarint.value

let addressBytes = Array(buffer[decodedVarint.bytesRead...])

// Commenting out this check due to using CID / Multihash Init as verification...
//guard addressBytes.count == expectedSize else { throw MultiaddrError.ipfsAddressLengthConflict }

//Ensure addressBytes is a valid CID or Multihash compliant buffer and store it as a b58 String if so...
guard let str = (try? CID(addressBytes).multihash.b58String) ?? (try? Multihash(addressBytes).b58String) else {
throw MultiaddrError.ipfsAddressLengthConflict
}
return str

//return addressBytes.asString(base: .base58btc) //Base58.base58FromBytes(addressBytes)
static func string(for data: Data) throws -> String {
let varInt = uVarInt(data.bytes)
guard varInt.bytesRead + Int(varInt.value) == data.count else { throw MultiaddrError.invalidFormat }
return try Multihash(multihash: data.dropFirst(varInt.bytesRead)).b58String
}
}
Loading

0 comments on commit cded294

Please sign in to comment.