wtransport_proto/
frame.rs

1use crate::bytes::BufferReader;
2use crate::bytes::BufferWriter;
3use crate::bytes::BytesReader;
4use crate::bytes::BytesWriter;
5use crate::bytes::EndOfBuffer;
6use crate::ids::InvalidSessionId;
7use crate::ids::SessionId;
8use crate::varint::VarInt;
9use std::borrow::Cow;
10
11#[cfg(feature = "async")]
12use crate::bytes::AsyncRead;
13
14#[cfg(feature = "async")]
15use crate::bytes::AsyncWrite;
16
17#[cfg(feature = "async")]
18use crate::bytes;
19
20/// Error frame parsing.
21#[derive(Debug, thiserror::Error)]
22pub enum ParseError {
23    /// Error for unknown frame ID.
24    #[error("cannot parse HTTP3 frame as ID is unknown")]
25    UnknownFrame,
26
27    /// Error for invalid session ID.
28    #[error("cannot parse HTTP3 frame as session ID is invalid")]
29    InvalidSessionId,
30
31    /// Payload required too big.
32    #[error("cannot parse HTTP3 frame as payload limit is reached")]
33    PayloadTooBig,
34}
35
36/// An error during frame I/O read operation.
37#[cfg(feature = "async")]
38#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
39#[derive(Debug, thiserror::Error)]
40pub enum IoReadError {
41    /// Error during parsing a frame.
42    #[error(transparent)]
43    Parse(ParseError),
44
45    /// Error due to I/O operation.
46    #[error(transparent)]
47    IO(bytes::IoReadError),
48}
49
50#[cfg(feature = "async")]
51impl From<bytes::IoReadError> for IoReadError {
52    #[inline(always)]
53    fn from(io_error: bytes::IoReadError) -> Self {
54        IoReadError::IO(io_error)
55    }
56}
57
58/// An error during frame I/O write operation.
59#[cfg(feature = "async")]
60pub type IoWriteError = bytes::IoWriteError;
61
62/// Alias for [`Frame<'static>`](Frame);
63pub type FrameOwned = Frame<'static>;
64
65/// An HTTP3 [`Frame`] type.
66#[derive(Copy, Clone, Debug)]
67pub enum FrameKind {
68    /// DATA frame type.
69    Data,
70
71    /// HEADERS frame type.
72    Headers,
73
74    /// SETTINGS frame type.
75    Settings,
76
77    /// WebTransport frame type.
78    WebTransport,
79
80    /// Exercise frame.
81    Exercise(VarInt),
82}
83
84impl FrameKind {
85    /// Checks whether an `id` is valid for a [`FrameKind::Exercise`].
86    #[inline(always)]
87    pub const fn is_id_exercise(id: VarInt) -> bool {
88        id.into_inner() >= 0x21 && ((id.into_inner() - 0x21) % 0x1f == 0)
89    }
90
91    const fn parse(id: VarInt) -> Option<Self> {
92        match id {
93            frame_kind_ids::DATA => Some(FrameKind::Data),
94            frame_kind_ids::HEADERS => Some(FrameKind::Headers),
95            frame_kind_ids::SETTINGS => Some(FrameKind::Settings),
96            frame_kind_ids::WEBTRANSPORT_STREAM => Some(FrameKind::WebTransport),
97            id if FrameKind::is_id_exercise(id) => Some(FrameKind::Exercise(id)),
98            _ => None,
99        }
100    }
101
102    const fn id(self) -> VarInt {
103        match self {
104            FrameKind::Data => frame_kind_ids::DATA,
105            FrameKind::Headers => frame_kind_ids::HEADERS,
106            FrameKind::Settings => frame_kind_ids::SETTINGS,
107            FrameKind::WebTransport => frame_kind_ids::WEBTRANSPORT_STREAM,
108            FrameKind::Exercise(id) => id,
109        }
110    }
111}
112
113/// An HTTP3 frame.
114#[derive(Debug)]
115pub struct Frame<'a> {
116    kind: FrameKind,
117    payload: Cow<'a, [u8]>,
118    session_id: Option<SessionId>,
119}
120
121impl<'a> Frame<'a> {
122    const MAX_PARSE_PAYLOAD_ALLOWED: usize = 4096;
123
124    /// Creates a new frame of type [`FrameKind::Headers`].
125    ///
126    /// # Panics
127    ///
128    /// Panics if the `payload` size if greater than [`VarInt::MAX`].
129    #[inline(always)]
130    pub fn new_headers(payload: Cow<'a, [u8]>) -> Self {
131        Self::new(FrameKind::Headers, payload, None)
132    }
133
134    /// Creates a new frame of type [`FrameKind::Settings`].
135    ///
136    /// # Panics
137    ///
138    /// Panics if the `payload` size if greater than [`VarInt::MAX`].
139    #[inline(always)]
140    pub fn new_settings(payload: Cow<'a, [u8]>) -> Self {
141        Self::new(FrameKind::Settings, payload, None)
142    }
143
144    /// Creates a new frame of type [`FrameKind::WebTransport`].
145    #[inline(always)]
146    pub fn new_webtransport(session_id: SessionId) -> Self {
147        Self::new(
148            FrameKind::WebTransport,
149            Cow::Owned(Default::default()),
150            Some(session_id),
151        )
152    }
153
154    /// Creates a new frame of type [`FrameKind::Data`].
155    ///
156    /// # Panics
157    ///
158    /// Panics if the `payload` size if greater than [`VarInt::MAX`].
159    #[inline(always)]
160    pub fn new_data(payload: Cow<'a, [u8]>) -> Self {
161        Self::new(FrameKind::Data, payload, None)
162    }
163
164    /// Creates a new frame of type [`FrameKind::Exercise`].
165    ///
166    /// # Panics
167    ///
168    /// * Panics if the `payload` size if greater than [`VarInt::MAX`].
169    /// * Panics if `id` is not a valid exercise (see [`FrameKind::is_id_exercise`]).
170    #[inline(always)]
171    pub fn new_exercise(id: VarInt, payload: Cow<'a, [u8]>) -> Self {
172        assert!(FrameKind::is_id_exercise(id));
173        Self::new(FrameKind::Exercise(id), payload, None)
174    }
175
176    /// Reads a [`Frame`] from a [`BytesReader`].
177    ///
178    /// It returns [`None`] if the `bytes_reader` does not contain enough bytes
179    /// to parse an entire frame.
180    ///
181    /// In case [`None`] or [`Err`], `bytes_reader` might be partially read.
182    pub fn read<R>(bytes_reader: &mut R) -> Result<Option<Self>, ParseError>
183    where
184        R: BytesReader<'a>,
185    {
186        let kind = match bytes_reader.get_varint() {
187            Some(kind_id) => FrameKind::parse(kind_id).ok_or(ParseError::UnknownFrame)?,
188            None => return Ok(None),
189        };
190
191        if matches!(kind, FrameKind::WebTransport) {
192            let session_id = match bytes_reader.get_varint() {
193                Some(session_id) => SessionId::try_from_varint(session_id)
194                    .map_err(|InvalidSessionId| ParseError::InvalidSessionId)?,
195                None => return Ok(None),
196            };
197
198            Ok(Some(Self::new_webtransport(session_id)))
199        } else {
200            let payload_len = match bytes_reader.get_varint() {
201                Some(payload_len) => payload_len.into_inner() as usize,
202                None => return Ok(None),
203            };
204
205            if payload_len > Self::MAX_PARSE_PAYLOAD_ALLOWED {
206                return Err(ParseError::PayloadTooBig);
207            }
208
209            let payload = match bytes_reader.get_bytes(payload_len) {
210                Some(payload) => payload,
211                None => return Ok(None),
212            };
213
214            Ok(Some(Self::new(kind, Cow::Borrowed(payload), None)))
215        }
216    }
217
218    /// Reads a [`Frame`] from a `reader`.
219    #[cfg(feature = "async")]
220    #[cfg_attr(docsrs, doc(cfg(feature = "async")))]
221    pub async fn read_async<R>(reader: &mut R) -> Result<Frame<'a>, IoReadError>
222    where
223        R: AsyncRead + Unpin + ?Sized,
224    {
225        use crate::bytes::BytesReaderAsync;
226
227        let kind_id = reader.get_varint().await?;
228        let kind = FrameKind::parse(kind_id).ok_or(IoReadError::Parse(ParseError::UnknownFrame))?;
229
230        if matches!(kind, FrameKind::WebTransport) {
231            let session_id =
232                SessionId::try_from_varint(reader.get_varint().await.map_err(|e| match e {
233                    bytes::IoReadError::ImmediateFin => bytes::IoReadError::UnexpectedFin,
234                    _ => e,
235                })?)
236                .map_err(|InvalidSessionId| IoReadError::Parse(ParseError::InvalidSessionId))?;
237
238            Ok(Self::new_webtransport(session_id))
239        } else {
240            let payload_len = reader
241                .get_varint()
242                .await
243                .map_err(|e| match e {
244                    bytes::IoReadError::ImmediateFin => bytes::IoReadError::UnexpectedFin,
245                    _ => e,
246                })?
247                .into_inner() as usize;
248
249            if payload_len > Self::MAX_PARSE_PAYLOAD_ALLOWED {
250                return Err(IoReadError::Parse(ParseError::PayloadTooBig));
251            }
252
253            let mut payload = vec![0; payload_len];
254
255            reader.get_buffer(&mut payload).await.map_err(|e| match e {
256                bytes::IoReadError::ImmediateFin => bytes::IoReadError::UnexpectedFin,
257                _ => e,
258            })?;
259
260            payload.shrink_to_fit();
261
262            Ok(Self::new(kind, Cow::Owned(payload), None))
263        }
264    }
265
266    /// Reads a [`Frame`] from a [`BufferReader`].
267    ///
268    /// It returns [`None`] if the `buffer_reader` does not contain enough bytes
269    /// to parse an entire frame.
270    ///
271    /// In case [`None`] or [`Err`], `buffer_reader` offset if not advanced.
272    pub fn read_from_buffer(
273        buffer_reader: &mut BufferReader<'a>,
274    ) -> Result<Option<Self>, ParseError> {
275        let mut buffer_reader_child = buffer_reader.child();
276
277        match Self::read(&mut *buffer_reader_child)? {
278            Some(frame) => {
279                buffer_reader_child.commit();
280                Ok(Some(frame))
281            }
282            None => Ok(None),
283        }
284    }
285
286    /// Writes a [`Frame`] into a [`BytesWriter`].
287    ///
288    /// It returns [`Err`] if the `bytes_writer` does not have enough capacity
289    /// to write the entire frame.
290    /// See [`Self::write_size`] to retrieve the exact amount of required capacity.
291    ///
292    /// In case [`Err`], `bytes_writer` might be partially written.
293    ///
294    /// # Panics
295    ///
296    /// Panics if the payload size if greater than [`VarInt::MAX`].
297    pub fn write<W>(&self, bytes_writer: &mut W) -> Result<(), EndOfBuffer>
298    where
299        W: BytesWriter,
300    {
301        bytes_writer.put_varint(self.kind.id())?;
302
303        if let Some(session_id) = self.session_id() {
304            bytes_writer.put_varint(session_id.into_varint())?;
305        } else {
306            bytes_writer.put_varint(
307                VarInt::try_from(self.payload.len() as u64)
308                    .expect("Payload cannot be larger than varint max"),
309            )?;
310            bytes_writer.put_bytes(&self.payload)?;
311        }
312
313        Ok(())
314    }
315
316    /// Writes a [`Frame`] into a `writer`.
317    ///
318    /// # Panics
319    ///
320    /// Panics if the payload size if greater than [`VarInt::MAX`].
321    #[cfg(feature = "async")]
322    #[cfg_attr(docsrs, doc(cfg(feature = "async")))]
323    pub async fn write_async<W>(&self, writer: &mut W) -> Result<(), IoWriteError>
324    where
325        W: AsyncWrite + Unpin + ?Sized,
326    {
327        use crate::bytes::BytesWriterAsync;
328
329        writer.put_varint(self.kind.id()).await?;
330
331        if let Some(session_id) = self.session_id() {
332            writer.put_varint(session_id.into_varint()).await?;
333        } else {
334            writer
335                .put_varint(
336                    VarInt::try_from(self.payload.len() as u64)
337                        .expect("Payload cannot be larger than varint max"),
338                )
339                .await?;
340            writer.put_buffer(&self.payload).await?;
341        }
342
343        Ok(())
344    }
345
346    /// Writes this [`Frame`] into a buffer via [`BufferWriter`].
347    ///
348    /// In case [`Err`], `buffer_writer` is not advanced.
349    ///
350    /// # Panics
351    ///
352    /// Panics if the payload size if greater than [`VarInt::MAX`].
353    pub fn write_to_buffer(&self, buffer_writer: &mut BufferWriter) -> Result<(), EndOfBuffer> {
354        if buffer_writer.capacity() < self.write_size() {
355            return Err(EndOfBuffer);
356        }
357
358        self.write(buffer_writer)
359            .expect("Enough capacity for frame");
360
361        Ok(())
362    }
363
364    /// Returns the needed capacity to write this frame into a buffer.
365    pub fn write_size(&self) -> usize {
366        if let Some(session_id) = self.session_id() {
367            self.kind.id().size() + session_id.into_varint().size()
368        } else {
369            self.kind.id().size()
370                + VarInt::try_from(self.payload.len() as u64)
371                    .expect("Payload cannot be larger than varint max")
372                    .size()
373                + self.payload.len()
374        }
375    }
376
377    /// Returns the [`FrameKind`] of this [`Frame`].
378    #[inline(always)]
379    pub const fn kind(&self) -> FrameKind {
380        self.kind
381    }
382
383    /// Returns the payload of this [`Frame`].
384    #[inline(always)]
385    pub fn payload(&self) -> &[u8] {
386        &self.payload
387    }
388
389    /// Returns the [`SessionId`] if frame is [`FrameKind::WebTransport`],
390    /// otherwise returns [`None`].
391    #[inline(always)]
392    pub fn session_id(&self) -> Option<SessionId> {
393        matches!(self.kind, FrameKind::WebTransport).then(|| {
394            self.session_id
395                .expect("WebTransport frame contains session id")
396        })
397    }
398
399    /// # Panics
400    ///
401    /// Panics if the `payload` size if greater than [`VarInt::MAX`].
402    fn new(kind: FrameKind, payload: Cow<'a, [u8]>, session_id: Option<SessionId>) -> Self {
403        if let FrameKind::Exercise(id) = kind {
404            debug_assert!(FrameKind::is_id_exercise(id));
405        } else if let FrameKind::WebTransport = kind {
406            debug_assert!(payload.is_empty());
407            debug_assert!(session_id.is_some());
408        }
409
410        assert!(payload.len() <= VarInt::MAX.into_inner() as usize);
411
412        Self {
413            kind,
414            payload,
415            session_id,
416        }
417    }
418
419    #[cfg(test)]
420    pub(crate) fn into_owned<'b>(self) -> Frame<'b> {
421        Frame {
422            kind: self.kind,
423            payload: Cow::Owned(self.payload.into_owned()),
424            session_id: self.session_id,
425        }
426    }
427
428    #[cfg(test)]
429    pub(crate) fn serialize_any(kind: VarInt, payload: &[u8]) -> Vec<u8> {
430        let mut buffer = Vec::new();
431
432        Self {
433            kind: FrameKind::Exercise(kind),
434            payload: Cow::Owned(payload.to_vec()),
435            session_id: None,
436        }
437        .write(&mut buffer)
438        .unwrap();
439
440        buffer
441    }
442
443    #[cfg(test)]
444    pub(crate) fn serialize_webtransport(session_id: SessionId) -> Vec<u8> {
445        let mut buffer = Vec::new();
446
447        Self {
448            kind: FrameKind::WebTransport,
449            payload: Cow::Owned(Default::default()),
450            session_id: Some(session_id),
451        }
452        .write(&mut buffer)
453        .unwrap();
454
455        buffer
456    }
457}
458
459mod frame_kind_ids {
460    use crate::varint::VarInt;
461
462    pub const DATA: VarInt = VarInt::from_u32(0x00);
463    pub const HEADERS: VarInt = VarInt::from_u32(0x01);
464    pub const SETTINGS: VarInt = VarInt::from_u32(0x04);
465    pub const WEBTRANSPORT_STREAM: VarInt = VarInt::from_u32(0x41);
466}
467
468#[cfg(test)]
469mod tests {
470    use super::*;
471    use crate::headers::Headers;
472    use crate::settings::Settings;
473
474    #[test]
475    fn settings() {
476        let settings = Settings::builder()
477            .qpack_blocked_streams(VarInt::from_u32(1))
478            .qpack_max_table_capacity(VarInt::from_u32(2))
479            .enable_h3_datagrams()
480            .enable_webtransport()
481            .webtransport_max_sessions(VarInt::from_u32(3))
482            .build();
483
484        let frame = settings.generate_frame();
485        assert!(frame.session_id().is_none());
486        assert!(matches!(frame.kind(), FrameKind::Settings));
487
488        let frame = utils::assert_serde(frame);
489        Settings::with_frame(&frame).unwrap();
490    }
491
492    #[tokio::test]
493    async fn settings_async() {
494        let settings = Settings::builder()
495            .qpack_blocked_streams(VarInt::from_u32(1))
496            .qpack_max_table_capacity(VarInt::from_u32(2))
497            .enable_h3_datagrams()
498            .enable_webtransport()
499            .webtransport_max_sessions(VarInt::from_u32(3))
500            .build();
501
502        let frame = settings.generate_frame();
503        assert!(frame.session_id().is_none());
504        assert!(matches!(frame.kind(), FrameKind::Settings));
505
506        let frame = utils::assert_serde_async(frame).await;
507        Settings::with_frame(&frame).unwrap();
508    }
509
510    #[test]
511    fn headers() {
512        let headers = Headers::from_iter([("key1", "value1")]);
513
514        let frame = headers.generate_frame();
515        assert!(frame.session_id().is_none());
516        assert!(matches!(frame.kind(), FrameKind::Headers));
517
518        let frame = utils::assert_serde(frame);
519        Headers::with_frame(&frame).unwrap();
520    }
521
522    #[tokio::test]
523    async fn headers_async() {
524        let headers = Headers::from_iter([("key1", "value1")]);
525
526        let frame = headers.generate_frame();
527        assert!(frame.session_id().is_none());
528        assert!(matches!(frame.kind(), FrameKind::Headers));
529
530        let frame = utils::assert_serde_async(frame).await;
531        Headers::with_frame(&frame).unwrap();
532    }
533
534    #[test]
535    fn webtransport() {
536        let session_id = SessionId::try_from_varint(VarInt::from_u32(0)).unwrap();
537        let frame = Frame::new_webtransport(session_id);
538
539        assert!(frame.payload().is_empty());
540        assert!(matches!(frame.session_id(), Some(x) if x == session_id));
541        assert!(matches!(frame.kind(), FrameKind::WebTransport));
542
543        let frame = utils::assert_serde(frame);
544
545        assert!(frame.payload().is_empty());
546        assert!(matches!(frame.session_id(), Some(x) if x == session_id));
547        assert!(matches!(frame.kind(), FrameKind::WebTransport));
548    }
549
550    #[tokio::test]
551    async fn webtransport_async() {
552        let session_id = SessionId::try_from_varint(VarInt::from_u32(0)).unwrap();
553        let frame = Frame::new_webtransport(session_id);
554
555        assert!(frame.payload().is_empty());
556        assert!(matches!(frame.session_id(), Some(x) if x == session_id));
557        assert!(matches!(frame.kind(), FrameKind::WebTransport));
558
559        let frame = utils::assert_serde_async(frame).await;
560
561        assert!(frame.payload().is_empty());
562        assert!(matches!(frame.session_id(), Some(x) if x == session_id));
563        assert!(matches!(frame.kind(), FrameKind::WebTransport));
564    }
565
566    #[test]
567    fn read_eof() {
568        let buffer = Frame::serialize_any(FrameKind::Data.id(), b"This is a test payload");
569        assert!(Frame::read(&mut &buffer[..buffer.len() - 1])
570            .unwrap()
571            .is_none());
572    }
573
574    #[tokio::test]
575    async fn read_eof_async() {
576        let buffer = Frame::serialize_any(FrameKind::Data.id(), b"This is a test payload");
577
578        for len in 0..buffer.len() {
579            let result = Frame::read_async(&mut &buffer[..len]).await;
580
581            match len {
582                0 => assert!(matches!(
583                    result,
584                    Err(IoReadError::IO(bytes::IoReadError::ImmediateFin))
585                )),
586                _ => assert!(matches!(
587                    result,
588                    Err(IoReadError::IO(bytes::IoReadError::UnexpectedFin))
589                )),
590            }
591        }
592    }
593
594    #[tokio::test]
595    async fn read_eof_webtransport_async() {
596        let session_id = SessionId::try_from_varint(VarInt::from_u32(0)).unwrap();
597        let buffer = Frame::serialize_webtransport(session_id);
598
599        for len in 0..buffer.len() {
600            let result = Frame::read_async(&mut &buffer[..len]).await;
601
602            match len {
603                0 => assert!(matches!(
604                    result,
605                    Err(IoReadError::IO(bytes::IoReadError::ImmediateFin))
606                )),
607                _ => assert!(matches!(
608                    result,
609                    Err(IoReadError::IO(bytes::IoReadError::UnexpectedFin))
610                )),
611            }
612        }
613    }
614
615    #[test]
616    fn unknown_frame() {
617        let buffer = Frame::serialize_any(VarInt::from_u32(0x0042_4242), b"This is a test payload");
618
619        assert!(matches!(
620            Frame::read(&mut buffer.as_slice()),
621            Err(ParseError::UnknownFrame)
622        ));
623    }
624
625    #[tokio::test]
626    async fn unknown_frame_async() {
627        let buffer = Frame::serialize_any(VarInt::from_u32(0x0042_4242), b"This is a test payload");
628
629        assert!(matches!(
630            Frame::read_async(&mut buffer.as_slice()).await,
631            Err(IoReadError::Parse(ParseError::UnknownFrame))
632        ));
633    }
634
635    #[test]
636    fn invalid_session_id() {
637        let invalid_session_id = SessionId::maybe_invalid(VarInt::from_u32(1));
638        let buffer = Frame::serialize_webtransport(invalid_session_id);
639
640        assert!(matches!(
641            Frame::read(&mut buffer.as_slice()),
642            Err(ParseError::InvalidSessionId)
643        ));
644    }
645
646    #[tokio::test]
647    async fn invalid_session_id_async() {
648        let invalid_session_id = SessionId::maybe_invalid(VarInt::from_u32(1));
649        let buffer = Frame::serialize_webtransport(invalid_session_id);
650
651        assert!(matches!(
652            Frame::read_async(&mut buffer.as_slice()).await,
653            Err(IoReadError::Parse(ParseError::InvalidSessionId))
654        ));
655    }
656
657    #[test]
658    fn payload_too_big() {
659        let mut buffer = Vec::new();
660        buffer.put_varint(FrameKind::Data.id()).unwrap();
661        buffer
662            .put_varint(VarInt::from_u32(
663                Frame::MAX_PARSE_PAYLOAD_ALLOWED as u32 + 1,
664            ))
665            .unwrap();
666
667        assert!(matches!(
668            Frame::read_from_buffer(&mut BufferReader::new(&buffer)),
669            Err(ParseError::PayloadTooBig)
670        ));
671    }
672
673    #[tokio::test]
674    async fn payload_too_big_async() {
675        let mut buffer = Vec::new();
676        buffer.put_varint(FrameKind::Data.id()).unwrap();
677        buffer
678            .put_varint(VarInt::from_u32(
679                Frame::MAX_PARSE_PAYLOAD_ALLOWED as u32 + 1,
680            ))
681            .unwrap();
682
683        assert!(matches!(
684            Frame::read_async(&mut &*buffer).await,
685            Err(IoReadError::Parse(ParseError::PayloadTooBig)),
686        ));
687    }
688
689    mod utils {
690        use super::*;
691
692        pub fn assert_serde(frame: Frame) -> Frame {
693            let mut buffer = Vec::new();
694
695            frame.write(&mut buffer).unwrap();
696            assert_eq!(buffer.len(), frame.write_size());
697
698            let mut buffer = buffer.as_slice();
699            let frame = Frame::read(&mut buffer).unwrap().unwrap();
700            assert!(buffer.is_empty());
701
702            frame.into_owned()
703        }
704
705        #[cfg(feature = "async")]
706        pub async fn assert_serde_async(frame: Frame<'_>) -> Frame {
707            let mut buffer = Vec::new();
708
709            frame.write_async(&mut buffer).await.unwrap();
710            assert_eq!(buffer.len(), frame.write_size());
711
712            let mut buffer = buffer.as_slice();
713            let frame = Frame::read_async(&mut buffer).await.unwrap();
714            assert!(buffer.is_empty());
715
716            frame.into_owned()
717        }
718    }
719}