github.com/Schaudge/grailbase@v0.0.0-20240223061707-44c758a471c0/compress/libdeflate/libdeflate_cgo.go (about)

     1  // Copyright 2018 GRAIL, Inc.  All rights reserved.
     2  // Use of this source code is governed by the Apache-2.0
     3  // license that can be found in the LICENSE file.
     4  
     5  //go:build cgo || !arm64
     6  // +build cgo !arm64
     7  
     8  package libdeflate
     9  
    10  /*
    11  #include "libdeflate.h"
    12  */
    13  import "C"
    14  
    15  import (
    16  	"fmt"
    17  	"unsafe"
    18  )
    19  
    20  // Decompressor is a minimal interface to libdeflate's decompressor object.  It
    21  // allocates a C memory area, so users are responsible for calling
    22  // Decompressor.Cleanup() when done to avoid memory leaks.  Multiple
    23  // Decompressors can be active at once.
    24  //
    25  // The Reader interface is not directly implemented here; instead, it is
    26  // assumed that the user is decompressing a multi-block BGZF-like format, and
    27  // they're fine with manual calls to Decompress().
    28  type Decompressor struct {
    29  	cobj *C.struct_libdeflate_decompressor
    30  }
    31  
    32  // Init allocates workspace needed by Decompress().
    33  func (dd *Decompressor) Init() error {
    34  	if dd.cobj == nil {
    35  		dd.cobj = C.libdeflate_alloc_decompressor()
    36  		if dd.cobj == nil {
    37  			return fmt.Errorf("libdeflate: failed to allocate decompressor")
    38  		}
    39  	}
    40  	return nil
    41  }
    42  
    43  // Decompress performs raw DEFLATE decompression on a byte slice.  outData[]
    44  // must be large enough to fit the decompressed data.  Byte count of the
    45  // decompressed data is returned on success (it may be smaller than
    46  // len(outData)).
    47  func (dd *Decompressor) Decompress(outData, inData []byte) (int, error) {
    48  	// Tolerate zero-length blocks on input, even though we don't on output.
    49  	// (Can be relevant when a BGZF file was formed by raw byte concatenation of
    50  	// smaller BGZF files.)
    51  	// Note that we can't use the usual *reflect.SliceHeader replacement for
    52  	// unsafe.Pointer(&inData[0]): that produces a "cgo argument has Go pointer
    53  	// to Go pointer" compile error.
    54  	if len(inData) == 0 {
    55  		return 0, nil
    56  	}
    57  	var outLen C.size_t
    58  	errcode := C.libdeflate_deflate_decompress(
    59  		dd.cobj, unsafe.Pointer(&inData[0]), C.size_t(len(inData)),
    60  		unsafe.Pointer(&outData[0]), C.size_t(len(outData)), &outLen)
    61  	if errcode != C.LIBDEFLATE_SUCCESS {
    62  		return 0, fmt.Errorf("libdeflate: libdeflate_deflate_decompress() error code %d", errcode)
    63  	}
    64  	return int(outLen), nil
    65  }
    66  
    67  // GzipDecompress performs gzip decompression on a byte slice.  outData[] must
    68  // be large enough to fit the decompressed data.  Byte count of the
    69  // decompressed data is returned on success (it may be smaller than
    70  // len(outData)).
    71  func (dd *Decompressor) GzipDecompress(outData, inData []byte) (int, error) {
    72  	if len(inData) == 0 {
    73  		return 0, nil
    74  	}
    75  	var outLen C.size_t
    76  	errcode := C.libdeflate_gzip_decompress(
    77  		dd.cobj, unsafe.Pointer(&inData[0]), C.size_t(len(inData)),
    78  		unsafe.Pointer(&outData[0]), C.size_t(len(outData)), &outLen)
    79  	if errcode != C.LIBDEFLATE_SUCCESS {
    80  		return 0, fmt.Errorf("libdeflate: libdeflate_gzip_decompress() error code %d", errcode)
    81  	}
    82  	return int(outLen), nil
    83  }
    84  
    85  // Cleanup frees workspace memory.
    86  func (dd *Decompressor) Cleanup() {
    87  	if dd.cobj != nil {
    88  		C.libdeflate_free_decompressor(dd.cobj)
    89  		dd.cobj = nil
    90  	}
    91  }
    92  
    93  // Compressor is a minimal interface to libdeflate's compressor object.
    94  type Compressor struct {
    95  	cobj *C.struct_libdeflate_compressor
    96  }
    97  
    98  // Init allocates workspace needed by Compress().
    99  func (cc *Compressor) Init(compressionLevel int) error {
   100  	if cc.cobj == nil {
   101  		if compressionLevel == -1 {
   102  			compressionLevel = 5
   103  		}
   104  		cc.cobj = C.libdeflate_alloc_compressor(C.int(compressionLevel))
   105  		if cc.cobj == nil {
   106  			return fmt.Errorf("libdeflate: failed to allocate compressor")
   107  		}
   108  	}
   109  	return nil
   110  }
   111  
   112  // Compress performs raw DEFLATE compression on a byte slice.  outData[] must
   113  // be large enough to fit the compressed data.  Byte count of the compressed
   114  // data is returned on success.
   115  // Zero is currently returned on failure.  A side effect is that inData cannot
   116  // be length zero; this function will panic or crash if it is.
   117  func (cc *Compressor) Compress(outData, inData []byte) int {
   118  	// We *want* to crash on length-zero (that implies an error in the calling
   119  	// code, we don't want to be writing zero-length BGZF blocks without knowing
   120  	// about it), so we intentionally exclude the len(inData) == 0 check.
   121  	outLen := int(C.libdeflate_deflate_compress(
   122  		cc.cobj, unsafe.Pointer(&inData[0]), C.size_t(len(inData)),
   123  		unsafe.Pointer(&outData[0]), C.size_t(len(outData))))
   124  	return outLen
   125  }
   126  
   127  // Cleanup frees workspace memory.
   128  func (cc *Compressor) Cleanup() {
   129  	if cc.cobj != nil {
   130  		C.libdeflate_free_compressor(cc.cobj)
   131  		cc.cobj = nil
   132  	}
   133  }