p2p-swift / Sources / IrdP2PCrypto / Identifiers.swift
448 words
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
import Foundation

/// Errors raised while building protocol identifiers.
public enum IdentifierError: Error, Sendable, Equatable {
    case roomSecretTooShort(bytes: Int)
    case malformedBase64URL
    case malformedRoomId
    case malformedPeerId
    case malformedSeed
}

/// The invite secret of a room: at least 16 random bytes, carried as URL-safe
/// base64 (43 characters for the recommended 32 bytes). Everything about a
/// room — its relay id, its keys, its mailbox id — is derived from it, and it
/// is the only thing guarding who can join.
public struct RoomSecret: Sendable, Hashable {
    public let bytes: Data

    /// Wraps raw secret bytes (at least 16).
    public init(bytes: Data) throws {
        guard bytes.count >= 16 else { throw IdentifierError.roomSecretTooShort(bytes: bytes.count) }
        self.bytes = bytes
    }

    /// Parses the URL-safe base64 form found in invite links.
    public init(base64URL: String) throws {
        guard let data = Bytes.fromBase64URL(base64URL) else { throw IdentifierError.malformedBase64URL }
        try self.init(bytes: data)
    }

    /// 32 fresh random bytes.
    public static func random() -> RoomSecret {
        try! RoomSecret(bytes: Bytes.random(32))
    }

    /// URL-safe base64, unpadded (43 characters for 32 bytes).
    public var base64URL: String { Bytes.base64URL(bytes) }
}

/// A relay room id: 43 URL-safe base64 characters (32 bytes), derived from the
/// room secret with HKDF. See `RoomCrypto`.
public struct RoomId: Sendable, Hashable, CustomStringConvertible {
    public static let length = 43
    public let string: String

    public init(_ string: String) throws {
        guard RoomId.isValid(string) else { throw IdentifierError.malformedRoomId }
        self.string = string
    }

    init(unchecked string: String) { self.string = string }

    public static func isValid(_ s: String) -> Bool {
        s.utf8.count == length && s.utf8.allSatisfy { c in
            (0x41...0x5A).contains(c) || (0x61...0x7A).contains(c) || (0x30...0x39).contains(c) || c == 0x2D || c == 0x5F
        }
    }

    public var description: String { string }
}

/// A relay peer id: 16 bytes as 32 lowercase hex characters, self-chosen per
/// connection. Rejoining with the same id replaces the previous connection.
public struct PeerId: Sendable, Hashable, Comparable, CustomStringConvertible, Codable {
    public static let length = 32
    public let hex: String

    public init(_ hex: String) throws {
        guard PeerId.isValid(hex) else { throw IdentifierError.malformedPeerId }
        self.hex = hex
    }

    public init(from decoder: any Decoder) throws {
        let s = try decoder.singleValueContainer().decode(String.self)
        try self.init(s)
    }

    public func encode(to encoder: any Encoder) throws {
        var c = encoder.singleValueContainer()
        try c.encode(hex)
    }

    /// 16 fresh random bytes.
    public static func random() -> PeerId {
        try! PeerId(Bytes.hex(Bytes.random(16)))
    }

    public static func isValid(_ s: String) -> Bool {
        s.utf8.count == length && s.utf8.allSatisfy { c in (0x30...0x39).contains(c) || (0x61...0x66).contains(c) }
    }

    /// Lexicographic on the hex string; the lower peer offers in WebRTC negotiation.
    public static func < (lhs: PeerId, rhs: PeerId) -> Bool { lhs.hex < rhs.hex }

    public var description: String { hex }
}

static mirror of HEAD · about · clone: git clone https://ird.cod-sursa.ro/p2p-swift.git