diff -urN linux-2.6.32-rc6/Documentation/isdn/README.LZS linux-2.6.32-rc6.fli4l/Documentation/isdn/README.LZS --- linux-2.6.32-rc6/Documentation/isdn/README.LZS 1970-01-01 01:00:00.000000000 +0100 +++ linux-2.6.32-rc6.fli4l/Documentation/isdn/README.LZS 2009-11-11 13:20:02.722061367 +0100 @@ -0,0 +1,317 @@ + +# $Id: README,v 1.4 2001/10/11 16:52:22 beck Exp $ + + LZS (aka Stac aka HiFn) compression support for I4L SyncPPP + +Sometime in 1998 I picked up a question in the de.alt.comm.isdn4linux +newsgroup asking "Is there Stac compression support for I4L ?". Due to a +strange coincidence, I had found out before on this same day that RFC1974 +is a somewhat complete documentation of the LZS CCP stuff and contains enough +information to code a decompressor and compressor. Thus I answered the +question like "a decompressor requires only some effort, a compressor +is somewhat more complicated but clearly possible". At this time the real +problem was that the isdn_ppp stuff in I4L was not ready for CCP modules at +all, most hooks were still missing. But then Michael Hipp implemented +the hooks in the newer CVS source of I4L and got BSD compression basically +working, so the ball was at my foot again. Thus I decided to code that +decompressor and eventually code a compressor, too. + + + LICENSE + +The code is Copyleft 1998-2001 by Andre Beck under terms of the +GNU GPL. While the code is provided free and under GPL, it may infringe +patents Stac has applied for in the US and maybe other countries. Verify +that this is not the case in your country before using it. This code is a +private development related in no way whatsoever to my employer, written in +Germany where software patents are still considered bogus. This +situation might change any day due to EU law. Linux distributors who +supply this stuff shrink wrapped should reconsider doing this (not only +for isdn_lzscomp, but as well for isdn_bsdcomp, which is likely to +infringe on the infamous Unisys LZW patent) with their law departement. +See also the PATENT ISSUES section. + + + STATUS + +After three years without a lot of changes, the code should still be +considered beta. There are two different things here, the actual LZS +implementation (isdn_lzscomp.c) and the CCP framework (to be found in +isdn_ppp.c) in conjunction with the necessary changes to ipppd. + +a) The compression module + The module appears to be working well. It could still get some reworks + (last but not least some comments are outdated and contrary to the + actual code), though. One of the things to be reworked is the does-or- + does-not-compress heuristics. Some code is already there, but not + enabled yet asl long as there is no clear API for the module to know + the MTU of the frames it can generate. Another potential improvement + would be to change the default for tweaks to no tweaks at all - but + then again, it's a lot of Ascends out there. If there would ever be + more peers supporting multihistory LZS, trying a basic multihistory + extension to the compressor would be quite interesting, but at the + moment it appears mostly useless. + +b) ipppd + The changes to ipppd have settled since the first implementation, + the version of ipppd in current distributions already contains them. + I had supplied patches later that disabled CCP per default, but I'm + not sure whether they made it everywhere. The code generally works, + but it is known not to be flawless. Primarily, it doesn't gracefully + terminate when the peer is rejecting our CCP ConfReqs, nor does it + try other parameters that may be supported by the peer. It could be + improved, ideally by querying for multihistory SEQ mode first, then + falling back to single history SEQ and then trying EXT, with a proper + bailout of CCP if all of them fail. + +c) The CCP framework + That's the most unstable part of the whole thing. We had to expand and + change some portions of isdn_ppp, mainly those dealing with Reset + Requests and -Acks. This code is raw and not really finished, primarily + because I didn't want to hack around in isdn_ppp but just wanted to write + the LZS module. But LZS CCP is more complicated than most other CCP + engines and required those changes. I hope someone with more + insight into isdn_ppp will adopt and complete them. + - The state engine that generates and deals with ResetReqs and -Acks + is quite a hack. I am not sure about it's fitting into the kernel + parallelism aspects. + - The code is trying to deal with both, the normal (bundle) and the + link compression variants of CCP. The latter is normally not used, + and still completely untested (and expected to fail). + +As a conclusion, the more betaish aspect of the whole thing is the CCP +framework with which you have to deal if you try any CCP module. For LZS, +the module is somewhat well tested and seems to not have obvious bugs, +though some deficiencies. Interaction with MP in newer kernels seems to +be rather flakey at least in EXT mode. If you don't need MP, disable it +in ipppd removing "+mp" from the config options. We're working on it. + + + BUILDING + +The easiest way to build the module is to copy isdn_lzscomp.c to +linux/drivers/isdn/ and to edit the Makefile in this directory, adding +a line for LZS. The structure of the kernel Makefiles has changed over +the years, so this is best done using the patch provided by Frank Elsner +for most kernel versions. The patch even includes changes to the kernel +configuration help and config file, so building the code can be triggered +with whatever config frontend you like. + +The module will be built on the next "make modules" and installed with +"make modules_install", or the distribution dependent mechanisms that +replace that (like Debian make-kpkg and dpkg). But if you want, you still +can build the module standalone and copy it wherever you see fit, but deal +with the oddities (aka MODVERSIONS) yourself. + + + USAGE + +If you want to use any CCP method with I4L, you must first load the +relevant module(s) explicitely into the kernel. This is best accomplished +either when loading the basic isdn modules, or in the script that sets up +the interfaces and ipppd for the connections. After loading the module(s) +you start the connection. You may need to give options to ipppd in order +to make sure negotiations succeed, see below. + + + Supported Modes + +This set of sources supports LZS check mode 3 (the default of RFC1974) +and LZS check mode 4 (the derivative used by Micro$oft in Win0.95). If your +peer is an Ascend Max, the techs of the ISP might use another nomenclature: + +What it is Ascend NVRAM Menu Ascend RADIUS + +RFC1974 LZS SEQ Mode Stac-9 (named after Link-Comp-Stac-Draft-9 +(check mode 3) draft 9 of the RFC) + +RFC1974 LZS EXT Mode MS-Stac Link-Comp-MS-Stac +(check mode 4) + + +Ask your ISP for what their NAS speaks. Either mode is Ok, but if you +insist on non-M$, I'm virtually yours. The compression deployed by german +mass dialin provider T-Online in mid-2001 (beeing the reason for much +increased usage of this code) is the EXT mode variant. + +Note: If your peer sends you CCP with the LZS identity (0x11) but a + length of 6 bytes, it is not compatible with RFC1974 but uses + a proprietary predecessor of it. Typical case is an Ascend Max + that has never been set to something else but the default "Stac" + in "Answer" and doesn't override this per-profile, either. Ask + your ISP about changing this, nobody (but other Ascend boxes) + will ever be able to use this compression. Especially not Win95, + if the ISP wants interoperability with Win95 he needs to set + MS-Stac, and then this module works as well. In a later patch + ipppd was modified to detect that variant of pre-RFC LZS and + name it correctly in the logs. + +If the NAS you are talking to will not negotiate gracefully you may want +to wire the exact mode ipppd is requesting. For instance, an Ascend Max +with a profile set to Stac-9, when asked for any number of histories but +one or asked for check mode 4 will not NAK these options with the ones +it likes better, but will reject LZS altogether. Your only chance to get +it to negotiate LZS is to use the exact options it wants in the first +place. To achieve that, ipppd got a new parameter: + + lzs [recv-hists[:recv-mode[,xmit-hists[:xmit-mode]]]] + + recv-hists Downstream # of Histories [8] + recv-mode Mode used downstream [3] + xmit-hists Upstream # of Histories [recv-hists] + xmit-mode Upstream mode [recv-mode] + +This option is, as always, accompanied by + + -lzs + nolzs + +to prevent any LZS negotiations from happening. Some examples: + + lzs 1 Mode 3 with 1 hist in both directions. This + will negotiate against a Max at Stac-9 + + lzs 1:4 Mode 4 with 1 hist in both directions. This + will negotiate against a Max at MS-Stac + +Multihistory is reported to work as expected by some users that found the +occasional NAS supporting it at their ISP. There is only downstream support +for multiple histories (improving significantly the overall compression +ratio as soon as more than one flow is active on the same PPP link), to +support it upstream would require nontrivial code extensions. + + + Module options + +When loading the isdn_lzscomp module, some options are available to tweak +it for ones personal desires: + + comp=n n=0..9 [0] + Specifies the level of compression to be used. + By default, compression is switched off, so + only decompression (downstream) is available. + Increasing numbers increase the ratio that + can be reached as well as the CPU ressources + eaten up for that. + + 0: No compression at all. + 1: Absolutely minimal compression. + ... + 7: Good compression. + 8: Heavy compression. + 9: Ultimate compression, but eats more + cycles than most CPUs can dream of. + + If you want to try upstream compression, + start with 7. Try 9 only for fun, it is + rather useless (compresses only 1 or 2% + better than 8, but a P100 facing that + amount of brute force becomes sluggish, + jumping mouse etc) - just not worth the + burned CPU (even if you have a GHz oven). + + debug=n n=0..3 + Increasing levels of debug messages from + the LZS code. 3 includes full packet dumps + before and after compression and decompression. + More than 1 is overkill if you don't hunt + bugs. Even 1 has some rather informational + output. BTW, that doesn't control the + messages from the CCP framework. + + tweak=n n=tweak-flag-mask + Tweaks some aspects of the module to behave + strange or even violate RFCs in order to talk + to RFC-impaired peers. For Ascend, use 0x7. + If you want to know why, UTSL. + For historic reasons, the tweak parameter, if + left unspecified, defaults to 7 (the Ascend + tweaks). If your peer is something else, you + should therefore try tweak=0 to not upset the + peer with erratic behavior that is not en- + tirely wrong, but then again, is only right + if you are an Ascend box. + + + PATENT ISSUES (argh) + +Please check whether using this code constitutes a crime in your country. +While I just implemented a module that can source and sink the data format +described by Stac/HiFn in RFC1974, they claim to have US Patents on +practically every aspect of this format. Thus I assume that selling this +code violates these US Patents. As this code is + +a) Freeware under GPL and +b) Developed in Germany + +I hope I wont get any terror from the lawyers of the patent holders. I'm +sure that the availability of this code will actually help HiFn sales +because more users (the Linux community) will ask their ISPs for LZS +support on their NASes and these NASes will contain HiFn chips for that +purpose. Then again, after they claimed even deflate would be covered by +their patents and tried to sue Netscape for using deflate in the PNG code +of Mozilla, they are probably not on the planet for good will games. +We'll see. + +FMI, surf http://www.delphion.com/ and search for "stac". + + + BUGS + +Over the years, there have been some bugs tracked down and eliminated that +tended to crash the whole thing (the kernel, that is, so beware). Another +problem was introduced to the CCP framework in 2.4.0 and removed in 2.4.1. +There are problem reports for 2.2.19 currently under investigation. I'm still +running an older 2.2 version and don't have much reason to upgrade. + + + FMI + +The source code isdn_lzscomp.c is heavily commented (so I can still read it +in a month). It contains a thourough description of the compression algo- +rithm used and how I came to it. This in turn involves Credits to the +net.persons who helped me with getting that compressor faster. When we +are at it: + + + Credits + +Phil Katz and L. Peter Deutsch + for the zlib and deflate format and specifications + +Jean-loup Gailly and Mark Adler + for deflate, gzip and zlib as well as the comp.compression FAQ + +David Carr + for a lot of general tips, hints and tricks speeding up a + compressor, pointers to freeze and the sigma buffer and especially + explaining to me the concept of the barrier hash until I finally + got it + +Leonid Broukis + for the freeze implementation with the sigma buffer which + inspired my 8-buffer and for pointing out how memcmp(3) can + be used in match searching + +Ojala Pasi 'Albert' + for general tips and his website which was the first reading + I had about speeding up a compressor beyond brute force + +Frank Elsner + for creating a full-blown kernel patch out of the module source + and adapting it to all the changes that I had no time to follow, + as well as supplying it on his website and giving support on this + topic in Usenet and mailing lists. + http://www.tu-berlin.de/~elsner/ISDN/ + + + Contact + +You can reach me by EMail: beck@ibh.de +URL: http://www.ibh.de/~beck/stuff/lzs4i4l/ + +Discussion will take place at de.comp.os.unix.linux.isdn, which recently +took over the remains of de.alt.comm.isdn4linux, the former Usenet mirror +of the I4L mailing list. + + diff -urN linux-2.6.32-rc6/drivers/isdn/i4l/isdn_lzscomp.c linux-2.6.32-rc6.fli4l/drivers/isdn/i4l/isdn_lzscomp.c --- linux-2.6.32-rc6/drivers/isdn/i4l/isdn_lzscomp.c 1970-01-01 01:00:00.000000000 +0100 +++ linux-2.6.32-rc6.fli4l/drivers/isdn/i4l/isdn_lzscomp.c 2009-11-11 13:20:02.726061105 +0100 @@ -0,0 +1,2090 @@ +/* -*- mode: c; c-basic-offset: 1 -*- + * + * $Id: isdn_lzscomp.c,v 2.3 2004/10/23 12:58:21 beck Exp $ + * + * PPP link compression code for Stac LZS (RFC1974) support + * + * (Copyleft) 1998 by Andre Beck under terms of the GPL + * + * Originally just a RFC1974 decompressor, this module now contains + * a useable compressor as well, but that one is disabled by default. + * + * This code is still in BETA state but appears to be quite stable. + * + * + * Compile with: + * gcc -O2 -I/usr/src/linux/include -D__KERNEL__ -DMODULE -c isdn_lzscomp.c + * + * or see the README for other options of building. + * + */ + +#ifndef MODULE +#error This file must be compiled as a module. +#endif + +static const char +rcsid[] = "$Id: isdn_lzscomp.c,v 2.3 2004/10/23 12:58:21 beck Exp $"; + +/* Wow. No wonder this needs so long to compile. This include list + * is a shameless rip from other compressor code. Hopefully no (C) + * violation ;) + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,4,15) +#include +#else +#include +#endif + +#include +#include +#include +#include /* to get the struct task_struct */ +#include /* used in new tty drivers */ +#include /* used in new tty drivers */ + +#include +#include +#include +#include +#include + +#include + + +#include +#include +#include +#include +#include + +#include + +#include +#include +/* #include */ +#include +#include +#include +#include + +#include +#include + +/* +#define TEST_BROKEN_SEQNO 10 +#define TEST_BROKEN_CCNT 10 +#define TEST_COMP_BROKEN_CCNT 10 +#define TEST_COMP_BROKEN_SEQNO 10 +*/ + +/* Test a simple multihistory extension to the compressor */ + +#define LZS_COMP_MULTIHIST 1 + +/* + * Values for debug: + * 0 - no additional debug info + * 1 - normal debug info + * 2 - redundant debug info + * 3 - heavy debug (packet dumps etc) + */ + +static int debug = 0; + +/* + * Values for comp: + * 0 - do no compression at all, send uncompressed + * 1 - do absolute minimal compression (somewhat fast) + * ... + * 8 - do optimal compression (heavy but still useable) + * 9 - do ultimate compression (chews up nonsense amounts of CPU time) + */ + +static int comp = 0; + +/* + * Tweak flags. If none of these is set, the code will try to accept any + * input that follows the RFCs and produce output as close to the RFC as + * possible. Setting some of the flags may lead to behavior that is indeed + * a RFC violation. Bad enough, some are necessary to talk to certain + * peers. + * + * The first tweak bits are for talking to an Ascend Max 4000 E1 running + * TAOS 5.0Ap13 (my only test peer). I've noticed _severe_ amounts of + * transmission errors talking to 6.0.x, making compression nearly use- + * less due to permanent resets. May be a prob with our M4k, though. + * + */ + +/* The history number in mode 3 (seqno) reset requests MUST be specified + explicitely, even if 1 */ +#define LZS_TW_M3_RSRQ_EXP_HIST 0x00000001 + +/* The history number in mode 3 (seqno) reset acks MUST NOT be reflected + from the request, but left out */ +#define LZS_TW_M3_RSAK_NO_HIST 0x00000002 + +/* The history number in mode 4 (ext) reset requests MUST be specified + explicitely, despite the fact that it cannot be anything but 1 */ +#define LZS_TW_M4_RSRQ_NO_HIST 0x00000004 + +/* My Ascend will not recongnize mode 3 reset requests if no history is + tacked on. On the other hand, it does send the reset acks that stem + from those requests _without_ the history tacked on. It will also + not recognize any reset ack _with_ a history tacked on in that mode. + To fat it up, in mode 4 it recognizes reset requests only if _no_ + hist is tacked on. I have a Ticket number open for that with Ascend + EMEA support, but dunno whether they want to fix this... */ + +#define LZS_TWEAK_ASCEND 0x00000007 + +static int tweak = LZS_TWEAK_ASCEND; + +MODULE_AUTHOR("Andre Beck "); +MODULE_DESCRIPTION("LZS Compression for I4L sync PPP"); + +#ifdef MODULE_LICENSE +MODULE_LICENSE("GPL"); +#endif + +module_param(debug, int, 0400); +module_param(comp, int, 0400); +module_param(tweak, int, 0400); + +/* + * I _have_ read Documentation/CodingStyle. I ignored the section about + * 8 space indentations. Errare humanum est. + * + * Ok, get to the iron. We start with some structures to define a LZS + * compressors state. Oops, read _de_compressor. + */ + +/* LZS histories are 2048 Byte chunks of memory. We start with non-smart + (read slow) indexing - running pointers follow when the code is working + and someone actually notices its slow */ + +#define LZS_HISTORY_SIZE 2048 +#define LZS_HISTORY_MASK 0x7ff /* 2047 */ +#define LZS_HASHTAB_SIZE 256 + +/* The number of bytes to compare before we consider to call memcmp(3) + Empirical results: + Linux/x86 on P54C: Appears that GNU C can optimize memcmp calls into + direct inline assembly instructions so nearly no call overhead is + there at all. The temporary manager doesn't seem to discover that + the results of later additions actually drop out of the repz cmpsb, + but hell - getting the rep is good enough. Thus we call memcmp on + this architecture unconditionally to save time. + + Sun Ultra/gcc: memcmp has advantages, but only on long strings (this + is RISC after all). 32 seems to be a good value. On a pure break even + measurement, memcmp starts winning at around 12, so one should settle + for 16. I have no results for an actual SPARC Linux, though. + + PPC/gcc: Pure break even measurement showed memcmp starts winning at + lengths of 16. + + Alpha and HPPA with gcc appear to let memcmp win unconditionally. That + seems strange for a RISC platform, but has been cross-tested for Alpha + and should be tested again for PA-RISC. + + Other: Not yet known. + + Semantics: Set to 0 to unconditionally use memcmp(3). Set to >0 to + use memcmp(3) only when the string is at least that long. + Undefine to completely wipe out memcmp(3) utilization. + For unknown architectures, no use of memcmp(3) is the + default. +*/ + +#if defined(__GNUC__) +# if defined(__i386__) +/* With GCC on i386, memcmp is intrinsic and will always be used */ +# define LZS_MEMCMP_THRESHOLD 0 +# endif /* defined(__i386__) */ +# if defined(__alpha__) +/* Despite it beeing counter intuitive for a RISC system, Alpha memcmp(3) + wins even for shortest strings - I haven't seen the code yet, but + seeing the timing from break even measurement, this is a special + string compare instruction for sure (PALcode?) */ +# define LZS_MEMCMP_THRESHOLD 0 +# endif /* defined(__alpha__) */ +# if defined(__ppc__) +/* With GCC on ppc, memcmp wins over C at lengths of 16 */ +# define LZS_MEMCMP_THRESHOLD 16 +# endif /* defined(__ppc__) */ +#endif /* defined(__GNUC__) */ + +/* Some words about the implementation of the compressor: + I started with brute force. This was - as expected - too slow for + any realworld use, the only chance to get it somewhat useful was + to limit search in a restrictive way. I then posted a question on + how to implement LZS efficient in ISO C to comp.compression. As + there were no immediate answers, I digged a bit on my own and found + out that gzip/zlib (aka "deflate") uses a hash table in its LZ77 + stage. They generate hash chains from every 3-byte-match in the + input and later traverse these hash chains. Inspired by this idea + (which even is claimed to be patent free) I adapted the 3-byte-match + to a 2-byte-match scheme. The hash chains do not grow without bounds + as in deflate, instead I realized that I could store hash entries + in a fixed mapping to the history. This even had the net effect of + controlling LRU disposal of hash entries that are not relevant any + longer because this hash entry map was a ring buffer the same way the + history is one. This implementation performed much better than the + brute force one, but I still was not pleased. The code that deals + with the LRU aspects of the hash entries was complicated, hard to + read and did cost some performance. + I then got EMail response to my posting in comp.compression from + David Carr - he gave me a bunch of valueable hints during our + EMail conversation. The current implementation is still based on + the hash idea like outlined above, but modified with these hints + and tricks in mind: + + The barrier hash + Instead of storing the actual value-range of indexes in the hash + table and hash entries and actively clean them up in the LRU case + as well as dealing with a NIL type index which must be faked some- + how because 0 is a valid index, use much larger values. In the + simplest case (as used here), just take unsigned longs. Of course + these values must be masked whenever used as an index into a table, + but this is necessary in surprisingly seldom cases. Most arithme- + tics can be done as easy as before. The greatest effect of the + barrier hash, however, is auto-scoping: Indexes that are no longer + valid can easily be determined because they leave the LZ77 window. + Thus both the hash table and hash entry list are invalidating them- + selves automatically and don't need active cleaning except when the + index wraps (which is quite seldom with unsigned long). Another big + advantage of the barrier hash is that it makes compressor state re- + sets very cheap. Just close the LZ77 window and move it far enough + forward and the hash table and entries are invalidated automatically. + My former implementation needed to clean out some 10K of memory in- + stead. As resets may occur on every small packet, this is a hell of + a lot more efficient. Due to the simplifications of the barrier + hash, the hash entries are no longer complicated compounds but just + a ring buffer of unsigned long "next" indexes. + + The sigma buffer + As inspired from the good old "freeze" compressor, the sigma buffer + is the idea to mirror the history. If you have a mirror history + directly behind the real one, you can run string compares and other + inner loop operations without taking care about the ring buffer + wrapping: the operations just run into the mirror history. While + freeze uses a relatively short max string length and thus did not + mirror the complete history but just a tail (thus the name sigma + buffer, a ring with a tail), LZS can code 2047 byte strings and + I mirror the complete history (we could call it an 8-Buffer). + + Memcmp + Another great hint from freeze was how to use memcmp(3) for the + string matching. If you know that you need a string match longer + than n and don't care about the length of the actual match if it + is less than n+1, you can first memcmp(histidx, inputidx, n+1) and + only if it succeeds count the remaining matching bytes. If the + memcmp doesn't succeed, the match was shorter than n+1 and thus + of no further interest. + +*/ + +/* Chris Toreks hash function as also utilized in DB. Surprisingly + simple but still performs great. An option for further optimization + of the compressor would be to find a hash that hashes better, is + faster and is applied incremental. */ + + +#define HASHME(val, hash) ((hash<<5) + hash + val) + +typedef struct _lzs_hist { + u8 *hist; /* LZS_HISTORY_SIZE*2 allocation (8-Buffer) */ + u32 *hashtab; /* LZS_HASHTAB_SIZE allocation */ + u32 *next; /* LZS_HISTORY_SIZE allocation hash entries */ + u32 head; /* current head index into history */ + int hlen; /* bytes valid in history so far (comp) */ + u8 seqno; /* next expected seqno of this history */ + u8 expra; /* a reset request is outstanding */ + u8 rsid; /* reset id used for this history */ +} LZSHist; + +typedef struct _lzs_hists { + LZSHist *hists; + u16 nhists; +} LZSHists; + +#define LZS_HMODE_TRASH 0 /* Trash hist after every frame */ +#define LZS_HMODE_ONE 1 /* Exactly one hist */ +#define LZS_HMODE_MANY 2 /* Multiple hists */ + +typedef struct _lzs_state { + LZSHists *h; /* The allocated histories if any */ + u32 word; /* bit blender */ + int left; /* bits left in blender */ + u8 *inbuf; /* where we read from (skbuff stuff is too */ + int inlen; /* heavy to be used in every inline) */ + u8 hmode; /* history mode (placed here for align) */ + u8 cmode; /* check mode (dito) */ + u8 zstuff; /* state of zero stuffing */ + u8 unit; /* CCP unit */ + u16 ccnt; /* Coherency Counter - Ext has only 1 hist */ + u8 rsid; /* Next reset id to be used */ + u8 comp:1; /* Flag: is this a compressor ? */ + u8 lastinc:1; /* Flag: was the last frame incompressible ? */ + u8 ackrs:1; /* Flag: ack a reset request inband (EXT) */ + int lastvjci; /* last VJ conn idx for trivial multihist */ + struct compstat stats; /* statistics sink */ + /* more to come */ +} LZSState; + +static short compparms[10][2] = { + /* depth, maxlen */ + {0, 0}, /* 0 Actually a special case, these parms are not used */ + {0, 32}, /* 1 Consider only first match and max string len 32 */ + {1, 32}, /* 2 Consider two matches */ + {2, 64}, /* 3 You got the picture */ + {8, 256}, /* 4 */ + {16, 1024}, /* 5 */ + {32, 2047}, /* 6 */ + {64, 2047}, /* 7 */ + {128, 2047}, /* 8 */ + {2047, 2047} /* 9 Full depth and max length - nonsense slow */ +}; + +/* Alloc a bunch of hists or if this fails cleanup and return NULL */ +static LZSHists *allocHists(u16 count, int comp) +{ + LZSHists *hs; + LZSHist *hv; + int i = 0; + int gotall; + + hs = (LZSHists *)kmalloc(sizeof(LZSHists), GFP_KERNEL); + if(!hs) + return NULL; + + /* Could be more than a page */ + hv = (LZSHist *)vmalloc(sizeof(LZSHist) * count); + if(!hv) { + kfree(hs); + return NULL; + } + memset(hv, 0, sizeof(LZSHist) * count); + while(i < count) { + /* 4096 is larger than a page on some systems */ + gotall = 1; + hv[i].hist = (u8 *)vmalloc(LZS_HISTORY_SIZE*2); + if(!hv[i].hist) + gotall = 0; + if(comp) { + /* Additional data structures for the compressor */ + if(hv[i].hist) { + hv[i].next = (u32 *)vmalloc(sizeof(u32) * LZS_HISTORY_SIZE); + if(hv[i].next) { + hv[i].hashtab = (u32 *)vmalloc(sizeof(u32) * LZS_HASHTAB_SIZE); + if(!hv[i].hashtab) + gotall = 0; + } else + gotall = 0; + } + } + if(!gotall) { + /* Oops - have to clean the whole mess up ... */ + while(i-- >= 0) { + if(hv[i].hist) + vfree(hv[i].hist); + if(hv[i].next) + vfree(hv[i].next); + if(hv[i].hashtab) + vfree(hv[i].hashtab); + } + vfree(hv); + kfree(hs); + return NULL; + } + /* The first expected seqno of a history is 1 */ + hv[i].seqno = 1; + if(comp) { + /* Initialize next buffer and hash table to 0 (hist doesn't need this) */ + memset(hv[i].next, 0, sizeof(u32) * LZS_HISTORY_SIZE); + memset(hv[i].hashtab, 0, sizeof(u32) * LZS_HASHTAB_SIZE); + /* Initialize history head to 2048 (so 0 is out of scope) */ + hv[i].head = LZS_HISTORY_SIZE; + } + i++; + } + /* We got through getting all the mem - link it and return */ + hs->hists = hv; + hs->nhists = count; + return hs; +} + +static void freeHists(LZSHists *hs) +{ + int i; + for(i=0; inhists; i++) { + if(hs->hists[i].hashtab) + vfree(hs->hists[i].hashtab); + if(hs->hists[i].next) + vfree(hs->hists[i].next); + vfree(hs->hists[i].hist); + } + vfree(hs->hists); + kfree(hs); +} + +static void lzsStats(void *state, struct compstat *stats) +{ + LZSState *s = (LZSState *)state; + + memcpy(stats, &s->stats, sizeof(struct compstat)); +} + +static void resetCompHist(LZSState *s, LZSHist *h) +{ + /* Reset the compressor state */ + u32 oldhead = h->head; + + h->hlen = 0; + + /* Use the barrier hash to invalidate the whole history in a hatch. It just + moves all current indices beyond the barrier. Without this nice trick + we would have to zero out approx. 10K memory - thanks David ;) */ + h->head += LZS_HISTORY_SIZE; + + if(h->head < oldhead) { + /* We wrapped around index space. Make sure all hash table entries are + invalidated - it is unlikely that they survived a full iteration + about 2^32, but not impossible. For instance, it requires just + two million uncompressible packets which cause a call to this + function to wrap the space - certainly possible. */ + h->head = LZS_HISTORY_SIZE; + memset(h->hashtab, 0, sizeof(u32) * LZS_HASHTAB_SIZE); + } +} + +static void lzsReset(void *state, u8 code, u8 id, u8 *data, unsigned len, + struct isdn_ppp_resetparams *rsparm) +{ + LZSState *s = (LZSState *)state; + u16 hi; + + if(debug) + printk(KERN_DEBUG "lzsReset: code %02x id %02x with %d data bytes\n", + code, id, len); + + if(s->comp) { + switch(s->cmode) { + case LZS_CMODE_EXT: + case LZS_CMODE_SEQNO: + if(len > 1) { + hi = (data[0] << 8) | data[1]; + } else { + hi = 1; + } + switch(s->hmode) { + case LZS_HMODE_TRASH: + case LZS_HMODE_ONE: + if(hi != 1) { + printk(KERN_INFO "lzsReset: reset for hist %d in single hist mode\n", hi); + hi = 1; + } + break; + case LZS_HMODE_MANY: + if(hi > s->h->nhists) { + printk(KERN_INFO "lzsReset: reset for hist %d but only %d hists\n", hi, + s->h->nhists); + return; + } + } + /* Ok, we reset the correct history */ + resetCompHist(s, &s->h->hists[hi-1]); + switch(s->cmode) { + case LZS_CMODE_EXT: + /* Remember that we did reset and prepare for sending an inband ack */ + s->ackrs = 1; + /* Tell the framework to NOT send a Reset Ack */ + rsparm->valid = 1; + rsparm->rsend = 0; + break; + case LZS_CMODE_SEQNO: + /* Tell the framework to send a Reset Ack reflecting the Req */ + rsparm->valid = 1; + rsparm->rsend = 1; + rsparm->idval = 1; + rsparm->id = id; + if(!(tweak & LZS_TW_M3_RSAK_NO_HIST)) { + if(rsparm->maxdlen >= len) { + if(len == 2) { + rsparm->dlen = len; + rsparm->dtval = 1; + rsparm->data[0] = data[0]; + rsparm->data[1] = data[1]; + } else { + printk(KERN_WARNING "lzsReset: unknown Reset Req Data - ignored\n"); + } + } + } + break; + } + break; + case LZS_CMODE_CRC: + case LZS_CMODE_LCB: + case LZS_CMODE_NONE: + default: + printk(KERN_INFO "lzsReset: cmode %d NYI - reset ignored\n", s->cmode); + } + } else { + switch(s->cmode) { + case LZS_CMODE_EXT: + /* RFC 1962 states that an implementation which receives a Reset-Request + MUST answer with a Reset-Ack. Thus the whole extended checkmode is + a single RFC violation. But it's M$, so what... */ + s->rsid++; /* Just to be sure */ + printk(KERN_INFO "lzsReset: reset ack rcvd in ext cmode - ignored\n"); + break; + case LZS_CMODE_SEQNO: + if(len > 1) { + hi = (data[0] << 8) | data[1]; + } else { + hi = 1; + } + switch(s->hmode) { + case LZS_HMODE_TRASH: + case LZS_HMODE_ONE: + if(hi != 1) { + printk(KERN_INFO "lzsReset: reset for hist %d in single hist mode\n", hi); + hi = 1; + } + break; + case LZS_HMODE_MANY: + if(hi > s->h->nhists) { + printk(KERN_INFO "lzsReset: reset for hist %d but only %d hists\n", hi, + s->h->nhists); + return; + } + } + /* Ok, we reset the correct history */ + if(s->h->hists[hi-1].rsid != id) + printk(KERN_INFO "lzsReset: reset id %d expected %d\n", id, + s->h->hists[hi-1].rsid); + s->h->hists[hi-1].expra = 0; + /* The next id must be incremented because we received an ack */ + s->rsid++; + break; + case LZS_CMODE_CRC: + case LZS_CMODE_LCB: + case LZS_CMODE_NONE: + default: + printk(KERN_INFO "lzsReset: cmode %d NYI - reset ignored\n", s->cmode); + } + } +} + +/* Alloc space for a decompressor - number of histories is known here */ + +static void *lzsAlloc(struct isdn_ppp_comp_data *data) +{ + u8 cmode; + u16 nhists; + LZSState *s; + + if(data->num != CI_LZS_COMPRESS || data->optlen != 3) + return NULL; + + /* We need 2 configuration options: The number of histories and the + check mode to use. The number is a short - we pass it in big endian */ + + nhists = (data->options[0] << 8) | data->options[1]; + cmode = data->options[2]; + + if(debug) + printk(KERN_DEBUG "lzsAlloc: hists %d cmode %d\n", nhists, cmode); + + if(cmode > LZS_CMODE_EXT || cmode == LZS_CMODE_CRC || cmode == LZS_CMODE_LCB) { + printk(KERN_WARNING "lzsAlloc: cmode %d not supported (yet)\n", cmode); + return NULL; + } + + /* Allocate the state */ + + s = (LZSState *)kmalloc(sizeof(LZSState), GFP_KERNEL); + if(!s) + return NULL; + + memset(s, 0, sizeof(LZSState)); + + if(data->flags & IPPP_COMP_FLAG_XMIT) { + /* A compressor is needed. */ + if(debug) + printk(KERN_DEBUG "lzsAlloc: Allocating compressor\n"); + s->comp = 1; + if(comp) { + s->hmode = LZS_HMODE_ONE; + switch(nhists) { + case 0: + s->hmode = LZS_HMODE_TRASH; + /* We still need a hist here _during_ the packet compression */ + /* This branch intentionally falls through */ + case 1: + s->h = allocHists(1, s->comp); + if(!s->h) { + printk(KERN_WARNING "lzsAlloc: comp history - out of mem\n"); + kfree(s); + return NULL; + } + break; + default: + s->hmode = LZS_HMODE_MANY; + s->h = allocHists(nhists, s->comp); + if(!s->h) { + printk(KERN_WARNING "lzsAlloc: comp %d histories - out of mem\n", nhists); + kfree(s); + return NULL; + } + break; + } + if(debug) + printk(KERN_DEBUG "lzsAlloc: Compressor successfully allocated\n"); + } else { + printk(KERN_DEBUG "lzsAlloc: No allocations (compressor disabled)\n"); + } + } else { + if(debug) + printk(KERN_DEBUG "lzsAlloc: Allocating decompressor\n"); + s->comp = 0; + s->hmode = LZS_HMODE_ONE; + switch(nhists) { + case 0: + s->hmode = LZS_HMODE_TRASH; + /* We still need a hist here _during_ the packet decompression */ + /* This branch intentionally falls through */ + case 1: + s->h = allocHists(1, s->comp); + if(!s->h) { + printk(KERN_WARNING "lzsAlloc: decomp history - out of mem\n"); + kfree(s); + return NULL; + } + break; + default: + s->hmode = LZS_HMODE_MANY; + s->h = allocHists(nhists, s->comp); + if(!s->h) { + printk(KERN_WARNING "lzsAlloc: decomp %d histories - out of mem\n", nhists); + kfree(s); + return NULL; + } + break; + } + if(debug) + printk(KERN_DEBUG "lzsAlloc: Decompressor successfully allocated\n"); + } + + s->cmode = cmode; + s->lastvjci = 256; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0) + MOD_INC_USE_COUNT; +#endif + + return s; +} + +static void lzsFree(void *state) +{ + LZSState *s = (LZSState *)state; + + if(s) { + if(s->h) { + if(debug) + printk(KERN_DEBUG "lzsFree: freeing %d histories\n", s->h->nhists); + freeHists(s->h); + } + if(debug) + printk(KERN_DEBUG "lzsFree: freeing state\n"); + kfree(s); + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0) + MOD_DEC_USE_COUNT; +#endif + + } +} + +static int lzsInit(void *state, struct isdn_ppp_comp_data *data, int unit, + int debug) +{ + u16 nhists; + u8 cmode; + LZSState *s = (LZSState *)state; + + /* Mostly NYI (what the heck is it good for actually ?) */ + + nhists = (data->options[0] << 8) | data->options[1]; + cmode = data->options[2]; + + if(debug) + printk(KERN_DEBUG "lzsInit: hists %d cmode %d\n", nhists, cmode); + + s->unit = unit; + + return 1; +} + +/* Compression stuff starts here, helper functions first */ + +#ifdef LZS_COMP_MULTIHIST + +/* From proto and the skb, decide which of the histories supported by + state s is to be used for this packet. */ +static u16 selectCompHist(LZSState *s, struct sk_buff *skb, int proto) +{ + /* We use history 1 for a default */ + u16 hi = 1; + int thists; + + /* Are we multihistory at all? */ + if(s->h->nhists == 1) + return 1; + + /* First try: We simply reuse the work already done by VJ. Anything that + is not a VJ compressed or VJ uncompressed frame is just dumped on + history 1. The VJ frames are stuffed into histories according to + their VJ connection index, modulo the number of histories. */ + + /* Get the number of histories we use for TCP connections */ + thists = s->h->nhists - 1; + + /* Now deal with proto */ + switch(proto) { + case PPP_VJC_UNCOMP: + /* Within a VJ uncompressed frame, the connection index is to be found + in the protocol field of the otherwise unchanged IP header. We just + pull it there. */ + if(skb->len < 10) + return 1; + hi = skb->data[9]; + if(debug > 1) + printk(KERN_DEBUG "lzsCompMultiHist: VJ uncomp frame ci %d ", hi); + break; + case PPP_VJC_COMP: + /* In a VJ compressed frame, the connection index is either compressed, + meaning it is the same as with the last frame, or it is explicitely + sent. */ + if(skb->len < 3) + return 1; + /* Is there a connection index present? */ + if(skb->data[0] & 0x40) { + hi = skb->data[1]; + s->lastvjci = hi; + printk(KERN_DEBUG "lzsCompMultiHist: VJ comp frame new ci %d ", hi); + } else { + /* No, check whether we know an earlier one */ + if(s->lastvjci == 256) + return 1; + /* We do, so just use it */ + hi = s->lastvjci; + printk(KERN_DEBUG "lzsCompMultiHist: VJ comp frame old ci %d ", hi); + } + break; + default: + /* All other protos just drop into history 1 */ + return 1; + } + + /* Shift the history number into the range we can support */ + hi %= thists; + hi += 2; + if(debug > 1) + printk(KERN_DEBUG "--> hist %d\n", hi); + + return hi; +} + +#endif /* LZS_COMP_MULTIHIST */ + +/* An index is valid if it is in the current range of the history */ +static int __inline validIndex(LZSHist *h, u32 ix) +{ + return ((ix < h->head) && (ix >= (h->head - h->hlen))); +} + +/* Add a new 2-byte-sequence to our hash infrastructure */ +static void __inline addhash(LZSHist *h) +{ + /* We will go to next[h->head - 1] because we have a fixed mapping */ + register int index = (h->head - 1) & LZS_HISTORY_MASK; + register u8 hash; + register u8 *p; + + hash = 0; + p = &h->hist[index]; + /* We can run through thanks to the 8-buffer (history mirror) */ + hash = HASHME(*p++, hash); + hash = HASHME(*p, hash); + + /* Loop ourselfs into the current hash chain for hash */ + h->next[index] = h->hashtab[hash]; + h->hashtab[hash] = h->head - 1; +} + +/* Zap one byte over to the history */ +static void __inline putHistByte(LZSHist *h, u8 byte) +{ + int hix = h->head & LZS_HISTORY_MASK; + /* Feed the real history */ + h->hist[hix] = byte; + /* Feed the mirror history (8-buffer) */ + h->hist[hix + LZS_HISTORY_SIZE] = byte; + /* Increase valid range. Actually, the valid range is 2048. If the hist + is full, head points to the location where the next byte will be written + but this location still contains the oldest byte in the history. We + just ignore this fact, for a simple reason: we cannot encode an offset + of 2048. When we cannot encode it, it makes no sense to look if a match + is there at offset 2048, and the oldest one we care about is 2047. */ + if(h->hlen < (LZS_HISTORY_SIZE - 1)) + h->hlen++; + if(h->hlen > 1) { + /* We have at least 2 bytes starting at head-1 - add a hash table + entry for them */ + addhash(h); + } + + /* Move scope forward */ + h->head++; + + /* We just incremented and next check whether the value became zero. + This should optimize to a single branch-equal on any decent pro- + cessor. But x86 can always surprise you ... */ + + /* Did we wrap around our index space ? */ + if(!(h->head)) { + /* We wrapped. It is very unlikely but still possible for us to find + entries in the hash table that are left over from a former trip + through index space. Make sure they are wiped out. */ + h->head = 2048; + h->hlen = 0; + memset(h->hashtab, 0, sizeof(u32) * LZS_HASHTAB_SIZE); + } +} + +/* Shift multiple bytes from inbuf to the history */ +static void __inline putHistBytes(LZSHist *h, u8 *buf, int len) +{ + while(len--) + putHistByte(h, *buf++); +} + +/* Put some bits onto the output stream */ +static void __inline putBits(LZSState *s, struct sk_buff *skbout, u32 bits, + int len) +{ + u8 byte; + + s->word <<= len; + s->word |= bits; + s->left += len; + while(s->left >= 8) { + s->left -= 8; + byte = s->word >> (s->left); + if(skb_tailroom(skbout) > 0) + *(skb_put(skbout, 1)) = byte; + else + printk(KERN_WARNING "lzsComp: output skb full - truncated\n"); + } +} + +static void __inline putLiteralByte(LZSState *s, struct sk_buff *skbout, + u8 byte) +{ + putBits(s, skbout, 0, 1); + putBits(s, skbout, byte, 8); +} + +static void __inline putCompressedString(LZSState *s, LZSHist *h, + struct sk_buff *skbout, + u32 index, int len) +{ + u32 offs = (h->head - index) & LZS_HISTORY_MASK; + + if(offs < 128) { + /* Deploy a seven bit offset */ + putBits(s, skbout, 3, 2); + putBits(s, skbout, offs, 7); + } else { + /* Deploy an eleven bit offset */ + putBits(s, skbout, 2, 2); + putBits(s, skbout, offs, 11); /* Blender is now 32bit */ + } + + switch(len) { + case 2: + putBits(s, skbout, 0, 2); + break; + case 3: + putBits(s, skbout, 1, 2); + break; + case 4: + putBits(s, skbout, 2, 2); + break; + case 5: + putBits(s, skbout, 12, 4); + break; + case 6: + putBits(s, skbout, 13, 4); + break; + case 7: + putBits(s, skbout, 14, 4); + break; + default: + len -= 8; + putBits(s, skbout, 15, 4); + while(len >= 15) { + putBits(s, skbout, 15, 4); + len -= 15; + } + putBits(s, skbout, len, 4); + } +} + +/* Return actual length of a match (new optimized version) */ +static int __inline getMatchLength(LZSState *s, LZSHist *h, u32 ix, int minlen) +{ + register int len = 0; + register int tlen; + register u8 *inbuf = s->inbuf; + register u8 *hbuf; + + /* We return the length of a match between the input buffer and the + history at index ix provided that the length of this match is at + least minlen. If it isn't, the caller doesn't care about the actual + value and we can return 0. This allows us to call memcmp(3) for + a more performant test. */ + + /* Constrain our test to the number of relevant bytes */ + tlen = h->head - ix; + tlen = compparms[comp][1] < tlen ? compparms[comp][1] : tlen; + tlen = s->inlen < tlen ? s->inlen : tlen; + + if(tlen < minlen) + /* Won't give a longer match anyway - ignore */ + return 0; + + /* Get start position in our history */ + hbuf = &h->hist[ix & LZS_HISTORY_MASK]; + +#ifdef LZS_MEMCMP_THRESHOLD + /* We use memcmp(3) at all */ +#if LZS_MEMCMP_THRESHOLD + /* We use it only when the minlen exceeds a threshold */ + + if(minlen > LZS_MEMCMP_THRESHOLD) { + +#endif /* LZS_MEMCMP_THRESHOLD */ + + /* If the two strings are not equal up to minlen, there will be no longer + match than minlen and we can return 0 as well. */ + if(memcmp(hbuf, inbuf, minlen)) + return 0; + + /* Ok, they are equal so far - the rest is done as usual */ + hbuf += minlen; + inbuf += minlen; + tlen -= minlen; + len += minlen; + /* Seen the overhead above ? Together with general call overhead, this is + why we are using a threshold for really calling memcmp(3). Some archi- + tectures use memcmp(3) implementations that perform great, but need + at least a long- or even octaword to actually show up with a gain. */ + +#if LZS_MEMCMP_THRESHOLD + + } + +#endif /* LZS_MEMCMP_THRESHOLD */ +#endif /* defined(LZS_MEMCMP_THRESHOLD) */ + + while(tlen-- && (*hbuf++ == *inbuf++)) + len++; + + return len; +} + + +static int lzsCompress(void *state, struct sk_buff *skbin, + struct sk_buff *skbout, int proto) +{ + /* The heart of the compression is here */ + + register LZSState *s = (LZSState *)state; + register LZSHist *h; + + u16 hi = 1; + u8 *p; + register int llen, nlen, retry; + register u32 hidx, lidx, next; + register u8 hash; + int prepd, ohlen, totlen, ext, ilen, cols, uncomp; + u8 *ibuf; + + /* Prefill statistics for the case of sending uncompressed - this will then + be used if any of the following return 0 statements hit */ + + s->stats.in_count += skbin->len + 2; + s->stats.bytes_out += skbin->len + 2; + s->stats.inc_bytes += skbin->len + 2; + s->stats.inc_packets++; + + if(!comp) { + if(debug > 1) + printk(KERN_DEBUG "lzsComp: leaving as is\n"); + return 0; + } + + if(proto < 0x21 || proto > 0xf9 || !(proto & 0x1)) { + printk(KERN_DEBUG "lzsComp: called with %04x\n", proto); + return 0; + } + + /* Step 1 - verify the output skb can receive the data we need to pre- + pend to the compressed stuff. */ + ohlen = 0; + switch(s->hmode) { + case LZS_HMODE_MANY: + if(s->h->nhists > 255) + ohlen += 2; + else + ohlen++; + break; + default: + /* Nothing to do - history is not sent explicitely */ + break; /* <-- for GCCv3's personal satisfaction */ + } + + switch(s->cmode) { + case LZS_CMODE_NONE: + /* Nothing */ + break; + case LZS_CMODE_SEQNO: + /* One byte */ + ohlen++; + break; + case LZS_CMODE_EXT: + /* Two bytes */ + ohlen += 2; + break; + case LZS_CMODE_LCB: + case LZS_CMODE_CRC: + default: + /* Still to be implemented */ + printk(KERN_WARNING "lzsComp: cmode %d NYI (sending as is)\n", s->cmode); + return 0; + } + + /* It appears newer versions of the I4L system are upset when the headroom + of skbs they pass to us is reduced. Therefore, we reserve the headroom + we will need so we don't touch theirs */ + + if(ohlen) { + if(skb_tailroom(skbout) < ohlen) { + printk(KERN_WARNING "lzsComp: out skb headroom cannot be reserved\n"); + return 0; + } + skb_reserve(skbout, ohlen); + } + + /* Step 2 - verify the output skb is large enough to eat our output. We + just statically verify that the tailroom can stand the maximum ex- + pansion of the input data. */ + if(skb_tailroom(skbout) < ((skbin->len + 4) + ((skbin->len + 2)>>3))) { + printk(KERN_WARNING "lzsComp: out skb tailroom too small\n"); + return 0; + } + + /* Step 3 - initialize some values for compression. First of all, find + the history we compress against. */ + +#ifdef LZS_COMP_MULTIHIST + if(s->hmode == LZS_HMODE_MANY) { + /* Check whether it would make sense to use another hist but the default + of 1 */ + hi = selectCompHist(s, skbin, proto); + } +#endif + + h = &s->h->hists[hi - 1]; + s->word = s->left = 0; + + /* Frames always start with the protocol. However, the input sk_buff does + not start with these two bytes. We instead get them communicated by + proto, and will first compress them. This has the drawback that we + cannot easily feed them to our compression engine, while they are + a very probable start of a sequence. We try whether we can temporarily + prepend them to the input skb, or if not just output them literal to + have something to start with. */ + + /* Step 4 - try to prepend the proto to skbin if possible */ + if(skb_headroom(skbin) > 1) { + p = skb_push(skbin, 2); + *p++ = proto >> 8; + *p = proto & 0xff; + prepd = 1; + } else { + putLiteralByte(s, skbout, proto >> 8); + putHistByte(h, proto >> 8); + putLiteralByte(s, skbout, proto & 0xff); + putHistByte(h, proto & 0xff); + prepd = 0; + } + + /* Step 5 - the actual compression code. Don't beat me. */ + + /* Start by setting our input area */ + + s->inbuf = skbin->data; + s->inlen = skbin->len; + + while(s->inlen) { + /* More bytes to consume */ + if(h->hlen < 2 || (s->inlen == 1)) { + /* No history to search so far or just one more byte anyway */ + putLiteralByte(s, skbout, *s->inbuf); + putHistByte(h, *s->inbuf); + s->inlen--; + s->inbuf++; + continue; + } + /* Ok, we have at least 2 bytes in the history and at least two still to + be read. So we can start to search our hash infrastructure for this + particular 2-byte-sequence */ + hash = 0; + hash = HASHME(s->inbuf[0], hash); + hash = HASHME(s->inbuf[1], hash); + hidx = h->hashtab[hash]; + if(validIndex(h, hidx)) { + /* Seems to be something valid in the hash table. May, however, be a + hash clash. */ + /* Where to try next */ + next = h->next[hidx & LZS_HISTORY_MASK]; + /* Where this supposed match starts */ + lidx = hidx; + /* How long it is - if it is a clash, it is 0 or 1 bytes long */ + llen = 0; + nlen = getMatchLength(s, h, lidx, llen + 1); + if(nlen > llen) + llen = nlen; + /* Is this already the best possible (remaining) match ? */ + if(llen == s->inlen || llen >= compparms[comp][1]) { + /* Yeah. Just spit it out */ + putCompressedString(s, h, skbout, lidx, llen); + putHistBytes(h, s->inbuf, llen); + s->inlen -= llen; + s->inbuf += llen; + continue; + } + retry = compparms[comp][0]; + while(validIndex(h, next) && retry--) { + /* Traverse this hash chain and find the longest match on it */ + hidx = next; + /* Climb forward */ + next = h->next[hidx & LZS_HISTORY_MASK]; + /* Try to find a longer match */ + nlen = getMatchLength(s, h, hidx, llen + 1); + if(nlen > llen) { + lidx = hidx; + llen = nlen; + /* Found the best possible match ? */ + if(llen == s->inlen || llen >= compparms[comp][1]) { + break; + } + } + } + /* Well, if me made it till here the longest match we found is in + lidx/llen. Do the right thing with it. */ + if(llen > 1) { + /* Nice, got a sequence match */ + putCompressedString(s, h, skbout, lidx, llen); + putHistBytes(h, s->inbuf, llen); + s->inlen -= llen; + s->inbuf += llen; + } else { + /* Just zap a literal byte and start over */ + putLiteralByte(s, skbout, *s->inbuf); + putHistByte(h, *s->inbuf); + s->inlen--; + s->inbuf++; + } + } else { + /* Never seen this sequence, zap this byte and try with the next */ + putLiteralByte(s, skbout, *s->inbuf); + putHistByte(h, *s->inbuf); + s->inlen--; + s->inbuf++; + } + } + /* Write the end marker - it is a 7 bit offset of zero */ + putBits(s, skbout, 0x180, 9); + /* Flush last bits */ + putBits(s, skbout, 0, 8); + + /* Step 6 - revert prepended proto if it exists */ + if(prepd) + skb_pull(skbin, 2); + + /* Step 7 - decision time. We have compressed the data and know the final + size of the frame to be sent. We must decide whether we want to send + the frame compressed or not. This is not as simple as it seems, espe- + cially because a single frame that expands a bit may help later frames + to compress. We use this heuristics for now: + a) If the final frame exceeds the MTU, it cannot be sent compressed. + b) If the frame expands somewhat and it is the first frame that does + expand, send it compressed. Maybe we gain better compression on + later frames this way. + c) If the frame expands and the former one did so as well, we send + it uncompressed. + + Note that we do not use the inband sending method of uncompressed + data of the EXT mode. This is a rather Micro$oftish nonsense which + just shows the particular inability of reading a standard paper by + these guys. If you want to send uncompressed, just do it - it is that + simple. + + UPDATE: We must use the inband sending method if we have to commu- + nicate a compressor state reset to the decompressor. EXT mode has + no ResetAck but does that using a bit in a uncompressed frame, so + we need to do this as well whenever a ResetAck is outstanding. + */ + + totlen = ohlen + skbout->len; + + /* Assume that data is compressed for EXT first */ + uncomp = 0; + +#ifdef COMP_XMIT_ADVANCED_HEURISTICS + + /* HACK Attack Warning - this looks weird. Would really be nice to have + the real MTU here. But I don't have a way to find it out. Seems that + another API change would be necessary to allow for this. On the other + hand, the MTU is never set or used by the isdn_ppp stuff, the ioctl(2) + for that job doesn't do anything. Thus, to have something to try with + at all, assume MTU is 1500 fixed. HACK anyway. + + Stability over Performance IMHO - use trivial decision until code + has settled a bit, then implement MTU based stuff. */ + + if((totlen + 4) > 1500) { + if(debug > 1) + printk(KERN_DEBUG "lzsComp: compressed size exceeds MTU\n"); + resetCompHist(s, h); + s->lastinc = 1; + return 0; + } + + if(totlen > skbin->len) { + if(s->lastinc) { + if(debug > 1) + printk(KERN_DEBUG "lzsComp: repeated incompressible frames\n"); + resetCompHist(s, h); + return 0; + } + s->lastinc = 1; + } else { + s->lastinc = 0; + } + +#else + + /* Trivial does-or-does-not-expand decision - suboptimal but stable */ + + if(totlen > skbin->len) { + if(debug > 1) + printk(KERN_DEBUG "lzsComp: frame expands\n"); + resetCompHist(s, h); + if(s->cmode == LZS_CMODE_EXT) { + /* In EXT mode, we must send an EXT frame with uncompressed data + and the flush-bit set now */ + uncomp = 1; + /* Copy input data to output data unmodified (Hmm, looks crashy) */ + p = skbout->data; + *p++ = proto >> 8; + *p++ = proto & 0xff; + memcpy(p, skbin->data, skbin->len); + skbout->len = skbin->len + 2; + } else { + /* All other modes just send uncompressed */ + return 0; + } + } + +#endif + + /* Step 8 - fill the remaining parts of the frame by prepending them to + the skbout. Return the frame length so it can be sent. */ + + p = skb_push(skbout, ohlen); + + /* Emit the history number. It is only present if greater than one, and + it is transmitted as 1 or 2 bytes, depending on the number of histories + negotiated */ + + switch(s->hmode) { + case LZS_HMODE_TRASH: + case LZS_HMODE_ONE: + /* We do not need to send one. It is implicitly 1. */ + break; + case LZS_HMODE_MANY: + if(s->h->nhists > 255) { + /* A 16 bit history number */ + *p++ = LZS_HIST_BYTE1(hi); + *p++ = LZS_HIST_BYTE2(hi); + } else { + /* An 8 bit history number */ + *p++ = hi; + } + break; + } + + /* The next thing to send is the checkmode specific data */ + + switch(s->cmode) { + case LZS_CMODE_NONE: + /* Nothing to be sent for this */ + break; + case LZS_CMODE_SEQNO: + /* Default mode. Emit a one-byte per-history sequence number. */ + +#ifdef TEST_COMP_BROKEN_SEQNO + if(h->seqno == TEST_COMP_BROKEN_SEQNO) { + *p++ = h->seqno - 1; + h->seqno++; + } else + *p++ = h->seqno++; +#else + *p++ = h->seqno++; +#endif + + break; + case LZS_CMODE_EXT: + /* We need to emit a 16bit word with 2 relevant flagbits and a 12 bit + coherency counter. */ + ext = s->ccnt; + +#ifdef TEST_COMP_BROKEN_CCNT + if(ext == TEST_COMP_BROKEN_CCNT) + ext--; +#endif + + s->ccnt++; + s->ccnt &= 0x0fff; + /* Mark the frame as compressed by setting bit C - except it is not + compressed but a uncompressed inband flush */ + if(!uncomp) + ext |= 0x2000; + if(s->ackrs || uncomp) { + /* Mark the frame as an inband reset ack by setting bit A */ + ext |= 0x8000; + s->ackrs = 0; + } + *p++ = ext >> 8; + *p++ = ext & 0xff; + break; + case LZS_CMODE_LCB: + case LZS_CMODE_CRC: + default: + /* Still to be implemented */ + printk(KERN_WARNING "lzsComp: cmode %d NYI (sending as is)\n", s->cmode); + return 0; + } + + if(debug > 1) { + if(uncomp) { + printk(KERN_DEBUG "lzsComp: %d in %d out - EXT uncompressed\n", + skbin->len, skbout->len); + } else { + printk(KERN_DEBUG "lzsComp: %d in %d out - sending compressed\n", + skbin->len, skbout->len); + } + } + if(debug > 2) { + printk(KERN_DEBUG "lzsComp packet in:\n"); + + ilen = skbin->len; + ibuf = skbin->data; + cols = 0; + + while(ilen--) { + if(!(cols % 16)) { + printk(KERN_DEBUG "[%04x]", cols); + } + printk(" %02x", *ibuf++); + cols++; + if(!(cols % 16) || !ilen) { + printk("\n"); + } + } + printk(KERN_DEBUG "\n"); + + printk(KERN_DEBUG "lzsComp packet out:\n"); + + ilen = skbout->len; + ibuf = skbout->data; + cols = 0; + + while(ilen--) { + if(!(cols % 16)) { + printk(KERN_DEBUG "[%04x]", cols); + } + printk(" %02x", *ibuf++); + cols++; + if(!(cols % 16) || !ilen) { + printk("\n"); + } + } + printk(KERN_DEBUG "\n"); + } + + /* Adapt statistics - correct the former assumption of an incompressible + packet by tweaking the counters */ + + s->stats.bytes_out -= skbin->len + 2; + s->stats.bytes_out += skbout->len; + + if(!uncomp) { + /* Correct only if not sending a uncompressed EXT frame inband */ + s->stats.inc_bytes -= skbin->len + 2; + s->stats.inc_packets--; + + s->stats.comp_bytes += skbout->len; + s->stats.comp_packets++; + } + + return skbout->len; +} + +static void lzsIncomp(void *state, struct sk_buff *skbin, int proto) +{ + /* If I understand it correctly this one is called when the peer has + sent a frame without compression (an incompressable one). We will + update the history with the data in order to keep it in sync with + the sender. It does not make any difference whether the sender knows + about this, a property of LZS is that the decompressor is pretty + selfcontained. */ + + /* Actually, the RFC states that a compressor transmitting an uncom- + pressed frame MUST reset his history, thus per the RFC it would be + completely irrelevant whether we feed back incomps or not. But + worse I get VJ errors when feeding back (should not happen) and much + less errors when not feeding back - something is wrong here (may + be with may peer). Deactivated feedback so far. */ + + register LZSState *s = (LZSState *)state; + +#if 0 + + int ilen, cols; + u8 *ibuf; + register LZSHist *h; + + + ibuf = skbin->data; + ilen = skbin->len; + + /* FIXME */ + h = &s->h->hists[0]; + + /* Just fluff over all bytes into the history. The pitfall is that the + compressor has generated his history with the frame _including_ the + PPP proto bytes while our skb points behind them. We regenerate the + two bytes from the proto argument, using big endian (as always) */ + + h->hist[h->head++] = proto >> 8; + h->head &= LZS_HISTORY_MASK; + h->hist[h->head++] = proto & 0xff; + h->head &= LZS_HISTORY_MASK; + + while(ilen--) { + h->hist[h->head++] = *ibuf++; + h->head &= LZS_HISTORY_MASK; + } + +#ifdef HEAVY_DEBUG + printk(KERN_DEBUG "lzsIncomp: packet in:\n"); + + /* Dump the frame for decomp experiments */ + + ilen = skbin->len; + ibuf = skbin->data; + cols = 0; + + while(ilen--) { + if(!(cols % 16)) { + printk(KERN_DEBUG "[%04x]", cols); + } + printk(" %02x", *ibuf++); + cols++; + if(!(cols % 16) || !ilen) { + printk("\n"); + } + } + printk(KERN_DEBUG "\n"); +#endif /* HEAVY_DEBUG */ +#endif /* 0 */ + + s->stats.inc_bytes += skbin->len; + s->stats.inc_packets++; + + s->stats.in_count += skbin->len; + s->stats.bytes_out += skbin->len; + +} + +/* Decompression stuff actually starting here. I'm not that proud of it + because it will not compile to highly efficient code. I've tried around + a bit with other paradigms (macros, inline functions with lots of pointer + parameters for all state variables) and they were unreadable. The current + one is a compromise of readability and efficiency, not the less because + efficiency is very processor specific (on decent processors you want + to utilize the whole register file for temporaries and do a lot to let + the optimizer find out how - but the x86 register file is a bad joke and + actually replaced by a quite fast L1 cache so keeping state in a small + struct is probably faster here - keeping more than four longs of state + in registers would only lead to register thrashing [give me back my 68k]). + Code like this should be written in assembly anyway ;) */ + +/* Get one bit from the skb */ +static __inline u32 get1(LZSState *s) +{ + register u8 byte; + register u32 ret; + + if(s->left == 0) { + /* Not a single bit left - get a new byte */ + if(s->inlen) { + byte = *s->inbuf++; + s->inlen--; + s->word |= (byte << 8); + s->left = 8; + } else { + if(s->zstuff > 0) { + printk(KERN_INFO "lzsDecomp: Warning: stuffing zeros\n"); + s->zstuff++; + } else { + s->zstuff = 1; + } + } + } + ret = s->word & 0x8000; + s->word <<= 1; + s->left--; + return ret; +} + +/* Common getbyte code - not used above because it is a bit more complicated + than the case above and get1 is called really often */ + +static __inline void pullByte(LZSState *s) +{ + register u8 byte; + + if(s->inlen) { + byte = *s->inbuf++; + s->inlen--; + s->word |= (byte << (8 - s->left)); + s->left += 8; + } else { + /* TODO: Fix zero stuffing */ + if(s->zstuff > 0) { + printk(KERN_INFO "lzsDecomp: Warning: stuffing zeros\n"); + s->zstuff++; + } else { + s->zstuff = 1; + } + } +} + +/* Get two bits */ + +static __inline u32 get2(LZSState *s) +{ + register u32 ret; + + if(s->left < 2) + /* Not enough bits left - get a new byte */ + pullByte(s); + ret = s->word & 0xc000; + s->word <<= 2; + s->left -= 2; + return ret; +} + +/* Get four bits */ + +static __inline u32 get4(LZSState *s) +{ + register u32 ret; + + if(s->left < 4) + /* Not enough bits left - get a new byte */ + pullByte(s); + ret = s->word & 0xf000; + s->word <<= 4; + s->left -= 4; + return ret; +} + +/* Get seven bits */ + +static __inline u32 get7(LZSState *s) +{ + register u32 ret; + + if(s->left < 7) + /* Not enough bits left - get a new byte */ + pullByte(s); + ret = s->word & 0xfe00; + s->word <<= 7; + s->left -= 7; + return ret; +} + +/* Get eight bits */ + +static __inline u32 get8(LZSState *s) +{ + register u32 ret; + + if(s->left < 8) + /* Not enough bits left - get a new byte */ + pullByte(s); + ret = s->word & 0xff00; + s->word <<= 8; + s->left -= 8; + return ret; +} + +/* Get eleven bits - we just get 7 and then another 4 or we had to code + this explicitely */ + +static __inline u32 get11(LZSState *s) +{ + register u32 ret; + + ret = get7(s); + ret |= get4(s) >> 7; + + return ret; +} + +/* Get the compressed length value from the input stream */ + +static __inline short getCompLen(LZSState *s) +{ + register int clen, nibble; + /* The most probable cases are hardwired */ + switch(get2(s)) { + case 0x0000: + return 2; + case 0x4000: + return 3; + case 0x8000: + return 4; + default: + switch(get2(s)) { + case 0x0000: + return 5; + case 0x4000: + return 6; + case 0x8000: + return 7; + default: + /* Ok, no shortcuts anymore - just get nibbles and add up */ + clen = 8; + do { + nibble = get4(s) >> 12; + clen += nibble; + /* If we find enough nibbles to wrap something went really wrong. Or + not ? Actually, lengths > 2048 could be pretty Ok, if compressing + any data stream that is repetitive on a 2^n basis and very long, + the compressor could theoretically issue a very long self-reference + so we eventually need to remove this protection. If anyone ever sees + this happen, I was too german about that. */ + if(clen > LZS_HISTORY_SIZE) + return 0; + } while(nibble == 0xf); + return clen; + } + } +} + +/* Output one byte to history and outbuffer */ + +static __inline void byteOut(LZSState *s, LZSHist *h, struct sk_buff *skbout, + u8 byte) +{ + h->hist[h->head++] = byte; + h->head &= LZS_HISTORY_MASK; + if(skb_tailroom(skbout) > 0) + *(skb_put(skbout, 1)) = byte; + else + printk(KERN_WARNING "lzsDecomp: output skb full - truncated\n"); +} + +/* Output a bytestream referenced in the history by offs & clen */ + +static __inline void copyComp(LZSState *s, LZSHist *h, struct sk_buff *skbout, + int offs, int clen) +{ + register int hpos = h->head - offs; + + hpos &= LZS_HISTORY_MASK; + while(clen--) { + byteOut(s, h, skbout, h->hist[hpos]); + hpos++; + hpos &= LZS_HISTORY_MASK; + } +} + +static int lzsDecompress(void *state, struct sk_buff *skbin, + struct sk_buff *skbout, + struct isdn_ppp_resetparams *rsparm) +{ + /* Ahh. We finally got where the interesting stuff sits */ + + int ilen, cols; + u8 *ibuf; + + register int offs, clen; + register LZSState *s = (LZSState *)state; + register LZSHist *h; + u16 hi = 0; + u8 seqno; + u16 ccnt; + + /* Start by setting our read area */ + + s->inbuf = skbin->data; + s->inlen = skbin->len; + + /* Prepare the reset parameters for use */ + rsparm->valid = 1; + rsparm->rsend = 1; + rsparm->idval = 1; + rsparm->id = s->rsid; + + /* The first option, if present, is the history number to decompress + this frame against. Note that histories are counted starting at 1, + while our array indexing is zero based. */ + + switch(s->hmode) { + case LZS_HMODE_TRASH: + case LZS_HMODE_ONE: + /* No history number in frame. History #1 is implicit */ + hi = 1; + break; + case LZS_HMODE_MANY: + /* History is at the frame top. Either one or two bytes */ + if(s->inlen) { + hi = *s->inbuf++; + s->inlen--; + if(s->h->nhists > 255) { + /* Two byte history number */ + if(s->inlen) { + hi <<= 8; + hi |= *s->inbuf++; + s->inlen--; + } else { + return DECOMP_ERROR; + } + } + } else { + return DECOMP_ERROR; + } + break; + } + + h = &s->h->hists[hi - 1]; + + /* We do now know the most relevant parameter to the Reset-Request. Decide + whether we tack it on. We usually do so only if it is necessary aka + not one, or if tweak flags tell us it would be better. */ + + if(rsparm->maxdlen >= 2 && + ((s->cmode == LZS_CMODE_SEQNO && hi == 1 && + (tweak & LZS_TW_M3_RSRQ_EXP_HIST)) || (hi != 1))) { + rsparm->dlen = 2; + rsparm->data[0] = (hi >> 8); + rsparm->data[1] = (hi & 0xff); + rsparm->dtval = 1; + } + + /* The second option, if present, is the check item according to the + check mode negotiated for this state */ + switch(s->cmode) { + case LZS_CMODE_NONE: + /* Very dumb mode. Will likely go boom if sneezed onto */ + break; + case LZS_CMODE_SEQNO: + /* Default mode. Next byte is a sequence number. Check this out */ + if(s->inlen) { + seqno = *s->inbuf++; + s->inlen--; + } else { + return DECOMP_ERROR; + } + +#ifdef TEST_BROKEN_SEQNO + if(seqno == TEST_BROKEN_SEQNO) + seqno++; +#endif + + if(seqno != h->seqno) { + /* We did not expect _that_ sequence number */ + if(debug) + printk(KERN_DEBUG "lzsDecomp: rcvd seq# %d exp seq# %d (sync lost)\n", + seqno, h->seqno); + /* We MUST resync on seqno+1 */ + seqno++; + h->seqno = seqno; + /* We have an outstanding reset ack now */ + rsparm->expra = 1; + h->expra = 1; + /* We expect the Ack to have this id (which we stuff into the Req) */ + h->rsid = s->rsid; + return DECOMP_ERROR; + } else { + /* Correct seqno got - expect the next one */ + h->seqno++; + /* Are we really resynced or is a reset ack still outstanding ? */ + if(h->expra) { + if(debug) + printk(KERN_DEBUG "lzsDecomp: rcvd seq# %d but missing ResetAck\n", seqno); + /* Error again */ + rsparm->expra = 1; + return DECOMP_ERROR; + } + } + break; + + case LZS_CMODE_EXT: + /* The next 16 bit contain the 4 flagbits of which 2 are actually used and + the 12 bit coherency counter (another sequence number, that is). */ + + rsparm->expra = 0; /* Ext mode uses inband signaling - no Acks */ + + if(tweak & LZS_TW_M4_RSRQ_NO_HIST) + /* Should be default, but make it sure finally */ + rsparm->dtval = rsparm->dlen = 0; + + if(s->inlen > 1) { + ccnt = (*s->inbuf++ << 8); + ccnt |= *s->inbuf++; + s->inlen -= 2; + } else { + return DECOMP_ERROR; + } + +#ifdef TEST_BROKEN_CCNT + if((ccnt & 0x0fff) == TEST_BROKEN_CCNT) + ccnt++; +#endif + + if((ccnt & 0x0fff) != s->ccnt) { + /* Coherency count out of sequence */ + if(ccnt & 0x8000) { + /* Bit A is set - this is an inband reset-ack and we resync our coherency + counter with the one supplied */ + s->ccnt = (ccnt & 0x0fff); + if(debug) + printk(KERN_DEBUG "lzsDecomp: coherency resync on %03x\n", ccnt & 0x0fff); + /* Make sure the next error uses a new id - this was an ack */ + s->rsid++; + } else { + /* We are indeed out of sync */ + if(debug) + printk(KERN_DEBUG "lzsDecomp: coherency %03x expected %03x (sync lost)\n", + ccnt & 0x0fff, s->ccnt); + return DECOMP_ERROR; + } + } + /* We expect the next coherency counter */ + s->ccnt++; + s->ccnt &= 0x0fff; + if(!(ccnt & 0x2000)) { + /* Bit C indicates the frame is not compressed - push it out */ + if(debug > 1) + printk(KERN_DEBUG "lzsDecomp: uncompressed frame passed through\n"); + if(skb_tailroom(skbout) >= s->inlen) { + memcpy(skb_put(skbout, s->inlen), s->inbuf, s->inlen); + + s->stats.inc_bytes += s->inlen; + s->stats.inc_packets++; + s->stats.in_count += s->inlen; + s->stats.bytes_out += s->inlen; + + return s->inlen; + } else { + printk(KERN_WARNING "lzsDecomp: uncompressed frame dropped (out of mem)\n"); + } + } + break; + + case LZS_CMODE_LCB: + case LZS_CMODE_CRC: + default: + /* Still to be implemented */ + printk(KERN_WARNING "lzsDecomp: cmode %d NYI (CCP teardown)\n", s->cmode); + return DECOMP_FATALERROR; + } + + /* The real decompression code. Looks quite simple ? */ + + /* Initialize for decomp */ + s->word = s->left = s->zstuff = 0; + + for(;;) { + if(s->zstuff > 1) { + printk(KERN_WARNING "lzsDecomp: missing end marker - cooked one\n"); + break; + } + if(get1(s)) { + /* Compressed bytes follow */ + if(get1(s)) { + /* Seven bit offset follows */ + offs = get7(s) >> 9; + if(!offs) + /* This is the end marker - a 7 bit offset of zero */ + break; + /* You see the error message down there ? You actually think it is + nonsense ? Look up my comment at getCompLen() and give me a hint on + what you find out. */ + if(!(clen = getCompLen(s))) { + printk(KERN_WARNING "lzsDecomp: length hosed - dropped\n"); + return DECOMP_ERROR; + } + copyComp(s, h, skbout, offs, clen); + } else { + /* Eleven bit offset follows */ + offs = get11(s) >> 5; + if(!(clen = getCompLen(s))) { + printk(KERN_WARNING "lzsDecomp: length hosed - dropped\n"); + return DECOMP_ERROR; + } + copyComp(s, h, skbout, offs, clen); + } + } else { + /* Literal byte follows */ + byteOut(s, h, skbout, get8(s) >> 8); + } + } + + /* Makes things more symmetric */ + if(debug > 1) + printk(KERN_DEBUG "lzsDecomp: %d in %d out - decompressed\n", + skbin->len, skbout->len); + + if(debug > 2) { + printk(KERN_DEBUG "lzsDecomp packet in:\n"); + + ilen = skbin->len; + ibuf = skbin->data; + cols = 0; + + while(ilen--) { + if(!(cols % 16)) { + printk(KERN_DEBUG "[%04x]", cols); + } + printk(" %02x", *ibuf++); + cols++; + if(!(cols % 16) || !ilen) { + printk("\n"); + } + } + printk(KERN_DEBUG "\n"); + + printk(KERN_DEBUG "lzsDecomp packet out:\n"); + + ilen = skbout->len; + ibuf = skbout->data; + cols = 0; + + while(ilen--) { + if(!(cols % 16)) { + printk(KERN_DEBUG "[%04x]", cols); + } + printk(" %02x", *ibuf++); + cols++; + if(!(cols % 16) || !ilen) { + printk("\n"); + } + } + printk(KERN_DEBUG "\n"); + } + + s->stats.comp_bytes += skbout->len; + s->stats.comp_packets++; + s->stats.in_count += skbin->len; + s->stats.bytes_out += skbout->len; + + return skbout->len; +} + +/* + * Entry points of this shrinker + */ + +static struct isdn_ppp_compressor ippp_lzs_compress = { + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0) + .owner = THIS_MODULE, /* 2.6 */ +#endif + + .num = CI_LZS_COMPRESS, /* CCP proto for PPP */ + .alloc = lzsAlloc, /* Alloc new state */ + .free = lzsFree, /* Drop state */ + .init = lzsInit, /* Initialize state */ + .reset = lzsReset, /* Reset state */ + .compress = lzsCompress, /* Do the shrink */ + .decompress = lzsDecompress, /* Do the other thing */ + .incomp = lzsIncomp, /* Handle incompressible frame */ + .stat = lzsStats, /* Get stats */ +}; + +/* + * Module init: Register myself with the compressor list + */ + +static int isdn_lzscomp_init(void) +{ + int a = isdn_ppp_register_compressor (&ippp_lzs_compress); + if (!a) { + printk (KERN_INFO "PPP Stac/HiFn LZS (De)Compression registered\n"); + if(comp < 0 || comp > 9) { + printk(KERN_ERR "lzs: 0 <= comp <= 9 - set to 0 (no compression)\n"); + comp = 0; + } + } + return a; +} + +/* + * Module fini: Clear my traces + */ + +void isdn_lzscomp_exit(void) +{ + isdn_ppp_unregister_compressor (&ippp_lzs_compress); +} + +module_init(isdn_lzscomp_init); +module_exit(isdn_lzscomp_exit); diff -urN linux-2.6.32-rc6/drivers/isdn/i4l/Kconfig linux-2.6.32-rc6.fli4l/drivers/isdn/i4l/Kconfig --- linux-2.6.32-rc6/drivers/isdn/i4l/Kconfig 2009-11-03 20:37:49.000000000 +0100 +++ linux-2.6.32-rc6.fli4l/drivers/isdn/i4l/Kconfig 2009-11-11 13:20:02.726061105 +0100 @@ -56,6 +56,21 @@ method as well for this to be useful. Even if they don't support it, it is safe to say Y here. +config ISDN_PPP_LZSCOMP + tristate "Support LZS compression" + depends on ISDN_PPP + help + This enables Stac/HiFn LZS compression (RFC 1974) for synchronous PPP + over ISDN lines. The other end has to support it as well for this to + be useful and you have to give the parameter "lzs x:y" to the "ipppd" + daemon to actually activate it, see Documentation/isdn/README.LZS. + + PLEASE CHECK WHETHER USING THIS CODE CONSTITUTES A CRIME IN YOUR COUNTRY. + + For patent and license issues refer to Documentation/isdn/README.LZS. + + If unsure, say N. + config ISDN_AUDIO bool "Support audio via ISDN" help diff -urN linux-2.6.32-rc6/drivers/isdn/i4l/Makefile linux-2.6.32-rc6.fli4l/drivers/isdn/i4l/Makefile --- linux-2.6.32-rc6/drivers/isdn/i4l/Makefile 2009-11-03 20:37:49.000000000 +0100 +++ linux-2.6.32-rc6.fli4l/drivers/isdn/i4l/Makefile 2009-11-11 11:18:21.000000000 +0100 @@ -4,6 +4,7 @@ obj-$(CONFIG_ISDN_I4L) += isdn.o obj-$(CONFIG_ISDN_PPP_BSDCOMP) += isdn_bsdcomp.o +obj-$(CONFIG_ISDN_PPP_LZSCOMP) += isdn_lzscomp.o obj-$(CONFIG_ISDN_HDLC) += isdnhdlc.o # Multipart objects. diff -urN linux-2.6.32-rc6/include/linux/isdn_lzscomp.h linux-2.6.32-rc6.fli4l/include/linux/isdn_lzscomp.h --- linux-2.6.32-rc6/include/linux/isdn_lzscomp.h 1970-01-01 01:00:00.000000000 +0100 +++ linux-2.6.32-rc6.fli4l/include/linux/isdn_lzscomp.h 2009-11-11 13:20:02.726061105 +0100 @@ -0,0 +1,28 @@ +/* $Id: isdn_lzscomp.h,v 1.1.4.1 2001/11/20 14:19:38 kai Exp $ + * + * Header for isdn_lzscomp.c + * Concentrated here to not mess up half a dozen kernel headers with code + * snippets + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#define CI_LZS_COMPRESS 17 +#define CILEN_LZS_COMPRESS 5 + +#define LZS_CMODE_NONE 0 +#define LZS_CMODE_LCB 1 +#define LZS_CMODE_CRC 2 +#define LZS_CMODE_SEQNO 3 /* MUST be implemented (default) */ +#define LZS_CMODE_EXT 4 /* Seems to be what Win0.95 uses */ + +#define LZS_COMP_MAX_HISTS 32 /* For two-way MultiHistory */ +#define LZS_COMP_DEF_HISTS 1 /* Most likely to negotiate */ +#define LZS_DECOMP_MAX_HISTS 32 /* More is really nonsense */ +#define LZS_DECOMP_DEF_HISTS 8 /* If we get it, this may be optimal */ + +#define LZS_HIST_BYTE1(word) (word>>8) /* Just for better reading */ +#define LZS_HIST_BYTE2(word) (word&0xff) /* of this big endian stuff */ +#define LZS_HIST_WORD(b1,b2) ((b1<<8)|b2) /* (network byte order rulez) */