/* * Copyright (c) 2008-2024 Jonathan Schleifer <js@nil.im> * * All rights reserved. * * This file is part of ObjFW. It may be distributed under the terms of the * Q Public License 1.0, which can be found in the file LICENSE.QPL included in * the packaging of this file. * * Alternatively, it may be distributed under the terms of the GNU General * Public License, either version 2 or 3, which can be found in the file * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this * file. */ #include "config.h" #import "OFLHADecompressingStream.h" #import "OFKernelEventObserver.h" #import "OFHuffmanTree.h" #import "OFInvalidFormatException.h" #import "OFNotOpenException.h" enum State { stateBlockHeader, stateCodeLenCodesCount, stateCodeLenTree, stateCodeLenTreeSingle, stateLitLenCodesCount, stateLitLenTree, stateLitLenTreeSingle, stateDistCodesCount, stateDistTree, stateDistTreeSingle, stateBlockLitLen, stateBlockDistLength, stateBlockDistLengthExtra, stateBlockLenDistPair }; @implementation OFLHADecompressingStream @synthesize bytesConsumed = _bytesConsumed; static OF_INLINE bool tryReadBits(OFLHADecompressingStream *stream, uint16_t *bits, uint8_t count) { uint16_t ret = stream->_savedBits; OFAssert(stream->_savedBitsLength < count); for (uint_fast8_t i = stream->_savedBitsLength; i < count; i++) { if OF_UNLIKELY (stream->_bitIndex == 8) { if OF_LIKELY (stream->_bufferIndex < stream->_bufferLength) stream->_byte = stream->_buffer[stream->_bufferIndex++]; else { const size_t bufferLength = OFLHADecompressingStreamBufferSize; size_t length = [stream->_stream readIntoBuffer: stream->_buffer length: bufferLength]; stream->_bytesConsumed += (uint32_t)length; if OF_UNLIKELY (length < 1) { stream->_savedBits = ret; stream->_savedBitsLength = i; return false; } stream->_byte = stream->_buffer[0]; stream->_bufferIndex = 1; stream->_bufferLength = (uint16_t)length; } stream->_bitIndex = 0; } ret = (ret << 1) | ((stream->_byte >> (7 - stream->_bitIndex++)) & 1); } stream->_savedBits = 0; stream->_savedBitsLength = 0; *bits = ret; return true; } - (instancetype)of_initWithStream: (OFStream *)stream distanceBits: (uint8_t)distanceBits dictionaryBits: (uint8_t)dictionaryBits { self = [super init]; @try { _stream = [stream retain]; /* 0-7 address the bit, 8 means fetch next byte */ _bitIndex = 8; _distanceBits = distanceBits; _dictionaryBits = dictionaryBits; _slidingWindowMask = (1u << dictionaryBits) - 1; _slidingWindow = OFAllocMemory(_slidingWindowMask + 1, 1); memset(_slidingWindow, ' ', _slidingWindowMask + 1); } @catch (id e) { [self release]; @throw e; } return self; } - (void)dealloc { if (_stream != nil) [self close]; OFFreeMemory(_slidingWindow); if (_codeLenTree != NULL) OFHuffmanTreeFree(_codeLenTree); if (_litLenTree != NULL) OFHuffmanTreeFree(_litLenTree); if (_distTree != NULL) OFHuffmanTreeFree(_distTree); OFFreeMemory(_codesLengths); [super dealloc]; } - (size_t)lowlevelReadIntoBuffer: (void *)buffer_ length: (size_t)length { unsigned char *buffer = buffer_; uint16_t bits = 0, value = 0; size_t bytesWritten = 0; if (_stream == nil) @throw [OFNotOpenException exceptionWithObject: self]; if (_stream.atEndOfStream && _bufferLength - _bufferIndex == 0 && _state == stateBlockHeader) return 0; start: switch ((enum State)_state) { case stateBlockHeader: if OF_UNLIKELY (!tryReadBits(self, &bits, 16)) return bytesWritten; _symbolsLeft = bits; _state = stateCodeLenCodesCount; goto start; case stateCodeLenCodesCount: if OF_UNLIKELY (!tryReadBits(self, &bits, 5)) return bytesWritten; if OF_UNLIKELY (bits > 20) @throw [OFInvalidFormatException exception]; if OF_UNLIKELY (bits == 0) { _state = stateCodeLenTreeSingle; goto start; } _codesCount = bits; _codesReceived = 0; _codesLengths = OFAllocZeroedMemory(bits, 1); _skip = true; _state = stateCodeLenTree; goto start; case stateCodeLenTree: while (_codesReceived < _codesCount) { if OF_UNLIKELY (_currentIsExtendedLength) { if OF_UNLIKELY (!tryReadBits(self, &bits, 1)) return bytesWritten; if OF_UNLIKELY (bits == 0) { _codesReceived++; _currentIsExtendedLength = false; continue; } _codesLengths[_codesReceived]++; continue; } if OF_UNLIKELY (_codesReceived == 3 && _skip) { if OF_UNLIKELY (!tryReadBits(self, &bits, 2)) return bytesWritten; if OF_UNLIKELY (_codesReceived + bits > _codesCount) @throw [OFInvalidFormatException exception]; for (uint_fast8_t j = 0; j < bits; j++) _codesLengths[_codesReceived++] = 0; _skip = false; continue; } if OF_UNLIKELY (!tryReadBits(self, &bits, 3)) return bytesWritten; _codesLengths[_codesReceived] = bits; if OF_UNLIKELY (bits == 7) { _currentIsExtendedLength = true; continue; } else _codesReceived++; } _codeLenTree = OFHuffmanTreeNew(_codesLengths, _codesCount); OFFreeMemory(_codesLengths); _codesLengths = NULL; _state = stateLitLenCodesCount; goto start; case stateCodeLenTreeSingle: if OF_UNLIKELY (!tryReadBits(self, &bits, 5)) return bytesWritten; _codeLenTree = OFHuffmanTreeNewSingle(bits); _state = stateLitLenCodesCount; goto start; case stateLitLenCodesCount: if OF_UNLIKELY (!tryReadBits(self, &bits, 9)) return bytesWritten; if OF_UNLIKELY (bits > 510) @throw [OFInvalidFormatException exception]; if OF_UNLIKELY (bits == 0) { OFHuffmanTreeFree(_codeLenTree); _codeLenTree = NULL; _state = stateLitLenTreeSingle; goto start; } _codesCount = bits; _codesReceived = 0; _codesLengths = OFAllocZeroedMemory(bits, 1); _skip = false; _treeIter = _codeLenTree; _state = stateLitLenTree; goto start; case stateLitLenTree: while (_codesReceived < _codesCount) { if OF_UNLIKELY (_skip) { uint16_t skipCount; switch (_codesLengths[_codesReceived]) { case 0: skipCount = 1; break; case 1: if OF_UNLIKELY (!tryReadBits(self, &bits, 4)) return bytesWritten; skipCount = bits + 3; break; case 2: if OF_UNLIKELY (!tryReadBits(self, &bits, 9)) return bytesWritten; skipCount = bits + 20; break; default: OFEnsure(0); } if OF_UNLIKELY (_codesReceived + skipCount > _codesCount) @throw [OFInvalidFormatException exception]; for (uint_fast16_t j = 0; j < skipCount; j++) _codesLengths[_codesReceived++] = 0; _skip = false; continue; } if (!OFHuffmanTreeWalk(self, tryReadBits, &_treeIter, &value)) return bytesWritten; _treeIter = _codeLenTree; if (value < 3) { _codesLengths[_codesReceived] = value; _skip = true; } else _codesLengths[_codesReceived++] = value - 2; } _litLenTree = OFHuffmanTreeNew(_codesLengths, _codesCount); OFFreeMemory(_codesLengths); _codesLengths = NULL; OFHuffmanTreeFree(_codeLenTree); _codeLenTree = NULL; _state = stateDistCodesCount; goto start; case stateLitLenTreeSingle: if OF_UNLIKELY (!tryReadBits(self, &bits, 9)) return bytesWritten; _litLenTree = OFHuffmanTreeNewSingle(bits); _state = stateDistCodesCount; goto start; case stateDistCodesCount: if OF_UNLIKELY (!tryReadBits(self, &bits, _distanceBits)) return bytesWritten; if OF_UNLIKELY (bits > _dictionaryBits) @throw [OFInvalidFormatException exception]; if OF_UNLIKELY (bits == 0) { _state = stateDistTreeSingle; goto start; } _codesCount = bits; _codesReceived = 0; _codesLengths = OFAllocZeroedMemory(bits, 1); _treeIter = _codeLenTree; _state = stateDistTree; goto start; case stateDistTree: while (_codesReceived < _codesCount) { if OF_UNLIKELY (_currentIsExtendedLength) { if OF_UNLIKELY (!tryReadBits(self, &bits, 1)) return bytesWritten; if OF_UNLIKELY (bits == 0) { _codesReceived++; _currentIsExtendedLength = false; continue; } _codesLengths[_codesReceived]++; continue; } if OF_UNLIKELY (!tryReadBits(self, &bits, 3)) return bytesWritten; _codesLengths[_codesReceived] = bits; if OF_UNLIKELY (bits == 7) { _currentIsExtendedLength = true; continue; } else _codesReceived++; } _distTree = OFHuffmanTreeNew(_codesLengths, _codesCount); OFFreeMemory(_codesLengths); _codesLengths = NULL; _treeIter = _litLenTree; _state = stateBlockLitLen; goto start; case stateDistTreeSingle: if OF_UNLIKELY (!tryReadBits(self, &bits, _distanceBits)) return bytesWritten; _distTree = OFHuffmanTreeNewSingle(bits); _treeIter = _litLenTree; _state = stateBlockLitLen; goto start; case stateBlockLitLen: if OF_UNLIKELY (_symbolsLeft == 0) { OFHuffmanTreeFree(_litLenTree); OFHuffmanTreeFree(_distTree); _litLenTree = _distTree = NULL; _state = stateBlockHeader; /* * We must return here, as there is no indication * whether this was the last block. Whoever called this * method needs to check if everything has been read * already and only call read again if that is not the * case. * * We must also unread the buffer, in case this was the * last block and something else follows, e.g. another * LHA header. */ [_stream unreadFromBuffer: _buffer + _bufferIndex length: _bufferLength - _bufferIndex]; _bytesConsumed -= _bufferLength - _bufferIndex; _bufferIndex = _bufferLength = 0; return bytesWritten; } if OF_UNLIKELY (length == 0) return bytesWritten; if OF_UNLIKELY (!OFHuffmanTreeWalk(self, tryReadBits, &_treeIter, &value)) return bytesWritten; if OF_LIKELY (value < 256) { buffer[bytesWritten++] = value; length--; _slidingWindow[_slidingWindowIndex] = value; _slidingWindowIndex = (_slidingWindowIndex + 1) & _slidingWindowMask; _symbolsLeft--; _treeIter = _litLenTree; } else { _length = value - 253; _treeIter = _distTree; _state = stateBlockDistLength; } goto start; case stateBlockDistLength: if OF_UNLIKELY (!OFHuffmanTreeWalk(self, tryReadBits, &_treeIter, &value)) return bytesWritten; _distance = value; _state = (value < 2 ? stateBlockLenDistPair : stateBlockDistLengthExtra); goto start; case stateBlockDistLengthExtra: if OF_UNLIKELY (!tryReadBits(self, &bits, _distance - 1)) return bytesWritten; _distance = bits + (1u << (_distance - 1)); _state = stateBlockLenDistPair; goto start; case stateBlockLenDistPair: for (uint_fast16_t i = 0; i < _length; i++) { uint32_t idx; if OF_UNLIKELY (length == 0) { _length -= i; return bytesWritten; } idx = (_slidingWindowIndex - _distance - 1) & _slidingWindowMask; value = _slidingWindow[idx]; buffer[bytesWritten++] = value; length--; _slidingWindow[_slidingWindowIndex] = value; _slidingWindowIndex = (_slidingWindowIndex + 1) & _slidingWindowMask; } _symbolsLeft--; _treeIter = _litLenTree; _state = stateBlockLitLen; goto start; } OF_UNREACHABLE } - (bool)lowlevelIsAtEndOfStream { if (_stream == nil) @throw [OFNotOpenException exceptionWithObject: self]; return (_stream.atEndOfStream && _bufferLength - _bufferIndex == 0 && _state == stateBlockHeader); } - (int)fileDescriptorForReading { return ((id <OFReadyForReadingObserving>)_stream) .fileDescriptorForReading; } - (bool)hasDataInReadBuffer { return (super.hasDataInReadBuffer || _stream.hasDataInReadBuffer || _bufferLength - _bufferIndex > 0); } - (void)close { if (_stream == nil) @throw [OFNotOpenException exceptionWithObject: self]; /* Give back our buffer to the stream, in case it's shared */ [_stream unreadFromBuffer: _buffer + _bufferIndex length: _bufferLength - _bufferIndex]; _bytesConsumed -= _bufferLength - _bufferIndex; _bufferIndex = _bufferLength = 0; [_stream release]; _stream = nil; [super close]; } @end