p2p-swift / Sources / IrdP2P / InnerMessage.swift
548 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
import Foundation
import IrdP2PCrypto

/// The identity block a peer announces in its hello.
public struct IdentityBlock: Sendable, Equatable {
    public var pub: String
    public var sig: String
    public var name: String?
    public var hue: Int?
    public var glyph: String?

    var json: JSONValue {
        var o = JSONObject([("pub", .string(pub)), ("sig", .string(sig))])
        if let name { o["name"] = .string(name) }
        if let hue { o["hue"] = .int(hue) }
        if let glyph { o["glyph"] = .string(glyph) }
        return .object(o)
    }

    init?(json: JSONValue?) {
        guard let o = json?.objectValue, let pub = o["pub"]?.stringValue, let sig = o["sig"]?.stringValue else { return nil }
        self.pub = pub
        self.sig = sig
        name = o["name"]?.stringValue
        hue = o["hue"]?.intValue
        glyph = o["glyph"]?.stringValue
    }

    init(pub: String, sig: String, name: String?, hue: Int?, glyph: String?) {
        self.pub = pub; self.sig = sig; self.name = name; self.hue = hue; self.glyph = glyph
    }
}

enum ChunkEncoding: String, Sendable { case json, bin }

/// The plaintexts inside sealed envelopes (PROTOCOL.md "Inner messages").
/// Unknown kinds and keys are ignored so the protocol can grow without a bump.
enum InnerMessage: Sendable, Equatable {
    case hello(IdentityBlock?)
    case offer(sdp: String)
    case answer(sdp: String)
    case ice(candidate: String?, sdpMid: String?, sdpMLineIndex: Int?)
    /// The higher peer asks the offerer to restart ICE.
    case restart
    case msg(JSONValue)
    case bin(Data)
    case chunk(id: String, index: Int, count: Int, encoding: ChunkEncoding, data: Data)
    case ping
    case unknown(kind: String)

    var json: JSONValue {
        switch self {
        case .hello(let id):
            var o = JSONObject([("kind", .string("hello"))])
            if let id { o["id"] = id.json }
            return .object(o)
        case .offer(let sdp): return .object(JSONObject([("kind", .string("offer")), ("sdp", .string(sdp))]))
        case .answer(let sdp): return .object(JSONObject([("kind", .string("answer")), ("sdp", .string(sdp))]))
        case .ice(let candidate, let mid, let index):
            return .object(JSONObject([("kind", .string("ice")), ("candidate", candidate.map { .string($0) } ?? .null),
                                       ("sdpMid", mid.map { .string($0) } ?? .null), ("sdpMLineIndex", index.map { .int($0) } ?? .null)]))
        case .restart: return .object(["kind": .string("restart")])
        case .msg(let payload): return .object(JSONObject([("kind", .string("msg")), ("payload", payload)]))
        case .bin(let data): return .object(JSONObject([("kind", .string("bin")), ("d", .string(Bytes.base64(data)))]))
        case .chunk(let id, let index, let count, let enc, let data):
            return .object(JSONObject([("kind", .string("chunk")), ("id", .string(id)), ("i", .int(index)), ("n", .int(count)),
                                       ("enc", .string(enc.rawValue)), ("d", .string(Bytes.base64(data)))]))
        case .ping: return .object(["kind": .string("ping")])
        case .unknown(let kind): return .object(["kind": .string(kind)])
        }
    }

    /// `nil` when the value is not an inner message at all (not an object, no kind).
    init?(json: JSONValue) {
        guard let o = json.objectValue, let kind = o["kind"]?.stringValue else { return nil }
        switch kind {
        case "hello": self = .hello(IdentityBlock(json: o["id"]))
        case "offer": guard let sdp = o["sdp"]?.stringValue else { return nil }; self = .offer(sdp: sdp)
        case "answer": guard let sdp = o["sdp"]?.stringValue else { return nil }; self = .answer(sdp: sdp)
        case "ice": self = .ice(candidate: o["candidate"]?.stringValue, sdpMid: o["sdpMid"]?.stringValue, sdpMLineIndex: o["sdpMLineIndex"]?.intValue)
        case "restart": self = .restart
        case "msg": guard let p = o["payload"] else { return nil }; self = .msg(p)
        case "bin": guard let d = o["d"]?.stringValue, let data = Bytes.fromBase64(d) else { return nil }; self = .bin(data)
        case "chunk":
            guard let id = o["id"]?.stringValue, let i = o["i"]?.intValue, let n = o["n"]?.intValue,
                  let enc = o["enc"]?.stringValue.flatMap(ChunkEncoding.init(rawValue:)),
                  let d = o["d"]?.stringValue, let data = Bytes.fromBase64(d), i >= 0, n > 0, i < n
            else { return nil }
            self = .chunk(id: id, index: i, count: n, encoding: enc, data: data)
        case "ping": self = .ping
        default: self = .unknown(kind: kind)
        }
    }
}

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