1use serde::{Deserialize, Deserializer, Serialize, de::DeserializeOwned};
8
9use crate::{
10 Blob, EntityFragment, EntityMigration, EntitySourceKey, FieldFragment, FieldSourceKey,
11 MAX_PROPOSAL_LITERAL_BYTES, MAX_SCHEMA_FRAGMENT_BYTES, MAX_SCHEMA_MIGRATION_PLAN_BYTES,
12 MAX_SCHEMA_PROPOSAL_BYTES, NamedTypeFragment, SchemaContractError, SchemaFragment,
13 SchemaMigrationPlan, SchemaProposal,
14};
15
16const FRAGMENT_HEADER: [u8; 5] = *b"ICWF\x01";
17const PROPOSAL_HEADER: [u8; 5] = *b"ICWP\x01";
18const MIGRATION_HEADER: [u8; 5] = *b"ICWM\x01";
19const SOURCE_MEANING_HEADER: [u8; 5] = *b"ICWS\x01";
20
21pub fn encode_schema_fragment(fragment: &SchemaFragment) -> Result<Vec<u8>, SchemaContractError> {
27 fragment.validate()?;
28 encode(FRAGMENT_HEADER, fragment, MAX_SCHEMA_FRAGMENT_BYTES)
29}
30
31pub fn decode_schema_fragment(bytes: &[u8]) -> Result<SchemaFragment, SchemaContractError> {
37 let fragment: SchemaFragment = decode(FRAGMENT_HEADER, bytes, MAX_SCHEMA_FRAGMENT_BYTES)?;
38 fragment.validate()?;
39 ensure_canonical(bytes, encode_schema_fragment(&fragment)?)?;
40 Ok(fragment)
41}
42
43pub fn encode_schema_proposal(proposal: &SchemaProposal) -> Result<Vec<u8>, SchemaContractError> {
49 proposal.validate_current()?;
50 encode(PROPOSAL_HEADER, proposal, MAX_SCHEMA_PROPOSAL_BYTES)
51}
52
53pub fn decode_schema_proposal(bytes: &[u8]) -> Result<SchemaProposal, SchemaContractError> {
59 let proposal: SchemaProposal = decode(PROPOSAL_HEADER, bytes, MAX_SCHEMA_PROPOSAL_BYTES)?;
60 proposal.validate_current()?;
61 ensure_canonical(bytes, encode_schema_proposal(&proposal)?)?;
62 Ok(proposal)
63}
64
65pub fn encode_schema_migration_plan(
71 plan: &SchemaMigrationPlan,
72) -> Result<Vec<u8>, SchemaContractError> {
73 plan.validate()?;
74 encode(MIGRATION_HEADER, plan, MAX_SCHEMA_MIGRATION_PLAN_BYTES)
75}
76
77pub fn decode_schema_migration_plan(
83 bytes: &[u8],
84) -> Result<SchemaMigrationPlan, SchemaContractError> {
85 let plan: SchemaMigrationPlan =
86 decode(MIGRATION_HEADER, bytes, MAX_SCHEMA_MIGRATION_PLAN_BYTES)?;
87 plan.validate()?;
88 ensure_canonical(bytes, encode_schema_migration_plan(&plan)?)?;
89 Ok(plan)
90}
91
92pub(crate) fn encode_migration_transitions_for_digest(
93 transitions: &[EntityMigration],
94) -> Result<Vec<u8>, SchemaContractError> {
95 encode(
98 MIGRATION_HEADER,
99 &(
100 crate::migration::MIGRATION_PROGRAM_VERSION_CURRENT,
101 transitions,
102 ),
103 MAX_SCHEMA_MIGRATION_PLAN_BYTES,
104 )
105}
106
107pub(crate) fn encode_entity_source_meaning(
108 entity: &EntityFragment,
109 relation_targets: &[(EntitySourceKey, Vec<(FieldSourceKey, FieldFragment)>)],
110 reachable_types: &[NamedTypeFragment],
111) -> Result<Vec<u8>, SchemaContractError> {
112 encode(
113 SOURCE_MEANING_HEADER,
114 &(entity, relation_targets, reachable_types),
115 MAX_SCHEMA_PROPOSAL_BYTES,
116 )
117}
118
119fn encode<T: Serialize + ?Sized>(
120 header: [u8; 5],
121 value: &T,
122 max: usize,
123) -> Result<Vec<u8>, SchemaContractError> {
124 let framed = (header, value);
125 let len = ic_wire::native::encoded_len(&framed, crate::wire::sizing_limits())
126 .map_err(|_| SchemaContractError::Encode)?;
127 ensure_input_bound(len, max)?;
128 ic_wire::native::encode(&framed, crate::wire::limits(max))
129 .map_err(|_| SchemaContractError::Encode)
130}
131
132fn decode<T: DeserializeOwned>(
133 header: [u8; 5],
134 bytes: &[u8],
135 max: usize,
136) -> Result<T, SchemaContractError> {
137 ensure_input_bound(bytes.len(), max)?;
138 if !bytes.starts_with(&header) {
139 return Err(SchemaContractError::Decode);
140 }
141 let (_, value): ([u8; 5], T) = ic_wire::native::decode(bytes, crate::wire::limits(max))
142 .map_err(|_| SchemaContractError::Decode)?;
143 Ok(value)
144}
145
146const fn ensure_input_bound(len: usize, max: usize) -> Result<(), SchemaContractError> {
147 if len > max {
148 Err(SchemaContractError::EncodedTooLarge { len, max })
149 } else {
150 Ok(())
151 }
152}
153
154fn ensure_canonical(bytes: &[u8], canonical: Vec<u8>) -> Result<(), SchemaContractError> {
155 if canonical == bytes {
156 Ok(())
157 } else {
158 Err(SchemaContractError::NonCanonical)
159 }
160}
161
162pub(crate) fn deserialize_list<'de, D, T, const N: usize>(
165 deserializer: D,
166) -> Result<Vec<T>, D::Error>
167where
168 D: Deserializer<'de>,
169 T: Deserialize<'de>,
170{
171 ic_wire::List::<T, N>::deserialize(deserializer).map(ic_wire::List::into_vec)
172}
173
174pub(crate) fn deserialize_literal_text<'de, D: Deserializer<'de>>(
175 deserializer: D,
176) -> Result<String, D::Error> {
177 if deserializer.is_human_readable() {
178 let value = String::deserialize(deserializer)?;
179 if value.len() > MAX_PROPOSAL_LITERAL_BYTES {
180 return Err(serde::de::Error::custom("proposal text limit"));
181 }
182 Ok(value)
183 } else {
184 ic_wire::Text::<MAX_PROPOSAL_LITERAL_BYTES>::deserialize(deserializer)
185 .map(ic_wire::Text::into_string)
186 }
187}
188
189pub(crate) fn deserialize_literal_blob<'de, D: Deserializer<'de>>(
190 deserializer: D,
191) -> Result<Blob, D::Error> {
192 if deserializer.is_human_readable() {
193 let value = Blob::deserialize(deserializer)?;
194 if value.len() > MAX_PROPOSAL_LITERAL_BYTES {
195 return Err(serde::de::Error::custom("proposal blob limit"));
196 }
197 Ok(value)
198 } else {
199 ic_wire::Bytes::<MAX_PROPOSAL_LITERAL_BYTES>::deserialize(deserializer)
200 .map(|bytes| Blob::from(bytes.into_vec()))
201 }
202}
203
204pub(crate) fn deserialize_key<'de, D, T, const N: usize>(d: D) -> Result<T, D::Error>
207where
208 D: Deserializer<'de>,
209 T: DeserializeOwned,
210{
211 if d.is_human_readable() {
212 return T::deserialize(d);
213 }
214 let text = ic_wire::Text::<N>::deserialize(d)?.into_string();
215 T::deserialize(serde::de::value::StringDeserializer::<D::Error>::new(text))
216}
217
218pub(crate) fn deserialize_optional_key<'de, D, T, const N: usize>(
219 d: D,
220) -> Result<Option<T>, D::Error>
221where
222 D: Deserializer<'de>,
223 T: DeserializeOwned,
224{
225 if d.is_human_readable() {
226 return Option::<T>::deserialize(d);
227 }
228 Option::<ic_wire::Text<N>>::deserialize(d)?
229 .map(|text| {
230 T::deserialize(serde::de::value::StringDeserializer::<D::Error>::new(
231 text.into_string(),
232 ))
233 })
234 .transpose()
235}
236
237pub(crate) fn deserialize_keys<'de, D, T, const N: usize>(d: D) -> Result<Vec<T>, D::Error>
238where
239 D: Deserializer<'de>,
240 T: DeserializeOwned,
241{
242 if d.is_human_readable() {
243 return deserialize_list::<D, T, N>(d);
244 }
245 let keys = ic_wire::List::<ic_wire::Text<{ crate::MAX_SOURCE_KEY_BYTES }>, N>::deserialize(d)?;
246 let mut values = Vec::new();
247 values
248 .try_reserve_exact(keys.as_slice().len())
249 .map_err(serde::de::Error::custom)?;
250 for key in keys.into_vec() {
251 values.push(T::deserialize(serde::de::value::StringDeserializer::<
252 D::Error,
253 >::new(key.into_string()))?);
254 }
255 Ok(values)
256}
257
258pub(crate) fn deserialize_literal_decimal<'de, D: Deserializer<'de>>(
261 deserializer: D,
262) -> Result<crate::Decimal, D::Error> {
263 const MAX_BYTES: usize = 1
264 + crate::decimal::DECIMAL_DIGIT_BUFFER_LEN
265 + 1
266 + crate::decimal::MAX_SUPPORTED_SCALE as usize;
267 let text = if deserializer.is_human_readable() {
268 let text = String::deserialize(deserializer)?;
269 if text.len() > MAX_BYTES {
270 return Err(serde::de::Error::custom("decimal text limit"));
271 }
272 text
273 } else {
274 ic_wire::Text::<MAX_BYTES>::deserialize(deserializer)?.into_string()
275 };
276 text.parse().map_err(serde::de::Error::custom)
277}
278
279#[cfg(test)]
280mod tests {
281 use super::*;
282
283 #[test]
284 fn artifacts_refuse_noncanonical_wire_and_truncation() {
285 let fragment = SchemaFragment::try_new(Vec::new(), Vec::new()).unwrap();
286 let canonical = encode_schema_fragment(&fragment).unwrap();
287 assert_eq!(&canonical[..5], b"ICWF\x01");
288 let mut alternate = canonical.clone();
289 alternate.splice(5..6, [0x80, 0]);
290 assert_eq!(
291 decode_schema_fragment(&alternate),
292 Err(SchemaContractError::Decode)
293 );
294 for end in 0..canonical.len() {
295 assert!(decode_schema_fragment(&canonical[..end]).is_err());
296 }
297 }
298
299 #[test]
300 fn artifact_decimal_admission_is_bounded_before_numeric_parsing() {
301 let wide = crate::wire::encode_one((4_u32, "0".repeat(1_024))).unwrap();
304 assert!(crate::wire::decode_one::<crate::ScalarLiteral>(&wide).is_err());
305 let invalid = crate::ScalarLiteral::Decimal(crate::Decimal::new_unchecked(
306 1,
307 crate::decimal::MAX_SUPPORTED_SCALE + 1,
308 ));
309 assert_eq!(invalid.validate(), Err(SchemaContractError::InvalidLiteral));
310 }
311
312 #[test]
313 fn artifact_relation_keys_bound_each_name_and_the_complete_list() {
314 for count in [crate::MAX_FRAGMENT_FIELDS, crate::MAX_FRAGMENT_FIELDS + 1] {
315 let bytes = crate::wire::encode_one((0_u32, vec!["id"; count])).unwrap();
316 assert_eq!(
317 crate::wire::decode_one::<crate::RelationSourceFragment>(&bytes).is_ok(),
318 count == crate::MAX_FRAGMENT_FIELDS
319 );
320 }
321 let bytes =
322 crate::wire::encode_one((0_u32, vec!["x".repeat(crate::MAX_SOURCE_KEY_BYTES + 1)]))
323 .unwrap();
324 assert!(crate::wire::decode_one::<crate::RelationSourceFragment>(&bytes).is_err());
325 }
326
327 #[test]
328 fn artifacts_admit_only_bounded_collections_and_valid_scalar_tags() {
329 let excessive = vec![(); crate::MAX_FRAGMENT_ENTITIES + 1];
330 let bytes =
331 crate::wire::encode_one((FRAGMENT_HEADER, (&excessive, Vec::<u8>::new()))).unwrap();
332 assert_eq!(
333 decode_schema_fragment(&bytes),
334 Err(SchemaContractError::Decode)
335 );
336 let invalid = crate::wire::encode_one(u32::MAX).unwrap();
337 assert!(
338 ic_wire::native::decode::<crate::ScalarLiteral>(&invalid, crate::wire::limits(64))
339 .is_err()
340 );
341 let wide = crate::wire::encode_one("x".repeat(crate::MAX_SOURCE_KEY_BYTES + 1)).unwrap();
342 assert!(crate::wire::decode_one::<crate::SchemaName>(&wide).is_err());
343 }
344}