1use std::convert::Infallible;
2use std::fmt;
3use std::net::{IpAddr, SocketAddr};
4use std::str::FromStr;
5
6use axum::RequestPartsExt;
7use axum::extract::rejection::PathRejection;
8use axum::extract::{ConnectInfo, FromRequestParts, OptionalFromRequestParts, Path};
9use axum::http::StatusCode;
10use axum::http::header::{self, AsHeaderName};
11use axum::http::request::Parts;
12use axum::response::{IntoResponse, Response};
13use chrono::{DateTime, Utc};
14use data_encoding::BASE64;
15use relay_auth::{RelayId, Signature};
16use relay_base_schema::organization::OrganizationId;
17use relay_base_schema::project::{ParseProjectKeyError, ProjectId, ProjectKey};
18use relay_common::{Auth, Dsn, ParseAuthError, ParseDsnError, Scheme};
19use relay_config::UpstreamDescriptor;
20use relay_event_normalization::{ClientHints, RawUserAgentInfo};
21use relay_quotas::Scoping;
22use serde::{Deserialize, Serialize};
23use url::Url;
24
25use crate::envelope::ClientName;
26use crate::extractors::{ForwardedFor, ReceivedAt, SignatureError};
27use crate::service::ServiceState;
28use crate::statsd::RelayCounters;
29use crate::utils::ApiErrorResponse;
30
31#[derive(Debug, thiserror::Error)]
32pub enum BadEventMeta {
33 #[error("missing authorization information")]
34 MissingAuth,
35
36 #[error("multiple authorization payloads detected")]
37 MultipleAuth,
38
39 #[error("unsupported protocol version ({0})")]
40 UnsupportedProtocolVersion(u16),
41
42 #[error("bad envelope authentication header")]
43 BadEnvelopeAuth(#[source] serde_json::Error),
44
45 #[error("bad project path parameter")]
46 BadProject(#[from] PathRejection),
47
48 #[error("bad x-sentry-auth header")]
49 BadAuth(#[from] ParseAuthError),
50
51 #[error("bad sentry DSN public key")]
52 BadPublicKey(#[from] ParseProjectKeyError),
53
54 #[error("bad x-sentry-relay-signature header")]
55 SignatureError(SignatureError),
56}
57
58impl From<Infallible> for BadEventMeta {
59 fn from(infallible: Infallible) -> Self {
60 match infallible {}
61 }
62}
63
64impl IntoResponse for BadEventMeta {
65 fn into_response(self) -> Response {
66 let code = match self {
67 Self::MissingAuth
68 | Self::MultipleAuth
69 | Self::BadAuth(_)
70 | Self::BadEnvelopeAuth(_) => StatusCode::UNAUTHORIZED,
71 Self::UnsupportedProtocolVersion(_)
72 | Self::BadProject(_)
73 | Self::BadPublicKey(_)
74 | Self::SignatureError(_) => StatusCode::BAD_REQUEST,
75 };
76
77 (code, ApiErrorResponse::from_error(&self)).into_response()
78 }
79}
80
81#[derive(Clone, Eq, PartialEq)]
90pub struct PartialDsn {
91 pub scheme: Scheme,
92 pub public_key: ProjectKey,
93 pub host: String,
94 pub port: u16,
95 pub path: String,
96 pub project_id: Option<ProjectId>,
97}
98
99impl PartialDsn {
100 fn from_dsn(dsn: Dsn) -> Result<Self, ParseDsnError> {
102 let project_id = dsn
103 .project_id()
104 .value()
105 .parse()
106 .map_err(|_| ParseDsnError::NoProjectId)?;
107
108 let public_key = dsn
109 .public_key()
110 .parse()
111 .map_err(|_| ParseDsnError::NoUsername)?;
112
113 Ok(Self {
114 scheme: dsn.scheme(),
115 public_key,
116 host: dsn.host().to_owned(),
117 port: dsn.port(),
118 path: dsn.path().to_owned(),
119 project_id: Some(project_id),
120 })
121 }
122
123 pub fn outbound(scoping: &Scoping, upstream: &UpstreamDescriptor) -> Self {
125 Self {
126 scheme: upstream.scheme(),
127 public_key: scoping.project_key,
128 host: upstream.host().to_owned(),
129 port: upstream.port(),
130 path: "".to_owned(),
131 project_id: Some(scoping.project_id),
132 }
133 }
134
135 pub fn project_id(&self) -> Option<ProjectId> {
137 self.project_id
138 }
139
140 pub fn public_key(&self) -> ProjectKey {
142 self.public_key
143 }
144}
145
146impl fmt::Display for PartialDsn {
147 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
148 write!(f, "{}://{}:@{}", self.scheme, self.public_key, self.host)?;
149 if self.port != self.scheme.default_port() {
150 write!(f, ":{}", self.port)?;
151 }
152 let project_id = self.project_id.unwrap_or_else(|| ProjectId::new(0));
153 write!(f, "/{}{}", self.path.trim_start_matches('/'), project_id)
154 }
155}
156
157impl fmt::Debug for PartialDsn {
158 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
159 write!(f, "{self}")
160 }
161}
162
163impl FromStr for PartialDsn {
164 type Err = ParseDsnError;
165
166 fn from_str(s: &str) -> Result<Self, Self::Err> {
167 Self::from_dsn(Dsn::from_str(s)?)
168 }
169}
170
171impl<'de> Deserialize<'de> for PartialDsn {
172 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
173 where
174 D: serde::Deserializer<'de>,
175 {
176 let dsn = Dsn::deserialize(deserializer)?;
177 Self::from_dsn(dsn).map_err(serde::de::Error::custom)
178 }
179}
180
181impl Serialize for PartialDsn {
182 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
183 where
184 S: serde::Serializer,
185 {
186 serializer.collect_str(self)
187 }
188}
189
190const fn default_version() -> u16 {
191 relay_event_schema::protocol::PROTOCOL_VERSION
192}
193
194fn is_false(value: &bool) -> bool {
195 !*value
196}
197
198fn make_false() -> bool {
199 false
200}
201
202#[derive(Clone, Serialize, Deserialize, PartialEq)]
204pub struct RequestMeta<D = PartialDsn> {
205 dsn: D,
207
208 #[serde(default, skip_serializing_if = "Option::is_none")]
210 client: Option<String>,
211
212 #[serde(default = "default_version")]
214 version: u16,
215
216 #[serde(default, skip_serializing_if = "Option::is_none")]
218 origin: Option<Url>,
219
220 #[serde(default, skip_serializing_if = "Option::is_none")]
222 remote_addr: Option<IpAddr>,
223
224 #[serde(default, skip_serializing_if = "String::is_empty")]
226 forwarded_for: String,
227
228 #[serde(default, skip_serializing_if = "Option::is_none")]
230 user_agent: Option<String>,
231
232 #[serde(default, skip_serializing_if = "ClientHints::is_empty")]
233 client_hints: ClientHints<String>,
234
235 #[serde(default = "make_false", skip_serializing_if = "is_false")]
237 no_cache: bool,
238
239 #[serde(skip, default = "Utc::now")]
243 received_at: DateTime<Utc>,
244
245 #[serde(skip)]
251 signature: Option<Signature>,
252
253 #[serde(skip)]
257 request_trust: Option<RequestTrust>,
258}
259
260impl<D> RequestMeta<D> {
261 pub fn client(&self) -> Option<&str> {
265 self.client.as_deref()
266 }
267
268 pub fn client_name(&self) -> ClientName<'_> {
272 self.client()
273 .and_then(|client| client.split_once('/'))
274 .map(|(client, _)| client)
275 .map_or(ClientName::Other("proprietary"), ClientName::from)
276 }
277
278 #[allow(dead_code)] pub fn version(&self) -> u16 {
281 self.version
282 }
283
284 pub fn origin(&self) -> Option<&Url> {
286 self.origin.as_ref()
287 }
288
289 #[allow(unused)]
291 pub fn remote_addr(&self) -> Option<IpAddr> {
292 self.remote_addr
293 }
294
295 pub fn client_addr(&self) -> Option<IpAddr> {
300 let client = self.forwarded_for().split(',').next()?;
301 client.trim().parse().ok()
302 }
303
304 pub fn forwarded_for(&self) -> &str {
306 &self.forwarded_for
307 }
308
309 pub fn user_agent(&self) -> Option<&str> {
314 self.user_agent.as_deref()
315 }
316
317 pub fn client_hints(&self) -> ClientHints<&str> {
318 self.client_hints.as_deref()
319 }
320
321 pub fn no_cache(&self) -> bool {
323 self.no_cache
324 }
325
326 pub fn received_at(&self) -> DateTime<Utc> {
328 self.received_at
329 }
330
331 pub fn set_received_at(&mut self, received_at: DateTime<Utc>) {
333 self.received_at = received_at
334 }
335
336 pub fn request_trust(&self) -> RequestTrust {
339 self.request_trust.unwrap_or(RequestTrust::Untrusted)
340 }
341
342 pub fn set_request_trust(&mut self, value: RequestTrust) {
344 self.request_trust = Some(value);
345 }
346
347 pub fn set_client(&mut self, client: String) {
349 self.client = Some(client);
350 }
351
352 pub fn signature(&self) -> Option<&Signature> {
354 self.signature.as_ref()
355 }
356}
357
358impl RequestMeta {
359 pub fn outbound(dsn: PartialDsn) -> Self {
361 Self {
362 dsn,
363 client: Some(crate::constants::CLIENT.to_owned()),
364 version: default_version(),
365 origin: None,
366 remote_addr: None,
367 forwarded_for: "".to_owned(),
368 user_agent: Some(crate::constants::SERVER.to_owned()),
369 no_cache: false,
370 received_at: Utc::now(),
371 client_hints: ClientHints::default(),
372 signature: None,
373 request_trust: None,
374 }
375 }
376
377 pub fn dsn(&self) -> &PartialDsn {
382 &self.dsn
383 }
384
385 pub fn project_id(&self) -> Option<ProjectId> {
391 self.dsn.project_id
392 }
393
394 pub fn set_project_id(&mut self, project_id: ProjectId) {
396 self.dsn.project_id = Some(project_id);
397 }
398
399 pub fn public_key(&self) -> ProjectKey {
401 self.dsn.public_key
402 }
403
404 pub fn auth_header(&self) -> String {
408 let mut auth = format!(
409 "Sentry sentry_key={}, sentry_version={}",
410 self.public_key(),
411 self.version
412 );
413
414 if let Some(ref client) = self.client {
415 use std::fmt::Write;
416 write!(auth, ", sentry_client={client}").ok();
417 }
418
419 auth
420 }
421
422 pub fn get_partial_scoping(&self) -> PartialScoping {
426 PartialScoping {
427 organization_id: None,
428 project_id: self.project_id(),
429 project_key: self.public_key(),
430 }
431 }
432}
433
434impl<D> fmt::Debug for RequestMeta<D>
435where
436 D: fmt::Debug,
437{
438 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
439 let Self {
440 dsn,
441 client,
442 version,
443 origin,
444 remote_addr,
445 forwarded_for,
446 user_agent,
447 client_hints,
448 no_cache,
449 received_at,
450 signature,
451 request_trust,
452 } = self;
453
454 let mut map = f.debug_map();
455
456 map.entry(&"dsn", &dsn);
457
458 if let Some(client) = client {
459 map.entry(&"client", client);
460 }
461 map.entry(&"version", version);
462
463 if let Some(origin) = origin {
464 map.entry(&"origin", origin);
465 }
466 if let Some(remote_addr) = remote_addr {
467 map.entry(&"remote_addr", remote_addr);
468 }
469 if !forwarded_for.is_empty() {
470 map.entry(&"forwarded_for", forwarded_for);
471 }
472 if let Some(user_agent) = user_agent {
473 map.entry(&"user_agent", user_agent);
474 }
475 if !client_hints.is_empty() {
476 map.entry(&"client_hints", client_hints);
477 }
478 if *no_cache {
479 map.entry(&"no_cache", no_cache);
480 }
481 map.entry(&"received_at", received_at);
482 if let Some(signature) = signature {
483 map.entry(&"signature", signature);
484 }
485 if let Some(request_trust) = request_trust {
486 map.entry(&"request_trust", request_trust);
487 }
488
489 map.finish()
490 }
491}
492
493#[derive(Debug, Copy, Clone)]
497pub struct PartialScoping {
498 pub organization_id: Option<OrganizationId>,
500 pub project_id: Option<ProjectId>,
502 pub project_key: ProjectKey,
504}
505
506impl PartialScoping {
507 pub fn into_scoping(self) -> Scoping {
511 Scoping {
512 organization_id: self
513 .organization_id
514 .unwrap_or_else(|| OrganizationId::new(0)),
515 project_id: self.project_id.unwrap_or_else(|| ProjectId::new(0)),
516 project_key: self.project_key,
517 key_id: None,
518 }
519 }
520}
521
522#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)]
531pub enum RequestTrust {
532 #[default]
534 Untrusted,
535 Trusted,
537}
538
539impl RequestTrust {
540 pub fn is_trusted(self) -> bool {
542 matches!(self, Self::Trusted)
543 }
544
545 pub fn is_untrusted(self) -> bool {
547 matches!(self, Self::Untrusted)
548 }
549}
550
551pub type PartialMeta = RequestMeta<Option<PartialDsn>>;
556
557impl PartialMeta {
558 pub fn dsn(&self) -> Option<&PartialDsn> {
563 self.dsn.as_ref()
564 }
565
566 pub fn copy_to(self, mut complete: RequestMeta) -> RequestMeta {
570 if self.client.is_some() {
573 complete.client = self.client;
574 }
575 if self.version != default_version() {
576 complete.version = self.version;
577 }
578 if self.origin.is_some() {
579 complete.origin = self.origin;
580 }
581 if self.remote_addr.is_some() {
582 complete.remote_addr = self.remote_addr;
583 }
584 if !self.forwarded_for.is_empty() {
585 complete.forwarded_for = self.forwarded_for;
586 }
587 if self.user_agent.is_some() {
588 complete.user_agent = self.user_agent;
589 }
590 if self.request_trust.is_some() {
591 complete.request_trust = self.request_trust;
592 }
593 complete.client_hints.copy_from(self.client_hints);
594
595 if self.no_cache {
596 complete.no_cache = true;
597 }
598
599 complete
600 }
601}
602
603impl FromRequestParts<ServiceState> for PartialMeta {
604 type Rejection = BadEventMeta;
605
606 async fn from_request_parts(
607 parts: &mut Parts,
608 state: &ServiceState,
609 ) -> Result<Self, Self::Rejection> {
610 let mut ua = RawUserAgentInfo::default();
611 for (key, value) in &parts.headers {
612 ua.set_ua_field_from_header(key.as_str(), value.to_str().ok().map(str::to_owned));
613 }
614
615 let mut from_internal_relay = false;
616 let relay_id = parts
617 .headers
618 .get("x-sentry-relay-id")
619 .and_then(|h| h.to_str().ok())
620 .and_then(|h| h.parse::<RelayId>().ok());
621
622 if let Some(relay_id) = relay_id {
623 relay_log::configure_scope(|s| s.set_tag("relay_id", relay_id));
624 from_internal_relay = state
625 .config()
626 .static_relays()
627 .get(&relay_id)
628 .is_some_and(|ri| ri.internal);
629 }
630
631 let request_trust = Some(match from_internal_relay {
632 true => RequestTrust::Trusted,
633 false => RequestTrust::Untrusted,
634 });
635
636 let ReceivedAt(received_at) = ReceivedAt::from_request_parts(parts, state).await?;
637
638 let signature = Signature::from_request_parts(parts, state)
639 .await
640 .map_err(BadEventMeta::SignatureError)?;
641
642 Ok(RequestMeta {
643 dsn: None,
644 version: default_version(),
645 client: None,
646 origin: parse_header_url(parts, header::ORIGIN)
647 .or_else(|| parse_header_url(parts, header::REFERER)),
648 remote_addr: ConnectInfo::<SocketAddr>::from_request_parts(parts, state)
649 .await
650 .map(|ConnectInfo(peer)| peer.ip())
651 .ok(),
652 forwarded_for: ForwardedFor::from_request_parts(parts, state)
653 .await?
654 .into_inner(),
655 user_agent: ua.user_agent,
656 no_cache: false,
657 received_at,
658 client_hints: ua.client_hints,
659 signature,
660 request_trust,
661 })
662 }
663}
664
665fn get_auth_header(req: &Parts, header_name: impl AsHeaderName) -> Option<&str> {
666 req.headers
667 .get(header_name)
668 .and_then(|x| x.to_str().ok())
669 .filter(|h| h.len() >= 7 && h[..7].eq_ignore_ascii_case("sentry "))
670}
671
672fn auth_from_parts(req: &Parts, path_key: Option<String>) -> Result<Auth, BadEventMeta> {
673 let mut auth = None;
674
675 if let Some(header) = get_auth_header(req, "x-sentry-auth") {
677 auth = Some(header.parse::<Auth>()?);
678 }
679
680 if let Some(header) = get_auth_header(req, header::AUTHORIZATION) {
682 if auth.is_some() {
683 return Err(BadEventMeta::MultipleAuth);
684 }
685
686 auth = Some(header.parse::<Auth>()?);
687 }
688
689 if let Some(basic_auth) = req
691 .headers
692 .get("authorization")
693 .and_then(|value| value.to_str().ok())
694 .and_then(|x| {
695 if x.len() >= 6 && x[..6].eq_ignore_ascii_case("basic ") {
696 x.get(6..)
697 } else {
698 None
699 }
700 })
701 .and_then(|value| {
702 let decoded = String::from_utf8(BASE64.decode(value.as_bytes()).ok()?).ok()?;
703 let (public_key, _) = decoded.split_once(':')?;
704 Auth::from_pairs([("sentry_key", public_key)]).ok()
705 })
706 {
707 if auth.is_some() {
708 return Err(BadEventMeta::MultipleAuth);
709 }
710 auth = Some(basic_auth);
711 }
712
713 let query = req.uri.query().unwrap_or_default();
715 if query.contains("sentry_") {
716 if auth.is_some() {
717 return Err(BadEventMeta::MultipleAuth);
718 }
719
720 auth = Some(Auth::from_querystring(query.as_bytes())?);
721 }
722
723 if let Some(sentry_key) = path_key {
725 if auth.is_some() {
726 return Err(BadEventMeta::MultipleAuth);
727 }
728
729 auth = Some(
730 Auth::from_pairs(std::iter::once(("sentry_key", sentry_key)))
731 .map_err(|_| BadEventMeta::MissingAuth)?,
732 );
733 }
734
735 auth.ok_or(BadEventMeta::MissingAuth)
736}
737
738fn parse_header_url(req: &Parts, header: impl AsHeaderName) -> Option<Url> {
739 req.headers
740 .get(header)
741 .and_then(|h| h.to_str().ok())
742 .and_then(|s| s.parse::<Url>().ok())
743 .and_then(|u| match u.scheme() {
744 "http" | "https" => Some(u),
745 _ => None,
746 })
747}
748
749#[derive(Debug, serde::Deserialize)]
754struct StorePath {
755 project_id: Option<ProjectId>,
766
767 sentry_key: Option<String>,
772}
773
774impl FromRequestParts<ServiceState> for RequestMeta {
775 type Rejection = BadEventMeta;
776
777 async fn from_request_parts(
778 parts: &mut Parts,
779 state: &ServiceState,
780 ) -> Result<Self, Self::Rejection> {
781 let Path(store_path): Path<StorePath> =
782 parts.extract().await.map_err(BadEventMeta::BadProject)?;
783
784 let auth = auth_from_parts(parts, store_path.sentry_key)?;
785 let partial_meta: PartialMeta = parts.extract_with_state(state).await?;
786 let (public_key, key_flags) = ProjectKey::parse_with_flags(auth.public_key())?;
787
788 let config = state.config();
789 let upstream = config.upstream();
790
791 let dsn = PartialDsn {
792 scheme: upstream.scheme(),
793 public_key,
794 host: upstream.host().to_owned(),
795 port: upstream.port(),
796 path: String::new(),
797 project_id: store_path.project_id,
798 };
799
800 let version = auth.version();
802 if version > relay_event_schema::protocol::PROTOCOL_VERSION {
803 return Err(BadEventMeta::UnsupportedProtocolVersion(version));
804 }
805
806 relay_statsd::metric!(
807 counter(RelayCounters::EventProtocol) += 1,
808 version = &version.to_string()
809 );
810
811 Ok(RequestMeta {
812 dsn,
813 version,
814 client: auth.client_agent().map(str::to_owned),
815 origin: partial_meta.origin,
816 remote_addr: partial_meta.remote_addr,
817 forwarded_for: partial_meta.forwarded_for,
818 user_agent: partial_meta.user_agent,
819 no_cache: key_flags.contains(&"no-cache"),
820 received_at: partial_meta.received_at,
821 client_hints: partial_meta.client_hints,
822 signature: partial_meta.signature,
823 request_trust: partial_meta.request_trust,
824 })
825 }
826}
827
828#[cfg(test)]
829mod tests {
830 use super::*;
831
832 impl RequestMeta {
833 pub fn new(dsn: relay_common::Dsn) -> Self {
835 Self {
836 dsn: PartialDsn::from_dsn(dsn).expect("invalid DSN"),
837 client: Some("sentry/client".to_owned()),
838 version: 7,
839 origin: Some("http://origin/".parse().unwrap()),
840 remote_addr: Some("192.168.0.1".parse().unwrap()),
841 forwarded_for: String::new(),
842 user_agent: Some("sentry/agent".to_owned()),
843 no_cache: false,
844 received_at: Utc::now(),
845 client_hints: ClientHints::default(),
846 request_trust: None,
847 signature: None,
848 }
849 }
850 }
851
852 #[test]
853 fn test_request_meta_roundtrip() {
854 let json = r#"{
855 "dsn": "https://e12d836b15bb49d7bbf99e64295d995b:@sentry.io/42",
856 "client": "sentry-client",
857 "version": 7,
858 "origin": "http://origin/",
859 "remote_addr": "192.168.0.1",
860 "forwarded_for": "8.8.8.8",
861 "user_agent": "0x8000",
862 "no_cache": false,
863 "client_hints": {
864 "sec_ch_ua_platform": "macOS",
865 "sec_ch_ua_platform_version": "13.1.0",
866 "sec_ch_ua": "\"Not_A Brand\";v=\"99\", \"Google Chrome\";v=\"109\", \"Chromium\";v=\"109\""
867 }
868 }"#;
869
870 let mut deserialized: RequestMeta = serde_json::from_str(json).unwrap();
871
872 let reqmeta = RequestMeta {
873 dsn: PartialDsn::from_str("https://e12d836b15bb49d7bbf99e64295d995b:@sentry.io/42")
874 .unwrap(),
875 client: Some("sentry-client".to_owned()),
876 version: 7,
877 origin: Some(Url::parse("http://origin/").unwrap()),
878 remote_addr: Some(IpAddr::from_str("192.168.0.1").unwrap()),
879 forwarded_for: "8.8.8.8".to_owned(),
880 user_agent: Some("0x8000".to_owned()),
881 no_cache: false,
882 received_at: Utc::now(),
883 client_hints: ClientHints {
884 sec_ch_ua_platform: Some("macOS".to_owned()),
885 sec_ch_ua_platform_version: Some("13.1.0".to_owned()),
886 sec_ch_ua: Some(
887 "\"Not_A Brand\";v=\"99\", \"Google Chrome\";v=\"109\", \"Chromium\";v=\"109\""
888 .to_owned(),
889 ),
890 sec_ch_ua_model: None,
891 },
892 request_trust: None,
893 signature: None,
894 };
895 deserialized.received_at = reqmeta.received_at;
896 assert_eq!(deserialized, reqmeta);
897 }
898
899 #[test]
900 fn test_signature_not_serialized() {
901 let dsn: relay_common::Dsn = "https://e12d836b15bb49d7bbf99e64295d995b:@sentry.io/42"
902 .parse()
903 .unwrap();
904 let without_signature = RequestMeta::new(dsn.clone());
905 let mut with_signature = RequestMeta::new(dsn);
906 with_signature.signature = Some(Signature("test-signature".to_owned()));
907
908 let serialized_without_signature = serde_json::to_string(&without_signature).unwrap();
909 let serialized_with_signature = serde_json::to_string(&with_signature).unwrap();
910 assert_eq!(serialized_with_signature, serialized_without_signature);
911 }
912
913 #[test]
914 fn test_trusted_is_not_untrusted() {
915 let x = RequestTrust::Trusted;
916 assert!(x.is_trusted());
917 assert!(!x.is_untrusted());
918
919 let x = RequestTrust::Untrusted;
920 assert!(!x.is_trusted());
921 assert!(x.is_untrusted());
922 }
923}