1use std::cmp::Ordering;
29
30use crate::error::{Error, Result};
31use crate::format::Value;
32use crate::schema::{AttributeFormat, Relation};
33
34#[derive(Debug, Clone, PartialEq, Eq)]
36pub enum IndexValue {
37 Integer(u32),
39 Bytes(Vec<u8>),
41}
42
43impl IndexValue {
44 pub fn from_value(value: Option<&Value>, format: AttributeFormat) -> Self {
46 match (value, format) {
47 (None, AttributeFormat::Sint32 | AttributeFormat::Uint32) => Self::Integer(0),
48 (None, _) => Self::Bytes(Vec::new()),
49 (Some(Value::Uint32(number)), _) => Self::Integer(*number),
50 (Some(Value::Sint32(number)), _) => Self::Integer(*number as u32),
51 (Some(Value::Date(bytes) | Value::String(bytes) | Value::Blob(bytes)), _) => {
52 Self::Bytes(bytes.clone())
53 }
54 }
55 }
56
57 fn encoded_len(&self) -> usize {
58 match self {
59 Self::Integer(_) => 4,
60 Self::Bytes(bytes) => pad4(4 + bytes.len()),
61 }
62 }
63
64 fn write(&self, out: &mut Vec<u8>) {
65 match self {
66 Self::Integer(number) => out.extend_from_slice(&number.to_be_bytes()),
67 Self::Bytes(bytes) => {
68 out.extend_from_slice(&(bytes.len() as u32).to_be_bytes());
69 out.extend_from_slice(bytes);
70 out.resize(pad4(out.len()), 0);
71 }
72 }
73 }
74}
75
76impl Ord for IndexValue {
77 fn cmp(&self, other: &Self) -> Ordering {
78 match (self, other) {
79 (Self::Integer(left), Self::Integer(right)) => left.cmp(right),
80 (Self::Bytes(left), Self::Bytes(right)) => left.cmp(right),
81 (Self::Integer(_), Self::Bytes(_)) => Ordering::Less,
83 (Self::Bytes(_), Self::Integer(_)) => Ordering::Greater,
84 }
85 }
86}
87
88impl PartialOrd for IndexValue {
89 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
90 Some(self.cmp(other))
91 }
92}
93
94#[derive(Debug, Clone, PartialEq, Eq)]
96pub struct IndexEntry {
97 pub record_number: u32,
98 pub key: Vec<IndexValue>,
99}
100
101impl IndexEntry {
102 fn payload_len(&self) -> usize {
104 self.key.iter().map(IndexValue::encoded_len).sum()
105 }
106
107 fn encoded_len(&self) -> usize {
108 4 + self.payload_len()
109 }
110}
111
112#[derive(Debug, Clone, PartialEq, Eq)]
114pub struct Index {
115 pub id: u32,
117 pub kind: u32,
119 pub attribute_ids: Vec<u32>,
121 pub entries: Vec<IndexEntry>,
123}
124
125impl Index {
126 fn header_len(&self) -> usize {
128 4 * (4 + self.attribute_ids.len())
129 }
130
131 fn encoded_len(&self) -> usize {
132 self.header_len()
133 + 4 + 8 * self.entries.len() + self.entries.iter().map(IndexEntry::encoded_len).sum::<usize>()
136 }
137
138 fn insert(&mut self, entry: IndexEntry) {
141 let at = self
142 .entries
143 .iter()
144 .position(|existing| existing.key > entry.key)
145 .unwrap_or(self.entries.len());
146 self.entries.insert(at, entry);
147 }
148}
149
150#[derive(Debug, Clone, PartialEq, Eq)]
152pub struct IndexBlob {
153 pub indexes: Vec<Index>,
154}
155
156impl IndexBlob {
157 pub fn parse(data: &[u8], table_offset: usize, relation: Option<&Relation>) -> Result<Self> {
164 let mut reader = Reader { data, at: 0 };
165 let size = reader.u32()? as usize;
166 if size != data.len() {
167 return Err(Error::format(format!(
168 "index region claims {size} bytes but has {}",
169 data.len()
170 )));
171 }
172 let count = reader.u32()? as usize;
173 if count > 64 {
174 return Err(Error::format(format!(
175 "index region claims {count} indexes"
176 )));
177 }
178
179 let mut offsets = Vec::with_capacity(count);
180 for _ in 0..count {
181 offsets.push(reader.u32()? as usize);
182 }
183
184 let mut indexes = Vec::with_capacity(count);
185 for offset in offsets {
186 let start = offset
187 .checked_sub(table_offset)
188 .ok_or_else(|| Error::format("index offset points before its table"))?;
189 indexes.push(parse_index(data, start, table_offset, relation)?);
190 }
191 Ok(Self { indexes })
192 }
193
194 pub fn to_bytes(&self, table_offset: usize) -> Vec<u8> {
196 let header_len = 8 + 4 * self.indexes.len();
197 let mut out = Vec::with_capacity(self.encoded_len());
198
199 out.extend_from_slice(&(self.encoded_len() as u32).to_be_bytes());
200 out.extend_from_slice(&(self.indexes.len() as u32).to_be_bytes());
201 let mut position = header_len;
202 for index in &self.indexes {
203 out.extend_from_slice(&((table_offset + position) as u32).to_be_bytes());
204 position += index.encoded_len();
205 }
206
207 let mut position = header_len;
208 for index in &self.indexes {
209 write_index(&mut out, index, table_offset, position);
210 position += index.encoded_len();
211 }
212 out
213 }
214
215 pub fn encoded_len(&self) -> usize {
216 8 + 4 * self.indexes.len() + self.indexes.iter().map(Index::encoded_len).sum::<usize>()
217 }
218
219 pub fn insert_record(&mut self, record_number: u32, key: impl Fn(u32) -> IndexValue) {
223 self.insert_record_where(record_number, key, |_| true);
224 }
225
226 pub fn insert_record_where(
232 &mut self,
233 record_number: u32,
234 key: impl Fn(u32) -> IndexValue,
235 indexable: impl Fn(&[u32]) -> bool,
236 ) {
237 for index in self
238 .indexes
239 .iter_mut()
240 .filter(|index| indexable(&index.attribute_ids))
241 {
242 let values = index.attribute_ids.iter().map(|id| key(*id)).collect();
243 index.insert(IndexEntry {
244 record_number,
245 key: values,
246 });
247 }
248 }
249
250 pub fn remove_record(&mut self, record_number: u32) {
253 for index in &mut self.indexes {
254 index
255 .entries
256 .retain(|entry| entry.record_number != record_number);
257 }
258 }
259
260 pub fn entry_count(&self) -> usize {
261 self.indexes.iter().map(|index| index.entries.len()).sum()
262 }
263
264 pub fn record_numbers(&self, index_id: u32) -> Option<Vec<u32>> {
266 self.indexes
267 .iter()
268 .find(|index| index.id == index_id)
269 .map(|index| {
270 index
271 .entries
272 .iter()
273 .map(|entry| entry.record_number)
274 .collect()
275 })
276 }
277}
278
279fn parse_index(
280 data: &[u8],
281 start: usize,
282 table_offset: usize,
283 relation: Option<&Relation>,
284) -> Result<Index> {
285 let mut reader = Reader { data, at: start };
286 let size = reader.u32()? as usize;
287 let id = reader.u32()?;
288 let kind = reader.u32()?;
289 let attribute_count = reader.u32()? as usize;
290 if attribute_count > 32 {
291 return Err(Error::format(format!(
292 "index claims {attribute_count} attributes"
293 )));
294 }
295 let mut attribute_ids = Vec::with_capacity(attribute_count);
296 for _ in 0..attribute_count {
297 attribute_ids.push(reader.u32()?);
298 }
299
300 let formats: Vec<AttributeFormat> = attribute_ids
301 .iter()
302 .map(|id| format_of(relation, *id))
303 .collect();
304
305 let entry_count = reader.u32()? as usize;
306 let mut entry_offsets = Vec::with_capacity(entry_count);
307 for _ in 0..entry_count {
308 entry_offsets.push(reader.u32()? as usize);
309 }
310 let mut record_numbers = Vec::with_capacity(entry_count);
311 for _ in 0..entry_count {
312 record_numbers.push(reader.u32()?);
313 }
314
315 let mut entries = Vec::with_capacity(entry_count);
316 for (offset, record_number) in entry_offsets.into_iter().zip(record_numbers) {
317 let at = offset
318 .checked_sub(table_offset)
319 .ok_or_else(|| Error::format("index entry offset points before its table"))?;
320 entries.push(parse_entry(data, at, record_number, &formats)?);
321 }
322
323 let index = Index {
324 id,
325 kind,
326 attribute_ids,
327 entries,
328 };
329 if index.encoded_len() != size {
330 return Err(Error::format(format!(
331 "index {id} claims {size} bytes but parses as {}",
332 index.encoded_len()
333 )));
334 }
335 Ok(index)
336}
337
338fn format_of(relation: Option<&Relation>, attribute_id: u32) -> AttributeFormat {
342 relation
343 .and_then(|relation| {
344 relation
345 .attributes
346 .iter()
347 .find(|attribute| attribute.id == attribute_id)
348 })
349 .map(|attribute| attribute.format)
350 .unwrap_or(AttributeFormat::Blob)
351}
352
353fn parse_entry(
354 data: &[u8],
355 at: usize,
356 record_number: u32,
357 formats: &[AttributeFormat],
358) -> Result<IndexEntry> {
359 let mut reader = Reader { data, at };
360 let payload = reader.u32()? as usize;
361 let mut key = Vec::with_capacity(formats.len());
362 for format in formats {
363 key.push(match format {
364 AttributeFormat::Sint32 | AttributeFormat::Uint32 => IndexValue::Integer(reader.u32()?),
365 _ => IndexValue::Bytes(reader.value()?),
366 });
367 }
368 let entry = IndexEntry { record_number, key };
369 if entry.payload_len() != payload {
370 return Err(Error::format(format!(
371 "index entry claims {payload} bytes but parses as {}",
372 entry.payload_len()
373 )));
374 }
375 Ok(entry)
376}
377
378fn write_index(out: &mut Vec<u8>, index: &Index, table_offset: usize, position: usize) {
379 out.extend_from_slice(&(index.encoded_len() as u32).to_be_bytes());
380 out.extend_from_slice(&index.id.to_be_bytes());
381 out.extend_from_slice(&index.kind.to_be_bytes());
382 out.extend_from_slice(&(index.attribute_ids.len() as u32).to_be_bytes());
383 for id in &index.attribute_ids {
384 out.extend_from_slice(&id.to_be_bytes());
385 }
386
387 out.extend_from_slice(&(index.entries.len() as u32).to_be_bytes());
388 let mut entry_position = position + index.header_len() + 4 + 8 * index.entries.len();
389 for entry in &index.entries {
390 out.extend_from_slice(&((table_offset + entry_position) as u32).to_be_bytes());
391 entry_position += entry.encoded_len();
392 }
393 for entry in &index.entries {
394 out.extend_from_slice(&entry.record_number.to_be_bytes());
395 }
396 for entry in &index.entries {
397 out.extend_from_slice(&(entry.payload_len() as u32).to_be_bytes());
398 for value in &entry.key {
399 value.write(out);
400 }
401 }
402}
403
404struct Reader<'a> {
405 data: &'a [u8],
406 at: usize,
407}
408
409impl Reader<'_> {
410 fn u32(&mut self) -> Result<u32> {
411 let bytes = self
412 .data
413 .get(self.at..self.at + 4)
414 .ok_or_else(|| Error::format("index region ends mid-word"))?;
415 self.at += 4;
416 Ok(u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]))
417 }
418
419 fn value(&mut self) -> Result<Vec<u8>> {
420 let len = self.u32()? as usize;
421 let bytes = self
422 .data
423 .get(self.at..self.at + len)
424 .ok_or_else(|| Error::format("index value runs past the region"))?
425 .to_vec();
426 self.at += pad4(4 + len) - 4;
427 Ok(bytes)
428 }
429}
430
431fn pad4(len: usize) -> usize {
432 (len + 3) & !3
433}
434
435#[cfg(test)]
436mod tests {
437 use super::*;
438 use crate::schema::{AttributeDef, RecordType};
439
440 fn bytes(text: &str) -> IndexValue {
441 IndexValue::Bytes(text.as_bytes().to_vec())
442 }
443
444 fn sample() -> IndexBlob {
445 IndexBlob {
446 indexes: vec![
447 Index {
448 id: 0,
449 kind: 1,
450 attribute_ids: vec![u32::from_be_bytes(*b"acct"), u32::from_be_bytes(*b"svce")],
451 entries: vec![
452 IndexEntry {
453 record_number: 0,
454 key: vec![bytes("alice"), bytes("myservice")],
455 },
456 IndexEntry {
457 record_number: 1,
458 key: vec![bytes("carol"), bytes("other")],
459 },
460 ],
461 },
462 Index {
463 id: 3,
464 kind: 0,
465 attribute_ids: vec![u32::from_be_bytes(*b"port")],
466 entries: vec![
467 IndexEntry {
468 record_number: 1,
469 key: vec![IndexValue::Integer(80)],
470 },
471 IndexEntry {
472 record_number: 0,
473 key: vec![IndexValue::Integer(8080)],
474 },
475 ],
476 },
477 ],
478 }
479 }
480
481 fn relation() -> Relation {
482 Relation {
483 record_type: RecordType::GENERIC_PASSWORD,
484 name: None,
485 attributes: vec![
486 AttributeDef {
487 id: u32::from_be_bytes(*b"acct"),
488 name: "acct".into(),
489 format: AttributeFormat::Blob,
490 },
491 AttributeDef {
492 id: u32::from_be_bytes(*b"svce"),
493 name: "svce".into(),
494 format: AttributeFormat::Blob,
495 },
496 AttributeDef {
497 id: u32::from_be_bytes(*b"port"),
498 name: "port".into(),
499 format: AttributeFormat::Uint32,
500 },
501 ],
502 }
503 }
504
505 #[test]
506 fn round_trips_at_any_table_offset() {
507 let blob = sample();
508 for table_offset in [0, 32, 492, 4096] {
509 let encoded = blob.to_bytes(table_offset);
510 assert_eq!(encoded.len(), blob.encoded_len());
511 assert_eq!(
512 IndexBlob::parse(&encoded, table_offset, Some(&relation())).unwrap(),
513 blob
514 );
515 }
516 }
517
518 #[test]
519 fn offsets_follow_the_regions_position_in_the_table() {
520 let blob = sample();
521 let low = blob.to_bytes(100);
522 let high = blob.to_bytes(200);
523 assert_eq!(low.len(), high.len(), "only the offsets differ");
524 assert_ne!(low, high, "a moved region must renumber its offsets");
525 }
526
527 #[test]
528 fn integer_keys_are_stored_raw_and_blob_keys_length_prefixed() {
529 let blob = IndexBlob {
530 indexes: vec![Index {
531 id: 0,
532 kind: 1,
533 attribute_ids: vec![u32::from_be_bytes(*b"port"), u32::from_be_bytes(*b"acct")],
534 entries: vec![IndexEntry {
535 record_number: 7,
536 key: vec![IndexValue::Integer(8080), bytes("bob")],
537 }],
538 }],
539 };
540 assert_eq!(blob.indexes[0].entries[0].payload_len(), 4 + 8);
542
543 let encoded = blob.to_bytes(0);
544 assert_eq!(
545 IndexBlob::parse(&encoded, 0, Some(&relation())).unwrap(),
546 blob
547 );
548 }
549
550 #[test]
551 fn empty_indexes_have_no_entry_arrays() {
552 let blob = IndexBlob {
553 indexes: vec![Index {
554 id: 0,
555 kind: 1,
556 attribute_ids: vec![u32::from_be_bytes(*b"acct")],
557 entries: Vec::new(),
558 }],
559 };
560 assert_eq!(blob.encoded_len(), 36);
562 let encoded = blob.to_bytes(32);
563 assert_eq!(
564 IndexBlob::parse(&encoded, 32, Some(&relation())).unwrap(),
565 blob
566 );
567 }
568
569 #[test]
570 fn insert_keeps_keys_sorted_and_record_numbers_aligned() {
571 let mut blob = IndexBlob {
572 indexes: vec![Index {
573 id: 0,
574 kind: 1,
575 attribute_ids: vec![u32::from_be_bytes(*b"acct")],
576 entries: Vec::new(),
577 }],
578 };
579
580 for (number, account) in [(0u32, "carol"), (1, "alice"), (2, "bob")] {
581 blob.insert_record(number, |_| bytes(account));
582 }
583
584 let entries = &blob.indexes[0].entries;
585 assert_eq!(
586 entries
587 .iter()
588 .map(|entry| entry.key[0].clone())
589 .collect::<Vec<_>>(),
590 vec![bytes("alice"), bytes("bob"), bytes("carol")]
591 );
592 assert_eq!(blob.record_numbers(0).unwrap(), vec![1, 2, 0]);
594 }
595
596 #[test]
597 fn remove_drops_only_that_records_entries() {
598 let mut blob = sample();
599 assert_eq!(blob.entry_count(), 4);
600 blob.remove_record(0);
601 assert_eq!(blob.entry_count(), 2);
602 assert!(
603 blob.indexes
604 .iter()
605 .all(|index| index.entries.iter().all(|entry| entry.record_number != 0))
606 );
607 }
608
609 #[test]
610 fn absent_values_index_as_empty_or_zero() {
611 assert_eq!(
612 IndexValue::from_value(None, AttributeFormat::Blob),
613 IndexValue::Bytes(Vec::new())
614 );
615 assert_eq!(
616 IndexValue::from_value(None, AttributeFormat::Uint32),
617 IndexValue::Integer(0)
618 );
619 assert_eq!(
620 IndexValue::from_value(Some(&Value::Uint32(5)), AttributeFormat::Uint32),
621 IndexValue::Integer(5)
622 );
623 assert_eq!(
624 IndexValue::from_value(Some(&Value::Blob(b"x".to_vec())), AttributeFormat::Blob),
625 IndexValue::Bytes(b"x".to_vec())
626 );
627 }
628
629 #[test]
630 fn malformed_regions_are_rejected() {
631 assert!(IndexBlob::parse(&[], 0, None).is_err());
632 let mut encoded = sample().to_bytes(0);
633 encoded[3] = 0xff;
634 assert!(IndexBlob::parse(&encoded, 0, Some(&relation())).is_err());
635 }
636}