src/openvpn/comp-lz4.c
40efb635
 /*
  *  OpenVPN -- An application to securely tunnel IP networks
  *             over a single UDP port, with support for SSL/TLS-based
  *             session authentication and key exchange,
  *             packet encryption, packet authentication, and
  *             packet compression.
  *
49979459
  *  Copyright (C) 2002-2018 OpenVPN Inc <sales@openvpn.net>
  *  Copyright (C) 2013-2018 Gert Doering <gert@greenie.muc.de>
40efb635
  *
  *  This program is free software; you can redistribute it and/or modify
  *  it under the terms of the GNU General Public License version 2
  *  as published by the Free Software Foundation.
  *
  *  This program is distributed in the hope that it will be useful,
  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  *  GNU General Public License for more details.
  *
caa54ac3
  *  You should have received a copy of the GNU General Public License along
  *  with this program; if not, write to the Free Software Foundation, Inc.,
  *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
40efb635
  */
 
 #ifdef HAVE_CONFIG_H
 #include "config.h"
 #elif defined(_MSC_VER)
 #include "config-msvc.h"
 #endif
 
 #include "syshead.h"
 
 #if defined(ENABLE_LZ4)
 
4308f237
 #if defined(NEED_COMPAT_LZ4)
 #include "compat-lz4.h"
 #else
40efb635
 #include "lz4.h"
4308f237
 #endif
40efb635
 
 #include "comp.h"
 #include "error.h"
 
 #include "memdbg.h"
 
5f6225c3
 
40efb635
 static void
81d882d5
 lz4_compress_init(struct compress_context *compctx)
40efb635
 {
81d882d5
     msg(D_INIT_MEDIUM, "LZ4 compression initializing");
     ASSERT(compctx->flags & COMP_F_SWAP);
40efb635
 }
 
 static void
81d882d5
 lz4v2_compress_init(struct compress_context *compctx)
40efb635
 {
81d882d5
     msg(D_INIT_MEDIUM, "LZ4v2 compression initializing");
40efb635
 }
 
 static void
81d882d5
 lz4_compress_uninit(struct compress_context *compctx)
40efb635
 {
a75bb2e4
 }
40efb635
 
a75bb2e4
 static bool
81d882d5
 do_lz4_compress(struct buffer *buf,
                 struct buffer *work,
                 struct compress_context *compctx,
                 const struct frame *frame)
a75bb2e4
 {
81d882d5
     /*
      * In order to attempt compression, length must be at least COMPRESS_THRESHOLD.
      */
     if (buf->len >= COMPRESS_THRESHOLD)
40efb635
     {
81d882d5
         const size_t ps = PAYLOAD_SIZE(frame);
         int zlen_max = ps + COMP_EXTRA_BUFFER(ps);
         int zlen;
40efb635
 
81d882d5
         ASSERT(buf_init(work, FRAME_HEADROOM(frame)));
         ASSERT(buf_safe(work, zlen_max));
40efb635
 
81d882d5
         if (buf->len > ps)
         {
             dmsg(D_COMP_ERRORS, "LZ4 compression buffer overflow");
             buf->len = 0;
             return false;
         }
40efb635
 
5f6225c3
         zlen = LZ4_compress_default((const char *)BPTR(buf), (char *)BPTR(work), BLEN(buf), zlen_max);
40efb635
 
81d882d5
         if (zlen <= 0)
         {
             dmsg(D_COMP_ERRORS, "LZ4 compression error");
             buf->len = 0;
             return false;
         }
40efb635
 
81d882d5
         ASSERT(buf_safe(work, zlen));
         work->len = zlen;
a75bb2e4
 
40efb635
 
81d882d5
         dmsg(D_COMP, "LZ4 compress %d -> %d", buf->len, work->len);
         compctx->pre_compress += buf->len;
         compctx->post_compress += work->len;
         return true;
40efb635
     }
81d882d5
     return false;
a75bb2e4
 }
 
 
 static void
81d882d5
 lz4_compress(struct buffer *buf, struct buffer work,
              struct compress_context *compctx,
              const struct frame *frame)
a75bb2e4
 {
81d882d5
     bool compressed;
     if (buf->len <= 0)
     {
         return;
     }
a75bb2e4
 
81d882d5
     compressed = do_lz4_compress(buf, &work, compctx, frame);
40efb635
 
81d882d5
     /* On error do_lz4_compress sets buf len to zero, just return */
     if (buf->len == 0)
     {
         return;
     }
40efb635
 
81d882d5
     /* did compression save us anything? */
40efb635
     {
81d882d5
         uint8_t comp_head_byte = NO_COMPRESS_BYTE_SWAP;
         if (compressed && work.len < buf->len)
         {
             *buf = work;
             comp_head_byte = LZ4_COMPRESS_BYTE;
         }
 
         {
             uint8_t *head = BPTR(buf);
             uint8_t *tail  = BEND(buf);
             ASSERT(buf_safe(buf, 1));
             ++buf->len;
 
             /* move head byte of payload to tail */
             *tail = *head;
             *head = comp_head_byte;
         }
40efb635
     }
 }
 
a75bb2e4
 
 static void
81d882d5
 lz4v2_compress(struct buffer *buf, struct buffer work,
                struct compress_context *compctx,
                const struct frame *frame)
