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}