]> git.saurik.com Git - apple/xnu.git/blame - bsd/netccitt/hd_input.c
xnu-201.19.tar.gz
[apple/xnu.git] / bsd / netccitt / hd_input.c
CommitLineData
1c79356b
A
1/*
2 * Copyright (c) 2000 Apple Computer, Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * The contents of this file constitute Original Code as defined in and
7 * are subject to the Apple Public Source License Version 1.1 (the
8 * "License"). You may not use this file except in compliance with the
9 * License. Please obtain a copy of the License at
10 * http://www.apple.com/publicsource and read it before using this file.
11 *
12 * This Original Code and all software distributed under the License are
13 * distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER
14 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
15 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT. Please see the
17 * License for the specific language governing rights and limitations
18 * under the License.
19 *
20 * @APPLE_LICENSE_HEADER_END@
21 */
22/*
23 * Copyright (c) University of British Columbia, 1984
24 * Copyright (c) 1990, 1993
25 * The Regents of the University of California. All rights reserved.
26 *
27 * This code is derived from software contributed to Berkeley by
28 * the Laboratory for Computation Vision and the Computer Science Department
29 * of the University of British Columbia.
30 *
31 * Redistribution and use in source and binary forms, with or without
32 * modification, are permitted provided that the following conditions
33 * are met:
34 * 1. Redistributions of source code must retain the above copyright
35 * notice, this list of conditions and the following disclaimer.
36 * 2. Redistributions in binary form must reproduce the above copyright
37 * notice, this list of conditions and the following disclaimer in the
38 * documentation and/or other materials provided with the distribution.
39 * 3. All advertising materials mentioning features or use of this software
40 * must display the following acknowledgement:
41 * This product includes software developed by the University of
42 * California, Berkeley and its contributors.
43 * 4. Neither the name of the University nor the names of its contributors
44 * may be used to endorse or promote products derived from this software
45 * without specific prior written permission.
46 *
47 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
48 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
49 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
50 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
51 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
52 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
53 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
54 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
55 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
56 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
57 * SUCH DAMAGE.
58 *
59 * @(#)hd_input.c 8.1 (Berkeley) 6/10/93
60 */
61
62#include <sys/param.h>
63#include <sys/systm.h>
64#include <sys/mbuf.h>
65#include <sys/domain.h>
66#include <sys/socket.h>
67#include <sys/protosw.h>
68#include <sys/errno.h>
69#include <sys/time.h>
70#include <sys/kernel.h>
71
72#include <net/if.h>
73
74#include <netccitt/hdlc.h>
75#include <netccitt/hd_var.h>
76#include <netccitt/x25.h>
77
78static frame_reject();
79static rej_routine();
80static free_iframes();
81/*
82 * HDLC INPUT INTERFACE
83 *
84 * This routine is called when the HDLC physical device has
85 * completed reading a frame.
86 */
87
88hdintr ()
89{
90 register struct mbuf *m;
91 register struct hdcb *hdp;
92 register struct ifnet *ifp;
93 register int s;
94 static struct ifnet *lastifp;
95 static struct hdcb *lasthdp;
96
97 for (;;) {
98 s = splimp ();
99 IF_DEQUEUE (&hdintrq, m);
100 splx (s);
101 if (m == 0)
102 break;
103 if (m->m_len < HDHEADERLN) {
104 printf ("hdintr: packet too short (len=%d)\n",
105 m->m_len);
106 m_freem (m);
107 continue;
108 }
109 if ((m->m_flags & M_PKTHDR) == 0)
110 panic("hdintr");
111 ifp = m->m_pkthdr.rcvif;
112
113 /*
114 * look up the appropriate hdlc control block
115 */
116
117 if (ifp == lastifp)
118 hdp = lasthdp;
119 else {
120 for (hdp = hdcbhead; hdp; hdp = hdp->hd_next)
121 if (hdp->hd_ifp == ifp)
122 break;
123 if (hdp == 0) {
124 printf ("hdintr: unknown interface %x\n", ifp);
125 m_freem (m);
126 continue;
127 }
128 lastifp = ifp;
129 lasthdp = hdp;
130 }
131
132 /* Process_rxframe returns FALSE if the frame was NOT queued
133 for the next higher layers. */
134 if (process_rxframe (hdp, m) == FALSE)
135 m_freem (m);
136 }
137}
138
139process_rxframe (hdp, fbuf)
140register struct hdcb *hdp;
141register struct mbuf *fbuf;
142{
143 register int queued = FALSE, frametype, pf;
144 register struct Hdlc_frame *frame;
145
146 frame = mtod (fbuf, struct Hdlc_frame *);
147 pf = ((struct Hdlc_iframe *) frame) -> pf;
148
149 hd_trace (hdp, RX, frame);
150 if (frame -> address != ADDRESS_A && frame -> address != ADDRESS_B)
151 return (queued);
152
153 switch ((frametype = hd_decode (hdp, frame)) + hdp->hd_state) {
154 case DM + DISC_SENT:
155 case UA + DISC_SENT:
156 /*
157 * Link now closed. Leave timer running
158 * so hd_timer() can periodically check the
159 * status of interface driver flag bit IFF_UP.
160 */
161 hdp->hd_state = DISCONNECTED;
162 break;
163
164 case DM + INIT:
165 case UA + INIT:
166 /*
167 * This is a non-standard state change needed for DCEs
168 * that do dynamic link selection. We can't go into the
169 * usual "SEND DM" state because a DM is a SARM in LAP.
170 */
171 hd_writeinternal (hdp, SABM, POLLOFF);
172 hdp->hd_state = SABM_SENT;
173 SET_TIMER (hdp);
174 break;
175
176 case SABM + DM_SENT:
177 case SABM + WAIT_SABM:
178 hd_writeinternal (hdp, UA, pf);
179 case UA + SABM_SENT:
180 case UA + WAIT_UA:
181 KILL_TIMER (hdp);
182 hd_initvars (hdp);
183 hdp->hd_state = ABM;
184 hd_message (hdp, "Link level operational");
185 /* Notify the packet level - to send RESTART. */
186 (void) pk_ctlinput (PRC_LINKUP, hdp->hd_pkp);
187 break;
188
189 case SABM + SABM_SENT:
190 /* Got a SABM collision. Acknowledge the remote's SABM
191 via UA but still wait for UA. */
192 hd_writeinternal (hdp, UA, pf);
193 break;
194
195 case SABM + ABM:
196 /* Request to reset the link from the remote. */
197 KILL_TIMER (hdp);
198 hd_message (hdp, "Link reset");
199#ifdef HDLCDEBUG
200 hd_dumptrace (hdp);
201#endif
202 hd_flush (hdp->hd_ifp);
203 hd_writeinternal (hdp, UA, pf);
204 hd_initvars (hdp);
205 (void) pk_ctlinput (PRC_LINKRESET, hdp->hd_pkp);
206 hdp->hd_resets++;
207 break;
208
209 case SABM + WAIT_UA:
210 hd_writeinternal (hdp, UA, pf);
211 break;
212
213 case DM + ABM:
214 hd_message (hdp, "DM received: link down");
215#ifdef HDLCDEBUG
216 hd_dumptrace (hdp);
217#endif
218 (void) pk_ctlinput (PRC_LINKDOWN, hdp->hd_pkp);
219 hd_flush (hdp->hd_ifp);
220 case DM + DM_SENT:
221 case DM + WAIT_SABM:
222 case DM + WAIT_UA:
223 hd_writeinternal (hdp, SABM, pf);
224 hdp->hd_state = SABM_SENT;
225 SET_TIMER (hdp);
226 break;
227
228 case DISC + INIT:
229 case DISC + DM_SENT:
230 case DISC + SABM_SENT:
231 /* Note: This is a non-standard state change. */
232 hd_writeinternal (hdp, UA, pf);
233 hd_writeinternal (hdp, SABM, POLLOFF);
234 hdp->hd_state = SABM_SENT;
235 SET_TIMER (hdp);
236 break;
237
238 case DISC + WAIT_UA:
239 hd_writeinternal (hdp, DM, pf);
240 SET_TIMER (hdp);
241 hdp->hd_state = DM_SENT;
242 break;
243
244 case DISC + ABM:
245 hd_message (hdp, "DISC received: link down");
246 (void) pk_ctlinput (PRC_LINKDOWN, hdp->hd_pkp);
247 case DISC + WAIT_SABM:
248 hd_writeinternal (hdp, UA, pf);
249 hdp->hd_state = DM_SENT;
250 SET_TIMER (hdp);
251 break;
252
253 case UA + ABM:
254 hd_message (hdp, "UA received: link down");
255 (void) pk_ctlinput (PRC_LINKDOWN, hdp->hd_pkp);
256 case UA + WAIT_SABM:
257 hd_writeinternal (hdp, DM, pf);
258 hdp->hd_state = DM_SENT;
259 SET_TIMER (hdp);
260 break;
261
262 case FRMR + DM_SENT:
263 hd_writeinternal (hdp, SABM, pf);
264 hdp->hd_state = SABM_SENT;
265 SET_TIMER (hdp);
266 break;
267
268 case FRMR + WAIT_SABM:
269 hd_writeinternal (hdp, DM, pf);
270 hdp->hd_state = DM_SENT;
271 SET_TIMER (hdp);
272 break;
273
274 case FRMR + ABM:
275 hd_message (hdp, "FRMR received: link down");
276 (void) pk_ctlinput (PRC_LINKDOWN, hdp->hd_pkp);
277#ifdef HDLCDEBUG
278 hd_dumptrace (hdp);
279#endif
280 hd_flush (hdp->hd_ifp);
281 hd_writeinternal (hdp, SABM, pf);
282 hdp->hd_state = WAIT_UA;
283 SET_TIMER (hdp);
284 break;
285
286 case RR + ABM:
287 case RNR + ABM:
288 case REJ + ABM:
289 process_sframe (hdp, (struct Hdlc_sframe *)frame, frametype);
290 break;
291
292 case IFRAME + ABM:
293 queued = process_iframe (hdp, fbuf, (struct Hdlc_iframe *)frame);
294 break;
295
296 case IFRAME + SABM_SENT:
297 case RR + SABM_SENT:
298 case RNR + SABM_SENT:
299 case REJ + SABM_SENT:
300 hd_writeinternal (hdp, DM, POLLON);
301 hdp->hd_state = DM_SENT;
302 SET_TIMER (hdp);
303 break;
304
305 case IFRAME + WAIT_SABM:
306 case RR + WAIT_SABM:
307 case RNR + WAIT_SABM:
308 case REJ + WAIT_SABM:
309 hd_writeinternal (hdp, FRMR, POLLOFF);
310 SET_TIMER (hdp);
311 break;
312
313 case ILLEGAL + SABM_SENT:
314 hdp->hd_unknown++;
315 hd_writeinternal (hdp, DM, POLLOFF);
316 hdp->hd_state = DM_SENT;
317 SET_TIMER (hdp);
318 break;
319
320 case ILLEGAL + ABM:
321 hd_message (hdp, "Unknown frame received: link down");
322 (void) pk_ctlinput (PRC_LINKDOWN, hdp->hd_pkp);
323 case ILLEGAL + WAIT_SABM:
324 hdp->hd_unknown++;
325#ifdef HDLCDEBUG
326 hd_dumptrace (hdp);
327#endif
328 hd_writeinternal (hdp, FRMR, POLLOFF);
329 hdp->hd_state = WAIT_SABM;
330 SET_TIMER (hdp);
331 break;
332 }
333
334 return (queued);
335}
336
337process_iframe (hdp, fbuf, frame)
338register struct hdcb *hdp;
339struct mbuf *fbuf;
340register struct Hdlc_iframe *frame;
341{
342 register int nr = frame -> nr,
343 ns = frame -> ns,
344 pf = frame -> pf;
345 register int queued = FALSE;
346
347 /*
348 * Validate the iframe's N(R) value. It's N(R) value must be in
349 * sync with our V(S) value and our "last received nr".
350 */
351
352 if (valid_nr (hdp, nr, FALSE) == FALSE) {
353 frame_reject (hdp, Z, frame);
354 return (queued);
355 }
356
357
358 /*
359 * This section tests the IFRAME for proper sequence. That is, it's
360 * sequence number N(S) MUST be equal to V(S).
361 */
362
363 if (ns != hdp->hd_vr) {
364 hdp->hd_invalid_ns++;
365 if (pf || (hdp->hd_condition & REJ_CONDITION) == 0) {
366 hdp->hd_condition |= REJ_CONDITION;
367 /*
368 * Flush the transmit queue. This is ugly but we
369 * have no choice. A reject response must be
370 * immediately sent to the DCE. Failure to do so
371 * may result in another out of sequence iframe
372 * arriving (and thus sending another reject)
373 * before the first reject is transmitted. This
374 * will cause the DCE to receive two or more
375 * rejects back to back, which must never happen.
376 */
377 hd_flush (hdp->hd_ifp);
378 hd_writeinternal (hdp, REJ, pf);
379 }
380 return (queued);
381 }
382 hdp->hd_condition &= ~REJ_CONDITION;
383
384 /*
385 * This section finally tests the IFRAME's sequence number against
386 * the window size (K) and the sequence number of the last frame
387 * we have acknowledged. If the IFRAME is completely correct then
388 * it is queued for the packet level.
389 */
390
391 if (ns != (hdp -> hd_lasttxnr + hdp -> hd_xcp -> xc_lwsize) % MODULUS) {
392 hdp -> hd_vr = (hdp -> hd_vr + 1) % MODULUS;
393 if (pf == 1) {
394 /* Must generate a RR or RNR with final bit on. */
395 hd_writeinternal (hdp, RR, POLLON);
396 } else
397 /*
398 * Hopefully we can piggyback the RR, if not we will generate
399 * a RR when T3 timer expires.
400 */
401 if (hdp -> hd_rrtimer == 0)
402 hdp->hd_rrtimer = hd_t3;
403
404 /* Forward iframe to packet level of X.25. */
405 fbuf -> m_data += HDHEADERLN;
406 fbuf -> m_len -= HDHEADERLN;
407 fbuf -> m_pkthdr.len -= HDHEADERLN;
408 fbuf -> m_pkthdr.rcvif = (struct ifnet *)hdp -> hd_pkp;
409#ifdef BSD4_3
410 fbuf->m_act = 0; /* probably not necessary */
411#else
412 {
413 register struct mbuf *m;
414
415 for (m = fbuf; m -> m_next; m = m -> m_next)
416 m -> m_act = (struct mbuf *) 0;
417 m -> m_act = (struct mbuf *) 1;
418 }
419#endif
420 pk_input (fbuf);
421 queued = TRUE;
422 hd_start (hdp);
423 } else {
424 /*
425 * Here if the remote station has transmitted more iframes then
426 * the number which have been acknowledged plus K.
427 */
428 hdp->hd_invalid_ns++;
429 frame_reject (hdp, W, frame);
430 }
431 return (queued);
432}
433
434/*
435 * This routine is used to determine if a value (the middle parameter)
436 * is between two other values. The low value is the first parameter
437 * the high value is the last parameter. The routine checks the middle
438 * value to see if it is within the range of the first and last values.
439 * The reason we need this routine is the values are modulo some base
440 * hence a simple test for greater or less than is not sufficient.
441 */
442
443bool
444range_check (rear, value, front)
445int rear,
446 value,
447 front;
448{
449 register bool result = FALSE;
450
451 if (front > rear)
452 result = (rear <= value) && (value <= front);
453 else
454 result = (rear <= value) || (value <= front);
455
456 return (result);
457}
458
459/*
460 * This routine handles all the frame reject conditions which can
461 * arise as a result of secondary processing. The frame reject
462 * condition Y (frame length error) are handled elsewhere.
463 */
464
465static
466frame_reject (hdp, rejectcode, frame)
467struct hdcb *hdp;
468struct Hdlc_iframe *frame;
469{
470 register struct Frmr_frame *frmr = &hd_frmr;
471
472 frmr -> frmr_control = ((struct Hdlc_frame *) frame) -> control;
473
474 frmr -> frmr_ns = frame -> ns;
475 frmr -> frmr_f1_0 = 0;
476 frmr -> frmr_nr = frame -> nr;
477 frmr -> frmr_f2_0 = 0;
478
479 frmr -> frmr_0000 = 0;
480 frmr -> frmr_w = frmr -> frmr_x = frmr -> frmr_y =
481 frmr -> frmr_z = 0;
482 switch (rejectcode) {
483 case Z:
484 frmr -> frmr_z = 1;/* invalid N(R). */
485 break;
486
487 case Y:
488 frmr -> frmr_y = 1;/* iframe length error. */
489 break;
490
491 case X:
492 frmr -> frmr_x = 1;/* invalid information field. */
493 frmr -> frmr_w = 1;
494 break;
495
496 case W:
497 frmr -> frmr_w = 1;/* invalid N(S). */
498 }
499
500 hd_writeinternal (hdp, FRMR, POLLOFF);
501
502 hdp->hd_state = WAIT_SABM;
503 SET_TIMER (hdp);
504}
505
506/*
507 * This procedure is invoked when ever we receive a supervisor
508 * frame such as RR, RNR and REJ. All processing for these
509 * frames is done here.
510 */
511
512process_sframe (hdp, frame, frametype)
513register struct hdcb *hdp;
514register struct Hdlc_sframe *frame;
515int frametype;
516{
517 register int nr = frame -> nr, pf = frame -> pf, pollbit = 0;
518
519 if (valid_nr (hdp, nr, pf) == TRUE) {
520 switch (frametype) {
521 case RR:
522 hdp->hd_condition &= ~REMOTE_RNR_CONDITION;
523 break;
524
525 case RNR:
526 hdp->hd_condition |= REMOTE_RNR_CONDITION;
527 hdp->hd_retxcnt = 0;
528 break;
529
530 case REJ:
531 hdp->hd_condition &= ~REMOTE_RNR_CONDITION;
532 rej_routine (hdp, nr);
533 }
534
535 if (pf == 1) {
536 hdp->hd_retxcnt = 0;
537 hdp->hd_condition &= ~TIMER_RECOVERY_CONDITION;
538
539 if (frametype == RR && hdp->hd_lastrxnr == hdp->hd_vs
540 && hdp->hd_timer == 0 && hdp->hd_txq.head == 0)
541 hd_writeinternal(hdp, RR, pf);
542 else
543 /* If any iframes have been queued because of the
544 timer condition, transmit then now. */
545 if (hdp->hd_condition & REMOTE_RNR_CONDITION) {
546 /* Remote is busy or timer condition, so only
547 send one. */
548 if (hdp->hd_vs != hdp->hd_retxqi)
549 hd_send_iframe (hdp, hdp->hd_retxq[hdp->hd_vs], pollbit);
550 }
551 else /* Flush the retransmit list first. */
552 while (hdp->hd_vs != hdp->hd_retxqi)
553 hd_send_iframe (hdp, hdp->hd_retxq[hdp->hd_vs], POLLOFF);
554 }
555
556 hd_start (hdp);
557 } else
558 frame_reject (hdp, Z, (struct Hdlc_iframe *)frame); /* Invalid N(R). */
559}
560
561/*
562 * This routine tests the validity of the N(R) which we have received.
563 * If it is ok, then all the iframes which it acknowledges (if any)
564 * will be freed.
565 */
566
567bool
568valid_nr (hdp, nr, finalbit)
569register struct hdcb *hdp;
570register int finalbit;
571{
572 /* Make sure it really does acknowledge something. */
573 if (hdp->hd_lastrxnr == nr)
574 return (TRUE);
575
576 /*
577 * This section validates the frame's N(R) value. It's N(R) value
578 * must be in syncronization with our V(S) value and our "last
579 * received nr" variable. If it is correct then we are able to send
580 * more IFRAME's, else frame reject condition is entered.
581 */
582
583 if (range_check (hdp->hd_lastrxnr, nr, hdp->hd_vs) == FALSE) {
584 if ((hdp->hd_condition & TIMER_RECOVERY_CONDITION) &&
585 range_check (hdp->hd_vs, nr, hdp->hd_xx) == TRUE)
586 hdp->hd_vs = nr;
587
588 else {
589 hdp->hd_invalid_nr++;
590 return (FALSE);
591 }
592 }
593
594 /*
595 * If we get to here, we do have a valid frame but it might be out
596 * of sequence. However, we should still accept the receive state
597 * number N(R) since it has already passed our previous test and it
598 * does acknowledge frames which we are sending.
599 */
600
601 KILL_TIMER (hdp);
602 free_iframes (hdp, &nr, finalbit);/* Free all acknowledged iframes */
603 if (nr != hdp->hd_vs)
604 SET_TIMER (hdp);
605
606 return (TRUE);
607}
608
609/*
610 * This routine determines how many iframes need to be retransmitted.
611 * It then resets the Send State Variable V(S) to accomplish this.
612 */
613
614static
615rej_routine (hdp, rejnr)
616register struct hdcb *hdp;
617register int rejnr;
618{
619 register int anchor;
620
621 /*
622 * Flush the output queue. Any iframes queued for
623 * transmission will be out of sequence.
624 */
625
626 hd_flush (hdp->hd_ifp);
627
628 /*
629 * Determine how many frames should be re-transmitted. In the case
630 * of a normal REJ this should be 1 to K. In the case of a timer
631 * recovery REJ (ie. a REJ with the Final Bit on) this could be 0.
632 */
633
634 anchor = hdp->hd_vs;
635 if (hdp->hd_condition & TIMER_RECOVERY_CONDITION)
636 anchor = hdp->hd_xx;
637
638 anchor = (anchor - rejnr + 8) % MODULUS;
639
640 if (anchor > 0) {
641
642 /* There is at least one iframe to retransmit. */
643 KILL_TIMER (hdp);
644 hdp->hd_vs = rejnr;
645
646 while (hdp->hd_vs != hdp->hd_retxqi)
647 hd_send_iframe (hdp, hdp->hd_retxq[hdp->hd_vs], POLLOFF);
648
649 }
650 hd_start (hdp);
651}
652
653/*
654 * This routine frees iframes from the retransmit queue. It is called
655 * when a previously written iframe is acknowledged.
656 */
657
658static
659free_iframes (hdp, nr, finalbit)
660register struct hdcb *hdp;
661int *nr;
662register int finalbit;
663
664{
665 register int i, k;
666
667 /*
668 * We need to do the following because of a funny quirk in the
669 * protocol. This case occures when in Timer recovery condition
670 * we get a N(R) which acknowledges all the outstanding iframes
671 * but with the Final Bit off. In this case we need to save the last
672 * iframe for possible retransmission even though it has already been
673 * acknowledged!
674 */
675
676 if ((hdp->hd_condition & TIMER_RECOVERY_CONDITION) && *nr == hdp->hd_xx && finalbit == 0) {
677 *nr = (*nr - 1 + 8) % MODULUS;
678/* printf ("QUIRK\n"); */
679 }
680
681 k = (*nr - hdp->hd_lastrxnr + 8) % MODULUS;
682
683 /* Loop here freeing all acknowledged iframes. */
684 for (i = 0; i < k; ++i) {
685 m_freem (hdp->hd_retxq[hdp->hd_lastrxnr]);
686 hdp->hd_retxq[hdp->hd_lastrxnr] = 0;
687 hdp->hd_lastrxnr = (hdp->hd_lastrxnr + 1) % MODULUS;
688 }
689
690}