First run at integrating LWIP into the tree (again)
[akaros.git] / user / lwip / netif / ppp / vj.c
1 /*
2  * Routines to compress and uncompess tcp packets (for transmission
3  * over low speed serial lines.
4  *
5  * Copyright (c) 1989 Regents of the University of California.
6  * All rights reserved.
7  *
8  * Redistribution and use in source and binary forms are permitted
9  * provided that the above copyright notice and this paragraph are
10  * duplicated in all such forms and that any documentation,
11  * advertising materials, and other materials related to such
12  * distribution and use acknowledge that the software was developed
13  * by the University of California, Berkeley.  The name of the
14  * University may not be used to endorse or promote products derived
15  * from this software without specific prior written permission.
16  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
17  * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
18  * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
19  *
20  * Van Jacobson (van@helios.ee.lbl.gov), Dec 31, 1989:
21  *   Initial distribution.
22  *
23  * Modified June 1993 by Paul Mackerras, paulus@cs.anu.edu.au,
24  * so that the entire packet being decompressed doesn't have
25  * to be in contiguous memory (just the compressed header).
26  *
27  * Modified March 1998 by Guy Lancaster, glanca@gesn.com,
28  * for a 16 bit processor.
29  */
30
31 #include "lwip/opt.h"
32
33 #if PPP_SUPPORT /* don't build if not configured for use in lwipopts.h */
34
35 #include "ppp.h"
36 #include "pppdebug.h"
37
38 #include "vj.h"
39
40 #include <string.h>
41
42 #if VJ_SUPPORT
43
44 #if LINK_STATS
45 #define INCR(counter) ++comp->stats.counter
46 #else
47 #define INCR(counter)
48 #endif
49
50 #if defined(NO_CHAR_BITFIELDS)
51 #define getip_hl(base)  ((base).ip_hl_v&0xf)
52 #define getth_off(base) (((base).th_x2_off&0xf0)>>4)
53 #else
54 #define getip_hl(base)  ((base).ip_hl)
55 #define getth_off(base) ((base).th_off)
56 #endif
57
58 void
59 vj_compress_init(struct vjcompress *comp)
60 {
61   register u_int i;
62   register struct cstate *tstate = comp->tstate;
63   
64 #if MAX_SLOTS == 0
65   memset((char *)comp, 0, sizeof(*comp));
66 #endif
67   comp->maxSlotIndex = MAX_SLOTS - 1;
68   comp->compressSlot = 0;    /* Disable slot ID compression by default. */
69   for (i = MAX_SLOTS - 1; i > 0; --i) {
70     tstate[i].cs_id = i;
71     tstate[i].cs_next = &tstate[i - 1];
72   }
73   tstate[0].cs_next = &tstate[MAX_SLOTS - 1];
74   tstate[0].cs_id = 0;
75   comp->last_cs = &tstate[0];
76   comp->last_recv = 255;
77   comp->last_xmit = 255;
78   comp->flags = VJF_TOSS;
79 }
80
81
82 /* ENCODE encodes a number that is known to be non-zero.  ENCODEZ
83  * checks for zero (since zero has to be encoded in the long, 3 byte
84  * form).
85  */
86 #define ENCODE(n) { \
87   if ((u_short)(n) >= 256) { \
88     *cp++ = 0; \
89     cp[1] = (n); \
90     cp[0] = (n) >> 8; \
91     cp += 2; \
92   } else { \
93     *cp++ = (n); \
94   } \
95 }
96 #define ENCODEZ(n) { \
97   if ((u_short)(n) >= 256 || (u_short)(n) == 0) { \
98     *cp++ = 0; \
99     cp[1] = (n); \
100     cp[0] = (n) >> 8; \
101     cp += 2; \
102   } else { \
103     *cp++ = (n); \
104   } \
105 }
106
107 #define DECODEL(f) { \
108   if (*cp == 0) {\
109     u32_t tmp = ntohl(f) + ((cp[1] << 8) | cp[2]); \
110     (f) = htonl(tmp); \
111     cp += 3; \
112   } else { \
113     u32_t tmp = ntohl(f) + (u32_t)*cp++; \
114     (f) = htonl(tmp); \
115   } \
116 }
117
118 #define DECODES(f) { \
119   if (*cp == 0) {\
120     u_short tmp = ntohs(f) + (((u_short)cp[1] << 8) | cp[2]); \
121     (f) = htons(tmp); \
122     cp += 3; \
123   } else { \
124     u_short tmp = ntohs(f) + (u_short)*cp++; \
125     (f) = htons(tmp); \
126   } \
127 }
128
129 #define DECODEU(f) { \
130   if (*cp == 0) {\
131     (f) = htons(((u_short)cp[1] << 8) | cp[2]); \
132     cp += 3; \
133   } else { \
134     (f) = htons((u_short)*cp++); \
135   } \
136 }
137
138 /*
139  * vj_compress_tcp - Attempt to do Van Jacobson header compression on a
140  * packet.  This assumes that nb and comp are not null and that the first
141  * buffer of the chain contains a valid IP header.
142  * Return the VJ type code indicating whether or not the packet was
143  * compressed.
144  */
145 u_int
146 vj_compress_tcp(struct vjcompress *comp, struct pbuf *pb)
147 {
148   register struct ip *ip = (struct ip *)pb->payload;
149   register struct cstate *cs = comp->last_cs->cs_next;
150   register u_short hlen = getip_hl(*ip);
151   register struct tcphdr *oth;
152   register struct tcphdr *th;
153   register u_short deltaS, deltaA;
154   register u_long deltaL;
155   register u_int changes = 0;
156   u_char new_seq[16];
157   register u_char *cp = new_seq;
158
159   /*  
160    * Check that the packet is IP proto TCP.
161    */
162   if (ip->ip_p != IPPROTO_TCP) {
163     return (TYPE_IP);
164   }
165
166   /*
167    * Bail if this is an IP fragment or if the TCP packet isn't
168    * `compressible' (i.e., ACK isn't set or some other control bit is
169    * set).  
170    */
171   if ((ip->ip_off & htons(0x3fff)) || pb->tot_len < 40) {
172     return (TYPE_IP);
173   }
174   th = (struct tcphdr *)&((long *)ip)[hlen];
175   if ((th->th_flags & (TCP_SYN|TCP_FIN|TCP_RST|TCP_ACK)) != TCP_ACK) {
176     return (TYPE_IP);
177   }
178   /*
179    * Packet is compressible -- we're going to send either a
180    * COMPRESSED_TCP or UNCOMPRESSED_TCP packet.  Either way we need
181    * to locate (or create) the connection state.  Special case the
182    * most recently used connection since it's most likely to be used
183    * again & we don't have to do any reordering if it's used.
184    */
185   INCR(vjs_packets);
186   if (ip->ip_src.s_addr != cs->cs_ip.ip_src.s_addr 
187       || ip->ip_dst.s_addr != cs->cs_ip.ip_dst.s_addr 
188       || *(long *)th != ((long *)&cs->cs_ip)[getip_hl(cs->cs_ip)]) {
189     /*
190      * Wasn't the first -- search for it.
191      *
192      * States are kept in a circularly linked list with
193      * last_cs pointing to the end of the list.  The
194      * list is kept in lru order by moving a state to the
195      * head of the list whenever it is referenced.  Since
196      * the list is short and, empirically, the connection
197      * we want is almost always near the front, we locate
198      * states via linear search.  If we don't find a state
199      * for the datagram, the oldest state is (re-)used.
200      */
201     register struct cstate *lcs;
202     register struct cstate *lastcs = comp->last_cs;
203     
204     do {
205       lcs = cs; cs = cs->cs_next;
206       INCR(vjs_searches);
207       if (ip->ip_src.s_addr == cs->cs_ip.ip_src.s_addr
208           && ip->ip_dst.s_addr == cs->cs_ip.ip_dst.s_addr
209           && *(long *)th == ((long *)&cs->cs_ip)[getip_hl(cs->cs_ip)]) {
210         goto found;
211       }
212     } while (cs != lastcs);
213
214     /*
215      * Didn't find it -- re-use oldest cstate.  Send an
216      * uncompressed packet that tells the other side what
217      * connection number we're using for this conversation.
218      * Note that since the state list is circular, the oldest
219      * state points to the newest and we only need to set
220      * last_cs to update the lru linkage.
221      */
222     INCR(vjs_misses);
223     comp->last_cs = lcs;
224     hlen += getth_off(*th);
225     hlen <<= 2;
226     /* Check that the IP/TCP headers are contained in the first buffer. */
227     if (hlen > pb->len) {
228       return (TYPE_IP);
229     }
230     goto uncompressed;
231
232     found:
233     /*
234      * Found it -- move to the front on the connection list.
235      */
236     if (cs == lastcs) {
237       comp->last_cs = lcs;
238     } else {
239       lcs->cs_next = cs->cs_next;
240       cs->cs_next = lastcs->cs_next;
241       lastcs->cs_next = cs;
242     }
243   }
244
245   oth = (struct tcphdr *)&((long *)&cs->cs_ip)[hlen];
246   deltaS = hlen;
247   hlen += getth_off(*th);
248   hlen <<= 2;
249   /* Check that the IP/TCP headers are contained in the first buffer. */
250   if (hlen > pb->len) {
251     PPPDEBUG((LOG_INFO, "vj_compress_tcp: header len %d spans buffers\n", hlen));
252     return (TYPE_IP);
253   }
254
255   /*
256    * Make sure that only what we expect to change changed. The first
257    * line of the `if' checks the IP protocol version, header length &
258    * type of service.  The 2nd line checks the "Don't fragment" bit.
259    * The 3rd line checks the time-to-live and protocol (the protocol
260    * check is unnecessary but costless).  The 4th line checks the TCP
261    * header length.  The 5th line checks IP options, if any.  The 6th
262    * line checks TCP options, if any.  If any of these things are
263    * different between the previous & current datagram, we send the
264    * current datagram `uncompressed'.
265    */
266   if (((u_short *)ip)[0] != ((u_short *)&cs->cs_ip)[0] 
267       || ((u_short *)ip)[3] != ((u_short *)&cs->cs_ip)[3] 
268       || ((u_short *)ip)[4] != ((u_short *)&cs->cs_ip)[4] 
269       || getth_off(*th) != getth_off(*oth) 
270       || (deltaS > 5 && BCMP(ip + 1, &cs->cs_ip + 1, (deltaS - 5) << 2)) 
271       || (getth_off(*th) > 5 && BCMP(th + 1, oth + 1, (getth_off(*th) - 5) << 2))) {
272     goto uncompressed;
273   }
274
275   /*
276    * Figure out which of the changing fields changed.  The
277    * receiver expects changes in the order: urgent, window,
278    * ack, seq (the order minimizes the number of temporaries
279    * needed in this section of code).
280    */
281   if (th->th_flags & TCP_URG) {
282     deltaS = ntohs(th->th_urp);
283     ENCODEZ(deltaS);
284     changes |= NEW_U;
285   } else if (th->th_urp != oth->th_urp) {
286     /* argh! URG not set but urp changed -- a sensible
287      * implementation should never do this but RFC793
288      * doesn't prohibit the change so we have to deal
289      * with it. */
290     goto uncompressed;
291   }
292
293   if ((deltaS = (u_short)(ntohs(th->th_win) - ntohs(oth->th_win))) != 0) {
294     ENCODE(deltaS);
295     changes |= NEW_W;
296   }
297
298   if ((deltaL = ntohl(th->th_ack) - ntohl(oth->th_ack)) != 0) {
299     if (deltaL > 0xffff) {
300       goto uncompressed;
301     }
302     deltaA = (u_short)deltaL;
303     ENCODE(deltaA);
304     changes |= NEW_A;
305   }
306
307   if ((deltaL = ntohl(th->th_seq) - ntohl(oth->th_seq)) != 0) {
308     if (deltaL > 0xffff) {
309       goto uncompressed;
310     }
311     deltaS = (u_short)deltaL;
312     ENCODE(deltaS);
313     changes |= NEW_S;
314   }
315
316   switch(changes) {
317   case 0:
318     /*
319      * Nothing changed. If this packet contains data and the
320      * last one didn't, this is probably a data packet following
321      * an ack (normal on an interactive connection) and we send
322      * it compressed.  Otherwise it's probably a retransmit,
323      * retransmitted ack or window probe.  Send it uncompressed
324      * in case the other side missed the compressed version.
325      */
326     if (ip->ip_len != cs->cs_ip.ip_len &&
327       ntohs(cs->cs_ip.ip_len) == hlen) {
328       break;
329     }
330
331   /* (fall through) */
332
333   case SPECIAL_I:
334   case SPECIAL_D:
335     /*
336      * actual changes match one of our special case encodings --
337      * send packet uncompressed.
338      */
339     goto uncompressed;
340
341   case NEW_S|NEW_A:
342     if (deltaS == deltaA && deltaS == ntohs(cs->cs_ip.ip_len) - hlen) {
343       /* special case for echoed terminal traffic */
344       changes = SPECIAL_I;
345       cp = new_seq;
346     }
347     break;
348
349   case NEW_S:
350     if (deltaS == ntohs(cs->cs_ip.ip_len) - hlen) {
351       /* special case for data xfer */
352       changes = SPECIAL_D;
353       cp = new_seq;
354     }
355     break;
356   }
357
358   deltaS = (u_short)(ntohs(ip->ip_id) - ntohs(cs->cs_ip.ip_id));
359   if (deltaS != 1) {
360     ENCODEZ(deltaS);
361     changes |= NEW_I;
362   }
363   if (th->th_flags & TCP_PSH) {
364     changes |= TCP_PUSH_BIT;
365   }
366   /*
367    * Grab the cksum before we overwrite it below.  Then update our
368    * state with this packet's header.
369    */
370   deltaA = ntohs(th->th_sum);
371   BCOPY(ip, &cs->cs_ip, hlen);
372
373   /*
374    * We want to use the original packet as our compressed packet.
375    * (cp - new_seq) is the number of bytes we need for compressed
376    * sequence numbers.  In addition we need one byte for the change
377    * mask, one for the connection id and two for the tcp checksum.
378    * So, (cp - new_seq) + 4 bytes of header are needed.  hlen is how
379    * many bytes of the original packet to toss so subtract the two to
380    * get the new packet size.
381    */
382   deltaS = (u_short)(cp - new_seq);
383   if (!comp->compressSlot || comp->last_xmit != cs->cs_id) {
384     comp->last_xmit = cs->cs_id;
385     hlen -= deltaS + 4;
386     if(pbuf_header(pb, -hlen)){
387       /* Can we cope with this failing?  Just assert for now */
388       LWIP_ASSERT("pbuf_header failed\n", 0);
389     }
390     cp = (u_char *)pb->payload;
391     *cp++ = changes | NEW_C;
392     *cp++ = cs->cs_id;
393   } else {
394     hlen -= deltaS + 3;
395     if(pbuf_header(pb, -hlen)) {
396       /* Can we cope with this failing?  Just assert for now */
397       LWIP_ASSERT("pbuf_header failed\n", 0);
398     }
399     cp = (u_char *)pb->payload;
400     *cp++ = changes;
401   }
402   *cp++ = deltaA >> 8;
403   *cp++ = deltaA;
404   BCOPY(new_seq, cp, deltaS);
405   INCR(vjs_compressed);
406   return (TYPE_COMPRESSED_TCP);
407
408   /*
409    * Update connection state cs & send uncompressed packet (that is,
410    * a regular ip/tcp packet but with the 'conversation id' we hope
411    * to use on future compressed packets in the protocol field).
412    */
413 uncompressed:
414   BCOPY(ip, &cs->cs_ip, hlen);
415   ip->ip_p = cs->cs_id;
416   comp->last_xmit = cs->cs_id;
417   return (TYPE_UNCOMPRESSED_TCP);
418 }
419
420 /*
421  * Called when we may have missed a packet.
422  */
423 void
424 vj_uncompress_err(struct vjcompress *comp)
425 {
426   comp->flags |= VJF_TOSS;
427   INCR(vjs_errorin);
428 }
429
430 /*
431  * "Uncompress" a packet of type TYPE_UNCOMPRESSED_TCP.
432  * Return 0 on success, -1 on failure.
433  */
434 int
435 vj_uncompress_uncomp(struct pbuf *nb, struct vjcompress *comp)
436 {
437   register u_int hlen;
438   register struct cstate *cs;
439   register struct ip *ip;
440   
441   ip = (struct ip *)nb->payload;
442   hlen = getip_hl(*ip) << 2;
443   if (ip->ip_p >= MAX_SLOTS
444       || hlen + sizeof(struct tcphdr) > nb->len
445       || (hlen += getth_off(*((struct tcphdr *)&((char *)ip)[hlen])) << 2)
446           > nb->len
447       || hlen > MAX_HDR) {
448     PPPDEBUG((LOG_INFO, "vj_uncompress_uncomp: bad cid=%d, hlen=%d buflen=%d\n", 
449           ip->ip_p, hlen, nb->len));
450     comp->flags |= VJF_TOSS;
451     INCR(vjs_errorin);
452     return -1;
453   }
454   cs = &comp->rstate[comp->last_recv = ip->ip_p];
455   comp->flags &=~ VJF_TOSS;
456   ip->ip_p = IPPROTO_TCP;
457   BCOPY(ip, &cs->cs_ip, hlen);
458   cs->cs_hlen = hlen;
459   INCR(vjs_uncompressedin);
460   return 0;
461 }
462
463 /*
464  * Uncompress a packet of type TYPE_COMPRESSED_TCP.
465  * The packet is composed of a buffer chain and the first buffer
466  * must contain an accurate chain length.
467  * The first buffer must include the entire compressed TCP/IP header. 
468  * This procedure replaces the compressed header with the uncompressed
469  * header and returns the length of the VJ header.
470  */
471 int
472 vj_uncompress_tcp(struct pbuf **nb, struct vjcompress *comp)
473 {
474   u_char *cp;
475   struct tcphdr *th;
476   struct cstate *cs;
477   u_short *bp;
478   struct pbuf *n0 = *nb;
479   u32_t tmp;
480   u_int vjlen, hlen, changes;
481
482   INCR(vjs_compressedin);
483   cp = (u_char *)n0->payload;
484   changes = *cp++;
485   if (changes & NEW_C) {
486     /* 
487      * Make sure the state index is in range, then grab the state.
488      * If we have a good state index, clear the 'discard' flag. 
489      */
490     if (*cp >= MAX_SLOTS) {
491       PPPDEBUG((LOG_INFO, "vj_uncompress_tcp: bad cid=%d\n", *cp));
492       goto bad;
493     }
494
495     comp->flags &=~ VJF_TOSS;
496     comp->last_recv = *cp++;
497   } else {
498     /* 
499      * this packet has an implicit state index.  If we've
500      * had a line error since the last time we got an
501      * explicit state index, we have to toss the packet. 
502      */
503     if (comp->flags & VJF_TOSS) {
504       PPPDEBUG((LOG_INFO, "vj_uncompress_tcp: tossing\n"));
505       INCR(vjs_tossed);
506       return (-1);
507     }
508   }
509   cs = &comp->rstate[comp->last_recv];
510   hlen = getip_hl(cs->cs_ip) << 2;
511   th = (struct tcphdr *)&((u_char *)&cs->cs_ip)[hlen];
512   th->th_sum = htons((*cp << 8) | cp[1]);
513   cp += 2;
514   if (changes & TCP_PUSH_BIT) {
515     th->th_flags |= TCP_PSH;
516   } else {
517     th->th_flags &=~ TCP_PSH;
518   }
519
520   switch (changes & SPECIALS_MASK) {
521   case SPECIAL_I:
522     {
523       register u32_t i = ntohs(cs->cs_ip.ip_len) - cs->cs_hlen;
524       /* some compilers can't nest inline assembler.. */
525       tmp = ntohl(th->th_ack) + i;
526       th->th_ack = htonl(tmp);
527       tmp = ntohl(th->th_seq) + i;
528       th->th_seq = htonl(tmp);
529     }
530     break;
531
532   case SPECIAL_D:
533     /* some compilers can't nest inline assembler.. */
534     tmp = ntohl(th->th_seq) + ntohs(cs->cs_ip.ip_len) - cs->cs_hlen;
535     th->th_seq = htonl(tmp);
536     break;
537
538   default:
539     if (changes & NEW_U) {
540       th->th_flags |= TCP_URG;
541       DECODEU(th->th_urp);
542     } else {
543       th->th_flags &=~ TCP_URG;
544     }
545     if (changes & NEW_W) {
546       DECODES(th->th_win);
547     }
548     if (changes & NEW_A) {
549       DECODEL(th->th_ack);
550     }
551     if (changes & NEW_S) {
552       DECODEL(th->th_seq);
553     }
554     break;
555   }
556   if (changes & NEW_I) {
557     DECODES(cs->cs_ip.ip_id);
558   } else {
559     cs->cs_ip.ip_id = ntohs(cs->cs_ip.ip_id) + 1;
560     cs->cs_ip.ip_id = htons(cs->cs_ip.ip_id);
561   }
562
563   /*
564    * At this point, cp points to the first byte of data in the
565    * packet.  Fill in the IP total length and update the IP
566    * header checksum.
567    */
568   vjlen = (u_short)(cp - (u_char*)n0->payload);
569   if (n0->len < vjlen) {
570     /* 
571      * We must have dropped some characters (crc should detect
572      * this but the old slip framing won't) 
573      */
574     PPPDEBUG((LOG_INFO, "vj_uncompress_tcp: head buffer %d too short %d\n", 
575           n0->len, vjlen));
576     goto bad;
577   }
578
579 #if BYTE_ORDER == LITTLE_ENDIAN
580   tmp = n0->tot_len - vjlen + cs->cs_hlen;
581   cs->cs_ip.ip_len = htons(tmp);
582 #else
583   cs->cs_ip.ip_len = htons(n0->tot_len - vjlen + cs->cs_hlen);
584 #endif
585
586   /* recompute the ip header checksum */
587   bp = (u_short *) &cs->cs_ip;
588   cs->cs_ip.ip_sum = 0;
589   for (tmp = 0; hlen > 0; hlen -= 2) {
590     tmp += *bp++;
591   }
592   tmp = (tmp & 0xffff) + (tmp >> 16);
593   tmp = (tmp & 0xffff) + (tmp >> 16);
594   cs->cs_ip.ip_sum = (u_short)(~tmp);
595   
596   /* Remove the compressed header and prepend the uncompressed header. */
597   if(pbuf_header(n0, -((s16_t)(vjlen)))) {
598     /* Can we cope with this failing?  Just assert for now */
599     LWIP_ASSERT("pbuf_header failed\n", 0);
600     goto bad;
601   }
602
603   if(LWIP_MEM_ALIGN(n0->payload) != n0->payload) {
604     struct pbuf *np, *q;
605     u8_t *bufptr;
606
607     np = pbuf_alloc(PBUF_RAW, n0->len + cs->cs_hlen, PBUF_POOL);
608     if(!np) {
609       PPPDEBUG((LOG_WARNING, "vj_uncompress_tcp: realign failed\n"));
610       goto bad;
611     }
612
613     if(pbuf_header(np, -cs->cs_hlen)) {
614       /* Can we cope with this failing?  Just assert for now */
615       LWIP_ASSERT("pbuf_header failed\n", 0);
616       goto bad;
617     }
618
619     bufptr = n0->payload;
620     for(q = np; q != NULL; q = q->next) {
621       MEMCPY(q->payload, bufptr, q->len);
622       bufptr += q->len;
623     }
624
625     if(n0->next) {
626       pbuf_chain(np, n0->next);
627       pbuf_dechain(n0);
628     }
629     pbuf_free(n0);
630     n0 = np;
631   }
632
633   if(pbuf_header(n0, cs->cs_hlen)) {
634     struct pbuf *np;
635
636     LWIP_ASSERT("vj_uncompress_tcp: cs->cs_hlen <= PBUF_POOL_BUFSIZE", cs->cs_hlen <= PBUF_POOL_BUFSIZE);
637     np = pbuf_alloc(PBUF_RAW, cs->cs_hlen, PBUF_POOL);
638     if(!np) {
639       PPPDEBUG((LOG_WARNING, "vj_uncompress_tcp: prepend failed\n"));
640       goto bad;
641     }
642     pbuf_cat(np, n0);
643     n0 = np;
644   }
645   LWIP_ASSERT("n0->len >= cs->cs_hlen", n0->len >= cs->cs_hlen);
646   MEMCPY(n0->payload, &cs->cs_ip, cs->cs_hlen);
647
648   *nb = n0;
649
650   return vjlen;
651
652 bad:
653   comp->flags |= VJF_TOSS;
654   INCR(vjs_errorin);
655   return (-1);
656 }
657
658 #endif /* VJ_SUPPORT */
659
660 #endif /* PPP_SUPPORT */