ObjFW
Loading...
Searching...
No Matches
OFInflateStream.m
1/*
2 * Copyright (c) 2008-2024 Jonathan Schleifer <js@nil.im>
3 *
4 * All rights reserved.
5 *
6 * This file is part of ObjFW. It may be distributed under the terms of the
7 * Q Public License 1.0, which can be found in the file LICENSE.QPL included in
8 * the packaging of this file.
9 *
10 * Alternatively, it may be distributed under the terms of the GNU General
11 * Public License, either version 2 or 3, which can be found in the file
12 * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this
13 * file.
14 */
15
16#include "config.h"
17
18#include <stdlib.h>
19#include <string.h>
20
21#ifndef OF_INFLATE64_STREAM_M
22# import "OFInflateStream.h"
23#else
24# import "OFInflate64Stream.h"
25# define OFInflateStream OFInflate64Stream
26#endif
27#import "OFHuffmanTree.h"
28
29#import "OFInitializationFailedException.h"
30#import "OFInvalidFormatException.h"
31#import "OFNotOpenException.h"
32#import "OFOutOfMemoryException.h"
33
34#ifndef OF_INFLATE64_STREAM_M
35# define bufferSize OFInflateStreamBufferSize
36#else
37# define bufferSize OFInflate64StreamBufferSize
38#endif
39
40enum State {
41 stateBlockHeader,
42 stateUncompressedBlockHeader,
43 stateUncompressedBlock,
44 stateHuffmanTree,
45 stateHuffmanBlock
46};
47
48enum HuffmanState {
49 huffmanStateWriteValue,
50 huffmanStateAwaitCode,
51 huffmanStateAwaitLengthExtraBits,
52 huffmanStateAwaitDistance,
53 huffmanStateAwaitDistanceExtraBits,
54 huffmanStateProcessPair
55};
56
57#ifndef OF_INFLATE64_STREAM_M
58static const uint8_t numDistanceCodes = 30;
59static const uint8_t lengthCodes[29] = {
60 /* indices are -257, values -3 */
61 0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 16, 20, 24, 28, 32, 40, 48, 56,
62 64, 80, 96, 112, 128, 160, 192, 224, 255
63};
64static const uint8_t lengthExtraBits[29] = {
65 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4,
66 5, 5, 5, 5, 0
67};
68static const uint16_t distanceCodes[30] = {
69 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385,
70 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577
71};
72static const uint8_t distanceExtraBits[30] = {
73 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10,
74 10, 11, 11, 12, 12, 13, 13
75};
76#else
77static const uint8_t numDistanceCodes = 32;
78static const uint8_t lengthCodes[29] = {
79 /* indices are -257, values -3 */
80 0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 16, 20, 24, 28, 32, 40, 48, 56,
81 64, 80, 96, 112, 128, 160, 192, 224, 0
82};
83static const uint8_t lengthExtraBits[29] = {
84 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4,
85 5, 5, 5, 5, 16
86};
87static const uint16_t distanceCodes[32] = {
88 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385,
89 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577,
90 32769, 49153
91};
92static const uint8_t distanceExtraBits[32] = {
93 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10,
94 10, 11, 11, 12, 12, 13, 13, 14, 14
95};
96#endif
97static const uint8_t codeLengthsOrder[19] = {
98 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15
99};
100static OFHuffmanTree fixedLitLenTree, fixedDistTree;
101
102@implementation OFInflateStream
103@synthesize underlyingStream = _stream;
104
105static OF_INLINE bool
106tryReadBits(OFInflateStream *stream, uint16_t *bits, uint8_t count)
107{
108 uint16_t ret = stream->_savedBits;
109
110 OFAssert(stream->_savedBitsLength < count);
111
112 for (uint_fast8_t i = stream->_savedBitsLength; i < count; i++) {
113 if OF_UNLIKELY (stream->_bitIndex == 8) {
114 if OF_LIKELY (stream->_bufferIndex <
115 stream->_bufferLength)
116 stream->_byte =
117 stream->_buffer[stream->_bufferIndex++];
118 else {
119 size_t length = [stream->_stream
120 readIntoBuffer: stream->_buffer
121 length: bufferSize];
122
123 if OF_UNLIKELY (length < 1) {
124 stream->_savedBits = ret;
125 stream->_savedBitsLength = i;
126 return false;
127 }
128
129 stream->_byte = stream->_buffer[0];
130 stream->_bufferIndex = 1;
131 stream->_bufferLength = (uint16_t)length;
132 }
133
134 stream->_bitIndex = 0;
135 }
136
137 ret |= ((stream->_byte >> stream->_bitIndex++) & 1) << i;
138 }
139
140 stream->_savedBits = 0;
141 stream->_savedBitsLength = 0;
142 *bits = ret;
143
144 return true;
145}
146
147+ (void)initialize
148{
149 uint8_t lengths[288];
150
151 if (self != [OFInflateStream class])
152 return;
153
154 for (uint16_t i = 0; i <= 143; i++)
155 lengths[i] = 8;
156 for (uint16_t i = 144; i <= 255; i++)
157 lengths[i] = 9;
158 for (uint16_t i = 256; i <= 279; i++)
159 lengths[i] = 7;
160 for (uint16_t i = 280; i <= 287; i++)
161 lengths[i] = 8;
162
163 fixedLitLenTree = OFHuffmanTreeNew(lengths, 288);
164
165 for (uint16_t i = 0; i <= 31; i++)
166 lengths[i] = 5;
167
168 fixedDistTree = OFHuffmanTreeNew(lengths, 32);
169}
170
171+ (instancetype)streamWithStream: (OFStream *)stream
172{
173 return [[[self alloc] initWithStream: stream] autorelease];
174}
175
176- (instancetype)init
177{
178 OF_INVALID_INIT_METHOD
179}
180
181- (instancetype)initWithStream: (OFStream *)stream
182{
183 self = [super init];
184
185 @try {
186 _stream = [stream retain];
187
188 /* 0-7 address the bit, 8 means fetch next byte */
189 _bitIndex = 8;
190
191#ifdef OF_INFLATE64_STREAM_M
192 _slidingWindowMask = 0xFFFF;
193#else
194 _slidingWindowMask = 0x7FFF;
195#endif
196 _slidingWindow = OFAllocZeroedMemory(_slidingWindowMask + 1, 1);
197 } @catch (id e) {
198 [self release];
199 @throw e;
200 }
201
202 return self;
203}
204
205- (void)dealloc
206{
207 if (_stream != nil)
208 [self close];
209
210 OFFreeMemory(_slidingWindow);
211
212 if (_state == stateHuffmanTree) {
213 OFFreeMemory(_context.huffmanTree.lengths);
214
215 if (_context.huffmanTree.codeLenTree != NULL)
216 OFHuffmanTreeFree(_context.huffmanTree.codeLenTree);
217 }
218
219 if (_state == stateHuffmanTree || _state == stateHuffmanBlock) {
220 if (_context.huffman.litLenTree != fixedLitLenTree)
221 OFHuffmanTreeFree(_context.huffman.litLenTree);
222 if (_context.huffman.distTree != fixedDistTree)
223 OFHuffmanTreeFree(_context.huffman.distTree);
224 }
225
226 [super dealloc];
227}
228
229- (size_t)lowlevelReadIntoBuffer: (void *)buffer_
230 length: (size_t)length
231{
232 unsigned char *buffer = buffer_;
233 uint16_t bits = 0, tmp, value = 0;
234 size_t bytesWritten = 0;
235 unsigned char *slidingWindow;
236 uint16_t slidingWindowIndex;
237
238 if (_stream == nil)
240
241 if (_atEndOfStream)
242 return 0;
243
244start:
245 switch ((enum State)_state) {
246 case stateBlockHeader:
247 if OF_UNLIKELY (_inLastBlock) {
248 [_stream unreadFromBuffer: _buffer + _bufferIndex
249 length: _bufferLength -
250 _bufferIndex];
251 _bufferIndex = _bufferLength = 0;
252
253 _atEndOfStream = true;
254 return bytesWritten;
255 }
256
257 if OF_UNLIKELY (!tryReadBits(self, &bits, 3))
258 return bytesWritten;
259
260 _inLastBlock = (bits & 1);
261
262 switch (bits >> 1) {
263 case 0: /* No compression */
264 _state = stateUncompressedBlockHeader;
265 _bitIndex = 8;
266 _context.uncompressedHeader.position = 0;
267 memset(_context.uncompressedHeader.length, 0, 4);
268 break;
269 case 1: /* Fixed Huffman */
270 _state = stateHuffmanBlock;
271 _context.huffman.state = huffmanStateAwaitCode;
272 _context.huffman.litLenTree = fixedLitLenTree;
273 _context.huffman.distTree = fixedDistTree;
274 _context.huffman.treeIter = fixedLitLenTree;
275 break;
276 case 2: /* Dynamic Huffman */
277 _state = stateHuffmanTree;
278 _context.huffmanTree.lengths = NULL;
279 _context.huffmanTree.receivedCount = 0;
280 _context.huffmanTree.value = 0xFE;
281 _context.huffmanTree.litLenCodesCount = 0xFF;
282 _context.huffmanTree.distCodesCount = 0xFF;
283 _context.huffmanTree.codeLenCodesCount = 0xFF;
284 break;
285 default:
287 }
288
289 goto start;
290 case stateUncompressedBlockHeader:
291#define CTX _context.uncompressedHeader
292 /* FIXME: This can be done more efficiently than unreading */
293 [_stream unreadFromBuffer: _buffer + _bufferIndex
294 length: _bufferLength - _bufferIndex];
295 _bufferIndex = _bufferLength = 0;
296
297 CTX.position += [_stream
298 readIntoBuffer: CTX.length + CTX.position
299 length: 4 - CTX.position];
300
301 if OF_UNLIKELY (CTX.position < 4)
302 return bytesWritten;
303
304 if OF_UNLIKELY ((CTX.length[0] | (CTX.length[1] << 8)) !=
305 (uint16_t)~(CTX.length[2] | (CTX.length[3] << 8)))
306 @throw [OFInvalidFormatException exception];
307
308 _state = stateUncompressedBlock;
309
310 /*
311 * Do not reorder! _context.uncompressed.position and
312 * _context.uncompressedHeader.length overlap!
313 */
314 _context.uncompressed.length =
315 CTX.length[0] | (CTX.length[1] << 8);
316 _context.uncompressed.position = 0;
317
318 goto start;
319#undef CTX
320 case stateUncompressedBlock:
321#define CTX _context.uncompressed
322 if OF_UNLIKELY (length == 0)
323 return bytesWritten;
324
325 tmp = (length < (size_t)CTX.length - CTX.position
326 ? (uint16_t)length : CTX.length - CTX.position);
327
328 tmp = (uint16_t)[_stream readIntoBuffer: buffer + bytesWritten
329 length: tmp];
330
331 if OF_UNLIKELY (tmp == 0)
332 return bytesWritten;
333
334 slidingWindow = _slidingWindow;
335 slidingWindowIndex = _slidingWindowIndex;
336 for (uint_fast16_t i = 0; i < tmp; i++) {
337 slidingWindow[slidingWindowIndex] =
338 buffer[bytesWritten + i];
339 slidingWindowIndex = (slidingWindowIndex + 1) &
340 _slidingWindowMask;
341 }
342 _slidingWindowIndex = slidingWindowIndex;
343
344 length -= tmp;
345 bytesWritten += tmp;
346
347 CTX.position += tmp;
348 if OF_UNLIKELY (CTX.position == CTX.length)
349 _state = stateBlockHeader;
350
351 goto start;
352#undef CTX
353 case stateHuffmanTree:
354#define CTX _context.huffmanTree
355 if OF_LIKELY (CTX.value == 0xFE) {
356 if OF_LIKELY (CTX.litLenCodesCount == 0xFF) {
357 if OF_UNLIKELY (!tryReadBits(self, &bits, 5))
358 return bytesWritten;
359
360 if OF_UNLIKELY (bits > 29)
362 exception];
363
364 CTX.litLenCodesCount = bits;
365 }
366
367 if OF_LIKELY (CTX.distCodesCount == 0xFF) {
368 if OF_UNLIKELY (!tryReadBits(self, &bits, 5))
369 return bytesWritten;
370
371 CTX.distCodesCount = bits;
372 }
373
374 if OF_LIKELY (CTX.codeLenCodesCount == 0xFF) {
375 if OF_UNLIKELY (!tryReadBits(self, &bits, 4))
376 return bytesWritten;
377
378 CTX.codeLenCodesCount = bits;
379 }
380
381 if OF_LIKELY (CTX.lengths == NULL)
382 CTX.lengths = OFAllocZeroedMemory(19, 1);
383
384 for (uint16_t i = CTX.receivedCount;
385 i < CTX.codeLenCodesCount + 4; i++) {
386 if OF_UNLIKELY (!tryReadBits(self, &bits, 3)) {
387 CTX.receivedCount = i;
388 return bytesWritten;
389 }
390
391 CTX.lengths[codeLengthsOrder[i]] = bits;
392 }
393
394 CTX.codeLenTree = OFHuffmanTreeNew(CTX.lengths, 19);
395 CTX.treeIter = CTX.codeLenTree;
396
397 OFFreeMemory(CTX.lengths);
398 CTX.lengths = NULL;
399 CTX.receivedCount = 0;
400 CTX.value = 0xFF;
401 }
402
403 if OF_LIKELY (CTX.lengths == NULL)
404 CTX.lengths = OFAllocMemory(
405 CTX.litLenCodesCount + CTX.distCodesCount + 258, 1);
406
407 for (uint16_t i = CTX.receivedCount;
408 i < CTX.litLenCodesCount + CTX.distCodesCount + 258;) {
409 uint8_t j, count;
410
411 if OF_LIKELY (CTX.value == 0xFF) {
412 if OF_UNLIKELY (!OFHuffmanTreeWalk(self,
413 tryReadBits, &CTX.treeIter, &value)) {
414 CTX.receivedCount = i;
415 return bytesWritten;
416 }
417
418 CTX.treeIter = CTX.codeLenTree;
419
420 if (value < 16) {
421 CTX.lengths[i++] = value;
422 continue;
423 }
424 } else
425 value = CTX.value;
426
427 switch (value) {
428 case 16:
429 if OF_UNLIKELY (i < 1)
431 exception];
432
433 if OF_UNLIKELY (!tryReadBits(self, &bits, 2)) {
434 CTX.receivedCount = i;
435 CTX.value = value;
436 return bytesWritten;
437 }
438
439 value = CTX.lengths[i - 1];
440 count = bits + 3;
441
442 break;
443 case 17:
444 if OF_UNLIKELY (!tryReadBits(self, &bits, 3)) {
445 CTX.receivedCount = i;
446 CTX.value = value;
447 return bytesWritten;
448 }
449
450 value = 0;
451 count = bits + 3;
452
453 break;
454 case 18:
455 if OF_UNLIKELY (!tryReadBits(self, &bits, 7)) {
456 CTX.receivedCount = i;
457 CTX.value = value;
458 return bytesWritten;
459 }
460
461 value = 0;
462 count = bits + 11;
463
464 break;
465 default:
467 }
468
469 if OF_UNLIKELY (i + count >
470 CTX.litLenCodesCount + CTX.distCodesCount + 258)
472
473 for (j = 0; j < count; j++)
474 CTX.lengths[i++] = value;
475
476 CTX.value = 0xFF;
477 }
478
479 OFHuffmanTreeFree(CTX.codeLenTree);
480 CTX.codeLenTree = NULL;
481
482 CTX.litLenTree = OFHuffmanTreeNew(CTX.lengths,
483 CTX.litLenCodesCount + 257);
484 CTX.distTree = OFHuffmanTreeNew(
485 CTX.lengths + CTX.litLenCodesCount + 257,
486 CTX.distCodesCount + 1);
487
488 OFFreeMemory(CTX.lengths);
489
490 /*
491 * litLenTree and distTree are at the same location in
492 * _context.huffman and _context.huffmanTree, thus no need to
493 * set them.
494 */
495 _state = stateHuffmanBlock;
496 _context.huffman.state = huffmanStateAwaitCode;
497 _context.huffman.treeIter = CTX.litLenTree;
498
499 goto start;
500#undef CTX
501 case stateHuffmanBlock:
502#define CTX _context.huffman
503 for (;;) {
504 uint8_t extraBits, lengthCodeIndex;
505
506 if OF_UNLIKELY (CTX.state == huffmanStateWriteValue) {
507 if OF_UNLIKELY (length == 0)
508 return bytesWritten;
509
510 buffer[bytesWritten++] = CTX.value;
511 length--;
512
513 _slidingWindow[_slidingWindowIndex] = CTX.value;
514 _slidingWindowIndex =
515 (_slidingWindowIndex + 1) &
516 _slidingWindowMask;
517
518 CTX.state = huffmanStateAwaitCode;
519 CTX.treeIter = CTX.litLenTree;
520 }
521
522 if OF_UNLIKELY (CTX.state ==
523 huffmanStateAwaitLengthExtraBits) {
524 if OF_UNLIKELY (!tryReadBits(self, &bits,
525 CTX.extraBits))
526 return bytesWritten;
527
528 CTX.length += bits;
529
530 CTX.state = huffmanStateAwaitDistance;
531 CTX.treeIter = CTX.distTree;
532 }
533
534 /* Distance of length distance pair */
535 if (CTX.state == huffmanStateAwaitDistance) {
536 if OF_UNLIKELY (!OFHuffmanTreeWalk(self,
537 tryReadBits, &CTX.treeIter, &value))
538 return bytesWritten;
539
540 if OF_UNLIKELY (value >= numDistanceCodes)
542 exception];
543
544 CTX.distance = distanceCodes[value];
545 extraBits = distanceExtraBits[value];
546
547 if (extraBits > 0) {
548 if OF_UNLIKELY (!tryReadBits(self,
549 &bits, extraBits)) {
550#define HSADEB huffmanStateAwaitDistanceExtraBits
551 CTX.state = HSADEB;
552#undef HSADEB
553 CTX.extraBits = extraBits;
554 return bytesWritten;
555 }
556
557 CTX.distance += bits;
558 }
559
560 CTX.state = huffmanStateProcessPair;
561 } else if (CTX.state ==
562 huffmanStateAwaitDistanceExtraBits) {
563 if OF_UNLIKELY (!tryReadBits(self, &bits,
564 CTX.extraBits))
565 return bytesWritten;
566
567 CTX.distance += bits;
568
569 CTX.state = huffmanStateProcessPair;
570 }
571
572 /* Length distance pair */
573 if (CTX.state == huffmanStateProcessPair) {
574 for (uint_fast16_t j = 0; j < CTX.length; j++) {
575 uint16_t idx;
576
577 if OF_UNLIKELY (length == 0) {
578 CTX.length -= j;
579 return bytesWritten;
580 }
581
582 idx = (_slidingWindowIndex -
583 CTX.distance) & _slidingWindowMask;
584 value = _slidingWindow[idx];
585
586 buffer[bytesWritten++] = value;
587 length--;
588
589 _slidingWindow[_slidingWindowIndex] =
590 value;
591 _slidingWindowIndex =
592 (_slidingWindowIndex + 1) &
593 _slidingWindowMask;
594 }
595
596 CTX.state = huffmanStateAwaitCode;
597 CTX.treeIter = CTX.litLenTree;
598 }
599
600 if OF_UNLIKELY (!OFHuffmanTreeWalk(self, tryReadBits,
601 &CTX.treeIter, &value))
602 return bytesWritten;
603
604 /* End of block */
605 if OF_UNLIKELY (value == 256) {
606 if (CTX.litLenTree != fixedLitLenTree)
607 OFHuffmanTreeFree(CTX.litLenTree);
608 if (CTX.distTree != fixedDistTree)
609 OFHuffmanTreeFree(CTX.distTree);
610
611 _state = stateBlockHeader;
612 goto start;
613 }
614
615 /* Literal byte */
616 if OF_LIKELY (value < 256) {
617 if OF_UNLIKELY (length == 0) {
618 CTX.state = huffmanStateWriteValue;
619 CTX.value = value;
620 return bytesWritten;
621 }
622
623 buffer[bytesWritten++] = value;
624 length--;
625
626 _slidingWindow[_slidingWindowIndex] = value;
627 _slidingWindowIndex =
628 (_slidingWindowIndex + 1) &
629 _slidingWindowMask;
630
631 CTX.treeIter = CTX.litLenTree;
632 continue;
633 }
634
635 if OF_UNLIKELY (value > 285)
637
638 /* Length of length distance pair */
639 lengthCodeIndex = value - 257;
640 CTX.length = lengthCodes[lengthCodeIndex] + 3;
641 extraBits = lengthExtraBits[lengthCodeIndex];
642
643 if (extraBits > 0) {
644 if OF_UNLIKELY (!tryReadBits(self, &bits,
645 extraBits)) {
646 CTX.extraBits = extraBits;
647 CTX.state =
648 huffmanStateAwaitLengthExtraBits;
649 return bytesWritten;
650 }
651
652 CTX.length += bits;
653 }
654
655 CTX.treeIter = CTX.distTree;
656 CTX.state = huffmanStateAwaitDistance;
657 }
658
659 break;
660#undef CTX
661 }
662
663 OF_UNREACHABLE
664}
665
667{
668 if (_stream == nil)
670
671 return _atEndOfStream;
672}
673
675{
676 return ((id <OFReadyForReadingObserving>)_stream)
677 .fileDescriptorForReading;
678}
679
681{
682 return (super.hasDataInReadBuffer || _stream.hasDataInReadBuffer ||
683 _bufferLength - _bufferIndex > 0);
684}
685
686- (void)close
687{
688 if (_stream == nil)
690
691 /* Give back our buffer to the stream, in case it's shared */
692 [_stream unreadFromBuffer: _buffer + _bufferIndex
693 length: _bufferLength - _bufferIndex];
694 _bufferIndex = _bufferLength = 0;
695
696 [_stream release];
697 _stream = nil;
698
699 [super close];
700}
701@end
void * OFAllocMemory(size_t count, size_t size)
Allocates memory for the specified number of items of the specified size.
Definition OFObject.m:101
void OFFreeMemory(void *pointer)
Frees memory allocated by OFAllocMemory, OFAllocZeroedMemory or OFResizeMemory.
Definition OFObject.m:156
void * OFAllocZeroedMemory(size_t count, size_t size)
Allocates memory for the specified number of items of the specified size and initializes it with zero...
Definition OFObject.m:119
#define nil
A value representing no object.
Definition ObjFWRT.h:64
instancetype exception()
Creates a new, autoreleased exception.
Definition OFException.m:279
A class that handles Deflate decompression transparently for an underlying stream.
Definition OFInflateStream.h:35
OFStream * underlyingStream
The underlying stream of the inflate stream.
Definition OFInflateStream.h:80
An exception indicating that the format is invalid.
Definition OFInvalidFormatException.h:27
An exception indicating an object is not open, connected or bound.
Definition OFNotOpenException.h:26
instancetype exceptionWithObject:(id object)
Creates a new, autoreleased not open exception.
Definition OFNotOpenException.m:29
instancetype init()
Initializes an already allocated object.
Definition OFObject.m:586
void dealloc()
Deallocates the object.
Definition OFObject.m:1229
void initialize()
A method which is called the moment before the first call to the class is being made.
Definition OFObject.m:434
A base class for different types of streams.
Definition OFStream.h:188
size_t readIntoBuffer:length:(void *buffer,[length] size_t length)
Reads at most length bytes from the stream into a buffer.
Definition OFStream.m:127
bool lowlevelIsAtEndOfStream()
Returns whether the lowlevel is at the end of the stream.
Definition OFStream.m:99
void close()
Closes the stream.
Definition OFStream.m:1298
bool hasDataInReadBuffer
Whether data is present in the internal read buffer.
Definition OFStream.h:214
instancetype retain()
Increases the retain count.
int fileDescriptorForReading
The file descriptor for reading that should be checked by the OFKernelEventObserver.
Definition OFKernelEventObserver.h:89