1 use crate::{metadata::GRPC_TIMEOUT_HEADER, TimeoutExpired};
2 use http::{HeaderMap, HeaderValue, Request};
3 use pin_project::pin_project;
4 use std::{
5     future::Future,
6     pin::Pin,
7     task::{ready, Context, Poll},
8     time::Duration,
9 };
10 use tokio::time::Sleep;
11 use tower_service::Service;
12 
13 #[derive(Debug, Clone)]
14 pub(crate) struct GrpcTimeout<S> {
15     inner: S,
16     server_timeout: Option<Duration>,
17 }
18 
19 impl<S> GrpcTimeout<S> {
new(inner: S, server_timeout: Option<Duration>) -> Self20     pub(crate) fn new(inner: S, server_timeout: Option<Duration>) -> Self {
21         Self {
22             inner,
23             server_timeout,
24         }
25     }
26 }
27 
28 impl<S, ReqBody> Service<Request<ReqBody>> for GrpcTimeout<S>
29 where
30     S: Service<Request<ReqBody>>,
31     S::Error: Into<crate::BoxError>,
32 {
33     type Response = S::Response;
34     type Error = crate::BoxError;
35     type Future = ResponseFuture<S::Future>;
36 
poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>>37     fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
38         self.inner.poll_ready(cx).map_err(Into::into)
39     }
40 
call(&mut self, req: Request<ReqBody>) -> Self::Future41     fn call(&mut self, req: Request<ReqBody>) -> Self::Future {
42         let client_timeout = try_parse_grpc_timeout(req.headers()).unwrap_or_else(|e| {
43             tracing::trace!("Error parsing `grpc-timeout` header {:?}", e);
44             None
45         });
46 
47         // Use the shorter of the two durations, if either are set
48         let timeout_duration = match (client_timeout, self.server_timeout) {
49             (None, None) => None,
50             (Some(dur), None) => Some(dur),
51             (None, Some(dur)) => Some(dur),
52             (Some(header), Some(server)) => {
53                 let shorter_duration = std::cmp::min(header, server);
54                 Some(shorter_duration)
55             }
56         };
57 
58         ResponseFuture {
59             inner: self.inner.call(req),
60             sleep: timeout_duration.map(tokio::time::sleep),
61         }
62     }
63 }
64 
65 #[pin_project]
66 pub(crate) struct ResponseFuture<F> {
67     #[pin]
68     inner: F,
69     #[pin]
70     sleep: Option<Sleep>,
71 }
72 
73 impl<F, Res, E> Future for ResponseFuture<F>
74 where
75     F: Future<Output = Result<Res, E>>,
76     E: Into<crate::BoxError>,
77 {
78     type Output = Result<Res, crate::BoxError>;
79 
poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output>80     fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
81         let this = self.project();
82 
83         if let ready @ Poll::Ready(_) = this.inner.poll(cx) {
84             return ready.map_err(Into::into);
85         }
86 
87         if let Some(sleep) = this.sleep.as_pin_mut() {
88             ready!(sleep.poll(cx));
89             return Poll::Ready(Err(TimeoutExpired(()).into()));
90         }
91 
92         Poll::Pending
93     }
94 }
95 
96 const SECONDS_IN_HOUR: u64 = 60 * 60;
97 const SECONDS_IN_MINUTE: u64 = 60;
98 
99 /// Tries to parse the `grpc-timeout` header if it is present. If we fail to parse, returns
100 /// the value we attempted to parse.
101 ///
102 /// Follows the [gRPC over HTTP2 spec](https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md).
try_parse_grpc_timeout( headers: &HeaderMap<HeaderValue>, ) -> Result<Option<Duration>, &HeaderValue>103 fn try_parse_grpc_timeout(
104     headers: &HeaderMap<HeaderValue>,
105 ) -> Result<Option<Duration>, &HeaderValue> {
106     let Some(val) = headers.get(GRPC_TIMEOUT_HEADER) else {
107         return Ok(None);
108     };
109 
110     let (timeout_value, timeout_unit) = val
111         .to_str()
112         .map_err(|_| val)
113         .and_then(|s| if s.is_empty() { Err(val) } else { Ok(s) })?
114         // `HeaderValue::to_str` only returns `Ok` if the header contains ASCII so this
115         // `split_at` will never panic from trying to split in the middle of a character.
116         // See https://docs.rs/http/1/http/header/struct.HeaderValue.html#method.to_str
117         //
118         // `len - 1` also wont panic since we just checked `s.is_empty`.
119         .split_at(val.len() - 1);
120 
121     // gRPC spec specifies `TimeoutValue` will be at most 8 digits
122     // Caping this at 8 digits also prevents integer overflow from ever occurring
123     if timeout_value.len() > 8 {
124         return Err(val);
125     }
126 
127     let timeout_value: u64 = timeout_value.parse().map_err(|_| val)?;
128 
129     let duration = match timeout_unit {
130         // Hours
131         "H" => Duration::from_secs(timeout_value * SECONDS_IN_HOUR),
132         // Minutes
133         "M" => Duration::from_secs(timeout_value * SECONDS_IN_MINUTE),
134         // Seconds
135         "S" => Duration::from_secs(timeout_value),
136         // Milliseconds
137         "m" => Duration::from_millis(timeout_value),
138         // Microseconds
139         "u" => Duration::from_micros(timeout_value),
140         // Nanoseconds
141         "n" => Duration::from_nanos(timeout_value),
142         _ => return Err(val),
143     };
144 
145     Ok(Some(duration))
146 }
147 
148 #[cfg(test)]
149 mod tests {
150     use super::*;
151     use quickcheck::{Arbitrary, Gen};
152     use quickcheck_macros::quickcheck;
153 
154     // Helper function to reduce the boiler plate of our test cases
setup_map_try_parse(val: Option<&str>) -> Result<Option<Duration>, HeaderValue>155     fn setup_map_try_parse(val: Option<&str>) -> Result<Option<Duration>, HeaderValue> {
156         let mut hm = HeaderMap::new();
157         if let Some(v) = val {
158             let hv = HeaderValue::from_str(v).unwrap();
159             hm.insert(GRPC_TIMEOUT_HEADER, hv);
160         };
161 
162         try_parse_grpc_timeout(&hm).map_err(|e| e.clone())
163     }
164 
165     #[test]
test_hours()166     fn test_hours() {
167         let parsed_duration = setup_map_try_parse(Some("3H")).unwrap().unwrap();
168         assert_eq!(Duration::from_secs(3 * 60 * 60), parsed_duration);
169     }
170 
171     #[test]
test_minutes()172     fn test_minutes() {
173         let parsed_duration = setup_map_try_parse(Some("1M")).unwrap().unwrap();
174         assert_eq!(Duration::from_secs(60), parsed_duration);
175     }
176 
177     #[test]
test_seconds()178     fn test_seconds() {
179         let parsed_duration = setup_map_try_parse(Some("42S")).unwrap().unwrap();
180         assert_eq!(Duration::from_secs(42), parsed_duration);
181     }
182 
183     #[test]
test_milliseconds()184     fn test_milliseconds() {
185         let parsed_duration = setup_map_try_parse(Some("13m")).unwrap().unwrap();
186         assert_eq!(Duration::from_millis(13), parsed_duration);
187     }
188 
189     #[test]
test_microseconds()190     fn test_microseconds() {
191         let parsed_duration = setup_map_try_parse(Some("2u")).unwrap().unwrap();
192         assert_eq!(Duration::from_micros(2), parsed_duration);
193     }
194 
195     #[test]
test_nanoseconds()196     fn test_nanoseconds() {
197         let parsed_duration = setup_map_try_parse(Some("82n")).unwrap().unwrap();
198         assert_eq!(Duration::from_nanos(82), parsed_duration);
199     }
200 
201     #[test]
test_header_not_present()202     fn test_header_not_present() {
203         let parsed_duration = setup_map_try_parse(None).unwrap();
204         assert!(parsed_duration.is_none());
205     }
206 
207     #[test]
208     #[should_panic(expected = "82f")]
test_invalid_unit()209     fn test_invalid_unit() {
210         // "f" is not a valid TimeoutUnit
211         setup_map_try_parse(Some("82f")).unwrap().unwrap();
212     }
213 
214     #[test]
215     #[should_panic(expected = "123456789H")]
test_too_many_digits()216     fn test_too_many_digits() {
217         // gRPC spec states TimeoutValue will be at most 8 digits
218         setup_map_try_parse(Some("123456789H")).unwrap().unwrap();
219     }
220 
221     #[test]
222     #[should_panic(expected = "oneH")]
test_invalid_digits()223     fn test_invalid_digits() {
224         // gRPC spec states TimeoutValue will be at most 8 digits
225         setup_map_try_parse(Some("oneH")).unwrap().unwrap();
226     }
227 
228     #[quickcheck]
fuzz(header_value: HeaderValueGen) -> bool229     fn fuzz(header_value: HeaderValueGen) -> bool {
230         let header_value = header_value.0;
231 
232         // this just shouldn't panic
233         let _ = setup_map_try_parse(Some(&header_value));
234 
235         true
236     }
237 
238     /// Newtype to implement `Arbitrary` for generating `String`s that are valid `HeaderValue`s.
239     #[derive(Clone, Debug)]
240     struct HeaderValueGen(String);
241 
242     impl Arbitrary for HeaderValueGen {
arbitrary(g: &mut Gen) -> Self243         fn arbitrary(g: &mut Gen) -> Self {
244             let max = g.choose(&(1..70).collect::<Vec<_>>()).copied().unwrap();
245             Self(gen_string(g, 0, max))
246         }
247     }
248 
249     // copied from https://github.com/hyperium/http/blob/master/tests/header_map_fuzz.rs
gen_string(g: &mut Gen, min: usize, max: usize) -> String250     fn gen_string(g: &mut Gen, min: usize, max: usize) -> String {
251         let bytes: Vec<_> = (min..max)
252             .map(|_| {
253                 // Chars to pick from
254                 g.choose(b"ABCDEFGHIJKLMNOPQRSTUVabcdefghilpqrstuvwxyz----")
255                     .copied()
256                     .unwrap()
257             })
258             .collect();
259 
260         String::from_utf8(bytes).unwrap()
261     }
262 }
263