1use bitflags::bitflags;
2use byteorder::{ByteOrder, NetworkEndian};
3use core::fmt;
4
5use super::{Error, Result};
6use crate::time::Duration;
7use crate::wire::{Ipv6Address, Ipv6AddressExt, Ipv6Packet, Ipv6Repr, MAX_HARDWARE_ADDRESS_LEN};
8
9use crate::wire::RawHardwareAddress;
10
11enum_with_unknown! {
12 pub enum Type(u8) {
14 SourceLinkLayerAddr = 0x1,
16 TargetLinkLayerAddr = 0x2,
18 PrefixInformation = 0x3,
20 RedirectedHeader = 0x4,
22 Mtu = 0x5
24 }
25}
26
27impl fmt::Display for Type {
28 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
29 match self {
30 Type::SourceLinkLayerAddr => write!(f, "source link-layer address"),
31 Type::TargetLinkLayerAddr => write!(f, "target link-layer address"),
32 Type::PrefixInformation => write!(f, "prefix information"),
33 Type::RedirectedHeader => write!(f, "redirected header"),
34 Type::Mtu => write!(f, "mtu"),
35 Type::Unknown(id) => write!(f, "{id}"),
36 }
37 }
38}
39
40bitflags! {
41 #[cfg_attr(feature = "defmt", derive(defmt::Format))]
42 pub struct PrefixInfoFlags: u8 {
43 const ON_LINK = 0b10000000;
44 const ADDRCONF = 0b01000000;
45 }
46}
47
48#[derive(Debug, PartialEq, Eq)]
52#[cfg_attr(feature = "defmt", derive(defmt::Format))]
53pub struct NdiscOption<T: AsRef<[u8]>> {
54 buffer: T,
55}
56
57mod field {
67 #![allow(non_snake_case)]
68
69 use crate::wire::field::*;
70
71 pub const TYPE: usize = 0;
73 pub const LENGTH: usize = 1;
75 pub const MIN_OPT_LEN: usize = 8;
77 pub const fn DATA(length: u8) -> Field {
79 2..length as usize * 8
80 }
81
82 pub const PREFIX_LEN: usize = 2;
108 pub const FLAGS: usize = 3;
110 pub const VALID_LT: Field = 4..8;
112 pub const PREF_LT: Field = 8..12;
114 pub const PREF_RESERVED: Field = 12..16;
116 pub const PREFIX: Field = 16..32;
118
119 pub const REDIRECTED_RESERVED: Field = 2..8;
132 pub const REDIR_MIN_SZ: usize = 48;
133
134 pub const MTU: Field = 4..8;
143}
144
145impl<T: AsRef<[u8]>> NdiscOption<T> {
147 pub const fn new_unchecked(buffer: T) -> NdiscOption<T> {
149 NdiscOption { buffer }
150 }
151
152 pub fn new_checked(buffer: T) -> Result<NdiscOption<T>> {
157 let opt = Self::new_unchecked(buffer);
158 opt.check_len()?;
159
160 if opt.data_len() == 0 {
162 return Err(Error);
163 }
164
165 Ok(opt)
166 }
167
168 pub fn check_len(&self) -> Result<()> {
175 let data = self.buffer.as_ref();
176 let len = data.len();
177
178 if len < field::MIN_OPT_LEN {
179 Err(Error)
180 } else {
181 let data_range = field::DATA(data[field::LENGTH]);
182 if len < data_range.end {
183 Err(Error)
184 } else {
185 match self.option_type() {
186 Type::SourceLinkLayerAddr | Type::TargetLinkLayerAddr | Type::Mtu => Ok(()),
187 Type::PrefixInformation if data_range.end >= field::PREFIX.end => Ok(()),
188 Type::RedirectedHeader if data_range.end >= field::REDIR_MIN_SZ => Ok(()),
189 Type::Unknown(_) => Ok(()),
190 _ => Err(Error),
191 }
192 }
193 }
194 }
195
196 pub fn into_inner(self) -> T {
198 self.buffer
199 }
200
201 #[inline]
203 pub fn option_type(&self) -> Type {
204 let data = self.buffer.as_ref();
205 Type::from(data[field::TYPE])
206 }
207
208 #[inline]
210 pub fn data_len(&self) -> u8 {
211 let data = self.buffer.as_ref();
212 data[field::LENGTH]
213 }
214}
215
216impl<T: AsRef<[u8]>> NdiscOption<T> {
218 #[inline]
220 pub fn link_layer_addr(&self) -> RawHardwareAddress {
221 let len = MAX_HARDWARE_ADDRESS_LEN.min(self.data_len() as usize * 8 - 2);
222 let data = self.buffer.as_ref();
223 RawHardwareAddress::from_bytes(&data[2..len + 2])
224 }
225}
226
227impl<T: AsRef<[u8]>> NdiscOption<T> {
229 #[inline]
231 pub fn mtu(&self) -> u32 {
232 let data = self.buffer.as_ref();
233 NetworkEndian::read_u32(&data[field::MTU])
234 }
235}
236
237impl<T: AsRef<[u8]>> NdiscOption<T> {
239 #[inline]
241 pub fn prefix_len(&self) -> u8 {
242 self.buffer.as_ref()[field::PREFIX_LEN]
243 }
244
245 #[inline]
247 pub fn prefix_flags(&self) -> PrefixInfoFlags {
248 PrefixInfoFlags::from_bits_truncate(self.buffer.as_ref()[field::FLAGS])
249 }
250
251 #[inline]
253 pub fn valid_lifetime(&self) -> Duration {
254 let data = self.buffer.as_ref();
255 Duration::from_secs(NetworkEndian::read_u32(&data[field::VALID_LT]) as u64)
256 }
257
258 #[inline]
260 pub fn preferred_lifetime(&self) -> Duration {
261 let data = self.buffer.as_ref();
262 Duration::from_secs(NetworkEndian::read_u32(&data[field::PREF_LT]) as u64)
263 }
264
265 #[inline]
267 pub fn prefix(&self) -> Ipv6Address {
268 let data = self.buffer.as_ref();
269 Ipv6Address::from_bytes(&data[field::PREFIX])
270 }
271}
272
273impl<'a, T: AsRef<[u8]> + ?Sized> NdiscOption<&'a T> {
274 #[inline]
276 pub fn data(&self) -> &'a [u8] {
277 let len = self.data_len();
278 let data = self.buffer.as_ref();
279 &data[field::DATA(len)]
280 }
281}
282
283impl<T: AsRef<[u8]> + AsMut<[u8]>> NdiscOption<T> {
285 #[inline]
287 pub fn set_option_type(&mut self, value: Type) {
288 let data = self.buffer.as_mut();
289 data[field::TYPE] = value.into();
290 }
291
292 #[inline]
294 pub fn set_data_len(&mut self, value: u8) {
295 let data = self.buffer.as_mut();
296 data[field::LENGTH] = value;
297 }
298}
299
300impl<T: AsRef<[u8]> + AsMut<[u8]>> NdiscOption<T> {
302 #[inline]
304 pub fn set_link_layer_addr(&mut self, addr: RawHardwareAddress) {
305 let data = self.buffer.as_mut();
306 data[2..2 + addr.len()].copy_from_slice(addr.as_bytes())
307 }
308}
309
310impl<T: AsRef<[u8]> + AsMut<[u8]>> NdiscOption<T> {
312 #[inline]
314 pub fn set_mtu(&mut self, value: u32) {
315 let data = self.buffer.as_mut();
316 NetworkEndian::write_u32(&mut data[field::MTU], value);
317 }
318}
319
320impl<T: AsRef<[u8]> + AsMut<[u8]>> NdiscOption<T> {
322 #[inline]
324 pub fn set_prefix_len(&mut self, value: u8) {
325 self.buffer.as_mut()[field::PREFIX_LEN] = value;
326 }
327
328 #[inline]
330 pub fn set_prefix_flags(&mut self, flags: PrefixInfoFlags) {
331 self.buffer.as_mut()[field::FLAGS] = flags.bits();
332 }
333
334 #[inline]
336 pub fn set_valid_lifetime(&mut self, time: Duration) {
337 let data = self.buffer.as_mut();
338 NetworkEndian::write_u32(&mut data[field::VALID_LT], time.secs() as u32);
339 }
340
341 #[inline]
343 pub fn set_preferred_lifetime(&mut self, time: Duration) {
344 let data = self.buffer.as_mut();
345 NetworkEndian::write_u32(&mut data[field::PREF_LT], time.secs() as u32);
346 }
347
348 #[inline]
350 pub fn clear_prefix_reserved(&mut self) {
351 let data = self.buffer.as_mut();
352 NetworkEndian::write_u32(&mut data[field::PREF_RESERVED], 0);
353 }
354
355 #[inline]
357 pub fn set_prefix(&mut self, addr: Ipv6Address) {
358 let data = self.buffer.as_mut();
359 data[field::PREFIX].copy_from_slice(&addr.octets());
360 }
361}
362
363impl<T: AsRef<[u8]> + AsMut<[u8]>> NdiscOption<T> {
365 #[inline]
367 pub fn clear_redirected_reserved(&mut self) {
368 let data = self.buffer.as_mut();
369 data[field::REDIRECTED_RESERVED].fill_with(|| 0);
370 }
371}
372
373impl<'a, T: AsRef<[u8]> + AsMut<[u8]> + ?Sized> NdiscOption<&'a mut T> {
374 #[inline]
376 pub fn data_mut(&mut self) -> &mut [u8] {
377 let len = self.data_len();
378 let data = self.buffer.as_mut();
379 &mut data[field::DATA(len)]
380 }
381}
382
383impl<'a, T: AsRef<[u8]> + ?Sized> fmt::Display for NdiscOption<&'a T> {
384 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
385 match Repr::parse(self) {
386 Ok(repr) => write!(f, "{repr}"),
387 Err(err) => {
388 write!(f, "NDISC Option ({err})")?;
389 Ok(())
390 }
391 }
392 }
393}
394
395#[derive(Debug, PartialEq, Eq, Clone, Copy)]
396#[cfg_attr(feature = "defmt", derive(defmt::Format))]
397pub struct PrefixInformation {
398 pub prefix_len: u8,
399 pub flags: PrefixInfoFlags,
400 pub valid_lifetime: Duration,
401 pub preferred_lifetime: Duration,
402 pub prefix: Ipv6Address,
403}
404
405#[derive(Debug, PartialEq, Eq, Clone, Copy)]
406#[cfg_attr(feature = "defmt", derive(defmt::Format))]
407pub struct RedirectedHeader<'a> {
408 pub header: Ipv6Repr,
409 pub data: &'a [u8],
410}
411
412#[derive(Debug, PartialEq, Eq, Clone, Copy)]
414#[cfg_attr(feature = "defmt", derive(defmt::Format))]
415pub enum Repr<'a> {
416 SourceLinkLayerAddr(RawHardwareAddress),
417 TargetLinkLayerAddr(RawHardwareAddress),
418 PrefixInformation(PrefixInformation),
419 RedirectedHeader(RedirectedHeader<'a>),
420 Mtu(u32),
421 Unknown {
422 type_: u8,
423 length: u8,
424 data: &'a [u8],
425 },
426}
427
428impl<'a> Repr<'a> {
429 pub fn parse<T>(opt: &NdiscOption<&'a T>) -> Result<Repr<'a>>
431 where
432 T: AsRef<[u8]> + ?Sized,
433 {
434 opt.check_len()?;
435
436 match opt.option_type() {
437 Type::SourceLinkLayerAddr => {
438 if opt.data_len() >= 1 {
439 Ok(Repr::SourceLinkLayerAddr(opt.link_layer_addr()))
440 } else {
441 Err(Error)
442 }
443 }
444 Type::TargetLinkLayerAddr => {
445 if opt.data_len() >= 1 {
446 Ok(Repr::TargetLinkLayerAddr(opt.link_layer_addr()))
447 } else {
448 Err(Error)
449 }
450 }
451 Type::PrefixInformation => {
452 if opt.data_len() == 4 {
453 Ok(Repr::PrefixInformation(PrefixInformation {
454 prefix_len: opt.prefix_len(),
455 flags: opt.prefix_flags(),
456 valid_lifetime: opt.valid_lifetime(),
457 preferred_lifetime: opt.preferred_lifetime(),
458 prefix: opt.prefix(),
459 }))
460 } else {
461 Err(Error)
462 }
463 }
464 Type::RedirectedHeader => {
465 if opt.data_len() < 6 {
469 Err(Error)
470 } else {
471 let redirected_packet = &opt.data()[field::REDIRECTED_RESERVED.len()..];
472
473 let ip_packet = Ipv6Packet::new_checked(redirected_packet)?;
474 let ip_repr = Ipv6Repr::parse(&ip_packet)?;
475
476 Ok(Repr::RedirectedHeader(RedirectedHeader {
477 header: ip_repr,
478 data: &redirected_packet[ip_repr.buffer_len()..][..ip_repr.payload_len],
479 }))
480 }
481 }
482 Type::Mtu => {
483 if opt.data_len() == 1 {
484 Ok(Repr::Mtu(opt.mtu()))
485 } else {
486 Err(Error)
487 }
488 }
489 Type::Unknown(id) => {
490 if opt.data_len() != 0 {
492 Ok(Repr::Unknown {
493 type_: id,
494 length: opt.data_len(),
495 data: opt.data(),
496 })
497 } else {
498 Err(Error)
499 }
500 }
501 }
502 }
503
504 pub const fn buffer_len(&self) -> usize {
506 match self {
507 &Repr::SourceLinkLayerAddr(addr) | &Repr::TargetLinkLayerAddr(addr) => {
508 let len = 2 + addr.len();
509 (len + 7) / 8 * 8
511 }
512 &Repr::PrefixInformation(_) => field::PREFIX.end,
513 &Repr::RedirectedHeader(RedirectedHeader { header, data }) => {
514 (8 + header.buffer_len() + data.len() + 7) / 8 * 8
515 }
516 &Repr::Mtu(_) => field::MTU.end,
517 &Repr::Unknown { length, .. } => field::DATA(length).end,
518 }
519 }
520
521 pub fn emit<T>(&self, opt: &mut NdiscOption<&'a mut T>)
523 where
524 T: AsRef<[u8]> + AsMut<[u8]> + ?Sized,
525 {
526 match *self {
527 Repr::SourceLinkLayerAddr(addr) => {
528 opt.set_option_type(Type::SourceLinkLayerAddr);
529 let opt_len = addr.len() + 2;
530 opt.set_data_len(((opt_len + 7) / 8) as u8); opt.set_link_layer_addr(addr);
532 }
533 Repr::TargetLinkLayerAddr(addr) => {
534 opt.set_option_type(Type::TargetLinkLayerAddr);
535 let opt_len = addr.len() + 2;
536 opt.set_data_len(((opt_len + 7) / 8) as u8); opt.set_link_layer_addr(addr);
538 }
539 Repr::PrefixInformation(PrefixInformation {
540 prefix_len,
541 flags,
542 valid_lifetime,
543 preferred_lifetime,
544 prefix,
545 }) => {
546 opt.clear_prefix_reserved();
547 opt.set_option_type(Type::PrefixInformation);
548 opt.set_data_len(4);
549 opt.set_prefix_len(prefix_len);
550 opt.set_prefix_flags(flags);
551 opt.set_valid_lifetime(valid_lifetime);
552 opt.set_preferred_lifetime(preferred_lifetime);
553 opt.set_prefix(prefix);
554 }
555 Repr::RedirectedHeader(RedirectedHeader { header, data }) => {
556 opt.clear_redirected_reserved();
559 opt.set_option_type(Type::RedirectedHeader);
560 opt.set_data_len((((8 + header.buffer_len() + data.len()) + 7) / 8) as u8);
561 let mut packet = &mut opt.data_mut()[field::REDIRECTED_RESERVED.end - 2..];
562 let mut ip_packet = Ipv6Packet::new_unchecked(&mut packet);
563 header.emit(&mut ip_packet);
564 ip_packet.payload_mut().copy_from_slice(data);
565 }
566 Repr::Mtu(mtu) => {
567 opt.set_option_type(Type::Mtu);
568 opt.set_data_len(1);
569 opt.set_mtu(mtu);
570 }
571 Repr::Unknown {
572 type_: id,
573 length,
574 data,
575 } => {
576 opt.set_option_type(Type::Unknown(id));
577 opt.set_data_len(length);
578 opt.data_mut().copy_from_slice(data);
579 }
580 }
581 }
582}
583
584impl<'a> fmt::Display for Repr<'a> {
585 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
586 write!(f, "NDISC Option: ")?;
587 match *self {
588 Repr::SourceLinkLayerAddr(addr) => {
589 write!(f, "SourceLinkLayer addr={addr}")
590 }
591 Repr::TargetLinkLayerAddr(addr) => {
592 write!(f, "TargetLinkLayer addr={addr}")
593 }
594 Repr::PrefixInformation(PrefixInformation {
595 prefix, prefix_len, ..
596 }) => {
597 write!(f, "PrefixInformation prefix={prefix}/{prefix_len}")
598 }
599 Repr::RedirectedHeader(RedirectedHeader { header, .. }) => {
600 write!(f, "RedirectedHeader header={header}")
601 }
602 Repr::Mtu(mtu) => {
603 write!(f, "MTU mtu={mtu}")
604 }
605 Repr::Unknown {
606 type_: id, length, ..
607 } => {
608 write!(f, "Unknown({id}) length={length}")
609 }
610 }
611 }
612}
613
614use crate::wire::pretty_print::{PrettyIndent, PrettyPrint};
615
616impl<T: AsRef<[u8]>> PrettyPrint for NdiscOption<T> {
617 fn pretty_print(
618 buffer: &dyn AsRef<[u8]>,
619 f: &mut fmt::Formatter,
620 indent: &mut PrettyIndent,
621 ) -> fmt::Result {
622 match NdiscOption::new_checked(buffer) {
623 Err(err) => write!(f, "{indent}({err})"),
624 Ok(ndisc) => match Repr::parse(&ndisc) {
625 Err(_) => Ok(()),
626 Ok(repr) => {
627 write!(f, "{indent}{repr}")
628 }
629 },
630 }
631 }
632}
633
634#[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))]
635#[cfg(test)]
636mod test {
637 use super::Error;
638 use super::{NdiscOption, PrefixInfoFlags, PrefixInformation, Repr, Type};
639 use crate::time::Duration;
640 use crate::wire::Ipv6Address;
641
642 #[cfg(feature = "medium-ethernet")]
643 use crate::wire::EthernetAddress;
644 #[cfg(all(not(feature = "medium-ethernet"), feature = "medium-ieee802154"))]
645 use crate::wire::Ieee802154Address;
646
647 static PREFIX_OPT_BYTES: [u8; 32] = [
648 0x03, 0x04, 0x40, 0xc0, 0x00, 0x00, 0x03, 0x84, 0x00, 0x00, 0x03, 0xe8, 0x00, 0x00, 0x00,
649 0x00, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
650 0x00, 0x01,
651 ];
652
653 #[test]
654 fn test_deconstruct() {
655 let opt = NdiscOption::new_unchecked(&PREFIX_OPT_BYTES[..]);
656 assert_eq!(opt.option_type(), Type::PrefixInformation);
657 assert_eq!(opt.data_len(), 4);
658 assert_eq!(opt.prefix_len(), 64);
659 assert_eq!(
660 opt.prefix_flags(),
661 PrefixInfoFlags::ON_LINK | PrefixInfoFlags::ADDRCONF
662 );
663 assert_eq!(opt.valid_lifetime(), Duration::from_secs(900));
664 assert_eq!(opt.preferred_lifetime(), Duration::from_secs(1000));
665 assert_eq!(opt.prefix(), Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1));
666 }
667
668 #[test]
669 fn test_construct() {
670 let mut bytes = [0x00; 32];
671 let mut opt = NdiscOption::new_unchecked(&mut bytes[..]);
672 opt.set_option_type(Type::PrefixInformation);
673 opt.set_data_len(4);
674 opt.set_prefix_len(64);
675 opt.set_prefix_flags(PrefixInfoFlags::ON_LINK | PrefixInfoFlags::ADDRCONF);
676 opt.set_valid_lifetime(Duration::from_secs(900));
677 opt.set_preferred_lifetime(Duration::from_secs(1000));
678 opt.set_prefix(Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1));
679 assert_eq!(&PREFIX_OPT_BYTES[..], &*opt.into_inner());
680 }
681
682 #[test]
683 fn test_short_packet() {
684 assert_eq!(NdiscOption::new_checked(&[0x00, 0x00]), Err(Error));
685 let bytes = [0x03, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
686 assert_eq!(NdiscOption::new_checked(&bytes), Err(Error));
687 }
688
689 #[cfg(feature = "medium-ethernet")]
690 #[test]
691 fn test_repr_parse_link_layer_opt_ethernet() {
692 let mut bytes = [0x01, 0x01, 0x54, 0x52, 0x00, 0x12, 0x23, 0x34];
693 let addr = EthernetAddress([0x54, 0x52, 0x00, 0x12, 0x23, 0x34]);
694 {
695 assert_eq!(
696 Repr::parse(&NdiscOption::new_unchecked(&bytes)),
697 Ok(Repr::SourceLinkLayerAddr(addr.into()))
698 );
699 }
700 bytes[0] = 0x02;
701 {
702 assert_eq!(
703 Repr::parse(&NdiscOption::new_unchecked(&bytes)),
704 Ok(Repr::TargetLinkLayerAddr(addr.into()))
705 );
706 }
707 }
708
709 #[cfg(all(not(feature = "medium-ethernet"), feature = "medium-ieee802154"))]
710 #[test]
711 fn test_repr_parse_link_layer_opt_ieee802154() {
712 let mut bytes = [
713 0x01, 0x02, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x00, 0x00, 0x00, 0x00,
714 0x00, 0x00,
715 ];
716 let addr = Ieee802154Address::Extended([0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]);
717 {
718 assert_eq!(
719 Repr::parse(&NdiscOption::new_unchecked(&bytes)),
720 Ok(Repr::SourceLinkLayerAddr(addr.into()))
721 );
722 }
723 bytes[0] = 0x02;
724 {
725 assert_eq!(
726 Repr::parse(&NdiscOption::new_unchecked(&bytes)),
727 Ok(Repr::TargetLinkLayerAddr(addr.into()))
728 );
729 }
730 }
731
732 #[test]
733 fn test_repr_parse_prefix_info() {
734 let repr = Repr::PrefixInformation(PrefixInformation {
735 prefix_len: 64,
736 flags: PrefixInfoFlags::ON_LINK | PrefixInfoFlags::ADDRCONF,
737 valid_lifetime: Duration::from_secs(900),
738 preferred_lifetime: Duration::from_secs(1000),
739 prefix: Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1),
740 });
741 assert_eq!(
742 Repr::parse(&NdiscOption::new_unchecked(&PREFIX_OPT_BYTES)),
743 Ok(repr)
744 );
745 }
746
747 #[test]
748 fn test_repr_emit_prefix_info() {
749 let mut bytes = [0x2a; 32];
750 let repr = Repr::PrefixInformation(PrefixInformation {
751 prefix_len: 64,
752 flags: PrefixInfoFlags::ON_LINK | PrefixInfoFlags::ADDRCONF,
753 valid_lifetime: Duration::from_secs(900),
754 preferred_lifetime: Duration::from_secs(1000),
755 prefix: Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1),
756 });
757 let mut opt = NdiscOption::new_unchecked(&mut bytes);
758 repr.emit(&mut opt);
759 assert_eq!(&opt.into_inner()[..], &PREFIX_OPT_BYTES[..]);
760 }
761
762 #[test]
763 fn test_repr_parse_mtu() {
764 let bytes = [0x05, 0x01, 0x00, 0x00, 0x00, 0x00, 0x05, 0xdc];
765 assert_eq!(
766 Repr::parse(&NdiscOption::new_unchecked(&bytes)),
767 Ok(Repr::Mtu(1500))
768 );
769 }
770}