Skip to main content

statsig_rust/interned_values/
mmap_sync.rs

1use serde::Deserialize;
2
3use crate::{
4    networking::ResponseData,
5    observability::ops_stats::OpsStatsForInstance,
6    specs_response::{proto_specs::deserialize_protobuf, spec_types::SpecsResponseFull},
7    StatsigErr,
8};
9
10/// Identifies the config generation used to build an mmap artifact.
11#[derive(Clone, Debug, PartialEq, Eq)]
12pub struct MmapSyncCursor {
13    pub lcut: u64,
14    pub checksum: Option<String>,
15}
16
17/// Reports whether a conditional mmap fetch published a new artifact.
18#[derive(Clone, Debug, PartialEq, Eq)]
19pub enum MmapWriteOutcome {
20    Published(MmapSyncCursor),
21    NoUpdate,
22}
23
24pub(super) struct MmapResolvedUpdate {
25    pub(super) specs: SpecsResponseFull,
26    pub(super) cursor: MmapSyncCursor,
27}
28
29pub(super) struct MmapConfigResponse<'a> {
30    data: &'a mut ResponseData,
31    headers: MmapResponseHeaders,
32    format: MmapResponseFormat,
33}
34
35#[derive(Clone, Copy)]
36enum MmapResponseFormat {
37    Json,
38    Protobuf,
39}
40
41#[derive(Deserialize)]
42struct MmapResponseMetadata {
43    has_updates: Option<bool>,
44    time: Option<u64>,
45    checksum: Option<String>,
46}
47
48struct MmapResponseHeaders {
49    lcut: Option<u64>,
50    checksum: ChecksumHeader,
51    cache_hit: bool,
52}
53
54enum ChecksumHeader {
55    Missing,
56    // An explicitly empty header is authoritative `None`; a missing header
57    // has no identity information and must not bypass body parsing.
58    Present(Option<String>),
59}
60
61#[derive(Clone, Copy)]
62struct ChecksumIdentity<'a> {
63    value: Option<&'a str>,
64    is_known: bool,
65}
66
67enum MetadataResolution {
68    NoUpdate,
69    ParseFull(MmapSyncCursor),
70}
71
72impl<'a> MmapConfigResponse<'a> {
73    pub(super) fn new(data: &'a mut ResponseData) -> Result<Self, StatsigErr> {
74        let headers = MmapResponseHeaders::from_response(data)?;
75        let format = MmapResponseFormat::from_response(data);
76
77        Ok(Self {
78            data,
79            headers,
80            format,
81        })
82    }
83
84    pub(super) fn resolve(
85        mut self,
86        previous: Option<&MmapSyncCursor>,
87    ) -> Result<Option<MmapResolvedUpdate>, StatsigErr> {
88        if self.headers.cache_hit {
89            let previous = previous.ok_or_else(|| {
90                invalid_mmap_response("Received a cache-hit response before any mmap was published")
91            })?;
92            validate_no_update_identity(
93                previous,
94                self.headers.lcut,
95                self.headers.checksum.identity(),
96            )?;
97            return Ok(None);
98        }
99
100        if previous.is_some_and(|previous| self.headers.is_no_update_without_body(previous)) {
101            return Ok(None);
102        }
103
104        let metadata_cursor = match self.format {
105            MmapResponseFormat::Protobuf => {
106                if previous.is_some() && self.headers.lcut.is_none() {
107                    return Err(invalid_mmap_response(
108                        "A conditional protobuf response did not include x-since-time",
109                    ));
110                }
111                None
112            }
113            MmapResponseFormat::Json => match self.resolve_json_metadata(previous)? {
114                MetadataResolution::NoUpdate => return Ok(None),
115                MetadataResolution::ParseFull(cursor) => Some(cursor),
116            },
117        };
118
119        let specs = parse_specs_response_data(&mut *self.data, self.format)?;
120        self.resolve_parsed(specs, metadata_cursor, previous)
121    }
122
123    fn resolve_json_metadata(
124        &mut self,
125        previous: Option<&MmapSyncCursor>,
126    ) -> Result<MetadataResolution, StatsigErr> {
127        let metadata = self.data.deserialize_into::<MmapResponseMetadata>()?;
128        if metadata.has_updates == Some(false) {
129            let previous = previous.ok_or_else(|| {
130                invalid_mmap_response("Received a no-update response before any mmap was published")
131            })?;
132            validate_json_no_update(&metadata, &self.headers, previous)?;
133            return Ok(MetadataResolution::NoUpdate);
134        }
135        if metadata.has_updates != Some(true) {
136            return Err(invalid_mmap_response(
137                "A config response did not include a valid has_updates value",
138            ));
139        }
140
141        let cursor = cursor_from_metadata(&metadata)?;
142        self.headers.validate_cursor(&cursor)?;
143        if previous.is_some_and(|previous| cursor_is_stale_or_exact(&cursor, previous)) {
144            return Ok(MetadataResolution::NoUpdate);
145        }
146
147        Ok(MetadataResolution::ParseFull(cursor))
148    }
149
150    fn resolve_parsed(
151        &self,
152        specs: SpecsResponseFull,
153        metadata_cursor: Option<MmapSyncCursor>,
154        previous: Option<&MmapSyncCursor>,
155    ) -> Result<Option<MmapResolvedUpdate>, StatsigErr> {
156        let cursor = cursor_from_specs_response(&specs)?;
157        self.headers.validate_cursor(&cursor)?;
158        if metadata_cursor
159            .as_ref()
160            .is_some_and(|metadata_cursor| metadata_cursor != &cursor)
161        {
162            return Err(invalid_mmap_response(
163                "The parsed config identity did not match its JSON metadata",
164            ));
165        }
166        if !specs.has_updates {
167            let previous = previous.ok_or_else(|| {
168                invalid_mmap_response("Received a no-update response before any mmap was published")
169            })?;
170            validate_no_update_identity(
171                previous,
172                Some(cursor.lcut),
173                ChecksumIdentity::known(cursor.checksum.as_deref()),
174            )?;
175            return Ok(None);
176        }
177        if previous.is_some_and(|previous| cursor_is_stale_or_exact(&cursor, previous)) {
178            return Ok(None);
179        }
180
181        Ok(Some(MmapResolvedUpdate { specs, cursor }))
182    }
183}
184
185impl MmapResponseFormat {
186    fn from_response(response_data: &ResponseData) -> Self {
187        let is_protobuf = response_data
188            .get_header_ref("content-type")
189            .is_some_and(|value| value.contains("application/octet-stream"))
190            && response_data
191                .get_header_ref("content-encoding")
192                .is_some_and(|value| value.contains("statsig-br"));
193
194        if is_protobuf {
195            Self::Protobuf
196        } else {
197            Self::Json
198        }
199    }
200}
201
202impl MmapResponseHeaders {
203    fn from_response(response_data: &ResponseData) -> Result<Self, StatsigErr> {
204        let lcut = response_data
205            .get_header_ref("x-since-time")
206            .map(|value| {
207                value.parse::<u64>().map_err(|_| {
208                    invalid_mmap_response("The x-since-time response header was not a valid u64")
209                })
210            })
211            .transpose()?;
212        let checksum = match response_data.get_header_ref("x-checksum") {
213            Some(value) => ChecksumHeader::Present(normalize_checksum(Some(value.clone()))),
214            None => ChecksumHeader::Missing,
215        };
216
217        if checksum.identity().value.is_some() && lcut.is_none() {
218            return Err(invalid_mmap_response(
219                "The response included x-checksum without x-since-time",
220            ));
221        }
222
223        Ok(Self {
224            lcut,
225            checksum,
226            cache_hit: response_data
227                .get_header_ref("x-cache-hit")
228                .is_some_and(|value| value.eq_ignore_ascii_case("true")),
229        })
230    }
231
232    fn is_no_update_without_body(&self, previous: &MmapSyncCursor) -> bool {
233        let Some(lcut) = self.lcut else {
234            return false;
235        };
236
237        lcut < previous.lcut || (lcut == previous.lcut && self.checksum.matches(&previous.checksum))
238    }
239
240    fn validate_cursor(&self, cursor: &MmapSyncCursor) -> Result<(), StatsigErr> {
241        if self.lcut.is_some_and(|lcut| lcut != cursor.lcut) {
242            return Err(invalid_mmap_response(
243                "The x-since-time response header did not match the config response time",
244            ));
245        }
246        if self
247            .checksum
248            .identity()
249            .conflicts_with(ChecksumIdentity::known(cursor.checksum.as_deref()))
250        {
251            return Err(invalid_mmap_response(
252                "The x-checksum response header did not match the config response checksum",
253            ));
254        }
255
256        Ok(())
257    }
258}
259
260impl ChecksumHeader {
261    fn identity(&self) -> ChecksumIdentity<'_> {
262        match self {
263            Self::Missing => ChecksumIdentity::unknown(),
264            Self::Present(checksum) => ChecksumIdentity::known(checksum.as_deref()),
265        }
266    }
267
268    fn matches(&self, expected: &Option<String>) -> bool {
269        let identity = self.identity();
270        identity.is_known && identity.value == expected.as_deref()
271    }
272}
273
274impl<'a> ChecksumIdentity<'a> {
275    fn unknown() -> Self {
276        Self {
277            value: None,
278            is_known: false,
279        }
280    }
281
282    fn known(value: Option<&'a str>) -> Self {
283        Self {
284            value,
285            is_known: true,
286        }
287    }
288
289    fn prefer(self, fallback: Self) -> Self {
290        if self.is_known {
291            self
292        } else {
293            fallback
294        }
295    }
296
297    fn conflicts_with(self, other: Self) -> bool {
298        self.is_known && other.is_known && self.value != other.value
299    }
300}
301
302fn cursor_from_metadata(metadata: &MmapResponseMetadata) -> Result<MmapSyncCursor, StatsigErr> {
303    let lcut = metadata
304        .time
305        .filter(|lcut| *lcut > 0)
306        .ok_or_else(|| invalid_mmap_response("A config response did not include a valid time"))?;
307
308    Ok(MmapSyncCursor {
309        lcut,
310        checksum: normalize_checksum(metadata.checksum.clone()),
311    })
312}
313
314fn cursor_from_specs_response(
315    specs_response: &SpecsResponseFull,
316) -> Result<MmapSyncCursor, StatsigErr> {
317    if specs_response.time == 0 {
318        return Err(invalid_mmap_response(
319            "A config response did not include a valid time",
320        ));
321    }
322
323    Ok(MmapSyncCursor {
324        lcut: specs_response.time,
325        checksum: normalize_checksum(specs_response.checksum.clone()),
326    })
327}
328
329fn normalize_checksum(checksum: Option<String>) -> Option<String> {
330    checksum.filter(|checksum| !checksum.is_empty())
331}
332
333fn cursor_is_stale_or_exact(cursor: &MmapSyncCursor, previous: &MmapSyncCursor) -> bool {
334    cursor.lcut < previous.lcut
335        || (cursor.lcut == previous.lcut && cursor.checksum == previous.checksum)
336}
337
338fn validate_json_no_update(
339    metadata: &MmapResponseMetadata,
340    headers: &MmapResponseHeaders,
341    previous: &MmapSyncCursor,
342) -> Result<(), StatsigErr> {
343    let body_checksum = match metadata.checksum.as_deref() {
344        Some(checksum) => ChecksumIdentity::known((!checksum.is_empty()).then_some(checksum)),
345        None => ChecksumIdentity::unknown(),
346    };
347    if body_checksum.is_known && metadata.time.is_none() {
348        return Err(invalid_mmap_response(
349            "A no-update response included checksum without time",
350        ));
351    }
352    if let (Some(header_lcut), Some(body_lcut)) = (headers.lcut, metadata.time) {
353        if header_lcut != body_lcut {
354            return Err(invalid_mmap_response(
355                "The x-since-time response header did not match the JSON response time",
356            ));
357        }
358    }
359
360    let header_checksum = headers.checksum.identity();
361    if header_checksum.conflicts_with(body_checksum) {
362        return Err(invalid_mmap_response(
363            "The x-checksum response header did not match the JSON response checksum",
364        ));
365    }
366
367    let lcut = metadata.time.or(headers.lcut);
368    validate_no_update_identity(previous, lcut, body_checksum.prefer(header_checksum))
369}
370
371fn validate_no_update_identity(
372    previous: &MmapSyncCursor,
373    lcut: Option<u64>,
374    checksum: ChecksumIdentity<'_>,
375) -> Result<(), StatsigErr> {
376    let Some(lcut) = lcut else {
377        return Ok(());
378    };
379
380    if lcut > previous.lcut
381        || (lcut == previous.lcut
382            && checksum.is_known
383            && previous.checksum.as_deref() != checksum.value)
384    {
385        return Err(invalid_mmap_response(
386            "A no-update response advertised a changed config identity",
387        ));
388    }
389
390    Ok(())
391}
392
393fn parse_specs_response_data(
394    response_data: &mut ResponseData,
395    format: MmapResponseFormat,
396) -> Result<SpecsResponseFull, StatsigErr> {
397    if matches!(format, MmapResponseFormat::Protobuf) {
398        let current = SpecsResponseFull::default();
399        let mut next = SpecsResponseFull::default();
400        deserialize_protobuf(
401            &OpsStatsForInstance::new(),
402            &current,
403            &mut next,
404            response_data,
405        )?;
406        return Ok(next);
407    }
408
409    response_data.deserialize_into::<SpecsResponseFull>()
410}
411
412fn invalid_mmap_response(message: &str) -> StatsigErr {
413    StatsigErr::InvalidOperation(format!("Invalid mmap config response: {message}"))
414}