Skip to main content

atmos/kernel/net/
arp.rs

1use alloc::string::String;
2use alloc::vec::Vec;
3
4pub fn arp_cache_lines() -> Vec<String> {
5    let mut lines: Vec<String> = Vec::new();
6
7    unsafe {
8        for i in 0..super::ARP_CACHE_SIZE {
9            let e = super::ARP_CACHE[i];
10            if !e.valid {
11                continue;
12            }
13            lines.push(alloc::format!(
14                "{} -> {} (age={})",
15                super::format_ipv4(e.ip),
16                super::format_mac(e.mac),
17                e.age
18            ));
19        }
20    }
21
22    lines
23}
24
25pub fn build_arp_request(target_ip: [u8; 4], out: &mut [u8]) -> Result<usize, &'static str> {
26    if out.len() < 42 {
27        return Err("buffer too small");
28    }
29
30    let st = super::status();
31
32    out[0..6].copy_from_slice(&[0xFF; 6]);
33    out[6..12].copy_from_slice(&st.mac);
34    out[12] = 0x08;
35    out[13] = 0x06;
36
37    out[14] = 0x00;
38    out[15] = 0x01;
39    out[16] = 0x08;
40    out[17] = 0x00;
41    out[18] = 0x06;
42    out[19] = 0x04;
43    out[20] = 0x00;
44    out[21] = 0x01;
45
46    out[22..28].copy_from_slice(&st.mac);
47    out[28..32].copy_from_slice(&st.ip);
48    out[32..38].copy_from_slice(&[0x00; 6]);
49    out[38..42].copy_from_slice(&target_ip);
50
51    unsafe {
52        super::NET_STATE.tx_packets = super::NET_STATE.tx_packets.saturating_add(1);
53    }
54
55    Ok(42)
56}
57
58
59
60pub fn ingest_ethernet_frame(frame: &[u8]) -> Result<(), &'static str> {
61    // 型判定・宛先フィルタより前に無条件でカウントする診断用カウンタ
62    // (`net.nic` で確認可能。「そもそも NIC に何か届いているか」の切り分け用)。
63    unsafe {
64        super::NET_STATE.raw_rx_frames = super::NET_STATE.raw_rx_frames.saturating_add(1);
65    }
66
67    // 【2026-07-30】振り分けは検証済みモジュールへ一本化
68    // (`kernel/net/eth_frame.rs`、単体試験 13 件)。
69    // 旧実装には 2 つの欠落があった:
70    //  - **VLAN タグ(802.1Q, EtherType 0x8100)を「未対応」として捨てていた**。
71    //    タグ付き環境では一切通信できない。
72    //  - ペイロード開始を 14 固定にしていたため、VLAN 時にずれる。
73    // 長さ不足・タグ切れの境界も型で弾く。
74    use super::eth_frame::{dispatch, Dispatch, FrameError};
75    // 【2026-07-30診断】DNS 応答が届かない件。フレームが NIC に来ているのに
76    // IPv4 として振り分けられていないのか、そもそも来ていないのかを切り分ける。
77    let d = dispatch(frame);
78    unsafe {
79        match &d {
80            Ok(Dispatch::Arp) => ETH_CNT_ARP += 1,
81            Ok(Dispatch::Ipv4 { .. }) => ETH_CNT_IPV4 += 1,
82            Ok(Dispatch::Unsupported { .. }) => ETH_CNT_OTHER += 1,
83            Err(_) => ETH_CNT_ERR += 1,
84        }
85    }
86    match d {
87        Ok(Dispatch::Arp) => {
88            parse_arp_packet(frame)?;
89            Ok(())
90        }
91        Ok(Dispatch::Ipv4 { start, end }) => {
92            let payload = frame.get(start..end).ok_or("ipv4 payload out of range")?;
93            parse_ipv4_packet(payload)?;
94            Ok(())
95        }
96        Ok(Dispatch::Unsupported { .. }) => Err("unsupported ethertype"),
97        Err(FrameError::TooShort { .. }) => Err("ethernet frame too short"),
98        Err(FrameError::TruncatedVlan { .. }) => Err("truncated vlan tag"),
99    }
100}
101
102/// 【2026-07-30診断】Ethernet 振り分けの種別カウンタ。
103pub static mut ETH_CNT_IPV4: u32 = 0;
104pub static mut ETH_CNT_ARP: u32 = 0;
105pub static mut ETH_CNT_OTHER: u32 = 0;
106pub static mut ETH_CNT_ERR: u32 = 0;
107
108/// 種別カウンタのスナップショット (ipv4, arp, other, err)。
109pub fn eth_dispatch_counts() -> (u32, u32, u32, u32) {
110    unsafe { (ETH_CNT_IPV4, ETH_CNT_ARP, ETH_CNT_OTHER, ETH_CNT_ERR) }
111}
112
113pub fn ingest_nic_ethernet_frame(frame: &[u8]) -> Result<(), &'static str> {
114    match super::status().nic_backend {
115        super::NicBackend::SoftwareLoopback => ingest_ethernet_frame(frame),
116        super::NicBackend::UsbEthernet => ingest_ethernet_frame(frame),
117    }
118}
119
120pub fn ingest_nic_ipv4_packet(packet: &[u8]) -> Result<(), &'static str> {
121    match super::status().nic_backend {
122        super::NicBackend::SoftwareLoopback => parse_ipv4_packet(packet),
123        super::NicBackend::UsbEthernet => parse_ipv4_packet(packet),
124    }
125}
126
127pub fn nic_pop_tx_ipv4_packet(out: &mut [u8]) -> Option<usize> {
128    nic_take_tx_ipv4_chunk(out.len(), out)
129}
130
131pub fn nic_take_tx_ipv4_chunk(max_len: usize, out: &mut [u8]) -> Option<usize> {
132    if max_len == 0 || out.is_empty() {
133        return None;
134    }
135
136    unsafe {
137        let mut best_idx: Option<usize> = None;
138        let mut best_age: u32 = 0;
139
140        for i in 0..super::NIC_TX_QUEUE_SIZE {
141            // 【2026-08-05】ここは `let e = super::NIC_TX_QUEUE[i];` と
142            // **値でコピー**していた。`NicTxEntry` は `payload: [u8; 1536]` を
143            // 内包するので 1 要素 1552 バイト、64 要素で
144            // **1 回の走査につき約 99KB の memcpy** になっていた。
145            //
146            // しかもこの関数は `poll_usb_ethernet_data_plane` の先頭の
147            // `while let Some(..)` 条件なので、**収穫のたびに必ず 1 回以上**通る。
148            // 実測で収穫は 1 ブートの取得中に 2000 回以上呼ばれる。
149            //
150            // 読みたいのは `valid` と `age` の 2 フィールドだけ。参照で足りる。
151            // 計算量は O(Q × P) から O(Q) へ(Q=64 エントリ、P=1536 バイト)。
152            let e = &super::NIC_TX_QUEUE[i];
153            if !e.valid {
154                continue;
155            }
156            if best_idx.is_none() || e.age < best_age {
157                best_idx = Some(i);
158                best_age = e.age;
159            }
160        }
161
162        let idx = best_idx?;
163        let entry = &mut super::NIC_TX_QUEUE[idx];
164        if entry.len == 0 || entry.offset >= entry.len {
165            // 【2026-08-05】`= NicTxEntry::empty()` は payload 1536 バイトを
166            // ゼロ埋めして丸ごと代入する。空きにするだけなら `valid` を
167            // 下ろせば足りる(再利用時は `len` の範囲しか読まないので、
168            // 残っている中身が漏れることはない)。
169            // 送信のたびに通る経路なので 1552 バイトの書き込みを丸ごと省く。
170            entry.valid = false;
171            entry.len = 0;
172            entry.offset = 0;
173            return None;
174        }
175
176        let remain = entry.len - entry.offset;
177        let chunk_len = remain.min(max_len).min(out.len());
178        let start = entry.offset;
179        let end = start + chunk_len;
180        out[..chunk_len].copy_from_slice(&entry.payload[start..end]);
181        entry.offset = end;
182
183        if entry.offset >= entry.len {
184            // 同上。ゼロ埋めせずに空き印だけ下ろす。
185            entry.valid = false;
186            entry.len = 0;
187            entry.offset = 0;
188        }
189
190        Some(chunk_len)
191    }
192}
193
194pub fn transmit_nic_ipv4_packet(packet: &[u8]) -> Result<usize, &'static str> {
195    if packet.is_empty() {
196        return Err("empty packet");
197    }
198    if packet.len() > super::NIC_TX_MAX_PAYLOAD {
199        return Err("packet too large");
200    }
201
202    match super::status().nic_backend {
203        super::NicBackend::SoftwareLoopback => {
204            unsafe {
205                super::NET_STATE.tx_packets = super::NET_STATE.tx_packets.saturating_add(1);
206            }
207            Ok(packet.len())
208        }
209        super::NicBackend::UsbEthernet => {
210            // 【2026-08-05】以前は
211            //   1. スタックに 1536 バイトの `framed` を**ゼロ初期化**
212            //   2. そこへフレームを組み立てる
213            //   3. スタックに `NicTxEntry`(payload 1536 バイト)を**ゼロ初期化**
214            //   4. `framed` から `entry.payload` へコピー
215            //   5. `entry` をキューへ**丸ごとコピー**
216            // と、1 パケット送るのに 1536 バイト級の操作を 4 回行っていた。
217            //
218            // ACK は受信セグメントごとに送るので、3.7MB の取得では
219            // 13000 回以上通る(実測 `frames=13864`)。
220            //
221            // 先に空きスロットを決めて、**そこへ直接組み立てる**。
222            // 1536 バイト級の操作は 0 回になる。
223            unsafe {
224                let mut slot: Option<usize> = None;
225                for i in 0..super::NIC_TX_QUEUE_SIZE {
226                    if !super::NIC_TX_QUEUE[i].valid {
227                        slot = Some(i);
228                        break;
229                    }
230                }
231
232                let idx = match slot {
233                    Some(v) => v,
234                    None => {
235                        // 【2026-07-26診断】送信キュー(8スロット)の枯渇は、呼び出し
236                        // 側の大半が `let _ = transmit_...` で戻り値を捨てていたため
237                        // **完全に不可視**だった。ACK やデータが黙って落ちれば相手
238                        // からは無応答に見え、接続回数依存のタイムアウト多発を
239                        // 説明しうる。まず実際に溢れているかを計測する。
240                        super::NIC_TX_DROPS = super::NIC_TX_DROPS.saturating_add(1);
241                        if super::NIC_TX_DROPS <= 4 || super::NIC_TX_DROPS.is_multiple_of(16) {
242                            crate::warn!(
243                                "[NET][DIAG] nic tx queue FULL (size={}) dropped_total={}",
244                                super::NIC_TX_QUEUE_SIZE,
245                                super::NIC_TX_DROPS
246                            );
247                        }
248                        return Err("nic tx queue full");
249                    }
250                };
251
252                // 組み立て先はキューのスロットそのもの。
253                // 失敗したらスロットは `valid=false` のままなので、
254                // 中途半端な内容が送られることはない。
255                let entry = &mut super::NIC_TX_QUEUE[idx];
256                let frame_len = build_usb_ethernet_ipv4_frame(packet, &mut entry.payload)?;
257                entry.valid = true;
258                entry.len = frame_len;
259                entry.offset = 0;
260                entry.age = super::NIC_TX_TICK;
261                super::NIC_TX_TICK = super::NIC_TX_TICK.saturating_add(1);
262
263                super::NET_STATE.tx_packets = super::NET_STATE.tx_packets.saturating_add(1);
264                Ok(frame_len)
265            }
266        }
267    }
268}
269
270pub fn transmit_nic_ethernet_frame(frame: &[u8]) -> Result<usize, &'static str> {
271    if frame.is_empty() {
272        return Err("empty frame");
273    }
274    if frame.len() > super::NIC_TX_MAX_PAYLOAD {
275        return Err("frame too large");
276    }
277
278    match super::status().nic_backend {
279        super::NicBackend::SoftwareLoopback => {
280            unsafe {
281                super::NET_STATE.tx_packets = super::NET_STATE.tx_packets.saturating_add(1);
282            }
283            Ok(frame.len())
284        }
285        super::NicBackend::UsbEthernet => {
286            unsafe {
287                let mut slot: Option<usize> = None;
288                for i in 0..super::NIC_TX_QUEUE_SIZE {
289                    if !super::NIC_TX_QUEUE[i].valid {
290                        slot = Some(i);
291                        break;
292                    }
293                }
294
295                let idx = match slot {
296                    Some(v) => v,
297                    None => {
298                        // 【2026-07-26診断】送信キュー(8スロット)の枯渇は、呼び出し
299                        // 側の大半が `let _ = transmit_...` で戻り値を捨てていたため
300                        // **完全に不可視**だった。ACK やデータが黙って落ちれば相手
301                        // からは無応答に見え、接続回数依存のタイムアウト多発を
302                        // 説明しうる。まず実際に溢れているかを計測する。
303                        super::NIC_TX_DROPS = super::NIC_TX_DROPS.saturating_add(1);
304                        if super::NIC_TX_DROPS <= 4 || super::NIC_TX_DROPS.is_multiple_of(16) {
305                            crate::warn!(
306                                "[NET][DIAG] nic tx queue FULL (size={}) dropped_total={}",
307                                super::NIC_TX_QUEUE_SIZE,
308                                super::NIC_TX_DROPS
309                            );
310                        }
311                        return Err("nic tx queue full");
312                    }
313                };
314
315                let mut entry = super::NicTxEntry::empty();
316                entry.valid = true;
317                entry.len = frame.len();
318                entry.offset = 0;
319                entry.payload[..frame.len()].copy_from_slice(frame);
320                entry.age = super::NIC_TX_TICK;
321                super::NIC_TX_TICK = super::NIC_TX_TICK.saturating_add(1);
322                super::NIC_TX_QUEUE[idx] = entry;
323
324                super::NET_STATE.tx_packets = super::NET_STATE.tx_packets.saturating_add(1);
325            }
326            Ok(frame.len())
327        }
328    }
329}
330
331pub fn get_next_hop_ip(dst_ip: [u8; 4]) -> [u8; 4] {
332    let st = super::status();
333    if dst_ip == [255, 255, 255, 255] {
334        return dst_ip;
335    }
336    let mask = if st.dhcp_subnet == [0; 4] {
337        [255, 255, 255, 0]
338    } else {
339        st.dhcp_subnet
340    };
341    let is_local = (dst_ip[0] & mask[0] == st.ip[0] & mask[0])
342        && (dst_ip[1] & mask[1] == st.ip[1] & mask[1])
343        && (dst_ip[2] & mask[2] == st.ip[2] & mask[2])
344        && (dst_ip[3] & mask[3] == st.ip[3] & mask[3]);
345
346    if is_local {
347        dst_ip
348    } else if st.gateway != [0; 4] {
349        st.gateway
350    } else {
351        dst_ip
352    }
353}
354
355fn build_usb_ethernet_ipv4_frame(packet: &[u8], out: &mut [u8]) -> Result<usize, &'static str> {
356    if packet.len() < 20 {
357        return Err("ipv4 packet too short");
358    }
359
360    let total_len = (((packet[2] as u16) << 8) | packet[3] as u16) as usize;
361    if total_len < 20 || total_len > packet.len() {
362        return Err("invalid ipv4 total length");
363    }
364
365    let dst_ip = [packet[16], packet[17], packet[18], packet[19]];
366    let src_ip = [packet[12], packet[13], packet[14], packet[15]];
367
368    let next_hop = get_next_hop_ip(dst_ip);
369
370    let dst_mac = if dst_ip == [255, 255, 255, 255] {
371        [0xFF; 6]
372    } else if let Some(mac) = arp_cache_lookup(next_hop) {
373        mac
374    } else if src_ip == [0, 0, 0, 0] {
375        [0xFF; 6]
376    } else if unsafe {
377        let now = crate::kernel::timer::get_system_time_ms();
378        super::ARP_NEG_IP == next_hop && now < super::ARP_NEG_UNTIL_MS
379    } {
380        // 直近この宛先への ARP 解決が失敗している。DNS の複数回リトライが同じ
381        // 到達不能な next-hop に対して毎回 2秒のブロッキング再試行を払うのを防ぐ。
382        crate::debug!(
383            "ARP: skipping resolve for {} (recently failed, negative-cached)",
384            super::format_ipv4(next_hop)
385        );
386        return Err("destination mac unresolved (recently failed)");
387    } else {
388        crate::warn!("[NET] ARP: resolving {}", super::format_ipv4(next_hop));
389        let mut resolved_mac = None;
390        // Send ARP request and retry up to 4 times (500 ms each = 2 s total).
391        'outer: for attempt in 0..4u32 {
392            let mut arp_req = [0u8; 42];
393            // ARP 要求を送れていないのに 500ms 待って「応答が無い」と結論づける
394            // のは誤診の元なので、組み立て・送出のどちらの失敗も必ず記録する。
395            match build_arp_request(next_hop, &mut arp_req) {
396                Ok(arp_len) => {
397                    if let Err(e) = transmit_nic_ethernet_frame(&arp_req[..arp_len]) {
398                        crate::warn!(
399                            "[NET] ARP: request transmit failed for {}: {}",
400                            super::format_ipv4(next_hop),
401                            e
402                        );
403                    }
404                }
405                Err(e) => {
406                    crate::warn!(
407                        "[NET] ARP: failed to build request for {}: {}",
408                        super::format_ipv4(next_hop),
409                        e
410                    );
411                }
412            }
413            let start = crate::kernel::timer::get_system_time_ms();
414            while crate::kernel::timer::get_system_time_ms().wrapping_sub(start) < 500 {
415                crate::kernel::usb::poll_ethernet_data_plane_only();
416                if let Some(mac) = arp_cache_lookup(next_hop) {
417                    resolved_mac = Some(mac);
418                    break 'outer;
419                }
420                // フリーズ対策: スリープで完全に待つのではなく yield_now() で
421                // パケット回収を高頻度で継続しつつ、他タスクへ CPU を譲る。
422                crate::kernel::scheduler::yield_now();
423            }
424            crate::warn!(
425                "ARP: no reply for {} (attempt {})",
426                super::format_ipv4(next_hop),
427                attempt + 1
428            );
429        }
430
431        if let Some(mac) = resolved_mac {
432            crate::warn!(
433                "ARP: resolved {} -> {}",
434                super::format_ipv4(next_hop),
435                super::format_mac(mac)
436            );
437            mac
438        } else {
439            // 3秒間はこの宛先への再解決をスキップする(DNS の後続リトライを高速失敗させる)。
440            unsafe {
441                super::ARP_NEG_IP = next_hop;
442                super::ARP_NEG_UNTIL_MS = crate::kernel::timer::get_system_time_ms() + 3000;
443            }
444            return Err("destination mac unresolved");
445        }
446    };
447
448    let frame_len = 14usize + total_len;
449    if frame_len > out.len() {
450        return Err("ethernet frame too large");
451    }
452
453    let st = super::status();
454    out[0..6].copy_from_slice(&dst_mac);
455    out[6..12].copy_from_slice(&st.mac);
456    out[12] = 0x08;
457    out[13] = 0x00;
458    out[14..14 + total_len].copy_from_slice(&packet[..total_len]);
459    Ok(frame_len)
460}
461
462pub(crate) fn arp_cache_lookup(ip: [u8; 4]) -> Option<[u8; 6]> {
463    unsafe {
464        let table = &*core::ptr::addr_of!(super::ARP_CACHE);
465        super::arp_cache::lookup(table, ip)
466    }
467}
468
469/// 表の操作判断そのものは純粋モジュール `arp_cache` へ委譲する
470/// (満杯時の LRU 退避等はホスト側ユニットテストで検証済み。
471/// `tests/src/test_arp_cache.rs`)。ここは age の採番・グローバル配列への
472/// アクセス・ログ出力という副作用のみを担当する。
473fn arp_cache_upsert(ip: [u8; 4], mac: [u8; 6]) {
474    unsafe {
475        let age = super::ARP_TICK;
476        super::ARP_TICK = super::ARP_TICK.saturating_add(1);
477
478        let table = &mut *core::ptr::addr_of_mut!(super::ARP_CACHE);
479        let outcome = super::arp_cache::upsert(table, ip, mac, age);
480
481        if matches!(
482            outcome,
483            super::arp_cache::UpsertOutcome::Inserted | super::arp_cache::UpsertOutcome::Evicted
484        ) {
485            crate::warn!(
486                "ARP: learned {} -> {}",
487                super::format_ipv4(ip),
488                super::format_mac(mac)
489            );
490        }
491    }
492}
493
494fn parse_arp_packet(frame: &[u8]) -> Result<(), &'static str> {
495    if frame.len() < 42 {
496        return Err("arp frame too short");
497    }
498
499    let htype = ((frame[14] as u16) << 8) | frame[15] as u16;
500    let ptype = ((frame[16] as u16) << 8) | frame[17] as u16;
501    let hlen = frame[18];
502    let plen = frame[19];
503    let oper = ((frame[20] as u16) << 8) | frame[21] as u16;
504
505    if htype != 1 || ptype != 0x0800 || hlen != 6 || plen != 4 {
506        return Err("unsupported arp format");
507    }
508    if oper != 1 && oper != 2 {
509        return Err("unsupported arp operation");
510    }
511
512    let sender_mac = [
513        frame[22], frame[23], frame[24], frame[25], frame[26], frame[27],
514    ];
515    let sender_ip = [frame[28], frame[29], frame[30], frame[31]];
516    let target_ip = [frame[38], frame[39], frame[40], frame[41]];
517
518    arp_cache_upsert(sender_ip, sender_mac);
519
520    unsafe {
521        super::NET_STATE.rx_packets = super::NET_STATE.rx_packets.saturating_add(1);
522
523        if oper == 1 && target_ip == super::NET_STATE.ip {
524            let mut reply = [0u8; 42];
525
526            reply[0..6].copy_from_slice(&sender_mac);
527            reply[6..12].copy_from_slice(&super::NET_STATE.mac);
528            reply[12] = 0x08;
529            reply[13] = 0x06;
530
531            reply[14] = 0x00;
532            reply[15] = 0x01;
533            reply[16] = 0x08;
534            reply[17] = 0x00;
535            reply[18] = 6;
536            reply[19] = 4;
537            reply[20] = 0x00;
538            reply[21] = 0x02;
539
540            reply[22..28].copy_from_slice(&super::NET_STATE.mac);
541            reply[28..32].copy_from_slice(&super::NET_STATE.ip);
542
543            reply[32..38].copy_from_slice(&sender_mac);
544            reply[38..42].copy_from_slice(&sender_ip);
545
546            // ARP 応答を返せないと相手からこちらへ一切届かなくなる。
547            // 受信処理からは呼び出し側へ返す先が無いため、必ず記録する。
548            if let Err(e) = transmit_nic_ethernet_frame(&reply) {
549                crate::warn!(
550                    "[NET] ARP: reply transmit failed to {}: {}",
551                    super::format_ipv4(sender_ip),
552                    e
553                );
554            }
555        }
556    }
557
558    Ok(())
559}
560
561fn parse_ipv4_packet(packet: &[u8]) -> Result<(), &'static str> {
562    if packet.len() < 20 {
563        return Err("ipv4 packet too short");
564    }
565
566    let version = packet[0] >> 4;
567    if version != 4 {
568        return Err("invalid ipv4 version");
569    }
570
571    let ihl = ((packet[0] & 0x0F) as usize) * 4;
572    if ihl < 20 || packet.len() < ihl {
573        return Err("invalid ipv4 header length");
574    }
575
576    let total_len = (((packet[2] as u16) << 8) | packet[3] as u16) as usize;
577    if total_len < ihl || total_len > packet.len() {
578        return Err("invalid ipv4 total length");
579    }
580
581    match packet[9] {
582        1 => {
583            // 診断用: ICMP パケットが届いた事実そのものを可視化する(echo reply として
584            // 受理できたかに関わらず)。「応答パケットは届いているが解析で弾かれている」のか
585            // 「そもそも届いていない」のかを切り分けるため。
586            let src_ip = [packet[12], packet[13], packet[14], packet[15]];
587            match super::icmp::parse_icmp_echo_reply_ipv4(packet) {
588                Ok(reply) => {
589                    crate::warn!(
590                        "ICMP: echo reply accepted from {} (id={:#06X} seq={})",
591                        super::format_ipv4(src_ip),
592                        reply.identifier,
593                        reply.sequence
594                    );
595                    unsafe {
596                        super::LAST_PING_REPLY = Some(reply);
597                    }
598                }
599                Err(e) => {
600                    crate::warn!(
601                        "ICMP: packet from {} rejected: {}",
602                        super::format_ipv4(src_ip),
603                        e
604                    );
605                }
606            }
607            Ok(())
608        }
609        17 => super::udp::parse_udp_ipv4_packet(packet),
610        6 => super::tcp::parse_tcp_ipv4_packet(packet),
611        _ => Err("unsupported ipv4 protocol"),
612    }
613}
614
615