Skip to content

Commit 6e5626f

Browse files
committed
refactor(unit): parse UnitDescriptor / UnitBindingAuth / UBR via schema
Three of the four parsers in unit.cpp migrate to parse_schema: - UnitDescriptor (7 top-level fields) plus a hand-written PayloadIdentity sub-map lookup that preserves the context-specific malformed-error routing. - UnitBindingAuth (4 fields) plus a literal kind-string check post- processing. - UnitBindingRecord (8 top-level fields) plus PayloadIdentity sub-map, UnitBindingAuth, and record_hash domain-hash verify as post-processing. parse_payload_identity stays imperative because the caller passes the correct malformed-error code per context (UnitDescriptorMalformed vs UnitBindingRecordMalformed), a distinction the shared schema traits can't reproduce without adding a per-call context knob.
1 parent 6d07444 commit 6e5626f

1 file changed

Lines changed: 89 additions & 109 deletions

File tree

runtime/src/binding/unit.cpp

Lines changed: 89 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include "VMPilot_crypto.hpp"
77
#include "binding/inner_partition.hpp"
88
#include "binding/resolved_profile.hpp"
9+
#include "cbor/schema.hpp"
910
#include "cbor/strict.hpp"
1011
#include "eh_guard.hpp"
1112
#include "vm/domain_labels.hpp"
@@ -15,9 +16,16 @@ namespace VMPilot::Cbor {
1516
template <>
1617
struct CborConsumerTraits<VMPilot::Runtime::Binding::UnitAcceptError> {
1718
using E = VMPilot::Runtime::Binding::UnitAcceptError;
18-
static constexpr E missing_field = E::MissingCoreField;
19+
static constexpr E missing_field = E::MissingCoreField;
1920
static constexpr E wrong_field_type = E::WrongFieldType;
20-
static constexpr E wrong_hash_size = E::WrongHashSize;
21+
static constexpr E wrong_hash_size = E::WrongHashSize;
22+
// Schema-side defaults. parse_payload_identity is still imperative
23+
// because its caller needs to route a not-a-map failure to
24+
// UnitDescriptorMalformed vs UnitBindingRecordMalformed by
25+
// context; the constants below are the generic fall-backs
26+
// used only by the top-level schema calls.
27+
static constexpr E bad_cbor = E::UnitDescriptorMalformed;
28+
static constexpr E not_a_map = E::UnitDescriptorMalformed;
2129
};
2230
} // namespace VMPilot::Cbor
2331

@@ -73,11 +81,6 @@ inline tl::unexpected<UnitAcceptError> err(UnitAcceptError e) noexcept {
7381
return tl::make_unexpected(e);
7482
}
7583

