identity_iota_core/state_metadata/
document.rs1use identity_core::convert::FromJson;
5use identity_core::convert::ToJson;
6use identity_did::CoreDID;
7use identity_document::document::CoreDocument;
8use once_cell::sync::Lazy;
9use serde::Deserialize;
10use serde::Serialize;
11
12use crate::error::Result;
13use crate::Error;
14use crate::IotaDID;
15use crate::IotaDocument;
16use crate::IotaDocumentMetadata;
17
18use super::StateMetadataEncoding;
19use super::StateMetadataVersion;
20
21pub(crate) static PLACEHOLDER_DID: Lazy<CoreDID> = Lazy::new(|| CoreDID::parse("did:0:0").unwrap());
22
23const DID_MARKER: &[u8] = b"DID";
25
26#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
30pub struct StateMetadataDocument {
31 #[serde(rename = "doc")]
32 pub(crate) document: CoreDocument,
33 #[serde(rename = "meta")]
34 pub(crate) metadata: IotaDocumentMetadata,
35}
36
37impl StateMetadataDocument {
38 pub fn into_iota_document(self, original_did: &IotaDID) -> Result<IotaDocument> {
40 let Self { document, metadata } = self;
41
42 let replace_placeholder_with_method_check = |did: CoreDID| -> Result<CoreDID> {
45 if did == PLACEHOLDER_DID.as_ref() {
46 Ok(CoreDID::from(original_did.clone()))
47 } else {
48 IotaDID::check_validity(&did).map_err(Error::DIDSyntaxError)?;
49 Ok(did)
50 }
51 };
52 let [id_update, controller_update] = [replace_placeholder_with_method_check; 2];
53
54 let replace_placeholder = |did: CoreDID| -> Result<CoreDID> {
56 if did == PLACEHOLDER_DID.as_ref() {
57 Ok(CoreDID::from(original_did.clone()))
58 } else {
59 Ok(did)
60 }
61 };
62 let [methods_update, service_update] = [replace_placeholder; 2];
63
64 let document = document.try_map(
65 id_update,
66 controller_update,
67 methods_update,
68 service_update,
69 crate::error::Error::InvalidDoc,
70 )?;
71
72 Ok(IotaDocument { document, metadata })
73 }
74
75 pub fn into_iota_document_with_placeholders(self) -> IotaDocument {
77 IotaDocument {
78 document: self.document,
79 metadata: self.metadata,
80 }
81 }
82
83 pub fn pack(self, encoding: StateMetadataEncoding) -> Result<Vec<u8>> {
86 let encoded_message_data: Vec<u8> = match encoding {
87 StateMetadataEncoding::Json => self
88 .to_json_vec()
89 .map_err(|err| Error::SerializationError("failed to serialize document to JSON", Some(err)))?,
90 };
91
92 let encoded_message_data_with_flags =
94 add_flags_to_message(encoded_message_data, StateMetadataVersion::CURRENT, encoding)?;
95 Ok(encoded_message_data_with_flags)
96 }
97
98 pub fn unpack(data: &[u8]) -> Result<Self> {
100 let marker: &[u8] = data
102 .get(0..=2)
103 .ok_or(identity_document::Error::InvalidDocument(
104 "state metadata decoding: expected DID marker at offset [0..=2]",
105 None,
106 ))
107 .map_err(Error::InvalidDoc)?;
108 if marker != DID_MARKER {
109 return Err(Error::InvalidStateMetadata("missing `DID` marker"));
110 }
111
112 let version: StateMetadataVersion = StateMetadataVersion::try_from(
114 *data
115 .get(3)
116 .ok_or(identity_document::Error::InvalidDocument(
117 "state metadata decoding: expected version at offset 3",
118 None,
119 ))
120 .map_err(Error::InvalidDoc)?,
121 )?;
122 if version != StateMetadataVersion::V1 {
123 return Err(Error::InvalidStateMetadata("unsupported version"));
124 }
125
126 let encoding: StateMetadataEncoding = StateMetadataEncoding::try_from(
128 *data
129 .get(4)
130 .ok_or(identity_document::Error::InvalidDocument(
131 "state metadata decoding: expected encoding at offset 4",
132 None,
133 ))
134 .map_err(Error::InvalidDoc)?,
135 )?;
136
137 let data_len_packed: [u8; 2] = data
138 .get(5..=6)
139 .ok_or(identity_document::Error::InvalidDocument(
140 "state metadata decoding: expected data length at offset [5..=6]",
141 None,
142 ))
143 .map_err(Error::InvalidDoc)?
144 .try_into()
145 .map_err(|_| {
146 identity_document::Error::InvalidDocument("state metadata decoding: data length conversion error", None)
147 })
148 .map_err(Error::InvalidDoc)?;
149 let data_len: u16 = u16::from_le_bytes(data_len_packed);
150
151 let data: &[u8] = data
152 .get(7..(7 + data_len as usize))
153 .ok_or(identity_document::Error::InvalidDocument(
154 "state metadata decoding: encoded document shorter than length prefix",
155 None,
156 ))
157 .map_err(Error::InvalidDoc)?;
158
159 match encoding {
160 StateMetadataEncoding::Json => StateMetadataDocument::from_json_slice(data).map_err(|err| {
161 Error::SerializationError(
162 "state metadata decoding: failed to deserialize JSON document",
163 Some(err),
164 )
165 }),
166 }
167 }
168}
169
170fn add_flags_to_message(
173 mut data: Vec<u8>,
174 version: StateMetadataVersion,
175 encoding: StateMetadataEncoding,
176) -> Result<Vec<u8>> {
177 let data_len: u16 =
178 u16::try_from(data.len()).map_err(|_| Error::SerializationError("failed to convert usize to u16", None))?;
179 let data_len_packed: [u8; 2] = data_len.to_le_bytes();
180 let mut buffer: Vec<u8> = Vec::with_capacity(DID_MARKER.len() + 1 + 1 + data_len_packed.len() + data_len as usize);
181 buffer.extend_from_slice(DID_MARKER);
182 buffer.push(version as u8);
183 buffer.push(encoding as u8);
184 buffer.extend_from_slice(&data_len_packed);
185 buffer.append(&mut data);
186 Ok(buffer)
187}
188
189impl From<IotaDocument> for StateMetadataDocument {
190 fn from(document: IotaDocument) -> Self {
193 let id: IotaDID = document.id().clone();
194 let IotaDocument { document, metadata } = document;
195
196 let replace_id_with_placeholder = |did: CoreDID| -> CoreDID {
198 if &did == id.as_ref() {
199 PLACEHOLDER_DID.clone()
200 } else {
201 did
202 }
203 };
204
205 let [id_update, controller_update, methods_update, service_update] = [replace_id_with_placeholder; 4];
206
207 StateMetadataDocument {
208 document: document.map_unchecked(id_update, controller_update, methods_update, service_update),
209 metadata,
210 }
211 }
212}
213
214#[cfg(test)]
215mod tests {
216 use identity_core::common::Object;
217 use identity_core::common::OneOrSet;
218 use identity_core::common::Url;
219 use identity_did::CoreDID;
220 use identity_did::DID;
221 use identity_verification::MethodScope;
222
223 use crate::state_metadata::document::DID_MARKER;
224 use crate::state_metadata::PLACEHOLDER_DID;
225 use crate::test_utils::generate_method;
226 use crate::IotaDID;
227 use crate::IotaDocument;
228 use crate::StateMetadataDocument;
229 use crate::StateMetadataEncoding;
230 use crate::StateMetadataVersion;
231 use identity_document::service::Service;
232
233 struct TestSetup {
234 document: IotaDocument,
235 did_self: IotaDID,
236 did_foreign: IotaDID,
237 }
238
239 fn test_document() -> TestSetup {
240 let did_self =
241 IotaDID::parse("did:iota:0x8036235b6b5939435a45d68bcea7890eef399209a669c8c263fac7f5089b2ec6").unwrap();
242 let did_foreign =
243 IotaDID::parse("did:iota:0x71b709dff439f1ac9dd2b9c2e28db0807156b378e13bfa3605ce665aa0d0fdca").unwrap();
244
245 let mut document: IotaDocument = IotaDocument::new_with_id(did_self.clone());
246 document
247 .insert_method(generate_method(&did_self, "did-self"), MethodScope::VerificationMethod)
248 .unwrap();
249
250 document
251 .insert_method(
252 generate_method(&did_foreign, "did-foreign"),
253 MethodScope::authentication(),
254 )
255 .unwrap();
256
257 assert!(document
258 .insert_service(
259 Service::builder(Object::new())
260 .id(document.id().to_url().join("#my-service").unwrap())
261 .type_("RevocationList2022")
262 .service_endpoint(Url::parse("https://example.com/xyzabc").unwrap())
263 .build()
264 .unwrap()
265 )
266 .is_ok());
267
268 assert!(document
269 .insert_service(
270 Service::builder(Object::new())
271 .id(did_foreign.to_url().join("#my-foreign-service").unwrap())
272 .type_("RevocationList2022")
273 .service_endpoint(Url::parse("https://example.com/0xf4c42e9da").unwrap())
274 .build()
275 .unwrap()
276 )
277 .is_ok());
278
279 document
280 .also_known_as_mut()
281 .append(Url::parse("did:example:abc").unwrap());
282 document
283 .also_known_as_mut()
284 .append(Url::parse("did:example:xyz").unwrap());
285
286 let controllers = OneOrSet::try_from(vec![did_foreign.clone().into(), did_self.clone().into()]).unwrap();
287 *document.core_document_mut().controller_mut() = Some(controllers);
288
289 TestSetup {
290 document,
291 did_self,
292 did_foreign,
293 }
294 }
295
296 #[test]
297 fn test_transformation_roundtrip() {
298 let TestSetup {
299 document,
300 did_self,
301 did_foreign,
302 } = test_document();
303
304 let state_metadata_doc: StateMetadataDocument = StateMetadataDocument::from(document.clone());
305
306 assert_eq!(
307 state_metadata_doc
308 .document
309 .resolve_method("#did-self", None)
310 .unwrap()
311 .id()
312 .did(),
313 <CoreDID as AsRef<CoreDID>>::as_ref(PLACEHOLDER_DID.as_ref())
314 );
315
316 assert_eq!(
317 state_metadata_doc
318 .document
319 .resolve_method("#did-foreign", None)
320 .unwrap()
321 .id()
322 .did(),
323 did_foreign.as_ref()
324 );
325
326 assert_eq!(
327 state_metadata_doc
328 .document
329 .service()
330 .iter()
331 .find(|service| service.id().fragment().unwrap() == "my-foreign-service")
332 .unwrap()
333 .id()
334 .did(),
335 did_foreign.as_ref()
336 );
337
338 assert_eq!(
339 state_metadata_doc
340 .document
341 .service()
342 .iter()
343 .find(|service| service.id().fragment().unwrap() == "my-service")
344 .unwrap()
345 .id()
346 .did(),
347 <CoreDID as AsRef<CoreDID>>::as_ref(PLACEHOLDER_DID.as_ref())
348 );
349
350 let controllers = state_metadata_doc.document.controller().unwrap();
351 assert_eq!(controllers.get(0).unwrap(), did_foreign.as_ref());
352 assert_eq!(
353 controllers.get(1).unwrap(),
354 <CoreDID as AsRef<CoreDID>>::as_ref(PLACEHOLDER_DID.as_ref())
355 );
356
357 let iota_document = state_metadata_doc.into_iota_document(&did_self).unwrap();
358 assert_eq!(iota_document, document);
359 }
360
361 #[test]
362 fn test_packing_roundtrip() {
363 let TestSetup { document, .. } = test_document();
364
365 let state_metadata_doc: StateMetadataDocument = StateMetadataDocument::from(document);
366 let packed_bytes: Vec<u8> = state_metadata_doc.clone().pack(StateMetadataEncoding::Json).unwrap();
367
368 let unpacked_doc = StateMetadataDocument::unpack(&packed_bytes).unwrap();
369 assert_eq!(state_metadata_doc.metadata.created, unpacked_doc.metadata.created);
370 assert_eq!(state_metadata_doc.metadata.updated, unpacked_doc.metadata.updated);
371 assert_eq!(
372 state_metadata_doc.metadata.deactivated,
373 unpacked_doc.metadata.deactivated
374 );
375
376 assert_eq!(state_metadata_doc.document.id(), unpacked_doc.document.id());
377 assert_eq!(
378 state_metadata_doc.document.also_known_as(),
379 unpacked_doc.document.also_known_as()
380 );
381 assert_eq!(
382 state_metadata_doc.document.verification_method(),
383 unpacked_doc.document.verification_method()
384 );
385 assert_eq!(
386 state_metadata_doc.document.authentication(),
387 unpacked_doc.document.authentication()
388 );
389 assert_eq!(
390 state_metadata_doc.document.assertion_method(),
391 unpacked_doc.document.assertion_method()
392 );
393 assert_eq!(
394 state_metadata_doc.document.key_agreement(),
395 unpacked_doc.document.key_agreement()
396 );
397 assert_eq!(
398 state_metadata_doc.document.capability_delegation(),
399 unpacked_doc.document.capability_delegation()
400 );
401 assert_eq!(state_metadata_doc.document.service(), unpacked_doc.document.service());
402 assert_eq!(
403 state_metadata_doc.document.properties(),
404 unpacked_doc.document.properties()
405 );
406 }
407
408 #[test]
409 fn test_pack_format() {
410 let TestSetup { document, .. } = test_document();
412 let state_metadata_doc: StateMetadataDocument = StateMetadataDocument::from(document);
413 let packed: Vec<u8> = state_metadata_doc.clone().pack(StateMetadataEncoding::Json).unwrap();
414 let expected_payload: String = format!(
415 "{{\"doc\":{},\"meta\":{}}}",
416 state_metadata_doc.document, state_metadata_doc.metadata
417 );
418
419 assert_eq!(&packed[0..3], DID_MARKER);
421 assert_eq!(packed[3], StateMetadataVersion::V1 as u8);
423 assert_eq!(packed[4], StateMetadataEncoding::Json as u8);
425 assert_eq!(&packed[5..=6], (expected_payload.len() as u16).to_le_bytes().as_ref());
427 assert_eq!(&packed[7..], expected_payload.as_bytes());
429 }
430
431 #[test]
432 fn test_no_controller() {
433 let TestSetup {
434 mut document, did_self, ..
435 } = test_document();
436 *document.core_document_mut().controller_mut() = None;
437 let state_metadata_doc: StateMetadataDocument = StateMetadataDocument::from(document);
438 let packed: Vec<u8> = state_metadata_doc.clone().pack(StateMetadataEncoding::Json).unwrap();
439 let expected_payload: String = format!(
440 "{{\"doc\":{},\"meta\":{}}}",
441 state_metadata_doc.document, state_metadata_doc.metadata
442 );
443 assert_eq!(&packed[7..], expected_payload.as_bytes());
444 let unpacked = StateMetadataDocument::unpack(&packed).unwrap();
445 assert_eq!(
446 unpacked
447 .into_iota_document(&did_self)
448 .unwrap()
449 .controller()
450 .collect::<Vec<_>>()
451 .len(),
452 0
453 );
454 }
455
456 #[test]
457 fn test_unpack_length_prefix() {
458 let TestSetup { document, .. } = test_document();
460 let state_metadata_doc: StateMetadataDocument = StateMetadataDocument::from(document);
461 let mut packed: Vec<u8> = state_metadata_doc.clone().pack(StateMetadataEncoding::Json).unwrap();
462 let original_length = u16::from_le_bytes(packed[5..=6].try_into().unwrap());
463
464 let longer = (original_length + 1_u16).to_le_bytes();
466 packed[5] = longer[0];
467 packed[6] = longer[1];
468 assert!(StateMetadataDocument::unpack(&packed).is_err());
469
470 let max: [u8; 2] = u16::MAX.to_le_bytes();
472 packed[5] = max[0];
473 packed[6] = max[1];
474 assert!(StateMetadataDocument::unpack(&packed).is_err());
475
476 let shorter = (original_length - 1_u16).to_le_bytes();
478 packed[5] = shorter[0];
479 packed[6] = shorter[1];
480 assert!(StateMetadataDocument::unpack(&packed).is_err());
481
482 let min = 0_u16.to_le_bytes();
484 packed[5] = min[0];
485 packed[6] = min[1];
486 assert!(StateMetadataDocument::unpack(&packed).is_err());
487
488 let original = original_length.to_le_bytes();
490 packed[5] = original[0];
491 packed[6] = original[1];
492
493 let unpacked_doc = StateMetadataDocument::unpack(&packed).unwrap();
494 assert_eq!(state_metadata_doc.metadata.created, unpacked_doc.metadata.created);
496 assert_eq!(state_metadata_doc.metadata.updated, unpacked_doc.metadata.updated);
497 assert_eq!(
498 state_metadata_doc.metadata.deactivated,
499 unpacked_doc.metadata.deactivated
500 );
501
502 assert_eq!(state_metadata_doc.document.id(), unpacked_doc.document.id());
503 assert_eq!(
504 state_metadata_doc.document.also_known_as(),
505 unpacked_doc.document.also_known_as()
506 );
507 assert_eq!(
508 state_metadata_doc.document.verification_method(),
509 unpacked_doc.document.verification_method()
510 );
511 assert_eq!(
512 state_metadata_doc.document.authentication(),
513 unpacked_doc.document.authentication()
514 );
515 assert_eq!(
516 state_metadata_doc.document.assertion_method(),
517 unpacked_doc.document.assertion_method()
518 );
519 assert_eq!(
520 state_metadata_doc.document.key_agreement(),
521 unpacked_doc.document.key_agreement()
522 );
523 assert_eq!(
524 state_metadata_doc.document.capability_delegation(),
525 unpacked_doc.document.capability_delegation()
526 );
527 assert_eq!(state_metadata_doc.document.service(), unpacked_doc.document.service());
528 assert_eq!(
529 state_metadata_doc.document.properties(),
530 unpacked_doc.document.properties()
531 );
532 }
533}