1
+// This program is free software; you can redistribute it and/or modify
2
+// it under the terms of the GNU General Public License as published by
3
+// the Free Software Foundation: version 2 of the License, dated June 1991.
4
+//
5
+// This program is distributed in the hope that it will be useful,
6
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
7
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
8
+// GNU General Public License for more details.
9
+//
10
+// You should have received a copy of the GNU General Public License along
11
+// with this program; if not, see <https://www.gnu.org/licenses/>.
12
+
13
+use crate::hash::{HashAlgorithm, ObjectID, GIT_MAX_RAWSZ};
14
+use std::collections::BTreeMap;
15
+use std::convert::TryInto;
16
+use std::io::{self, Write};
17
+
18
+/// The type of object stored in the map.
19
+///
20
+/// If this value is `Reserved`, then it is never written to disk and is used primarily to store
21
+/// certain hard-coded objects, like the empty tree, empty blob, or null object ID.
22
+///
23
+/// If this value is `LooseObject`, then this represents a loose object. `Shallow` represents a
24
+/// shallow commit, its parent, or its tree. `Submodule` represents a submodule commit.
25
+#[repr(C)]
26
+#[derive(Debug, Clone, Copy, Ord, PartialOrd, Eq, PartialEq)]
27
+pub enum MapType {
28
+ Reserved = 0,
29
+ LooseObject = 1,
30
+ Shallow = 2,
31
+ Submodule = 3,
32
+}
33
+
34
+impl MapType {
35
+ pub fn from_u32(n: u32) -> Option<MapType> {
36
+ match n {
37
+ 0 => Some(Self::Reserved),
38
+ 1 => Some(Self::LooseObject),
39
+ 2 => Some(Self::Shallow),
40
+ 3 => Some(Self::Submodule),
41
+ _ => None,
42
+ }
43
+ }
44
+}
45
+
46
+/// The value of an object stored in a `ObjectMemoryMap`.
47
+///
48
+/// This keeps the object ID to which the key is mapped and its kind together.
49
+struct MappedObject {
50
+ oid: ObjectID,
51
+ kind: MapType,
52
+}
53
+
54
+/// Memory storage for a loose object.
55
+struct ObjectMemoryMap {
56
+ to_compat: BTreeMap<ObjectID, MappedObject>,
57
+ to_storage: BTreeMap<ObjectID, MappedObject>,
58
+ compat: HashAlgorithm,
59
+ storage: HashAlgorithm,
60
+}
61
+
62
+impl ObjectMemoryMap {
63
+ /// Create a new `ObjectMemoryMap`.
64
+ ///
65
+ /// The storage and compatibility `HashAlgorithm` instances are used to store the object IDs in
66
+ /// the correct map.
67
+ fn new(storage: HashAlgorithm, compat: HashAlgorithm) -> Self {
68
+ Self {
69
+ to_compat: BTreeMap::new(),
70
+ to_storage: BTreeMap::new(),
71
+ compat,
72
+ storage,
73
+ }
74
+ }
75
+
76
+ fn len(&self) -> usize {
77
+ self.to_compat.len()
78
+ }
79
+
80
+ /// Write this map to an interface implementing `std::io::Write`.
81
+ fn write<W: Write>(&self, wrtr: W) -> io::Result<()> {
82
+ const VERSION_NUMBER: u32 = 1;
83
+ const NUM_OBJECT_FORMATS: u32 = 2;
84
+ const PADDING: [u8; 4] = [0u8; 4];
85
+
86
+ let mut wrtr = wrtr;
87
+ let header_size: u32 = (4 * 5) + (4 + 4 + 8) * NUM_OBJECT_FORMATS + 8;
88
+
89
+ wrtr.write_all(b"LMAP")?;
90
+ wrtr.write_all(&VERSION_NUMBER.to_be_bytes())?;
91
+ wrtr.write_all(&header_size.to_be_bytes())?;
92
+ wrtr.write_all(&(self.to_compat.len() as u32).to_be_bytes())?;
93
+ wrtr.write_all(&NUM_OBJECT_FORMATS.to_be_bytes())?;
94
+
95
+ let storage_short_len = self.find_short_name_len(&self.to_compat, self.storage);
96
+ let compat_short_len = self.find_short_name_len(&self.to_storage, self.compat);
97
+
98
+ let storage_npadding = Self::required_nul_padding(self.to_compat.len(), storage_short_len);
99
+ let compat_npadding = Self::required_nul_padding(self.to_compat.len(), compat_short_len);
100
+
101
+ let mut offset: u64 = header_size as u64;
102
+
103
+ for (algo, len, npadding) in &[
104
+ (self.storage, storage_short_len, storage_npadding),
105
+ (self.compat, compat_short_len, compat_npadding),
106
+ ] {
107
+ wrtr.write_all(&algo.format_id().to_be_bytes())?;
108
+ wrtr.write_all(&(*len as u32).to_be_bytes())?;
109
+
110
+ offset += *npadding;
111
+ wrtr.write_all(&offset.to_be_bytes())?;
112
+
113
+ offset += self.to_compat.len() as u64 * (*len as u64 + algo.raw_len() as u64 + 4);
114
+ }
115
+
116
+ wrtr.write_all(&offset.to_be_bytes())?;
117
+
118
+ let order_map: BTreeMap<&ObjectID, usize> = self
119
+ .to_compat
120
+ .keys()
121
+ .enumerate()
122
+ .map(|(i, oid)| (oid, i))
123
+ .collect();
124
+
125
+ wrtr.write_all(&PADDING[0..storage_npadding as usize])?;
126
+ for oid in self.to_compat.keys() {
127
+ wrtr.write_all(&oid.as_slice().unwrap()[0..storage_short_len])?;
128
+ }
129
+ for oid in self.to_compat.keys() {
130
+ wrtr.write_all(oid.as_slice().unwrap())?;
131
+ }
132
+ for meta in self.to_compat.values() {
133
+ wrtr.write_all(&(meta.kind as u32).to_be_bytes())?;
134
+ }
135
+
136
+ wrtr.write_all(&PADDING[0..compat_npadding as usize])?;
137
+ for oid in self.to_storage.keys() {
138
+ wrtr.write_all(&oid.as_slice().unwrap()[0..compat_short_len])?;
139
+ }
140
+ for meta in self.to_compat.values() {
141
+ wrtr.write_all(meta.oid.as_slice().unwrap())?;
142
+ }
143
+ for meta in self.to_storage.values() {
144
+ wrtr.write_all(&(order_map[&meta.oid] as u32).to_be_bytes())?;
145
+ }
146
+
147
+ Ok(())
148
+ }
149
+
150
+ fn required_nul_padding(nitems: usize, short_len: usize) -> u64 {
151
+ let shortened_table_len = nitems as u64 * short_len as u64;
152
+ let misalignment = shortened_table_len & 3;
153
+ // If the value is 0, return 0; otherwise, return the difference from 4.
154
+ (4 - misalignment) & 3
155
+ }
156
+
157
+ fn last_matching_offset(a: &ObjectID, b: &ObjectID, algop: HashAlgorithm) -> usize {
158
+ for i in 0..=algop.raw_len() {
159
+ if a.hash[i] != b.hash[i] {
160
+ return i;
161
+ }
162
+ }
163
+ algop.raw_len()
164
+ }
165
+
166
+ fn find_short_name_len(
167
+ &self,
168
+ map: &BTreeMap<ObjectID, MappedObject>,
169
+ algop: HashAlgorithm,
170
+ ) -> usize {
171
+ if map.len() <= 1 {
172
+ return 1;
173
+ }
174
+ let mut len = 1;
175
+ let mut iter = map.keys();
176
+ let mut cur = match iter.next() {
177
+ Some(cur) => cur,
178
+ None => return len,
179
+ };
180
+ for item in iter {
181
+ let offset = Self::last_matching_offset(cur, item, algop);
182
+ if offset >= len {
183
+ len = offset + 1;
184
+ }
185
+ cur = item;
186
+ }
187
+ if len > algop.raw_len() {
188
+ algop.raw_len()
189
+ } else {
190
+ len
191
+ }
192
+ }
193
+}
194
+
195
+struct ObjectFormatData {
196
+ data_off: usize,
197
+ shortened_len: usize,
198
+ full_off: usize,
199
+ mapping_off: Option<usize>,
200
+}
201
+
202
+pub struct MmapedObjectMapIter<'a> {
203
+ offset: usize,
204
+ algos: Vec<HashAlgorithm>,
205
+ source: &'a MmapedObjectMap<'a>,
206
+}
207
+
208
+impl<'a> Iterator for MmapedObjectMapIter<'a> {
209
+ type Item = Vec<ObjectID>;
210
+
211
+ fn next(&mut self) -> Option<Self::Item> {
212
+ if self.offset >= self.source.nitems {
213
+ return None;
214
+ }
215
+ let offset = self.offset;
216
+ self.offset += 1;
217
+ let v: Vec<ObjectID> = self
218
+ .algos
219
+ .iter()
220
+ .cloned()
221
+ .filter_map(|algo| self.source.oid_from_offset(offset, algo))
222
+ .collect();
223
+ if v.len() != self.algos.len() {
224
+ return None;
225
+ }
226
+ Some(v)
227
+ }
228
+}
229
+
230
+#[allow(dead_code)]
231
+pub struct MmapedObjectMap<'a> {
232
+ memory: &'a [u8],
233
+ nitems: usize,
234
+ meta_off: usize,
235
+ obj_formats: BTreeMap<HashAlgorithm, ObjectFormatData>,
236
+ main_algo: HashAlgorithm,
237
+}
238
+
239
+#[derive(Debug)]
240
+#[allow(dead_code)]
241
+enum MmapedParseError {
242
+ HeaderTooSmall,
243
+ InvalidSignature,
244
+ InvalidVersion,
245
+ UnknownAlgorithm,
246
+ OffsetTooLarge,
247
+ TooFewObjectFormats,
248
+ UnalignedData,
249
+ InvalidTrailerOffset,
250
+}
251
+
252
+#[allow(dead_code)]
253
+impl<'a> MmapedObjectMap<'a> {
254
+ fn new(
255
+ slice: &'a [u8],
256
+ hash_algo: HashAlgorithm,
257
+ ) -> Result<MmapedObjectMap<'a>, MmapedParseError> {
258
+ let object_format_header_size = 4 + 4 + 8;
259
+ let trailer_offset_size = 8;
260
+ let header_size: usize =
261
+ 4 + 4 + 4 + 4 + 4 + object_format_header_size * 2 + trailer_offset_size;
262
+ if slice.len() < header_size {
263
+ return Err(MmapedParseError::HeaderTooSmall);
264
+ }
265
+ if slice[0..4] != *b"LMAP" {
266
+ return Err(MmapedParseError::InvalidSignature);
267
+ }
268
+ if Self::u32_at_offset(slice, 4) != 1 {
269
+ return Err(MmapedParseError::InvalidVersion);
270
+ }
271
+ let _ = Self::u32_at_offset(slice, 8) as usize;
272
+ let nitems = Self::u32_at_offset(slice, 12) as usize;
273
+ let nobj_formats = Self::u32_at_offset(slice, 16) as usize;
274
+ if nobj_formats < 2 {
275
+ return Err(MmapedParseError::TooFewObjectFormats);
276
+ }
277
+ let mut offset = 20;
278
+ let mut meta_off = None;
279
+ let mut data = BTreeMap::new();
280
+ for i in 0..nobj_formats {
281
+ if offset + object_format_header_size + trailer_offset_size > slice.len() {
282
+ return Err(MmapedParseError::HeaderTooSmall);
283
+ }
284
+ let format_id = Self::u32_at_offset(slice, offset);
285
+ let shortened_len = Self::u32_at_offset(slice, offset + 4) as usize;
286
+ let data_off = Self::u64_at_offset(slice, offset + 8);
287
+
288
+ let algo = HashAlgorithm::from_format_id(format_id)
289
+ .ok_or(MmapedParseError::UnknownAlgorithm)?;
290
+ let data_off: usize = data_off
291
+ .try_into()
292
+ .map_err(|_| MmapedParseError::OffsetTooLarge)?;
293
+
294
+ // Every object format must have these entries.
295
+ let shortened_table_len = shortened_len
296
+ .checked_mul(nitems)
297
+ .ok_or(MmapedParseError::OffsetTooLarge)?;
298
+ let full_off = data_off
299
+ .checked_add(shortened_table_len)
300
+ .ok_or(MmapedParseError::OffsetTooLarge)?;
301
+ Self::verify_aligned(full_off)?;
302
+ Self::verify_valid(slice, full_off as u64)?;
303
+
304
+ let full_length = algo
305
+ .raw_len()
306
+ .checked_mul(nitems)
307
+ .ok_or(MmapedParseError::OffsetTooLarge)?;
308
+ let off = full_length
309
+ .checked_add(full_off)
310
+ .ok_or(MmapedParseError::OffsetTooLarge)?;
311
+ Self::verify_aligned(off)?;
312
+ Self::verify_valid(slice, off as u64)?;
313
+
314
+ // This is for the metadata for the first object format and for the order mapping for
315
+ // other object formats.
316
+ let meta_size = nitems
317
+ .checked_mul(4)
318
+ .ok_or(MmapedParseError::OffsetTooLarge)?;
319
+ let meta_end = off
320
+ .checked_add(meta_size)
321
+ .ok_or(MmapedParseError::OffsetTooLarge)?;
322
+ Self::verify_valid(slice, meta_end as u64)?;
323
+
324
+ let mut mapping_off = None;
325
+ if i == 0 {
326
+ meta_off = Some(off);
327
+ } else {
328
+ mapping_off = Some(off);
329
+ }
330
+
331
+ data.insert(
332
+ algo,
333
+ ObjectFormatData {
334
+ data_off,
335
+ shortened_len,
336
+ full_off,
337
+ mapping_off,
338
+ },
339
+ );
340
+ offset += object_format_header_size;
341
+ }
342
+ let trailer = Self::u64_at_offset(slice, offset);
343
+ Self::verify_aligned(trailer as usize)?;
344
+ Self::verify_valid(slice, trailer)?;
345
+ let end = trailer
346
+ .checked_add(hash_algo.raw_len() as u64)
347
+ .ok_or(MmapedParseError::OffsetTooLarge)?;
348
+ if end != slice.len() as u64 {
349
+ return Err(MmapedParseError::InvalidTrailerOffset);
350
+ }
351
+ match meta_off {
352
+ Some(meta_off) => Ok(MmapedObjectMap {
353
+ memory: slice,
354
+ nitems,
355
+ meta_off,
356
+ obj_formats: data,
357
+ main_algo: hash_algo,
358
+ }),
359
+ None => Err(MmapedParseError::TooFewObjectFormats),
360
+ }
361
+ }
362
+
363
+ fn iter(&self) -> MmapedObjectMapIter<'_> {
364
+ let mut algos = Vec::with_capacity(self.obj_formats.len());
365
+ algos.push(self.main_algo);
366
+ for algo in self.obj_formats.keys().cloned() {
367
+ if algo != self.main_algo {
368
+ algos.push(algo);
369
+ }
370
+ }
371
+ MmapedObjectMapIter {
372
+ offset: 0,
373
+ algos,
374
+ source: self,
375
+ }
376
+ }
377
+
378
+ /// Treats `sl` as if it were a set of slices of `wanted.len()` bytes, and searches for
379
+ /// `wanted` within it.
380
+ ///
381
+ /// If found, returns the offset of the subslice in `sl`.
382
+ ///
383
+ /// ```
384
+ /// let sl = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
385
+ ///
386
+ /// assert_eq!(MmapedObjectMap::binary_search_slice(sl, &[2, 3]), Some(1));
387
+ /// assert_eq!(MmapedObjectMap::binary_search_slice(sl, &[6, 7]), Some(4));
388
+ /// assert_eq!(MmapedObjectMap::binary_search_slice(sl, &[1, 2]), None);
389
+ /// assert_eq!(MmapedObjectMap::binary_search_slice(sl, &[10, 20]), None);
390
+ /// ```
391
+ fn binary_search_slice(sl: &[u8], wanted: &[u8]) -> Option<usize> {
392
+ let len = wanted.len();
393
+ let res = sl.binary_search_by(|item| {
394
+ // We would like element_offset, but that is currently nightly only. Instead, do a
395
+ // pointer subtraction to find the index.
396
+ let index = unsafe { (item as *const u8).offset_from(sl.as_ptr()) } as usize;
397
+ // Now we have the index of this object. Round it down to the nearest full-sized
398
+ // chunk to find the actual offset where this starts.
399
+ let index = index - (index % len);
400
+ // Compute the comparison of that value instead, which will provide the expected
401
+ // result.
402
+ sl[index..index + wanted.len()].cmp(wanted)
403
+ });
404
+ res.ok().map(|offset| offset / len)
405
+ }
406
+
407
+ /// Look up `oid` in the map in order to convert it to `algo`.
408
+ ///
409
+ /// If this object is in the map, return the offset in the table for the main algorithm.
410
+ fn look_up_object(&self, oid: &ObjectID) -> Option<usize> {
411
+ let oid_algo = HashAlgorithm::from_u32(oid.algo)?;
412
+ let params = self.obj_formats.get(&oid_algo)?;
413
+ let short_table =
414
+ &self.memory[params.data_off..params.data_off + (params.shortened_len * self.nitems)];
415
+ let index = Self::binary_search_slice(
416
+ short_table,
417
+ &oid.as_slice().unwrap()[0..params.shortened_len],
418
+ )?;
419
+ match params.mapping_off {
420
+ Some(from_off) => {
421
+ // oid is in a compatibility algorithm. Find the mapping index.
422
+ let mapped = Self::u32_at_offset(self.memory, from_off + index * 4) as usize;
423
+ if mapped >= self.nitems {
424
+ return None;
425
+ }
426
+ let oid_offset = params.full_off + mapped * oid_algo.raw_len();
427
+ if self.memory[oid_offset..oid_offset + oid_algo.raw_len()]
428
+ != *oid.as_slice().unwrap()
429
+ {
430
+ return None;
431
+ }
432
+ Some(mapped)
433
+ }
434
+ None => {
435
+ // oid is in the main algorithm. Find the object ID in the main map to confirm
436
+ // it's correct.
437
+ let oid_offset = params.full_off + index * oid_algo.raw_len();
438
+ if self.memory[oid_offset..oid_offset + oid_algo.raw_len()]
439
+ != *oid.as_slice().unwrap()
440
+ {
441
+ return None;
442
+ }
443
+ Some(index)
444
+ }
445
+ }
446
+ }
447
+
448
+ #[allow(dead_code)]
449
+ fn map_object(&self, oid: &ObjectID, algo: HashAlgorithm) -> Option<MappedObject> {
450
+ let main = self.look_up_object(oid)?;
451
+ let meta = MapType::from_u32(Self::u32_at_offset(self.memory, self.meta_off + (main * 4)))?;
452
+ Some(MappedObject {
453
+ oid: self.oid_from_offset(main, algo)?,
454
+ kind: meta,
455
+ })
456
+ }
457
+
458
+ fn map_oid(&self, oid: &ObjectID, algo: HashAlgorithm) -> Option<ObjectID> {
459
+ if algo as u32 == oid.algo {
460
+ return Some(oid.clone());
461
+ }
462
+
463
+ let main = self.look_up_object(oid)?;
464
+ self.oid_from_offset(main, algo)
465
+ }
466
+
467
+ fn oid_from_offset(&self, offset: usize, algo: HashAlgorithm) -> Option<ObjectID> {
468
+ let aparams = self.obj_formats.get(&algo)?;
469
+
470
+ let mut hash = [0u8; GIT_MAX_RAWSZ];
471
+ let len = algo.raw_len();
472
+ let oid_off = aparams.full_off + (offset * len);
473
+ hash[0..len].copy_from_slice(&self.memory[oid_off..oid_off + len]);
474
+ Some(ObjectID {
475
+ hash,
476
+ algo: algo as u32,
477
+ })
478
+ }
479
+
480
+ fn u32_at_offset(slice: &[u8], offset: usize) -> u32 {
481
+ u32::from_be_bytes(slice[offset..offset + 4].try_into().unwrap())
482
+ }
483
+
484
+ fn u64_at_offset(slice: &[u8], offset: usize) -> u64 {
485
+ u64::from_be_bytes(slice[offset..offset + 8].try_into().unwrap())
486
+ }
487
+
488
+ fn verify_aligned(offset: usize) -> Result<(), MmapedParseError> {
489
+ if (offset & 3) != 0 {
490
+ return Err(MmapedParseError::UnalignedData);
491
+ }
492
+ Ok(())
493
+ }
494
+
495
+ fn verify_valid(slice: &[u8], offset: u64) -> Result<(), MmapedParseError> {
496
+ if offset >= slice.len() as u64 {
497
+ return Err(MmapedParseError::OffsetTooLarge);
498
+ }
499
+ Ok(())
500
+ }
501
+}
502
+
503
+/// A map for loose and other non-packed object IDs that maps between a storage and compatibility
504
+/// mapping.
505
+///
506
+/// In addition to the in-memory option, there is an optional batched storage, which can be used to
507
+/// write objects to disk in an efficient way.
508
+pub struct ObjectMap {
509
+ mem: ObjectMemoryMap,
510
+ batch: Option<ObjectMemoryMap>,
511
+}
512
+
513
+impl ObjectMap {
514
+ /// Create a new `ObjectMap` with the given hash algorithms.
515
+ ///
516
+ /// This initializes the memory map to automatically map the empty tree, empty blob, and null
517
+ /// object ID.
518
+ pub fn new(storage: HashAlgorithm, compat: HashAlgorithm) -> Self {
519
+ let mut map = ObjectMemoryMap::new(storage, compat);
520
+ for (main, compat) in &[
521
+ (storage.empty_tree(), compat.empty_tree()),
522
+ (storage.empty_blob(), compat.empty_blob()),
523
+ (storage.null_oid(), compat.null_oid()),
524
+ ] {
525
+ map.to_storage.insert(
526
+ (*compat).clone(),
527
+ MappedObject {
528
+ oid: (*main).clone(),
529
+ kind: MapType::Reserved,
530
+ },
531
+ );
532
+ map.to_compat.insert(
533
+ (*main).clone(),
534
+ MappedObject {
535
+ oid: (*compat).clone(),
536
+ kind: MapType::Reserved,
537
+ },
538
+ );
539
+ }
540
+ Self {
541
+ mem: map,
542
+ batch: None,
543
+ }
544
+ }
545
+
546
+ pub fn hash_algo(&self) -> HashAlgorithm {
547
+ self.mem.storage
548
+ }
549
+
550
+ /// Start a batch for efficient writing.
551
+ ///
552
+ /// If there is already a batch started, this does nothing and the existing batch is retained.
553
+ pub fn start_batch(&mut self) {
554
+ if self.batch.is_none() {
555
+ self.batch = Some(ObjectMemoryMap::new(self.mem.storage, self.mem.compat));
556
+ }
557
+ }
558
+
559
+ pub fn batch_len(&self) -> Option<usize> {
560
+ self.batch.as_ref().map(|b| b.len())
561
+ }
562
+
563
+ /// If a batch exists, write it to the writer.
564
+ pub fn finish_batch<W: Write>(&mut self, w: W) -> io::Result<()> {
565
+ if let Some(txn) = self.batch.take() {
566
+ txn.write(w)?;
567
+ }
568
+ Ok(())
569
+ }
570
+
571
+ /// If a batch exists, write it to the writer.
572
+ pub fn abort_batch(&mut self) {
573
+ self.batch = None;
574
+ }
575
+
576
+ /// Return whether there is a batch already started.
577
+ ///
578
+ /// If you just want a batch to exist and don't care whether one has already been started, you
579
+ /// may simply call `start_batch` unconditionally.
580
+ pub fn has_batch(&self) -> bool {
581
+ self.batch.is_some()
582
+ }
583
+
584
+ /// Insert an object into the map.
585
+ ///
586
+ /// If `write` is true and there is a batch started, write the object into the batch as well as
587
+ /// into the memory map.
588
+ pub fn insert(&mut self, oid1: &ObjectID, oid2: &ObjectID, kind: MapType, write: bool) {
589
+ let (compat_oid, storage_oid) =
590
+ if HashAlgorithm::from_u32(oid1.algo) == Some(self.mem.compat) {
591
+ (oid1, oid2)
592
+ } else {
593
+ (oid2, oid1)
594
+ };
595
+ Self::insert_into(&mut self.mem, storage_oid, compat_oid, kind);
596
+ if write {
597
+ if let Some(ref mut batch) = self.batch {
598
+ Self::insert_into(batch, storage_oid, compat_oid, kind);
599
+ }
600
+ }
601
+ }
602
+
603
+ fn insert_into(
604
+ map: &mut ObjectMemoryMap,
605
+ storage: &ObjectID,
606
+ compat: &ObjectID,
607
+ kind: MapType,
608
+ ) {
609
+ map.to_compat.insert(
610
+ storage.clone(),
611
+ MappedObject {
612
+ oid: compat.clone(),
613
+ kind,
614
+ },
615
+ );
616
+ map.to_storage.insert(
617
+ compat.clone(),
618
+ MappedObject {
619
+ oid: storage.clone(),
620
+ kind,
621
+ },
622
+ );
623
+ }
624
+
625
+ #[allow(dead_code)]
626
+ fn map_object(&self, oid: &ObjectID, algo: HashAlgorithm) -> Option<&MappedObject> {
627
+ let map = if algo == self.mem.storage {
628
+ &self.mem.to_storage
629
+ } else {
630
+ &self.mem.to_compat
631
+ };
632
+ map.get(oid)
633
+ }
634
+
635
+ #[allow(dead_code)]
636
+ fn map_oid<'a, 'b: 'a>(
637
+ &'b self,
638
+ oid: &'a ObjectID,
639
+ algo: HashAlgorithm,
640
+ ) -> Option<&'a ObjectID> {
641
+ if algo as u32 == oid.algo {
642
+ return Some(oid);
643
+ }
644
+ let entry = self.map_object(oid, algo);
645
+ entry.map(|obj| &obj.oid)
646
+ }
647
+}
648
+
649
+#[cfg(test)]
650
+mod tests {
651
+ use super::{MapType, MmapedObjectMap, ObjectMap, ObjectMemoryMap};
652
+ use crate::hash::{CryptoDigest, CryptoHasher, HashAlgorithm, ObjectID};
653
+ use std::convert::TryInto;
654
+ use std::io::{self, Cursor, Write};
655
+
656
+ struct TrailingWriter {
657
+ curs: Cursor<Vec<u8>>,
658
+ hasher: CryptoHasher,
659
+ }
660
+
661
+ impl TrailingWriter {
662
+ fn new() -> Self {
663
+ Self {
664
+ curs: Cursor::new(Vec::new()),
665
+ hasher: CryptoHasher::new(HashAlgorithm::SHA256),
666
+ }
667
+ }
668
+
669
+ fn finalize(mut self) -> Vec<u8> {
670
+ let _ = self.hasher.flush();
671
+ let mut v = self.curs.into_inner();
672
+ v.extend(self.hasher.into_vec());
673
+ v
674
+ }
675
+ }
676
+
677
+ impl Write for TrailingWriter {
678
+ fn write(&mut self, data: &[u8]) -> io::Result<usize> {
679
+ self.hasher.write_all(data)?;
680
+ self.curs.write_all(data)?;
681
+ Ok(data.len())
682
+ }
683
+
684
+ fn flush(&mut self) -> io::Result<()> {
685
+ self.hasher.flush()?;
686
+ self.curs.flush()?;
687
+ Ok(())
688
+ }
689
+ }
690
+
691
+ fn sha1_oid(b: &[u8]) -> ObjectID {
692
+ assert_eq!(b.len(), 20);
693
+ let mut data = [0u8; 32];
694
+ data[0..20].copy_from_slice(b);
695
+ ObjectID {
696
+ hash: data,
697
+ algo: HashAlgorithm::SHA1 as u32,
698
+ }
699
+ }
700
+
701
+ fn sha256_oid(b: &[u8]) -> ObjectID {
702
+ assert_eq!(b.len(), 32);
703
+ ObjectID {
704
+ hash: b.try_into().unwrap(),
705
+ algo: HashAlgorithm::SHA256 as u32,
706
+ }
707
+ }
708
+
709
+ #[allow(clippy::type_complexity)]
710
+ fn test_entries() -> &'static [(&'static str, &'static [u8], &'static [u8], MapType, bool)] {
711
+ // These are all example blobs containing the content in the first argument.
712
+ &[
713
+ ("abc", b"\xf2\xba\x8f\x84\xab\x5c\x1b\xce\x84\xa7\xb4\x41\xcb\x19\x59\xcf\xc7\x09\x3b\x7f", b"\xc1\xcf\x6e\x46\x50\x77\x93\x0e\x88\xdc\x51\x36\x64\x1d\x40\x2f\x72\xa2\x29\xdd\xd9\x96\xf6\x27\xd6\x0e\x96\x39\xea\xba\x35\xa6", MapType::LooseObject, false),
714
+ ("def", b"\x0c\x00\x38\x32\xe7\xbf\xa9\xca\x8b\x5c\x20\x35\xc9\xbd\x68\x4a\x5f\x26\x23\xbc", b"\x8a\x90\x17\x26\x48\x4d\xb0\xf2\x27\x9f\x30\x8d\x58\x96\xd9\x6b\xf6\x3a\xd6\xde\x95\x7c\xa3\x8a\xdc\x33\x61\x68\x03\x6e\xf6\x63", MapType::Shallow, true),
715
+ ("ghi", b"\x45\xa8\x2e\x29\x5c\x52\x47\x31\x14\xc5\x7c\x18\xf4\xf5\x23\x68\xdf\x2a\x3c\xfd", b"\x6e\x47\x4c\x74\xf5\xd7\x78\x14\xc7\xf7\xf0\x7c\x37\x80\x07\x90\x53\x42\xaf\x42\x81\xe6\x86\x8d\x33\x46\x45\x4b\xb8\x63\xab\xc3", MapType::Submodule, false),
716
+ ("jkl", b"\x45\x32\x8c\x36\xff\x2e\x9b\x9b\x4e\x59\x2c\x84\x7d\x3f\x9a\x7f\xd9\xb3\xe7\x16", b"\xc3\xee\xf7\x54\xa2\x1e\xc6\x9d\x43\x75\xbe\x6f\x18\x47\x89\xa8\x11\x6f\xd9\x66\xfc\x67\xdc\x31\xd2\x11\x15\x42\xc8\xd5\xa0\xaf", MapType::LooseObject, true),
717
+ ]
718
+ }
719
+
720
+ fn test_map(write_all: bool) -> Box<ObjectMap> {
721
+ let mut map = Box::new(ObjectMap::new(HashAlgorithm::SHA256, HashAlgorithm::SHA1));
722
+
723
+ map.start_batch();
724
+
725
+ for (_blob_content, sha1, sha256, kind, swap) in test_entries() {
726
+ let s256 = sha256_oid(sha256);
727
+ let s1 = sha1_oid(sha1);
728
+ let write = write_all || (*kind as u32 & 2) == 0;
729
+ if *swap {
730
+ // Insert the item into the batch arbitrarily based on the type. This tests that
731
+ // we can specify either order and we'll do the right thing.
732
+ map.insert(&s256, &s1, *kind, write);
733
+ } else {
734
+ map.insert(&s1, &s256, *kind, write);
735
+ }
736
+ }
737
+
738
+ map
739
+ }
740
+
741
+ #[test]
742
+ fn can_read_and_write_format() {
743
+ for full in &[true, false] {
744
+ let mut map = test_map(*full);
745
+ let mut wrtr = TrailingWriter::new();
746
+ map.finish_batch(&mut wrtr).unwrap();
747
+
748
+ assert!(!map.has_batch());
749
+
750
+ let data = wrtr.finalize();
751
+ MmapedObjectMap::new(&data, HashAlgorithm::SHA256).unwrap();
752
+ }
753
+ }
754
+
755
+ #[test]
756
+ fn looks_up_from_mmaped() {
757
+ let mut map = test_map(true);
758
+ let mut wrtr = TrailingWriter::new();
759
+ map.finish_batch(&mut wrtr).unwrap();
760
+
761
+ assert!(!map.has_batch());
762
+
763
+ let data = wrtr.finalize();
764
+ let entries = test_entries();
765
+ let map = MmapedObjectMap::new(&data, HashAlgorithm::SHA256).unwrap();
766
+
767
+ for (_, sha1, sha256, kind, _) in entries {
768
+ let s256 = sha256_oid(sha256);
769
+ let s1 = sha1_oid(sha1);
770
+
771
+ let res = map.map_object(&s256, HashAlgorithm::SHA1).unwrap();
772
+ assert_eq!(res.oid, s1);
773
+ assert_eq!(res.kind, *kind);
774
+ let res = map.map_oid(&s256, HashAlgorithm::SHA1).unwrap();
775
+ assert_eq!(res, s1);
776
+
777
+ let res = map.map_object(&s256, HashAlgorithm::SHA256).unwrap();
778
+ assert_eq!(res.oid, s256);
779
+ assert_eq!(res.kind, *kind);
780
+ let res = map.map_oid(&s256, HashAlgorithm::SHA256).unwrap();
781
+ assert_eq!(res, s256);
782
+
783
+ let res = map.map_object(&s1, HashAlgorithm::SHA256).unwrap();
784
+ assert_eq!(res.oid, s256);
785
+ assert_eq!(res.kind, *kind);
786
+ let res = map.map_oid(&s1, HashAlgorithm::SHA256).unwrap();
787
+ assert_eq!(res, s256);
788
+
789
+ let res = map.map_object(&s1, HashAlgorithm::SHA1).unwrap();
790
+ assert_eq!(res.oid, s1);
791
+ assert_eq!(res.kind, *kind);
792
+ let res = map.map_oid(&s1, HashAlgorithm::SHA1).unwrap();
793
+ assert_eq!(res, s1);
794
+ }
795
+
796
+ for octet in &[0x00u8, 0x6d, 0x6e, 0x8a, 0xff] {
797
+ let missing_oid = ObjectID {
798
+ hash: [*octet; 32],
799
+ algo: HashAlgorithm::SHA256 as u32,
800
+ };
801
+
802
+ assert!(map.map_object(&missing_oid, HashAlgorithm::SHA1).is_none());
803
+ assert!(map.map_oid(&missing_oid, HashAlgorithm::SHA1).is_none());
804
+
805
+ assert_eq!(
806
+ map.map_oid(&missing_oid, HashAlgorithm::SHA256).unwrap(),
807
+ missing_oid
808
+ );
809
+ }
810
+ }
811
+
812
+ #[test]
813
+ fn binary_searches_slices_correctly() {
814
+ let sl = &[
815
+ 0, 1, 2, 15, 14, 13, 18, 10, 2, 20, 20, 20, 21, 21, 0, 21, 21, 1, 21, 21, 21, 21, 21,
816
+ 22, 22, 23, 24,
817
+ ];
818
+
819
+ let expected: &[(&[u8], Option<usize>)] = &[
820
+ (&[0, 1, 2], Some(0)),
821
+ (&[15, 14, 13], Some(1)),
822
+ (&[18, 10, 2], Some(2)),
823
+ (&[20, 20, 20], Some(3)),
824
+ (&[21, 21, 0], Some(4)),
825
+ (&[21, 21, 1], Some(5)),
826
+ (&[21, 21, 21], Some(6)),
827
+ (&[21, 21, 22], Some(7)),
828
+ (&[22, 23, 24], Some(8)),
829
+ (&[2, 15, 14], None),
830
+ (&[0, 21, 21], None),
831
+ (&[21, 21, 23], None),
832
+ (&[22, 22, 23], None),
833
+ (&[0xff, 0xff, 0xff], None),
834
+ (&[0, 0, 0], None),
835
+ ];
836
+
837
+ for (wanted, value) in expected {
838
+ assert_eq!(MmapedObjectMap::binary_search_slice(sl, wanted), *value);
839
+ }
840
+ }
841
+
842
+ #[test]
843
+ fn looks_up_oid_correctly() {
844
+ let map = test_map(false);
845
+ let entries = test_entries();
846
+
847
+ let s256 = sha256_oid(entries[0].2);
848
+ let s1 = sha1_oid(entries[0].1);
849
+
850
+ let missing_oid = ObjectID {
851
+ hash: [0xffu8; 32],
852
+ algo: HashAlgorithm::SHA256 as u32,
853
+ };
854
+
855
+ let res = map.map_object(&s256, HashAlgorithm::SHA1).unwrap();
856
+ assert_eq!(res.oid, s1);
857
+ assert_eq!(res.kind, MapType::LooseObject);
858
+ let res = map.map_oid(&s256, HashAlgorithm::SHA1).unwrap();
859
+ assert_eq!(*res, s1);
860
+
861
+ let res = map.map_object(&s1, HashAlgorithm::SHA256).unwrap();
862
+ assert_eq!(res.oid, s256);
863
+ assert_eq!(res.kind, MapType::LooseObject);
864
+ let res = map.map_oid(&s1, HashAlgorithm::SHA256).unwrap();
865
+ assert_eq!(*res, s256);
866
+
867
+ assert!(map.map_object(&missing_oid, HashAlgorithm::SHA1).is_none());
868
+ assert!(map.map_oid(&missing_oid, HashAlgorithm::SHA1).is_none());
869
+
870
+ assert_eq!(
871
+ *map.map_oid(&missing_oid, HashAlgorithm::SHA256).unwrap(),
872
+ missing_oid
873
+ );
874
+ }
875
+
876
+ #[test]
877
+ fn looks_up_known_oids_correctly() {
878
+ let map = test_map(false);
879
+
880
+ let funcs: &[&dyn Fn(HashAlgorithm) -> &'static ObjectID] = &[
881
+ &|h: HashAlgorithm| h.empty_tree(),
882
+ &|h: HashAlgorithm| h.empty_blob(),
883
+ &|h: HashAlgorithm| h.null_oid(),
884
+ ];
885
+
886
+ for f in funcs {
887
+ let s256 = f(HashAlgorithm::SHA256);
888
+ let s1 = f(HashAlgorithm::SHA1);
889
+
890
+ let res = map.map_object(s256, HashAlgorithm::SHA1).unwrap();
891
+ assert_eq!(res.oid, *s1);
892
+ assert_eq!(res.kind, MapType::Reserved);
893
+ let res = map.map_oid(s256, HashAlgorithm::SHA1).unwrap();
894
+ assert_eq!(*res, *s1);
895
+
896
+ let res = map.map_object(s1, HashAlgorithm::SHA256).unwrap();
897
+ assert_eq!(res.oid, *s256);
898
+ assert_eq!(res.kind, MapType::Reserved);
899
+ let res = map.map_oid(s1, HashAlgorithm::SHA256).unwrap();
900
+ assert_eq!(*res, *s256);
901
+ }
902
+ }
903
+
904
+ #[test]
905
+ fn nul_padding() {
906
+ assert_eq!(ObjectMemoryMap::required_nul_padding(1, 1), 3);
907
+ assert_eq!(ObjectMemoryMap::required_nul_padding(2, 1), 2);
908
+ assert_eq!(ObjectMemoryMap::required_nul_padding(3, 1), 1);
909
+ assert_eq!(ObjectMemoryMap::required_nul_padding(2, 2), 0);
910
+
911
+ assert_eq!(ObjectMemoryMap::required_nul_padding(39, 3), 3);
912
+ }
913
+}