1use {
22 crate::authenticity,
23 bytes::Bytes,
24 krabka_ids::{LeaderEpoch, Offset},
25 krabka_object_store::{ObjectOps, ObjectStoreError},
26 krabka_remote_storage::{ObjectEntry, TopicIdPartition},
27 object_store::path::Path,
28 std::collections::BTreeMap,
29 uuid::Uuid,
30};
31
32mod index;
33mod leader_epoch;
34mod log_walk;
35mod snapshot;
36
37#[cfg(test)]
38mod tests;
39
40use {
41 self::{
42 index::{validate_offset_index, validate_time_index, validate_txn_index},
43 leader_epoch::parse_leader_epoch_checkpoint,
44 log_walk::walk_log,
45 snapshot::validate_producer_snapshot,
46 },
47 crate::{
48 backend::ArchiveStore,
49 discover::{ArchiveObject, SegmentInventory},
50 error::RestoreError,
51 },
52};
53
54#[derive(Debug, Clone, PartialEq, Eq)]
56pub struct SegmentFacts {
57 pub segment_id: Uuid,
59 pub base_offset: Offset,
61 pub end_offset: Offset,
63 pub max_timestamp_ms: i64,
66 pub batches: u64,
68 pub records: u64,
70 pub log_bytes: u64,
72 pub leader_epochs: Vec<LeaderEpochStart>,
75}
76
77#[derive(Debug, Clone, Copy, PartialEq, Eq)]
79pub struct LeaderEpochStart {
80 pub epoch: LeaderEpoch,
82 pub start_offset: Offset,
84}
85
86#[derive(Debug, Clone, PartialEq, Eq)]
88pub struct VerifiedSegment {
89 pub facts: SegmentFacts,
91 pub log: Bytes,
93}
94
95pub const MAX_LOG_BYTES: u64 = 8 * 1024 * 1024 * 1024;
100
101const MAX_INDEX_BYTES: u64 = 256 * 1024 * 1024;
106
107const MAX_TXN_INDEX_BYTES: u64 = MAX_INDEX_BYTES;
109
110const MAX_SNAPSHOT_BYTES: u64 = 64 * 1024 * 1024;
112
113const MAX_LEADER_EPOCH_BYTES: u64 = 16 * 1024 * 1024;
116
117pub async fn verify_segment(
126 store: &ArchiveStore,
127 partition: &TopicIdPartition,
128 segment: &SegmentInventory,
129) -> Result<VerifiedSegment, RestoreError> {
130 verify_segment_inner(store, partition, segment, None).await
131}
132
133pub async fn verify_segment_authenticated(
141 store: &ArchiveStore,
142 partition: &TopicIdPartition,
143 segment: &SegmentInventory,
144 objects: &BTreeMap<String, ObjectEntry>,
145) -> Result<VerifiedSegment, RestoreError> {
146 verify_segment_inner(store, partition, segment, Some(objects)).await
147}
148
149async fn verify_segment_inner(
150 store: &ArchiveStore,
151 partition: &TopicIdPartition,
152 segment: &SegmentInventory,
153 objects: Option<&BTreeMap<String, ObjectEntry>>,
154) -> Result<VerifiedSegment, RestoreError> {
155 let log = require_artifact(segment.log.as_ref(), ".log", partition, segment)?;
158 let offset_index =
159 require_artifact(segment.offset_index.as_ref(), ".index", partition, segment)?;
160 let time_index = require_artifact(
161 segment.time_index.as_ref(),
162 ".timeindex",
163 partition,
164 segment,
165 )?;
166 let producer_snapshot = require_artifact(
167 segment.producer_snapshot.as_ref(),
168 ".snapshot",
169 partition,
170 segment,
171 )?;
172 let leader_epoch = require_artifact(
173 segment.leader_epoch.as_ref(),
174 ".leader_epoch_checkpoint",
175 partition,
176 segment,
177 )?;
178
179 let ops = store.ops();
180 let log_bytes = fetch_capped(ops, &log.key, MAX_LOG_BYTES, objects).await?;
183 let offset_index_bytes = fetch_capped(ops, &offset_index.key, MAX_INDEX_BYTES, objects).await?;
184 let time_index_bytes = fetch_capped(ops, &time_index.key, MAX_INDEX_BYTES, objects).await?;
185 let producer_snapshot_bytes =
186 fetch_capped(ops, &producer_snapshot.key, MAX_SNAPSHOT_BYTES, objects).await?;
187 let leader_epoch_bytes =
188 fetch_capped(ops, &leader_epoch.key, MAX_LEADER_EPOCH_BYTES, objects).await?;
189 let transaction_index = match &segment.transaction_index {
190 Some(artifact) => Some((
191 artifact.key.clone(),
192 fetch_capped(ops, &artifact.key, MAX_TXN_INDEX_BYTES, objects).await?,
193 )),
194 None => None,
195 };
196
197 let walked = walk_log(&log.key, segment, &log_bytes)?;
198 let log_bytes_len = u64::try_from(log_bytes.len()).unwrap_or(u64::MAX);
199
200 validate_offset_index(
201 &offset_index.key,
202 &offset_index_bytes,
203 segment.base_offset,
204 walked.end_offset,
205 log_bytes_len,
206 )?;
207 validate_time_index(
208 &time_index.key,
209 &time_index_bytes,
210 segment.base_offset,
211 walked.end_offset,
212 )?;
213 if let Some((key, bytes)) = &transaction_index {
214 validate_txn_index(key, bytes, segment.base_offset, walked.end_offset)?;
215 }
216 let snapshot_offset =
217 walked
218 .end_offset
219 .0
220 .checked_add(1)
221 .ok_or_else(|| RestoreError::TruncatedSegment {
222 key: producer_snapshot.key.to_string(),
223 position: 0,
224 declared: u64::MAX,
225 available: u64::MAX,
226 })?;
227 validate_producer_snapshot(
228 &producer_snapshot.key,
229 &producer_snapshot_bytes,
230 snapshot_offset,
231 )?;
232 let leader_epochs = parse_leader_epoch_checkpoint(
233 &leader_epoch.key,
234 &leader_epoch_bytes,
235 segment.base_offset,
236 walked.end_offset,
237 )?;
238
239 Ok(VerifiedSegment {
240 facts: SegmentFacts {
241 segment_id: segment.segment_id,
242 base_offset: segment.base_offset,
243 end_offset: walked.end_offset,
244 max_timestamp_ms: walked.max_timestamp_ms,
245 batches: walked.batches,
246 records: walked.records,
247 log_bytes: log_bytes_len,
248 leader_epochs,
249 },
250 log: log_bytes,
251 })
252}
253
254fn require_artifact<'a>(
256 artifact: Option<&'a ArchiveObject>,
257 name: &str,
258 partition: &TopicIdPartition,
259 segment: &SegmentInventory,
260) -> Result<&'a ArchiveObject, RestoreError> {
261 artifact.ok_or_else(|| RestoreError::TornCopy {
262 topic: partition.topic.clone(),
263 partition: partition.partition,
264 segment_id: segment.segment_id,
265 artifact: name.to_owned(),
266 })
267}
268
269pub async fn fetch_capped(
273 ops: &dyn ObjectOps,
274 key: &Path,
275 max_bytes: u64,
276 authenticated: Option<&BTreeMap<String, ObjectEntry>>,
277) -> Result<Bytes, RestoreError> {
278 let expected = authenticity::object_claim(key, authenticated)?;
279 let meta = ops.head(key).await?;
280 authenticity::verify_object_size(key, expected, meta.size)?;
281 if meta.size > max_bytes {
282 return Err(ObjectStoreError::TooLarge {
283 key: key.clone(),
284 size: meta.size,
285 max_bytes,
286 }
287 .into());
288 }
289 let bytes = ops.get_capped(key, max_bytes).await?;
290 authenticity::verify_object_bytes(key, expected, &bytes)?;
291 Ok(bytes)
292}
293
294fn offset_as_u64(value: i64) -> u64 { u64::try_from(value).unwrap_or(0) }