| /*- |
| * Copyright (c) 1997 Brian Somers <brian@Awfulhak.org> |
| * All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND |
| * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE |
| * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
| * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS |
| * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
| * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
| * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
| * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
| * SUCH DAMAGE. |
| * |
| * $FreeBSD: src/usr.sbin/ppp/deflate.c,v 1.26.26.1 2010/12/21 17:10:29 kensmith Exp $ |
| */ |
| |
| #include <sys/types.h> |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <zlib.h> |
| |
| #include "mbuf.h" |
| #include "log.h" |
| #include "timer.h" |
| #include "fsm.h" |
| #include "ccp.h" |
| #include "deflate.h" |
| |
| /* Our state */ |
| struct deflate_state { |
| u_short seqno; |
| int uncomp_rec; |
| int winsize; |
| z_stream cx; |
| }; |
| |
| static char garbage[10]; |
| static u_char EMPTY_BLOCK[4] = { 0x00, 0x00, 0xff, 0xff }; |
| |
| #define DEFLATE_CHUNK_LEN (1536 - sizeof(struct mbuf)) |
| |
| static int |
| DeflateResetOutput(void *v) |
| { |
| struct deflate_state *state = (struct deflate_state *)v; |
| |
| state->seqno = 0; |
| state->uncomp_rec = 0; |
| deflateReset(&state->cx); |
| log_Printf(LogCCP, "Deflate: Output channel reset\n"); |
| |
| return 1; /* Ask FSM to ACK */ |
| } |
| |
| static struct mbuf * |
| DeflateOutput(void *v, struct ccp *ccp, struct link *l __unused, |
| int pri __unused, u_short *proto, struct mbuf *mp) |
| { |
| struct deflate_state *state = (struct deflate_state *)v; |
| u_char *wp, *rp; |
| int olen, ilen, len, res, flush; |
| struct mbuf *mo_head, *mo, *mi_head, *mi; |
| |
| ilen = m_length(mp); |
| log_Printf(LogDEBUG, "DeflateOutput: Proto %02x (%d bytes)\n", *proto, ilen); |
| log_DumpBp(LogDEBUG, "DeflateOutput: Compress packet:", mp); |
| |
| /* Stuff the protocol in front of the input */ |
| mi_head = mi = m_get(2, MB_CCPOUT); |
| mi->m_next = mp; |
| rp = MBUF_CTOP(mi); |
| if (*proto < 0x100) { /* Compress the protocol */ |
| rp[0] = *proto & 0377; |
| mi->m_len = 1; |
| } else { /* Don't compress the protocol */ |
| rp[0] = *proto >> 8; |
| rp[1] = *proto & 0377; |
| mi->m_len = 2; |
| } |
| |
| /* Allocate the initial output mbuf */ |
| mo_head = mo = m_get(DEFLATE_CHUNK_LEN, MB_CCPOUT); |
| mo->m_len = 2; |
| wp = MBUF_CTOP(mo); |
| *wp++ = state->seqno >> 8; |
| *wp++ = state->seqno & 0377; |
| log_Printf(LogDEBUG, "DeflateOutput: Seq %d\n", state->seqno); |
| state->seqno++; |
| |
| /* Set up the deflation context */ |
| state->cx.next_out = wp; |
| state->cx.avail_out = DEFLATE_CHUNK_LEN - 2; |
| state->cx.next_in = MBUF_CTOP(mi); |
| state->cx.avail_in = mi->m_len; |
| flush = Z_NO_FLUSH; |
| |
| olen = 0; |
| while (1) { |
| if ((res = deflate(&state->cx, flush)) != Z_OK) { |
| if (res == Z_STREAM_END) |
| break; /* Done */ |
| log_Printf(LogWARN, "DeflateOutput: deflate returned %d (%s)\n", |
| res, state->cx.msg ? state->cx.msg : ""); |
| m_freem(mo_head); |
| m_free(mi_head); |
| state->seqno--; |
| return mp; /* Our dictionary's probably dead now :-( */ |
| } |
| |
| if (flush == Z_SYNC_FLUSH && state->cx.avail_out != 0) |
| break; |
| |
| if (state->cx.avail_in == 0 && mi->m_next != NULL) { |
| mi = mi->m_next; |
| state->cx.next_in = MBUF_CTOP(mi); |
| state->cx.avail_in = mi->m_len; |
| if (mi->m_next == NULL) |
| flush = Z_SYNC_FLUSH; |
| } |
| |
| if (state->cx.avail_out == 0) { |
| mo->m_next = m_get(DEFLATE_CHUNK_LEN, MB_CCPOUT); |
| olen += (mo->m_len = DEFLATE_CHUNK_LEN); |
| mo = mo->m_next; |
| mo->m_len = 0; |
| state->cx.next_out = MBUF_CTOP(mo); |
| state->cx.avail_out = DEFLATE_CHUNK_LEN; |
| } |
| } |
| |
| olen += (mo->m_len = DEFLATE_CHUNK_LEN - state->cx.avail_out); |
| olen -= 4; /* exclude the trailing EMPTY_BLOCK */ |
| |
| /* |
| * If the output packet (including seqno and excluding the EMPTY_BLOCK) |
| * got bigger, send the original. |
| */ |
| if (olen >= ilen) { |
| m_freem(mo_head); |
| m_free(mi_head); |
| log_Printf(LogDEBUG, "DeflateOutput: %d => %d: Uncompressible (0x%04x)\n", |
| ilen, olen, *proto); |
| ccp->uncompout += ilen; |
| ccp->compout += ilen; /* We measure this stuff too */ |
| return mp; |
| } |
| |
| m_freem(mi_head); |
| |
| /* |
| * Lose the last four bytes of our output. |
| * XXX: We should probably assert that these are the same as the |
| * contents of EMPTY_BLOCK. |
| */ |
| mo = mo_head; |
| for (len = mo->m_len; len < olen; mo = mo->m_next, len += mo->m_len) |
| ; |
| mo->m_len -= len - olen; |
| if (mo->m_next != NULL) { |
| m_freem(mo->m_next); |
| mo->m_next = NULL; |
| } |
| |
| ccp->uncompout += ilen; |
| ccp->compout += olen; |
| |
| log_Printf(LogDEBUG, "DeflateOutput: %d => %d bytes, proto 0x%04x\n", |
| ilen, olen, *proto); |
| |
| *proto = ccp_Proto(ccp); |
| return mo_head; |
| } |
| |
| static void |
| DeflateResetInput(void *v) |
| { |
| struct deflate_state *state = (struct deflate_state *)v; |
| |
| state->seqno = 0; |
| state->uncomp_rec = 0; |
| inflateReset(&state->cx); |
| log_Printf(LogCCP, "Deflate: Input channel reset\n"); |
| } |
| |
| static struct mbuf * |
| DeflateInput(void *v, struct ccp *ccp, u_short *proto, struct mbuf *mi) |
| { |
| struct deflate_state *state = (struct deflate_state *)v; |
| struct mbuf *mo, *mo_head, *mi_head; |
| u_char *wp; |
| int ilen, olen; |
| int seq, flush, res, first; |
| u_char hdr[2]; |
| |
| log_DumpBp(LogDEBUG, "DeflateInput: Decompress packet:", mi); |
| mi_head = mi = mbuf_Read(mi, hdr, 2); |
| ilen = 2; |
| |
| /* Check the sequence number. */ |
| seq = (hdr[0] << 8) + hdr[1]; |
| log_Printf(LogDEBUG, "DeflateInput: Seq %d\n", seq); |
| if (seq != state->seqno) { |
| if (seq <= state->uncomp_rec) |
| /* |
| * So the peer's started at zero again - fine ! If we're wrong, |
| * inflate() will fail. This is better than getting into a loop |
| * trying to get a ResetReq to a busy sender. |
| */ |
| state->seqno = seq; |
| else { |
| log_Printf(LogCCP, "DeflateInput: Seq error: Got %d, expected %d\n", |
| seq, state->seqno); |
| m_freem(mi_head); |
| ccp_SendResetReq(&ccp->fsm); |
| return NULL; |
| } |
| } |
| state->seqno++; |
| state->uncomp_rec = 0; |
| |
| /* Allocate an output mbuf */ |
| mo_head = mo = m_get(DEFLATE_CHUNK_LEN, MB_CCPIN); |
| |
| /* Our proto starts with 0 if it's compressed */ |
| wp = MBUF_CTOP(mo); |
| wp[0] = '\0'; |
| |
| /* |
| * We set avail_out to 1 initially so we can look at the first |
| * byte of the output and decide whether we have a compressed |
| * proto field. |
| */ |
| state->cx.next_in = MBUF_CTOP(mi); |
| state->cx.avail_in = mi->m_len; |
| state->cx.next_out = wp + 1; |
| state->cx.avail_out = 1; |
| ilen += mi->m_len; |
| |
| flush = mi->m_next ? Z_NO_FLUSH : Z_SYNC_FLUSH; |
| first = 1; |
| olen = 0; |
| |
| while (1) { |
| if ((res = inflate(&state->cx, flush)) != Z_OK) { |
| if (res == Z_STREAM_END) |
| break; /* Done */ |
| log_Printf(LogCCP, "DeflateInput: inflate returned %d (%s)\n", |
| res, state->cx.msg ? state->cx.msg : ""); |
| m_freem(mo_head); |
| m_freem(mi); |
| ccp_SendResetReq(&ccp->fsm); |
| return NULL; |
| } |
| |
| if (flush == Z_SYNC_FLUSH && state->cx.avail_out != 0) |
| break; |
| |
| if (state->cx.avail_in == 0 && mi && (mi = m_free(mi)) != NULL) { |
| /* underflow */ |
| state->cx.next_in = MBUF_CTOP(mi); |
| ilen += (state->cx.avail_in = mi->m_len); |
| if (mi->m_next == NULL) |
| flush = Z_SYNC_FLUSH; |
| } |
| |
| if (state->cx.avail_out == 0) { |
| /* overflow */ |
| if (first) { |
| if (!(wp[1] & 1)) { |
| /* 2 byte proto, shuffle it back in output */ |
| wp[0] = wp[1]; |
| state->cx.next_out--; |
| state->cx.avail_out = DEFLATE_CHUNK_LEN-1; |
| } else |
| state->cx.avail_out = DEFLATE_CHUNK_LEN-2; |
| first = 0; |
| } else { |
| olen += (mo->m_len = DEFLATE_CHUNK_LEN); |
| mo->m_next = m_get(DEFLATE_CHUNK_LEN, MB_CCPIN); |
| mo = mo->m_next; |
| state->cx.next_out = MBUF_CTOP(mo); |
| state->cx.avail_out = DEFLATE_CHUNK_LEN; |
| } |
| } |
| } |
| |
| if (mi != NULL) |
| m_freem(mi); |
| |
| if (first) { |
| log_Printf(LogCCP, "DeflateInput: Length error\n"); |
| m_freem(mo_head); |
| ccp_SendResetReq(&ccp->fsm); |
| return NULL; |
| } |
| |
| olen += (mo->m_len = DEFLATE_CHUNK_LEN - state->cx.avail_out); |
| |
| *proto = ((u_short)wp[0] << 8) | wp[1]; |
| mo_head->m_offset += 2; |
| mo_head->m_len -= 2; |
| olen -= 2; |
| |
| ccp->compin += ilen; |
| ccp->uncompin += olen; |
| |
| log_Printf(LogDEBUG, "DeflateInput: %d => %d bytes, proto 0x%04x\n", |
| ilen, olen, *proto); |
| |
| /* |
| * Simulate an EMPTY_BLOCK so that our dictionary stays in sync. |
| * The peer will have silently removed this! |
| */ |
| state->cx.next_out = garbage; |
| state->cx.avail_out = sizeof garbage; |
| state->cx.next_in = EMPTY_BLOCK; |
| state->cx.avail_in = sizeof EMPTY_BLOCK; |
| inflate(&state->cx, Z_SYNC_FLUSH); |
| |
| return mo_head; |
| } |
| |
| static void |
| DeflateDictSetup(void *v, struct ccp *ccp, u_short proto, struct mbuf *mi) |
| { |
| struct deflate_state *state = (struct deflate_state *)v; |
| int res, flush, expect_error; |
| u_char *rp; |
| struct mbuf *mi_head; |
| short len; |
| |
| log_Printf(LogDEBUG, "DeflateDictSetup: Got seq %d\n", state->seqno); |
| |
| /* |
| * Stuff an ``uncompressed data'' block header followed by the |
| * protocol in front of the input |
| */ |
| mi_head = m_get(7, MB_CCPOUT); |
| mi_head->m_next = mi; |
| len = m_length(mi); |
| mi = mi_head; |
| rp = MBUF_CTOP(mi); |
| if (proto < 0x100) { /* Compress the protocol */ |
| rp[5] = proto & 0377; |
| mi->m_len = 6; |
| len++; |
| } else { /* Don't compress the protocol */ |
| rp[5] = proto >> 8; |
| rp[6] = proto & 0377; |
| mi->m_len = 7; |
| len += 2; |
| } |
| rp[0] = 0x80; /* BITS: 100xxxxx */ |
| rp[1] = len & 0377; /* The length */ |
| rp[2] = len >> 8; |
| rp[3] = (~len) & 0377; /* One's compliment of the length */ |
| rp[4] = (~len) >> 8; |
| |
| state->cx.next_in = rp; |
| state->cx.avail_in = mi->m_len; |
| state->cx.next_out = garbage; |
| state->cx.avail_out = sizeof garbage; |
| flush = Z_NO_FLUSH; |
| expect_error = 0; |
| |
| while (1) { |
| if ((res = inflate(&state->cx, flush)) != Z_OK) { |
| if (res == Z_STREAM_END) |
| break; /* Done */ |
| if (expect_error && res == Z_BUF_ERROR) |
| break; |
| log_Printf(LogCCP, "DeflateDictSetup: inflate returned %d (%s)\n", |
| res, state->cx.msg ? state->cx.msg : ""); |
| log_Printf(LogCCP, "DeflateDictSetup: avail_in %d, avail_out %d\n", |
| state->cx.avail_in, state->cx.avail_out); |
| ccp_SendResetReq(&ccp->fsm); |
| m_free(mi_head); /* lose our allocated ``head'' buf */ |
| return; |
| } |
| |
| if (flush == Z_SYNC_FLUSH && state->cx.avail_out != 0) |
| break; |
| |
| if (state->cx.avail_in == 0 && mi && (mi = mi->m_next) != NULL) { |
| /* underflow */ |
| state->cx.next_in = MBUF_CTOP(mi); |
| state->cx.avail_in = mi->m_len; |
| if (mi->m_next == NULL) |
| flush = Z_SYNC_FLUSH; |
| } |
| |
| if (state->cx.avail_out == 0) { |
| if (state->cx.avail_in == 0) |
| /* |
| * This seems to be a bug in libz ! If inflate() finished |
| * with 0 avail_in and 0 avail_out *and* this is the end of |
| * our input *and* inflate() *has* actually written all the |
| * output it's going to, it *doesn't* return Z_STREAM_END ! |
| * When we subsequently call it with no more input, it gives |
| * us Z_BUF_ERROR :-( It seems pretty safe to ignore this |
| * error (the dictionary seems to stay in sync). In the worst |
| * case, we'll drop the next compressed packet and do a |
| * CcpReset() then. |
| */ |
| expect_error = 1; |
| /* overflow */ |
| state->cx.next_out = garbage; |
| state->cx.avail_out = sizeof garbage; |
| } |
| } |
| |
| ccp->compin += len; |
| ccp->uncompin += len; |
| |
| state->seqno++; |
| state->uncomp_rec++; |
| m_free(mi_head); /* lose our allocated ``head'' buf */ |
| } |
| |
| static const char * |
| DeflateDispOpts(struct fsm_opt *o) |
| { |
| static char disp[7]; /* Must be used immediately */ |
| |
| sprintf(disp, "win %d", (o->data[0]>>4) + 8); |
| return disp; |
| } |
| |
| static void |
| DeflateInitOptsOutput(struct bundle *bundle __unused, struct fsm_opt *o, |
| const struct ccp_config *cfg) |
| { |
| o->hdr.len = 4; |
| o->data[0] = ((cfg->deflate.out.winsize - 8) << 4) + 8; |
| o->data[1] = '\0'; |
| } |
| |
| static int |
| DeflateSetOptsOutput(struct bundle *bundle __unused, struct fsm_opt *o, |
| const struct ccp_config *cfg __unused) |
| { |
| if (o->hdr.len != 4 || (o->data[0] & 15) != 8 || o->data[1] != '\0') |
| return MODE_REJ; |
| |
| if ((o->data[0] >> 4) + 8 > 15) { |
| o->data[0] = ((15 - 8) << 4) + 8; |
| return MODE_NAK; |
| } |
| |
| return MODE_ACK; |
| } |
| |
| static int |
| DeflateSetOptsInput(struct bundle *bundle __unused, struct fsm_opt *o, |
| const struct ccp_config *cfg) |
| { |
| int want; |
| |
| if (o->hdr.len != 4 || (o->data[0] & 15) != 8 || o->data[1] != '\0') |
| return MODE_REJ; |
| |
| want = (o->data[0] >> 4) + 8; |
| if (cfg->deflate.in.winsize == 0) { |
| if (want < 8 || want > 15) { |
| o->data[0] = ((15 - 8) << 4) + 8; |
| } |
| } else if (want != cfg->deflate.in.winsize) { |
| o->data[0] = ((cfg->deflate.in.winsize - 8) << 4) + 8; |
| return MODE_NAK; |
| } |
| |
| return MODE_ACK; |
| } |
| |
| static void * |
| DeflateInitInput(struct bundle *bundle __unused, struct fsm_opt *o) |
| { |
| struct deflate_state *state; |
| |
| state = (struct deflate_state *)malloc(sizeof(struct deflate_state)); |
| if (state != NULL) { |
| state->winsize = (o->data[0] >> 4) + 8; |
| state->cx.zalloc = NULL; |
| state->cx.opaque = NULL; |
| state->cx.zfree = NULL; |
| state->cx.next_out = NULL; |
| if (inflateInit2(&state->cx, -state->winsize) == Z_OK) |
| DeflateResetInput(state); |
| else { |
| free(state); |
| state = NULL; |
| } |
| } |
| |
| return state; |
| } |
| |
| static void * |
| DeflateInitOutput(struct bundle *bundle __unused, struct fsm_opt *o) |
| { |
| struct deflate_state *state; |
| |
| state = (struct deflate_state *)malloc(sizeof(struct deflate_state)); |
| if (state != NULL) { |
| state->winsize = (o->data[0] >> 4) + 8; |
| state->cx.zalloc = NULL; |
| state->cx.opaque = NULL; |
| state->cx.zfree = NULL; |
| state->cx.next_in = NULL; |
| if (deflateInit2(&state->cx, Z_DEFAULT_COMPRESSION, 8, |
| -state->winsize, 8, Z_DEFAULT_STRATEGY) == Z_OK) |
| DeflateResetOutput(state); |
| else { |
| free(state); |
| state = NULL; |
| } |
| } |
| |
| return state; |
| } |
| |
| static void |
| DeflateTermInput(void *v) |
| { |
| struct deflate_state *state = (struct deflate_state *)v; |
| |
| inflateEnd(&state->cx); |
| free(state); |
| } |
| |
| static void |
| DeflateTermOutput(void *v) |
| { |
| struct deflate_state *state = (struct deflate_state *)v; |
| |
| deflateEnd(&state->cx); |
| free(state); |
| } |
| |
| const struct ccp_algorithm PppdDeflateAlgorithm = { |
| TY_PPPD_DEFLATE, /* Older versions of pppd expected this ``type'' */ |
| CCP_NEG_DEFLATE24, |
| DeflateDispOpts, |
| ccp_DefaultUsable, |
| ccp_DefaultRequired, |
| { |
| DeflateSetOptsInput, |
| DeflateInitInput, |
| DeflateTermInput, |
| DeflateResetInput, |
| DeflateInput, |
| DeflateDictSetup |
| }, |
| { |
| 0, |
| DeflateInitOptsOutput, |
| DeflateSetOptsOutput, |
| DeflateInitOutput, |
| DeflateTermOutput, |
| DeflateResetOutput, |
| DeflateOutput |
| }, |
| }; |
| |
| const struct ccp_algorithm DeflateAlgorithm = { |
| TY_DEFLATE, /* rfc 1979 */ |
| CCP_NEG_DEFLATE, |
| DeflateDispOpts, |
| ccp_DefaultUsable, |
| ccp_DefaultRequired, |
| { |
| DeflateSetOptsInput, |
| DeflateInitInput, |
| DeflateTermInput, |
| DeflateResetInput, |
| DeflateInput, |
| DeflateDictSetup |
| }, |
| { |
| 0, |
| DeflateInitOptsOutput, |
| DeflateSetOptsOutput, |
| DeflateInitOutput, |
| DeflateTermOutput, |
| DeflateResetOutput, |
| DeflateOutput |
| }, |
| }; |