p2p-swift / Sources / IrdP2PSignaling / Frames.swift
346 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
import Foundation
import IrdP2PCrypto

/// Frames a client sends on `/ws`. Every frame is a JSON text message with a `t` field.
public enum ClientFrame: Sendable, Equatable {
    case join(room: RoomId, peer: PeerId)
    case signal(to: PeerId, payload: JSONValue)
    case leave
    case ping

    /// The wire text, keys in the documented order.
    public var encoded: String { json.serialized }

    public var json: JSONValue {
        switch self {
        case .join(let room, let peer):
            return .object(JSONObject([("t", .string("join")), ("room", .string(room.string)), ("peer", .string(peer.hex))]))
        case .signal(let to, let payload):
            return .object(JSONObject([("t", .string("signal")), ("to", .string(to.hex)), ("payload", payload)]))
        case .leave: return .object(["t": .string("leave")])
        case .ping: return .object(["t": .string("ping")])
        }
    }
}

/// Frames the relay sends. Unknown types decode to `.unknown` and unknown keys are
/// ignored: additive protocol changes ship without a version bump.
public enum ServerFrame: Sendable, Equatable {
    /// Reply to `join`: who is already in the room (never includes yourself).
    case peers([PeerId])
    case peerJoined(PeerId)
    case peerLeft(PeerId)
    /// A forwarded payload; `from` is stamped by the relay and cannot be spoofed.
    case signal(from: PeerId, payload: JSONValue)
    case pong
    case error(RelayErrorCode)
    case unknown(type: String)

    public struct DecodeError: Error, Sendable, Equatable { public let reason: String }

    public static func decode(_ text: String) throws -> ServerFrame {
        let v: JSONValue
        do { v = try JSONValue.parse(text) } catch { throw DecodeError(reason: "not JSON: \(error)") }
        return try decode(v)
    }

    public static func decode(_ v: JSONValue) throws -> ServerFrame {
        guard case .object(let o) = v, let t = o["t"]?.stringValue else { throw DecodeError(reason: "missing t") }
        switch t {
        case "peers":
            let list = o["peers"]?.arrayValue ?? []
            return .peers(try list.map { try peer($0) })
        case "peer-joined": return .peerJoined(try peer(o["peer"]))
        case "peer-left": return .peerLeft(try peer(o["peer"]))
        case "signal":
            guard let payload = o["payload"] else { throw DecodeError(reason: "signal without payload") }
            return .signal(from: try peer(o["from"]), payload: payload)
        case "pong": return .pong
        case "error": return .error(RelayErrorCode(o["code"]?.stringValue ?? ""))
        default: return .unknown(type: t)
        }
    }

    private static func peer(_ v: JSONValue?) throws -> PeerId {
        guard let s = v?.stringValue, let p = try? PeerId(s) else { throw DecodeError(reason: "bad peer id") }
        return p
    }
}

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