Merge remote-tracking branch 'origin/master'
[unleashed/lotheac.git] / usr / src / uts / common / io / ppp / spppcomp / vjcompress.c
blobeea51d7b26eddc5084b3d4f9395f758dba3846d0
1 /*
2 * Copyright (c) 2000 by Sun Microsystems, Inc.
3 * All rights reserved.
5 * Routines to compress and uncompess tcp packets (for transmission
6 * over low speed serial lines.
8 * Copyright (c) 1989 Regents of the University of California.
9 * All rights reserved.
11 * Redistribution and use in source and binary forms are permitted
12 * provided that the above copyright notice and this paragraph are
13 * duplicated in all such forms and that any documentation,
14 * advertising materials, and other materials related to such
15 * distribution and use acknowledge that the software was developed
16 * by the University of California, Berkeley. The name of the
17 * University may not be used to endorse or promote products derived
18 * from this software without specific prior written permission.
19 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
20 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
21 * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
23 * Van Jacobson (van@helios.ee.lbl.gov), Dec 31, 1989:
24 * - Initial distribution.
26 * Modified June 1993 by Paul Mackerras, paulus@cs.anu.edu.au,
27 * so that the entire packet being decompressed doesn't have
28 * to be in contiguous memory (just the compressed header).
32 * This version is used under STREAMS in Solaris 2
34 * $Id: vjcompress.c,v 1.10 1999/09/15 23:49:06 masputra Exp $
37 #include <sys/types.h>
38 #include <sys/param.h>
39 #include <sys/byteorder.h> /* for ntohl, etc. */
40 #include <sys/systm.h>
41 #include <sys/sysmacros.h>
43 #include <netinet/in.h>
44 #include <netinet/in_systm.h>
45 #include <netinet/ip.h>
46 #include <netinet/tcp.h>
48 #include <net/ppp_defs.h>
49 #include <net/vjcompress.h>
51 #pragma ident "%Z%%M% %I% %E% SMI"
53 #ifndef VJ_NO_STATS
54 #define INCR(counter) ++comp->stats.counter
55 #else
56 #define INCR(counter)
57 #endif
59 #define BCMP(p1, p2, n) bcmp((char *)(p1), (char *)(p2), (unsigned int)(n))
61 #undef BCOPY
62 #define BCOPY(p1, p2, n) bcopy((char *)(p1), (char *)(p2), (unsigned int)(n))
65 * I'd like to use offsetof(struct ip,ip_hl) and offsetof(struct
66 * tcp,th_off), but these are bitfields.
68 #define getip_hl(bp) (((uchar_t *)bp)[0] & 0x0F)
69 #define getth_off(bp) (((uchar_t *)bp)[12] >> 4)
70 #define getip_p(bp) (((uchar_t *)bp)[offsetof(struct ip, ip_p)])
71 #define setip_p(bp, v) (((uchar_t *)bp)[offsetof(struct ip, ip_p)] = (v))
74 * vj_compress_init()
76 void
77 vj_compress_init(struct vjcompress *comp, int max_state)
79 register uint_t i;
80 register struct cstate *tstate = comp->tstate;
82 if (max_state == -1) {
83 max_state = MAX_STATES - 1;
86 bzero((char *)comp, sizeof (*comp));
88 for (i = max_state; i > 0; --i) {
89 tstate[i].cs_id = i & 0xff;
90 tstate[i].cs_next = &tstate[i - 1];
93 tstate[0].cs_next = &tstate[max_state];
94 tstate[0].cs_id = 0;
96 comp->last_cs = &tstate[0];
97 comp->last_recv = 255;
98 comp->last_xmit = 255;
99 comp->flags = VJF_TOSS;
103 * ENCODE encodes a number that is known to be non-zero. ENCODEZ
104 * checks for zero (since zero has to be encoded in the long, 3 byte
105 * form).
107 #define ENCODE(n) { \
108 if ((ushort_t)(n) >= 256) { \
109 *cp++ = 0; \
110 cp[1] = (n) & 0xff; \
111 cp[0] = ((n) >> 8) & 0xff; \
112 cp += 2; \
113 } else { \
114 *cp++ = (n) & 0xff; \
117 #define ENCODEZ(n) { \
118 if ((ushort_t)(n) >= 256 || (ushort_t)(n) == 0) { \
119 *cp++ = 0; \
120 cp[1] = (n) & 0xff; \
121 cp[0] = ((n) >> 8) & 0xff; \
122 cp += 2; \
123 } else { \
124 *cp++ = (n) & 0xff; \
128 #define DECODEL(f) { \
129 if (*cp == 0) { \
130 uint32_t tmp = ntohl(f) + ((cp[1] << 8) | cp[2]); \
131 (f) = htonl(tmp); \
132 cp += 3; \
133 } else { \
134 uint32_t tmp = ntohl(f) + (uint32_t)*cp++; \
135 (f) = htonl(tmp); \
139 #define DECODES(f) { \
140 if (*cp == 0) { \
141 ushort_t tmp = ntohs(f) + ((cp[1] << 8) | cp[2]); \
142 (f) = htons(tmp); \
143 cp += 3; \
144 } else { \
145 ushort_t tmp = ntohs(f) + (uint32_t)*cp++; \
146 (f) = htons(tmp); \
150 #define DECODEU(f) { \
151 if (*cp == 0) { \
152 (f) = htons((cp[1] << 8) | cp[2]); \
153 cp += 3; \
154 } else { \
155 (f) = htons((uint32_t)*cp++); \
159 uint_t
160 vj_compress_tcp(register struct ip *ip, uint_t mlen, struct vjcompress *comp,
161 int compress_cid, uchar_t **vjhdrp)
163 register struct cstate *cs = comp->last_cs->cs_next;
164 register uint_t hlen = getip_hl(ip);
165 register struct tcphdr *oth;
166 register struct tcphdr *th;
167 register uint_t deltaS;
168 register uint_t deltaA;
169 register uint_t changes = 0;
170 uchar_t new_seq[16];
171 register uchar_t *cp = new_seq;
172 register uint_t thlen;
175 * Bail if this is an IP fragment or if the TCP packet isn't
176 * `compressible' (i.e., ACK isn't set or some other control bit is
177 * set). (We assume that the caller has already made sure the
178 * packet is IP proto TCP)
180 if ((ip->ip_off & htons(0x3fff)) || mlen < 40) {
181 return (TYPE_IP);
184 th = (struct tcphdr *)&((int *)ip)[hlen];
186 if ((th->th_flags & (TH_SYN|TH_FIN|TH_RST|TH_ACK)) != TH_ACK) {
187 return (TYPE_IP);
190 thlen = (hlen + getth_off(th)) << 2;
191 if (thlen > mlen) {
192 return (TYPE_IP);
196 * Packet is compressible -- we're going to send either a
197 * COMPRESSED_TCP or UNCOMPRESSED_TCP packet. Either way we need
198 * to locate (or create) the connection state. Special case the
199 * most recently used connection since it's most likely to be used
200 * again & we don't have to do any reordering if it's used.
202 INCR(vjs_packets);
204 if (ip->ip_src.s_addr != cs->cs_ip.ip_src.s_addr ||
205 ip->ip_dst.s_addr != cs->cs_ip.ip_dst.s_addr ||
206 *(int *)th != ((int *)&cs->cs_ip)[getip_hl(&cs->cs_ip)]) {
209 * Wasn't the first -- search for it.
211 * States are kept in a circularly linked list with
212 * last_cs pointing to the end of the list. The
213 * list is kept in lru order by moving a state to the
214 * head of the list whenever it is referenced. Since
215 * the list is short and, empirically, the connection
216 * we want is almost always near the front, we locate
217 * states via linear search. If we don't find a state
218 * for the datagram, the oldest state is (re-)used.
220 register struct cstate *lcs;
221 register struct cstate *lastcs = comp->last_cs;
223 do {
224 lcs = cs; cs = cs->cs_next;
226 INCR(vjs_searches);
228 if (ip->ip_src.s_addr == cs->cs_ip.ip_src.s_addr &&
229 ip->ip_dst.s_addr == cs->cs_ip.ip_dst.s_addr &&
230 *(int *)th == ((int *)
231 &cs->cs_ip)[getip_hl(&cs->cs_ip)]) {
233 goto found;
236 } while (cs != lastcs);
239 * Didn't find it -- re-use oldest cstate. Send an
240 * uncompressed packet that tells the other side what
241 * connection number we're using for this conversation.
242 * Note that since the state list is circular, the oldest
243 * state points to the newest and we only need to set
244 * last_cs to update the lru linkage.
246 INCR(vjs_misses);
248 comp->last_cs = lcs;
250 goto uncompressed;
252 found:
254 * Found it -- move to the front on the connection list.
256 if (cs == lastcs) {
257 comp->last_cs = lcs;
258 } else {
259 lcs->cs_next = cs->cs_next;
260 cs->cs_next = lastcs->cs_next;
261 lastcs->cs_next = cs;
266 * Make sure that only what we expect to change changed. The first
267 * line of the `if' checks the IP protocol version, header length &
268 * type of service. The 2nd line checks the "Don't fragment" bit.
269 * The 3rd line checks the time-to-live and protocol (the protocol
270 * check is unnecessary but costless). The 4th line checks the TCP
271 * header length. The 5th line checks IP options, if any. The 6th
272 * line checks TCP options, if any. If any of these things are
273 * different between the previous & current datagram, we send the
274 * current datagram `uncompressed'.
276 oth = (struct tcphdr *)&((int *)&cs->cs_ip)[hlen];
278 /* Used to check for IP options. */
279 deltaS = hlen;
281 if (((ushort_t *)ip)[0] != ((ushort_t *)&cs->cs_ip)[0] ||
282 ((ushort_t *)ip)[3] != ((ushort_t *)&cs->cs_ip)[3] ||
283 ((ushort_t *)ip)[4] != ((ushort_t *)&cs->cs_ip)[4] ||
284 getth_off(th) != getth_off(oth) ||
285 (deltaS > 5 &&
286 BCMP(ip + 1, &cs->cs_ip + 1, (deltaS - 5) << 2)) ||
287 (getth_off(th) > 5 &&
288 BCMP(th + 1, oth + 1, (getth_off(th) - 5) << 2))) {
290 goto uncompressed;
294 * Figure out which of the changing fields changed. The
295 * receiver expects changes in the order: urgent, window,
296 * ack, seq (the order minimizes the number of temporaries
297 * needed in this section of code).
299 if (th->th_flags & TH_URG) {
301 deltaS = ntohs(th->th_urp);
303 ENCODEZ(deltaS);
305 changes |= NEW_U;
307 } else if (th->th_urp != oth->th_urp) {
310 * argh! URG not set but urp changed -- a sensible
311 * implementation should never do this but RFC793
312 * doesn't prohibit the change so we have to deal
313 * with it
315 goto uncompressed;
318 if ((deltaS = (ushort_t)(ntohs(th->th_win) - ntohs(oth->th_win))) > 0) {
319 ENCODE(deltaS);
321 changes |= NEW_W;
324 if ((deltaA = ntohl(th->th_ack) - ntohl(oth->th_ack)) > 0) {
325 if (deltaA > 0xffff) {
326 goto uncompressed;
329 ENCODE(deltaA);
331 changes |= NEW_A;
334 if ((deltaS = ntohl(th->th_seq) - ntohl(oth->th_seq)) > 0) {
335 if (deltaS > 0xffff) {
336 goto uncompressed;
339 ENCODE(deltaS);
341 changes |= NEW_S;
344 switch (changes) {
346 case 0:
348 * Nothing changed. If this packet contains data and the
349 * last one didn't, this is probably a data packet following
350 * an ack (normal on an interactive connection) and we send
351 * it compressed. Otherwise it's probably a retransmit,
352 * retransmitted ack or window probe. Send it uncompressed
353 * in case the other side missed the compressed version.
355 if (ip->ip_len != cs->cs_ip.ip_len &&
356 ntohs(cs->cs_ip.ip_len) == thlen) {
357 break;
360 /* (otherwise fall through) */
361 /* FALLTHRU */
363 case SPECIAL_I:
364 case SPECIAL_D:
367 * actual changes match one of our special case encodings --
368 * send packet uncompressed.
370 goto uncompressed;
372 case NEW_S|NEW_A:
374 if (deltaS == deltaA &&
375 deltaS == ntohs(cs->cs_ip.ip_len) - thlen) {
378 * special case for echoed terminal traffic
380 changes = SPECIAL_I;
381 cp = new_seq;
384 break;
386 case NEW_S:
388 if (deltaS == ntohs(cs->cs_ip.ip_len) - thlen) {
391 * special case for data xfer
393 changes = SPECIAL_D;
394 cp = new_seq;
397 break;
400 deltaS = ntohs(ip->ip_id) - ntohs(cs->cs_ip.ip_id);
401 if (deltaS != 1) {
402 ENCODEZ(deltaS);
404 changes |= NEW_I;
407 if (th->th_flags & TH_PUSH) {
408 changes |= TCP_PUSH_BIT;
412 * Grab the cksum before we overwrite it below. Then update our
413 * state with this packet's header.
415 deltaA = ntohs(th->th_sum);
417 BCOPY(ip, &cs->cs_ip, thlen);
420 * We want to use the original packet as our compressed packet.
421 * (cp - new_seq) is the number of bytes we need for compressed
422 * sequence numbers. In addition we need one byte for the change
423 * mask, one for the connection id and two for the tcp checksum.
424 * So, (cp - new_seq) + 4 bytes of header are needed. thlen is how
425 * many bytes of the original packet to toss so subtract the two to
426 * get the new packet size.
428 deltaS = cp - new_seq;
430 cp = (uchar_t *)ip;
432 if (compress_cid == 0 || comp->last_xmit != cs->cs_id) {
433 comp->last_xmit = cs->cs_id;
435 thlen -= deltaS + 4;
437 *vjhdrp = (cp += thlen);
439 *cp++ = changes | NEW_C;
440 *cp++ = cs->cs_id;
441 } else {
442 thlen -= deltaS + 3;
444 *vjhdrp = (cp += thlen);
446 *cp++ = changes & 0xff;
449 *cp++ = (deltaA >> 8) & 0xff;
450 *cp++ = deltaA & 0xff;
452 BCOPY(new_seq, cp, deltaS);
454 INCR(vjs_compressed);
456 return (TYPE_COMPRESSED_TCP);
459 * Update connection state cs & send uncompressed packet (that is,
460 * a regular ip/tcp packet but with the 'conversation id' we hope
461 * to use on future compressed packets in the protocol field).
463 uncompressed:
465 BCOPY(ip, &cs->cs_ip, thlen);
467 ip->ip_p = cs->cs_id;
468 comp->last_xmit = cs->cs_id;
470 return (TYPE_UNCOMPRESSED_TCP);
474 * vj_uncompress_err()
476 * Called when we may have missed a packet.
478 void
479 vj_uncompress_err(struct vjcompress *comp)
481 comp->flags |= VJF_TOSS;
483 INCR(vjs_errorin);
487 * vj_uncompress_uncomp()
489 * "Uncompress" a packet of type TYPE_UNCOMPRESSED_TCP.
492 vj_uncompress_uncomp(uchar_t *buf, int buflen, struct vjcompress *comp)
494 register uint_t hlen;
495 register struct cstate *cs;
497 hlen = getip_hl(buf) << 2;
499 if (getip_p(buf) >= MAX_STATES ||
500 hlen + sizeof (struct tcphdr) > buflen ||
501 (hlen += getth_off(buf+hlen) << 2) > buflen || hlen > MAX_HDR) {
503 comp->flags |= VJF_TOSS;
505 INCR(vjs_errorin);
507 return (0);
510 cs = &comp->rstate[comp->last_recv = getip_p(buf)];
511 comp->flags &= ~VJF_TOSS;
512 setip_p(buf, IPPROTO_TCP);
514 BCOPY(buf, &cs->cs_ip, hlen);
516 cs->cs_hlen = hlen & 0xff;
518 INCR(vjs_uncompressedin);
520 return (1);
524 * vj_uncompress_tcp()
526 * Uncompress a packet of type TYPE_COMPRESSED_TCP.
527 * The packet starts at buf and is of total length total_len.
528 * The first buflen bytes are at buf; this must include the entire
529 * compressed TCP/IP header. This procedure returns the length
530 * of the VJ header, with a pointer to the uncompressed IP header
531 * in *hdrp and its length in *hlenp.
534 vj_uncompress_tcp(uchar_t *buf, int buflen, int total_len,
535 struct vjcompress *comp, uchar_t **hdrp, uint_t *hlenp)
537 register uchar_t *cp;
538 register uint_t hlen;
539 register uint_t changes;
540 register struct tcphdr *th;
541 register struct cstate *cs;
542 register ushort_t *bp;
543 register uint_t vjlen;
544 register uint32_t tmp;
546 INCR(vjs_compressedin);
548 cp = buf;
549 changes = *cp++;
551 if (changes & NEW_C) {
553 * Make sure the state index is in range, then grab the state.
554 * If we have a good state index, clear the 'discard' flag.
556 if (*cp >= MAX_STATES) {
557 goto bad;
560 comp->flags &= ~VJF_TOSS;
561 comp->last_recv = *cp++;
562 } else {
564 * this packet has an implicit state index. If we've
565 * had a line error since the last time we got an
566 * explicit state index, we have to toss the packet
568 if (comp->flags & VJF_TOSS) {
569 INCR(vjs_tossed);
570 return (-1);
574 cs = &comp->rstate[comp->last_recv];
575 hlen = getip_hl(&cs->cs_ip) << 2;
577 th = (struct tcphdr *)((uint32_t *)&cs->cs_ip+hlen/sizeof (uint32_t));
578 th->th_sum = htons((*cp << 8) | cp[1]);
580 cp += 2;
582 if (changes & TCP_PUSH_BIT) {
583 th->th_flags |= TH_PUSH;
584 } else {
585 th->th_flags &= ~TH_PUSH;
588 switch (changes & SPECIALS_MASK) {
590 case SPECIAL_I:
594 register uint32_t i;
596 i = ntohs(cs->cs_ip.ip_len) - cs->cs_hlen;
598 tmp = ntohl(th->th_ack) + i;
599 th->th_ack = htonl(tmp);
601 tmp = ntohl(th->th_seq) + i;
602 th->th_seq = htonl(tmp);
606 break;
608 case SPECIAL_D:
610 tmp = ntohl(th->th_seq) + ntohs(cs->cs_ip.ip_len) - cs->cs_hlen;
611 th->th_seq = htonl(tmp);
613 break;
615 default:
617 if (changes & NEW_U) {
618 th->th_flags |= TH_URG;
619 DECODEU(th->th_urp);
620 } else {
621 th->th_flags &= ~TH_URG;
624 if (changes & NEW_W) {
625 DECODES(th->th_win);
628 if (changes & NEW_A) {
629 DECODEL(th->th_ack);
632 if (changes & NEW_S) {
633 DECODEL(th->th_seq);
636 break;
639 if (changes & NEW_I) {
640 DECODES(cs->cs_ip.ip_id);
641 } else {
642 cs->cs_ip.ip_id = ntohs(cs->cs_ip.ip_id) + 1;
643 cs->cs_ip.ip_id = htons(cs->cs_ip.ip_id);
647 * At this point, cp points to the first byte of data in the
648 * packet. Fill in the IP total length and update the IP
649 * header checksum.
651 vjlen = cp - buf;
652 buflen -= vjlen;
653 if (buflen < 0) {
655 * we must have dropped some characters (crc should detect
656 * this but the old slip framing won't)
658 goto bad;
661 total_len += cs->cs_hlen - vjlen;
662 cs->cs_ip.ip_len = htons(total_len);
665 * recompute the ip header checksum
667 bp = (ushort_t *)&cs->cs_ip;
668 cs->cs_ip.ip_sum = 0;
670 for (changes = 0; hlen > 0; hlen -= 2) {
671 changes += *bp++;
674 changes = (changes & 0xffff) + (changes >> 16);
675 changes = (changes & 0xffff) + (changes >> 16);
676 cs->cs_ip.ip_sum = ~ changes;
678 *hdrp = (uchar_t *)&cs->cs_ip;
679 *hlenp = cs->cs_hlen;
681 return (vjlen);
683 bad:
685 comp->flags |= VJF_TOSS;
687 INCR(vjs_errorin);
689 return (-1);