vyre_runtime/pipeline_cache/
remote.rs1use super::disk::read_verified_cache_blob;
5use super::fingerprint::PipelineFingerprint;
6use super::store::PipelineCacheStore;
7
8const HEADER_FINGERPRINT: &str = "x-vyre-cache-fingerprint";
9const HEADER_SOURCE_PROVENANCE: &str = "x-vyre-cache-source-provenance";
10const HEADER_DEVICE_COMPATIBILITY: &str = "x-vyre-cache-device-compatibility";
11
12pub struct RemoteCache {
20 base_url: String,
21 agent: ureq::Agent,
22 expected_source_provenance: Option<String>,
23 expected_device_compatibility: Option<String>,
24}
25
26impl RemoteCache {
27 #[must_use]
30 pub fn new(base_url: impl Into<String>) -> Self {
31 Self {
32 base_url: base_url.into(),
33 agent: ureq::Agent::new_with_defaults(),
34 expected_source_provenance: None,
35 expected_device_compatibility: None,
36 }
37 }
38
39 #[must_use]
42 pub fn with_required_metadata(
43 mut self,
44 source_provenance: impl Into<String>,
45 device_compatibility: impl Into<String>,
46 ) -> Self {
47 self.expected_source_provenance = Some(source_provenance.into());
48 self.expected_device_compatibility = Some(device_compatibility.into());
49 self
50 }
51
52 fn metadata_expectation(&self) -> RemoteMetadataExpectation<'_> {
53 RemoteMetadataExpectation {
54 expected_source_provenance: self.expected_source_provenance.as_deref(),
55 expected_device_compatibility: self.expected_device_compatibility.as_deref(),
56 }
57 }
58}
59
60impl PipelineCacheStore for RemoteCache {
61 fn get(&self, fp: &PipelineFingerprint) -> Option<Vec<u8>> {
62 let url = format!("{}/{}.bin", self.base_url.trim_end_matches('/'), fp.hex());
63 let mut resp = self.agent.get(&url).call().ok()?;
64 if !remote_metadata_allows(
65 fp,
66 header_value(&resp, HEADER_FINGERPRINT),
67 header_value(&resp, HEADER_SOURCE_PROVENANCE),
68 header_value(&resp, HEADER_DEVICE_COMPATIBILITY),
69 self.metadata_expectation(),
70 ) {
71 return None;
72 }
73 read_verified_cache_blob(resp.body_mut().as_reader())
74 }
75
76 fn put(&self, _fp: PipelineFingerprint, _artifact: Vec<u8>) {
77 }
79}
80
81fn header_value<'a>(resp: &'a ureq::http::Response<ureq::Body>, name: &str) -> Option<&'a str> {
82 resp.headers()
83 .get(name)
84 .and_then(|value| value.to_str().ok())
85}
86
87#[derive(Debug, Clone, Copy)]
88struct RemoteMetadataExpectation<'a> {
89 expected_source_provenance: Option<&'a str>,
90 expected_device_compatibility: Option<&'a str>,
91}
92
93fn remote_metadata_allows(
94 fp: &PipelineFingerprint,
95 fingerprint: Option<&str>,
96 source_provenance: Option<&str>,
97 device_compatibility: Option<&str>,
98 expectation: RemoteMetadataExpectation<'_>,
99) -> bool {
100 let expected_fingerprint = fp.hex();
101 let Some(fingerprint) = fingerprint else {
102 return false;
103 };
104 if fingerprint != expected_fingerprint {
105 return false;
106 }
107 let Some(source_provenance) = source_provenance.filter(|value| !value.is_empty()) else {
108 return false;
109 };
110 let Some(device_compatibility) = device_compatibility.filter(|value| !value.is_empty()) else {
111 return false;
112 };
113 if let Some(expected) = expectation.expected_source_provenance {
114 if source_provenance != expected {
115 return false;
116 }
117 }
118 if let Some(expected) = expectation.expected_device_compatibility {
119 if device_compatibility != expected {
120 return false;
121 }
122 }
123 true
124}
125
126#[cfg(test)]
127mod tests {
128 use super::*;
129 use crate::pipeline_cache::test_helpers::tiny_program;
130
131 #[test]
132 fn remote_cache_owns_reusable_http_agent() {
133 let cache = RemoteCache::new("https://cache.invalid/root/");
134
135 assert_eq!(cache.base_url, "https://cache.invalid/root/");
136 let _shared_agent = cache.agent.clone();
137 }
138
139 #[test]
140 fn remote_cache_metadata_accepts_matching_fingerprint_source_and_device() {
141 let fp = PipelineFingerprint::of(&tiny_program());
142 let fp_hex = fp.hex();
143 let expectation = RemoteMetadataExpectation {
144 expected_source_provenance: Some("git:abc123"),
145 expected_device_compatibility: Some("cuda-sm90"),
146 };
147
148 assert!(remote_metadata_allows(
149 &fp,
150 Some(&fp_hex),
151 Some("git:abc123"),
152 Some("cuda-sm90"),
153 expectation,
154 ));
155 }
156
157 #[test]
158 fn remote_cache_metadata_rejects_stale_source_device_and_fingerprint() {
159 let fp = PipelineFingerprint::of(&tiny_program());
160 let fp_hex = fp.hex();
161 let mut other = fp;
162 other.0[0] ^= 0xFF;
163 let other_hex = other.hex();
164 let expectation = RemoteMetadataExpectation {
165 expected_source_provenance: Some("git:abc123"),
166 expected_device_compatibility: Some("cuda-sm90"),
167 };
168
169 assert!(!remote_metadata_allows(
170 &fp,
171 Some(&other_hex),
172 Some("git:abc123"),
173 Some("cuda-sm90"),
174 expectation,
175 ));
176 assert!(!remote_metadata_allows(
177 &fp,
178 Some(&fp_hex),
179 Some("git:stale"),
180 Some("cuda-sm90"),
181 expectation,
182 ));
183 assert!(!remote_metadata_allows(
184 &fp,
185 Some(&fp_hex),
186 Some("git:abc123"),
187 Some("metal-apple7"),
188 expectation,
189 ));
190 assert!(!remote_metadata_allows(
191 &fp,
192 Some(&fp_hex),
193 None,
194 Some("cuda-sm90"),
195 expectation,
196 ));
197 }
198}