///|
/// The object reached by a non-null HDF5 object reference.
///
/// `path` is the first hard-link path found by a bounded hierarchy walk. It is
/// `None` when the target is valid but has no named hard link.
#derive(Eq, @debug.Debug)
pub(all) struct Hdf5ReferenceTarget {
address : UInt64
kind : Hdf5ObjectKind
path : String?
}
///|
pub extend Hdf5ReferenceTarget with Eq::{not_equal, equal}
///|
pub extend Hdf5ReferenceTarget with @debug.Debug::{to_repr}
///|
fn reference_is_null(reference : Bytes) -> Bool {
let mut index = 0
while index < reference.length() {
if reference.unsafe_get(index) != b'\x00' {
return false
}
index = index + 1
}
true
}
///|
fn Hdf5File::find_reference_path_in_group(
self : Hdf5File,
path : String,
address : UInt64,
target : UInt64,
visited : Array[UInt64],
depth : Int32,
) -> String? raise Hdf5Error {
guard depth < 128 else { raise InvalidField("group hierarchy is too deep") }
if visited.contains(address) {
return None
}
visited.push(address)
let header = self.object_header_at(address)
for link in self.links(header) {
match link.link_target {
Some(_) => ()
None => {
let child_path = join_path(path, link.name)
if link.object_header_address == target {
return Some(child_path)
}
let child_kind = object_kind(
self.object_header_at(link.object_header_address),
) catch {
_ => Unknown
}
if child_kind == Group {
let found = self.find_reference_path_in_group(
child_path,
link.object_header_address,
target,
visited,
depth + 1,
)
if found is Some(found_path) {
return Some(found_path)
}
}
}
}
}
None
}
///|
fn Hdf5File::find_reference_path(
self : Hdf5File,
target : UInt64,
) -> String? raise Hdf5Error {
if target == self.root_address() {
return Some("/")
}
let visited : Array[UInt64] = []
self.find_reference_path_in_group(
"/",
self.root_address(),
target,
visited,
0,
)
}
///|
/// Resolves a legacy HDF5 object reference to its target object.
///
/// The all-zero null reference returns `None`. Region references must be
/// resolved with `resolve_region_reference`; malformed references and dangling
/// addresses raise an error. References are local to this file; use
/// `Hdf5Resolver` to obtain a file that contains a remotely linked dataset
/// before resolving its stored references.
pub fn Hdf5File::resolve_object_reference(
self : Hdf5File,
reference : Bytes,
) -> Hdf5ReferenceTarget? raise Hdf5Error {
let offset_size = self.superblock.offset_size
guard reference.length() == offset_size else {
if reference.length() == offset_size + 4 {
raise InvalidArgument(
"region references require resolve_region_reference",
)
}
raise InvalidField(
"object reference must be " +
offset_size.to_string() +
" bytes, found " +
reference.length().to_string(),
)
}
if reference_is_null(reference) {
return None
}
let reader = Reader::new(reference)
let address = reader.read_uint_le(offset_size)
let header = self.object_header_at(address) catch {
error =>
raise InvalidField(
"object reference target is not a valid object header: " +
error.message(),
)
}
let kind = object_kind(header)
let path = self.find_reference_path(address)
Some({ address, kind, path, })
}