]> git.ozlabs.org Git - ppp.git/blob - pppd/fsm.c
updated
[ppp.git] / pppd / fsm.c
1 /*
2  * fsm.c - {Link, IP} Control Protocol Finite State Machine.
3  *
4  * Copyright (c) 1989 Carnegie Mellon University.
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms are permitted
8  * provided that the above copyright notice and this paragraph are
9  * duplicated in all such forms and that any documentation,
10  * advertising materials, and other materials related to such
11  * distribution and use acknowledge that the software was developed
12  * by Carnegie Mellon University.  The name of the
13  * University may not be used to endorse or promote products derived
14  * from this software without specific prior written permission.
15  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
16  * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
17  * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
18  */
19
20 #ifndef lint
21 static char rcsid[] = "$Id: fsm.c,v 1.10 1995/12/18 03:44:42 paulus Exp $";
22 #endif
23
24 /*
25  * TODO:
26  * Randomize fsm id on link/init.
27  * Deal with variable outgoing MTU.
28  */
29
30 #include <stdio.h>
31 #include <string.h>
32 #include <sys/types.h>
33 #include <syslog.h>
34
35 #include "pppd.h"
36 #include "fsm.h"
37
38 extern char *proto_name();
39
40 static void fsm_timeout __P((caddr_t));
41 static void fsm_rconfreq __P((fsm *, int, u_char *, int));
42 static void fsm_rconfack __P((fsm *, int, u_char *, int));
43 static void fsm_rconfnakrej __P((fsm *, int, int, u_char *, int));
44 static void fsm_rtermreq __P((fsm *, int));
45 static void fsm_rtermack __P((fsm *));
46 static void fsm_rcoderej __P((fsm *, u_char *, int));
47 static void fsm_sconfreq __P((fsm *, int));
48
49 #define PROTO_NAME(f)   ((f)->callbacks->proto_name)
50
51 int peer_mru[NUM_PPP];
52
53
54 /*
55  * fsm_init - Initialize fsm.
56  *
57  * Initialize fsm state.
58  */
59 void
60 fsm_init(f)
61     fsm *f;
62 {
63     f->state = INITIAL;
64     f->flags = 0;
65     f->id = 0;                          /* XXX Start with random id? */
66     f->timeouttime = DEFTIMEOUT;
67     f->maxconfreqtransmits = DEFMAXCONFREQS;
68     f->maxtermtransmits = DEFMAXTERMREQS;
69     f->maxnakloops = DEFMAXNAKLOOPS;
70     f->term_reason_len = 0;
71 }
72
73
74 /*
75  * fsm_lowerup - The lower layer is up.
76  */
77 void
78 fsm_lowerup(f)
79     fsm *f;
80 {
81     switch( f->state ){
82     case INITIAL:
83         f->state = CLOSED;
84         break;
85
86     case STARTING:
87         if( f->flags & OPT_SILENT )
88             f->state = STOPPED;
89         else {
90             /* Send an initial configure-request */
91             fsm_sconfreq(f, 0);
92             f->state = REQSENT;
93         }
94         break;
95
96     default:
97         FSMDEBUG((LOG_INFO, "%s: Up event in state %d!",
98                   PROTO_NAME(f), f->state));
99     }
100 }
101
102
103 /*
104  * fsm_lowerdown - The lower layer is down.
105  *
106  * Cancel all timeouts and inform upper layers.
107  */
108 void
109 fsm_lowerdown(f)
110     fsm *f;
111 {
112     switch( f->state ){
113     case CLOSED:
114         f->state = INITIAL;
115         break;
116
117     case STOPPED:
118         f->state = STARTING;
119         if( f->callbacks->starting )
120             (*f->callbacks->starting)(f);
121         break;
122
123     case CLOSING:
124         f->state = INITIAL;
125         UNTIMEOUT(fsm_timeout, (caddr_t) f);    /* Cancel timeout */
126         break;
127
128     case STOPPING:
129     case REQSENT:
130     case ACKRCVD:
131     case ACKSENT:
132         f->state = STARTING;
133         UNTIMEOUT(fsm_timeout, (caddr_t) f);    /* Cancel timeout */
134         break;
135
136     case OPENED:
137         if( f->callbacks->down )
138             (*f->callbacks->down)(f);
139         f->state = STARTING;
140         break;
141
142     default:
143         FSMDEBUG((LOG_INFO, "%s: Down event in state %d!",
144                   PROTO_NAME(f), f->state));
145     }
146 }
147
148
149 /*
150  * fsm_open - Link is allowed to come up.
151  */
152 void
153 fsm_open(f)
154     fsm *f;
155 {
156     switch( f->state ){
157     case INITIAL:
158         f->state = STARTING;
159         if( f->callbacks->starting )
160             (*f->callbacks->starting)(f);
161         break;
162
163     case CLOSED:
164         if( f->flags & OPT_SILENT )
165             f->state = STOPPED;
166         else {
167             /* Send an initial configure-request */
168             fsm_sconfreq(f, 0);
169             f->state = REQSENT;
170         }
171         break;
172
173     case CLOSING:
174         f->state = STOPPING;
175         /* fall through */
176     case STOPPED:
177     case OPENED:
178         if( f->flags & OPT_RESTART ){
179             fsm_lowerdown(f);
180             fsm_lowerup(f);
181         }
182         break;
183     }
184 }
185
186
187 /*
188  * fsm_close - Start closing connection.
189  *
190  * Cancel timeouts and either initiate close or possibly go directly to
191  * the CLOSED state.
192  */
193 void
194 fsm_close(f, reason)
195     fsm *f;
196     char *reason;
197 {
198     f->term_reason = reason;
199     f->term_reason_len = (reason == NULL? 0: strlen(reason));
200     switch( f->state ){
201     case STARTING:
202         f->state = INITIAL;
203         break;
204     case STOPPED:
205         f->state = CLOSED;
206         break;
207     case STOPPING:
208         f->state = CLOSING;
209         break;
210
211     case REQSENT:
212     case ACKRCVD:
213     case ACKSENT:
214     case OPENED:
215         if( f->state != OPENED )
216             UNTIMEOUT(fsm_timeout, (caddr_t) f);        /* Cancel timeout */
217         else if( f->callbacks->down )
218             (*f->callbacks->down)(f);   /* Inform upper layers we're down */
219
220         /* Init restart counter, send Terminate-Request */
221         f->retransmits = f->maxtermtransmits;
222         fsm_sdata(f, TERMREQ, f->reqid = ++f->id,
223                   (u_char *) f->term_reason, f->term_reason_len);
224         TIMEOUT(fsm_timeout, (caddr_t) f, f->timeouttime);
225         --f->retransmits;
226
227         f->state = CLOSING;
228         break;
229     }
230 }
231
232
233 /*
234  * fsm_timeout - Timeout expired.
235  */
236 static void
237 fsm_timeout(arg)
238     caddr_t arg;
239 {
240     fsm *f = (fsm *) arg;
241
242     switch (f->state) {
243     case CLOSING:
244     case STOPPING:
245         if( f->retransmits <= 0 ){
246             /*
247              * We've waited for an ack long enough.  Peer probably heard us.
248              */
249             f->state = (f->state == CLOSING)? CLOSED: STOPPED;
250             if( f->callbacks->finished )
251                 (*f->callbacks->finished)(f);
252         } else {
253             /* Send Terminate-Request */
254             fsm_sdata(f, TERMREQ, f->reqid = ++f->id,
255                       (u_char *) f->term_reason, f->term_reason_len);
256             TIMEOUT(fsm_timeout, (caddr_t) f, f->timeouttime);
257             --f->retransmits;
258         }
259         break;
260
261     case REQSENT:
262     case ACKRCVD:
263     case ACKSENT:
264         if (f->retransmits <= 0) {
265             syslog(LOG_WARNING, "%s: timeout sending Config-Requests",
266                    PROTO_NAME(f));
267             f->state = STOPPED;
268             if( (f->flags & OPT_PASSIVE) == 0 && f->callbacks->finished )
269                 (*f->callbacks->finished)(f);
270
271         } else {
272             /* Retransmit the configure-request */
273             if (f->callbacks->retransmit)
274                 (*f->callbacks->retransmit)(f);
275             fsm_sconfreq(f, 1);         /* Re-send Configure-Request */
276             if( f->state == ACKRCVD )
277                 f->state = REQSENT;
278         }
279         break;
280
281     default:
282         FSMDEBUG((LOG_INFO, "%s: Timeout event in state %d!",
283                   PROTO_NAME(f), f->state));
284     }
285 }
286
287
288 /*
289  * fsm_input - Input packet.
290  */
291 void
292 fsm_input(f, inpacket, l)
293     fsm *f;
294     u_char *inpacket;
295     int l;
296 {
297     u_char *inp, *outp;
298     u_char code, id;
299     int len;
300
301     /*
302      * Parse header (code, id and length).
303      * If packet too short, drop it.
304      */
305     inp = inpacket;
306     if (l < HEADERLEN) {
307         FSMDEBUG((LOG_WARNING, "fsm_input(%x): Rcvd short header.",
308                   f->protocol));
309         return;
310     }
311     GETCHAR(code, inp);
312     GETCHAR(id, inp);
313     GETSHORT(len, inp);
314     if (len < HEADERLEN) {
315         FSMDEBUG((LOG_INFO, "fsm_input(%x): Rcvd illegal length.",
316                   f->protocol));
317         return;
318     }
319     if (len > l) {
320         FSMDEBUG((LOG_INFO, "fsm_input(%x): Rcvd short packet.",
321                   f->protocol));
322         return;
323     }
324     len -= HEADERLEN;           /* subtract header length */
325
326     if( f->state == INITIAL || f->state == STARTING ){
327         FSMDEBUG((LOG_INFO, "fsm_input(%x): Rcvd packet in state %d.",
328                   f->protocol, f->state));
329         return;
330     }
331
332     /*
333      * Action depends on code.
334      */
335     switch (code) {
336     case CONFREQ:
337         fsm_rconfreq(f, id, inp, len);
338         break;
339     
340     case CONFACK:
341         fsm_rconfack(f, id, inp, len);
342         break;
343     
344     case CONFNAK:
345     case CONFREJ:
346         fsm_rconfnakrej(f, code, id, inp, len);
347         break;
348     
349     case TERMREQ:
350         fsm_rtermreq(f, id);
351         break;
352     
353     case TERMACK:
354         fsm_rtermack(f);
355         break;
356     
357     case CODEREJ:
358         fsm_rcoderej(f, inp, len);
359         break;
360     
361     default:
362         if( !f->callbacks->extcode
363            || !(*f->callbacks->extcode)(f, code, id, inp, len) )
364             fsm_sdata(f, CODEREJ, ++f->id, inpacket, len + HEADERLEN);
365         break;
366     }
367 }
368
369
370 /*
371  * fsm_rconfreq - Receive Configure-Request.
372  */
373 static void
374 fsm_rconfreq(f, id, inp, len)
375     fsm *f;
376     u_char id;
377     u_char *inp;
378     int len;
379 {
380     u_char *outp;
381     int code, reject_if_disagree;
382
383     FSMDEBUG((LOG_INFO, "fsm_rconfreq(%s): Rcvd id %d.", PROTO_NAME(f), id));
384     switch( f->state ){
385     case CLOSED:
386         /* Go away, we're closed */
387         fsm_sdata(f, TERMACK, id, NULL, 0);
388         return;
389     case CLOSING:
390     case STOPPING:
391         return;
392
393     case OPENED:
394         /* Go down and restart negotiation */
395         if( f->callbacks->down )
396             (*f->callbacks->down)(f);   /* Inform upper layers */
397         fsm_sconfreq(f, 0);             /* Send initial Configure-Request */
398         break;
399
400     case STOPPED:
401         /* Negotiation started by our peer */
402         fsm_sconfreq(f, 0);             /* Send initial Configure-Request */
403         f->state = REQSENT;
404         break;
405     }
406
407     /*
408      * Pass the requested configuration options
409      * to protocol-specific code for checking.
410      */
411     if (f->callbacks->reqci){           /* Check CI */
412         reject_if_disagree = (f->nakloops >= f->maxnakloops);
413         code = (*f->callbacks->reqci)(f, inp, &len, reject_if_disagree);
414     } else if (len)
415         code = CONFREJ;                 /* Reject all CI */
416     else
417         code = CONFACK;
418
419     /* send the Ack, Nak or Rej to the peer */
420     fsm_sdata(f, code, id, inp, len);
421
422     if (code == CONFACK) {
423         if (f->state == ACKRCVD) {
424             UNTIMEOUT(fsm_timeout, (caddr_t) f);        /* Cancel timeout */
425             f->state = OPENED;
426             if (f->callbacks->up)
427                 (*f->callbacks->up)(f); /* Inform upper layers */
428         } else
429             f->state = ACKSENT;
430         f->nakloops = 0;
431
432     } else {
433         /* we sent CONFACK or CONFREJ */
434         if (f->state != ACKRCVD)
435             f->state = REQSENT;
436         if( code == CONFNAK )
437             ++f->nakloops;
438     }
439 }
440
441
442 /*
443  * fsm_rconfack - Receive Configure-Ack.
444  */
445 static void
446 fsm_rconfack(f, id, inp, len)
447     fsm *f;
448     int id;
449     u_char *inp;
450     int len;
451 {
452     FSMDEBUG((LOG_INFO, "fsm_rconfack(%s): Rcvd id %d.",
453               PROTO_NAME(f), id));
454
455     if (id != f->reqid || f->seen_ack)          /* Expected id? */
456         return;                                 /* Nope, toss... */
457     if( !(f->callbacks->ackci? (*f->callbacks->ackci)(f, inp, len):
458           (len == 0)) ){
459         /* Ack is bad - ignore it */
460         FSMDEBUG((LOG_INFO, "%s: received bad Ack (length %d)",
461                   PROTO_NAME(f), len));
462         return;
463     }
464     f->seen_ack = 1;
465
466     switch (f->state) {
467     case CLOSED:
468     case STOPPED:
469         fsm_sdata(f, TERMACK, id, NULL, 0);
470         break;
471
472     case REQSENT:
473         f->state = ACKRCVD;
474         f->retransmits = f->maxconfreqtransmits;
475         break;
476
477     case ACKRCVD:
478         /* Huh? an extra valid Ack? oh well... */
479         UNTIMEOUT(fsm_timeout, (caddr_t) f);    /* Cancel timeout */
480         fsm_sconfreq(f, 0);
481         f->state = REQSENT;
482         break;
483
484     case ACKSENT:
485         UNTIMEOUT(fsm_timeout, (caddr_t) f);    /* Cancel timeout */
486         f->state = OPENED;
487         f->retransmits = f->maxconfreqtransmits;
488         if (f->callbacks->up)
489             (*f->callbacks->up)(f);     /* Inform upper layers */
490         break;
491
492     case OPENED:
493         /* Go down and restart negotiation */
494         if (f->callbacks->down)
495             (*f->callbacks->down)(f);   /* Inform upper layers */
496         fsm_sconfreq(f, 0);             /* Send initial Configure-Request */
497         f->state = REQSENT;
498         break;
499     }
500 }
501
502
503 /*
504  * fsm_rconfnakrej - Receive Configure-Nak or Configure-Reject.
505  */
506 static void
507 fsm_rconfnakrej(f, code, id, inp, len)
508     fsm *f;
509     int code, id;
510     u_char *inp;
511     int len;
512 {
513     int (*proc)();
514     int ret;
515
516     FSMDEBUG((LOG_INFO, "fsm_rconfnakrej(%s): Rcvd id %d.",
517               PROTO_NAME(f), id));
518
519     if (id != f->reqid || f->seen_ack)  /* Expected id? */
520         return;                         /* Nope, toss... */
521     proc = (code == CONFNAK)? f->callbacks->nakci: f->callbacks->rejci;
522     if (!proc || !(ret = proc(f, inp, len))) {
523         /* Nak/reject is bad - ignore it */
524         FSMDEBUG((LOG_INFO, "%s: received bad %s (length %d)",
525                   PROTO_NAME(f), (code==CONFNAK? "Nak": "reject"), len));
526         return;
527     }
528     f->seen_ack = 1;
529
530     switch (f->state) {
531     case CLOSED:
532     case STOPPED:
533         fsm_sdata(f, TERMACK, id, NULL, 0);
534         break;
535
536     case REQSENT:
537     case ACKSENT:
538         /* They didn't agree to what we wanted - try another request */
539         UNTIMEOUT(fsm_timeout, (caddr_t) f);    /* Cancel timeout */
540         if (ret < 0)
541             f->state = STOPPED;         /* kludge for stopping CCP */
542         else
543             fsm_sconfreq(f, 0);         /* Send Configure-Request */
544         break;
545
546     case ACKRCVD:
547         /* Got a Nak/reject when we had already had an Ack?? oh well... */
548         UNTIMEOUT(fsm_timeout, (caddr_t) f);    /* Cancel timeout */
549         fsm_sconfreq(f, 0);
550         f->state = REQSENT;
551         break;
552
553     case OPENED:
554         /* Go down and restart negotiation */
555         if (f->callbacks->down)
556             (*f->callbacks->down)(f);   /* Inform upper layers */
557         fsm_sconfreq(f, 0);             /* Send initial Configure-Request */
558         f->state = REQSENT;
559         break;
560     }
561 }
562
563
564 /*
565  * fsm_rtermreq - Receive Terminate-Req.
566  */
567 static void
568 fsm_rtermreq(f, id)
569     fsm *f;
570     int id;
571 {
572     FSMDEBUG((LOG_INFO, "fsm_rtermreq(%s): Rcvd id %d.",
573               PROTO_NAME(f), id));
574
575     switch (f->state) {
576     case ACKRCVD:
577     case ACKSENT:
578         f->state = REQSENT;             /* Start over but keep trying */
579         break;
580
581     case OPENED:
582         syslog(LOG_INFO, "%s terminated at peer's request", PROTO_NAME(f));
583         if (f->callbacks->down)
584             (*f->callbacks->down)(f);   /* Inform upper layers */
585         f->retransmits = 0;
586         f->state = STOPPING;
587         TIMEOUT(fsm_timeout, (caddr_t) f, f->timeouttime);
588         break;
589     }
590
591     fsm_sdata(f, TERMACK, id, NULL, 0);
592 }
593
594
595 /*
596  * fsm_rtermack - Receive Terminate-Ack.
597  */
598 static void
599 fsm_rtermack(f)
600     fsm *f;
601 {
602     FSMDEBUG((LOG_INFO, "fsm_rtermack(%s).", PROTO_NAME(f)));
603
604     switch (f->state) {
605     case CLOSING:
606         UNTIMEOUT(fsm_timeout, (caddr_t) f);
607         f->state = CLOSED;
608         if( f->callbacks->finished )
609             (*f->callbacks->finished)(f);
610         break;
611     case STOPPING:
612         UNTIMEOUT(fsm_timeout, (caddr_t) f);
613         f->state = STOPPED;
614         if( f->callbacks->finished )
615             (*f->callbacks->finished)(f);
616         break;
617
618     case ACKRCVD:
619         f->state = REQSENT;
620         break;
621
622     case OPENED:
623         if (f->callbacks->down)
624             (*f->callbacks->down)(f);   /* Inform upper layers */
625         fsm_sconfreq(f, 0);
626         break;
627     }
628 }
629
630
631 /*
632  * fsm_rcoderej - Receive an Code-Reject.
633  */
634 static void
635 fsm_rcoderej(f, inp, len)
636     fsm *f;
637     u_char *inp;
638     int len;
639 {
640     u_char code, id;
641
642     FSMDEBUG((LOG_INFO, "fsm_rcoderej(%s).", PROTO_NAME(f)));
643
644     if (len < HEADERLEN) {
645         FSMDEBUG((LOG_INFO, "fsm_rcoderej: Rcvd short Code-Reject packet!"));
646         return;
647     }
648     GETCHAR(code, inp);
649     GETCHAR(id, inp);
650     syslog(LOG_WARNING, "%s: Rcvd Code-Reject for code %d, id %d",
651            PROTO_NAME(f), code, id);
652
653     if( f->state == ACKRCVD )
654         f->state = REQSENT;
655 }
656
657
658 /*
659  * fsm_protreject - Peer doesn't speak this protocol.
660  *
661  * Treat this as a catastrophic error (RXJ-).
662  */
663 void
664 fsm_protreject(f)
665     fsm *f;
666 {
667     switch( f->state ){
668     case CLOSING:
669         UNTIMEOUT(fsm_timeout, (caddr_t) f);    /* Cancel timeout */
670         /* fall through */
671     case CLOSED:
672         f->state = CLOSED;
673         if( f->callbacks->finished )
674             (*f->callbacks->finished)(f);
675         break;
676
677     case STOPPING:
678     case REQSENT:
679     case ACKRCVD:
680     case ACKSENT:
681         UNTIMEOUT(fsm_timeout, (caddr_t) f);    /* Cancel timeout */
682         /* fall through */
683     case STOPPED:
684         f->state = STOPPED;
685         if( f->callbacks->finished )
686             (*f->callbacks->finished)(f);
687         break;
688
689     case OPENED:
690         if( f->callbacks->down )
691             (*f->callbacks->down)(f);
692
693         /* Init restart counter, send Terminate-Request */
694         f->retransmits = f->maxtermtransmits;
695         fsm_sdata(f, TERMREQ, f->reqid = ++f->id,
696                   (u_char *) f->term_reason, f->term_reason_len);
697         TIMEOUT(fsm_timeout, (caddr_t) f, f->timeouttime);
698         --f->retransmits;
699
700         f->state = STOPPING;
701         break;
702
703     default:
704         FSMDEBUG((LOG_INFO, "%s: Protocol-reject event in state %d!",
705                   PROTO_NAME(f), f->state));
706     }
707 }
708
709
710 /*
711  * fsm_sconfreq - Send a Configure-Request.
712  */
713 static void
714 fsm_sconfreq(f, retransmit)
715     fsm *f;
716     int retransmit;
717 {
718     u_char *outp;
719     int outlen, cilen;
720
721     if( f->state != REQSENT && f->state != ACKRCVD && f->state != ACKSENT ){
722         /* Not currently negotiating - reset options */
723         if( f->callbacks->resetci )
724             (*f->callbacks->resetci)(f);
725         f->nakloops = 0;
726     }
727
728     if( !retransmit ){
729         /* New request - reset retransmission counter, use new ID */
730         f->retransmits = f->maxconfreqtransmits;
731         f->reqid = ++f->id;
732     }
733
734     f->seen_ack = 0;
735
736     /*
737      * Make up the request packet
738      */
739     outp = outpacket_buf + PPP_HDRLEN + HEADERLEN;
740     if( f->callbacks->cilen && f->callbacks->addci ){
741         cilen = (*f->callbacks->cilen)(f);
742         if( cilen > peer_mru[f->unit] - HEADERLEN )
743             cilen = peer_mru[f->unit] - HEADERLEN;
744         if (f->callbacks->addci)
745             (*f->callbacks->addci)(f, outp, &cilen);
746     } else
747         cilen = 0;
748
749     /* send the request to our peer */
750     fsm_sdata(f, CONFREQ, f->reqid, outp, cilen);
751
752     /* start the retransmit timer */
753     --f->retransmits;
754     TIMEOUT(fsm_timeout, (caddr_t) f, f->timeouttime);
755
756     FSMDEBUG((LOG_INFO, "%s: sending Configure-Request, id %d",
757               PROTO_NAME(f), f->reqid));
758 }
759
760
761 /*
762  * fsm_sdata - Send some data.
763  *
764  * Used for all packets sent to our peer by this module.
765  */
766 void
767 fsm_sdata(f, code, id, data, datalen)
768     fsm *f;
769     u_char code, id;
770     u_char *data;
771     int datalen;
772 {
773     u_char *outp;
774     int outlen;
775
776     /* Adjust length to be smaller than MTU */
777     outp = outpacket_buf;
778     if (datalen > peer_mru[f->unit] - HEADERLEN)
779         datalen = peer_mru[f->unit] - HEADERLEN;
780     if (datalen && data != outp + PPP_HDRLEN + HEADERLEN)
781         BCOPY(data, outp + PPP_HDRLEN + HEADERLEN, datalen);
782     outlen = datalen + HEADERLEN;
783     MAKEHEADER(outp, f->protocol);
784     PUTCHAR(code, outp);
785     PUTCHAR(id, outp);
786     PUTSHORT(outlen, outp);
787     output(f->unit, outpacket_buf, outlen + PPP_HDRLEN);
788
789     FSMDEBUG((LOG_INFO, "fsm_sdata(%s): Sent code %d, id %d.",
790               PROTO_NAME(f), code, id));
791 }