libspa/pod/
builder.rs

1use std::{
2    ffi::{c_int, c_void, CString},
3    mem::MaybeUninit,
4};
5
6use nix::errno::Errno;
7
8use crate::utils::{Fraction, Id, Rectangle};
9
10static CALLBACKS: spa_sys::spa_pod_builder_callbacks = spa_sys::spa_pod_builder_callbacks {
11    version: spa_sys::SPA_VERSION_POD_BUILDER_CALLBACKS,
12    overflow: Some(Builder::overflow),
13};
14
15struct BuilderInner<'d> {
16    builder: spa_sys::spa_pod_builder,
17    data: &'d mut Vec<u8>,
18}
19
20pub struct Builder<'d> {
21    // Keep the actual state in a box, so that
22    // we can be sure that it does not move while the builder is in use
23    // This lets us access it via pointer in the overflow callback
24    inner: Box<BuilderInner<'d>>,
25}
26
27impl<'d> Builder<'d> {
28    unsafe extern "C" fn overflow(data: *mut c_void, size: u32) -> c_int {
29        let this: *mut BuilderInner = data.cast();
30
31        assert!(!this.is_null());
32        assert!(size as usize > (*this).data.len());
33
34        // Resize the vec to be `size` longer, so that the new value fits,
35        // then update the builders internal data size and also the data pointer
36        // in case the vec had to reallocate
37        (*this).data.resize(size as usize, 0);
38        (*this).builder.data = (*this).data.as_mut_ptr().cast::<c_void>();
39        (*this).builder.size = (*this)
40            .data
41            .len()
42            .try_into()
43            .expect("data length does not fit in a u32");
44
45        // Return zero to indicate that we successfully resized our data
46        0
47    }
48
49    pub fn new(data: &'d mut Vec<u8>) -> Self {
50        unsafe {
51            let mut builder: MaybeUninit<spa_sys::spa_pod_builder> = MaybeUninit::uninit();
52
53            spa_sys::spa_pod_builder_init(
54                builder.as_mut_ptr(),
55                data.as_mut_ptr().cast(),
56                data.len()
57                    .try_into()
58                    .expect("data length does not fit in a u32"),
59            );
60
61            let inner = Box::new(BuilderInner {
62                builder: builder.assume_init(),
63                data,
64            });
65
66            spa_sys::spa_pod_builder_set_callbacks(
67                std::ptr::addr_of!(inner.builder).cast_mut(),
68                std::ptr::addr_of!(CALLBACKS),
69                std::ptr::addr_of!(*inner).cast::<c_void>().cast_mut(),
70            );
71
72            Self { inner }
73        }
74    }
75
76    pub fn as_raw(&self) -> &spa_sys::spa_pod_builder {
77        &self.inner.builder
78    }
79
80    pub fn as_raw_ptr(&self) -> *mut spa_sys::spa_pod_builder {
81        std::ptr::addr_of!(self.inner.builder).cast_mut()
82    }
83
84    /// # Safety
85    ///
86    /// The builder state may only be used as long as all frames that were pushed
87    /// to the builder at the time of this call are alive and not moved
88    pub unsafe fn state(&self) -> spa_sys::spa_pod_builder_state {
89        let mut state: MaybeUninit<spa_sys::spa_pod_builder_state> = MaybeUninit::uninit();
90        spa_sys::spa_pod_builder_get_state(self.as_raw_ptr(), state.as_mut_ptr());
91        state.assume_init()
92    }
93
94    // not bound: set_callbacks
95    // we set those ourselves to resize the Vec
96
97    /// # Safety
98    ///
99    /// TODO: Constraints unknown, use at own risk
100    pub unsafe fn reset(&mut self, state: *mut spa_sys::spa_pod_builder_state) {
101        spa_sys::spa_pod_builder_reset(self.as_raw_ptr(), state)
102    }
103
104    /// # Safety
105    ///
106    /// TODO: Constraints unknown, use at own risk
107    pub unsafe fn deref(&mut self, offset: u32) -> *mut spa_sys::spa_pod {
108        spa_sys::spa_pod_builder_deref(self.as_raw_ptr(), offset)
109    }
110
111    /// # Safety
112    ///
113    /// TODO: Constraints unknown, use at own risk
114    pub unsafe fn frame(&mut self, frame: *mut spa_sys::spa_pod_frame) -> *mut spa_sys::spa_pod {
115        spa_sys::spa_pod_builder_frame(self.as_raw_ptr(), frame)
116    }
117
118    /// # Safety
119    ///
120    /// TODO: Constraints unknown, use at own risk
121    pub unsafe fn push(
122        &mut self,
123        frame: *mut spa_sys::spa_pod_frame,
124        pod: *const spa_sys::spa_pod,
125        offset: u32,
126    ) {
127        spa_sys::spa_pod_builder_push(self.as_raw_ptr(), frame, pod, offset)
128    }
129
130    // TODO: raw, pad
131
132    pub fn raw_padded(&mut self, data: &[u8]) -> Result<(), Errno> {
133        let res = unsafe {
134            spa_sys::spa_pod_builder_raw_padded(
135                self.as_raw_ptr(),
136                data.as_ptr().cast::<c_void>(),
137                data.len().try_into().unwrap(),
138            )
139        };
140
141        if res >= 0 {
142            Ok(())
143        } else {
144            Err(Errno::from_raw(-res))
145        }
146    }
147
148    /// # Safety
149    ///
150    /// Only the last added frame may be popped
151    pub unsafe fn pop(&mut self, frame: &mut spa_sys::spa_pod_frame) {
152        unsafe {
153            spa_sys::spa_pod_builder_pop(self.as_raw_ptr(), frame as *mut _);
154        }
155    }
156
157    // TODO: primitive
158
159    pub fn add_none(&mut self) -> Result<(), Errno> {
160        unsafe {
161            let res = spa_sys::spa_pod_builder_none(self.as_raw_ptr());
162
163            if res >= 0 {
164                Ok(())
165            } else {
166                Err(Errno::from_raw(-res))
167            }
168        }
169    }
170
171    // todo: child
172
173    pub fn add_bool(&mut self, val: bool) -> Result<(), Errno> {
174        unsafe {
175            let res = spa_sys::spa_pod_builder_bool(self.as_raw_ptr(), val);
176
177            if res >= 0 {
178                Ok(())
179            } else {
180                Err(Errno::from_raw(-res))
181            }
182        }
183    }
184
185    pub fn add_id(&mut self, val: Id) -> Result<(), Errno> {
186        unsafe {
187            let res = spa_sys::spa_pod_builder_id(self.as_raw_ptr(), val.0);
188
189            if res >= 0 {
190                Ok(())
191            } else {
192                Err(Errno::from_raw(-res))
193            }
194        }
195    }
196
197    pub fn add_int(&mut self, val: i32) -> Result<(), Errno> {
198        unsafe {
199            let res = spa_sys::spa_pod_builder_int(self.as_raw_ptr(), val);
200
201            if res >= 0 {
202                Ok(())
203            } else {
204                Err(Errno::from_raw(-res))
205            }
206        }
207    }
208
209    pub fn add_long(&mut self, val: i64) -> Result<(), Errno> {
210        unsafe {
211            let res = spa_sys::spa_pod_builder_long(self.as_raw_ptr(), val);
212
213            if res >= 0 {
214                Ok(())
215            } else {
216                Err(Errno::from_raw(-res))
217            }
218        }
219    }
220
221    pub fn add_float(&mut self, val: f32) -> Result<(), Errno> {
222        unsafe {
223            let res = spa_sys::spa_pod_builder_float(self.as_raw_ptr(), val);
224
225            if res >= 0 {
226                Ok(())
227            } else {
228                Err(Errno::from_raw(-res))
229            }
230        }
231    }
232
233    pub fn add_double(&mut self, val: f64) -> Result<(), Errno> {
234        unsafe {
235            let res = spa_sys::spa_pod_builder_double(self.as_raw_ptr(), val);
236
237            if res >= 0 {
238                Ok(())
239            } else {
240                Err(Errno::from_raw(-res))
241            }
242        }
243    }
244
245    // TODO: write_string, string_len,
246    // TODO: add_string_raw variant?
247
248    /// # Panics
249    ///
250    /// If `string` contains an interior null byte
251    pub fn add_string(&mut self, string: &str) -> Result<(), Errno> {
252        let c_str = CString::new(string).expect("string should not contain an interior null byte");
253
254        let res = unsafe { spa_sys::spa_pod_builder_string(self.as_raw_ptr(), c_str.as_ptr()) };
255
256        if res >= 0 {
257            Ok(())
258        } else {
259            Err(Errno::from_raw(-res))
260        }
261    }
262
263    // TODO: raw bytes variant?
264
265    pub fn add_bytes(&mut self, bytes: &[u8]) -> Result<(), Errno> {
266        let res = unsafe {
267            spa_sys::spa_pod_builder_bytes(
268                self.as_raw_ptr(),
269                bytes.as_ptr().cast::<c_void>(),
270                bytes.len().try_into().unwrap(),
271            )
272        };
273
274        if res >= 0 {
275            Ok(())
276        } else {
277            Err(Errno::from_raw(-res))
278        }
279    }
280
281    // TODO: reserve_bytes
282
283    /// # Safety
284    ///
285    /// The pointer must be pointing to valid, well-aligned data which has the type as specified by `type_`.
286    pub unsafe fn add_pointer(&mut self, type_: Id, val: *const c_void) -> Result<(), Errno> {
287        unsafe {
288            let res = spa_sys::spa_pod_builder_pointer(self.as_raw_ptr(), type_.0, val);
289
290            if res >= 0 {
291                Ok(())
292            } else {
293                Err(Errno::from_raw(-res))
294            }
295        }
296    }
297
298    pub fn add_fd(&mut self, val: std::os::fd::RawFd) -> Result<(), Errno> {
299        unsafe {
300            let res = spa_sys::spa_pod_builder_fd(self.as_raw_ptr(), val.into());
301
302            if res >= 0 {
303                Ok(())
304            } else {
305                Err(Errno::from_raw(-res))
306            }
307        }
308    }
309
310    pub fn add_rectangle(&mut self, val: Rectangle) -> Result<(), Errno> {
311        unsafe {
312            let res = spa_sys::spa_pod_builder_rectangle(self.as_raw_ptr(), val.width, val.height);
313
314            if res >= 0 {
315                Ok(())
316            } else {
317                Err(Errno::from_raw(-res))
318            }
319        }
320    }
321
322    pub fn add_fraction(&mut self, val: Fraction) -> Result<(), Errno> {
323        unsafe {
324            let res = spa_sys::spa_pod_builder_fraction(self.as_raw_ptr(), val.num, val.denom);
325
326            if res >= 0 {
327                Ok(())
328            } else {
329                Err(Errno::from_raw(-res))
330            }
331        }
332    }
333
334    /// # Safety
335    /// The provided frame must not be moved or destroyed before it is popped again.
336    ///
337    /// The frame may only be assumed as initialized if this method returns `Ok`.
338    pub unsafe fn push_array(
339        &mut self,
340        frame: &mut MaybeUninit<spa_sys::spa_pod_frame>,
341    ) -> Result<(), Errno> {
342        let res = spa_sys::spa_pod_builder_push_array(self.as_raw_ptr(), frame.as_mut_ptr());
343
344        if res >= 0 {
345            Ok(())
346        } else {
347            Err(Errno::from_raw(-res))
348        }
349    }
350
351    /// # Safety
352    ///
353    /// `elems` must point to a valid array containing at least `n_elems`
354    /// with each child having exactly the size as specified by `child_size` and the type `child_type`.
355    pub unsafe fn add_array(
356        &mut self,
357        child_size: u32,
358        child_type: u32,
359        n_elems: u32,
360        elems: *const c_void,
361    ) -> Result<(), Errno> {
362        let res = spa_sys::spa_pod_builder_array(
363            self.as_raw_ptr(),
364            child_size,
365            child_type,
366            n_elems,
367            elems,
368        );
369
370        if res >= 0 {
371            Ok(())
372        } else {
373            Err(Errno::from_raw(-res))
374        }
375    }
376
377    /// # Safety
378    /// The provided frame must not be moved or destroyed before it is popped again.
379    ///
380    /// The frame may only be assumed as initialized if this method returns `Ok`.
381    pub unsafe fn push_choice(
382        &mut self,
383        frame: &mut MaybeUninit<spa_sys::spa_pod_frame>,
384        type_: u32,
385        flags: u32, // FIXME: Make dedicated flag type
386    ) -> Result<(), Errno> {
387        let res = spa_sys::spa_pod_builder_push_choice(
388            self.as_raw_ptr(),
389            frame.as_mut_ptr(),
390            type_,
391            flags,
392        );
393
394        if res >= 0 {
395            Ok(())
396        } else {
397            Err(Errno::from_raw(-res))
398        }
399    }
400
401    /// # Safety
402    /// The provided frame must not be moved or destroyed before it is popped again.
403    ///
404    /// The frame may only be assumed as initialized if this method returns `Ok`.
405    pub unsafe fn push_struct(
406        &mut self,
407        frame: &mut MaybeUninit<spa_sys::spa_pod_frame>,
408    ) -> Result<(), Errno> {
409        unsafe {
410            let res = spa_sys::spa_pod_builder_push_struct(self.as_raw_ptr(), frame.as_mut_ptr());
411
412            if res >= 0 {
413                Ok(())
414            } else {
415                Err(Errno::from_raw(-res))
416            }
417        }
418    }
419
420    /// # Safety
421    /// The provided frame must not be moved or destroyed before it is popped again.
422    ///
423    /// The frame may only be assumed as initialized if this method returns `Ok`.
424    pub unsafe fn push_object(
425        &mut self,
426        frame: &mut MaybeUninit<spa_sys::spa_pod_frame>,
427        type_: u32,
428        id: u32,
429    ) -> Result<(), Errno> {
430        unsafe {
431            let res = spa_sys::spa_pod_builder_push_object(
432                self.as_raw_ptr(),
433                frame.as_mut_ptr(),
434                type_,
435                id,
436            );
437
438            if res >= 0 {
439                Ok(())
440            } else {
441                Err(Errno::from_raw(-res))
442            }
443        }
444    }
445
446    pub fn add_prop(&mut self, key: u32, flags: u32) -> Result<(), Errno> {
447        let res = unsafe { spa_sys::spa_pod_builder_prop(self.as_raw_ptr(), key, flags) };
448
449        if res >= 0 {
450            Ok(())
451        } else {
452            Err(Errno::from_raw(-res))
453        }
454    }
455
456    /// # Safety
457    /// The provided frame must not be moved or destroyed before it is popped again.
458    ///
459    /// The frame may only be assumed as initialized if this method returns `Ok`.
460    pub unsafe fn push_sequence(
461        &mut self,
462        frame: &mut MaybeUninit<spa_sys::spa_pod_frame>,
463        unit: u32,
464    ) -> Result<(), Errno> {
465        let res =
466            spa_sys::spa_pod_builder_push_sequence(self.as_raw_ptr(), frame.as_mut_ptr(), unit);
467
468        if res >= 0 {
469            Ok(())
470        } else {
471            Err(Errno::from_raw(-res))
472        }
473    }
474
475    pub fn add_control(&mut self, offset: u32, type_: u32) -> c_int {
476        // Older versions of pipewire mistakenly had the return type as uint32_t,
477        // so we need to use try_into().unwrap() to ensure those versions also work
478        #[allow(clippy::useless_conversion)]
479        unsafe {
480            spa_sys::spa_pod_builder_control(self.as_raw_ptr(), offset, type_)
481                .try_into()
482                .unwrap()
483        }
484    }
485}
486
487/// Convenience macro to build a pod from values using a spa pod builder.
488///
489/// For arguments, the macro accepts the builder, and then the structure of the desired pod:
490///
491/// ```ignore
492/// builder_add!(<&mut libspa::pod::builder::Builder>, Bool(<bool>));
493/// builder_add!(<&mut libspa::pod::builder::Builder>, Id(<ibspa::utils::Id>));
494/// builder_add!(<&mut libspa::pod::builder::Builder>, Int(<i32>));
495/// builder_add!(<&mut libspa::pod::builder::Builder>, Long(<i64>));
496/// builder_add!(<&mut libspa::pod::builder::Builder>, Float(<f32>));
497/// builder_add!(<&mut libspa::pod::builder::Builder>, Double(<f64>));
498/// builder_add!(<&mut libspa::pod::builder::Builder>, Bytes(<&[u8]>));
499/// // Macro using `Pointer` can only be called in `unsafe` block.
500/// // Safety rules from `Builder::add_pointer()` apply.
501/// builder_add!(<&mut libspa::pod::builder::Builder>, Pointer(<*const c_void>));
502/// builder_add!(<&mut libspa::pod::builder::Builder>, Fd(<i64>));
503/// builder_add!(<&mut libspa::pod::builder::Builder>, Rectangle(<libspa::utils::Rectangle>));
504/// builder_add!(<&mut libspa::pod::builder::Builder>, Fraction(<libspa::utils::Fraction>));
505/// builder_add!(<&mut libspa::pod::builder::Builder>,
506///     Struct {
507///         // 0 to n fields, e.g.:
508///         Struct {
509///             Int(<i32>),
510///             Float(<f32>),
511///         },
512///         Bytes(<&[u8]>),
513///     }
514/// );
515/// builder_add!(<&mut libspa::pod::builder::Builder>,
516///     Object(
517///         <type as u32>,
518///         <id as u32>
519///     ) {
520///         // 0 to n properties of format
521///         // `<key as u32> => <value>`
522///         // e.g.
523///           0 => Bool(false),
524///         313 => String("313"),
525///     }
526/// );
527/// ```
528///
529/// # Returns
530///
531/// The macro returns a `Result<(), Errno>`.
532/// If building succeeds, an `Ok(())` is returned.
533/// Otherwise, the `Err(Errno)` from the point where building failed is returned, and the rest of the values are not added.
534#[macro_export]
535macro_rules! __builder_add__ {
536    ($builder:expr, None) => {
537        $crate::pod::builder::Builder::add_none($builder)
538    };
539    ($builder:expr, Bool($val:expr)) => {
540        $crate::pod::builder::Builder::add_bool($builder, $val)
541    };
542    ($builder:expr, Id($val:expr)) => {
543        $crate::pod::builder::Builder::add_id($builder, $val)
544    };
545    ($builder:expr, Int($val:expr)) => {
546        $crate::pod::builder::Builder::add_int($builder, $val)
547    };
548    ($builder:expr, Long($val:expr)) => {
549        $crate::pod::builder::Builder::add_long($builder, $val)
550    };
551    ($builder:expr, Float($val:expr)) => {
552        $crate::pod::builder::Builder::add_float($builder, $val)
553    };
554    ($builder:expr, Double($val:expr)) => {
555        $crate::pod::builder::Builder::add_double($builder, $val)
556    };
557    ($builder:expr, String($val:expr)) => {
558        $crate::pod::builder::Builder::add_string($builder, $val)
559    };
560    ($builder:expr, Bytes($val:expr)) => {
561        $crate::pod::builder::Builder::add_bytes($builder, $val)
562    };
563    ($builder:expr, Pointer($type_:expr, $val:expr)) => {
564        $crate::pod::builder::Builder::add_bool($builder, $type_, $val)
565    };
566    ($builder:expr, Fd($val:expr)) => {
567        $crate::pod::builder::Builder::add_fd($builder, $val)
568    };
569    ($builder:expr, Rectangle($val:expr)) => {
570        $crate::pod::builder::Builder::add_rectangle($builder, $val)
571    };
572    ($builder:expr, Fraction($val:expr)) => {
573        $crate::pod::builder::Builder::add_fraction($builder, $val)
574    };
575    // TODO: Choice
576    (
577        $builder:expr,
578        Struct {
579            $( $field_type:tt $field:tt ),* $(,)?
580        }
581    ) => {
582        'outer: {
583            // Ensure that $builder expansion doesn't contain unsafe code without an unsafe block.
584            if false {
585                let _ = $builder;
586            }
587
588            let mut frame: ::std::mem::MaybeUninit<$crate::sys::spa_pod_frame> = ::std::mem::MaybeUninit::uninit();
589            let res = unsafe { $crate::pod::builder::Builder::push_struct($builder, &mut frame) };
590            if res.is_err() {
591                break 'outer res;
592            }
593
594            $(
595                let res = $crate::__builder_add__!($builder, $field_type $field);
596                if res.is_err() {
597                    break 'outer res;
598                }
599            )*
600
601            unsafe { $crate::pod::builder::Builder::pop($builder, frame.assume_init_mut()) }
602
603            Ok(())
604        }
605    };
606    (
607        $builder:expr,
608        Object($type_:expr, $id:expr $(,)?) {
609            $( $key:expr => $value_type:tt $value:tt ),* $(,)?
610        }
611    ) => {
612        'outer: {
613            // Ensure that argument expansion doesn't contain unsafe code without an unsafe block.
614            if false {
615                let _ = $builder;
616                let _ = $type_;
617                let _ = $id;
618            }
619
620            let mut frame: ::std::mem::MaybeUninit<$crate::sys::spa_pod_frame> = ::std::mem::MaybeUninit::uninit();
621            let res = unsafe { $crate::pod::builder::Builder::push_object($builder, &mut frame, $type_, $id) };
622            if res.is_err() {
623                break 'outer res;
624            }
625
626            $(
627                let res = $crate::pod::builder::Builder::add_prop($builder, $key, 0);
628                if res.is_err() {
629                    break 'outer res;
630                }
631                let res = $crate::__builder_add__!($builder, $value_type $value);
632                if res.is_err() {
633                    break 'outer res;
634                }
635            )*
636
637            unsafe { $crate::pod::builder::Builder::pop($builder, frame.assume_init_mut()) }
638
639            Ok(())
640        }
641    };
642    // TODO: Sequence
643    // TODO: Control
644}
645pub use __builder_add__ as builder_add;
646
647#[cfg(test)]
648mod tests {
649    use super::*;
650
651    #[test]
652    #[cfg_attr(miri, ignore)]
653    fn build_empty_struct() {
654        let mut data = Vec::new();
655
656        let mut builder = Builder::new(&mut data);
657        let res = builder_add!(&mut builder, Struct {});
658
659        assert!(res.is_ok());
660
661        let other: Vec<u8> = [
662            0u32.to_ne_bytes(),  // body has size 16
663            14u32.to_ne_bytes(), // struct type is 14
664        ]
665        .iter()
666        .copied()
667        .flatten()
668        .collect();
669
670        assert_eq!(&data, &other)
671    }
672
673    #[test]
674    #[cfg_attr(miri, ignore)]
675    fn build_small_struct() {
676        let mut data = Vec::new();
677
678        let mut builder = Builder::new(&mut data);
679        let res = builder_add!(
680            &mut builder,
681            Struct {
682                Int(3),
683            }
684        );
685
686        assert!(res.is_ok());
687
688        let other: Vec<u8> = [
689            16u32.to_ne_bytes(), // body has size 16
690            14u32.to_ne_bytes(), // struct type is 14
691            4u32.to_ne_bytes(),  // child body size is 4
692            4u32.to_ne_bytes(),  // Int child type is 4
693            3i32.to_ne_bytes(),  // the integer
694            [0, 0, 0, 0],        // padding
695        ]
696        .iter()
697        .copied()
698        .flatten()
699        .collect();
700
701        assert_eq!(&data, &other)
702    }
703
704    #[test]
705    #[cfg_attr(miri, ignore)]
706    fn build_complex_struct() {
707        let mut data = Vec::new();
708
709        let mut builder = Builder::new(&mut data);
710        let res = builder_add!(
711            &mut builder,
712            Struct {
713                Struct {
714                    Float(31.3),
715                    String("foo")
716                },
717                Int(3),
718            }
719        );
720
721        dbg!(res.unwrap());
722        assert!(res.is_ok());
723    }
724
725    #[test]
726    #[cfg_attr(miri, ignore)]
727    fn build_empty_object() {
728        use crate::param::ParamType;
729
730        let mut data = Vec::new();
731        let mut builder = Builder::new(&mut data);
732        let res = builder_add!(
733            &mut builder,
734            Object(
735                ParamType::Format.as_raw(),
736                0,
737            ) {}
738        );
739
740        assert!(res.is_ok());
741    }
742
743    #[test]
744    #[cfg_attr(miri, ignore)]
745    fn build_object() {
746        use crate::param::{
747            format::{FormatProperties, MediaSubtype, MediaType},
748            ParamType,
749        };
750
751        let mut data = Vec::new();
752        let mut builder = Builder::new(&mut data);
753        let res = builder_add!(
754            &mut builder,
755            Object(
756                ParamType::Format.as_raw(),
757                0,
758            ) {
759                FormatProperties::MediaType.as_raw() => Id(crate::utils::Id(MediaType::Audio.as_raw())),
760                FormatProperties::MediaSubtype.as_raw() => Id(crate::utils::Id(MediaSubtype::Raw.as_raw())),
761            }
762        );
763
764        assert!(res.is_ok());
765    }
766}