Skip to main content

omp_rpc/
lib.rs

1//! Transport plumbing for the omp gRPC protocol.
2//!
3//! The daemon serves local clients over an owner-only Unix-domain socket; `omp
4//! gateway serve` exposes the same services over TCP with mutual TLS. Every
5//! connection starts with the gateway Hello handshake. A client rejects a
6//! server whose schema revision is older than its own, because protobuf's
7//! unknown-field behavior would otherwise silently discard newer client data.
8//!
9//! Liveness and per-service readiness use the standard `grpc.health.v1`
10//! protocol.
11
12use omp_core::Str;
13
14pub mod health;
15pub mod hello;
16pub mod tls;
17pub mod uds;
18
19pub use health::{HealthReporter, health_service};
20pub use hello::{HelloService, MIN_SCHEMA_REV, Peer, handshake};
21pub use tls::{TlsConfig, client_tls, server_tls};
22pub use uds::{Incoming, connect, listen};
23
24/// An RPC transport or protocol-negotiation failure.
25pub enum Error {
26	/// A filesystem, socket, or stream operation failed.
27	Io(std::io::Error),
28	/// Tonic could not establish or configure a transport.
29	Transport(tonic::transport::Error),
30	/// A gRPC request failed after the transport was established.
31	Rpc(tonic::Status),
32	/// TLS material was invalid or could not be configured.
33	Tls(Str),
34	/// The server schema is older than the client schema.
35	SchemaTooOld {
36		/// Revision advertised by the server.
37		server: u32,
38		/// Revision sent by the client.
39		client: u32,
40	},
41	/// The client does not implement the oldest schema accepted by the server.
42	SchemaUnsupported {
43		/// Minimum revision accepted by the server.
44		server_min: u32,
45		/// Revision implemented by the client.
46		client:     u32,
47	},
48	/// The requested transport is unavailable on this operating system.
49	Unsupported(&'static str),
50}
51
52impl std::fmt::Debug for Error {
53	fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
54		std::fmt::Display::fmt(self, formatter)
55	}
56}
57
58impl std::fmt::Display for Error {
59	fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
60		match self {
61			Self::Io(error) => write!(formatter, "I/O error ({:?})", error.kind()),
62			Self::Transport(_) => formatter.write_str("transport error"),
63			Self::Rpc(status) => write!(formatter, "RPC error ({:?})", status.code()),
64			Self::Tls(_) => formatter.write_str("TLS configuration error"),
65			Self::SchemaTooOld { server, client } => write!(
66				formatter,
67				"server schema revision {server} is older than client revision {client}"
68			),
69			Self::SchemaUnsupported { server_min, client } => write!(
70				formatter,
71				"client schema revision {client} is below server minimum {server_min}"
72			),
73			Self::Unsupported(kind) => write!(formatter, "unsupported transport: {kind}"),
74		}
75	}
76}
77
78impl std::error::Error for Error {}
79
80impl From<std::io::Error> for Error {
81	fn from(error: std::io::Error) -> Self {
82		Self::Io(error)
83	}
84}
85
86impl From<tonic::transport::Error> for Error {
87	fn from(error: tonic::transport::Error) -> Self {
88		Self::Transport(error)
89	}
90}
91
92impl From<tonic::Status> for Error {
93	fn from(status: tonic::Status) -> Self {
94		Self::Rpc(status)
95	}
96}
97
98#[cfg(test)]
99mod tests {
100	use super::Error;
101
102	#[test]
103	fn observable_error_surfaces_discard_untrusted_diagnostics() {
104		const CANARY: &str = "canary-private-key-and-access-token";
105		let errors = [
106			Error::Io(std::io::Error::other(CANARY)),
107			Error::Rpc(tonic::Status::permission_denied(CANARY)),
108			Error::Tls(CANARY.into()),
109		];
110
111		for error in errors {
112			assert!(!error.to_string().contains(CANARY));
113			assert!(!format!("{error:?}").contains(CANARY));
114			assert!(std::error::Error::source(&error).is_none());
115		}
116	}
117}