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