Skip to main content

pktstrings/
net.rs

1use cfg_if::cfg_if;
2use colored::*;
3use pcap::Packet;
4use std::collections::HashMap;
5use std::default::Default;
6use std::fmt::Write;
7use std::net::IpAddr;
8use std::result::Result;
9
10use crate::proto::*;
11
12#[cfg(feature = "resolve")]
13use dns_lookup::lookup_addr;
14
15pub type Resolver = HashMap<IpAddr, String>;
16
17#[derive(Eq, PartialEq, Default)]
18pub struct PacketSummary<'a> {
19    pub l2_src: Option<u128>,
20    pub l2_dst: Option<u128>,
21    pub ethertype: Option<Ethertype>,
22    pub vlan_id: Option<u16>,
23    pub l3_src: Option<u128>,
24    pub l3_dst: Option<u128>,
25    pub next_proto: Option<NextProto>,
26    pub l4_sport: Option<u16>,
27    pub l4_dport: Option<u16>,
28    pub resolver: Option<&'a mut HashMap<IpAddr, String>>,
29}
30
31#[allow(clippy::upper_case_acronyms)]
32#[derive(Eq, PartialEq, Debug)]
33pub enum ProtoHandler {
34    COMPLETE, // special member
35    UNKNOWN,  // special member
36    ETH,
37    VLAN,
38    IPV4,
39    IPV6,
40    UDP,
41    TCP,
42    DCCP,
43    SCTP,
44}
45
46pub fn handle_protocol(
47    pkt: &Packet,
48    offset: usize,
49    pktsum: &mut PacketSummary,
50    proto: &ProtoHandler,
51) -> Result<(usize, ProtoHandler), String> {
52    let handler = match proto {
53        ProtoHandler::ETH => handle_eth,
54        ProtoHandler::VLAN => handle_vlan,
55        ProtoHandler::IPV4 => handle_ipv4,
56        ProtoHandler::IPV6 => handle_ipv6,
57        ProtoHandler::UDP => handle_ports,
58        ProtoHandler::TCP => handle_ports,
59        ProtoHandler::SCTP => handle_ports,
60        ProtoHandler::DCCP => handle_ports,
61        _ => handle_unknown,
62    };
63
64    handler(pkt, offset, pktsum)
65}
66
67pub fn get_field(data: &[u8], offset: usize, bytelen: usize) -> Result<u128, &str> {
68    assert!(bytelen <= 16, "Length must be less than 16 bytes");
69    if (data.len() - offset) < bytelen {
70        return Err("Data after offset is shorter than bytelen");
71    }
72    let mut addr: u128 = 0;
73    for i in 0..(bytelen) {
74        addr |= (data[offset + i] as u128) << (((bytelen - 1) * 8) - (i * 8))
75    }
76    Ok(addr)
77}
78
79pub fn int_to_mac_str(addr: &u64, formatted: &mut String) {
80    let bytes = addr.to_be_bytes();
81    write!(formatted, "{:02x}", bytes[2]).unwrap();
82    for byte in bytes[3..].iter() {
83        write!(formatted, ":{:02x}", byte).unwrap();
84    }
85}
86
87pub fn int_to_ipv6_str(addr: &u128, formatted: &mut String) {
88    let bytes = addr.to_be_bytes();
89    write!(formatted, "{:02x}{:02x}", bytes[0], bytes[1]).unwrap();
90    for pair in bytes
91        .iter()
92        .skip(2)
93        .step_by(2)
94        .zip(bytes.iter().skip(3).step_by(2))
95    {
96        write!(formatted, ":{:02x}{:02x}", pair.0, pair.1).unwrap();
97    }
98}
99
100pub fn int_to_ipv4_str(addr: &u32, formatted: &mut String) {
101    let bytes = addr.to_be_bytes();
102    write!(formatted, "{}", bytes[0]).unwrap();
103    for byte in bytes.iter().skip(1) {
104        write!(formatted, ".{}", byte).unwrap();
105    }
106}
107
108pub fn get_ethertype_handler(ethertype: &Option<Ethertype>) -> ProtoHandler {
109    match ethertype {
110        Some(VLAN) => ProtoHandler::VLAN,
111        Some(IPV4) => ProtoHandler::IPV4,
112        Some(IPV6) => ProtoHandler::IPV6,
113        _ => ProtoHandler::UNKNOWN,
114    }
115}
116
117pub fn get_nextproto_handler(next_proto: &Option<NextProto>) -> ProtoHandler {
118    match next_proto {
119        Some(TCP) => ProtoHandler::TCP,
120        Some(UDP) => ProtoHandler::UDP,
121        Some(DCCP) => ProtoHandler::DCCP,
122        Some(SCTP) => ProtoHandler::SCTP,
123        _ => ProtoHandler::UNKNOWN,
124    }
125}
126
127pub fn handle_eth(
128    pkt: &Packet,
129    offset: usize,
130    pktsum: &mut PacketSummary,
131) -> Result<(usize, ProtoHandler), String> {
132    pktsum.l2_dst = get_field(pkt, offset, 6).ok();
133    pktsum.l2_src = get_field(pkt, offset + 6, 6).ok();
134    pktsum.ethertype = get_field(pkt.data, offset + 12, 2).map(|x| x as u16).ok();
135
136    let next_proto_hdl = get_ethertype_handler(&pktsum.ethertype);
137
138    Ok((offset + 14, next_proto_hdl))
139}
140
141pub fn handle_vlan(
142    pkt: &Packet,
143    offset: usize,
144    pktsum: &mut PacketSummary,
145) -> Result<(usize, ProtoHandler), String> {
146    pktsum.vlan_id = get_field(pkt.data, offset, 2)
147        .map(|x| x as u16 & 0xfff)
148        .ok();
149    pktsum.ethertype = get_field(pkt.data, offset + 2, 2).map(|x| x as u16).ok();
150
151    let next_proto_hdl = get_ethertype_handler(&pktsum.ethertype);
152
153    Ok((offset + 4, next_proto_hdl))
154}
155
156pub fn handle_ipv4(
157    pkt: &Packet,
158    offset: usize,
159    pktsum: &mut PacketSummary,
160) -> Result<(usize, ProtoHandler), String> {
161    let ihl = ((pkt.data[offset] & 0xf) * 4) as usize;
162
163    pktsum.next_proto = Some(pkt.data[offset + 9]);
164    pktsum.l3_src = get_field(pkt, offset + 12, 4).ok();
165    pktsum.l3_dst = get_field(pkt, offset + 16, 4).ok();
166
167    let next_proto_hdl = get_nextproto_handler(&pktsum.next_proto);
168
169    Ok((offset + ihl, next_proto_hdl))
170}
171
172pub fn handle_ipv6(
173    pkt: &Packet,
174    offset: usize,
175    pktsum: &mut PacketSummary,
176) -> Result<(usize, ProtoHandler), String> {
177    let mut next_offset = offset + 40;
178    let mut next_proto = pkt.data[offset + 6];
179
180    pktsum.l3_src = get_field(pkt, offset + 8, 16).ok();
181    pktsum.l3_dst = get_field(pkt, offset + 24, 16).ok();
182
183    // walk until we hit bottom of IPv6 header stack
184    let mut bos = false;
185    while !bos {
186        match next_proto {
187            HOPOPT | IPV6_ROUTE | IPV6_OPTS => {
188                next_proto = pkt[next_offset];
189                next_offset += 8 + (pkt[next_offset + 1] * 8) as usize;
190            }
191            IPV6_FRAG => {
192                let frag = get_field(pkt, next_offset + 2, 2)
193                    .map(|x| x as u16 & 0xff8)
194                    .unwrap();
195                next_proto = pkt[next_offset];
196                next_offset += 8;
197
198                // if we aren't on the first fragment then pass an error up
199                // to prevent the protocol walker from continuing to try and parse
200                // whatever data that follows as an L4 header.
201                if frag != 0 {
202                    // IPv6 Frag, halt parsing
203                    pktsum.next_proto = Some(next_proto);
204                    return Ok((next_offset, ProtoHandler::COMPLETE));
205                }
206                bos = true; // prevent re-looping as we will always be BoS here
207            }
208            AH => {
209                // IPv6 Auth Hdr, halt parsing
210                pktsum.next_proto = Some(next_proto);
211                return Ok((next_offset, ProtoHandler::COMPLETE));
212            }
213            IPV6_NONXT => {
214                // IPv6 No Next Header, halt parsing
215                pktsum.next_proto = Some(next_proto);
216                return Ok((next_offset, ProtoHandler::COMPLETE));
217            }
218            _ => bos = true,
219        };
220    }
221
222    pktsum.next_proto = Some(next_proto);
223
224    let next_proto_hdl = get_nextproto_handler(&pktsum.next_proto);
225
226    Ok((next_offset, next_proto_hdl))
227}
228
229pub fn handle_ports(
230    pkt: &Packet,
231    offset: usize,
232    pktsum: &mut PacketSummary,
233) -> Result<(usize, ProtoHandler), String> {
234    let sport_offset = offset;
235    let dport_offset = offset + 2;
236    pktsum.l4_sport = get_field(pkt.data, sport_offset, 2).ok().map(|x| x as u16);
237    pktsum.l4_dport = get_field(pkt.data, dport_offset, 2).ok().map(|x| x as u16);
238
239    Ok((offset + 4, ProtoHandler::COMPLETE))
240}
241
242pub fn handle_unknown(
243    _pkt: &Packet,
244    _offset: usize,
245    _pktsum: &mut PacketSummary,
246) -> Result<(usize, ProtoHandler), String> {
247    Err("Unknown protocol".to_string())
248}
249
250impl<'a> PacketSummary<'a> {
251    pub fn new() -> Self {
252        Default::default()
253    }
254
255    pub fn from_packet(pkt: &Packet, resolver: Option<&'a mut HashMap<IpAddr, String>>) -> Self {
256        let mut pktsum = Self::new();
257        pktsum.resolver = resolver;
258
259        // start with Ethernet handler at offset 0
260        let mut offset = 0;
261        let mut proto_handler = ProtoHandler::ETH;
262
263        // walk the protocol stacks until we hit something we cannot handle.
264        // the handlers will populate pktsum as we go.
265        loop {
266            break match proto_handler {
267                ProtoHandler::UNKNOWN => {}  // walking complete
268                ProtoHandler::COMPLETE => {} // walking complete
269                _ => {
270                    match handle_protocol(pkt, offset, &mut pktsum, &proto_handler) {
271                        Ok((o, h)) => {
272                            // move offset & handler onto returned values
273                            offset = o;
274                            proto_handler = h;
275                        }
276                        Err(_) => return pktsum, // cannot continue
277                    }
278                    continue; // don't break, continue walking
279                }
280            };
281        }
282        pktsum
283    }
284
285    pub fn formatted(&mut self) -> String {
286        // use a bit more memory per packet to avoid reallocating.
287        // memory is only required for the lifetime of the packet
288        // so this seems a fair optimisation.
289        let mut out = String::with_capacity(128);
290
291        out.push('[');
292
293        // pre-allocate ipv6 max strlen
294        let mut l3_src = String::with_capacity(39);
295        let mut l3_dst = String::with_capacity(39);
296
297        if let Some(vlan_id) = self.vlan_id {
298            let tag = vlan_id.to_string();
299            out.push_str(format!("Dot1Q: {} | ", tag.yellow()).as_str());
300        }
301
302        let l4_sport = match self.l4_sport {
303            Some(p) => format!(":{}", p),
304            None => String::new(),
305        };
306
307        let l4_dport = match self.l4_dport {
308            Some(p) => format!(":{}", p),
309            None => String::new(),
310        };
311
312        let next_proto = match self.next_proto {
313            Some(np) => np.resolve(),
314            None => "-".to_string(),
315        };
316
317        let is_ip = match self.ethertype {
318            Some(IPV6) => {
319                int_to_ipv6_str(&self.l3_src.unwrap(), &mut l3_src);
320                int_to_ipv6_str(&self.l3_dst.unwrap(), &mut l3_dst);
321                true
322            }
323            Some(IPV4) => {
324                int_to_ipv4_str(&(self.l3_src.unwrap() as u32), &mut l3_src);
325                int_to_ipv4_str(&(self.l3_dst.unwrap() as u32), &mut l3_dst);
326                true
327            }
328            _ => false,
329        };
330
331        if is_ip {
332            cfg_if! {
333                if #[cfg(feature = "resolve")] {
334                    if let Some(resolver) = &mut self.resolver {
335                        let srcip: IpAddr = l3_src.parse().unwrap();
336                        let dstip: IpAddr = l3_dst.parse().unwrap();
337
338                        let l3_src_resolved = resolver.entry(srcip).or_insert_with(|| {
339                            match lookup_addr(&srcip) {
340                                Ok(addr) => addr,
341                                Err(_) => l3_src.to_string(),
342                            }
343                        });
344                        out.push_str(
345                            format!(
346                                "{}{} → ",
347                                l3_src_resolved.magenta(),
348                                l4_sport.cyan(),
349                            ).as_str()
350                        );
351
352                        let l3_dst_resolved = resolver.entry(dstip).or_insert_with(|| {
353                            match lookup_addr(&dstip) {
354                                Ok(addr) => addr,
355                                Err(_) => l3_dst.to_string(),
356                            }
357                        });
358                        out.push_str(
359                            format!(
360                                "{}{} ",
361                                l3_dst_resolved.magenta(),
362                                l4_dport.cyan(),
363                            ).as_str()
364                        );
365                    } else {
366                        out.push_str(
367                            format!(
368                                "{}{} → {}{} ",
369                                l3_src.magenta(),
370                                l4_sport.cyan(),
371                                l3_dst.magenta(),
372                                l4_dport.cyan(),
373                            ).as_str()
374                        );
375                    }
376                } else {
377                    out.push_str(
378                        format!(
379                            "{}{} → {}{} ",
380                            l3_src.magenta(),
381                            l4_sport.cyan(),
382                            l3_dst.magenta(),
383                            l4_dport.cyan(),
384                        ).as_str()
385                    );
386                }
387            }
388            out.push_str(format!("({})", next_proto.green()).as_str());
389        } else {
390            // create with 17 byte capacity as this will be fixed len
391            let mut l2_src = String::with_capacity(17);
392            let mut l2_dst = String::with_capacity(17);
393
394            int_to_mac_str(&(self.l2_src.unwrap_or(0) as u64), &mut l2_src);
395            int_to_mac_str(&(self.l2_dst.unwrap_or(0) as u64), &mut l2_dst);
396
397            let mut ethertype = "----".to_string();
398            if let Some(et) = self.ethertype {
399                ethertype = et.resolve();
400            }
401
402            out.push_str(
403                format!(
404                    "{} → {} ({})",
405                    l2_src.magenta(),
406                    l2_dst.magenta(),
407                    ethertype.green(),
408                )
409                .as_str(),
410            );
411        }
412        out.push(']');
413        out
414    }
415}
416
417#[cfg(test)]
418mod tests {
419    use libc::timeval;
420    use pcap::{Packet, PacketHeader};
421
422    use super::*;
423
424    const REF_V4_PACKET: Packet = Packet {
425        header: &PacketHeader {
426            ts: timeval {
427                tv_sec: 0,
428                tv_usec: 0,
429            },
430            caplen: 77,
431            len: 77,
432        },
433        data: &[
434            0x0, 0x0, 0x0, 0x0, 0x0, 0x1, // dst
435            0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // src
436            0x81, 0x0, // 802.1q
437            0x5f, 0xff, // VLAN ID 4095
438            0x8, 0x0, // TPID IPv4
439            0x45, 0x0, 0x0, 0x3b, 0x0, 0x1, // default headers
440            0x0, 0x0, 0x40, 0x6, 0x3a, 0x12, // headers w/ TCP proto
441            0x7f, 0x0, 0x0, 0x1, // src 127.0.0.1
442            0xc0, 0xa8, 0x1, 0x1, // dst 192.168.1.1
443            0x0, 0x50, // sport 80
444            0x14, 0xeb, // dport 5355
445            0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // headers
446            0x50, 0x2, 0x20, 0x0, 0x76, 0x46, 0x0, 0x0, // headers
447            0x74, 0x65, 0x73, 0x74, 0x20, // test<space>
448            0x74, 0x65, 0x73, 0x74, 0x20, // test<space>
449            0x74, 0x65, 0x73, 0x74, 0x20, // test<space>
450            0x74, 0x65, 0x73, 0x74, // test
451        ],
452    };
453
454    const REF_V6_PACKET: Packet = Packet {
455        header: &PacketHeader {
456            ts: timeval {
457                tv_sec: 0,
458                tv_usec: 0,
459            },
460            caplen: 97,
461            len: 97,
462        },
463        data: &[
464            /* Ethernet + Dot1Q */
465            0x0, 0x0, 0x0, 0x0, 0x0, 0x1, // dst
466            0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // src
467            0x81, 0x0, // 802.1q
468            0x5f, 0xff, // VLAN ID 4095
469            0x86, 0xdd, // TPID IPv6
470            /* IPv6 */
471            0x60, 0x0, 0x0, 0x0, 0x0, 0x27, 0x0, 0x40, // defaults + Hop-By-Hop Next Header
472            0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
473            0x1, // src ::1
474            0x73, 0x57, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1,
475            0x23, // dst 7357::123
476            /* Hop-By-Hop */
477            0x3c, // Next Header
478            0x01, // Hdr Ext Len
479            b'A', b'A', b'A', b'A', b'A', b'A', b'A', b'A', b'A', b'A', b'A', b'A', b'A', b'A',
480            /* Destination Options */
481            0x2b, // Next Header
482            0x01, // Hdr Ext Len
483            b'B', b'B', b'B', b'B', b'B', b'B', b'B', b'B', b'B', b'B', b'B', b'B', b'B', b'B',
484            /* Routing */
485            0x2c, // Next Header
486            0x01, // Hdr Ext Len
487            0x04, // Routing Type
488            0x02, // Segments Left
489            b'C', b'C', b'C', b'C', b'C', b'C', b'C', b'C', b'C', b'C', b'C', b'C',
490            /* Fragment */
491            0x6,  // Next Header
492            0x00, // Reserved
493            0x00, 0x00, // Fragment Offset
494            0x00, 0x00, 0x04, 0xd2, // Identification
495            /* TCP */
496            0x0, 0x50, // sport 80
497            0x14, 0xeb, // dport 5355
498            0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // headers
499            0x50, 0x2, 0x20, 0x0, 0x42, 0x76, 0x0, 0x0, // headers
500            0x74, 0x65, 0x73, 0x74, 0x20, // test<space>
501            0x74, 0x65, 0x73, 0x74, 0x20, // test<space>
502            0x74, 0x65, 0x73, 0x74, 0x20, // test<space>
503            0x74, 0x65, 0x73, 0x74, // test
504        ],
505    };
506
507    #[test]
508    fn test_get_field() {
509        let data = &[0x01, 0x23, 0x34, 0x0f, 0xff, 0x56];
510        let expected = 4095;
511
512        let result = get_field(data, 3, 2);
513        assert!(result.is_ok());
514        assert_eq!(result.unwrap(), expected);
515    }
516
517    #[test]
518    fn test_int_to_mac_str() {
519        let expected = "00:00:00:00:00:01";
520
521        let mut result = String::new();
522        int_to_mac_str(&1u64, &mut result);
523        assert_eq!(result, expected);
524    }
525
526    #[test]
527    fn test_int_to_ipv6_str() {
528        let expected = "0000:0000:0000:0000:0000:0000:0000:0001";
529
530        let mut result = String::new();
531        int_to_ipv6_str(&1u128, &mut result);
532        assert_eq!(result, expected);
533    }
534
535    #[test]
536    fn test_int_to_ipv4_str() {
537        let expected = "0.0.0.1";
538
539        let mut result = String::new();
540        int_to_ipv4_str(&1u32, &mut result);
541        assert_eq!(result, expected);
542    }
543
544    #[test]
545    fn test_from_packet_v4() {
546        let pktsum = PacketSummary::from_packet(&REF_V4_PACKET, None);
547
548        assert_eq!(pktsum.l2_dst.unwrap(), 1, "l2_dst");
549        assert_eq!(pktsum.l2_src.unwrap(), 281474976710655, "l2_src");
550        assert_eq!(pktsum.ethertype.unwrap(), 2048, "ethertype");
551        assert_eq!(pktsum.vlan_id.unwrap(), 4095, "vlan_id");
552        assert_eq!(pktsum.l3_src.unwrap(), 2130706433, "l3_src");
553        assert_eq!(pktsum.l3_dst.unwrap(), 3232235777, "l3_dst");
554        assert_eq!(pktsum.next_proto.unwrap(), 6, "next_proto");
555        assert_eq!(pktsum.l4_sport.unwrap(), 80, "l4_sport");
556        assert_eq!(pktsum.l4_dport.unwrap(), 5355, "l4_dport");
557    }
558
559    #[test]
560    fn test_from_packet_v6() {
561        let pktsum = PacketSummary::from_packet(&REF_V6_PACKET, None);
562
563        assert_eq!(pktsum.l2_dst.unwrap(), 1, "l2_dst");
564        assert_eq!(pktsum.l2_src.unwrap(), 281474976710655, "l2_src");
565        assert_eq!(pktsum.ethertype.unwrap(), 34525, "ethertype");
566        assert_eq!(pktsum.vlan_id.unwrap(), 4095, "vlan_id");
567        assert_eq!(pktsum.l3_src.unwrap(), 1, "l3_src");
568        assert_eq!(
569            pktsum.l3_dst.unwrap(),
570            153312949341957855387619965112881774883,
571            "l3_dst"
572        );
573        assert_eq!(pktsum.next_proto.unwrap(), 6, "next_proto");
574        assert_eq!(pktsum.l4_sport.unwrap(), 80, "l4_sport");
575        assert_eq!(pktsum.l4_dport.unwrap(), 5355, "l4_dport");
576    }
577
578    #[test]
579    fn test_handle_eth() {
580        let mut pktsum = PacketSummary::new();
581        let expected = (14, ProtoHandler::VLAN);
582
583        let result = handle_eth(&REF_V4_PACKET, 0, &mut pktsum);
584        assert_eq!(result.unwrap(), expected, "offset");
585        assert_eq!(pktsum.l2_dst.unwrap(), 1, "l2_dst");
586        assert_eq!(pktsum.l2_src.unwrap(), 281474976710655, "l2_src");
587    }
588
589    #[test]
590    fn test_handle_vlan() {
591        let mut pktsum = PacketSummary::new();
592        let expected = (18, ProtoHandler::IPV4);
593
594        let result = handle_vlan(&REF_V4_PACKET, 14, &mut pktsum);
595        assert_eq!(result.unwrap(), expected, "offset");
596        assert_eq!(pktsum.ethertype.unwrap(), 2048, "ethertype");
597        assert_eq!(pktsum.vlan_id.unwrap(), 4095, "vlan_id");
598    }
599
600    #[test]
601    fn test_handle_ipv4() {
602        let mut pktsum = PacketSummary::new();
603        let expected = (38, ProtoHandler::TCP);
604
605        let result = handle_ipv4(&REF_V4_PACKET, 18, &mut pktsum);
606        assert_eq!(result.unwrap(), expected, "offset");
607        assert_eq!(pktsum.l3_src.unwrap(), 2130706433, "l3_src");
608        assert_eq!(pktsum.l3_dst.unwrap(), 3232235777, "l3_dst");
609        assert_eq!(pktsum.next_proto.unwrap(), 6, "next_proto");
610    }
611
612    #[test]
613    fn test_handle_ipv6() {
614        let mut pktsum = PacketSummary::new();
615        let expected = (114, ProtoHandler::TCP);
616
617        let result = handle_ipv6(&REF_V6_PACKET, 18, &mut pktsum);
618        assert_eq!(result.unwrap(), expected, "offset");
619        assert_eq!(pktsum.l3_src.unwrap(), 1, "l3_src");
620        assert_eq!(
621            pktsum.l3_dst.unwrap(),
622            153312949341957855387619965112881774883,
623            "l3_dst"
624        );
625        assert_eq!(pktsum.next_proto.unwrap(), 6, "next_proto");
626    }
627
628    #[test]
629    fn test_handle_unknown() {
630        let mut pktsum = PacketSummary::new();
631
632        let result = handle_unknown(&REF_V4_PACKET, 0, &mut pktsum);
633        assert!(result.is_err());
634    }
635}