1use crate::{
2 SensitiveBytes,
3 credential::Credential,
4 crypto::{HpkePublicKey, HpkePublicKeyRef, SignaturePublicKey, SignaturePublicKeyRef},
5 defs::{Capabilities, LeafIndex},
6 group::{GroupIdRef, KeyPackageLifetime, RequiredCapabilities, extensions::Extension},
7};
8
9#[derive(
10 Debug,
11 Clone,
12 Copy,
13 PartialEq,
14 Eq,
15 tls_codec::TlsSize,
16 tls_codec::TlsDeserialize,
17 tls_codec::TlsSerialize,
18 strum::Display,
19)]
20#[repr(u8)]
21pub enum LeafNodeSourceType {
22 Reserved = 0x00,
23 KeyPackage = 0x01,
24 Update = 0x02,
25 Commit = 0x03,
26}
27
28#[derive(
29 Debug,
30 Clone,
31 PartialEq,
32 Eq,
33 Hash,
34 tls_codec::TlsSerialize,
35 tls_codec::TlsDeserialize,
36 tls_codec::TlsSize,
37)]
38#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
39#[repr(u8)]
40pub enum LeafNodeSource {
41 #[tls_codec(discriminant = "LeafNodeSourceType::KeyPackage")]
42 KeyPackage { lifetime: KeyPackageLifetime },
43 #[tls_codec(discriminant = "LeafNodeSourceType::Update")]
44 Update,
45 #[tls_codec(discriminant = "LeafNodeSourceType::Commit")]
46 Commit { parent_hash: SensitiveBytes },
47}
48
49impl From<&LeafNodeSource> for LeafNodeSourceType {
50 fn from(value: &LeafNodeSource) -> Self {
51 match value {
52 LeafNodeSource::KeyPackage { .. } => Self::KeyPackage,
53 LeafNodeSource::Update => Self::Update,
54 LeafNodeSource::Commit { .. } => Self::Commit,
55 }
56 }
57}
58
59#[derive(Debug, Copy, Clone, PartialEq, Eq, tls_codec::TlsSerialize, tls_codec::TlsSize)]
60#[cfg_attr(feature = "serde", derive(serde::Serialize))]
61pub struct LeafNodeMemberInfo<'a> {
62 #[tls_codec(with = "crate::tlspl::bytes")]
63 pub group_id: GroupIdRef<'a>,
64 pub leaf_index: LeafIndex,
65}
66
67#[derive(
68 Debug,
69 Clone,
70 PartialEq,
71 Eq,
72 Hash,
73 tls_codec::TlsSerialize,
74 tls_codec::TlsDeserialize,
75 tls_codec::TlsSize,
76)]
77#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
78pub struct LeafNode {
79 pub encryption_key: HpkePublicKey,
80 pub signature_key: SignaturePublicKey,
81 pub credential: Credential,
82 pub capabilities: Capabilities,
83 pub source: LeafNodeSource,
84 pub extensions: Vec<Extension>,
85 pub signature: SensitiveBytes,
86}
87
88impl LeafNode {
89 #[inline]
90 pub fn requires_member_info(&self) -> bool {
91 matches!(
92 self.source,
93 LeafNodeSource::Update | LeafNodeSource::Commit { .. }
94 )
95 }
96
97 pub fn parent_hash(&self) -> Option<&[u8]> {
98 match &self.source {
99 LeafNodeSource::Commit { parent_hash } => Some(parent_hash),
100 _ => None,
101 }
102 }
103
104 pub fn to_tbs<'a>(
105 &'a self,
106 member_info: Option<LeafNodeMemberInfo<'a>>,
107 ) -> Option<LeafNodeTBS<'a>> {
108 Some(LeafNodeTBS {
109 encryption_key: &self.encryption_key,
110 signature_key: &self.signature_key,
111 credential: &self.credential,
112 capabilities: &self.capabilities,
113 source: &self.source,
114 extensions: &self.extensions,
115 member_info: if self.requires_member_info() {
116 Some(member_info?)
118 } else {
119 None
120 },
121 })
122 }
123
124 pub fn application_id(&self) -> Option<&[u8]> {
125 self.extensions.iter().find_map(|ext| {
126 if let Extension::ApplicationId(app_id) = ext {
127 Some(app_id.as_slice())
128 } else {
129 None
130 }
131 })
132 }
133
134 pub fn supports_required_capabilities(&self, required_caps: &RequiredCapabilities) -> bool {
135 if !required_caps.extension_types.iter().all(|req_ext| {
136 req_ext.is_grease_value()
137 || req_ext.is_spec_default()
138 || self.capabilities.extensions.contains(req_ext)
139 }) {
140 return false;
141 }
142
143 if !required_caps.proposal_types.iter().all(|req_prop| {
144 req_prop.is_grease_value()
145 || req_prop.is_spec_default()
146 || self.capabilities.proposals.contains(req_prop)
147 }) {
148 return false;
149 }
150
151 if !required_caps.credential_types.iter().all(|req_cred| {
152 req_cred.is_grease_value() || self.capabilities.credentials.contains(req_cred)
153 }) {
154 return false;
155 }
156
157 true
158 }
159}
160
161#[derive(Debug, PartialEq, Eq)]
162pub struct LeafNodeTBS<'a> {
163 pub encryption_key: HpkePublicKeyRef<'a>,
164 pub signature_key: SignaturePublicKeyRef<'a>,
165 pub credential: &'a Credential,
166 pub capabilities: &'a Capabilities,
167 pub source: &'a LeafNodeSource,
168 pub extensions: &'a Vec<Extension>,
169 pub member_info: Option<LeafNodeMemberInfo<'a>>,
170}
171
172impl tls_codec::Size for LeafNodeTBS<'_> {
173 fn tls_serialized_len(&self) -> usize {
174 self.encryption_key.tls_serialized_len()
175 + self.signature_key.tls_serialized_len()
176 + self.credential.tls_serialized_len()
177 + self.capabilities.tls_serialized_len()
178 + self.source.tls_serialized_len()
179 + self.extensions.tls_serialized_len()
180 + self.member_info.map_or(0, |mi| mi.tls_serialized_len())
181 }
182}
183
184impl tls_codec::Serialize for LeafNodeTBS<'_> {
185 fn tls_serialize<W: std::io::Write>(&self, writer: &mut W) -> Result<usize, tls_codec::Error> {
186 let mut written = 0;
187 written += crate::tlspl::bytes::tls_serialize(self.encryption_key, writer)?;
188 written += crate::tlspl::bytes::tls_serialize(self.signature_key, writer)?;
189 written += self.credential.tls_serialize(writer)?;
190 written += self.capabilities.tls_serialize(writer)?;
191 written += self.source.tls_serialize(writer)?;
192 written += self.extensions.tls_serialize(writer)?;
193 if let Some(member_info) = self.member_info {
194 written += member_info.tls_serialize(writer)?;
195 }
196
197 Ok(written)
198 }
199}