1// Copyright (c) 2022 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
2//
3// This Source Code Form is subject to the terms of the Mozilla Public
4// License, v. 2.0. If a copy of the MPL was not distributed with this
5// file, You can obtain one at http://mozilla.org/MPL/2.0/.
6
7use crate::ns;
8use crate::util::error::Error;
9use crate::util::helpers::PlainText;
10use crate::Element;
11use std::convert::TryFrom;
12
13generate_attribute!(
14 /// Events for real-time text.
15 Event, "event", {
16 /// Begin a new real-time message.
17 New => "new",
18
19 /// Re-initialize the real-time message.
20 Reset => "reset",
21
22 /// Modify existing real-time message.
23 Edit => "edit",
24
25 /// Signals activation of real-time text.
26 Init => "init",
27
28 /// Signals deactivation of real-time text.
29 Cancel => "cancel",
30 }, Default = Edit
31);
32
33generate_element!(
34 /// Supports the transmission of text, including key presses, and text block inserts.
35 Insert, "t", RTT,
36 attributes: [
37 /// Position in the message to start inserting from. If None, this means to start from the
38 /// end of the message.
39 pos: Option<u32> = "p",
40 ],
41 text: (
42 /// Text to insert.
43 text: PlainText<Option<String>>
44 )
45);
46
47impl TryFrom<Action> for Insert {
48 type Error = Error;
49
50 fn try_from(action: Action) -> Result<Insert, Error> {
51 match action {
52 Action::Insert(insert) => Ok(insert),
53 _ => Err(Error::ParseError("This is not an insert action.")),
54 }
55 }
56}
57
58// TODO: add a way in the macro to set a default value.
59/*
60generate_element!(
61 Erase, "e", RTT,
62 attributes: [
63 pos: Option<u32> = "p",
64 num: Default<u32> = "n",
65 ]
66);
67*/
68
69/// Supports the behavior of backspace key presses. Text is removed towards beginning of the
70/// message. This element is also used for all delete operations, including the backspace key, the
71/// delete key, and text block deletes.
72#[derive(Debug, Clone, PartialEq)]
73pub struct Erase {
74 /// Position in the message to start erasing from. If None, this means to start from the end
75 /// of the message.
76 pub pos: Option<u32>,
77
78 /// Amount of characters to erase, to the left.
79 pub num: u32,
80}
81
82impl TryFrom<Element> for Erase {
83 type Error = Error;
84 fn try_from(elem: Element) -> Result<Erase, Error> {
85 check_self!(elem, "e", RTT);
86 check_no_unknown_attributes!(elem, "e", ["p", "n"]);
87 let pos = get_attr!(elem, "p", Option);
88 let num = get_attr!(elem, "n", Option).unwrap_or(1);
89 check_no_children!(elem, "e");
90 Ok(Erase { pos, num })
91 }
92}
93
94impl From<Erase> for Element {
95 fn from(elem: Erase) -> Element {
96 Element::builder("e", ns::RTT)
97 .attr("p", elem.pos)
98 .attr("n", elem.num)
99 .build()
100 }
101}
102
103impl TryFrom<Action> for Erase {
104 type Error = Error;
105
106 fn try_from(action: Action) -> Result<Erase, Error> {
107 match action {
108 Action::Erase(erase) => Ok(erase),
109 _ => Err(Error::ParseError("This is not an erase action.")),
110 }
111 }
112}
113
114generate_element!(
115 /// Allow for the transmission of intervals, between real-time text actions, to recreate the
116 /// pauses between key presses.
117 Wait, "w", RTT,
118
119 attributes: [
120 /// Amount of milliseconds to wait before the next action.
121 time: Required<u32> = "n",
122 ]
123);
124
125impl TryFrom<Action> for Wait {
126 type Error = Error;
127
128 fn try_from(action: Action) -> Result<Wait, Error> {
129 match action {
130 Action::Wait(wait) => Ok(wait),
131 _ => Err(Error::ParseError("This is not a wait action.")),
132 }
133 }
134}
135
136/// Choice between the three possible actions.
137#[derive(Debug, Clone, PartialEq)]
138pub enum Action {
139 /// Insert text action.
140 Insert(Insert),
141
142 /// Erase text action.
143 Erase(Erase),
144
145 /// Wait action.
146 Wait(Wait),
147}
148
149impl TryFrom<Element> for Action {
150 type Error = Error;
151
152 fn try_from(elem: Element) -> Result<Action, Error> {
153 match elem.name() {
154 "t" => Insert::try_from(elem).map(|insert| Action::Insert(insert)),
155 "e" => Erase::try_from(elem).map(|erase| Action::Erase(erase)),
156 "w" => Wait::try_from(elem).map(|wait| Action::Wait(wait)),
157 _ => Err(Error::ParseError("This is not a rtt action element.")),
158 }
159 }
160}
161
162impl From<Action> for Element {
163 fn from(action: Action) -> Element {
164 match action {
165 Action::Insert(insert) => Element::from(insert),
166 Action::Erase(erase) => Element::from(erase),
167 Action::Wait(wait) => Element::from(wait),
168 }
169 }
170}
171
172// TODO: Allow a wildcard name to the macro, to simplify the following code:
173/*
174generate_element!(
175 Rtt, "rtt", RTT,
176 attributes: [
177 seq: Required<u32> = "seq",
178 event: Default<Event> = "event",
179 id: Option<String> = "id",
180 ],
181 children: [
182 actions: Vec<Action> = (*, RTT) => Action,
183 ]
184);
185*/
186
187/// Element transmitted at regular interval by the sender client while a message is being composed.
188#[derive(Debug, Clone, PartialEq)]
189pub struct Rtt {
190 /// Counter to maintain synchronisation of real-time text. Senders MUST increment this value
191 /// by 1 for each subsequent edit to the same real-time message, including when appending new
192 /// text. Receiving clients MUST monitor this 'seq' value as a lightweight verification on the
193 /// synchronization of real-time text messages. The bounds of 'seq' is 31-bits, the range of
194 /// positive values for a signed 32-bit integer.
195 pub seq: u32,
196
197 /// This attribute signals events for real-time text.
198 pub event: Event,
199
200 /// When editing a message using XEP-0308, this references the id of the message being edited.
201 pub id: Option<String>,
202
203 /// Vector of actions being transmitted by this element.
204 pub actions: Vec<Action>,
205}
206
207impl TryFrom<Element> for Rtt {
208 type Error = Error;
209 fn try_from(elem: Element) -> Result<Rtt, Error> {
210 check_self!(elem, "rtt", RTT);
211
212 check_no_unknown_attributes!(elem, "rtt", ["seq", "event", "id"]);
213 let seq = get_attr!(elem, "seq", Required);
214 let event = get_attr!(elem, "event", Default);
215 let id = get_attr!(elem, "id", Option);
216
217 let mut actions = Vec::new();
218 for child in elem.children() {
219 if child.ns() != ns::RTT {
220 return Err(Error::ParseError("Unknown child in rtt element."));
221 }
222 actions.push(Action::try_from(child.clone())?);
223 }
224
225 Ok(Rtt {
226 seq,
227 event,
228 id,
229 actions: actions,
230 })
231 }
232}
233
234impl From<Rtt> for Element {
235 fn from(elem: Rtt) -> Element {
236 Element::builder("rtt", ns::RTT)
237 .attr("seq", elem.seq)
238 .attr("event", elem.event)
239 .attr("id", elem.id)
240 .append_all(elem.actions)
241 .build()
242 }
243}
244
245#[cfg(test)]
246mod tests {
247 use super::*;
248 use std::convert::TryInto;
249
250 #[cfg(target_pointer_width = "32")]
251 #[test]
252 fn test_size() {
253 assert_size!(Event, 1);
254 assert_size!(Insert, 20);
255 assert_size!(Erase, 12);
256 assert_size!(Wait, 4);
257 assert_size!(Action, 24);
258 assert_size!(Rtt, 32);
259 }
260
261 #[cfg(target_pointer_width = "64")]
262 #[test]
263 fn test_size() {
264 assert_size!(Event, 1);
265 assert_size!(Insert, 32);
266 assert_size!(Erase, 12);
267 assert_size!(Wait, 4);
268 assert_size!(Action, 32);
269 assert_size!(Rtt, 56);
270 }
271
272 #[test]
273 fn simple() {
274 let elem: Element = "<rtt xmlns='urn:xmpp:rtt:0' seq='0'/>".parse().unwrap();
275 let rtt = Rtt::try_from(elem).unwrap();
276 assert_eq!(rtt.seq, 0);
277 assert_eq!(rtt.event, Event::Edit);
278 assert_eq!(rtt.id, None);
279 assert_eq!(rtt.actions.len(), 0);
280 }
281
282 #[test]
283 fn sequence() {
284 let elem: Element = "<rtt xmlns='urn:xmpp:rtt:0' seq='0' event='new'><t>Hello,</t><w n='50'/><e/><t>!</t></rtt>"
285 .parse()
286 .unwrap();
287
288 let rtt = Rtt::try_from(elem).unwrap();
289 assert_eq!(rtt.seq, 0);
290 assert_eq!(rtt.event, Event::New);
291 assert_eq!(rtt.id, None);
292
293 let mut actions = rtt.actions.into_iter();
294 assert_eq!(actions.len(), 4);
295
296 let t: Insert = actions.next().unwrap().try_into().unwrap();
297 assert_eq!(t.pos, None);
298 assert_eq!(t.text, Some(String::from("Hello,")));
299
300 let w: Wait = actions.next().unwrap().try_into().unwrap();
301 assert_eq!(w.time, 50);
302
303 let e: Erase = actions.next().unwrap().try_into().unwrap();
304 assert_eq!(e.pos, None);
305 assert_eq!(e.num, 1);
306
307 let t: Insert = actions.next().unwrap().try_into().unwrap();
308 assert_eq!(t.pos, None);
309 assert_eq!(t.text, Some(String::from("!")));
310 }
311}