1mod rate_limit;
28
29pub use rate_limit::{RateLimitConfig, RateLimiters};
30
31use std::net::SocketAddr;
32use std::sync::Arc;
33
34use brain_db_sdk::transport::{read_frame, write_frame};
35use brain_db_sdk::wire::cbor::{from_cbor_bytes, to_cbor_bytes};
36use brain_db_sdk::wire::frame::{FLAG_EOS, Frame};
37use brain_db_sdk::wire::opcode::Opcode;
38use brain_db_sdk::wire::types::{
39 AuthCredentials, AuthPayload, ErrorCategoryWire, ErrorCodeWire, ErrorResponse,
40};
41use tokio::io::AsyncWriteExt;
42use tokio::net::tcp::{OwnedReadHalf, OwnedWriteHalf};
43use tokio::net::{TcpListener, TcpStream};
44use tokio::sync::Mutex;
45use tracing::{debug, info, warn};
46
47use crate::port::{MeterEvent, MeteringSink, Outcome};
48
49#[derive(Clone, Debug)]
51pub struct WireProxyConfig {
52 pub wire_listen_addr: SocketAddr,
54 pub brain_addr: SocketAddr,
56 pub rate: RateLimitConfig,
58}
59
60pub async fn serve(config: WireProxyConfig, meter: Arc<dyn MeteringSink>) -> std::io::Result<()> {
67 let listener = TcpListener::bind(config.wire_listen_addr).await?;
68 info!(
69 addr = %config.wire_listen_addr,
70 brain = %config.brain_addr,
71 "wire proxy listening"
72 );
73 let limiters = Arc::new(RateLimiters::new(config.rate));
74
75 loop {
76 let (sock, peer) = match listener.accept().await {
77 Ok(v) => v,
78 Err(e) => {
79 warn!(error = %e, "wire proxy accept failed");
80 continue;
81 }
82 };
83 let config = config.clone();
84 let meter = Arc::clone(&meter);
85 let limiters = Arc::clone(&limiters);
86 tokio::spawn(async move {
87 debug!(%peer, "wire proxy connection opened");
88 handle_conn(sock, config, meter, limiters).await;
89 debug!(%peer, "wire proxy connection closed");
90 });
91 }
92}
93
94async fn handle_conn(
100 customer: TcpStream,
101 config: WireProxyConfig,
102 meter: Arc<dyn MeteringSink>,
103 limiters: Arc<RateLimiters>,
104) {
105 let _ = customer.set_nodelay(true);
106 let (cust_rd, cust_wr) = customer.into_split();
107 let cust_wr = Arc::new(Mutex::new(cust_wr));
111
112 let brain = match TcpStream::connect(config.brain_addr).await {
113 Ok(b) => b,
114 Err(e) => {
115 warn!(brain = %config.brain_addr, error = %e, "wire proxy: brain unreachable");
116 let mut w = cust_wr.lock().await;
119 let _ = write_frame(
120 &mut *w,
121 &error_frame(
122 0,
123 ErrorCodeWire::ShardUnavailable,
124 ErrorCategoryWire::Unavailable,
125 "brain upstream unreachable",
126 ),
127 )
128 .await;
129 let _ = w.shutdown().await;
130 return;
131 }
132 };
133 let _ = brain.set_nodelay(true);
134 let (brain_rd, brain_wr) = brain.into_split();
135
136 tokio::select! {
137 () = pump_customer_to_brain(cust_rd, brain_wr, Arc::clone(&cust_wr), &meter, &limiters) => {}
138 () = pump_brain_to_customer(brain_rd, Arc::clone(&cust_wr)) => {}
139 }
140}
141
142async fn pump_brain_to_customer(mut brain_rd: OwnedReadHalf, cust_wr: Arc<Mutex<OwnedWriteHalf>>) {
145 let mut buf = Vec::new();
146 loop {
147 match read_frame(&mut brain_rd, &mut buf).await {
148 Ok(frame) => {
149 let mut w = cust_wr.lock().await;
150 if write_frame(&mut *w, &frame).await.is_err() {
151 return;
152 }
153 }
154 Err(_) => return,
156 }
157 }
158}
159
160async fn pump_customer_to_brain(
163 mut cust_rd: OwnedReadHalf,
164 mut brain_wr: OwnedWriteHalf,
165 cust_wr: Arc<Mutex<OwnedWriteHalf>>,
166 meter: &Arc<dyn MeteringSink>,
167 limiters: &Arc<RateLimiters>,
168) {
169 let mut buf = Vec::new();
170 let mut credential: Option<String> = None;
173 let mut label: Option<String> = None;
174
175 loop {
176 let Ok(frame) = read_frame(&mut cust_rd, &mut buf).await else {
177 return;
178 };
179
180 if credential.is_none() && frame.opcode == Opcode::Auth.as_u16() {
184 if let Some(token) = sniff_credential(&frame.payload) {
185 label = Some(fingerprint(&token));
186 credential = Some(token);
187 }
188 }
189
190 if !is_control_frame(frame.opcode) {
193 if let Some(cred) = &credential {
194 if !limiters.allow(cred) {
195 let ef = error_frame(
198 frame.stream_id,
199 ErrorCodeWire::RateLimited,
200 ErrorCategoryWire::ResourceExhausted,
201 "rate limit exceeded",
202 );
203 {
204 let mut w = cust_wr.lock().await;
205 if write_frame(&mut *w, &ef).await.is_err() {
206 return;
207 }
208 }
209 record(meter, label.as_deref(), frame.opcode, Outcome::Err);
210 continue;
211 }
212 }
213 record(meter, label.as_deref(), frame.opcode, Outcome::Ok);
216 }
217
218 if write_frame(&mut brain_wr, &frame).await.is_err() {
221 return;
222 }
223 }
224}
225
226fn record(meter: &Arc<dyn MeteringSink>, tenant: Option<&str>, opcode: u16, outcome: Outcome) {
228 meter.record(&MeterEvent {
229 tenant,
230 op: op_name(opcode),
231 outcome,
232 });
233}
234
235fn sniff_credential(payload: &[u8]) -> Option<String> {
241 let auth: AuthPayload = from_cbor_bytes(payload).ok()?;
242 match auth.credentials {
243 AuthCredentials::Token(bytes) => Some(String::from_utf8_lossy(&bytes).into_owned()),
244 AuthCredentials::Mtls(claim) => Some(claim.asserted_subject),
245 }
246}
247
248fn fingerprint(credential: &str) -> String {
251 use std::hash::{Hash, Hasher};
252 let mut h = std::collections::hash_map::DefaultHasher::new();
253 credential.hash(&mut h);
254 format!("cred-{:016x}", h.finish())
255}
256
257fn error_frame(
259 stream_id: u32,
260 code: ErrorCodeWire,
261 category: ErrorCategoryWire,
262 message: &str,
263) -> Frame {
264 let payload = to_cbor_bytes(&ErrorResponse {
265 code,
266 category,
267 message: message.to_string(),
268 details: None,
269 retry_after_ms: None,
270 });
271 Frame::new(Opcode::Error.as_u16(), FLAG_EOS, stream_id, payload)
272}
273
274fn is_control_frame(opcode: u16) -> bool {
276 opcode == Opcode::Hello.as_u16()
277 || opcode == Opcode::Auth.as_u16()
278 || opcode == Opcode::Bye.as_u16()
279 || opcode == Opcode::Ping.as_u16()
280 || opcode == Opcode::ClientPong.as_u16()
281}
282
283fn op_name(opcode: u16) -> &'static str {
287 match opcode {
288 o if o == Opcode::EncodeReq.as_u16() => "encode",
289 o if o == Opcode::EncodeVectorDirectReq.as_u16() => "encode_vector",
290 o if o == Opcode::RecallReq.as_u16() => "recall",
291 o if o == Opcode::PlanReq.as_u16() => "plan",
292 o if o == Opcode::ReasonReq.as_u16() => "reason",
293 o if o == Opcode::ForgetReq.as_u16() => "forget",
294 o if o == Opcode::LinkReq.as_u16() => "link",
295 o if o == Opcode::UnlinkReq.as_u16() => "unlink",
296 o if o == Opcode::MemoryListReq.as_u16() => "memory_list",
297 o if o == Opcode::MemoryInspectReq.as_u16() => "memory_inspect",
298 o if o == Opcode::SubscribeReq.as_u16() => "subscribe",
299 o if o == Opcode::UnsubscribeReq.as_u16() => "unsubscribe",
300 o if o == Opcode::TxnBegin.as_u16() => "txn_begin",
301 o if o == Opcode::TxnCommit.as_u16() => "txn_commit",
302 o if o == Opcode::TxnAbort.as_u16() => "txn_abort",
303 o if o == Opcode::GetCapabilitiesReq.as_u16() => "get_capabilities",
304 _ => "op",
305 }
306}
307
308#[cfg(test)]
309mod tests {
310 use super::*;
311 use brain_db_sdk::wire::types::{AuthMethod, MtlsClaim};
312
313 #[test]
314 fn sniffs_a_token_credential() {
315 let payload = to_cbor_bytes(&AuthPayload {
316 method: AuthMethod::Token,
317 credentials: AuthCredentials::Token(b"secret-key".to_vec()),
318 });
319 assert_eq!(sniff_credential(&payload).as_deref(), Some("secret-key"));
320 }
321
322 #[test]
323 fn sniffs_an_mtls_subject() {
324 let payload = to_cbor_bytes(&AuthPayload {
325 method: AuthMethod::Mtls,
326 credentials: AuthCredentials::Mtls(MtlsClaim {
327 cert_fingerprint: [7u8; 32],
328 asserted_subject: "spiffe://acme/edge".into(),
329 }),
330 });
331 assert_eq!(
332 sniff_credential(&payload).as_deref(),
333 Some("spiffe://acme/edge")
334 );
335 }
336
337 #[test]
338 fn malformed_auth_payload_sniffs_to_none() {
339 assert_eq!(sniff_credential(b"not-cbor-at-all"), None);
340 }
341
342 #[test]
343 fn fingerprint_is_stable_and_hides_the_raw_key() {
344 let a = fingerprint("super-secret");
345 let b = fingerprint("super-secret");
346 assert_eq!(a, b, "same credential → same label");
347 assert!(!a.contains("super-secret"), "raw key is never exposed");
348 assert_ne!(fingerprint("other"), a);
349 }
350
351 #[test]
352 fn control_frames_are_not_metered() {
353 for op in [
354 Opcode::Hello,
355 Opcode::Auth,
356 Opcode::Bye,
357 Opcode::Ping,
358 Opcode::ClientPong,
359 ] {
360 assert!(is_control_frame(op.as_u16()), "{op:?} is control");
361 }
362 assert!(!is_control_frame(Opcode::EncodeReq.as_u16()));
363 assert!(!is_control_frame(Opcode::RecallReq.as_u16()));
364 }
365
366 #[test]
367 fn op_names_cover_the_common_verbs_and_default() {
368 assert_eq!(op_name(Opcode::EncodeReq.as_u16()), "encode");
369 assert_eq!(op_name(Opcode::RecallReq.as_u16()), "recall");
370 assert_eq!(op_name(0xFFFF), "op");
371 }
372
373 #[test]
374 fn error_frame_round_trips_as_a_terminal_error() {
375 let f = error_frame(
376 5,
377 ErrorCodeWire::RateLimited,
378 ErrorCategoryWire::ResourceExhausted,
379 "rate limit exceeded",
380 );
381 assert_eq!(f.opcode, Opcode::Error.as_u16());
382 assert_eq!(f.stream_id, 5);
383 assert!(f.flags & FLAG_EOS != 0, "error frame terminates the stream");
384 let decoded: ErrorResponse = from_cbor_bytes(&f.payload).expect("decode error payload");
385 assert_eq!(decoded.code, ErrorCodeWire::RateLimited);
386 assert_eq!(decoded.message, "rate limit exceeded");
387 }
388}