76-
template <typename E>
77-
tl::unexpected<UnitAcceptError> err_as(UnitAcceptError e, const E&) noexcept {
78-
return tl::make_unexpected(e);
79-
}
80-
8184
bool hash_equals(const std::array<std::uint8_t, 32>& a,
8285
const std::array<std::uint8_t, 32>& b) noexcept {
8386
return std::memcmp(a.data(), b.data(), 32) == 0;
@@ -115,9 +118,6 @@ UnitAcceptError map_eh_contract_error(
115118
// {MissingCoreField, WrongFieldType, WrongHashSize} triple, so the
116119
// per-call error customisation point was dead weight.
117120

118-
inline auto require_text(const Value& m, std::uint64_t k) noexcept {
119-
return VMPilot::Cbor::require_text<UnitAcceptError>(m, k);
120-
}
121121
inline auto require_uint(const Value& m, std::uint64_t k) noexcept {
122122
return VMPilot::Cbor::require_uint<UnitAcceptError>(m, k);
123123
}
@@ -144,77 +144,68 @@ tl::expected<PayloadIdentity, UnitAcceptError> parse_payload_identity(
144144

145145
tl::expected<UnitDescriptor, UnitAcceptError> parse_unit_descriptor_bytes(
146146
const std::vector<std::uint8_t>& bytes) noexcept {
147+
using namespace VMPilot::Cbor::Schema;
147148
auto tree_or = parse_strict(bytes.data(), bytes.size());
148149
if (!tree_or)
149150
return err(UnitAcceptError::UnitDescriptorMalformed);
150151
const Value& tree = *tree_or;
151-
if (tree.kind() != Value::Kind::Map)
152-
return err(UnitAcceptError::UnitDescriptorMalformed);
153152

154-
VMPILOT_TRY_ASSIGN(ver, require_text(tree, kUd_DescriptorVersion));
155-
VMPILOT_TRY_ASSIGN(uid, require_text(tree, kUd_UnitId));
156-
VMPILOT_TRY_ASSIGN(uih, require_hash32(tree, kUd_UnitIdentityHash));
157-
VMPILOT_TRY_ASSIGN(fam, require_text(tree, kUd_FamilyId));
158-
VMPILOT_TRY_ASSIGN(pol, require_text(tree, kUd_RequestedPolicyId));
159-
VMPILOT_TRY_ASSIGN(prof, require_text(tree, kUd_ResolvedFamilyProfileId));
160-
VMPILOT_TRY_ASSIGN(ubr, require_text(tree, kUd_UnitBindingRecordId));
161-
162-
auto fam_enum = VMPilot::DomainLabels::parse_family_id(fam);
163-
if (!fam_enum)
164-
return err(UnitAcceptError::UnknownFamilyId);
165-
auto pol_enum = VMPilot::DomainLabels::parse_policy_id(pol);
166-
if (!pol_enum)
167-
return err(UnitAcceptError::UnknownPolicyId);
153+
const auto schema = std::make_tuple(
154+
TextField<UnitDescriptor>{kUd_DescriptorVersion,
155+
&UnitDescriptor::descriptor_version},
156+
TextField<UnitDescriptor>{kUd_UnitId, &UnitDescriptor::unit_id},
157+
HashField<UnitDescriptor>{kUd_UnitIdentityHash,
158+
&UnitDescriptor::unit_identity_hash},
159+
EnumTextField<UnitDescriptor,
160+
VMPilot::DomainLabels::FamilyId, UnitAcceptError>{
161+
kUd_FamilyId, &UnitDescriptor::family_id,
162+
UnitAcceptError::UnknownFamilyId},
163+
EnumTextField<UnitDescriptor,
164+
VMPilot::DomainLabels::PolicyId, UnitAcceptError>{
165+
kUd_RequestedPolicyId, &UnitDescriptor::requested_policy_id,
166+
UnitAcceptError::UnknownPolicyId},
167+
TextField<UnitDescriptor>{kUd_ResolvedFamilyProfileId,
168+
&UnitDescriptor::resolved_family_profile_id},
169+
TextField<UnitDescriptor>{kUd_UnitBindingRecordId,
170+
&UnitDescriptor::unit_binding_record_id}
171+
);
172+
auto parsed = parse_schema<UnitDescriptor, UnitAcceptError>(tree, schema);
173+
if (!parsed) return err(parsed.error());
168174

169175
const Value* pid_v = tree.find_by_uint_key(kUd_PayloadIdentity);
170176
if (pid_v == nullptr)
171177
return err(UnitAcceptError::MissingCoreField);
172-
VMPILOT_TRY_ASSIGN(
173-
pid, parse_payload_identity(*pid_v,
174-
UnitAcceptError::UnitDescriptorMalformed));
175-
176-
UnitDescriptor out;
177-
out.descriptor_version = std::string(ver);
178-
out.unit_id = std::string(uid);
179-
out.unit_identity_hash = uih;
180-
out.family_id = *fam_enum;
181-
out.requested_policy_id = *pol_enum;
182-
out.resolved_family_profile_id = std::string(prof);
183-
out.payload_identity = pid;
184-
out.unit_binding_record_id = std::string(ubr);
185-
return out;
178+
auto pid = parse_payload_identity(*pid_v,
179+
UnitAcceptError::UnitDescriptorMalformed);
180+
if (!pid) return err(pid.error());
181+
parsed->payload_identity = *pid;
182+
return parsed;
186183
}
187184

188185
// ─── UnitBindingAuth + UBR ──────────────────────────────────────────────
189186

190187
tl::expected<UnitBindingAuth, UnitAcceptError> parse_binding_auth(
191188
const Value& auth_v) noexcept {
189+
using namespace VMPilot::Cbor::Schema;
192190
if (auth_v.kind() != Value::Kind::Map)
193191
return err(UnitAcceptError::UnitBindingAuthMalformed);
194192

195-
auto kind = require_text(auth_v, kAuth_Kind);
196-
auto ubt_hash = require_hash32(auth_v, kAuth_UnitBindingTableHash);
197-
auto inclusion = require_uint(auth_v, kAuth_InclusionIndex);
198-
auto rec_hash = require_hash32(auth_v, kAuth_RecordHash);
199-
if (!kind)
200-
return err(kind.error());
201-
if (!ubt_hash)
202-
return err(ubt_hash.error());
203-
if (!inclusion)
204-
return err(inclusion.error());
205-
if (!rec_hash)
206-
return err(rec_hash.error());
207-
208-
if (*kind != kAuthKindPackageSignedUnitInclusionV1) {
193+
const auto schema = std::make_tuple(
194+
TextField<UnitBindingAuth>{kAuth_Kind, &UnitBindingAuth::kind},
195+
HashField<UnitBindingAuth>{kAuth_UnitBindingTableHash,
196+
&UnitBindingAuth::unit_binding_table_hash},
197+
UintField<UnitBindingAuth>{kAuth_InclusionIndex,
198+
&UnitBindingAuth::inclusion_index},
199+
HashField<UnitBindingAuth>{kAuth_RecordHash,
200+
&UnitBindingAuth::record_hash}
201+
);
202+
auto parsed = parse_schema<UnitBindingAuth, UnitAcceptError>(auth_v, schema);
203+
if (!parsed) return err(parsed.error());
204+
205+
if (parsed->kind != kAuthKindPackageSignedUnitInclusionV1) {
209206
return err(UnitAcceptError::UnitBindingAuthMalformed);
210207
}
211-
212-
UnitBindingAuth out;
213-
out.kind = std::move(*kind);
214-
out.unit_binding_table_hash = *ubt_hash;
215-
out.inclusion_index = *inclusion;
216-
out.record_hash = *rec_hash;
217-
return out;
208+
return parsed;
218209
}
219210

220211
// Parse a single UBT entry (CBOR array[2] = [canonical_without_auth_bytes,
@@ -238,47 +229,46 @@ tl::expected<UnitBindingRecord, UnitAcceptError> parse_unit_binding_record(
238229
return err(UnitAcceptError::UnitBindingAuthMalformed);
239230
}
240231

232+
using namespace VMPilot::Cbor::Schema;
241233
const auto& canonical_bytes = canonical_v.as_bytes();
242234
auto inner_or =
243235
parse_strict(canonical_bytes.data(), canonical_bytes.size());
244236
if (!inner_or)
245237
return err(UnitAcceptError::UnitBindingRecordMalformed);
246238
const Value& ubr_v = *inner_or;
247-
if (ubr_v.kind() != Value::Kind::Map) {
248-
return err(UnitAcceptError::UnitBindingRecordMalformed);
249-
}
250239

251-
auto id = require_text(ubr_v, kUbr_UnitBindingRecordId);
252-
auto uih = require_hash32(ubr_v, kUbr_UnitIdentityHash);
253-
auto udh = require_hash32(ubr_v, kUbr_UnitDescriptorHash);
254-
auto fam = require_text(ubr_v, kUbr_FamilyId);
255-
auto pol = require_text(ubr_v, kUbr_RequestedPolicyId);
256-
auto prof = require_text(ubr_v, kUbr_ResolvedFamilyProfileId);
257-
auto pch = require_hash32(ubr_v, kUbr_ResolvedFamilyProfileContentHash);
258-
auto epoch = require_uint(ubr_v, kUbr_AntiDowngradeEpoch);
259-
if (!id)
260-
return err(id.error());
261-
if (!uih)
262-
return err(uih.error());
263-
if (!udh)
264-
return err(udh.error());
265-
if (!fam)
266-
return err(fam.error());
267-
if (!pol)
268-
return err(pol.error());
269-
if (!prof)
270-
return err(prof.error());
271-
if (!pch)
272-
return err(pch.error());
273-
if (!epoch)
274-
return err(epoch.error());
275-
276-
auto fam_enum = VMPilot::DomainLabels::parse_family_id(*fam);
277-
if (!fam_enum)
278-
return err(UnitAcceptError::UnknownFamilyId);
279-
auto pol_enum = VMPilot::DomainLabels::parse_policy_id(*pol);
280-
if (!pol_enum)
281-
return err(UnitAcceptError::UnknownPolicyId);
240+
const auto schema = std::make_tuple(
241+
TextField<UnitBindingRecord>{kUbr_UnitBindingRecordId,
242+
&UnitBindingRecord::unit_binding_record_id},
243+
HashField<UnitBindingRecord>{kUbr_UnitIdentityHash,
244+
&UnitBindingRecord::unit_identity_hash},
245+
HashField<UnitBindingRecord>{kUbr_UnitDescriptorHash,
246+
&UnitBindingRecord::unit_descriptor_hash},
247+
EnumTextField<UnitBindingRecord,
248+
VMPilot::DomainLabels::FamilyId, UnitAcceptError>{
249+
kUbr_FamilyId, &UnitBindingRecord::family_id,
250+
UnitAcceptError::UnknownFamilyId},
251+
EnumTextField<UnitBindingRecord,
252+
VMPilot::DomainLabels::PolicyId, UnitAcceptError>{
253+
kUbr_RequestedPolicyId, &UnitBindingRecord::requested_policy_id,
254+
UnitAcceptError::UnknownPolicyId},
255+
TextField<UnitBindingRecord>{kUbr_ResolvedFamilyProfileId,
256+
&UnitBindingRecord::resolved_family_profile_id},
257+
HashField<UnitBindingRecord>{kUbr_ResolvedFamilyProfileContentHash,
258+
&UnitBindingRecord::resolved_family_profile_content_hash},
259+
UintField<UnitBindingRecord>{kUbr_AntiDowngradeEpoch,
260+
&UnitBindingRecord::anti_downgrade_epoch}
261+
);
262+
auto parsed = parse_schema<UnitBindingRecord, UnitAcceptError>(ubr_v, schema);
263+
if (!parsed) {
264+
// parse_schema emits NotAMap via the default trait constants,
265+
// which route to UnitDescriptorMalformed. Remap to the UBR
266+
// variant here so the error keeps pointing at the right layer.
267+
if (ubr_v.kind() != Value::Kind::Map) {
268+
return err(UnitAcceptError::UnitBindingRecordMalformed);
269+
}
270+
return err(parsed.error());
271+
}
282272

283273
const Value* pid_v = ubr_v.find_by_uint_key(kUbr_PayloadIdentity);
284274
if (pid_v == nullptr)
@@ -287,10 +277,12 @@ tl::expected<UnitBindingRecord, UnitAcceptError> parse_unit_binding_record(
287277
*pid_v, UnitAcceptError::UnitBindingRecordMalformed);
288278
if (!pid)
289279
return err(pid.error());
280+
parsed->payload_identity = *pid;
290281

291282
auto auth = parse_binding_auth(auth_v);
292283
if (!auth)
293284
return err(auth.error());
285+
parsed->binding_auth = std::move(*auth);
294286

295287
// Record hash must commit to the exact canonical bytes we just parsed.
296288
// Without this the wrapper's element [0] could be swapped for any
@@ -299,22 +291,10 @@ tl::expected<UnitBindingRecord, UnitAcceptError> parse_unit_binding_record(
299291
const auto computed_record_hash =
300292
domain_hash_sha256(VMPilot::DomainLabels::Hash::UnitBindingRecord,
301293
canonical_bytes.data(), canonical_bytes.size());
302-
if (!hash_equals(computed_record_hash, auth->record_hash)) {
294+
if (!hash_equals(computed_record_hash, parsed->binding_auth.record_hash)) {
303295
return err(UnitAcceptError::UnitBindingRecordRecordHashMismatch);
304296
}
305-
306-
UnitBindingRecord out;
307-
out.unit_binding_record_id = std::move(*id);
308-
out.unit_identity_hash = *uih;
309-
out.unit_descriptor_hash = *udh;
310-
out.family_id = *fam_enum;
311-
out.requested_policy_id = *pol_enum;
312-
out.resolved_family_profile_id = std::move(*prof);
313-
out.resolved_family_profile_content_hash = *pch;
314-
out.payload_identity = *pid;
315-
out.anti_downgrade_epoch = *epoch;
316-
out.binding_auth = std::move(*auth);
317-
return out;
297+
return parsed;
318298
}
319299

320300
// ─── Table lookups ──────────────────────────────────────────────────────

0 commit comments

Comments
 (0)