1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
// Copyright 2020-2021 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

//! Module describing the tagged data payload.

use alloc::vec::Vec;
use core::ops::RangeInclusive;

use packable::{
    bounded::{BoundedU32, BoundedU8},
    prefix::BoxedSlicePrefix,
    Packable,
};

use crate::{Block, Error};

pub(crate) type TagLength =
    BoundedU8<{ *TaggedDataPayload::TAG_LENGTH_RANGE.start() }, { *TaggedDataPayload::TAG_LENGTH_RANGE.end() }>;
pub(crate) type TaggedDataLength =
    BoundedU32<{ *TaggedDataPayload::DATA_LENGTH_RANGE.start() }, { *TaggedDataPayload::DATA_LENGTH_RANGE.end() }>;

/// A payload which holds a tag and associated data.
#[derive(Clone, Eq, PartialEq, Packable)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[packable(unpack_error = Error)]
pub struct TaggedDataPayload {
    #[packable(unpack_error_with = |err| Error::InvalidTagLength(err.into_prefix_err().into()))]
    tag: BoxedSlicePrefix<u8, TagLength>,
    #[packable(unpack_error_with = |err| Error::InvalidTaggedDataLength(err.into_prefix_err().into()))]
    data: BoxedSlicePrefix<u8, TaggedDataLength>,
}

impl TaggedDataPayload {
    /// The payload kind of a [`TaggedDataPayload`].
    pub const KIND: u32 = 5;
    /// Valid lengths for the tag.
    pub const TAG_LENGTH_RANGE: RangeInclusive<u8> = 0..=64;
    /// Valid lengths for the data.
    pub const DATA_LENGTH_RANGE: RangeInclusive<u32> = 0..=Block::LENGTH_MAX as u32;

    /// Creates a new [`TaggedDataPayload`].
    pub fn new(tag: Vec<u8>, data: Vec<u8>) -> Result<Self, Error> {
        Ok(Self {
            tag: tag.into_boxed_slice().try_into().map_err(Error::InvalidTagLength)?,
            data: data
                .into_boxed_slice()
                .try_into()
                .map_err(Error::InvalidTaggedDataLength)?,
        })
    }

    /// Returns the tag of a [`TaggedDataPayload`].
    pub fn tag(&self) -> &[u8] {
        &self.tag
    }

    /// Returns the data of a [`TaggedDataPayload`].
    pub fn data(&self) -> &[u8] {
        &self.data
    }
}

impl core::fmt::Debug for TaggedDataPayload {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        f.debug_struct("TaggedDataPayload")
            .field("tag", &prefix_hex::encode(self.tag()))
            .field("data", &prefix_hex::encode(self.data()))
            .finish()
    }
}

#[cfg(feature = "dto")]
#[allow(missing_docs)]
pub mod dto {
    use serde::{Deserialize, Serialize};

    use super::*;
    use crate::error::dto::DtoError;

    /// The payload type to define a tagged data payload.
    #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
    pub struct TaggedDataPayloadDto {
        #[serde(rename = "type")]
        pub kind: u32,
        #[serde(skip_serializing_if = "String::is_empty", default)]
        pub tag: String,
        #[serde(skip_serializing_if = "String::is_empty", default)]
        pub data: String,
    }

    impl From<&TaggedDataPayload> for TaggedDataPayloadDto {
        fn from(value: &TaggedDataPayload) -> Self {
            TaggedDataPayloadDto {
                kind: TaggedDataPayload::KIND,
                tag: prefix_hex::encode(value.tag()),
                data: prefix_hex::encode(value.data()),
            }
        }
    }

    impl TryFrom<&TaggedDataPayloadDto> for TaggedDataPayload {
        type Error = DtoError;

        fn try_from(value: &TaggedDataPayloadDto) -> Result<Self, Self::Error> {
            Ok(TaggedDataPayload::new(
                if !value.tag.is_empty() {
                    prefix_hex::decode(&value.tag).map_err(|_| DtoError::InvalidField("tag"))?
                } else {
                    Vec::new()
                },
                if !value.data.is_empty() {
                    prefix_hex::decode(&value.data).map_err(|_| DtoError::InvalidField("data"))?
                } else {
                    Vec::new()
                },
            )?)
        }
    }
}