1use std::future::Future;
4
5use crate::pipeline::{NoPipeline, Pipeline, PipelinePolicy};
6
7pub type IntermediaryTransition<Current, Next, Output, Error> =
9 Result<(Next, Output), (Current, Error)>;
10
11#[must_use = "dropping an intermediary abandons both PostgreSQL sessions"]
19#[derive(Debug)]
20pub struct Intermediary<Downstream, Upstream, Policy = NoPipeline> {
21 downstream: Downstream,
22 upstream: Upstream,
23 pipeline: Pipeline<Policy>,
24}
25
26impl<Downstream, Upstream> Intermediary<Downstream, Upstream, NoPipeline> {
27 pub fn new(downstream: Downstream, upstream: Upstream) -> Self {
29 Self {
30 downstream,
31 upstream,
32 pipeline: Pipeline::new(NoPipeline),
33 }
34 }
35}
36
37impl<Downstream, Upstream, Policy: PipelinePolicy> Intermediary<Downstream, Upstream, Policy> {
38 pub fn with_pipeline<Next: PipelinePolicy>(
44 self,
45 policy: Next,
46 ) -> Intermediary<Downstream, Upstream, Next> {
47 let Self {
48 downstream,
49 upstream,
50 pipeline,
51 } = self;
52 assert!(
53 pipeline.is_empty(),
54 "pipeline policy cannot change with outstanding operations"
55 );
56 Intermediary {
57 downstream,
58 upstream,
59 pipeline: Pipeline::new(policy),
60 }
61 }
62
63 pub const fn pipeline(&self) -> &Pipeline<Policy> {
65 &self.pipeline
66 }
67
68 pub const fn pipeline_mut(&mut self) -> &mut Pipeline<Policy> {
70 &mut self.pipeline
71 }
72
73 pub const fn downstream(&self) -> &Downstream {
75 &self.downstream
76 }
77
78 pub const fn upstream(&self) -> &Upstream {
80 &self.upstream
81 }
82
83 pub const fn sides_mut(&mut self) -> (&mut Downstream, &mut Upstream) {
85 (&mut self.downstream, &mut self.upstream)
86 }
87
88 pub fn into_parts(self) -> (Downstream, Upstream) {
90 (self.downstream, self.upstream)
91 }
92
93 pub fn transition_downstream<Next, Output, Error>(
104 self,
105 transition: impl FnOnce(Downstream) -> Result<(Next, Output), (Downstream, Error)>,
106 ) -> IntermediaryTransition<Self, Intermediary<Next, Upstream, Policy>, Output, Error> {
107 let Self {
108 downstream,
109 upstream,
110 pipeline,
111 } = self;
112 match transition(downstream) {
113 Ok((downstream, output)) => Ok((
114 Intermediary {
115 downstream,
116 upstream,
117 pipeline,
118 },
119 output,
120 )),
121 Err((downstream, error)) => Err((
122 Self {
123 downstream,
124 upstream,
125 pipeline,
126 },
127 error,
128 )),
129 }
130 }
131
132 pub fn transition_upstream<Next, Output, Error>(
140 self,
141 transition: impl FnOnce(Upstream) -> Result<(Next, Output), (Upstream, Error)>,
142 ) -> IntermediaryTransition<Self, Intermediary<Downstream, Next, Policy>, Output, Error> {
143 let Self {
144 downstream,
145 upstream,
146 pipeline,
147 } = self;
148 match transition(upstream) {
149 Ok((upstream, output)) => Ok((
150 Intermediary {
151 downstream,
152 upstream,
153 pipeline,
154 },
155 output,
156 )),
157 Err((upstream, error)) => Err((
158 Self {
159 downstream,
160 upstream,
161 pipeline,
162 },
163 error,
164 )),
165 }
166 }
167
168 pub fn inspect<Message, Output, Error>(
177 &mut self,
178 message: Message,
179 inspect: impl FnOnce(&mut Downstream, &mut Upstream, Message) -> Result<Output, Error>,
180 ) -> Result<Output, Error> {
181 inspect(&mut self.downstream, &mut self.upstream, message)
182 }
183
184 pub async fn inspect_async<Message, Output, Error, Work>(
190 &mut self,
191 message: Message,
192 inspect: impl FnOnce(&mut Downstream, &mut Upstream, Message) -> Work,
193 ) -> Result<Output, Error>
194 where
195 Work: Future<Output = Result<Output, Error>>,
196 {
197 inspect(&mut self.downstream, &mut self.upstream, message).await
198 }
199}
200
201#[cfg(test)]
202mod tests {
203 use bytes::Bytes;
204
205 use super::Intermediary;
206 use crate::{
207 Conn, Pristine,
208 auth::{Auth, AuthOffer, SaslInitial, TlsServerEndPoint},
209 codec::Authentication,
210 grammar::{backend, frontend},
211 server_auth::{ServerAuth, ServerPassword},
212 };
213
214 #[derive(Debug)]
215 struct ClientFacingTls;
216
217 #[derive(Debug)]
218 struct UpstreamTls;
219
220 impl TlsServerEndPoint for ClientFacingTls {
221 fn tls_server_end_point(&self) -> &[u8] {
222 b"client-facing-certificate"
223 }
224 }
225
226 impl TlsServerEndPoint for UpstreamTls {
227 fn tls_server_end_point(&self) -> &[u8] {
228 b"upstream-certificate"
229 }
230 }
231
232 #[test]
233 fn each_side_transitions_without_coupling_the_other() {
234 #[derive(Debug)]
235 struct Clean;
236
237 let downstream: backend::TypedSession<(), backend::Ready, Clean> =
238 backend::TypedSession::with_transport(());
239 let upstream: frontend::TypedSession<(), frontend::Ready, Clean> =
240 frontend::TypedSession::with_transport(());
241 let intermediary = Intermediary::new(downstream, upstream);
242
243 let (intermediary, downstream_query) = intermediary
244 .transition_downstream(|session| {
245 session.query(Bytes::from_static(b"select 1"), |(), query| {
246 Ok::<_, &'static str>(query)
247 })
248 })
249 .expect("downstream inspection succeeds");
250 assert_eq!(downstream_query, Bytes::from_static(b"select 1"));
251
252 let (intermediary, upstream_query) = intermediary
253 .transition_upstream(|session| {
254 session.query(Bytes::from_static(b"select 2"), |(), query| {
255 Ok::<_, &'static str>(query)
256 })
257 })
258 .expect("upstream inspection succeeds");
259 assert_eq!(upstream_query, Bytes::from_static(b"select 2"));
260
261 let (_downstream, _upstream): (
262 backend::TypedSession<(), backend::Simple, backend::Dirty>,
263 frontend::TypedSession<(), frontend::Simple, frontend::Dirty>,
264 ) = intermediary.into_parts();
265 }
266
267 #[test]
268 fn tls_and_authentication_mechanisms_remain_asymmetric() {
269 let downstream: Conn<ClientFacingTls, ServerAuth, Pristine> =
270 Conn::new(ClientFacingTls).transition();
271 let upstream: Conn<UpstreamTls, Auth, Pristine> = Conn::new(UpstreamTls).transition();
272
273 let (downstream, cleartext_request) = downstream.request_cleartext().unwrap();
274 let AuthOffer::Sasl {
275 conn: upstream,
276 mechanisms,
277 } = upstream
278 .offer(Authentication::Sasl {
279 mechanisms: vec![Bytes::from_static(b"SCRAM-SHA-256-PLUS")],
280 })
281 .unwrap()
282 else {
283 panic!("upstream did not independently select SASL")
284 };
285 assert_eq!(cleartext_request.tag, b'R');
286 assert_eq!(mechanisms, [Bytes::from_static(b"SCRAM-SHA-256-PLUS")]);
287
288 let intermediary: Intermediary<
289 Conn<ClientFacingTls, ServerPassword, Pristine>,
290 Conn<UpstreamTls, SaslInitial, Pristine>,
291 > = Intermediary::new(downstream, upstream);
292 assert_eq!(
293 intermediary.downstream().tls_server_end_point(),
294 b"client-facing-certificate"
295 );
296 assert_eq!(
297 intermediary.upstream().tls_server_end_point(),
298 b"upstream-certificate"
299 );
300 let (downstream, upstream) = intermediary.into_parts();
301 let _downstream_transport = downstream.into_transport();
302 let _upstream_transport = upstream.into_transport();
303 }
304
305 #[test]
306 fn rejected_transition_reconstructs_both_original_sides() {
307 let intermediary = Intermediary::new(vec![1_u8], vec![2_u8]);
308 let (intermediary, error) = intermediary
309 .transition_downstream(|downstream| Err::<(Vec<u8>, ()), _>((downstream, "reject")))
310 .unwrap_err();
311 assert_eq!(error, "reject");
312 assert_eq!(intermediary.into_parts(), (vec![1], vec![2]));
313 }
314
315 #[tokio::test]
316 async fn asynchronous_policy_can_modify_or_replace_a_typed_message() {
317 let mut intermediary = Intermediary::new(Vec::<Bytes>::new(), Vec::<Bytes>::new());
318 let rewritten = intermediary
319 .inspect_async(
320 Bytes::from_static(b"select secret"),
321 |downstream, upstream, query| {
322 downstream.push(query);
323 upstream.push(Bytes::from_static(b"select public"));
324 std::future::ready(Ok::<_, std::convert::Infallible>(Bytes::from_static(
325 b"select public",
326 )))
327 },
328 )
329 .await
330 .unwrap();
331 assert_eq!(rewritten, Bytes::from_static(b"select public"));
332 assert_eq!(intermediary.downstream().len(), 1);
333 assert_eq!(intermediary.upstream().len(), 1);
334 }
335}