rtt.rs

  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}