| 1 | // Package protocol is the frozen Extension Protocol v1 wire contract between |
| 2 | // the Reasonix host and out-of-process extension sidecars. It is a public |
| 3 | // protocol: sidecars are written against this package's generated JSON Schema |
| 4 | // and its compatibility hash, not against Reasonix internals. |
| 5 | // |
| 6 | // Stability contract: within major version 1, only optional fields, new enum |
| 7 | // values, and new methods may be added. Existing required fields, method |
| 8 | // names, directions, limits, error reasons, and semantics never change. Any |
| 9 | // such change requires a new major protocol version. |
| 10 | // |
| 11 | // The sidecar process is the "extension" peer; Reasonix is the "host" peer. |
| 12 | // Directions are named from the host's point of view: host_to_extension_* |
| 13 | // flows from Reasonix to the sidecar, extension_to_host_* flows back. |
| 14 | package protocol |
| 15 | |
| 16 | import ( |
| 17 | "fmt" |
| 18 | "strconv" |
| 19 | ) |
| 20 | |
| 21 | // ProtocolID is the immutable identity string peers exchange during the |
| 22 | // initialize handshake. It is also the generated schema document's $id. |
| 23 | const ProtocolID = "reasonix.extension.v1" |
| 24 | |
| 25 | // ProtocolMajor is the frozen major version of this protocol build. |
| 26 | const ProtocolMajor = 1 |
| 27 | |
| 28 | // ProtocolVersion is the wire string form of ProtocolMajor carried in the |
| 29 | // initialize handshake. |
| 30 | const ProtocolVersion = "1" |
| 31 | |
| 32 | // NoResult is the result placeholder for notifications, which carry no |
| 33 | // response payload. |
| 34 | type NoResult struct{} |
| 35 | |
| 36 | // CompareProtocolVersion validates a peer's handshake identity for the |
| 37 | // extension protocol: the protocol ID must match ProtocolID exactly and the |
| 38 | // peer's major version must equal ProtocolMajor. Mismatches return the frozen |
| 39 | // unsupported_version or protocol_error *ProtocolError so transports can |
| 40 | // answer the handshake with a structured error instead of an ad-hoc string. |
| 41 | func CompareProtocolVersion(peerID, peerVersion string) error { |
| 42 | if peerID != ProtocolID { |
| 43 | return MustProtocolError(ErrUnsupportedVersion) |
| 44 | } |
| 45 | major, err := strconv.Atoi(peerVersion) |
| 46 | if err != nil { |
| 47 | return MustProtocolError(ErrProtocolError) |
| 48 | } |
| 49 | if major != ProtocolMajor { |
| 50 | return MustProtocolError(ErrUnsupportedVersion) |
| 51 | } |
| 52 | return nil |
| 53 | } |
| 54 | |
| 55 | // HandshakeIdentity is the SchemaHash-bearing identity line a peer may log or |
| 56 | // compare after a successful CompareProtocolVersion check. Two peers with |
| 57 | // equal schema hashes run byte-identical contracts. |
| 58 | func HandshakeIdentity() string { |
| 59 | return fmt.Sprintf("%s major=%d schema=%s", ProtocolID, ProtocolMajor, SchemaHash()) |
| 60 | } |
| 61 |