Skip to main content

relay_server/utils/
sizes.rs

1use relay_config::Config;
2
3use crate::envelope::{Envelope, Item, ItemType};
4use crate::integrations::Integration;
5use crate::managed::Managed;
6use crate::services::outcome::DiscardItemType;
7use crate::statsd::RelayCounters;
8
9/// Checks for size limits of items in this envelope.
10///
11/// Returns `Ok`, if the envelope adheres to the configured size limits. Otherwise, returns
12/// an `Err` containing the offending item type, in which case the envelope should be discarded
13/// and a `413 Payload Too Large` response should be given.
14///
15/// Each envelope item is checked against its limits defined in the [`Config`].
16pub fn check_envelope_size_limits(
17    config: &Config,
18    envelope: &Envelope,
19) -> Result<(), DiscardItemType> {
20    // TODO(#6249 / RELAY-272): These limits are built from the old `limits` config,
21    // we can think about reworking how limits are configured and exposing a similar structure
22    // for these size limits also in the config.
23    let mut empty = Limit {
24        item_size: 0,
25        total_count: 0,
26        total_size: 0,
27    };
28    let mut event = Limit {
29        item_size: config.max_event_size(),
30        total_count: 1,
31        total_size: config.max_event_size(),
32    };
33    let mut attachment = Limit {
34        item_size: config.max_attachment_size(),
35        total_count: config.max_attachment_count(),
36        total_size: config.max_attachments_size(),
37    };
38    let mut replay_recording = Limit {
39        item_size: config.max_replay_compressed_size(),
40        total_count: 1,
41        total_size: config.max_replay_compressed_size(),
42    };
43    let mut replay_video = Limit {
44        item_size: config.max_replay_compressed_size(),
45        total_count: 1,
46        total_size: config.max_replay_compressed_size(),
47    };
48    let mut session = Limit {
49        item_size: config.max_sessions_size(),
50        total_count: config.max_session_count(),
51        total_size: config.max_sessions_size(),
52    };
53    let mut client_report = Limit {
54        item_size: config.max_client_reports_size(),
55        total_count: config.max_client_reports_count(),
56        total_size: config.max_client_reports_size(),
57    };
58    let mut profile = Limit {
59        item_size: config.max_profile_size(),
60        total_count: 1,
61        total_size: config.max_profile_size(),
62    };
63    let mut check_in = Limit {
64        item_size: config.max_check_in_size(),
65        total_count: 1,
66        total_size: config.max_check_in_size(),
67    };
68    let mut statsd = Limit {
69        item_size: config.max_statsd_size(),
70        total_count: 1,
71        total_size: config.max_statsd_size(),
72    };
73    let mut metric_bucket = Limit {
74        item_size: config.max_metric_buckets_size(),
75        total_count: 1,
76        total_size: config.max_metric_buckets_size(),
77    };
78    let mut log = Limit {
79        item_size: config.max_log_size(),
80        total_count: 1,
81        total_size: config.max_container_size(),
82    };
83    let mut log_integration = Limit {
84        item_size: config.max_container_size(),
85        total_count: 1,
86        total_size: config.max_container_size(),
87    };
88    let mut trace_metric = Limit {
89        item_size: config.max_trace_metric_size(),
90        total_count: 1,
91        total_size: config.max_container_size(),
92    };
93    let mut span = Limit {
94        item_size: config.max_span_size(),
95        total_count: 1,
96        total_size: config.max_container_size(),
97    };
98    let mut span_integration = Limit {
99        item_size: config.max_container_size(),
100        total_count: 1,
101        total_size: config.max_container_size(),
102    };
103    let mut span_legacy = Limit {
104        item_size: config.max_span_size(),
105        total_count: config.max_standalone_span_count(),
106        total_size: config.max_container_size(),
107    };
108
109    for item in envelope.items() {
110        let limit = match item.ty() {
111            ItemType::Event
112            | ItemType::Transaction
113            | ItemType::Security
114            | ItemType::ReplayEvent
115            | ItemType::RawSecurity
116            | ItemType::UserReportV2
117            | ItemType::FormData => &mut event,
118            ItemType::Attachment | ItemType::UnrealReport | ItemType::UserReport => &mut attachment,
119            ItemType::ReplayRecording => &mut replay_recording,
120            ItemType::ReplayVideo => &mut replay_video,
121            ItemType::Session | ItemType::Sessions => &mut session,
122            ItemType::ClientReport => &mut client_report,
123            ItemType::Profile | ItemType::ProfileChunk => &mut profile,
124            ItemType::CheckIn => &mut check_in,
125            ItemType::Statsd => &mut statsd,
126            ItemType::MetricBuckets => &mut metric_bucket,
127            ItemType::Log => &mut log,
128            ItemType::TraceMetric => &mut trace_metric,
129            ItemType::Span => match item.is_container() {
130                true => &mut span,
131                false => &mut span_legacy,
132            },
133            ItemType::Integration => match item.integration() {
134                Some(Integration::Logs(_)) => &mut log_integration,
135                Some(Integration::Spans(_)) => &mut span_integration,
136                None => {
137                    // Shouldn't be possible, if it still happens due to a bug, be defensive.
138                    debug_assert!(false);
139                    &mut empty
140                }
141            },
142            // Unknown items are either filtered or forwarded as is.
143            ItemType::Unknown(_) => continue,
144        };
145
146        if let Err(err) = limit.apply(item) {
147            relay_statsd::metric!(
148                counter(RelayCounters::EnvelopeSizeLimited) += 1,
149                item = item.ty().name(),
150                limit = match err {
151                    Rejected::ItemSize => "item_size",
152                    Rejected::TotalCount => "total_count",
153                    Rejected::TotalSize => "total_size",
154                }
155            );
156
157            let discard_item: DiscardItemType = item
158                .attachment_type()
159                .map(|t| t.into())
160                .unwrap_or_else(|| item.ty().into());
161            return Err(discard_item);
162        }
163    }
164
165    Ok(())
166}
167
168enum Rejected {
169    ItemSize,
170    TotalCount,
171    TotalSize,
172}
173
174struct Limit {
175    /// The maximum size of a single item.
176    item_size: usize,
177    /// The remaining total count of this item type allowed in an envelope.
178    total_count: usize,
179    /// The remaining total count of this item type in an envelope.
180    total_size: usize,
181}
182
183impl Limit {
184    fn apply(&mut self, item: &Item) -> Result<(), Rejected> {
185        let size = match item.is_container() {
186            // For container items, check that the contained items obey the size limit on average.
187            //
188            // Individual size validation must then be done again after parsing.
189            true => item.len() / item.item_count().unwrap_or(1).max(1) as usize,
190            false => item.len(),
191        };
192        if size > self.item_size {
193            return Err(Rejected::ItemSize);
194        }
195
196        self.total_count = self
197            .total_count
198            .checked_sub(1)
199            .ok_or(Rejected::TotalCount)?;
200
201        self.total_size = self
202            .total_size
203            .checked_sub(item.len())
204            .ok_or(Rejected::TotalSize)?;
205
206        Ok(())
207    }
208}
209
210/// Checks for valid envelope items.
211///
212/// If Relay is configured to drop unknown items, this function removes them from the Envelope. All
213/// known items will be retained.
214pub fn remove_unknown_items(config: &Config, envelope: &mut Managed<Box<Envelope>>) {
215    if config.accept_unknown_items() {
216        return;
217    }
218
219    envelope.modify(|envelope, records| {
220        envelope.retain_items(|item| {
221            if item.is_unknown() {
222                relay_log::debug!("dropping unknown item");
223                // Reject items silently, without outcomes.
224                records.reject_err(None, &*item);
225                false
226            } else {
227                true
228            }
229        });
230    });
231}