a75bb2e4
 {
81d882d5
     bool compressed;
     if (buf->len <= 0)
     {
         return;
     }
a75bb2e4
 
81d882d5
     compressed = do_lz4_compress(buf, &work, compctx, frame);
a75bb2e4
 
81d882d5
     /* On Error just return */
     if (buf->len == 0)
     {
         return;
     }
a75bb2e4
 
81d882d5
     /* did compression save us anything?  Include 2 byte compression header
      * in calculation */
     if (compressed && work.len + 2 < buf->len)
a75bb2e4
     {
81d882d5
         ASSERT(buf_prepend(&work, 2));
         uint8_t *head = BPTR(&work);
         head[0] = COMP_ALGV2_INDICATOR_BYTE;
         head[1] = COMP_ALGV2_LZ4_BYTE;
         *buf = work;
a75bb2e4
     }
81d882d5
     else
a75bb2e4
     {
81d882d5
         compv2_escape_data_ifneeded(buf);
a75bb2e4
     }
 }
 
72bcdfdc
 static void
a75bb2e4
 do_lz4_decompress(size_t zlen_max,
81d882d5
                   struct buffer *work,
                   struct buffer *buf,
                   struct compress_context *compctx)
a75bb2e4
 {
81d882d5
     int uncomp_len;
     ASSERT(buf_safe(work, zlen_max));
     uncomp_len = LZ4_decompress_safe((const char *)BPTR(buf), (char *)BPTR(work), (size_t)BLEN(buf), zlen_max);
     if (uncomp_len <= 0)
a75bb2e4
     {
81d882d5
         dmsg(D_COMP_ERRORS, "LZ4 decompression error: %d", uncomp_len);
         buf->len = 0;
         return;
a75bb2e4
     }
 
81d882d5
     ASSERT(buf_safe(work, uncomp_len));
     work->len = uncomp_len;
a75bb2e4
 
81d882d5
     dmsg(D_COMP, "LZ4 decompress %d -> %d", buf->len, work->len);
     compctx->pre_decompress += buf->len;
     compctx->post_decompress += work->len;
a75bb2e4
 
81d882d5
     *buf = *work;
a75bb2e4
 }
 
40efb635
 static void
81d882d5
 lz4_decompress(struct buffer *buf, struct buffer work,
                struct compress_context *compctx,
                const struct frame *frame)
40efb635
 {
81d882d5
     size_t zlen_max = EXPANDED_SIZE(frame);
     uint8_t c;          /* flag indicating whether or not our peer compressed */
40efb635
 
81d882d5
     if (buf->len <= 0)
     {
         return;
     }
40efb635
 
81d882d5
     ASSERT(buf_init(&work, FRAME_HEADROOM(frame)));
40efb635
 
81d882d5
     /* do unframing/swap (assumes buf->len > 0) */
     {
         uint8_t *head = BPTR(buf);
         c = *head;
         --buf->len;
         *head = *BEND(buf);
     }
40efb635
 
81d882d5
     if (c == LZ4_COMPRESS_BYTE) /* packet was compressed */
40efb635
     {
81d882d5
         do_lz4_decompress(zlen_max, &work, buf, compctx);
40efb635
     }
81d882d5
     else if (c == NO_COMPRESS_BYTE_SWAP) /* packet was not compressed */
40efb635
     {
     }
81d882d5
     else
40efb635
     {
81d882d5
         dmsg(D_COMP_ERRORS, "Bad LZ4 decompression header byte: %d", c);
         buf->len = 0;
40efb635
     }
 }
 
a75bb2e4
 static void
81d882d5
 lz4v2_decompress(struct buffer *buf, struct buffer work,
                  struct compress_context *compctx,
                  const struct frame *frame)
a75bb2e4
 {
81d882d5
     size_t zlen_max = EXPANDED_SIZE(frame);
     uint8_t c;          /* flag indicating whether or not our peer compressed */
a75bb2e4
 
81d882d5
     if (buf->len <= 0)
     {
         return;
     }
a75bb2e4
 
81d882d5
     ASSERT(buf_init(&work, FRAME_HEADROOM(frame)));
a75bb2e4
 
81d882d5
     /* do unframing/swap (assumes buf->len > 0) */
     uint8_t *head = BPTR(buf);
     c = *head;
a75bb2e4
 
81d882d5
     /* Not compressed */
     if (c != COMP_ALGV2_INDICATOR_BYTE)
     {
         return;
     }
a75bb2e4
 
81d882d5
     /* Packet to short to make sense */
     if (buf->len <= 1)
a75bb2e4
     {
81d882d5
         buf->len = 0;
         return;
a75bb2e4
     }
 
81d882d5
     c = head[1];
     if (c == COMP_ALGV2_LZ4_BYTE) /* packet was compressed */
a75bb2e4
     {
81d882d5
         buf_advance(buf,2);
         do_lz4_decompress(zlen_max, &work, buf, compctx);
a75bb2e4
     }
81d882d5
     else if (c == COMP_ALGV2_UNCOMPRESSED_BYTE)
a75bb2e4
     {
81d882d5
         buf_advance(buf,2);
a75bb2e4
     }
81d882d5
     else
a75bb2e4
     {
81d882d5
         dmsg(D_COMP_ERRORS, "Bad LZ4v2 decompression header byte: %d", c);
         buf->len = 0;
a75bb2e4
     }
 }
 
40efb635
 const struct compress_alg lz4_alg = {
81d882d5
     "lz4",
     lz4_compress_init,
     lz4_compress_uninit,
     lz4_compress,
     lz4_decompress
40efb635
 };
 
a75bb2e4
 const struct compress_alg lz4v2_alg = {
81d882d5
     "lz4v2",
     lz4v2_compress_init,
     lz4_compress_uninit,
     lz4v2_compress,
     lz4v2_decompress
a75bb2e4
 };
 
81d882d5
 #else  /* if defined(ENABLE_LZ4) */
 static void
4cd4899e
 dummy(void)
 {
81d882d5
 }
40efb635
 #endif /* ENABLE_LZ4 */