1 /* 2 * Copyright (c) University of British Columbia, 1984 3 * Copyright (c) 1990 The Regents of the University of California. 4 * All rights reserved. 5 * 6 * This code is derived from software contributed to Berkeley by 7 * the Laboratory for Computation Vision and the Computer Science Department 8 * of the University of British Columbia. 9 * 10 * %sccs.include.redist.c% 11 * 12 * @(#)pk_input.c 7.6 (Berkeley) 10/04/90 13 */ 14 15 #include "param.h" 16 #include "systm.h" 17 #include "mbuf.h" 18 #include "socket.h" 19 #include "protosw.h" 20 #include "socketvar.h" 21 #include "errno.h" 22 23 #include "../net/if.h" 24 25 #include "x25.h" 26 #include "pk.h" 27 #include "pk_var.h" 28 29 /* 30 * This procedure is called by the link level whenever the link 31 * becomes operational, is reset, or when the link goes down. 32 */ 33 34 pk_ctlinput (code, xcp) 35 register struct x25config *xcp; 36 { 37 38 register struct pkcb *pkp; 39 40 for (pkp = pkcbhead; pkp; pkp = pkp -> pk_next) 41 if (pkp -> pk_xcp == xcp) 42 break; 43 44 if (pkp == 0) 45 return (EINVAL); 46 47 switch (code) { 48 case PRC_LINKUP: 49 if (pkp -> pk_state == DTE_WAITING) 50 pk_restart (pkp, X25_RESTART_NETWORK_CONGESTION); 51 break; 52 53 case PRC_LINKDOWN: 54 pk_restart (pkp, -1); /* Clear all active circuits */ 55 pkp -> pk_state = DTE_WAITING; 56 break; 57 58 case PRC_LINKRESET: 59 pk_restart (pkp, X25_RESTART_NETWORK_CONGESTION); 60 break; 61 62 } 63 return (0); 64 } 65 struct ifqueue pkintrq; 66 /* 67 * This routine is called if there are semi-smart devices that do HDLC 68 * in hardware and want to queue the packet and call level 3 directly 69 */ 70 pkintr () 71 { 72 register struct mbuf *m; 73 register struct ifaddr *ifa; 74 register struct ifnet *ifp; 75 register int s; 76 77 for (;;) { 78 s = splimp (); 79 IF_DEQUEUE (&pkintrq, m); 80 splx (s); 81 if (m == 0) 82 break; 83 if (m->m_len < PKHEADERLN) { 84 printf ("pkintr: packet too short (len=%d)\n", 85 m->m_len); 86 m_freem (m); 87 continue; 88 } 89 if ((m->m_flags & M_PKTHDR) == 0) 90 panic("pkintr"); 91 ifp = m->m_pkthdr.rcvif; 92 /* 93 * look up the appropriate control block 94 */ 95 for (ifa = ifp->if_addrlist; ifa; ifa = ifa->ifa_next) 96 if (ifa->ifa_addr->sa_family == AF_CCITT) 97 break; 98 if (ifa == 0) 99 continue; 100 pk_input(m, ((struct x25_ifaddr *)ifa)->ia_xcp); 101 } 102 } 103 struct mbuf *pk_bad_packet; 104 /* 105 * X.25 PACKET INPUT 106 * 107 * This procedure is called by a link level procedure whenever 108 * an information frame is received. It decodes the packet and 109 * demultiplexes based on the logical channel number. 110 * 111 */ 112 113 pk_input (m, xcp) 114 register struct mbuf *m; 115 struct x25config *xcp; 116 { 117 register struct x25_packet *xp; 118 register struct pklcd *lcp; 119 register struct socket *so = 0; 120 register struct pkcb *pkp; 121 int ptype, lcn, lcdstate = LISTEN; 122 static struct x25config *lastxcp; 123 static struct pkcb *lastpkp; 124 125 if (xcp == lastxcp) 126 pkp = lastpkp; 127 else { 128 for (pkp = pkcbhead; ; pkp = pkp -> pk_next) { 129 if (pkp == 0) { 130 pk_message (0, xcp, "pk_input: unknown network"); 131 m_freem (m); 132 return; 133 } 134 if (pkp -> pk_xcp == xcp) 135 break; 136 } 137 lastxcp = xcp; 138 lastpkp = pkp; 139 } 140 141 xp = mtod (m, struct x25_packet *); 142 ptype = pk_decode (xp); 143 lcn = xp -> logical_channel_number; 144 lcp = pkp -> pk_chan[lcn]; 145 146 /* 147 * If the DTE is in Restart state, then it will ignore data, 148 * interrupt, call setup and clearing, flow control and reset 149 * packets. 150 */ 151 if (lcn < 0 || lcn > pkp -> pk_maxlcn) { 152 pk_message (lcn, pkp -> pk_xcp, "illegal lcn"); 153 m_freem (m); 154 return; 155 } 156 157 pk_trace (pkp -> pk_xcp, xp, "P-In"); 158 159 if (pkp -> pk_state != DTE_READY && ptype != RESTART && ptype != RESTART_CONF) { 160 m_freem (m); 161 return; 162 } 163 if (lcp) { 164 so = lcp -> lcd_so; 165 lcdstate = lcp -> lcd_state; 166 } else { 167 if (ptype == CLEAR) { /* idle line probe (Datapac specific) */ 168 /* send response on lcd 0's output queue */ 169 lcp -> lcd_template = pk_template (lcn, X25_CLEAR_CONFIRM); 170 pk_output (lcp); 171 m_freem (m); 172 return; 173 } 174 if (ptype != CALL) 175 ptype = INVALID_PACKET; 176 } 177 178 if (lcn == 0 && ptype != RESTART && ptype != RESTART_CONF) { 179 pk_message (0, pkp -> pk_xcp, "illegal ptype (%d, %s) on lcn 0", 180 ptype, pk_name[ptype / MAXSTATES]); 181 if (pk_bad_packet) 182 m_freem (pk_bad_packet); 183 pk_bad_packet = m; 184 return; 185 } 186 187 switch (ptype + lcdstate) { 188 /* 189 * Incoming Call packet received. 190 */ 191 case CALL + LISTEN: 192 incoming_call (pkp, xp, m -> m_len); 193 break; 194 195 /* 196 * Call collision: Just throw this "incoming call" away since 197 * the DCE will ignore it anyway. 198 */ 199 case CALL + SENT_CALL: 200 pk_message ((int)xp -> logical_channel_number, pkp -> pk_xcp, 201 "incoming call collision"); 202 break; 203 204 /* 205 * Call confirmation packet received. This usually means our 206 * previous connect request is now complete. 207 */ 208 case CALL_ACCEPTED + SENT_CALL: 209 call_accepted (lcp, xp, m -> m_len); 210 break; 211 212 /* 213 * This condition can only happen if the previous state was 214 * SENT_CALL. Just ignore the packet, eventually a clear 215 * confirmation should arrive. 216 */ 217 case CALL_ACCEPTED + SENT_CLEAR: 218 break; 219 220 /* 221 * Clear packet received. This requires a complete tear down 222 * of the virtual circuit. Free buffers and control blocks. 223 * and send a clear confirmation. 224 */ 225 case CLEAR + READY: 226 case CLEAR + RECEIVED_CALL: 227 case CLEAR + SENT_CALL: 228 case CLEAR + DATA_TRANSFER: 229 lcp -> lcd_state = RECEIVED_CLEAR; 230 lcp -> lcd_template = pk_template (lcp -> lcd_lcn, X25_CLEAR_CONFIRM); 231 pk_output (lcp); 232 pk_clearcause (pkp, xp); 233 pk_close (lcp); 234 break; 235 236 /* 237 * Clear collision: Treat this clear packet as a confirmation. 238 */ 239 case CLEAR + SENT_CLEAR: 240 pk_close (lcp); 241 break; 242 243 /* 244 * Clear confirmation received. This usually means the virtual 245 * circuit is now completely removed. 246 */ 247 case CLEAR_CONF + SENT_CLEAR: 248 pk_close (lcp); 249 break; 250 251 /* 252 * A clear confirmation on an unassigned logical channel - just 253 * ignore it. Note: All other packets on an unassigned channel 254 * results in a clear. 255 */ 256 case CLEAR_CONF + READY: 257 break; 258 259 /* 260 * Data packet received. Pass on to next level. Move the Q and M 261 * bits into the data portion for the next level. 262 */ 263 case DATA + DATA_TRANSFER: 264 if (lcp -> lcd_reset_condition) { 265 ptype = DELETE_PACKET; 266 break; 267 } 268 269 /* 270 * Process the P(S) flow control information in this Data packet. 271 * Check that the packets arrive in the correct sequence and that 272 * they are within the "lcd_input_window". Input window rotation is 273 * initiated by the receive interface. 274 */ 275 276 if (PS(xp) != ((lcp -> lcd_rsn + 1) % MODULUS) || 277 PS(xp) == ((lcp -> lcd_input_window + lcp->lcd_windowsize) % MODULUS)) { 278 m_freem (m); 279 pk_procerror (RESET, lcp, "p(s) flow control error"); 280 break; 281 } 282 lcp -> lcd_rsn = PS(xp); 283 284 if (pk_ack (lcp, PR(xp)) != PACKET_OK) { 285 m_freem (m); 286 break; 287 } 288 if (so == 0) 289 break; 290 m -> m_data += PKHEADERLN; 291 m -> m_len -= PKHEADERLN; 292 if (lcp -> lcd_flags & X25_MQBIT) { 293 octet *t; 294 295 m -> m_data -= 1; 296 m -> m_len += 1; 297 t = mtod (m, octet *); 298 *t = 0x00; 299 if (xp -> q_bit) 300 *t |= 0x80; 301 if (MBIT(xp)) 302 *t |= 0x40; 303 } 304 305 /* 306 * Discard Q-BIT packets if the application 307 * doesn't want to be informed of M and Q bit status 308 */ 309 if (xp -> q_bit && (lcp -> lcd_flags & X25_MQBIT) == 0) { 310 m_freem (m); 311 lcp -> lcd_rxcnt++; 312 /* 313 * NB. This is dangerous: sending a RR here can 314 * cause sequence number errors if a previous data 315 * packet has not yet been passed up to the application 316 * (RR's are normally generated via PRU_RCVD). 317 */ 318 lcp -> lcd_template = pk_template (lcp -> lcd_lcn, X25_RR); 319 pk_output (lcp); 320 } else { 321 #ifdef BSD4_3 322 sbappendrecord (&so -> so_rcv, m); 323 #else 324 sbappend (&so -> so_rcv, m); 325 #endif 326 sorwakeup (so); 327 } 328 break; 329 330 /* 331 * Interrupt packet received. 332 */ 333 case INTERRUPT + DATA_TRANSFER: 334 if (lcp -> lcd_reset_condition) 335 break; 336 lcp -> lcd_intrdata = xp -> packet_data; 337 lcp -> lcd_template = pk_template (lcp -> lcd_lcn, X25_INTERRUPT_CONFIRM); 338 pk_output (lcp); 339 MCHTYPE(m, MT_OOBDATA); 340 if (so) 341 sohasoutofband (so); 342 break; 343 344 /* 345 * Interrupt confirmation packet received. 346 */ 347 case INTERRUPT_CONF + DATA_TRANSFER: 348 if (lcp -> lcd_reset_condition) 349 break; 350 if (lcp -> lcd_intrconf_pending == TRUE) 351 lcp -> lcd_intrconf_pending = FALSE; 352 else 353 pk_procerror (RESET, lcp, "unexpected packet"); 354 MCHTYPE(m, MT_CONTROL); 355 break; 356 357 /* 358 * Receiver ready received. Rotate the output window and output 359 * any data packets waiting transmission. 360 */ 361 case RR + DATA_TRANSFER: 362 if (lcp -> lcd_reset_condition || 363 pk_ack (lcp, PR(xp)) != PACKET_OK) { 364 ptype = DELETE_PACKET; 365 break; 366 } 367 if (lcp -> lcd_rnr_condition == TRUE) 368 lcp -> lcd_rnr_condition = FALSE; 369 pk_output (lcp); 370 MCHTYPE(m, MT_CONTROL); 371 break; 372 373 /* 374 * Receiver Not Ready received. Packets up to the P(R) can be 375 * be sent. Condition is cleared with a RR. 376 */ 377 case RNR + DATA_TRANSFER: 378 if (lcp -> lcd_reset_condition || 379 pk_ack (lcp, PR(xp)) != PACKET_OK) { 380 ptype = DELETE_PACKET; 381 break; 382 } 383 lcp -> lcd_rnr_condition = TRUE; 384 MCHTYPE(m, MT_CONTROL); 385 break; 386 387 /* 388 * Reset packet received. Set state to FLOW_OPEN. The Input and 389 * Output window edges ar set to zero. Both the send and receive 390 * numbers are reset. A confirmation is returned. 391 */ 392 case RESET + DATA_TRANSFER: 393 if (lcp -> lcd_reset_condition) 394 /* Reset collision. Just ignore packet. */ 395 break; 396 397 pk_resetcause (pkp, xp); 398 lcp -> lcd_window_condition = lcp -> lcd_rnr_condition = 399 lcp -> lcd_intrconf_pending = FALSE; 400 lcp -> lcd_output_window = lcp -> lcd_input_window = 401 lcp -> lcd_last_transmitted_pr = 0; 402 lcp -> lcd_ssn = 0; 403 lcp -> lcd_rsn = MODULUS - 1; 404 405 lcp -> lcd_template = pk_template (lcp -> lcd_lcn, X25_RESET_CONFIRM); 406 pk_output (lcp); 407 408 MCHTYPE(m, MT_CONTROL); 409 if (so == 0) 410 break; 411 sbflush (&so -> so_snd); 412 sbflush (&so -> so_rcv); 413 wakeup ((caddr_t) & so -> so_timeo); 414 sorwakeup (so); 415 sowwakeup (so); 416 break; 417 418 /* 419 * Reset confirmation received. 420 */ 421 case RESET_CONF + DATA_TRANSFER: 422 if (lcp -> lcd_reset_condition) { 423 lcp -> lcd_reset_condition = FALSE; 424 pk_output (lcp); 425 } 426 else 427 pk_procerror (RESET, lcp, "unexpected packet"); 428 MCHTYPE(m, MT_CONTROL); 429 break; 430 431 case DATA + SENT_CLEAR: 432 ptype = DELETE_PACKET; 433 case RR + SENT_CLEAR: 434 case RNR + SENT_CLEAR: 435 case INTERRUPT + SENT_CLEAR: 436 case INTERRUPT_CONF + SENT_CLEAR: 437 case RESET + SENT_CLEAR: 438 case RESET_CONF + SENT_CLEAR: 439 /* Just ignore p if we have sent a CLEAR already. 440 */ 441 break; 442 443 /* 444 * Restart sets all the permanent virtual circuits to the "Data 445 * Transfer" stae and all the switched virtual circuits to the 446 * "Ready" state. 447 */ 448 case RESTART + READY: 449 switch (pkp -> pk_state) { 450 case DTE_SENT_RESTART: 451 /* Restart collision. */ 452 pkp -> pk_state = DTE_READY; 453 pk_message (0, pkp -> pk_xcp, 454 "Packet level operational"); 455 break; 456 457 default: 458 pk_restart (pkp, -1); 459 pk_restartcause (pkp, xp); 460 pkp -> pk_chan[0] -> lcd_template = pk_template (0, 461 X25_RESTART_CONFIRM); 462 pk_output (pkp -> pk_chan[0]); 463 } 464 break; 465 466 /* 467 * Restart confirmation received. All logical channels are set 468 * to READY. 469 */ 470 case RESTART_CONF + READY: 471 switch (pkp -> pk_state) { 472 case DTE_SENT_RESTART: 473 pkp -> pk_state = DTE_READY; 474 pk_message (0, pkp -> pk_xcp, 475 "Packet level operational"); 476 break; 477 478 default: 479 /* Restart local procedure error. */ 480 pk_restart (pkp, X25_RESTART_LOCAL_PROCEDURE_ERROR); 481 pkp -> pk_state = DTE_SENT_RESTART; 482 } 483 break; 484 485 default: 486 if (lcp) { 487 pk_procerror (CLEAR, lcp, "unknown packet error"); 488 pk_message (lcn, pkp -> pk_xcp, 489 "\"%s\" unexpected in \"%s\" state", 490 pk_name[ptype/MAXSTATES], pk_state[lcdstate]); 491 } 492 else /* Packets arrived on an unassigned channel. 493 */ 494 pk_message ((int)xp->logical_channel_number, pkp -> pk_xcp, 495 "packet arrived on unassigned lcn"); 496 break; 497 } 498 if (so == 0 && lcdstate == DATA_TRANSFER && lcp -> lcd_upper) 499 lcp -> lcd_upper (lcp, m); 500 else if (ptype != DATA) 501 m_freem (m); 502 } 503 504 505 /* 506 * This routine handles incoming call packets. It matches the protocol 507 * field on the Call User Data field (usually the first four bytes) with 508 * sockets awaiting connections. 509 */ 510 511 static 512 incoming_call (pkp, xp, len) 513 struct pkcb *pkp; 514 struct x25_packet *xp; 515 { 516 register struct pklcd *lcp = 0, *l; 517 register struct sockaddr_x25 *sa; 518 register struct x25_calladdr *a; 519 register struct socket *so = 0; 520 struct mbuf *m; 521 register int l1, l2; 522 char *e, *errstr = "server unavailable"; 523 octet *u; 524 int lcn = xp -> logical_channel_number; 525 526 /* First, copy the data from the incoming call packet to a X25_socket 527 descriptor. */ 528 529 a = (struct x25_calladdr *) &xp -> packet_data; 530 l1 = a -> calling_addrlen; 531 l2 = a -> called_addrlen; 532 if ((m = m_getclr (M_DONTWAIT, MT_SONAME)) == 0) 533 return; 534 sa = mtod (m, struct sockaddr_x25 *); 535 u = (octet *) (a -> address_field + l2 / 2); 536 e = sa -> x25_addr; 537 if (l2 & 0x01) { 538 *e++ = *u++ & 0x0f; 539 l1--; 540 } 541 from_bcd (e, &u, l1); 542 if (l1 & 0x01) 543 u++; 544 545 parse_facilities (u, sa); 546 u += *u + 1; 547 sa -> x25_udlen = min (16, ((octet *)xp) + len - u); 548 if (sa -> x25_udlen < 0) 549 sa -> x25_udlen = 0; 550 bcopy ((caddr_t)u, sa -> x25_udata, (unsigned)sa -> x25_udlen); 551 552 /* 553 * Now, loop through the listen sockets looking for a match on the 554 * PID. That is the first four octets of the user data field. This 555 * is the closest thing to a port number for X.25 packets. What it 556 * does provide is away of multiplexing services at the user level. 557 */ 558 559 for (l = pk_listenhead; l; l = l -> lcd_listen) { 560 struct sockaddr_x25 *sxp = l -> lcd_ceaddr; 561 562 if (bcmp (sxp -> x25_udata, sa -> x25_udata, sxp->x25_udlen)) 563 continue; 564 if (sxp -> x25_net && 565 sxp -> x25_net != pkp->pk_xc.xc_addr.x25_net) 566 continue; 567 /* 568 * don't accept incoming collect calls unless 569 * the server sets the reverse charging option. 570 */ 571 if ((sxp -> x25_opts.op_flags & (X25_OLDSOCKADDR|X25_REVERSE_CHARGE)) == 0 && 572 sa -> x25_opts.op_flags & X25_REVERSE_CHARGE) { 573 errstr = "incoming collect call refused"; 574 break; 575 } 576 if (l -> lcd_so) { 577 if (so = sonewconn (l -> lcd_so, SS_ISCONNECTED)) 578 lcp = (struct pklcd *) so -> so_pcb; 579 } else 580 lcp = pk_attach((struct socket *) 0); 581 if (lcp == 0) { 582 /* 583 * Insufficient space or too many unaccepted 584 * connections. Just throw the call away. 585 */ 586 errstr = "server malfunction"; 587 break; 588 } 589 lcp -> lcd_upper = l -> lcd_upper; 590 lcp -> lcd_upnext = l -> lcd_upnext; 591 lcp -> lcd_lcn = lcn; 592 lcp -> lcd_state = RECEIVED_CALL; 593 lcp -> lcd_craddr = sa; 594 sa -> x25_opts.op_flags |= sxp -> x25_opts.op_flags & 595 ~X25_REVERSE_CHARGE; 596 pk_assoc (pkp, lcp, sa); 597 lcp -> lcd_template = pk_template (lcp -> lcd_lcn, X25_CALL_ACCEPTED); 598 if (so) { 599 pk_output (lcp); 600 soisconnected (so); 601 } else if (lcp->lcd_upper) 602 (*lcp->lcd_upper)(lcp, m); 603 return; 604 } 605 606 /* 607 * If the call fails for whatever reason, we still need to build a 608 * skeleton LCD in order to be able to properly receive the CLEAR 609 * CONFIRMATION. 610 */ 611 #ifdef WATERLOO /* be explicit */ 612 if (l == 0 && bcmp(sa->x25_udata, "ean", 3) == 0) 613 pk_message (lcn, pkp -> pk_xcp, "host=%s ean%c: %s", 614 sa->x25_addr, sa->x25_udata[3] & 0xff, errstr); 615 else if (l == 0 && bcmp(sa->x25_udata, "\1\0\0\0", 4) == 0) 616 pk_message (lcn, pkp -> pk_xcp, "host=%s x29d: %s", 617 sa->x25_addr, errstr); 618 else 619 #endif 620 pk_message (lcn, pkp -> pk_xcp, "host=%s pid=%x %x %x %x: %s", 621 sa -> x25_addr, sa -> x25_udata[0] & 0xff, 622 sa -> x25_udata[1] & 0xff, sa -> x25_udata[2] & 0xff, 623 sa -> x25_udata[3] & 0xff, errstr); 624 if ((lcp = pk_attach((struct socket *)0)) == 0) { 625 (void) m_free (m); 626 return; 627 } 628 lcp -> lcd_lcn = lcn; 629 lcp -> lcd_state = RECEIVED_CALL; 630 pk_assoc (pkp, lcp, sa); 631 (void) m_free (m); 632 pk_clear (lcp); 633 } 634 635 static 636 call_accepted (lcp, xp, len) 637 struct pklcd *lcp; 638 struct x25_packet *xp; 639 { 640 register struct x25_calladdr *ap; 641 register octet *fcp; 642 643 lcp -> lcd_state = DATA_TRANSFER; 644 if (lcp -> lcd_so) 645 soisconnected (lcp -> lcd_so); 646 if (len > 3) { 647 ap = (struct x25_calladdr *) &xp -> packet_data; 648 fcp = (octet *) ap -> address_field + (ap -> calling_addrlen + 649 ap -> called_addrlen + 1) / 2; 650 if (fcp + *fcp <= ((octet *)xp) + len) 651 parse_facilities (fcp, lcp -> lcd_ceaddr); 652 } 653 pk_assoc (lcp -> lcd_pkp, lcp, lcp -> lcd_ceaddr); 654 } 655 656 static 657 parse_facilities (fcp, sa) 658 register octet *fcp; 659 register struct sockaddr_x25 *sa; 660 { 661 register octet *maxfcp; 662 663 maxfcp = fcp + *fcp; 664 fcp++; 665 while (fcp < maxfcp) { 666 /* 667 * Ignore national DCE or DTE facilities 668 */ 669 if (*fcp == 0 || *fcp == 0xff) 670 break; 671 switch (*fcp) { 672 case FACILITIES_WINDOWSIZE: 673 sa -> x25_opts.op_wsize = fcp[1]; 674 fcp += 3; 675 break; 676 677 case FACILITIES_PACKETSIZE: 678 sa -> x25_opts.op_psize = fcp[1]; 679 fcp += 3; 680 break; 681 682 case FACILITIES_THROUGHPUT: 683 sa -> x25_opts.op_speed = fcp[1]; 684 fcp += 2; 685 break; 686 687 case FACILITIES_REVERSE_CHARGE: 688 if (fcp[1] & 01) 689 sa -> x25_opts.op_flags |= X25_REVERSE_CHARGE; 690 /* 691 * Datapac specific: for a X.25(1976) DTE, bit 2 692 * indicates a "hi priority" (eg. international) call. 693 */ 694 if (fcp[1] & 02 && sa -> x25_opts.op_psize == 0) 695 sa -> x25_opts.op_psize = X25_PS128; 696 fcp += 2; 697 break; 698 699 default: 700 /*printf("unknown facility %x, class=%d\n", *fcp, (*fcp & 0xc0) >> 6);*/ 701 switch ((*fcp & 0xc0) >> 6) { 702 case 0: /* class A */ 703 fcp += 2; 704 break; 705 706 case 1: 707 fcp += 3; 708 break; 709 710 case 2: 711 fcp += 4; 712 break; 713 714 case 3: 715 fcp++; 716 fcp += *fcp; 717 } 718 } 719 } 720 } 721 722 from_bcd (a, x, len) 723 register char *a; 724 register octet **x; 725 register int len; 726 { 727 register int posn = 0; 728 729 while (--len >= 0) { 730 if (posn++ & 0x01) 731 *a = *(*x)++ & 0x0f; 732 else 733 *a = (**x >> 4) & 0x0F; 734 *a++ |= 0x30; 735 } 736 } 737