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