Skip to content

Commit 660e0e3

Browse files
committed
refactor: use EventLogVersion enum instead of raw u32
Replace u32 version fields with an EventLogVersion enum to get exhaustive match checking — adding a v3 in the future will force all match sites to be updated at compile time. Also removes version field from event log serialization as it's unnecessary (verifier distinguishes v1/v2 via event_type).
1 parent 654021a commit 660e0e3

14 files changed

Lines changed: 113 additions & 73 deletions

File tree

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cc-eventlog/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ license.workspace = true
1313
[dependencies]
1414
anyhow.workspace = true
1515
digest = "0.10.7"
16+
dstack-types.workspace = true
1617
ez-hash.workspace = true
1718
fs-err.workspace = true
1819
hex.workspace = true

cc-eventlog/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
//
33
// SPDX-License-Identifier: Apache-2.0
44

5+
pub use dstack_types::EventLogVersion;
56
pub use runtime_events::{
67
canonical_event_json, replay_events, RuntimeEvent, DSTACK_RUNTIME_EVENT_TYPE_V2,
78
};

cc-eventlog/src/runtime_events.rs

Lines changed: 39 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// SPDX-License-Identifier: Apache-2.0
44

55
use anyhow::{Context, Result};
6+
use dstack_types::EventLogVersion;
67
use fs_err as fs;
78
use scale::{Decode, Encode};
89
use serde::{Deserialize, Serialize};
@@ -30,13 +31,13 @@ pub struct RuntimeEvent {
3031
/// Event payload
3132
#[serde(with = "base64")]
3233
pub payload: Vec<u8>,
33-
/// Event log version (0 or absent = v1, 2 = v2 JSON canonical)
34+
/// Event log version
3435
#[serde(default, skip_serializing)]
35-
pub version: u32,
36+
pub version: EventLogVersion,
3637
}
3738

