diff --git a/pkg/compression/compression.go b/pkg/compression/compression.go index e8d8b07a..e98e3bb2 100644 --- a/pkg/compression/compression.go +++ b/pkg/compression/compression.go @@ -31,6 +31,7 @@ type Compressor interface { var ( LZMAGUID = *guid.MustParse("EE4E5898-3914-4259-9D6E-DC7BD79403CF") LZMAX86GUID = *guid.MustParse("D42AE6BD-1352-4BFB-909A-CA72A6EAE889") + ZLIBGUID = *guid.MustParse("CE3233F5-2CD6-4D87-9152-4A238BB6D1C4") ) // CompressorFromGUID returns a Compressor for the corresponding GUIDed Section. @@ -51,6 +52,8 @@ func CompressorFromGUID(guid *guid.GUID) Compressor { // into xz. It does not make much difference because // the x86 filter is not the bottleneck. return &LZMAX86{lzma} + case ZLIBGUID: + return &ZLIB{} } return nil } diff --git a/pkg/compression/compression_test.go b/pkg/compression/compression_test.go index 9e370a20..501f3e0d 100644 --- a/pkg/compression/compression_test.go +++ b/pkg/compression/compression_test.go @@ -48,6 +48,12 @@ var tests = []struct { decodedFilename: "testdata/random.bin", compressor: &LZMAX86{&SystemLZMA{"xz"}}, }, + { + name: "random data ZLIB", + encodedFilename: "testdata/random.bin.zlib", + decodedFilename: "testdata/random.bin", + compressor: &ZLIB{}, + }, } func TestEncodeDecode(t *testing.T) { @@ -122,6 +128,13 @@ func TestCompressorFromGUID(t *testing.T) { encodedFilename: "testdata/random.bin.lzma86", decodedFilename: "testdata/random.bin", }, + { + name: "zlib", + guid: &ZLIBGUID, + expected: &ZLIB{}, + encodedFilename: "testdata/random.bin.zlib", + decodedFilename: "testdata/random.bin", + }, } for _, tt := range compressors { t.Run(tt.name, func(t *testing.T) { diff --git a/pkg/compression/testdata/random.bin.zlib b/pkg/compression/testdata/random.bin.zlib new file mode 100644 index 00000000..eec95465 Binary files /dev/null and b/pkg/compression/testdata/random.bin.zlib differ diff --git a/pkg/compression/zlib.go b/pkg/compression/zlib.go new file mode 100644 index 00000000..9955e1c0 --- /dev/null +++ b/pkg/compression/zlib.go @@ -0,0 +1,83 @@ +// Copyright 2023 the LinuxBoot Authors. All rights reserved +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package compression + +import ( + "bytes" + "compress/zlib" + "encoding/binary" + "errors" + "io" +) + +const ( + zlibCompressionLevel = 9 + zlibSectionHeaderSize = 256 + zlibSizeOffset = 20 +) + +// ZLIB implements Compressor and uses the zlib package from the standard +// library +type ZLIB struct{} + +// Name returns the type of compression employed. +func (c *ZLIB) Name() string { + return "ZLIB" +} + +// Decode decodes a byte slice of ZLIB data. +func (c *ZLIB) Decode(encodedData []byte) ([]byte, error) { + if len(encodedData) < 256 { + return nil, errors.New("Zlib.Decode: missing section header") + } + + // Check size in ZLIB section header + size := binary.LittleEndian.Uint32( + encodedData[zlibSizeOffset : zlibSizeOffset+4], + ) + if size != uint32(len(encodedData)-zlibSectionHeaderSize) { + return nil, errors.New("ZLIB.Decode: size mismatch") + } + + // Remove section header + r, err := zlib.NewReader( + bytes.NewBuffer(encodedData[zlibSectionHeaderSize:]), + ) + if err != nil { + return nil, err + } + + decodedData, err := io.ReadAll(r) + r.Close() + if err != nil { + return nil, err + } + + return decodedData, nil +} + +// Encode encodes a byte slice with ZLIB. +func (c *ZLIB) Encode(decodedData []byte) ([]byte, error) { + var encodedData bytes.Buffer + + w, err := zlib.NewWriterLevel(&encodedData, zlibCompressionLevel) + if err != nil { + return nil, err + } + + _, err = w.Write(decodedData) + w.Close() + if err != nil { + return nil, err + } + + // Add ZLIB section header containing the compressed size and zero padding. + zlib_header := make([]byte, zlibSectionHeaderSize) + binary.LittleEndian.PutUint32( + zlib_header[zlibSizeOffset:], + uint32(len(encodedData.Bytes())), + ) + return append(zlib_header, encodedData.Bytes()[:]...), nil +}