Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
jgrahamc committed Nov 26, 2013
1 parent cca12d0 commit ca17983
Show file tree
Hide file tree
Showing 6 changed files with 1,242 additions and 2 deletions.
4 changes: 2 additions & 2 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Copyright (c) 2013, CloudFlare
Copyright (c) 2013 CloudFlare, Inc.
All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
Expand All @@ -11,7 +11,7 @@ are permitted provided that the following conditions are met:
list of conditions and the following disclaimer in the documentation and/or
other materials provided with the distribution.

* Neither the name of the {organization} nor the names of its
* Neither the name of the CloudFlare, Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.

Expand Down
14 changes: 14 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
GCFLAGS := -B
LDFLAGS :=

.PHONY: install
install:
@go install -v .

.PHONY: test
test:
@go test -gcflags='$(GCFLAGS)' -ldflags='$(LDFLAGS)' .

.PHONY: bench
bench:
@go test -gcflags='$(GCFLAGS)' -ldflags='$(LDFLAGS)' -bench .
57 changes: 57 additions & 0 deletions lz4.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Package lz4 implements compression using lz4.c
//
// Copyright (c) 2013 CloudFlare, Inc.

package lz4

// #cgo CFLAGS: -O3
// #include "src/lz4.h"
// #include "src/lz4.c"
import "C"

import (
"fmt"
"unsafe"
)

// p gets a char pointer to the first byte of a []byte slice
func p(in []byte) *C.char {
return (*C.char)(unsafe.Pointer(&in[0]))
}

// clen gets the length of a []byte slice as a char *
func clen(s []byte) C.int {
return C.int(len(s))
}

// Uncompress with a known output size. len(out) should be equal to
// the length of the uncompressed outout.
func Uncompress(in []byte, out []byte) (err error) {
read := int(C.LZ4_uncompress(p(in), p(out), clen(out)))

if read != len(in) {
err = fmt.Errorf("Uncompress read %d bytes should have read %d",
read, len(in))
}
return
}

// CompressBound calculates the size of the output buffer needed by
// Compress. This is based on the following macro:
//
// #define LZ4_COMPRESSBOUND(isize)
// ((unsigned int)(isize) > (unsigned int)LZ4_MAX_INPUT_SIZE ? 0 : (isize) + ((isize)/255) + 16)
func CompressBound(in []byte) int {
return len(in) + ((len(in)/255) + 16)
}

// Compress compresses in and puts the content in out. len(out)
// should have enough space for the compressed data (use CompressBound
// to calculate). Returns the number of bytes in the out slice.
func Compress(in []byte, out []byte) (outSize int, err error) {
outSize = int(C.LZ4_compress_limitedOutput(p(in), p(out), clen(in), clen(out)))
if outSize == 0 {
err = fmt.Errorf("Insufficient space for compression")
}
return
}
142 changes: 142 additions & 0 deletions lz4_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
// Package lz4 implements compression using lz4.c. This is its test
// suite.
//
// Copyright (c) 2013 CloudFlare, Inc.

package lz4

import (
"strings"
"testing"
)

func TestCompression(t *testing.T) {
input := []byte(strings.Repeat("Hello world, this is quite something", 10))
output := make([]byte, CompressBound(input))
outSize, err := Compress(input, output)
if err != nil {
t.Fatalf("Compression failed: %v", err)
}
if outSize == 0 {
t.Fatal("Output buffer is empty.")
}
output = output[:outSize]
decompressed := make([]byte, len(input))
err = Uncompress(output, decompressed)
if err != nil {
t.Fatalf("Decompression failed: %v", err)
}
if string(decompressed) != string(input) {
t.Fatalf("Decompressed output != input: %q != %q", decompressed, input)
}
}

func TestEmptyCompression(t *testing.T) {
input := []byte("")
output := make([]byte, CompressBound(input))
outSize, err := Compress(input, output)
if err != nil {
t.Fatalf("Compression failed: %v", err)
}
if outSize == 0 {
t.Fatal("Output buffer is empty.")
}
output = output[:outSize]
decompressed := make([]byte, len(input))
err = Uncompress(output, decompressed)
if err != nil {
t.Fatalf("Decompression failed: %v", err)
}
if string(decompressed) != string(input) {
t.Fatalf("Decompressed output != input: %q != %q", decompressed, input)
}
}

func TestNoCompression(t *testing.T) {
input := []byte("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
output := make([]byte, CompressBound(input))
outSize, err := Compress(input, output)
if err != nil {
t.Fatalf("Compression failed: %v", err)
}
if outSize == 0 {
t.Fatal("Output buffer is empty.")
}
output = output[:outSize]
decompressed := make([]byte, len(input))
err = Uncompress(output, decompressed)
if err != nil {
t.Fatalf("Decompression failed: %v", err)
}
if string(decompressed) != string(input) {
t.Fatalf("Decompressed output != input: %q != %q", decompressed, input)
}
}

func TestCompressionError(t *testing.T) {
input := []byte(strings.Repeat("Hello world, this is quite something", 10))
output := make([]byte, 1)
_, err := Compress(input, output)
if err == nil {
t.Fatalf("Compression should have failed but didn't")
}

output = make([]byte, 0)
_, err = Compress(input, output)
if err == nil {
t.Fatalf("Compression should have failed but didn't")
}
}

func TestDecompressionError(t *testing.T) {
input := []byte(strings.Repeat("Hello world, this is quite something", 10))
output := make([]byte, CompressBound(input))
outSize, err := Compress(input, output)
if err != nil {
t.Fatalf("Compression failed: %v", err)
}
if outSize == 0 {
t.Fatal("Output buffer is empty.")
}
output = output[:outSize]
decompressed := make([]byte, len(input)-1)
err = Uncompress(output, decompressed)
if err == nil {
t.Fatalf("Decompression should have failed")
}

decompressed = make([]byte, 1)
err = Uncompress(output, decompressed)
if err == nil {
t.Fatalf("Decompression should have failed")
}

decompressed = make([]byte, 0)
err = Uncompress(output, decompressed)
if err == nil {
t.Fatalf("Decompression should have failed")
}
}

func assert(t *testing.T, b bool) {
if !b {
t.Fatalf("assert failed")
}
}

func TestCompressBound(t *testing.T) {
input := make([]byte, 0)
assert(t, CompressBound(input) == 16)

input = make([]byte, 1)
assert(t, CompressBound(input) == 17)

input = make([]byte, 254)
assert(t, CompressBound(input) == 270)

input = make([]byte, 255)
assert(t, CompressBound(input) == 272)

input = make([]byte, 510)
assert(t, CompressBound(input) == 528)
}
Loading

0 comments on commit ca17983

Please sign in to comment.