3839
impl RuntimeEvent {
39-
pub fn new(event: String, payload: Vec<u8>, version: u32) -> Self {
40+
pub fn new(event: String, payload: Vec<u8>, version: EventLogVersion) -> Self {
4041
Self {
4142
event,
4243
payload,
@@ -109,45 +110,29 @@ impl RuntimeEvent {
109110

110111
/// Compute the digest of the event.
111112
///
112-
/// For v1 (version 0 or 1): `SHA(event_type_le || ":" || event_name || ":" || payload)`
113-
/// For v2 (version 2): `SHA(canonical_json({"event":"...","event_type":134217730,"payload":"hex..."}))`
113+
/// - V1: `SHA(event_type_le || ":" || event_name || ":" || payload)`
114+
/// - V2: `SHA(canonical_json({"event":"...","event_type":134217730,"payload":"hex..."}))`
114115
pub fn digest<H: Hasher>(&self) -> H::Output {
115-
if self.is_v2() {
116-
self.digest_v2::<H>()
117-
} else {
118-
self.digest_v1::<H>()
116+
match self.version {
117+
EventLogVersion::V1 => H::hash([
118+
&DSTACK_RUNTIME_EVENT_TYPE.to_ne_bytes()[..],
119+
b":",
120+
self.event.as_bytes(),
121+
b":",
122+
&self.payload,
123+
]),
124+
EventLogVersion::V2 => {
125+
let canonical =
126+
canonical_event_json(&self.event, DSTACK_RUNTIME_EVENT_TYPE_V2, &self.payload);
127+
H::hash([canonical.as_bytes()])
128+
}
119129
}
120130
}
121131

122-
fn digest_v1<H: Hasher>(&self) -> H::Output {
123-
H::hash([
124-
&DSTACK_RUNTIME_EVENT_TYPE.to_ne_bytes()[..],
125-
b":",
126-
self.event.as_bytes(),
127-
b":",
128-
&self.payload,
129-
])
130-
}
131-
132-
/// Compute the v2 digest using JCS (RFC 8785) canonical JSON.
133-
///
134-
/// The canonical JSON has keys sorted alphabetically:
135-
/// `{"event":"<name>","event_type":134217730,"payload":"<hex>"}`
136-
fn digest_v2<H: Hasher>(&self) -> H::Output {
137-
let canonical =
138-
canonical_event_json(&self.event, DSTACK_RUNTIME_EVENT_TYPE_V2, &self.payload);
139-
H::hash([canonical.as_bytes()])
140-
}
141-
142-
pub fn is_v2(&self) -> bool {
143-
self.version == 2
144-
}
145-
146132
pub fn cc_event_type(&self) -> u32 {
147-
if self.is_v2() {
148-
DSTACK_RUNTIME_EVENT_TYPE_V2
149-
} else {
150-
DSTACK_RUNTIME_EVENT_TYPE
133+
match self.version {
134+
EventLogVersion::V1 => DSTACK_RUNTIME_EVENT_TYPE,
135+
EventLogVersion::V2 => DSTACK_RUNTIME_EVENT_TYPE_V2,
151136
}
152137
}
153138
}
@@ -189,7 +174,11 @@ mod tests {
189174

190175
#[test]
191176
fn v1_digest_unchanged() {
192-
let event = RuntimeEvent::new("app-id".to_string(), vec![0xde, 0xad, 0xbe, 0xef], 1);
177+
let event = RuntimeEvent::new(
178+
"app-id".to_string(),
179+
vec![0xde, 0xad, 0xbe, 0xef],
180+
EventLogVersion::V1,
181+
);
193182
let digest = event.digest::<Sha384>();
194183
let expected = Sha384::hash([
195184
&DSTACK_RUNTIME_EVENT_TYPE.to_ne_bytes()[..],
@@ -203,7 +192,11 @@ mod tests {
203192

204193
#[test]
205194
fn v2_digest_is_canonical_json_hash() {
206-
let event = RuntimeEvent::new("compose-hash".to_string(), vec![0xab, 0xcd], 2);
195+
let event = RuntimeEvent::new(
196+
"compose-hash".to_string(),
197+
vec![0xab, 0xcd],
198+
EventLogVersion::V2,
199+
);
207200
let canonical =
208201
canonical_event_json(&event.event, DSTACK_RUNTIME_EVENT_TYPE_V2, &event.payload);
209202
assert_eq!(
@@ -217,8 +210,8 @@ mod tests {
217210

218211
#[test]
219212
fn v2_digest_differs_from_v1() {
220-
let v1 = RuntimeEvent::new("test".to_string(), vec![1, 2, 3], 1);
221-
let v2 = RuntimeEvent::new("test".to_string(), vec![1, 2, 3], 2);
213+
let v1 = RuntimeEvent::new("test".to_string(), vec![1, 2, 3], EventLogVersion::V1);
214+
let v2 = RuntimeEvent::new("test".to_string(), vec![1, 2, 3], EventLogVersion::V2);
222215
assert_ne!(
223216
v1.digest::<Sha384>(),
224217
v2.digest::<Sha384>(),
@@ -228,29 +221,28 @@ mod tests {
228221

229222
#[test]
230223
fn v1_event_type() {
231-
let event = RuntimeEvent::new("test".to_string(), vec![], 1);
224+
let event = RuntimeEvent::new("test".to_string(), vec![], EventLogVersion::V1);
232225
assert_eq!(event.cc_event_type(), DSTACK_RUNTIME_EVENT_TYPE);
233226
}
234227

235228
#[test]
236229
fn v2_event_type() {
237-
let event = RuntimeEvent::new("test".to_string(), vec![], 2);
230+
let event = RuntimeEvent::new("test".to_string(), vec![], EventLogVersion::V2);
238231
assert_eq!(event.cc_event_type(), DSTACK_RUNTIME_EVENT_TYPE_V2);
239232
}
240233

241234
#[test]
242235
fn deserialize_v1_without_version_field() {
243236
let json = r#"{"event":"app-id","payload":"AQID"}"#;
244237
let event: RuntimeEvent = serde_json::from_str(json).unwrap();
245-
assert_eq!(event.version, 0);
246-
assert!(!event.is_v2());
238+
assert_eq!(event.version, EventLogVersion::V1);
247239
assert_eq!(event.cc_event_type(), DSTACK_RUNTIME_EVENT_TYPE);
248240
}
249241

250242
#[test]
251243
fn serialize_omits_version() {
252-
let v1 = RuntimeEvent::new("test".to_string(), vec![1], 1);
253-
let v2 = RuntimeEvent::new("test".to_string(), vec![1], 2);
244+
let v1 = RuntimeEvent::new("test".to_string(), vec![1], EventLogVersion::V1);
245+
let v2 = RuntimeEvent::new("test".to_string(), vec![1], EventLogVersion::V2);
254246
let json_v1 = serde_json::to_string(&v1).unwrap();
255247
let json_v2 = serde_json::to_string(&v2).unwrap();
256248
assert!(

cc-eventlog/src/tdx.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,10 +84,11 @@ impl TdxEvent {
8484
if !self.is_runtime_event() {
8585
return None;
8686
}
87+
use dstack_types::EventLogVersion;
8788
let version = if self.event_type == DSTACK_RUNTIME_EVENT_TYPE_V2 {
88-
2
89+
EventLogVersion::V2
8990
} else {
90-
0
91+
EventLogVersion::V1
9192
};
9293
Some(RuntimeEvent {
9394
event: self.event.clone(),

dstack-attest/src/attestation.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ pub const TDX_QUOTE_REPORT_DATA_RANGE: std::ops::Range<usize> = 568..632;
1212
use std::{borrow::Cow, time::SystemTime};
1313

1414
use anyhow::{anyhow, bail, Context, Result};
15-
use cc_eventlog::{RuntimeEvent, TdxEvent};
15+
use cc_eventlog::{EventLogVersion, RuntimeEvent, TdxEvent};
1616
use dcap_qvl::{
1717
quote::{EnclaveReport, Quote, Report, TDReport10, TDReport15},
1818
verify::VerifiedReport as TdxVerifiedReport,
@@ -1036,7 +1036,11 @@ impl Attestation {
10361036
let runtime_events = if mode.is_composable() {
10371037
RuntimeEvent::read_all().context("Failed to read runtime events")?
10381038
} else if let Some(app_id) = app_id {
1039-
vec![RuntimeEvent::new("app-id".to_string(), app_id.to_vec(), 0)]
1039+
vec![RuntimeEvent::new(
1040+
"app-id".to_string(),
1041+
app_id.to_vec(),
1042+
EventLogVersion::V1,
1043+
)]
10401044
} else {
10411045
vec![]
10421046
};

dstack-attest/src/lib.rs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
// SPDX-License-Identifier: Apache-2.0
44

55
use anyhow::Context;
6-
use cc_eventlog::RuntimeEvent;
6+
use cc_eventlog::{EventLogVersion, RuntimeEvent};
77

88
pub use cc_eventlog as ccel;
99
pub use tdx_attest as tdx;
@@ -14,14 +14,12 @@ pub mod attestation;
1414
mod v1;
1515

1616
/// Emit a runtime event that extends RTMR3 and logs the event.
17-
///
18-
/// `event_log_version`: 1 for legacy binary digest, 2 for JSON canonical digest.
1917
pub fn emit_runtime_event(
2018
event: &str,
2119
payload: &[u8],
22-
event_log_version: u32,
20+
version: EventLogVersion,
2321
) -> anyhow::Result<()> {
24-
let event = RuntimeEvent::new(event.to_string(), payload.to_vec(), event_log_version);
22+
let event = RuntimeEvent::new(event.to_string(), payload.to_vec(), version);
2523

2624
let mode = AttestationMode::detect()?;
2725

dstack-types/src/lib.rs

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,45 @@ use serde::{Deserialize, Serialize};
99
use serde_human_bytes as hex_bytes;
1010
use size_parser::human_size;
1111

12+
/// Event log version controlling the digest format.
13+
///
14+
/// Using an enum ensures exhaustive matching — adding a new version
15+
/// forces all match sites to be updated.
16+
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Encode, Decode)]
17+
pub enum EventLogVersion {
18+
/// Legacy binary digest: `SHA(event_type_le || ":" || name || ":" || payload)`
19+
#[default]
20+
V1,
21+
/// JSON canonical digest (JCS RFC 8785):
22+
/// `SHA({"event":"...","event_type":134217730,"payload":"hex..."})`
23+
V2,
24+
}
25+
26+
impl EventLogVersion {
27+
pub fn from_u32(v: u32) -> Self {
28+
match v {
29+
2 => EventLogVersion::V2,
30+
_ => EventLogVersion::V1,
31+
}
32+
}
33+
}
34+
35+
impl Serialize for EventLogVersion {
36+
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
37+
match self {
38+
EventLogVersion::V1 => serializer.serialize_u32(1),
39+
EventLogVersion::V2 => serializer.serialize_u32(2),
40+
}
41+
}
42+
}
43+
44+
impl<'de> Deserialize<'de> for EventLogVersion {
45+
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
46+
let v = u32::deserialize(deserializer)?;
47+
Ok(EventLogVersion::from_u32(v))
48+
}
49+
}
50+
1251
#[derive(Deserialize, Serialize, Debug, Clone)]
1352
pub struct AppCompose {
1453
pub manifest_version: u32,
@@ -45,18 +84,14 @@ pub struct AppCompose {
4584
pub storage_fs: Option<String>,
4685
#[serde(default, with = "human_size")]
4786
pub swap_size: u64,
48-
#[serde(default = "default_event_log_version")]
49-
pub event_log_version: u32,
87+
#[serde(default)]
88+
pub event_log_version: EventLogVersion,
5089
}
5190

5291
fn default_true() -> bool {
5392
true
5493
}
5594

56-
fn default_event_log_version() -> u32 {
57-
1
58-
}
59-
6095
fn deserialize_gateway_enabled<'de, D>(deserializer: D) -> Result<bool, D::Error>
6196
where
6297
D: serde::Deserializer<'de>,

dstack-util/src/main.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -228,8 +228,8 @@ fn hex_decode(hex_str: &str) -> Result<Vec<u8>> {
228228

229229
fn cmd_extend(extend_args: ExtendArgs) -> Result<()> {
230230
let payload = hex_decode(&extend_args.payload).context("Failed to decode payload")?;
231-
emit_runtime_event(&extend_args.event, &payload, extend_args.event_log_version)
232-
.context("Failed to extend RTMR")
231+
let version = dstack_types::EventLogVersion::from_u32(extend_args.event_log_version);
232+
emit_runtime_event(&extend_args.event, &payload, version).context("Failed to extend RTMR")
233233
}
234234

235235
fn cmd_rand(rand_args: RandArgs) -> Result<()> {

dstack-util/src/system_setup.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ use dstack_types::{
2020
APP_COMPOSE, APP_KEYS, DECRYPTED_ENV, DECRYPTED_ENV_JSON, ENCRYPTED_ENV,
2121
HOST_SHARED_DIR_NAME, HOST_SHARED_DISK_LABEL, INSTANCE_INFO, SYS_CONFIG, USER_CONFIG,
2222
},
23-
KeyProvider, KeyProviderInfo,
23+
EventLogVersion, KeyProvider, KeyProviderInfo,
2424
};
2525
use fs_err as fs;
2626
use luks2::{
@@ -688,7 +688,10 @@ fn truncate(s: &[u8], len: usize) -> &[u8] {
688688
}
689689
}
690690

691-
fn emit_key_provider_info(provider_info: &KeyProviderInfo, event_log_version: u32) -> Result<()> {
691+
fn emit_key_provider_info(
692+
provider_info: &KeyProviderInfo,
693+
event_log_version: EventLogVersion,
694+
) -> Result<()> {
692695
info!("Key provider info: {provider_info:?}");
693696
let provider_info_json = serde_json::to_vec(&provider_info)?;
694697
emit_runtime_event("key-provider", &provider_info_json, event_log_version)?;

0 commit comments

Comments
 (0)