162306a36Sopenharmony_ci/* 262306a36Sopenharmony_ci * Routines to compress and uncompress tcp packets (for transmission 362306a36Sopenharmony_ci * over low speed serial lines). 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (c) 1989 Regents of the University of California. 662306a36Sopenharmony_ci * All rights reserved. 762306a36Sopenharmony_ci * 862306a36Sopenharmony_ci * Redistribution and use in source and binary forms are permitted 962306a36Sopenharmony_ci * provided that the above copyright notice and this paragraph are 1062306a36Sopenharmony_ci * duplicated in all such forms and that any documentation, 1162306a36Sopenharmony_ci * advertising materials, and other materials related to such 1262306a36Sopenharmony_ci * distribution and use acknowledge that the software was developed 1362306a36Sopenharmony_ci * by the University of California, Berkeley. The name of the 1462306a36Sopenharmony_ci * University may not be used to endorse or promote products derived 1562306a36Sopenharmony_ci * from this software without specific prior written permission. 1662306a36Sopenharmony_ci * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR 1762306a36Sopenharmony_ci * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED 1862306a36Sopenharmony_ci * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. 1962306a36Sopenharmony_ci * 2062306a36Sopenharmony_ci * Van Jacobson (van@helios.ee.lbl.gov), Dec 31, 1989: 2162306a36Sopenharmony_ci * - Initial distribution. 2262306a36Sopenharmony_ci * 2362306a36Sopenharmony_ci * 2462306a36Sopenharmony_ci * modified for KA9Q Internet Software Package by 2562306a36Sopenharmony_ci * Katie Stevens (dkstevens@ucdavis.edu) 2662306a36Sopenharmony_ci * University of California, Davis 2762306a36Sopenharmony_ci * Computing Services 2862306a36Sopenharmony_ci * - 01-31-90 initial adaptation (from 1.19) 2962306a36Sopenharmony_ci * PPP.05 02-15-90 [ks] 3062306a36Sopenharmony_ci * PPP.08 05-02-90 [ks] use PPP protocol field to signal compression 3162306a36Sopenharmony_ci * PPP.15 09-90 [ks] improve mbuf handling 3262306a36Sopenharmony_ci * PPP.16 11-02 [karn] substantially rewritten to use NOS facilities 3362306a36Sopenharmony_ci * 3462306a36Sopenharmony_ci * - Feb 1991 Bill_Simpson@um.cc.umich.edu 3562306a36Sopenharmony_ci * variable number of conversation slots 3662306a36Sopenharmony_ci * allow zero or one slots 3762306a36Sopenharmony_ci * separate routines 3862306a36Sopenharmony_ci * status display 3962306a36Sopenharmony_ci * - Jul 1994 Dmitry Gorodchanin 4062306a36Sopenharmony_ci * Fixes for memory leaks. 4162306a36Sopenharmony_ci * - Oct 1994 Dmitry Gorodchanin 4262306a36Sopenharmony_ci * Modularization. 4362306a36Sopenharmony_ci * - Jan 1995 Bjorn Ekwall 4462306a36Sopenharmony_ci * Use ip_fast_csum from ip.h 4562306a36Sopenharmony_ci * - July 1995 Christos A. Polyzols 4662306a36Sopenharmony_ci * Spotted bug in tcp option checking 4762306a36Sopenharmony_ci * 4862306a36Sopenharmony_ci * 4962306a36Sopenharmony_ci * This module is a difficult issue. It's clearly inet code but it's also clearly 5062306a36Sopenharmony_ci * driver code belonging close to PPP and SLIP 5162306a36Sopenharmony_ci */ 5262306a36Sopenharmony_ci 5362306a36Sopenharmony_ci#include <linux/module.h> 5462306a36Sopenharmony_ci#include <linux/slab.h> 5562306a36Sopenharmony_ci#include <linux/types.h> 5662306a36Sopenharmony_ci#include <linux/string.h> 5762306a36Sopenharmony_ci#include <linux/errno.h> 5862306a36Sopenharmony_ci#include <linux/kernel.h> 5962306a36Sopenharmony_ci#include <net/slhc_vj.h> 6062306a36Sopenharmony_ci 6162306a36Sopenharmony_ci#ifdef CONFIG_INET 6262306a36Sopenharmony_ci/* Entire module is for IP only */ 6362306a36Sopenharmony_ci#include <linux/mm.h> 6462306a36Sopenharmony_ci#include <linux/socket.h> 6562306a36Sopenharmony_ci#include <linux/sockios.h> 6662306a36Sopenharmony_ci#include <linux/termios.h> 6762306a36Sopenharmony_ci#include <linux/in.h> 6862306a36Sopenharmony_ci#include <linux/fcntl.h> 6962306a36Sopenharmony_ci#include <linux/inet.h> 7062306a36Sopenharmony_ci#include <linux/netdevice.h> 7162306a36Sopenharmony_ci#include <net/ip.h> 7262306a36Sopenharmony_ci#include <net/protocol.h> 7362306a36Sopenharmony_ci#include <net/icmp.h> 7462306a36Sopenharmony_ci#include <net/tcp.h> 7562306a36Sopenharmony_ci#include <linux/skbuff.h> 7662306a36Sopenharmony_ci#include <net/sock.h> 7762306a36Sopenharmony_ci#include <linux/timer.h> 7862306a36Sopenharmony_ci#include <linux/uaccess.h> 7962306a36Sopenharmony_ci#include <net/checksum.h> 8062306a36Sopenharmony_ci#include <asm/unaligned.h> 8162306a36Sopenharmony_ci 8262306a36Sopenharmony_cistatic unsigned char *encode(unsigned char *cp, unsigned short n); 8362306a36Sopenharmony_cistatic long decode(unsigned char **cpp); 8462306a36Sopenharmony_cistatic unsigned char * put16(unsigned char *cp, unsigned short x); 8562306a36Sopenharmony_cistatic unsigned short pull16(unsigned char **cpp); 8662306a36Sopenharmony_ci 8762306a36Sopenharmony_ci/* Allocate compression data structure 8862306a36Sopenharmony_ci * slots must be in range 0 to 255 (zero meaning no compression) 8962306a36Sopenharmony_ci * Returns pointer to structure or ERR_PTR() on error. 9062306a36Sopenharmony_ci */ 9162306a36Sopenharmony_cistruct slcompress * 9262306a36Sopenharmony_cislhc_init(int rslots, int tslots) 9362306a36Sopenharmony_ci{ 9462306a36Sopenharmony_ci short i; 9562306a36Sopenharmony_ci struct cstate *ts; 9662306a36Sopenharmony_ci struct slcompress *comp; 9762306a36Sopenharmony_ci 9862306a36Sopenharmony_ci if (rslots < 0 || rslots > 255 || tslots < 0 || tslots > 255) 9962306a36Sopenharmony_ci return ERR_PTR(-EINVAL); 10062306a36Sopenharmony_ci 10162306a36Sopenharmony_ci comp = kzalloc(sizeof(struct slcompress), GFP_KERNEL); 10262306a36Sopenharmony_ci if (! comp) 10362306a36Sopenharmony_ci goto out_fail; 10462306a36Sopenharmony_ci 10562306a36Sopenharmony_ci if (rslots > 0) { 10662306a36Sopenharmony_ci size_t rsize = rslots * sizeof(struct cstate); 10762306a36Sopenharmony_ci comp->rstate = kzalloc(rsize, GFP_KERNEL); 10862306a36Sopenharmony_ci if (! comp->rstate) 10962306a36Sopenharmony_ci goto out_free; 11062306a36Sopenharmony_ci comp->rslot_limit = rslots - 1; 11162306a36Sopenharmony_ci } 11262306a36Sopenharmony_ci 11362306a36Sopenharmony_ci if (tslots > 0) { 11462306a36Sopenharmony_ci size_t tsize = tslots * sizeof(struct cstate); 11562306a36Sopenharmony_ci comp->tstate = kzalloc(tsize, GFP_KERNEL); 11662306a36Sopenharmony_ci if (! comp->tstate) 11762306a36Sopenharmony_ci goto out_free2; 11862306a36Sopenharmony_ci comp->tslot_limit = tslots - 1; 11962306a36Sopenharmony_ci } 12062306a36Sopenharmony_ci 12162306a36Sopenharmony_ci comp->xmit_oldest = 0; 12262306a36Sopenharmony_ci comp->xmit_current = 255; 12362306a36Sopenharmony_ci comp->recv_current = 255; 12462306a36Sopenharmony_ci /* 12562306a36Sopenharmony_ci * don't accept any packets with implicit index until we get 12662306a36Sopenharmony_ci * one with an explicit index. Otherwise the uncompress code 12762306a36Sopenharmony_ci * will try to use connection 255, which is almost certainly 12862306a36Sopenharmony_ci * out of range 12962306a36Sopenharmony_ci */ 13062306a36Sopenharmony_ci comp->flags |= SLF_TOSS; 13162306a36Sopenharmony_ci 13262306a36Sopenharmony_ci if ( tslots > 0 ) { 13362306a36Sopenharmony_ci ts = comp->tstate; 13462306a36Sopenharmony_ci for(i = comp->tslot_limit; i > 0; --i){ 13562306a36Sopenharmony_ci ts[i].cs_this = i; 13662306a36Sopenharmony_ci ts[i].next = &(ts[i - 1]); 13762306a36Sopenharmony_ci } 13862306a36Sopenharmony_ci ts[0].next = &(ts[comp->tslot_limit]); 13962306a36Sopenharmony_ci ts[0].cs_this = 0; 14062306a36Sopenharmony_ci } 14162306a36Sopenharmony_ci return comp; 14262306a36Sopenharmony_ci 14362306a36Sopenharmony_ciout_free2: 14462306a36Sopenharmony_ci kfree(comp->rstate); 14562306a36Sopenharmony_ciout_free: 14662306a36Sopenharmony_ci kfree(comp); 14762306a36Sopenharmony_ciout_fail: 14862306a36Sopenharmony_ci return ERR_PTR(-ENOMEM); 14962306a36Sopenharmony_ci} 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_ci 15262306a36Sopenharmony_ci/* Free a compression data structure */ 15362306a36Sopenharmony_civoid 15462306a36Sopenharmony_cislhc_free(struct slcompress *comp) 15562306a36Sopenharmony_ci{ 15662306a36Sopenharmony_ci if ( IS_ERR_OR_NULL(comp) ) 15762306a36Sopenharmony_ci return; 15862306a36Sopenharmony_ci 15962306a36Sopenharmony_ci if ( comp->tstate != NULLSLSTATE ) 16062306a36Sopenharmony_ci kfree( comp->tstate ); 16162306a36Sopenharmony_ci 16262306a36Sopenharmony_ci if ( comp->rstate != NULLSLSTATE ) 16362306a36Sopenharmony_ci kfree( comp->rstate ); 16462306a36Sopenharmony_ci 16562306a36Sopenharmony_ci kfree( comp ); 16662306a36Sopenharmony_ci} 16762306a36Sopenharmony_ci 16862306a36Sopenharmony_ci 16962306a36Sopenharmony_ci/* Put a short in host order into a char array in network order */ 17062306a36Sopenharmony_cistatic inline unsigned char * 17162306a36Sopenharmony_ciput16(unsigned char *cp, unsigned short x) 17262306a36Sopenharmony_ci{ 17362306a36Sopenharmony_ci *cp++ = x >> 8; 17462306a36Sopenharmony_ci *cp++ = x; 17562306a36Sopenharmony_ci 17662306a36Sopenharmony_ci return cp; 17762306a36Sopenharmony_ci} 17862306a36Sopenharmony_ci 17962306a36Sopenharmony_ci 18062306a36Sopenharmony_ci/* Encode a number */ 18162306a36Sopenharmony_cistatic unsigned char * 18262306a36Sopenharmony_ciencode(unsigned char *cp, unsigned short n) 18362306a36Sopenharmony_ci{ 18462306a36Sopenharmony_ci if(n >= 256 || n == 0){ 18562306a36Sopenharmony_ci *cp++ = 0; 18662306a36Sopenharmony_ci cp = put16(cp,n); 18762306a36Sopenharmony_ci } else { 18862306a36Sopenharmony_ci *cp++ = n; 18962306a36Sopenharmony_ci } 19062306a36Sopenharmony_ci return cp; 19162306a36Sopenharmony_ci} 19262306a36Sopenharmony_ci 19362306a36Sopenharmony_ci/* Pull a 16-bit integer in host order from buffer in network byte order */ 19462306a36Sopenharmony_cistatic unsigned short 19562306a36Sopenharmony_cipull16(unsigned char **cpp) 19662306a36Sopenharmony_ci{ 19762306a36Sopenharmony_ci short rval; 19862306a36Sopenharmony_ci 19962306a36Sopenharmony_ci rval = *(*cpp)++; 20062306a36Sopenharmony_ci rval <<= 8; 20162306a36Sopenharmony_ci rval |= *(*cpp)++; 20262306a36Sopenharmony_ci return rval; 20362306a36Sopenharmony_ci} 20462306a36Sopenharmony_ci 20562306a36Sopenharmony_ci/* Decode a number */ 20662306a36Sopenharmony_cistatic long 20762306a36Sopenharmony_cidecode(unsigned char **cpp) 20862306a36Sopenharmony_ci{ 20962306a36Sopenharmony_ci int x; 21062306a36Sopenharmony_ci 21162306a36Sopenharmony_ci x = *(*cpp)++; 21262306a36Sopenharmony_ci if(x == 0){ 21362306a36Sopenharmony_ci return pull16(cpp) & 0xffff; /* pull16 returns -1 on error */ 21462306a36Sopenharmony_ci } else { 21562306a36Sopenharmony_ci return x & 0xff; /* -1 if PULLCHAR returned error */ 21662306a36Sopenharmony_ci } 21762306a36Sopenharmony_ci} 21862306a36Sopenharmony_ci 21962306a36Sopenharmony_ci/* 22062306a36Sopenharmony_ci * icp and isize are the original packet. 22162306a36Sopenharmony_ci * ocp is a place to put a copy if necessary. 22262306a36Sopenharmony_ci * cpp is initially a pointer to icp. If the copy is used, 22362306a36Sopenharmony_ci * change it to ocp. 22462306a36Sopenharmony_ci */ 22562306a36Sopenharmony_ci 22662306a36Sopenharmony_ciint 22762306a36Sopenharmony_cislhc_compress(struct slcompress *comp, unsigned char *icp, int isize, 22862306a36Sopenharmony_ci unsigned char *ocp, unsigned char **cpp, int compress_cid) 22962306a36Sopenharmony_ci{ 23062306a36Sopenharmony_ci struct cstate *ocs = &(comp->tstate[comp->xmit_oldest]); 23162306a36Sopenharmony_ci struct cstate *lcs = ocs; 23262306a36Sopenharmony_ci struct cstate *cs = lcs->next; 23362306a36Sopenharmony_ci unsigned long deltaS, deltaA; 23462306a36Sopenharmony_ci short changes = 0; 23562306a36Sopenharmony_ci int nlen, hlen; 23662306a36Sopenharmony_ci unsigned char new_seq[16]; 23762306a36Sopenharmony_ci unsigned char *cp = new_seq; 23862306a36Sopenharmony_ci struct iphdr *ip; 23962306a36Sopenharmony_ci struct tcphdr *th, *oth; 24062306a36Sopenharmony_ci __sum16 csum; 24162306a36Sopenharmony_ci 24262306a36Sopenharmony_ci 24362306a36Sopenharmony_ci /* 24462306a36Sopenharmony_ci * Don't play with runt packets. 24562306a36Sopenharmony_ci */ 24662306a36Sopenharmony_ci 24762306a36Sopenharmony_ci if(isize<sizeof(struct iphdr)) 24862306a36Sopenharmony_ci return isize; 24962306a36Sopenharmony_ci 25062306a36Sopenharmony_ci ip = (struct iphdr *) icp; 25162306a36Sopenharmony_ci if (ip->version != 4 || ip->ihl < 5) 25262306a36Sopenharmony_ci return isize; 25362306a36Sopenharmony_ci 25462306a36Sopenharmony_ci /* Bail if this packet isn't TCP, or is an IP fragment */ 25562306a36Sopenharmony_ci if (ip->protocol != IPPROTO_TCP || (ntohs(ip->frag_off) & 0x3fff)) { 25662306a36Sopenharmony_ci /* Send as regular IP */ 25762306a36Sopenharmony_ci if(ip->protocol != IPPROTO_TCP) 25862306a36Sopenharmony_ci comp->sls_o_nontcp++; 25962306a36Sopenharmony_ci else 26062306a36Sopenharmony_ci comp->sls_o_tcp++; 26162306a36Sopenharmony_ci return isize; 26262306a36Sopenharmony_ci } 26362306a36Sopenharmony_ci nlen = ip->ihl * 4; 26462306a36Sopenharmony_ci if (isize < nlen + sizeof(*th)) 26562306a36Sopenharmony_ci return isize; 26662306a36Sopenharmony_ci 26762306a36Sopenharmony_ci th = (struct tcphdr *)(icp + nlen); 26862306a36Sopenharmony_ci if (th->doff < sizeof(struct tcphdr) / 4) 26962306a36Sopenharmony_ci return isize; 27062306a36Sopenharmony_ci hlen = nlen + th->doff * 4; 27162306a36Sopenharmony_ci 27262306a36Sopenharmony_ci /* Bail if the TCP packet isn't `compressible' (i.e., ACK isn't set or 27362306a36Sopenharmony_ci * some other control bit is set). Also uncompressible if 27462306a36Sopenharmony_ci * it's a runt. 27562306a36Sopenharmony_ci */ 27662306a36Sopenharmony_ci if(hlen > isize || th->syn || th->fin || th->rst || 27762306a36Sopenharmony_ci ! (th->ack)){ 27862306a36Sopenharmony_ci /* TCP connection stuff; send as regular IP */ 27962306a36Sopenharmony_ci comp->sls_o_tcp++; 28062306a36Sopenharmony_ci return isize; 28162306a36Sopenharmony_ci } 28262306a36Sopenharmony_ci /* 28362306a36Sopenharmony_ci * Packet is compressible -- we're going to send either a 28462306a36Sopenharmony_ci * COMPRESSED_TCP or UNCOMPRESSED_TCP packet. Either way, 28562306a36Sopenharmony_ci * we need to locate (or create) the connection state. 28662306a36Sopenharmony_ci * 28762306a36Sopenharmony_ci * States are kept in a circularly linked list with 28862306a36Sopenharmony_ci * xmit_oldest pointing to the end of the list. The 28962306a36Sopenharmony_ci * list is kept in lru order by moving a state to the 29062306a36Sopenharmony_ci * head of the list whenever it is referenced. Since 29162306a36Sopenharmony_ci * the list is short and, empirically, the connection 29262306a36Sopenharmony_ci * we want is almost always near the front, we locate 29362306a36Sopenharmony_ci * states via linear search. If we don't find a state 29462306a36Sopenharmony_ci * for the datagram, the oldest state is (re-)used. 29562306a36Sopenharmony_ci */ 29662306a36Sopenharmony_ci for ( ; ; ) { 29762306a36Sopenharmony_ci if( ip->saddr == cs->cs_ip.saddr 29862306a36Sopenharmony_ci && ip->daddr == cs->cs_ip.daddr 29962306a36Sopenharmony_ci && th->source == cs->cs_tcp.source 30062306a36Sopenharmony_ci && th->dest == cs->cs_tcp.dest) 30162306a36Sopenharmony_ci goto found; 30262306a36Sopenharmony_ci 30362306a36Sopenharmony_ci /* if current equal oldest, at end of list */ 30462306a36Sopenharmony_ci if ( cs == ocs ) 30562306a36Sopenharmony_ci break; 30662306a36Sopenharmony_ci lcs = cs; 30762306a36Sopenharmony_ci cs = cs->next; 30862306a36Sopenharmony_ci comp->sls_o_searches++; 30962306a36Sopenharmony_ci } 31062306a36Sopenharmony_ci /* 31162306a36Sopenharmony_ci * Didn't find it -- re-use oldest cstate. Send an 31262306a36Sopenharmony_ci * uncompressed packet that tells the other side what 31362306a36Sopenharmony_ci * connection number we're using for this conversation. 31462306a36Sopenharmony_ci * 31562306a36Sopenharmony_ci * Note that since the state list is circular, the oldest 31662306a36Sopenharmony_ci * state points to the newest and we only need to set 31762306a36Sopenharmony_ci * xmit_oldest to update the lru linkage. 31862306a36Sopenharmony_ci */ 31962306a36Sopenharmony_ci comp->sls_o_misses++; 32062306a36Sopenharmony_ci comp->xmit_oldest = lcs->cs_this; 32162306a36Sopenharmony_ci goto uncompressed; 32262306a36Sopenharmony_ci 32362306a36Sopenharmony_cifound: 32462306a36Sopenharmony_ci /* 32562306a36Sopenharmony_ci * Found it -- move to the front on the connection list. 32662306a36Sopenharmony_ci */ 32762306a36Sopenharmony_ci if(lcs == ocs) { 32862306a36Sopenharmony_ci /* found at most recently used */ 32962306a36Sopenharmony_ci } else if (cs == ocs) { 33062306a36Sopenharmony_ci /* found at least recently used */ 33162306a36Sopenharmony_ci comp->xmit_oldest = lcs->cs_this; 33262306a36Sopenharmony_ci } else { 33362306a36Sopenharmony_ci /* more than 2 elements */ 33462306a36Sopenharmony_ci lcs->next = cs->next; 33562306a36Sopenharmony_ci cs->next = ocs->next; 33662306a36Sopenharmony_ci ocs->next = cs; 33762306a36Sopenharmony_ci } 33862306a36Sopenharmony_ci 33962306a36Sopenharmony_ci /* 34062306a36Sopenharmony_ci * Make sure that only what we expect to change changed. 34162306a36Sopenharmony_ci * Check the following: 34262306a36Sopenharmony_ci * IP protocol version, header length & type of service. 34362306a36Sopenharmony_ci * The "Don't fragment" bit. 34462306a36Sopenharmony_ci * The time-to-live field. 34562306a36Sopenharmony_ci * The TCP header length. 34662306a36Sopenharmony_ci * IP options, if any. 34762306a36Sopenharmony_ci * TCP options, if any. 34862306a36Sopenharmony_ci * If any of these things are different between the previous & 34962306a36Sopenharmony_ci * current datagram, we send the current datagram `uncompressed'. 35062306a36Sopenharmony_ci */ 35162306a36Sopenharmony_ci oth = &cs->cs_tcp; 35262306a36Sopenharmony_ci 35362306a36Sopenharmony_ci if(ip->version != cs->cs_ip.version || ip->ihl != cs->cs_ip.ihl 35462306a36Sopenharmony_ci || ip->tos != cs->cs_ip.tos 35562306a36Sopenharmony_ci || (ip->frag_off & htons(0x4000)) != (cs->cs_ip.frag_off & htons(0x4000)) 35662306a36Sopenharmony_ci || ip->ttl != cs->cs_ip.ttl 35762306a36Sopenharmony_ci || th->doff != cs->cs_tcp.doff 35862306a36Sopenharmony_ci || (ip->ihl > 5 && memcmp(ip+1,cs->cs_ipopt,((ip->ihl)-5)*4) != 0) 35962306a36Sopenharmony_ci || (th->doff > 5 && memcmp(th+1,cs->cs_tcpopt,((th->doff)-5)*4) != 0)){ 36062306a36Sopenharmony_ci goto uncompressed; 36162306a36Sopenharmony_ci } 36262306a36Sopenharmony_ci 36362306a36Sopenharmony_ci /* 36462306a36Sopenharmony_ci * Figure out which of the changing fields changed. The 36562306a36Sopenharmony_ci * receiver expects changes in the order: urgent, window, 36662306a36Sopenharmony_ci * ack, seq (the order minimizes the number of temporaries 36762306a36Sopenharmony_ci * needed in this section of code). 36862306a36Sopenharmony_ci */ 36962306a36Sopenharmony_ci if(th->urg){ 37062306a36Sopenharmony_ci deltaS = ntohs(th->urg_ptr); 37162306a36Sopenharmony_ci cp = encode(cp,deltaS); 37262306a36Sopenharmony_ci changes |= NEW_U; 37362306a36Sopenharmony_ci } else if(th->urg_ptr != oth->urg_ptr){ 37462306a36Sopenharmony_ci /* argh! URG not set but urp changed -- a sensible 37562306a36Sopenharmony_ci * implementation should never do this but RFC793 37662306a36Sopenharmony_ci * doesn't prohibit the change so we have to deal 37762306a36Sopenharmony_ci * with it. */ 37862306a36Sopenharmony_ci goto uncompressed; 37962306a36Sopenharmony_ci } 38062306a36Sopenharmony_ci if((deltaS = ntohs(th->window) - ntohs(oth->window)) != 0){ 38162306a36Sopenharmony_ci cp = encode(cp,deltaS); 38262306a36Sopenharmony_ci changes |= NEW_W; 38362306a36Sopenharmony_ci } 38462306a36Sopenharmony_ci if((deltaA = ntohl(th->ack_seq) - ntohl(oth->ack_seq)) != 0L){ 38562306a36Sopenharmony_ci if(deltaA > 0x0000ffff) 38662306a36Sopenharmony_ci goto uncompressed; 38762306a36Sopenharmony_ci cp = encode(cp,deltaA); 38862306a36Sopenharmony_ci changes |= NEW_A; 38962306a36Sopenharmony_ci } 39062306a36Sopenharmony_ci if((deltaS = ntohl(th->seq) - ntohl(oth->seq)) != 0L){ 39162306a36Sopenharmony_ci if(deltaS > 0x0000ffff) 39262306a36Sopenharmony_ci goto uncompressed; 39362306a36Sopenharmony_ci cp = encode(cp,deltaS); 39462306a36Sopenharmony_ci changes |= NEW_S; 39562306a36Sopenharmony_ci } 39662306a36Sopenharmony_ci 39762306a36Sopenharmony_ci switch(changes){ 39862306a36Sopenharmony_ci case 0: /* Nothing changed. If this packet contains data and the 39962306a36Sopenharmony_ci * last one didn't, this is probably a data packet following 40062306a36Sopenharmony_ci * an ack (normal on an interactive connection) and we send 40162306a36Sopenharmony_ci * it compressed. Otherwise it's probably a retransmit, 40262306a36Sopenharmony_ci * retransmitted ack or window probe. Send it uncompressed 40362306a36Sopenharmony_ci * in case the other side missed the compressed version. 40462306a36Sopenharmony_ci */ 40562306a36Sopenharmony_ci if(ip->tot_len != cs->cs_ip.tot_len && 40662306a36Sopenharmony_ci ntohs(cs->cs_ip.tot_len) == hlen) 40762306a36Sopenharmony_ci break; 40862306a36Sopenharmony_ci goto uncompressed; 40962306a36Sopenharmony_ci case SPECIAL_I: 41062306a36Sopenharmony_ci case SPECIAL_D: 41162306a36Sopenharmony_ci /* actual changes match one of our special case encodings -- 41262306a36Sopenharmony_ci * send packet uncompressed. 41362306a36Sopenharmony_ci */ 41462306a36Sopenharmony_ci goto uncompressed; 41562306a36Sopenharmony_ci case NEW_S|NEW_A: 41662306a36Sopenharmony_ci if(deltaS == deltaA && 41762306a36Sopenharmony_ci deltaS == ntohs(cs->cs_ip.tot_len) - hlen){ 41862306a36Sopenharmony_ci /* special case for echoed terminal traffic */ 41962306a36Sopenharmony_ci changes = SPECIAL_I; 42062306a36Sopenharmony_ci cp = new_seq; 42162306a36Sopenharmony_ci } 42262306a36Sopenharmony_ci break; 42362306a36Sopenharmony_ci case NEW_S: 42462306a36Sopenharmony_ci if(deltaS == ntohs(cs->cs_ip.tot_len) - hlen){ 42562306a36Sopenharmony_ci /* special case for data xfer */ 42662306a36Sopenharmony_ci changes = SPECIAL_D; 42762306a36Sopenharmony_ci cp = new_seq; 42862306a36Sopenharmony_ci } 42962306a36Sopenharmony_ci break; 43062306a36Sopenharmony_ci } 43162306a36Sopenharmony_ci deltaS = ntohs(ip->id) - ntohs(cs->cs_ip.id); 43262306a36Sopenharmony_ci if(deltaS != 1){ 43362306a36Sopenharmony_ci cp = encode(cp,deltaS); 43462306a36Sopenharmony_ci changes |= NEW_I; 43562306a36Sopenharmony_ci } 43662306a36Sopenharmony_ci if(th->psh) 43762306a36Sopenharmony_ci changes |= TCP_PUSH_BIT; 43862306a36Sopenharmony_ci /* Grab the cksum before we overwrite it below. Then update our 43962306a36Sopenharmony_ci * state with this packet's header. 44062306a36Sopenharmony_ci */ 44162306a36Sopenharmony_ci csum = th->check; 44262306a36Sopenharmony_ci memcpy(&cs->cs_ip,ip,20); 44362306a36Sopenharmony_ci memcpy(&cs->cs_tcp,th,20); 44462306a36Sopenharmony_ci /* We want to use the original packet as our compressed packet. 44562306a36Sopenharmony_ci * (cp - new_seq) is the number of bytes we need for compressed 44662306a36Sopenharmony_ci * sequence numbers. In addition we need one byte for the change 44762306a36Sopenharmony_ci * mask, one for the connection id and two for the tcp checksum. 44862306a36Sopenharmony_ci * So, (cp - new_seq) + 4 bytes of header are needed. 44962306a36Sopenharmony_ci */ 45062306a36Sopenharmony_ci deltaS = cp - new_seq; 45162306a36Sopenharmony_ci if(compress_cid == 0 || comp->xmit_current != cs->cs_this){ 45262306a36Sopenharmony_ci cp = ocp; 45362306a36Sopenharmony_ci *cpp = ocp; 45462306a36Sopenharmony_ci *cp++ = changes | NEW_C; 45562306a36Sopenharmony_ci *cp++ = cs->cs_this; 45662306a36Sopenharmony_ci comp->xmit_current = cs->cs_this; 45762306a36Sopenharmony_ci } else { 45862306a36Sopenharmony_ci cp = ocp; 45962306a36Sopenharmony_ci *cpp = ocp; 46062306a36Sopenharmony_ci *cp++ = changes; 46162306a36Sopenharmony_ci } 46262306a36Sopenharmony_ci *(__sum16 *)cp = csum; 46362306a36Sopenharmony_ci cp += 2; 46462306a36Sopenharmony_ci/* deltaS is now the size of the change section of the compressed header */ 46562306a36Sopenharmony_ci memcpy(cp,new_seq,deltaS); /* Write list of deltas */ 46662306a36Sopenharmony_ci memcpy(cp+deltaS,icp+hlen,isize-hlen); 46762306a36Sopenharmony_ci comp->sls_o_compressed++; 46862306a36Sopenharmony_ci ocp[0] |= SL_TYPE_COMPRESSED_TCP; 46962306a36Sopenharmony_ci return isize - hlen + deltaS + (cp - ocp); 47062306a36Sopenharmony_ci 47162306a36Sopenharmony_ci /* Update connection state cs & send uncompressed packet (i.e., 47262306a36Sopenharmony_ci * a regular ip/tcp packet but with the 'conversation id' we hope 47362306a36Sopenharmony_ci * to use on future compressed packets in the protocol field). 47462306a36Sopenharmony_ci */ 47562306a36Sopenharmony_ciuncompressed: 47662306a36Sopenharmony_ci memcpy(&cs->cs_ip,ip,20); 47762306a36Sopenharmony_ci memcpy(&cs->cs_tcp,th,20); 47862306a36Sopenharmony_ci if (ip->ihl > 5) 47962306a36Sopenharmony_ci memcpy(cs->cs_ipopt, ip+1, ((ip->ihl) - 5) * 4); 48062306a36Sopenharmony_ci if (th->doff > 5) 48162306a36Sopenharmony_ci memcpy(cs->cs_tcpopt, th+1, ((th->doff) - 5) * 4); 48262306a36Sopenharmony_ci comp->xmit_current = cs->cs_this; 48362306a36Sopenharmony_ci comp->sls_o_uncompressed++; 48462306a36Sopenharmony_ci memcpy(ocp, icp, isize); 48562306a36Sopenharmony_ci *cpp = ocp; 48662306a36Sopenharmony_ci ocp[9] = cs->cs_this; 48762306a36Sopenharmony_ci ocp[0] |= SL_TYPE_UNCOMPRESSED_TCP; 48862306a36Sopenharmony_ci return isize; 48962306a36Sopenharmony_ci} 49062306a36Sopenharmony_ci 49162306a36Sopenharmony_ci 49262306a36Sopenharmony_ciint 49362306a36Sopenharmony_cislhc_uncompress(struct slcompress *comp, unsigned char *icp, int isize) 49462306a36Sopenharmony_ci{ 49562306a36Sopenharmony_ci int changes; 49662306a36Sopenharmony_ci long x; 49762306a36Sopenharmony_ci struct tcphdr *thp; 49862306a36Sopenharmony_ci struct iphdr *ip; 49962306a36Sopenharmony_ci struct cstate *cs; 50062306a36Sopenharmony_ci int len, hdrlen; 50162306a36Sopenharmony_ci unsigned char *cp = icp; 50262306a36Sopenharmony_ci 50362306a36Sopenharmony_ci /* We've got a compressed packet; read the change byte */ 50462306a36Sopenharmony_ci comp->sls_i_compressed++; 50562306a36Sopenharmony_ci if(isize < 3){ 50662306a36Sopenharmony_ci comp->sls_i_error++; 50762306a36Sopenharmony_ci return 0; 50862306a36Sopenharmony_ci } 50962306a36Sopenharmony_ci changes = *cp++; 51062306a36Sopenharmony_ci if(changes & NEW_C){ 51162306a36Sopenharmony_ci /* Make sure the state index is in range, then grab the state. 51262306a36Sopenharmony_ci * If we have a good state index, clear the 'discard' flag. 51362306a36Sopenharmony_ci */ 51462306a36Sopenharmony_ci x = *cp++; /* Read conn index */ 51562306a36Sopenharmony_ci if(x < 0 || x > comp->rslot_limit) 51662306a36Sopenharmony_ci goto bad; 51762306a36Sopenharmony_ci 51862306a36Sopenharmony_ci /* Check if the cstate is initialized */ 51962306a36Sopenharmony_ci if (!comp->rstate[x].initialized) 52062306a36Sopenharmony_ci goto bad; 52162306a36Sopenharmony_ci 52262306a36Sopenharmony_ci comp->flags &=~ SLF_TOSS; 52362306a36Sopenharmony_ci comp->recv_current = x; 52462306a36Sopenharmony_ci } else { 52562306a36Sopenharmony_ci /* this packet has an implicit state index. If we've 52662306a36Sopenharmony_ci * had a line error since the last time we got an 52762306a36Sopenharmony_ci * explicit state index, we have to toss the packet. */ 52862306a36Sopenharmony_ci if(comp->flags & SLF_TOSS){ 52962306a36Sopenharmony_ci comp->sls_i_tossed++; 53062306a36Sopenharmony_ci return 0; 53162306a36Sopenharmony_ci } 53262306a36Sopenharmony_ci } 53362306a36Sopenharmony_ci cs = &comp->rstate[comp->recv_current]; 53462306a36Sopenharmony_ci thp = &cs->cs_tcp; 53562306a36Sopenharmony_ci ip = &cs->cs_ip; 53662306a36Sopenharmony_ci 53762306a36Sopenharmony_ci thp->check = *(__sum16 *)cp; 53862306a36Sopenharmony_ci cp += 2; 53962306a36Sopenharmony_ci 54062306a36Sopenharmony_ci thp->psh = (changes & TCP_PUSH_BIT) ? 1 : 0; 54162306a36Sopenharmony_ci/* 54262306a36Sopenharmony_ci * we can use the same number for the length of the saved header and 54362306a36Sopenharmony_ci * the current one, because the packet wouldn't have been sent 54462306a36Sopenharmony_ci * as compressed unless the options were the same as the previous one 54562306a36Sopenharmony_ci */ 54662306a36Sopenharmony_ci 54762306a36Sopenharmony_ci hdrlen = ip->ihl * 4 + thp->doff * 4; 54862306a36Sopenharmony_ci 54962306a36Sopenharmony_ci switch(changes & SPECIALS_MASK){ 55062306a36Sopenharmony_ci case SPECIAL_I: /* Echoed terminal traffic */ 55162306a36Sopenharmony_ci { 55262306a36Sopenharmony_ci short i; 55362306a36Sopenharmony_ci i = ntohs(ip->tot_len) - hdrlen; 55462306a36Sopenharmony_ci thp->ack_seq = htonl( ntohl(thp->ack_seq) + i); 55562306a36Sopenharmony_ci thp->seq = htonl( ntohl(thp->seq) + i); 55662306a36Sopenharmony_ci } 55762306a36Sopenharmony_ci break; 55862306a36Sopenharmony_ci 55962306a36Sopenharmony_ci case SPECIAL_D: /* Unidirectional data */ 56062306a36Sopenharmony_ci thp->seq = htonl( ntohl(thp->seq) + 56162306a36Sopenharmony_ci ntohs(ip->tot_len) - hdrlen); 56262306a36Sopenharmony_ci break; 56362306a36Sopenharmony_ci 56462306a36Sopenharmony_ci default: 56562306a36Sopenharmony_ci if(changes & NEW_U){ 56662306a36Sopenharmony_ci thp->urg = 1; 56762306a36Sopenharmony_ci if((x = decode(&cp)) == -1) { 56862306a36Sopenharmony_ci goto bad; 56962306a36Sopenharmony_ci } 57062306a36Sopenharmony_ci thp->urg_ptr = htons(x); 57162306a36Sopenharmony_ci } else 57262306a36Sopenharmony_ci thp->urg = 0; 57362306a36Sopenharmony_ci if(changes & NEW_W){ 57462306a36Sopenharmony_ci if((x = decode(&cp)) == -1) { 57562306a36Sopenharmony_ci goto bad; 57662306a36Sopenharmony_ci } 57762306a36Sopenharmony_ci thp->window = htons( ntohs(thp->window) + x); 57862306a36Sopenharmony_ci } 57962306a36Sopenharmony_ci if(changes & NEW_A){ 58062306a36Sopenharmony_ci if((x = decode(&cp)) == -1) { 58162306a36Sopenharmony_ci goto bad; 58262306a36Sopenharmony_ci } 58362306a36Sopenharmony_ci thp->ack_seq = htonl( ntohl(thp->ack_seq) + x); 58462306a36Sopenharmony_ci } 58562306a36Sopenharmony_ci if(changes & NEW_S){ 58662306a36Sopenharmony_ci if((x = decode(&cp)) == -1) { 58762306a36Sopenharmony_ci goto bad; 58862306a36Sopenharmony_ci } 58962306a36Sopenharmony_ci thp->seq = htonl( ntohl(thp->seq) + x); 59062306a36Sopenharmony_ci } 59162306a36Sopenharmony_ci break; 59262306a36Sopenharmony_ci } 59362306a36Sopenharmony_ci if(changes & NEW_I){ 59462306a36Sopenharmony_ci if((x = decode(&cp)) == -1) { 59562306a36Sopenharmony_ci goto bad; 59662306a36Sopenharmony_ci } 59762306a36Sopenharmony_ci ip->id = htons (ntohs (ip->id) + x); 59862306a36Sopenharmony_ci } else 59962306a36Sopenharmony_ci ip->id = htons (ntohs (ip->id) + 1); 60062306a36Sopenharmony_ci 60162306a36Sopenharmony_ci /* 60262306a36Sopenharmony_ci * At this point, cp points to the first byte of data in the 60362306a36Sopenharmony_ci * packet. Put the reconstructed TCP and IP headers back on the 60462306a36Sopenharmony_ci * packet. Recalculate IP checksum (but not TCP checksum). 60562306a36Sopenharmony_ci */ 60662306a36Sopenharmony_ci 60762306a36Sopenharmony_ci len = isize - (cp - icp); 60862306a36Sopenharmony_ci if (len < 0) 60962306a36Sopenharmony_ci goto bad; 61062306a36Sopenharmony_ci len += hdrlen; 61162306a36Sopenharmony_ci ip->tot_len = htons(len); 61262306a36Sopenharmony_ci ip->check = 0; 61362306a36Sopenharmony_ci 61462306a36Sopenharmony_ci memmove(icp + hdrlen, cp, len - hdrlen); 61562306a36Sopenharmony_ci 61662306a36Sopenharmony_ci cp = icp; 61762306a36Sopenharmony_ci memcpy(cp, ip, 20); 61862306a36Sopenharmony_ci cp += 20; 61962306a36Sopenharmony_ci 62062306a36Sopenharmony_ci if (ip->ihl > 5) { 62162306a36Sopenharmony_ci memcpy(cp, cs->cs_ipopt, (ip->ihl - 5) * 4); 62262306a36Sopenharmony_ci cp += (ip->ihl - 5) * 4; 62362306a36Sopenharmony_ci } 62462306a36Sopenharmony_ci 62562306a36Sopenharmony_ci put_unaligned(ip_fast_csum(icp, ip->ihl), 62662306a36Sopenharmony_ci &((struct iphdr *)icp)->check); 62762306a36Sopenharmony_ci 62862306a36Sopenharmony_ci memcpy(cp, thp, 20); 62962306a36Sopenharmony_ci cp += 20; 63062306a36Sopenharmony_ci 63162306a36Sopenharmony_ci if (thp->doff > 5) { 63262306a36Sopenharmony_ci memcpy(cp, cs->cs_tcpopt, ((thp->doff) - 5) * 4); 63362306a36Sopenharmony_ci cp += ((thp->doff) - 5) * 4; 63462306a36Sopenharmony_ci } 63562306a36Sopenharmony_ci 63662306a36Sopenharmony_ci return len; 63762306a36Sopenharmony_cibad: 63862306a36Sopenharmony_ci comp->sls_i_error++; 63962306a36Sopenharmony_ci return slhc_toss( comp ); 64062306a36Sopenharmony_ci} 64162306a36Sopenharmony_ci 64262306a36Sopenharmony_ci 64362306a36Sopenharmony_ciint 64462306a36Sopenharmony_cislhc_remember(struct slcompress *comp, unsigned char *icp, int isize) 64562306a36Sopenharmony_ci{ 64662306a36Sopenharmony_ci struct cstate *cs; 64762306a36Sopenharmony_ci unsigned ihl; 64862306a36Sopenharmony_ci 64962306a36Sopenharmony_ci unsigned char index; 65062306a36Sopenharmony_ci 65162306a36Sopenharmony_ci if(isize < 20) { 65262306a36Sopenharmony_ci /* The packet is shorter than a legal IP header */ 65362306a36Sopenharmony_ci comp->sls_i_runt++; 65462306a36Sopenharmony_ci return slhc_toss( comp ); 65562306a36Sopenharmony_ci } 65662306a36Sopenharmony_ci /* Peek at the IP header's IHL field to find its length */ 65762306a36Sopenharmony_ci ihl = icp[0] & 0xf; 65862306a36Sopenharmony_ci if(ihl < 20 / 4){ 65962306a36Sopenharmony_ci /* The IP header length field is too small */ 66062306a36Sopenharmony_ci comp->sls_i_runt++; 66162306a36Sopenharmony_ci return slhc_toss( comp ); 66262306a36Sopenharmony_ci } 66362306a36Sopenharmony_ci index = icp[9]; 66462306a36Sopenharmony_ci icp[9] = IPPROTO_TCP; 66562306a36Sopenharmony_ci 66662306a36Sopenharmony_ci if (ip_fast_csum(icp, ihl)) { 66762306a36Sopenharmony_ci /* Bad IP header checksum; discard */ 66862306a36Sopenharmony_ci comp->sls_i_badcheck++; 66962306a36Sopenharmony_ci return slhc_toss( comp ); 67062306a36Sopenharmony_ci } 67162306a36Sopenharmony_ci if(index > comp->rslot_limit) { 67262306a36Sopenharmony_ci comp->sls_i_error++; 67362306a36Sopenharmony_ci return slhc_toss(comp); 67462306a36Sopenharmony_ci } 67562306a36Sopenharmony_ci 67662306a36Sopenharmony_ci /* Update local state */ 67762306a36Sopenharmony_ci cs = &comp->rstate[comp->recv_current = index]; 67862306a36Sopenharmony_ci comp->flags &=~ SLF_TOSS; 67962306a36Sopenharmony_ci memcpy(&cs->cs_ip,icp,20); 68062306a36Sopenharmony_ci memcpy(&cs->cs_tcp,icp + ihl*4,20); 68162306a36Sopenharmony_ci if (ihl > 5) 68262306a36Sopenharmony_ci memcpy(cs->cs_ipopt, icp + sizeof(struct iphdr), (ihl - 5) * 4); 68362306a36Sopenharmony_ci if (cs->cs_tcp.doff > 5) 68462306a36Sopenharmony_ci memcpy(cs->cs_tcpopt, icp + ihl*4 + sizeof(struct tcphdr), (cs->cs_tcp.doff - 5) * 4); 68562306a36Sopenharmony_ci cs->cs_hsize = ihl*2 + cs->cs_tcp.doff*2; 68662306a36Sopenharmony_ci cs->initialized = true; 68762306a36Sopenharmony_ci /* Put headers back on packet 68862306a36Sopenharmony_ci * Neither header checksum is recalculated 68962306a36Sopenharmony_ci */ 69062306a36Sopenharmony_ci comp->sls_i_uncompressed++; 69162306a36Sopenharmony_ci return isize; 69262306a36Sopenharmony_ci} 69362306a36Sopenharmony_ci 69462306a36Sopenharmony_ciint 69562306a36Sopenharmony_cislhc_toss(struct slcompress *comp) 69662306a36Sopenharmony_ci{ 69762306a36Sopenharmony_ci if ( comp == NULLSLCOMPR ) 69862306a36Sopenharmony_ci return 0; 69962306a36Sopenharmony_ci 70062306a36Sopenharmony_ci comp->flags |= SLF_TOSS; 70162306a36Sopenharmony_ci return 0; 70262306a36Sopenharmony_ci} 70362306a36Sopenharmony_ci 70462306a36Sopenharmony_ci#else /* CONFIG_INET */ 70562306a36Sopenharmony_ci 70662306a36Sopenharmony_ciint 70762306a36Sopenharmony_cislhc_toss(struct slcompress *comp) 70862306a36Sopenharmony_ci{ 70962306a36Sopenharmony_ci printk(KERN_DEBUG "Called IP function on non IP-system: slhc_toss"); 71062306a36Sopenharmony_ci return -EINVAL; 71162306a36Sopenharmony_ci} 71262306a36Sopenharmony_ciint 71362306a36Sopenharmony_cislhc_uncompress(struct slcompress *comp, unsigned char *icp, int isize) 71462306a36Sopenharmony_ci{ 71562306a36Sopenharmony_ci printk(KERN_DEBUG "Called IP function on non IP-system: slhc_uncompress"); 71662306a36Sopenharmony_ci return -EINVAL; 71762306a36Sopenharmony_ci} 71862306a36Sopenharmony_ciint 71962306a36Sopenharmony_cislhc_compress(struct slcompress *comp, unsigned char *icp, int isize, 72062306a36Sopenharmony_ci unsigned char *ocp, unsigned char **cpp, int compress_cid) 72162306a36Sopenharmony_ci{ 72262306a36Sopenharmony_ci printk(KERN_DEBUG "Called IP function on non IP-system: slhc_compress"); 72362306a36Sopenharmony_ci return -EINVAL; 72462306a36Sopenharmony_ci} 72562306a36Sopenharmony_ci 72662306a36Sopenharmony_ciint 72762306a36Sopenharmony_cislhc_remember(struct slcompress *comp, unsigned char *icp, int isize) 72862306a36Sopenharmony_ci{ 72962306a36Sopenharmony_ci printk(KERN_DEBUG "Called IP function on non IP-system: slhc_remember"); 73062306a36Sopenharmony_ci return -EINVAL; 73162306a36Sopenharmony_ci} 73262306a36Sopenharmony_ci 73362306a36Sopenharmony_civoid 73462306a36Sopenharmony_cislhc_free(struct slcompress *comp) 73562306a36Sopenharmony_ci{ 73662306a36Sopenharmony_ci printk(KERN_DEBUG "Called IP function on non IP-system: slhc_free"); 73762306a36Sopenharmony_ci} 73862306a36Sopenharmony_cistruct slcompress * 73962306a36Sopenharmony_cislhc_init(int rslots, int tslots) 74062306a36Sopenharmony_ci{ 74162306a36Sopenharmony_ci printk(KERN_DEBUG "Called IP function on non IP-system: slhc_init"); 74262306a36Sopenharmony_ci return NULL; 74362306a36Sopenharmony_ci} 74462306a36Sopenharmony_ci 74562306a36Sopenharmony_ci#endif /* CONFIG_INET */ 74662306a36Sopenharmony_ci 74762306a36Sopenharmony_ci/* VJ header compression */ 74862306a36Sopenharmony_ciEXPORT_SYMBOL(slhc_init); 74962306a36Sopenharmony_ciEXPORT_SYMBOL(slhc_free); 75062306a36Sopenharmony_ciEXPORT_SYMBOL(slhc_remember); 75162306a36Sopenharmony_ciEXPORT_SYMBOL(slhc_compress); 75262306a36Sopenharmony_ciEXPORT_SYMBOL(slhc_uncompress); 75362306a36Sopenharmony_ciEXPORT_SYMBOL(slhc_toss); 75462306a36Sopenharmony_ci 75562306a36Sopenharmony_ciMODULE_LICENSE("Dual BSD/GPL"); 756