1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
//! The superblock base address, as a type distinct from the addresses it shifts.
//!
//! # Why this exists
//!
//! The format specification says of the superblock's base address that "unless
//! otherwise noted, all other file addresses are relative to this base address".
//! A userblock makes that base non-zero — 512 bytes for every `.mat` file this
//! crate writes — so an HDF5 file holds addresses in *two* frames at once:
//!
//! * a **stored** address, as written in metadata, relative to the base;
//! * an **absolute** byte position in the file, which is what a
//! [`Source`](crate::source::Source) reads at.
//!
//! Both are `u64`, both are usually equal (a plain file has a base of zero), and
//! the compiler has nothing to say about mixing them. The result is a defect
//! class rather than a defect: a reader that forgets the base lands inside the
//! userblock, and every test written against a base-0 file passes anyway.
//! `tests/userblock_base_address_crosscheck.rs` was written after that happened
//! three separate times — dense attributes, object header continuations, and
//! dense link storage — and it covers the ground its four rows name, which is
//! not the same as covering the ground.
//!
//! [`BaseAddress`] does not make the two frames distinct types; addresses stay
//! `u64` throughout the crate. What it makes distinct is the **base itself**,
//! which is the operand every one of those defects got wrong. That buys three
//! things a bare `u64` did not give:
//!
//! * A base cannot be passed where an address is expected, or an address where a
//! base is expected. Both directions were previously a silent argument swap in
//! functions that take `(address, ..., base_address)` — the shape of nearly
//! every parser in this crate.
//! * Both conversions are **checked, and named**: 46 call sites of
//! [`BaseAddress::absolute`] and 18 of [`BaseAddress::relative`], plus their
//! test fixtures. Most replaced a hand-written `checked_add(base)` chain, but
//! **seven unchecked additions and eighteen unchecked subtractions** were in
//! that set, each of which panics in a debug build and wraps in a release one.
//! Two were the `stored_addr + base` in [`group_v2`](crate::group_v2)'s two
//! path resolvers, now the single [`ChildLookup::of`](crate::group_v2::ChildLookup)
//! that both share; `a_link_target_that_overflows_the_base_address_is_refused`
//! covers it. The rest are in the write engine, where an absolute address goes
//! back to stored form on the way into a link message or a superblock. Three
//! additions of a base survive unconverted, all inside `debug_assert_eq!` in
//! [`file_writer`](crate::file_writer), whose bodies a release build does not
//! compile.
//! * "This code does not need the base" becomes [`BaseAddress::ZERO`]. Five call
//! sites in the crate's code paths make that claim, so a `grep` finds all five
//! — where a literal `0` argument was indistinguishable from the other zeroes
//! on the line. (Another 34 are test fixtures satisfying a signature with a
//! base they do not care about.)
//!
//! # What it deliberately does not do
//!
//! The read path resolves the two frames two different ways, and this type is
//! neutral between them. Object headers and group entries add the base to each
//! address as they parse it ([`absolute`](BaseAddress::absolute)); raw data,
//! chunk indices, and dense attribute storage instead *frame the file* at the
//! base — [`frame`](crate::source::frame) for a buffer,
//! [`BaseOffsetSource`](crate::source::BaseOffsetSource) for a stream — and read
//! stored addresses against that shifted view directly. Both are correct, and
//! which one a given parser is owed is a fact about the parser, not about the
//! base. A newtype for stored-versus-absolute *addresses* would encode that too;
//! it would also have to propagate through every chunk address, free-space
//! section, and index element in the crate, which is a far larger change than
//! this one and is not attempted here.
use crateFormatError;
/// The superblock's base address: the byte offset at which the HDF5 image
/// begins within the file.
///
/// Zero for a plain file, and the userblock size for a file that has one. See
/// the [module documentation](self) for why this is a type rather than a `u64`.
///
/// `pub` rather than `pub(crate)` only because
/// [`Superblock`](crate::superblock::Superblock) names it in a `pub` field and
/// that struct lives in a `pub(crate)` module. It is not re-exported from
/// `lib.rs`, so it is no part of the crate's public API; its members stay
/// `pub(crate)`.
;