1 /*********************************************************** 2 Copyright IBM Corporation 1987 3 4 All Rights Reserved 5 6 Permission to use, copy, modify, and distribute this software and its 7 documentation for any purpose and without fee is hereby granted, 8 provided that the above copyright notice appear in all copies and that 9 both that copyright notice and this permission notice appear in 10 supporting documentation, and that the name of IBM not be 11 used in advertising or publicity pertaining to distribution of the 12 software without specific, written prior permission. 13 14 IBM DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING 15 ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL 16 IBM BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR 17 ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, 18 WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, 19 ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS 20 SOFTWARE. 21 22 ******************************************************************/ 23 24 /* 25 * ARGO Project, Computer Sciences Dept., University of Wisconsin - Madison 26 */ 27 /* 28 * ARGO TP 29 * $Header: /var/src/sys/netiso/RCS/tp_iso.c,v 5.1 89/02/09 16:20:51 hagens Exp $ 30 * $Source: /var/src/sys/netiso/RCS/tp_iso.c,v $ 31 * @(#)tp_iso.c 7.4 (Berkeley) 09/26/89 32 * 33 * Here is where you find the iso-dependent code. We've tried 34 * keep all net-level and (primarily) address-family-dependent stuff 35 * out of the tp source, and everthing here is reached indirectly 36 * through a switch table (struct nl_protosw *) tpcb->tp_nlproto 37 * (see tp_pcb.c). 38 * The routines here are: 39 * iso_getsufx: gets transport suffix out of an isopcb structure. 40 * iso_putsufx: put transport suffix into an isopcb structure. 41 * iso_putnetaddr: put a whole net addr into an isopcb. 42 * iso_getnetaddr: get a whole net addr from an isopcb. 43 * iso_recycle_suffix: clear suffix for reuse in isopcb 44 * tpclnp_ctlinput: handle ER CNLPdu : icmp-like stuff 45 * tpclnp_mtu: figure out what size tpdu to use 46 * tpclnp_input: take a pkt from clnp, strip off its clnp header, 47 * give to tp 48 * tpclnp_output_dg: package a pkt for clnp given 2 addresses & some data 49 * tpclnp_output: package a pkt for clnp given an isopcb & some data 50 */ 51 52 #ifndef lint 53 static char *rcsid = "$Header: /var/src/sys/netiso/RCS/tp_iso.c,v 5.1 89/02/09 16:20:51 hagens Exp $"; 54 #endif lint 55 56 #ifdef ISO 57 58 #include "param.h" 59 #include "socket.h" 60 #include "socketvar.h" 61 #include "domain.h" 62 #include "malloc.h" 63 #include "mbuf.h" 64 #include "errno.h" 65 #include "time.h" 66 #include "protosw.h" 67 68 #include "../net/if.h" 69 #include "../net/route.h" 70 71 #include "argo_debug.h" 72 #include "tp_param.h" 73 #include "tp_stat.h" 74 #include "tp_pcb.h" 75 #include "tp_trace.h" 76 #include "tp_stat.h" 77 #include "tp_tpdu.h" 78 #include "tp_clnp.h" 79 80 /* 81 * CALLED FROM: 82 * pr_usrreq() on PRU_BIND, PRU_CONNECT, PRU_ACCEPT, and PRU_PEERADDR 83 * FUNCTION, ARGUMENTS: 84 * The argument (which) takes the value TP_LOCAL or TP_FOREIGN. 85 */ 86 87 iso_getsufx(isop, lenp, data_out, which) 88 struct isopcb *isop; 89 u_short *lenp; 90 caddr_t data_out; 91 int which; 92 { 93 register struct sockaddr_iso *addr = 0; 94 95 switch (which) { 96 case TP_LOCAL: 97 addr = isop->isop_laddr; 98 break; 99 100 case TP_FOREIGN: 101 addr = isop->isop_faddr; 102 } 103 if (addr) 104 bcopy(TSEL(addr), data_out, (*lenp = addr->siso_tlen)); 105 } 106 107 /* CALLED FROM: 108 * tp_newsocket(); i.e., when a connection is being established by an 109 * incoming CR_TPDU. 110 * 111 * FUNCTION, ARGUMENTS: 112 * Put a transport suffix (found in name) into an isopcb structure (isop). 113 * The argument (which) takes the value TP_LOCAL or TP_FOREIGN. 114 */ 115 void 116 iso_putsufx(isop, sufxloc, sufxlen, which) 117 struct isopcb *isop; 118 caddr_t sufxloc; 119 int sufxlen, which; 120 { 121 struct sockaddr_iso **dst, *backup; 122 register struct sockaddr_iso *addr; 123 struct mbuf *m; 124 int len; 125 126 switch (which) { 127 default: 128 return; 129 130 case TP_LOCAL: 131 dst = &isop->isop_laddr; 132 backup = &isop->isop_sladdr; 133 break; 134 135 case TP_FOREIGN: 136 dst = &isop->isop_faddr; 137 backup = &isop->isop_sfaddr; 138 } 139 if ((addr = *dst) == 0) { 140 addr = *dst = backup; 141 addr->siso_nlen = 0; 142 addr->siso_slen = 0; 143 addr->siso_plen = 0; 144 printf("iso_putsufx on un-initialized isopcb\n"); 145 } 146 len = sufxlen + addr->siso_nlen + 147 (sizeof(*addr) - sizeof(addr->siso_data)); 148 if (addr == backup) { 149 if (len > sizeof(*addr)) { 150 m = m_getclr(M_DONTWAIT, MT_SONAME); 151 if (m == 0) 152 return; 153 addr = *dst = mtod(m, struct sockaddr_iso *); 154 *addr = *backup; 155 m->m_len = len; 156 } 157 } 158 bcopy(sufxloc, TSEL(addr), sufxlen); 159 addr->siso_tlen = sufxlen; 160 addr->siso_len = len; 161 } 162 163 /* 164 * CALLED FROM: 165 * tp.trans whenever we go into REFWAIT state. 166 * FUNCTION and ARGUMENT: 167 * Called when a ref is frozen, to allow the suffix to be reused. 168 * (isop) is the net level pcb. This really shouldn't have to be 169 * done in a NET level pcb but... for the internet world that just 170 * the way it is done in BSD... 171 * The alternative is to have the port unusable until the reference 172 * timer goes off. 173 */ 174 void 175 iso_recycle_tsuffix(isop) 176 struct isopcb *isop; 177 { 178 isop->isop_laddr->siso_tlen = isop->isop_faddr->siso_tlen = 0; 179 } 180 181 /* 182 * CALLED FROM: 183 * tp_newsocket(); i.e., when a connection is being established by an 184 * incoming CR_TPDU. 185 * 186 * FUNCTION and ARGUMENTS: 187 * Copy a whole net addr from a struct sockaddr (name). 188 * into an isopcb (isop). 189 * The argument (which) takes values TP_LOCAL or TP_FOREIGN 190 */ 191 void 192 iso_putnetaddr(isop, name, which) 193 register struct isopcb *isop; 194 struct sockaddr_iso *name; 195 int which; 196 { 197 struct sockaddr_iso **sisop, *backup; 198 register struct sockaddr_iso *siso; 199 200 switch (which) { 201 case TP_LOCAL: 202 sisop = &isop->isop_laddr; 203 backup = &isop->isop_sladdr; 204 break; 205 case TP_FOREIGN: 206 sisop = &isop->isop_faddr; 207 backup = &isop->isop_sfaddr; 208 } 209 siso = ((*sisop == 0) ? (*sisop = backup) : *sisop); 210 IFDEBUG(D_TPISO) 211 printf("ISO_PUTNETADDR\n"); 212 dump_isoaddr(isop->isop_faddr); 213 ENDDEBUG 214 siso->siso_addr = name->siso_addr; 215 } 216 217 /* 218 * CALLED FROM: 219 * pr_usrreq() PRU_SOCKADDR, PRU_ACCEPT, PRU_PEERADDR 220 * FUNCTION and ARGUMENTS: 221 * Copy a whole net addr from an isopcb (isop) into 222 * a struct sockaddr (name). 223 * The argument (which) takes values TP_LOCAL or TP_FOREIGN. 224 */ 225 226 void 227 iso_getnetaddr( isop, name, which) 228 struct isopcb *isop; 229 struct mbuf *name; 230 int which; 231 { 232 struct sockaddr_iso *siso = 233 (which == TP_LOCAL ? isop->isop_laddr : isop->isop_faddr); 234 if (siso) 235 bcopy((caddr_t)siso, mtod(name, caddr_t), 236 (unsigned)(name->m_len = siso->siso_len)); 237 else 238 name->m_len = 0; 239 } 240 241 /* 242 * CALLED FROM: 243 * tp_input() on incoming CR, CC, and pr_usrreq() for PRU_CONNECT 244 * FUNCTION, ARGUMENTS, SIDE EFFECTS and RETURN VALUE: 245 * Determine the proper maximum transmission unit, i.e., MTU, to use, given 246 * a) the header size for the network protocol and the max transmission 247 * unit on the subnet interface, determined from the information in (isop), 248 * b) the max size negotiated so far (negot) 249 * c) the window size used by the tp connection (found in so), 250 * 251 * The result is put in the integer *size in its integer form and in 252 * *negot in its logarithmic form. 253 * 254 * The rules are: 255 * a) can only negotiate down from the value found in *negot. 256 * b) the MTU must be < the windowsize, 257 * c) If src and dest are on the same net, 258 * we will negotiate the closest size larger than MTU but really USE 259 * the actual device mtu - ll hdr sizes. 260 * otherwise we negotiate the closest size smaller than MTU - ll hdr sizes. 261 */ 262 263 void 264 tpclnp_mtu(so, isop, size, negot ) 265 struct socket *so; 266 struct isopcb *isop; 267 int *size; 268 u_char *negot; 269 { 270 struct ifnet *ifp = 0; 271 struct iso_ifaddr *ia = 0; 272 register int i; 273 int windowsize = so->so_rcv.sb_hiwat; 274 int clnp_size; 275 int sizeismtu = 0; 276 register struct rtentry *rt = isop->isop_route.ro_rt; 277 278 IFDEBUG(D_CONN) 279 printf("tpclnp_mtu(0x%x,0x%x,0x%x,0x%x)\n", so, isop, size, negot); 280 ENDDEBUG 281 IFTRACE(D_CONN) 282 tptrace(TPPTmisc, "ENTER GET MTU: size negot \n",*size, *negot, 0, 0); 283 ENDTRACE 284 285 *size = 1 << *negot; 286 287 if( *size > windowsize ) { 288 *size = windowsize; 289 } 290 291 if (rt == 0 || (rt->rt_flags & RTF_UP == 0) || 292 (ia = (struct iso_ifaddr *)rt->rt_ifa) == 0 || 293 (ifp = ia->ia_ifp) == 0) { 294 IFDEBUG(D_CONN) 295 printf("tpclnp_mtu routing abort rt=0x%x ia=0x%x ifp=0x%x\n", 296 rt, ia, ifp) 297 ENDDEBUG 298 return; 299 } 300 301 /* TODO - make this indirect off the socket structure to the 302 * network layer to get headersize 303 */ 304 if (isop->isop_laddr) 305 clnp_size = clnp_hdrsize(isop->isop_laddr->siso_addr.isoa_len); 306 else 307 clnp_size = 20; 308 309 if(*size > ifp->if_mtu - clnp_size) { 310 *size = ifp->if_mtu - clnp_size; 311 sizeismtu = 1; 312 } 313 /* have to transform size to the log2 of size */ 314 for(i=TP_MIN_TPDUSIZE; (i<TP_MAX_TPDUSIZE && ((1<<i) <= *size)) ; i++) 315 ; 316 i--; 317 318 IFTRACE(D_CONN) 319 tptrace(TPPTmisc, "GET MTU MID: tpcb size negot i \n", 320 *size, *negot, i, 0); 321 ENDTRACE 322 323 /* are we on the same LAN? if so, negotiate one tpdu size larger, 324 * and actually send the real mtu size 325 */ 326 if (iso_localifa(isop->isop_faddr)) { 327 i++; 328 } else { 329 *size = 1<<i; 330 } 331 *negot = i; 332 333 IFDEBUG(D_CONN) 334 printf("GET MTU RETURNS: ifp %s size 0x%x negot 0x%x\n", 335 ifp->if_name, *size, *negot); 336 ENDDEBUG 337 IFTRACE(D_CONN) 338 tptrace(TPPTmisc, "EXIT GET MTU: tpcb size negot \n", 339 *size, *negot, 0, 0); 340 ENDTRACE 341 } 342 343 344 /* 345 * CALLED FROM: 346 * tp_emit() 347 * FUNCTION and ARGUMENTS: 348 * Take a packet(m0) from tp and package it so that clnp will accept it. 349 * This means prepending space for the clnp header and filling in a few 350 * of the fields. 351 * inp is the isopcb structure; datalen is the length of the data in the 352 * mbuf string m0. 353 * RETURN VALUE: 354 * whatever (E*) is returned form the net layer output routine. 355 */ 356 357 int 358 tpclnp_output(isop, m0, datalen, nochksum) 359 struct isopcb *isop; 360 struct mbuf *m0; 361 int datalen; 362 int nochksum; 363 { 364 register struct mbuf *m = m0; 365 IncStat(ts_tpdu_sent); 366 367 IFDEBUG(D_TPISO) 368 struct tpdu *hdr = mtod(m0, struct tpdu *); 369 370 printf( 371 "abt to call clnp_output: datalen 0x%x, hdr.li 0x%x, hdr.dutype 0x%x nocsum x%x dst addr:\n", 372 datalen, 373 (int)hdr->tpdu_li, (int)hdr->tpdu_type, nochksum); 374 dump_isoaddr(isop->isop_faddr); 375 printf("\nsrc addr:\n"); 376 dump_isoaddr(isop->isop_laddr); 377 dump_mbuf(m0, "at tpclnp_output"); 378 ENDDEBUG 379 380 return 381 clnp_output(m0, isop, datalen, /* flags */nochksum ? CLNP_NO_CKSUM : 0); 382 } 383 384 /* 385 * CALLED FROM: 386 * tp_error_emit() 387 * FUNCTION and ARGUMENTS: 388 * This is a copy of tpclnp_output that takes the addresses 389 * instead of a pcb. It's used by the tp_error_emit, when we 390 * don't have an iso_pcb with which to call the normal output rtn. 391 * RETURN VALUE: 392 * ENOBUFS or 393 * whatever (E*) is returned form the net layer output routine. 394 */ 395 396 int 397 tpclnp_output_dg(laddr, faddr, m0, datalen, ro, nochksum) 398 struct iso_addr *laddr, *faddr; 399 struct mbuf *m0; 400 int datalen; 401 struct route *ro; 402 int nochksum; 403 { 404 struct isopcb tmppcb; 405 int err; 406 int flags; 407 register struct mbuf *m = m0; 408 409 IFDEBUG(D_TPISO) 410 printf("tpclnp_output_dg datalen 0x%x m0 0x%x\n", datalen, m0); 411 ENDDEBUG 412 413 /* 414 * Fill in minimal portion of isopcb so that clnp can send the 415 * packet. 416 */ 417 bzero((caddr_t)&tmppcb, sizeof(tmppcb)); 418 tmppcb.isop_laddr = &tmppcb.isop_sladdr; 419 tmppcb.isop_laddr->siso_addr = *laddr; 420 tmppcb.isop_faddr = &tmppcb.isop_sfaddr; 421 tmppcb.isop_faddr->siso_addr = *faddr; 422 423 IFDEBUG(D_TPISO) 424 printf("tpclnp_output_dg faddr: \n"); 425 dump_isoaddr(&tmppcb.isop_sfaddr); 426 printf("\ntpclnp_output_dg laddr: \n"); 427 dump_isoaddr(&tmppcb.isop_sladdr); 428 printf("\n"); 429 ENDDEBUG 430 431 /* 432 * Do not use packet cache since this is a one shot error packet 433 */ 434 flags = (CLNP_NOCACHE|(nochksum?CLNP_NO_CKSUM:0)); 435 436 IncStat(ts_tpdu_sent); 437 438 err = clnp_output(m0, &tmppcb, datalen, flags); 439 440 /* 441 * Free route allocated by clnp (if the route was indeed allocated) 442 */ 443 if (tmppcb.isop_route.ro_rt) 444 RTFREE(tmppcb.isop_route.ro_rt); 445 446 return(err); 447 } 448 extern struct sockaddr_iso blank_siso; 449 /* 450 * CALLED FROM: 451 * clnp's input routine, indirectly through the protosw. 452 * FUNCTION and ARGUMENTS: 453 * Take a packet (m) from clnp, strip off the clnp header and give it to tp 454 * No return value. 455 */ 456 ProtoHook 457 tpclnp_input(m, faddr, laddr, clnp_len) 458 struct mbuf *m; 459 struct iso_addr *faddr, *laddr; 460 int clnp_len; 461 { 462 struct sockaddr_iso src, dst; 463 int s = splnet(); 464 struct mbuf *tp_inputprep(); 465 466 IncStat(ts_pkt_rcvd); 467 468 IFDEBUG(D_TPINPUT) 469 printf("tpclnp_input: m 0x%x clnp_len 0x%x\n", m, clnp_len); 470 dump_mbuf(m, "at tpclnp_input"); 471 ENDDEBUG 472 /* 473 * CLNP gives us an mbuf chain WITH the clnp header pulled up, 474 * and the length of the clnp header. 475 * First, strip off the Clnp header. leave the mbuf there for the 476 * pullup that follows. 477 */ 478 479 m->m_len -= clnp_len; 480 m->m_data += clnp_len; 481 482 m = tp_inputprep(m); 483 484 IFDEBUG(D_TPINPUT) 485 dump_mbuf(m, "after tpclnp_input both pullups"); 486 ENDDEBUG 487 488 src = blank_siso; dst = blank_siso; 489 bcopy((caddr_t)faddr, (caddr_t)&src.siso_addr, 1 + faddr->isoa_len); 490 bcopy((caddr_t)laddr, (caddr_t)&dst.siso_addr, 1 + laddr->isoa_len); 491 492 IFDEBUG(D_TPISO) 493 printf("calling tp_input: &src 0x%x &dst 0x%x, src addr:\n", 494 &src, &dst); 495 printf(" dst addr:\n"); 496 dump_isoaddr(&src); 497 dump_isoaddr(&dst); 498 ENDDEBUG 499 500 (void) tp_input(m, (struct sockaddr *)&src, (struct sockaddr *)&dst, 501 0, tpclnp_output_dg); 502 503 IFDEBUG(D_QUENCH) 504 { 505 if(time.tv_usec & 0x4 && time.tv_usec & 0x40) { 506 printf("tpclnp_input: FAKING %s\n", 507 tp_stat.ts_pkt_rcvd & 0x1?"QUENCH":"QUENCH2"); 508 if(tp_stat.ts_pkt_rcvd & 0x1) { 509 tpclnp_ctlinput(PRC_QUENCH, &src); 510 } else { 511 tpclnp_ctlinput(PRC_QUENCH2, &src); 512 } 513 } 514 } 515 ENDDEBUG 516 517 splx(s); 518 return 0; 519 } 520 521 ProtoHook 522 iso_rtchange() 523 { 524 return 0; 525 } 526 527 /* 528 * CALLED FROM: 529 * tpclnp_ctlinput() 530 * FUNCTION and ARGUMENTS: 531 * find the tpcb pointer and pass it to tp_quench 532 */ 533 void 534 tpiso_decbit(isop) 535 struct isopcb *isop; 536 { 537 tp_quench((struct tp_pcb *)isop->isop_socket->so_tpcb, PRC_QUENCH2); 538 } 539 /* 540 * CALLED FROM: 541 * tpclnp_ctlinput() 542 * FUNCTION and ARGUMENTS: 543 * find the tpcb pointer and pass it to tp_quench 544 */ 545 void 546 tpiso_quench(isop) 547 struct isopcb *isop; 548 { 549 tp_quench((struct tp_pcb *)isop->isop_socket->so_tpcb, PRC_QUENCH); 550 } 551 552 /* 553 * CALLED FROM: 554 * The network layer through the protosw table. 555 * FUNCTION and ARGUMENTS: 556 * When clnp an ICMP-like msg this gets called. 557 * It either returns an error status to the user or 558 * it causes all connections on this address to be aborted 559 * by calling the appropriate xx_notify() routine. 560 * (cmd) is the type of ICMP error. 561 * (siso) is the address of the guy who sent the ER CLNPDU 562 */ 563 ProtoHook 564 tpclnp_ctlinput(cmd, siso) 565 int cmd; 566 struct sockaddr_iso *siso; 567 { 568 return tpclnp_ctlinput1(cmd, &siso->siso_addr); 569 } 570 571 /* 572 * Entry to ctlinput with argument of an iso_addr rather than a sockaddr 573 */ 574 ProtoHook 575 tpclnp_ctlinput1(cmd, isoa) 576 int cmd; 577 struct iso_addr *isoa; 578 { 579 extern u_char inetctlerrmap[]; 580 extern ProtoHook tpiso_abort(); 581 extern ProtoHook iso_rtchange(); 582 extern ProtoHook tpiso_reset(); 583 void iso_pcbnotify(); 584 585 IFDEBUG(D_TPINPUT) 586 printf("tpclnp_ctlinput1: cmd 0x%x addr: %s\n", cmd, 587 clnp_iso_addrp(isoa)); 588 ENDDEBUG 589 590 if (cmd < 0 || cmd > PRC_NCMDS) 591 return 0; 592 switch (cmd) { 593 594 case PRC_QUENCH2: 595 iso_pcbnotify(&tp_isopcb, isoa, 0, (int (*)())tpiso_decbit); 596 break; 597 598 case PRC_QUENCH: 599 iso_pcbnotify(&tp_isopcb, isoa, 0, (int (*)())tpiso_quench); 600 break; 601 602 case PRC_TIMXCEED_REASS: 603 case PRC_ROUTEDEAD: 604 iso_pcbnotify(&tp_isopcb, isoa, 0, tpiso_reset); 605 break; 606 607 case PRC_HOSTUNREACH: 608 case PRC_UNREACH_NET: 609 case PRC_IFDOWN: 610 case PRC_HOSTDEAD: 611 iso_pcbnotify(&tp_isopcb, isoa, 612 (int)inetctlerrmap[cmd], iso_rtchange); 613 break; 614 615 default: 616 /* 617 case PRC_MSGSIZE: 618 case PRC_UNREACH_HOST: 619 case PRC_UNREACH_PROTOCOL: 620 case PRC_UNREACH_PORT: 621 case PRC_UNREACH_NEEDFRAG: 622 case PRC_UNREACH_SRCFAIL: 623 case PRC_REDIRECT_NET: 624 case PRC_REDIRECT_HOST: 625 case PRC_REDIRECT_TOSNET: 626 case PRC_REDIRECT_TOSHOST: 627 case PRC_TIMXCEED_INTRANS: 628 case PRC_PARAMPROB: 629 */ 630 iso_pcbnotify(&tp_isopcb, isoa, (int)inetctlerrmap[cmd], tpiso_abort); 631 break; 632 } 633 return 0; 634 } 635 636 /* 637 * These next 2 routines are 638 * CALLED FROM: 639 * xxx_notify() from tp_ctlinput() when 640 * net level gets some ICMP-equiv. type event. 641 * FUNCTION and ARGUMENTS: 642 * Cause the connection to be aborted with some sort of error 643 * reason indicating that the network layer caused the abort. 644 * Fakes an ER TPDU so we can go through the driver. 645 * abort always aborts the TP connection. 646 * reset may or may not, depending on the TP class that's in use. 647 */ 648 ProtoHook 649 tpiso_abort(isop) 650 struct isopcb *isop; 651 { 652 struct tp_event e; 653 654 IFDEBUG(D_CONN) 655 printf("tpiso_abort 0x%x\n", isop); 656 ENDDEBUG 657 e.ev_number = ER_TPDU; 658 e.ATTR(ER_TPDU).e_reason = ECONNABORTED; 659 return tp_driver((struct tp_pcb *)isop->isop_socket->so_tpcb, &e); 660 } 661 662 ProtoHook 663 tpiso_reset(isop) 664 struct isopcb *isop; 665 { 666 struct tp_event e; 667 668 e.ev_number = T_NETRESET; 669 return tp_driver((struct tp_pcb *)isop->isop_socket->so_tpcb, &e); 670 671 } 672 673 #endif ISO 674