Skip to main content

tsoracle_proto/
lib.rs

1//
2//  ░▀█▀░█▀▀░█▀█░█▀▄░█▀█░█▀▀░█░░░█▀▀
3//  ░░█░░▀▀█░█░█░█▀▄░█▀█░█░░░█░░░█▀▀
4//  ░░▀░░▀▀▀░▀▀▀░▀░▀░▀░▀░▀▀▀░▀▀▀░▀▀▀
5//
6//  tsoracle — Distributed Timestamp Oracle
7//  https://www.tsoracle.rs
8//
9//  Copyright (c) 2026 Prisma Risk
10//
11//  Licensed under the Apache License, Version 2.0 (the "License");
12//  you may not use this file except in compliance with the License.
13//  You may obtain a copy of the License at
14//
15//      https://www.apache.org/licenses/LICENSE-2.0
16//
17//  Unless required by applicable law or agreed to in writing, software
18//  distributed under the License is distributed on an "AS IS" BASIS,
19//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20//  See the License for the specific language governing permissions and
21//  limitations under the License.
22//
23
24#![doc = include_str!("../README.md")]
25#![allow(clippy::all)]
26
27pub mod v1 {
28    tonic::include_proto!("tsoracle.v1");
29
30    use prost::Message;
31    use tonic::Status;
32    use tonic::metadata::MetadataKey;
33
34    /// Binary metadata trailer key carrying the [`LeaderHint`] payload on a
35    /// `NOT_LEADER` (`FAILED_PRECONDITION`) response.
36    ///
37    /// This is the single source of truth for the wire contract: the server
38    /// inserts the trailer under this key and the client decodes it from the
39    /// same key. If the two ever disagreed, the client would silently stop
40    /// following hints and degrade to round-robin, so both sides reference
41    /// this one constant rather than re-spelling the string.
42    pub const LEADER_HINT_TRAILER_KEY: &str = "tsoracle-leader-hint-bin";
43
44    /// Outcome of inspecting a `Status`'s trailers for the leader-hint payload.
45    ///
46    /// The client's retry loop treats the three cases differently: `Absent` is
47    /// the normal "this peer doesn't know who the leader is" signal and stays
48    /// silent; `Malformed` is a wire-protocol bug worth a warning + counter;
49    /// `Decoded` is the followable redirect. The server's decode path collapses
50    /// this to `Option<LeaderHint>` via a thin adapter, but the richer shape is
51    /// kept here so that distinction is not lost for callers that need it.
52    pub enum LeaderHintLookup {
53        Absent,
54        Decoded(LeaderHint),
55        Malformed,
56    }
57
58    /// Decode the [`LeaderHint`] trailer keyed by [`LEADER_HINT_TRAILER_KEY`]
59    /// from `status`, classifying the three outcomes (see [`LeaderHintLookup`]).
60    pub fn decode_leader_hint(status: &Status) -> LeaderHintLookup {
61        let Ok(key) = MetadataKey::from_bytes(LEADER_HINT_TRAILER_KEY.as_bytes()) else {
62            return LeaderHintLookup::Absent;
63        };
64        let Some(value) = status.metadata().get_bin(key) else {
65            return LeaderHintLookup::Absent;
66        };
67        let Ok(bytes) = value.to_bytes() else {
68            return LeaderHintLookup::Malformed;
69        };
70        match LeaderHint::decode(bytes.as_ref()) {
71            Ok(hint) => LeaderHintLookup::Decoded(hint),
72            Err(_) => LeaderHintLookup::Malformed,
73        }
74    }
75}
76
77/// Encoded `FileDescriptorSet` for every proto compiled by this crate.
78/// Feed to `tonic_reflection::server::Builder::register_encoded_file_descriptor_set`
79/// to expose gRPC server reflection (`grpcurl`, `evans`, Postman) without
80/// shipping the `.proto` files to clients.
81#[cfg(feature = "reflection")]
82pub const FILE_DESCRIPTOR_SET: &[u8] =
83    include_bytes!(concat!(env!("OUT_DIR"), "/tsoracle_descriptor.bin"));
84
85#[cfg(test)]
86mod tests {
87    use super::v1::*;
88    use prost::Message;
89    use tonic::Status;
90    use tonic::metadata::{BinaryMetadataKey, BinaryMetadataValue};
91
92    #[test]
93    fn message_types_exist() {
94        let _ = GetTsRequest { count: 1 };
95        let _ = GetTsResponse {
96            physical_ms: 0,
97            logical_start: 0,
98            count: 0,
99            epoch_hi: 0,
100            epoch_lo: 0,
101        };
102        let _ = LeaderHint {
103            leader_endpoint: Some("127.0.0.1:50551".into()),
104            leader_epoch: Some(EpochWire { hi: 0, lo: 1 }),
105        };
106    }
107
108    /// The leader epoch travels as a single nested `EpochWire`, so presence
109    /// implies *both* halves: a hint either carries a complete epoch or none
110    /// at all. The old two-`optional`-`uint64` shape could encode a corrupt
111    /// half-populated epoch; that state is no longer representable.
112    #[test]
113    fn leader_epoch_round_trips_as_a_single_unit() {
114        // Both halves carried — and a non-zero hi guards against a hi/lo swap
115        // that an all-low-bits value could not detect.
116        let with_epoch = LeaderHint {
117            leader_endpoint: Some("127.0.0.1:50551".into()),
118            leader_epoch: Some(EpochWire { hi: 1, lo: 3 }),
119        };
120        let decoded = LeaderHint::decode(with_epoch.encode_to_vec().as_ref()).unwrap();
121        assert_eq!(decoded.leader_epoch, Some(EpochWire { hi: 1, lo: 3 }));
122
123        // No epoch at all — the only other representable state.
124        let without_epoch = LeaderHint {
125            leader_endpoint: Some("127.0.0.1:50551".into()),
126            leader_epoch: None,
127        };
128        let decoded = LeaderHint::decode(without_epoch.encode_to_vec().as_ref()).unwrap();
129        assert_eq!(decoded.leader_epoch, None);
130    }
131
132    /// A `Status` without a `tsoracle-leader-hint-bin` trailer must decode to
133    /// `Absent` — this is the steady-state case (every response other than
134    /// NOT_LEADER, plus NOT_LEADER from a server that has no known leader) and
135    /// must not surface as `Malformed`, which would cause the client's retry
136    /// loop to count it against the wire-protocol-bug bucket.
137    #[test]
138    fn decode_leader_hint_returns_absent_when_no_trailer_present() {
139        let status = Status::failed_precondition("not leader");
140        assert!(matches!(
141            decode_leader_hint(&status),
142            LeaderHintLookup::Absent
143        ));
144    }
145
146    /// A `Status` with a `tsoracle-leader-hint-bin` trailer whose payload is
147    /// not a valid `LeaderHint` protobuf must surface as `Malformed` — the
148    /// distinction from `Absent` is what lets the client's retry loop count
149    /// wire-protocol bugs separately from "this peer doesn't know the leader."
150    #[test]
151    fn decode_leader_hint_returns_malformed_on_bad_protobuf() {
152        let mut status = Status::failed_precondition("not leader");
153        let key = BinaryMetadataKey::from_bytes(LEADER_HINT_TRAILER_KEY.as_bytes())
154            .expect("LEADER_HINT_TRAILER_KEY must be a valid binary metadata key");
155        // Bytes that are not a valid `LeaderHint` proto. A wire-tag-shaped run
156        // of `0xff` makes the decoder enter varint parsing and then fail.
157        let value = BinaryMetadataValue::from_bytes(&[0xff, 0xff, 0xff, 0xff]);
158        status.metadata_mut().insert_bin(key, value);
159        assert!(matches!(
160            decode_leader_hint(&status),
161            LeaderHintLookup::Malformed
162        ));
163    }
164
165    /// A well-formed trailer round-trips through `encode` ↔ `decode` and
166    /// surfaces as `Decoded(hint)` with the original payload preserved. The
167    /// server's `not_leader_status` is the producer; both sides must agree on
168    /// the wire shape or NOT_LEADER redirects will silently degrade.
169    #[test]
170    fn decode_leader_hint_decodes_well_formed_trailer() {
171        let mut status = Status::failed_precondition("not leader");
172        let key = BinaryMetadataKey::from_bytes(LEADER_HINT_TRAILER_KEY.as_bytes())
173            .expect("LEADER_HINT_TRAILER_KEY must be a valid binary metadata key");
174        let hint = LeaderHint {
175            leader_endpoint: Some("10.0.0.7:50551".into()),
176            leader_epoch: Some(EpochWire { hi: 0, lo: 42 }),
177        };
178        let value = BinaryMetadataValue::from_bytes(&hint.encode_to_vec());
179        status.metadata_mut().insert_bin(key, value);
180
181        match decode_leader_hint(&status) {
182            LeaderHintLookup::Decoded(decoded) => {
183                assert_eq!(decoded.leader_endpoint, hint.leader_endpoint);
184                assert_eq!(decoded.leader_epoch, hint.leader_epoch);
185            }
186            other => panic!(
187                "expected Decoded(_), got something else: {}",
188                match other {
189                    LeaderHintLookup::Absent => "Absent",
190                    LeaderHintLookup::Malformed => "Malformed",
191                    LeaderHintLookup::Decoded(_) => unreachable!(),
192                }
193            ),
194        }
195    }
196}