wot_td/protocol/
coap.rs

1//! CoAP Binding Template
2
3use crate::extend::ExtendableThing;
4use serde::{Deserialize, Serialize};
5use serde_repr::{Deserialize_repr, Serialize_repr};
6use serde_with::{serde_as, skip_serializing_none};
7
8/// CoAP request method
9#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, Hash)]
10#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
11pub enum Method {
12    Get,
13    Put,
14    Post,
15    Delete,
16    Patch,
17    Fetch,
18    #[serde(rename = "iPATCH")]
19    Ipatch,
20}
21
22/// CoAP Allowed block size
23#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Deserialize_repr, Serialize_repr)]
24#[repr(u16)]
25pub enum BlockSize {
26    Size16 = 16,
27    Size32 = 32,
28    Size64 = 64,
29    Size128 = 128,
30    Size256 = 256,
31    Size512 = 512,
32    Size1024 = 1024,
33}
34
35/// CoAP BlockWise Transfer Parameters
36///
37/// They may apply to Block-Wise Transfers [RFC7959] or
38/// Block-Wise Transfer Options Supporting Robust Transmission [RFC9177].
39///
40/// [RFC7959]: https://www.rfc-editor.org/rfc/rfc7959.html
41/// [RFC9177]: https://www.rfc-editor.org/rfc/rfc9177.html
42#[serde_as]
43#[skip_serializing_none]
44#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, Hash, Default)]
45pub struct BlockWiseTransferParameters {
46    #[serde(rename = "cov:block2Size")]
47    pub block2_size: Option<BlockSize>,
48    #[serde(rename = "cov:block1Size")]
49    pub block1_size: Option<BlockSize>,
50}
51
52/// CoAP Protocol Form fields
53#[serde_as]
54#[skip_serializing_none]
55#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, Hash, Default)]
56pub struct Form {
57    #[serde(rename = "cov:method")]
58    pub method: Option<Method>,
59    #[serde(rename = "cov:blockwise")]
60    pub blockwise: Option<BlockWiseTransferParameters>,
61    #[serde(rename = "cov:qblockwise")]
62    pub qblockwise: Option<BlockWiseTransferParameters>,
63    #[serde(rename = "cov:hopLimit")]
64    pub hop_limit: Option<u8>,
65    #[serde(rename = "cov:accept")]
66    pub accept: Option<u16>,
67    #[serde(rename = "cov:contentFormat")]
68    pub content_format: Option<u16>,
69}
70
71/// CoAP Protocol ExpectedResponse fields
72#[serde_as]
73#[skip_serializing_none]
74#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, Hash, Default)]
75pub struct ExpectedResponse {
76    #[serde(rename = "cov:contentFormat")]
77    pub content_format: Option<u16>,
78}
79
80/// Extension for the CoAP protocol
81#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash, Default)]
82pub struct CoapProtocol {}
83
84impl ExtendableThing for CoapProtocol {
85    type InteractionAffordance = ();
86    type PropertyAffordance = ();
87    type ActionAffordance = ();
88    type EventAffordance = ();
89    type Form = Form;
90    type ExpectedResponse = ExpectedResponse;
91    type DataSchema = ();
92    type ObjectSchema = ();
93    type ArraySchema = ();
94}
95
96#[cfg(test)]
97mod test {
98    use alloc::vec;
99
100    use super::{BlockSize, CoapProtocol};
101    use crate::thing::{ExpectedResponse, Form};
102    fn deserialize_form(s: &str, r: Form<CoapProtocol>) {
103        let f: crate::thing::Form<CoapProtocol> = serde_json::from_str(s).unwrap();
104
105        assert_eq!(f, r);
106    }
107
108    #[test]
109    fn deserialize_observe() {
110        let form = r#"
111            {
112                "cov:method": "GET",
113                "href": "coap://[2001:DB8::1]/status",
114                "contentType": "text/plain;charset=utf-8",
115                "subprotocol": "cov:observe",
116                "op": ["observeproperty"]
117            }
118        "#;
119        let expected = Form {
120            op: crate::thing::DefaultedFormOperations::Custom(vec![
121                crate::thing::FormOperation::ObserveProperty,
122            ]),
123            href: "coap://[2001:DB8::1]/status".into(),
124            content_type: Some("text/plain;charset=utf-8".into()),
125            subprotocol: Some("cov:observe".into()),
126            other: super::Form {
127                method: Some(super::Method::Get),
128                ..Default::default()
129            },
130            ..Default::default()
131        };
132
133        deserialize_form(form, expected);
134    }
135
136    #[test]
137    fn deserialize_blockwise() {
138        let form = r#"
139            {
140                "href": "coap://[2001:DB8::1]/status",
141                "contentType": "text/plain;charset=utf-8",
142                "cov:blockwise": { }
143            }
144        "#;
145        let expected = Form {
146            href: "coap://[2001:DB8::1]/status".into(),
147            content_type: Some("text/plain;charset=utf-8".into()),
148            other: super::Form {
149                blockwise: Some(super::BlockWiseTransferParameters::default()),
150                ..Default::default()
151            },
152            ..Default::default()
153        };
154
155        deserialize_form(form, expected);
156    }
157
158    #[test]
159    fn deserialize_qblockwise_params() {
160        let form = r#"
161            {
162                "href": "coap://[2001:DB8::1]/status",
163                "contentType": "text/plain;charset=utf-8",
164                "cov:qblockwise": {
165                    "cov:block2Size": 64
166                }
167            }
168        "#;
169        let expected = Form {
170            href: "coap://[2001:DB8::1]/status".into(),
171            content_type: Some("text/plain;charset=utf-8".into()),
172            other: super::Form {
173                qblockwise: Some(super::BlockWiseTransferParameters {
174                    block2_size: Some(BlockSize::Size64),
175                    ..Default::default()
176                }),
177                ..Default::default()
178            },
179            ..Default::default()
180        };
181
182        deserialize_form(form, expected);
183    }
184
185    #[test]
186    fn deserialize_hop_limit() {
187        let form = r#"
188            {
189                "href": "coap://[2001:DB8::1]/status",
190                "contentType": "text/plain;charset=utf-8",
191                "cov:hopLimit": 5
192            }
193        "#;
194        let expected = Form {
195            href: "coap://[2001:DB8::1]/status".into(),
196            content_type: Some("text/plain;charset=utf-8".into()),
197            other: super::Form {
198                hop_limit: Some(5),
199                ..Default::default()
200            },
201            ..Default::default()
202        };
203
204        deserialize_form(form, expected);
205    }
206
207    #[test]
208    fn deserialize_content_format() {
209        let form = r#"
210            {
211                "href": "coap://[2001:DB8::1]/status",
212                "contentType": "application/cbor",
213                "cov:contentFormat": 60,
214                "cov:accept": 60,
215                "response": {
216                    "contentType": "application/cbor",
217                    "cov:contentFormat": 60
218                }
219            }
220        "#;
221        let expected = Form {
222            href: "coap://[2001:DB8::1]/status".into(),
223            content_type: Some("application/cbor".into()),
224            other: super::Form {
225                content_format: Some(60),
226                accept: Some(60),
227                ..Default::default()
228            },
229            response: Some(ExpectedResponse {
230                content_type: "application/cbor".into(),
231                other: super::ExpectedResponse {
232                    content_format: Some(60),
233                },
234            }),
235            ..Default::default()
236        };
237
238        deserialize_form(form, expected);
239    }
240}