@@ -17,11 +17,11 @@ // // It can buffer data as the predictor mandates 8-byte blocks with a header. // A call with no data will force a flush. func Compressor(writer io.Writer) func([]byte) error { var ctx context - ctx.input = ctx.buffer[:] + ctx.input = ctx.buffer[:0] // Forward declaration as it is required for recursion var write func(data []byte) error write = func(data []byte) error { @@ -31,18 +31,22 @@ bufferLength int = len(ctx.input) ) // Force a flush if we are called with no data to write if len(data) == 0 { + if len(ctx.input) == 0 { + return nil + } + data = ctx.input + // We can't have more than 7 bytes in the buffer so this is safe blockSize = len(ctx.input) goto write } // Check if there are pending bytes in the buffer - if bufferLength > 0 && bufferLength < 8 { - + if len(data) < blockSize || bufferLength > 0 { // Check whether we have enough bytes for a complete block if len(data) > 8-bufferLength { // Fill the buffer ... ctx.input = append(ctx.input, data[:8-bufferLength]...) // ... and recurse, calling ourselves with the full buffer @@ -70,11 +74,17 @@ } } write: var buf []byte = make([]byte, 1, blockSize+1) - for block := 0; block < len(data)/blockSize; block++ { + + var blocks int = len(data) / blockSize + if blocks == 0 { + blocks++ + } + + for block := 0; block < blocks; block++ { for i := 0; i < blockSize; i++ { var current byte = data[(block*blockSize)+i] if ctx.table[ctx.hash] == current { // Guess was right - don't output buf[0] |= 1 << uint(i) @@ -92,10 +102,19 @@ // Reset the flags and buffer for the next iteration buf[0] ^= buf[0] buf = buf[:1] } + + var remaining int = len(data) % blockSize + if remaining > 0 { + ctx.input = ctx.buffer[:remaining] + copy(ctx.input, data[len(data)-remaining:]) + } else { + ctx.input = ctx.buffer[:0] + } + return nil } return write } @@ -105,15 +124,15 @@ func (r reader) Read(output []byte) (int, error) { return r(output) } // TODO - document -func Decompressor(reader io.Reader) reader { +func Decompressor(wrapped io.Reader) io.Reader { var ctx context ctx.input = ctx.buffer[:0] - return func(output []byte) (int, error) { + return reader(func(output []byte) (int, error) { var ( err error flags byte readCount int ) @@ -128,34 +147,31 @@ readCount = copy(output, ctx.input) ctx.input = ctx.input[readCount:] return readCount, nil } - // // The buffer will shrink as it empties, restore it if it is needed - // if len(ctx.input) == 0 { - // ctx.input = ctx.buffer[:1] - // } + // This is single-iteration only but it is fine according to io.Reader's contract ?! + // TODO - read all bytes from a block based on the hamming weight of the flag + // and just shuffle them for predictions instead of bite-sized reads ;) // Read the flags - readCount, err = reader.Read(ctx.buffer[:1]) + ctx.input = ctx.buffer[:1] + readCount, err = wrapped.Read(ctx.input) if readCount == 0 || err != nil { return readCount, err } - // This is single-iteration only but it is fine according to io.Reader's contract ?! - // TODO - read all bytes from a block based on the hamming weight of the flag - // and just shuffle them for predictions instead of bite-sized reads ;) - - flags = ctx.buffer[0] + flags = ctx.input[0] + ctx.input = ctx.buffer[:8] var i uint = 0 for ; i < 8; i++ { if flags&(1< 0 { // Guess was right - ctx.buffer[i] = ctx.table[ctx.hash] + ctx.input[i] = ctx.table[ctx.hash] } else { - readCount, err = reader.Read(ctx.buffer[i:(i + 1)]) + readCount, err = wrapped.Read(ctx.input[i:(i + 1)]) if err == io.EOF { break } @@ -165,21 +181,23 @@ if readCount == 0 { // treat as EoF break } - ctx.table[ctx.hash] = ctx.buffer[i] + ctx.table[ctx.hash] = ctx.input[i] } - ctx.hash = (ctx.hash << 4) ^ uint16(ctx.buffer[i]) + ctx.hash = (ctx.hash << 4) ^ uint16(ctx.input[i]) } - readCount = copy(output, ctx.buffer[:i]) + readCount = copy(output, ctx.input[:i]) // Place any remaining bytes in the buffer if uint(readCount) < i { - ctx.input = ctx.buffer[readCount:i] + ctx.input = ctx.input[readCount:i] + } else { + ctx.input = ctx.buffer[:0] } return readCount, nil - } + }) }