1use std::io::{Seek, SeekFrom, Write};
2
3use crate::error::{Error, Result};
4use crate::format::btree_v2::BTreeV2Header;
5use crate::format::checksum::checksum_metadata;
6use crate::format::fractal_heap::FractalHeapHeader;
7use crate::format::messages::attribute::AttributeMessage;
8use crate::format::messages::attribute_info::AttributeInfoMessage;
9use crate::format::object_header::{
10 self, HDR_ATTR_STORE_PHASE_CHANGE, HDR_CHUNK0_SIZE_MASK, HDR_STORE_TIMES, HDR_V2_KNOWN_FLAGS,
11};
12
13use super::MutableFile;
14
15#[derive(Debug)]
16struct CompactAttributeMessageLocation {
17 msg_type_offset: u64,
18 msg_data_offset: u64,
19 oh_start: u64,
20 oh_check_len: usize,
21 raw_data: Vec<u8>,
22}
23
24#[derive(Debug)]
25struct DenseAttributeLocation {
26 attr_info_addr: u64,
27 heap: FractalHeapHeader,
28 btree: BTreeV2Header,
29 leaf_records: Vec<u8>,
30 record_index: usize,
31 direct_block_addr: u64,
32 direct_block_size: u64,
33 object_offset: u64,
34 raw_data: Vec<u8>,
35}
36
37impl MutableFile {
38 pub fn delete_root_attr(&mut self, name: &str) -> Result<()> {
44 self.delete_attr_at(self.superblock.root_addr, name)
45 }
46
47 pub fn delete_group_attr(&mut self, group_path: &str, name: &str) -> Result<()> {
49 let group = self.group(group_path)?;
50 self.delete_attr_at(group.addr(), name)
51 }
52
53 pub fn delete_dataset_attr(&mut self, dataset_path: &str, name: &str) -> Result<()> {
55 let dataset = self.dataset(dataset_path)?;
56 self.delete_attr_at(dataset.addr(), name)
57 }
58
59 pub fn rename_root_attr(&mut self, old_name: &str, new_name: &str) -> Result<()> {
64 self.rename_attr_at(self.superblock.root_addr, old_name, new_name)
65 }
66
67 pub fn rename_group_attr(
69 &mut self,
70 group_path: &str,
71 old_name: &str,
72 new_name: &str,
73 ) -> Result<()> {
74 let group = self.group(group_path)?;
75 self.rename_attr_at(group.addr(), old_name, new_name)
76 }
77
78 pub fn rename_dataset_attr(
80 &mut self,
81 dataset_path: &str,
82 old_name: &str,
83 new_name: &str,
84 ) -> Result<()> {
85 let dataset = self.dataset(dataset_path)?;
86 self.rename_attr_at(dataset.addr(), old_name, new_name)
87 }
88
89 fn delete_attr_at(&mut self, object_addr: u64, name: &str) -> Result<()> {
90 match self.find_compact_attribute_message_in_oh(object_addr, name) {
91 Ok(location) => {
92 self.write_handle
93 .seek(SeekFrom::Start(location.msg_type_offset))?;
94 self.write_handle
95 .write_all(&[object_header::MSG_NIL as u8])?;
96 self.rewrite_oh_checksum(location.oh_start, location.oh_check_len)?;
97 }
98 Err(Error::Unsupported(message)) if message.contains("dense attributes") => {
99 let location = self.find_dense_attribute_location(object_addr, name, None)?;
100 self.delete_dense_attribute(location)?;
101 }
102 Err(err) => return Err(err),
103 }
104 self.write_handle.flush()?;
105 self.reopen_reader()?;
106 Ok(())
107 }
108
109 fn rename_attr_at(&mut self, object_addr: u64, old_name: &str, new_name: &str) -> Result<()> {
110 if new_name.is_empty() {
111 return Err(Error::InvalidFormat(
112 "attribute name cannot be empty".into(),
113 ));
114 }
115 if old_name == new_name {
116 return Ok(());
117 }
118 match self.find_compact_attribute_rename_location(object_addr, old_name, new_name) {
119 Ok(mut location) => {
120 let (name_offset, name_size) =
121 Self::compact_attribute_name_field(&location.raw_data)?;
122 if new_name.len() + 1 != name_size {
123 return Err(Error::Unsupported(
124 "in-place compact attribute rename cannot grow or shrink the encoded name field"
125 .into(),
126 ));
127 }
128 let name_offset_u64 = Self::usize_to_u64(name_offset, "attribute name offset")?;
129 let file_name_offset = location
130 .msg_data_offset
131 .checked_add(name_offset_u64)
132 .ok_or_else(|| Error::InvalidFormat("attribute name offset overflow".into()))?;
133
134 let encoded_name = encode_attribute_name_in_place(
135 &mut location.raw_data,
136 name_offset,
137 name_size,
138 new_name,
139 )?;
140 self.write_handle.seek(SeekFrom::Start(file_name_offset))?;
141 self.write_handle.write_all(encoded_name)?;
142 self.rewrite_oh_checksum(location.oh_start, location.oh_check_len)?;
143 }
144 Err(Error::Unsupported(message)) if message.contains("dense attributes") => {
145 let location =
146 self.find_dense_attribute_location(object_addr, old_name, Some(new_name))?;
147 self.rename_dense_attribute(location, new_name)?;
148 }
149 Err(err) => return Err(err),
150 }
151 self.write_handle.flush()?;
152 self.reopen_reader()?;
153 Ok(())
154 }
155
156 fn compact_attribute_name_field(raw: &[u8]) -> Result<(usize, usize)> {
157 if raw.len() < 4 {
158 return Err(Error::InvalidFormat("attribute message too short".into()));
159 }
160 let version = raw
161 .first()
162 .copied()
163 .ok_or_else(|| Error::InvalidFormat("attribute message too short".into()))?;
164 let name_size = usize::from(read_u16_le_at(raw, 2, "attribute name size")?);
165 if name_size == 0 {
166 return Err(Error::InvalidFormat(
167 "attribute message name length is zero".into(),
168 ));
169 }
170 let offset: usize = match version {
171 1 | 2 => 8,
172 3 => 9,
173 other => Err(Error::InvalidFormat(format!(
174 "attribute message version {other}"
175 )))?,
176 };
177 let end = offset
178 .checked_add(name_size)
179 .ok_or_else(|| Error::InvalidFormat("attribute name field overflow".into()))?;
180 if end > raw.len() {
181 return Err(Error::InvalidFormat(
182 "attribute name field exceeds message".into(),
183 ));
184 }
185 Ok((offset, name_size))
186 }
187
188 fn find_compact_attribute_message_in_oh(
189 &self,
190 oh_addr: u64,
191 name: &str,
192 ) -> Result<CompactAttributeMessageLocation> {
193 self.find_compact_attribute_in_oh(oh_addr, name, None)
194 }
195
196 fn find_compact_attribute_rename_location(
197 &self,
198 oh_addr: u64,
199 old_name: &str,
200 new_name: &str,
201 ) -> Result<CompactAttributeMessageLocation> {
202 self.find_compact_attribute_in_oh(oh_addr, old_name, Some(new_name))
203 }
204
205 fn find_compact_attribute_in_oh(
206 &self,
207 oh_addr: u64,
208 target_name: &str,
209 reject_duplicate_name: Option<&str>,
210 ) -> Result<CompactAttributeMessageLocation> {
211 let mut guard = self.inner.lock();
212 let reader = &mut guard.reader;
213 reader.seek(oh_addr)?;
214
215 let mut first_bytes = [0u8; 4];
216 reader.read_bytes_into(&mut first_bytes)?;
217 if first_bytes != [b'O', b'H', b'D', b'R'] {
218 return Err(Error::Unsupported(
219 "attribute mutation currently supports only v2 object headers".into(),
220 ));
221 }
222
223 let version = reader.read_u8()?;
224 if version != 2 {
225 return Err(Error::Unsupported(
226 "attribute mutation currently supports only v2 object headers".into(),
227 ));
228 }
229
230 let flags = reader.read_u8()?;
231 if flags & !HDR_V2_KNOWN_FLAGS != 0 {
232 return Err(Error::InvalidFormat(format!(
233 "object header v2 flags contain reserved bits: {flags:#04x}"
234 )));
235 }
236 if flags & HDR_STORE_TIMES != 0 {
237 reader.skip(16)?;
238 }
239 if flags & HDR_ATTR_STORE_PHASE_CHANGE != 0 {
240 reader.skip(4)?;
241 }
242
243 let chunk0_size_bytes = 1u8 << (flags & HDR_CHUNK0_SIZE_MASK);
244 let chunk0_data_size = reader.read_uint(chunk0_size_bytes)?;
245 let chunk0_data_start = reader.position()?;
246 let chunk0_data_end = chunk0_data_start
247 .checked_add(chunk0_data_size)
248 .ok_or_else(|| Error::InvalidFormat("object-header chunk range overflow".into()))?;
249 let oh_check_len = usize::try_from(chunk0_data_end - oh_addr)
250 .map_err(|_| Error::InvalidFormat("object-header checksum range overflow".into()))?;
251
252 let mut has_dense_attrs = false;
253 let mut found = None;
254 while reader.position()? < chunk0_data_end {
255 let msg_header_pos = reader.position()?;
256 if msg_header_pos
257 .checked_add(4)
258 .is_none_or(|end| end > chunk0_data_end)
259 {
260 break;
261 }
262
263 let msg_type = u16::from(reader.read_u8()?);
264 let msg_size = usize::from(reader.read_u16()?);
265 let _msg_flags = reader.read_u8()?;
266 if flags & object_header::HDR_ATTR_CRT_ORDER_TRACKED != 0 {
267 reader.skip(2)?;
268 }
269
270 let msg_data_offset = reader.position()?;
271 let msg_size_u64 = Self::usize_to_u64(msg_size, "object-header message size")?;
272 if msg_data_offset
273 .checked_add(msg_size_u64)
274 .is_none_or(|end| end > chunk0_data_end)
275 {
276 return Err(Error::InvalidFormat(
277 "object-header message payload exceeds chunk".into(),
278 ));
279 }
280
281 if msg_type == object_header::MSG_ATTRIBUTE {
282 let mut data = vec![0u8; msg_size];
283 reader.read_bytes_into(&mut data)?;
284 let attr = AttributeMessage::decode(&data)?;
285 if reject_duplicate_name.is_some_and(|name| attr.name == name) {
286 return Err(Error::InvalidFormat(format!(
287 "attribute '{}' already exists",
288 attr.name
289 )));
290 }
291 if attr.name == target_name {
292 let location = CompactAttributeMessageLocation {
293 msg_type_offset: msg_header_pos,
294 msg_data_offset,
295 oh_start: oh_addr,
296 oh_check_len,
297 raw_data: data,
298 };
299 if reject_duplicate_name.is_none() {
300 return Ok(location);
301 }
302 found = Some(location);
303 }
304 } else {
305 if msg_type == object_header::MSG_ATTR_INFO {
306 has_dense_attrs = true;
307 }
308 reader.skip(msg_size_u64)?;
309 }
310 }
311
312 if let Some(location) = found {
313 return Ok(location);
314 }
315
316 if has_dense_attrs {
317 Err(Error::Unsupported(
318 "mutating dense attributes is not implemented".into(),
319 ))
320 } else {
321 Err(Error::InvalidFormat(format!(
322 "attribute '{target_name}' not found"
323 )))
324 }
325 }
326
327 fn find_dense_attribute_location(
328 &self,
329 object_addr: u64,
330 target_name: &str,
331 reject_duplicate_name: Option<&str>,
332 ) -> Result<DenseAttributeLocation> {
333 let mut guard = self.inner.lock();
334 let attr_info =
335 Self::read_dense_attribute_info_message(&mut guard.reader, object_addr, target_name)?;
336 if !attr_info.has_dense_storage() {
337 return Err(Error::InvalidFormat(format!(
338 "attribute '{target_name}' not found"
339 )));
340 }
341 if attr_info.corder_btree_addr.is_some() {
342 return Err(Error::Unsupported(
343 "mutating creation-order indexed dense attributes is not implemented".into(),
344 ));
345 }
346
347 let heap = FractalHeapHeader::read_at(&mut guard.reader, attr_info.fractal_heap_addr)?;
348 if heap.io_filter_len != 0 {
349 return Err(Error::Unsupported(
350 "mutating filtered dense attribute heaps is not implemented".into(),
351 ));
352 }
353
354 let btree = BTreeV2Header::read_at(&mut guard.reader, attr_info.name_btree_addr)?;
355 if btree.tree_type != 8 || btree.depth != 0 {
356 return Err(Error::Unsupported(
357 "mutating non-leaf dense attribute name indexes is not implemented".into(),
358 ));
359 }
360 let mut leaf_records = Vec::new();
361 Self::read_dense_attribute_leaf_records_into(&mut guard.reader, &btree, &mut leaf_records)?;
362 let heap_id_len = usize::from(heap.heap_id_len);
363 let mut found = None;
364 let record_size = usize::from(btree.record_size);
365 for (idx, record) in leaf_records.chunks_exact(record_size).enumerate() {
366 let heap_id = checked_window(record, 0, heap_id_len, "dense attribute heap ID")?;
367 let raw_data = heap.read_managed_object(&mut guard.reader, heap_id)?;
368 let attr = AttributeMessage::decode(&raw_data)?;
369 if reject_duplicate_name.is_some_and(|name| attr.name == name) {
370 return Err(Error::InvalidFormat(format!(
371 "attribute '{}' already exists",
372 attr.name
373 )));
374 }
375 if attr.name == target_name {
376 let object_location = heap.managed_object_location(&mut guard.reader, heap_id)?;
377 found = Some((idx, object_location, raw_data));
378 }
379 }
380
381 let (record_index, object_location, raw_data) = found
382 .ok_or_else(|| Error::InvalidFormat(format!("attribute '{target_name}' not found")))?;
383 Ok(DenseAttributeLocation {
384 attr_info_addr: attr_info.name_btree_addr,
385 heap,
386 btree,
387 leaf_records,
388 record_index,
389 direct_block_addr: object_location.block_addr,
390 direct_block_size: object_location.block_size,
391 object_offset: object_location.object_offset,
392 raw_data,
393 })
394 }
395
396 fn delete_dense_attribute(&mut self, mut location: DenseAttributeLocation) -> Result<()> {
397 let record_size = usize::from(location.btree.record_size);
398 let start = location
399 .record_index
400 .checked_mul(record_size)
401 .ok_or_else(|| Error::InvalidFormat("dense attribute record offset overflow".into()))?;
402 let end = start
403 .checked_add(record_size)
404 .ok_or_else(|| Error::InvalidFormat("dense attribute record offset overflow".into()))?;
405 if end > location.leaf_records.len() {
406 return Err(Error::InvalidFormat(
407 "dense attribute record index is invalid".into(),
408 ));
409 }
410 location.leaf_records.copy_within(end.., start);
411 location
412 .leaf_records
413 .truncate(location.leaf_records.len() - record_size);
414 self.rewrite_dense_attribute_name_index(&location)
415 }
416
417 fn rename_dense_attribute(
418 &mut self,
419 mut location: DenseAttributeLocation,
420 new_name: &str,
421 ) -> Result<()> {
422 let (name_offset, name_size) = Self::compact_attribute_name_field(&location.raw_data)?;
423 if new_name.len() + 1 != name_size {
424 return Err(Error::Unsupported(
425 "in-place dense attribute rename cannot grow or shrink the encoded name field"
426 .into(),
427 ));
428 }
429
430 let name_offset_u64 = Self::usize_to_u64(name_offset, "dense attribute name offset")?;
431 let file_name_offset = location
432 .direct_block_addr
433 .checked_add(location.object_offset)
434 .and_then(|offset| offset.checked_add(name_offset_u64))
435 .ok_or_else(|| Error::InvalidFormat("dense attribute name offset overflow".into()))?;
436 self.write_handle.seek(SeekFrom::Start(file_name_offset))?;
437 {
438 let encoded_name = encode_attribute_name_in_place(
439 &mut location.raw_data,
440 name_offset,
441 name_size,
442 new_name,
443 )?;
444 self.write_handle.write_all(encoded_name)?;
445 }
446
447 let record_size = usize::from(location.btree.record_size);
448 let record_start = location
449 .record_index
450 .checked_mul(record_size)
451 .ok_or_else(|| Error::InvalidFormat("dense attribute record offset overflow".into()))?;
452 let record_end = record_start
453 .checked_add(record_size)
454 .ok_or_else(|| Error::InvalidFormat("dense attribute record offset overflow".into()))?;
455 let record = location
456 .leaf_records
457 .get_mut(record_start..record_end)
458 .ok_or_else(|| {
459 Error::InvalidFormat("dense attribute record index is invalid".into())
460 })?;
461 let hash_pos = dense_attribute_record_hash_pos(&location.heap, record)?;
462 let hash_end = hash_pos
463 .checked_add(4)
464 .ok_or_else(|| Error::InvalidFormat("dense attribute hash offset overflow".into()))?;
465 record[hash_pos..hash_end].copy_from_slice(&dense_name_hash(new_name).to_le_bytes());
466 Self::reposition_dense_attribute_record_by_hash(
467 &location.heap,
468 &mut location.leaf_records,
469 location.record_index,
470 record_size,
471 )?;
472
473 let encoded_name = checked_window(
474 &location.raw_data,
475 name_offset,
476 name_size,
477 "dense attribute encoded name",
478 )?;
479 self.rewrite_dense_attribute_direct_block_checksum(
480 &location.heap,
481 location.direct_block_addr,
482 location.direct_block_size,
483 file_name_offset,
484 encoded_name,
485 )?;
486 self.rewrite_dense_attribute_name_index(&location)
487 }
488
489 fn rewrite_dense_attribute_name_index(
490 &mut self,
491 location: &DenseAttributeLocation,
492 ) -> Result<()> {
493 let record_size = usize::from(location.btree.record_size);
494 if record_size == 0 || !location.leaf_records.len().is_multiple_of(record_size) {
495 return Err(Error::InvalidFormat(
496 "dense attribute records have inconsistent sizes".into(),
497 ));
498 }
499 let record_count = location.leaf_records.len() / record_size;
500 let record_count_u16 = Self::usize_to_u16(record_count, "dense attribute record count")?;
501 let record_count_u64 = Self::usize_to_u64(record_count, "dense attribute record count")?;
502
503 let mut leaf = Vec::with_capacity(
504 6usize
505 .checked_add(location.leaf_records.len())
506 .and_then(|len| len.checked_add(4))
507 .ok_or_else(|| Error::InvalidFormat("dense attribute leaf size overflow".into()))?,
508 );
509 leaf.extend_from_slice(b"BTLF");
510 leaf.push(0);
511 leaf.push(location.btree.tree_type);
512 leaf.extend_from_slice(&location.leaf_records);
513 let checksum = checksum_metadata(&leaf);
514 leaf.extend_from_slice(&checksum.to_le_bytes());
515 self.write_handle
516 .seek(SeekFrom::Start(location.btree.root_addr))?;
517 self.write_handle.write_all(&leaf)?;
518
519 let sa = usize::from(self.superblock.sizeof_addr);
520 let ss = usize::from(self.superblock.sizeof_size);
521 let header_capacity = 22usize
522 .checked_add(sa)
523 .and_then(|len| len.checked_add(ss))
524 .ok_or_else(|| {
525 Error::InvalidFormat("dense attribute B-tree header size overflow".into())
526 })?;
527 let mut header = Vec::with_capacity(header_capacity);
528 header.extend_from_slice(b"BTHD");
529 header.push(0);
530 header.push(location.btree.tree_type);
531 header.extend_from_slice(&location.btree.node_size.to_le_bytes());
532 header.extend_from_slice(&location.btree.record_size.to_le_bytes());
533 header.extend_from_slice(&0u16.to_le_bytes());
534 header.push(location.btree.split_pct);
535 header.push(location.btree.merge_pct);
536 let mut scratch = [0u8; 8];
537 header.extend_from_slice(Self::encode_uint_le_into(
538 location.btree.root_addr,
539 &mut scratch,
540 sa,
541 "dense attribute B-tree root address",
542 )?);
543 header.extend_from_slice(&record_count_u16.to_le_bytes());
544 header.extend_from_slice(Self::encode_uint_le_into(
545 record_count_u64,
546 &mut scratch,
547 ss,
548 "dense attribute B-tree total record count",
549 )?);
550 let checksum = checksum_metadata(&header);
551 header.extend_from_slice(&checksum.to_le_bytes());
552 self.write_handle
553 .seek(SeekFrom::Start(location.attr_info_addr))?;
554 self.write_handle.write_all(&header)?;
555 Ok(())
556 }
557
558 fn read_dense_attribute_info_message<R: std::io::Read + Seek>(
559 reader: &mut crate::io::reader::HdfReader<R>,
560 oh_addr: u64,
561 target_name: &str,
562 ) -> Result<AttributeInfoMessage> {
563 reader.seek(oh_addr)?;
564
565 let mut first_bytes = [0u8; 4];
566 reader.read_bytes_into(&mut first_bytes)?;
567 if first_bytes != [b'O', b'H', b'D', b'R'] {
568 return Err(Error::Unsupported(
569 "attribute mutation currently supports only v2 object headers".into(),
570 ));
571 }
572
573 let version = reader.read_u8()?;
574 if version != 2 {
575 return Err(Error::Unsupported(
576 "attribute mutation currently supports only v2 object headers".into(),
577 ));
578 }
579
580 let flags = reader.read_u8()?;
581 if flags & !HDR_V2_KNOWN_FLAGS != 0 {
582 return Err(Error::InvalidFormat(format!(
583 "object header v2 flags contain reserved bits: {flags:#04x}"
584 )));
585 }
586 if flags & HDR_STORE_TIMES != 0 {
587 reader.skip(16)?;
588 }
589 if flags & HDR_ATTR_STORE_PHASE_CHANGE != 0 {
590 reader.skip(4)?;
591 }
592
593 let chunk0_size_bytes = 1u8 << (flags & HDR_CHUNK0_SIZE_MASK);
594 let chunk0_data_size = reader.read_uint(chunk0_size_bytes)?;
595 let chunk0_data_start = reader.position()?;
596 let chunk0_data_end = chunk0_data_start
597 .checked_add(chunk0_data_size)
598 .ok_or_else(|| Error::InvalidFormat("object-header chunk range overflow".into()))?;
599
600 while reader.position()? < chunk0_data_end {
601 let msg_header_pos = reader.position()?;
602 if msg_header_pos
603 .checked_add(4)
604 .is_none_or(|end| end > chunk0_data_end)
605 {
606 break;
607 }
608
609 let msg_type = u16::from(reader.read_u8()?);
610 let msg_size = usize::from(reader.read_u16()?);
611 let _msg_flags = reader.read_u8()?;
612 if flags & object_header::HDR_ATTR_CRT_ORDER_TRACKED != 0 {
613 reader.skip(2)?;
614 }
615
616 let msg_data_offset = reader.position()?;
617 let msg_size_u64 = Self::usize_to_u64(msg_size, "object-header message size")?;
618 if msg_data_offset
619 .checked_add(msg_size_u64)
620 .is_none_or(|end| end > chunk0_data_end)
621 {
622 return Err(Error::InvalidFormat(
623 "object-header message payload exceeds chunk".into(),
624 ));
625 }
626
627 if msg_type == object_header::MSG_ATTR_INFO {
628 let mut data = vec![0u8; msg_size];
629 reader.read_bytes_into(&mut data)?;
630 return AttributeInfoMessage::decode(&data, reader.sizeof_addr());
631 }
632 reader.skip(msg_size_u64)?;
633 }
634
635 Err(Error::InvalidFormat(format!(
636 "attribute '{target_name}' not found"
637 )))
638 }
639
640 fn read_dense_attribute_leaf_records_into<R: std::io::Read + Seek>(
641 reader: &mut crate::io::reader::HdfReader<R>,
642 btree: &BTreeV2Header,
643 records: &mut Vec<u8>,
644 ) -> Result<()> {
645 let record_size = usize::from(btree.record_size);
646 let record_count = usize::from(btree.root_nrecords);
647 let records_len = record_size
648 .checked_mul(record_count)
649 .ok_or_else(|| Error::InvalidFormat("dense attribute leaf records overflow".into()))?;
650 let check_len = 6usize
651 .checked_add(records_len)
652 .ok_or_else(|| Error::InvalidFormat("dense attribute leaf size overflow".into()))?;
653 let total_len = check_len
654 .checked_add(4)
655 .ok_or_else(|| Error::InvalidFormat("dense attribute leaf size overflow".into()))?;
656
657 records.clear();
658 records.resize(total_len, 0);
659 reader.seek(btree.root_addr)?;
660 reader.read_bytes_into(records)?;
661 if records.get(..4) != Some(&b"BTLF"[..]) {
662 return Err(Error::InvalidFormat(
663 "invalid dense attribute B-tree leaf magic".into(),
664 ));
665 }
666 if records.get(4).copied() != Some(0) || records.get(5).copied() != Some(btree.tree_type) {
667 return Err(Error::InvalidFormat(
668 "dense attribute B-tree leaf header does not match index".into(),
669 ));
670 }
671 let stored = read_u32_le_at(records, check_len, "dense attribute leaf checksum")?;
672 let computed = checksum_metadata(&records[..check_len]);
673 if stored != computed {
674 return Err(Error::InvalidFormat(format!(
675 "dense attribute leaf checksum mismatch: stored={stored:#010x}, computed={computed:#010x}"
676 )));
677 }
678 records.drain(..6);
679 records.truncate(records_len);
680 Ok(())
681 }
682
683 fn reposition_dense_attribute_record_by_hash(
684 heap: &FractalHeapHeader,
685 records: &mut [u8],
686 record_index: usize,
687 record_size: usize,
688 ) -> Result<()> {
689 if record_size == 0 || !records.len().is_multiple_of(record_size) {
690 return Err(Error::InvalidFormat(
691 "dense attribute records have inconsistent sizes".into(),
692 ));
693 }
694 let record_count = records.len() / record_size;
695 if record_index >= record_count {
696 return Err(Error::InvalidFormat(
697 "dense attribute record index is invalid".into(),
698 ));
699 }
700 let start = record_index
701 .checked_mul(record_size)
702 .ok_or_else(|| Error::InvalidFormat("dense attribute record offset overflow".into()))?;
703 let end = start
704 .checked_add(record_size)
705 .ok_or_else(|| Error::InvalidFormat("dense attribute record offset overflow".into()))?;
706 let record = records.get(start..end).ok_or_else(|| {
707 Error::InvalidFormat("dense attribute record index is invalid".into())
708 })?;
709 let hash_pos = dense_attribute_record_hash_pos(heap, record)?;
710 let changed_hash = read_u32_le_at(record, hash_pos, "dense attribute record hash")?;
711
712 let mut insert_index = record_count - 1;
713 let mut logical_idx = 0usize;
714 for (idx, record) in records.chunks_exact(record_size).enumerate() {
715 if idx == record_index {
716 continue;
717 }
718 let hash_pos = dense_attribute_record_hash_pos(heap, record)?;
719 let hash = read_u32_le_at(record, hash_pos, "dense attribute record hash")?;
720 if changed_hash < hash {
721 insert_index = logical_idx;
722 break;
723 }
724 logical_idx += 1;
725 }
726
727 if insert_index == record_index {
728 return Ok(());
729 }
730 let insert_pos = insert_index
731 .checked_mul(record_size)
732 .ok_or_else(|| Error::InvalidFormat("dense attribute record offset overflow".into()))?;
733 if insert_index < record_index {
734 records[insert_pos..end].rotate_right(record_size);
735 } else {
736 let rotate_end = insert_pos.checked_add(record_size).ok_or_else(|| {
737 Error::InvalidFormat("dense attribute record offset overflow".into())
738 })?;
739 records[start..rotate_end].rotate_left(record_size);
740 }
741 Ok(())
742 }
743
744 fn rewrite_dense_attribute_direct_block_checksum(
745 &mut self,
746 heap: &FractalHeapHeader,
747 block_addr: u64,
748 block_size: u64,
749 patched_addr: u64,
750 patched_data: &[u8],
751 ) -> Result<()> {
752 if !heap.has_checksum {
753 return Ok(());
754 }
755 let block_size = usize::try_from(block_size)
756 .map_err(|_| Error::InvalidFormat("dense attribute direct block too large".into()))?;
757 let checksum_pos = direct_block_checksum_pos(heap, self.superblock.sizeof_addr)?;
758 let checksum_end = checksum_pos
759 .checked_add(4)
760 .ok_or_else(|| Error::InvalidFormat("direct block checksum offset overflow".into()))?;
761 let mut guard = self.inner.lock();
762 guard.reader.seek(block_addr)?;
763 let mut block = vec![0u8; block_size];
764 guard.reader.read_bytes_into(&mut block)?;
765 drop(guard);
766 let patch_start = patched_addr
767 .checked_sub(block_addr)
768 .ok_or_else(|| Error::InvalidFormat("direct block patch address underflow".into()))?;
769 let patch_start = usize::try_from(patch_start)
770 .map_err(|_| Error::InvalidFormat("direct block patch offset too large".into()))?;
771 let patch_end = patch_start
772 .checked_add(patched_data.len())
773 .ok_or_else(|| Error::InvalidFormat("direct block patch range overflow".into()))?;
774 block
775 .get_mut(patch_start..patch_end)
776 .ok_or_else(|| Error::InvalidFormat("direct block patch exceeds block".into()))?
777 .copy_from_slice(patched_data);
778 let checksum_window = block.get_mut(checksum_pos..checksum_end).ok_or_else(|| {
779 Error::InvalidFormat("direct block checksum field is truncated".into())
780 })?;
781 checksum_window.fill(0);
782 let checksum = checksum_metadata(&block);
783 let checksum_pos_u64 = Self::usize_to_u64(checksum_pos, "direct block checksum position")?;
784 self.write_handle.seek(SeekFrom::Start(
785 block_addr.checked_add(checksum_pos_u64).ok_or_else(|| {
786 Error::InvalidFormat("direct block checksum address overflow".into())
787 })?,
788 ))?;
789 self.write_handle.write_all(&checksum.to_le_bytes())?;
790 Ok(())
791 }
792}
793
794fn checked_window<'a>(raw: &'a [u8], pos: usize, len: usize, context: &str) -> Result<&'a [u8]> {
795 let end = pos
796 .checked_add(len)
797 .ok_or_else(|| Error::InvalidFormat(format!("{context} offset overflow")))?;
798 raw.get(pos..end)
799 .ok_or_else(|| Error::InvalidFormat(format!("{context} is truncated")))
800}
801
802fn read_u16_le_at(raw: &[u8], pos: usize, context: &str) -> Result<u16> {
803 let bytes = checked_window(raw, pos, 2, context)?;
804 let bytes: [u8; 2] = bytes
805 .try_into()
806 .map_err(|_| Error::InvalidFormat(format!("{context} is truncated")))?;
807 Ok(u16::from_le_bytes(bytes))
808}
809
810fn read_u32_le_at(raw: &[u8], pos: usize, context: &str) -> Result<u32> {
811 let bytes = checked_window(raw, pos, 4, context)?;
812 let bytes: [u8; 4] = bytes
813 .try_into()
814 .map_err(|_| Error::InvalidFormat(format!("{context} is truncated")))?;
815 Ok(u32::from_le_bytes(bytes))
816}
817
818fn encode_attribute_name_in_place<'a>(
819 raw: &'a mut [u8],
820 name_offset: usize,
821 name_size: usize,
822 name: &str,
823) -> Result<&'a [u8]> {
824 let name_field = raw
825 .get_mut(
826 name_offset..name_offset.checked_add(name_size).ok_or_else(|| {
827 Error::InvalidFormat("attribute name field offset overflow".into())
828 })?,
829 )
830 .ok_or_else(|| Error::InvalidFormat("attribute name field exceeds message".into()))?;
831 name_field.fill(0);
832 name_field[..name.len()].copy_from_slice(name.as_bytes());
833 Ok(name_field)
834}
835
836fn direct_block_checksum_pos(heap: &FractalHeapHeader, sizeof_addr: u8) -> Result<usize> {
837 let offset_bytes = fractal_heap_offset_width(heap)?;
838 5usize
839 .checked_add(usize::from(sizeof_addr))
840 .and_then(|pos| pos.checked_add(offset_bytes))
841 .ok_or_else(|| Error::InvalidFormat("direct block checksum position overflow".into()))
842}
843
844fn fractal_heap_offset_width(heap: &FractalHeapHeader) -> Result<usize> {
845 let max_heap_size = usize::from(heap.max_heap_size);
846 let offset_bytes = max_heap_size
847 .checked_add(7)
848 .ok_or_else(|| Error::InvalidFormat("dense attribute heap offset width overflow".into()))?
849 / 8;
850 if offset_bytes == 0 || offset_bytes > 8 {
851 return Err(Error::Unsupported(format!(
852 "dense attribute heap offset width {offset_bytes} is unsupported"
853 )));
854 }
855 Ok(offset_bytes)
856}
857
858fn dense_attribute_record_hash_pos(heap: &FractalHeapHeader, record: &[u8]) -> Result<usize> {
859 let heap_id_len = usize::from(heap.heap_id_len);
860 let hash_pos = heap_id_len
861 .checked_add(1)
862 .and_then(|pos| pos.checked_add(4))
863 .ok_or_else(|| Error::InvalidFormat("dense attribute hash position overflow".into()))?;
864 checked_window(record, hash_pos, 4, "dense attribute record hash")?;
865 Ok(hash_pos)
866}
867
868fn dense_name_hash(name: &str) -> u32 {
869 crate::format::checksum::checksum_lookup3(name.as_bytes(), 0)
870}
871
872#[cfg(test)]
873mod tests {
874 use super::*;
875
876 #[test]
877 fn checked_window_rejects_offset_overflow() {
878 let err = checked_window(&[], usize::MAX, 1, "attribute mutation test").unwrap_err();
879 assert!(
880 err.to_string()
881 .contains("attribute mutation test offset overflow"),
882 "unexpected error: {err}"
883 );
884 }
885
886 #[test]
887 fn fractal_heap_offset_width_rejects_invalid_widths() {
888 let heap = FractalHeapHeader {
889 heap_addr: 0,
890 heap_id_len: 0,
891 io_filter_len: 0,
892 flags: 0,
893 max_managed_obj_size: 0,
894 table_width: 0,
895 start_block_size: 0,
896 max_direct_block_size: 0,
897 max_heap_size: 0,
898 start_root_rows: 0,
899 root_block_addr: 0,
900 current_root_rows: 0,
901 num_managed_objects: 0,
902 has_checksum: false,
903 sizeof_addr: 8,
904 sizeof_size: 8,
905 huge_btree_addr: 0,
906 root_direct_filtered_size: None,
907 root_direct_filter_mask: 0,
908 filter_pipeline: None,
909 };
910 assert!(fractal_heap_offset_width(&heap).is_err());
911
912 let mut heap = heap;
913 heap.max_heap_size = 65;
914 assert!(fractal_heap_offset_width(&heap).is_err());
915
916 heap.max_heap_size = 64;
917 assert_eq!(fractal_heap_offset_width(&heap).unwrap(), 8);
918 }
919}