jaulib v1.4.0-2-g788cf73
Jau Support Library (C++, Java, ..)
Loading...
Searching...
No Matches
bit_stream.hpp
Go to the documentation of this file.
1/*
2 * Author: Sven Gothel <sgothel@jausoft.com>
3 * Copyright (c) 2021-2025 Gothel Software e.K.
4 *
5 * SPDX-License-Identifier: MIT
6 *
7 * This Source Code Form is subject to the terms of the MIT License
8 * If a copy of the MIT was not distributed with this file,
9 * you can obtain one at https://opensource.org/license/mit/.
10 */
11
12#ifndef JAU_IO_BIT_STREAM_HPP_
13#define JAU_IO_BIT_STREAM_HPP_
14
15#include <unistd.h>
16#include <climits>
17#include <cmath>
18#include <cstdint>
19#include <limits>
20#include <memory>
21
22#include <jau/basic_types.hpp>
23#include <jau/byte_util.hpp>
24#include <jau/cpp_lang_util.hpp>
25#include <jau/debug.hpp>
26#include <jau/int_types.hpp>
28#include <jau/io/io_util.hpp>
29#include <jau/string_util.hpp>
30
31namespace jau::io {
32
33 using namespace jau::enums;
34
35 /** \addtogroup IOUtils
36 *
37 * @{
38 */
39
40 /** I/O read or write access. */
41 enum class ioaccess_t : bool {
42 /** Read intent */
43 read = false,
44 /** Write intend */
45 write = true
46 };
47 /**
48 * Return std::string representation of the given ioaccess_t.
49 * @param v the ioaccess_t value
50 * @return the std::string representation
51 */
52 std::string to_string(const ioaccess_t v) noexcept;
53
54 /**
55 * Versatile Bitstream implementation supporting:
56 * - Utilize I/O operations on I/O streams, buffers and arrays
57 * - Uses least-significant-bit (lsb) first addressing and order for bit-operations
58 * - Linear bit R/W operations
59 * - Bulk 64-bit R/W bit-operations
60 * - Bulk data-type operations w/ endian conversion
61 * - Allow mark/reset and switching streams and input/output mode
62 * - Optimized bulk-operations
63 */
64 class Bitstream {
65 private:
66 // private static final boolean DEBUG = Debug.debug("Bitstream");
67 constexpr static jau::nsize_t ncount = std::numeric_limits<jau::nsize_t>::max();
68 typedef uint64_t data_type; ///< uint64_t data type, bit buffer
69
70 public:
71 using size_type = jau::io::ByteStream::size_type; ///< uint64_t size data type, bit position and count
72
73 /** Invalid position constant, denoting unset mark() or invalid position. Value: `std::numeric_limits<size_type>::max()` */
75
76 /** Maximum read bitCacheSizeRead() and fixed 64-bit write cache size. */
77 constexpr static jau::nsize_t MaxBitCacheSize = sizeof(data_type)*CHAR_BIT;
78
79 private:
80 constexpr static size_type data_shift = log2_byteshift(sizeof(data_type));
81 constexpr static size_type byte_shift = log2_byteshift(sizeof(uint8_t));
82
83 constexpr static bool useFastPathStream = true;
84 constexpr static bool useFastPathTypes = true;
85
86 std::unique_ptr<ByteStream> m_bytes;
87
88 /** 64-bit cache of byte stream */
89 data_type m_bitCache;
90 data_type m_bitsDataMark;
91
92 jau::nsize_t m_bitCacheSizeRead;
93 /** See bitCount, ranges [0..63]. */
94 jau::nsize_t m_bitCount;
95 jau::nsize_t m_bitsCountMark;
96
97 ioaccess_t m_access;
98
99 public:
100 /**
101 * @param stream input and/or output stream
102 * @param access ioaccess_t::read for read-access and ioaccess_t::write for read-access
103 * @throws IllegalArgumentError if requested `writeMode` doesn't match stream's ByteStream::canRead() and ByteStream::canWrite().
104 */
105 Bitstream(std::unique_ptr<ByteStream> && stream, ioaccess_t access)
106 : m_bytes(std::move(stream)), m_access(access) {
107 resetLocal();
108 validateMode1(access);
109 }
110
111 private:
112 void resetLocal() {
113 m_bitCache = 0;
114 m_bitCacheSizeRead = 0;
115 m_bitCount = 0;
116 m_bitsDataMark = 0;
117 m_bitsCountMark = ncount;
118 }
119 bool canRead0() const noexcept { return m_bytes->canRead(); }
120 bool canWrite0() const noexcept { return m_bytes->canWrite(); }
121 void validateMode1(ioaccess_t access) const {
122 if( !canRead0() && !canWrite0() ) {
123 throw jau::IllegalArgumentError("stream can neither input nor output: "+toStringImpl(), E_FILE_LINE);
124 }
125 if( ioaccess_t::write == access && !canWrite0() ) {
126 throw jau::IllegalArgumentError("stream cannot output as requested: "+toStringImpl(), E_FILE_LINE);
127 }
128 if( ioaccess_t::read == access && !canRead0() ) {
129 throw jau::IllegalArgumentError("stream cannot input as requested: "+toStringImpl(), E_FILE_LINE);
130 }
131 }
132 bool validateMode2(ioaccess_t access) const {
133 if( !canRead0() && !canWrite0() ) {
134 return false;
135 }
136 if( ioaccess_t::write == access && !canWrite0() ) {
137 return false;
138 }
139 if( ioaccess_t::read == access && !canRead0() ) {
140 return false;
141 }
142 return true;
143 }
144 [[nodiscard]] bool writeCache() noexcept {
145 const size_t s = (m_bitCount + 7) >> byte_shift;
146 size_t w;
147 if( s > 0 ) {
148 m_bitCache &= jau::bit_mask<data_type>(m_bitCount);
149 w = m_bytes->write(&m_bitCache, s); // LSB
150 } else {
151 w = 0;
152 }
153 if( s == w ) {
154 m_bitCount = 0;
155 m_bitCache = 0;
156 m_bitCacheSizeRead = 0;
157 return true;
158 } else {
159 return false;
160 }
161 }
162 void fillCache() noexcept {
163 m_bitCache = 0;
164 m_bitCacheSizeRead = m_bytes->read(&m_bitCache, sizeof(data_type)) << byte_shift;
165 }
166
167 public:
168 /** Returns the used underlying ByteStream. */
169 ByteStream& byteStream() { return *m_bytes; }
170
171 constexpr_cxx23 iomode_t mode() const noexcept { return m_bytes->mode(); }
172
173 /**
174 * Changes the access-mode to write or read and resets position and cache to zero.
175 *
176 * If the previous stream was in ioaccess_t::write mode, flush() is being called.
177 *
178 * Certain ByteStream implementations may not allow random rewinding of the stream,
179 *
180 * @param writeMode new access-mode
181 * @returns false if requested `access` doesn't match stream's ByteStream::canRead() and
182 * ByteStream::canWrite() - or flush() failed, otherwise true
183 */
184 [[nodiscard]] bool setAccess(ioaccess_t access) noexcept {
185 if( !validateMode2(access) ) {
186 return false;
187 }
188 if( canWrite() && npos == flush() ) {
189 return false;
190 }
191 m_access = access;
192 if( 0 != m_bytes->seek(0) ) {
193 return false;
194 }
195 resetLocal();
196 return true;
197 }
198
199 /**
200 * Changes the write-mode to read, sets the underlying ByteStream to read-only and resets position and cache to zero.
201 *
202 * If the previous stream was in ioaccess_t::write mode, flush() is being called.
203 *
204 * Certain ByteStream implementations may not allow random rewinding of the stream,
205 *
206 * @returns false if requested ioaccess_t::read doesn't match stream's ByteStream::canRead() - or flush() failed, otherwise true
207 */
208 [[nodiscard]] bool setImmutable() noexcept {
209 if( canWrite() ) {
210 if( !validateMode2(ioaccess_t::read) ) {
211 return false;
212 }
213 if( npos == flush() ) {
214 return false;
215 }
216 m_access = ioaccess_t::read;
217 }
218 m_bytes->setImmutable();
219 if( 0 != m_bytes->seek(0) ) {
220 return false;
221 }
222 resetLocal();
223 return true;
224 }
225
226 /**
227 * Returns endian byte-order of stream storage.
228 *
229 * Only affects multi-byte r/w operations, e.g. readU16(), writeU16(), etc.
230 */
231 constexpr_cxx23 lb_endian_t byteOrder() const noexcept { return m_bytes->byteOrder(); }
232
233 /** Returns true in case stream is in write mode, false if in read mode. */
234 constexpr bool canWrite() const noexcept { return ioaccess_t::write == m_access; }
235 /** Returns ioaccess_t stream mode. */
236 constexpr ioaccess_t ioaccess() const noexcept { return m_access; }
237
238 /**
239 * Closing the underlying stream, implies {@link #flush()}.
240 * <p>
241 * Implementation will <code>null</code> the stream references,
242 * hence {@link #setStream(Object)} must be called before re-using instance.
243 * </p>
244 * <p>
245 * If the closed stream was in {@link #writeMode() output mode},
246 * {@link #flush()} is being called.
247 * </p>
248 *
249 * @throws IOException
250 */
251 void close() noexcept {
252 flush();
253 m_bytes->close();
254 resetLocal();
255 }
256
257 /**
258 * Synchronizes underlying ByteStream output stream operations in writeMode(), or does nothing.
259 *
260 * Method also flushes incomplete bytes to the underlying ByteStream
261 * and hence skips to the next byte position.
262 *
263 * @return ::npos caused by writing failure, otherwise one if pending bit-buffer was written or zero for none.
264 */
265 size_type flush() noexcept {
266 if( !canWrite() ) {
267 return 0;
268 }
269 size_type c = 0;
270 if( 0 != m_bitCount ) {
271 if( !writeCache() ) {
272 return npos;
273 }
274 c = 1;
275 }
276 m_bytes->flush();
277 return c;
278 }
279
280 /**
281 * Set `markpos` to current bit-position, allowing the stream to be seekMark().
282 *
283 * seek() will clear `markpos` if > newPos.
284 *
285 * For implementations where seek() doesn't allow random rewinding of the stream,
286 * setMark() will allow rewinding back to `markpos` if not exceeding `readLimit`.
287 *
288 * @param readlimit maximum number of bytes able to read before invalidating the `markpos`.
289 * @return true if marks is set successfully, otherwise false
290 */
291 [[nodiscard]] bool setMark(size_type readLimit) noexcept {
292 if(!m_bytes->setMark(readLimit) ) {
293 return false;
294 }
295 m_bitsDataMark = m_bitCache;
296 m_bitsCountMark = m_bitCount;
297 return true;
298 }
299
300 /** Returns the `markpos` set via setMark() or ByteStream::npos if unset. */
301 size_type mark() const noexcept { return m_bytes->mark(); }
302
303 /** Returns the `readLimit` set via setMark(). If unset either 0 or implicit limit. */
304 uint64_t markReadLimit() const noexcept { return m_bytes->markReadLimit(); }
305
306 /**
307 * Seeks stream bit-position to `markpos` as set via setMark().
308 *
309 * `markpos` is kept, hence seekMark() can be called multiple times.
310 *
311 * @return true if successful (incl eofbit),
312 * otherwise false with unchanged position due to I/O failure (iostate::fail set) or setMark() not set.
313 */
314 [[nodiscard]] bool seekMark() noexcept {
315 if( ncount == m_bitsCountMark || !m_bytes->seekMark() ) {
316 return false;
317 }
318 m_bitCache = m_bitsDataMark;
319 m_bitCount = m_bitsCountMark;
320 return true;
321 }
322
323 /**
324 * Returns filled read bit-cache-size, i.e. up to 64-bit MaxBitCacheSize reading.
325 * @see MaxBitCacheSize
326 * @see cachedBitCount()
327 * @see position()
328 */
329 constexpr jau::nsize_t bitCacheSizeRead() const noexcept { return m_bitCacheSizeRead; }
330
331 /**
332 * Returns number of cached bits.
333 *
334 * - Read operation
335 * - Number of bits cached before next up-to 64-bit-read when zero is reached, flipping over to bitCacheSizeRead()
336 * - Counting down
337 * - Range (bitCacheSizeRead()..0]
338 * - bitCacheSizeRead() is set when zero is reached and cache is filled
339 *
340 * - Write operation
341 * - Number of bits cached before next 64-bit MaxBitCacheSize write when 64-bits limit is reached, flipping over to 0
342 * - Counting up
343 * - Range [0..MaxBitCacheSize)
344 * - bitCacheSizeRead() is set to cachedBitCount() after writing
345 *
346 * @see MaxBitCacheSize
347 * @see bitCacheSizeRead()
348 * @see bitCache()
349 * @see position()
350 */
351 constexpr jau::nsize_t cachedBitCount() const noexcept { return m_bitCount; }
352
353 /**
354 * Return the next cached bit position.
355 * @see MaxBitCacheSize
356 * @see bitCacheSizeRead()
357 * @see cachedBitCount()
358 */
359 constexpr jau::nsize_t cachedBitPos() const noexcept {
360 if ( canWrite() ) {
361 return m_bitCount;
362 } else {
363 return m_bitCacheSizeRead - m_bitCount;
364 }
365 }
366
367 /**
368 * Returns the 64-bit MaxBitCacheSize cache buffer value.
369 * @see MaxBitCacheSize
370 * @see bitCacheSizeRead()
371 * @see cachedBitCount()
372 * @see position()
373 */
374 data_type bitCache() { return m_bitCache; }
375
376 /**
377 * Returns the bit position in the stream.
378 * @see MaxBitCacheSize
379 * @see bitCacheSizeRead()
380 * @see cachedBitCount()
381 */
382 size_type position() const noexcept {
383 if ( !m_bytes->isOpen() ) {
384 return npos;
385 }
386 const size_type streamBitPos = m_bytes->position() << byte_shift;
387 if ( canWrite() ) {
388 return streamBitPos + m_bitCount;
389 } else {
390 return streamBitPos - m_bitCount;
391 }
392 }
393
394 /**
395 * Sets this stream's bit position.
396 *
397 * A set mark is cleared.
398 *
399 * Known supporting implementation is {@link ByteBufferStream} and {@link ByteArrayStream}.
400 *
401 * @param newPos desired absolute bit-position
402 * @return resulting bit-position if successful (incl EOF) or npos otherwise having an unchanged position().
403 */
404 [[nodiscard]] size_type seek(size_type newPos) noexcept {
405 const size_type pos0 = position();
406 if( newPos == pos0 ) {
407 return newPos;
408 } else if( newPos > pos0 ) {
409 return pos0 + skip(newPos - pos0);
410 } else {
411 // backwards
412 if( canWrite() ) {
413 if( 0 < m_bitCount && !writeCache() ) {
414 return 0;
415 }
416 }
417 resetLocal();
418 if( 0 != m_bytes->seek(0) ) {
419 return position();
420 }
421 return skip(newPos);
422 }
423 }
424
425 /**
426 * Skip given number of bits.
427 *
428 * @param n number of bits to skip
429 * @return actual skipped bits
430 */
431 [[nodiscard]] size_type skip(size_type n) noexcept {
432 DBG_PRINT("Bitstream.skip.0: %" PRIu64 " - %s", n, toStringImpl().c_str());
433 // forward skip
434 if( !canWrite() && n <= m_bitCount ) {
435 m_bitCount -= n;
436 DBG_PRINT("Bitstream.skip.F_N1: %" PRIu64 " - %s", n, toStringImpl().c_str());
437 return n;
438 } else if( canWrite() && n <= MaxBitCacheSize-m_bitCount ) {
439 m_bitCount += n;
440 DBG_PRINT("Bitstream.skip.F_N2: %" PRIu64 " - %s", n, toStringImpl().c_str());
441 return n;
442 } else {
443 // r: n > bitCount
444 // w: n > MaxBitCacheSize-m_bitCount
445 if( canWrite() ) {
446 if( 0 < m_bitCount && !writeCache() ) {
447 return 0;
448 }
449 }
450 const size_type n1 = n - m_bitCount; // bits to skip, subtracting cached bits, bitCount is zero at this point
451 const size_type n2 = n1 & ~(MaxBitCacheSize-1); // 64-bit aligned bits to skip
452 const size_type n3 = n2 >> byte_shift; // bytes to skip (64-bit aligned)
453 const size_type n4 = m_bytes->seek(m_bytes->position()+n3); // actual skipped bytes (64-bit aligned)
454 const size_type n5 = n1 - ( n3 << byte_shift ); // remaining skip bits == nX % 64 (64-bit aligned)
455 const size_type nX = (n4 << byte_shift) + n5 + m_bitCount; // actual skipped bits
456 m_bitCount = 0;
457 // DBG_PRINT("Bitstream.skip.1: n %" PRIu64 ", n1 %" PRIu64 ", n2 %" PRIu64 ", n3 %" PRIu64 ", n4 %" PRIu64 ", n5 %" PRIu64 ", nX %" PRIu64 ", - %s",
458 // n, n1, n2, n3, n4, n5, nX, toStringImpl().c_str());
459 if( nX < n ) {
460 // incomplete skipping
461 m_bitCache = 0;
462 DBG_PRINT("Bitstream.skip.F_EOS: %" PRIu64 " - %s", n, toStringImpl().c_str());
463 return nX;
464 }
465 jau::nsize_t notReadBits = 0;
466 if( 0 < n5 ) {
467 assert(!canWrite()); // flushed above, m_bitCount:=0
468 fillCache();
469 if( m_bitCacheSizeRead >= n5 ) {
470 m_bitCount = m_bitCacheSizeRead - n5;
471 } else {
472 notReadBits = n5 - m_bitCacheSizeRead; // EOF
473 }
474 } else {
475 m_bitCount = 0;
476 }
477 DBG_PRINT("Bitstream.skip.F_X2: %" PRIu64 ", notReadBits %zu - %s", n, (size_t)notReadBits, toStringImpl().c_str());
478 return nX - notReadBits;
479 }
480 }
481
482 /**
483 * Read incoming bit in least-significant-bit (LSB) first order.
484 * @return the read bit or `-1` if end-of-stream is reached.
485 * @see msbFirst()
486 */
487 [[nodiscard]] int readBit() noexcept {
488 if( canWrite() ) {
489 return -1;
490 }
491 if( 0 < m_bitCount ) {
492 --m_bitCount;
493 return int((m_bitCache >> (m_bitCacheSizeRead - 1 - m_bitCount)) & data_type(0x01)); // LSB
494 } else {
495 fillCache();
496 if( 0 < m_bitCacheSizeRead ) {
497 m_bitCount = m_bitCacheSizeRead - 1;
498 return int(m_bitCache & data_type(0x01)); // LSB
499 } else {
500 return -1;
501 }
502 }
503 }
504
505 /**
506 * Write given bit in least-significant-bit (LSB) first order.
507 * @param bit
508 * @return true if successful, otherwise false
509 * @see msbFirst()
510 */
511 [[nodiscard]] bool writeBit(uint8_t bit) noexcept {
512 if( !canWrite() ) {
513 return false;
514 }
515 m_bitCache |= data_type(0x01 & bit) << m_bitCount; // LSB
516 if( MaxBitCacheSize == ++m_bitCount ) {
517 if( !writeCache() ) {
518 return false;
519 }
520 }
521 m_bitCacheSizeRead = m_bitCount;
522 return true;
523 }
524
525 /**
526 * Read incoming bits in least-significant-bit (LSB) first order.
527 * @param n number of bits, maximum 64 bits
528 * @return the number of bits read. Zero for none, including errors.
529 */
530 [[nodiscard]] jau::nsize_t readBits64(jau::nsize_t n, data_type &r) noexcept {
531 r = 0;
532 if( MaxBitCacheSize < n || canWrite() || 0 == n ) {
533 return 0;
534 }
535 if constexpr ( !useFastPathStream ) {
536 // Slow path
537 for(jau::nsize_t i = 0; i < n; ++i) {
538 const int b = readBit();
539 if( 0 > b ) {
540 return i;
541 }
542 r |= data_type(b) << i;
543 }
544 } else {
545 // fast path
546 jau::nsize_t c = n;
547 const jau::nsize_t n1 = std::min(n, m_bitCount); // remaining portion
548 if( 0 < n1 ) {
549 const data_type m1 = (data_type(1) << n1) - data_type(1);
550 const jau::nsize_t s1 = m_bitCacheSizeRead - m_bitCount; // LSB: right-shift to new bits
551 m_bitCount -= n1;
552 c -= n1;
553 r = m1 & (m_bitCache >> s1); // LSB
554 if( 0 == c ) {
555 return n1;
556 }
557 }
558 assert(0 == m_bitCount);
559 fillCache();
560 if( 0 == m_bitCacheSizeRead ) {
561 return n1;
562 }
563 const nsize_t n2 = std::min(c, m_bitCacheSizeRead); // full portion
564 const data_type m2 = (data_type(1) << n2) - data_type(1);
565 m_bitCount = m_bitCacheSizeRead - n2;
566 c -= n2;
567 r |= (m2 & m_bitCache) << n1; // LSB
568 assert(0 == c);
569 (void)c;
570 }
571 return n;
572 }
573
574 /**
575 * Write given bits in least-significant-bit (LSB) first order.
576 * @param n number of bits, maximum 64 bits
577 * @param bits the bits to write
578 * @return the number of bits written. Zero for none, including errors.
579 */
580 [[nodiscard]] jau::nsize_t writeBits64(jau::nsize_t n, data_type bits) noexcept {
581 if( MaxBitCacheSize < n || !canWrite() || 0 == n ) {
582 return 0;
583 }
584 if constexpr ( !useFastPathStream ) {
585 // Slow path
586 for(jau::nsize_t i = 0; i < n; ++i) {
587 if( !writeBit( uint8_t( (bits >> i) & data_type(0x1) ) ) ) {
588 return i;
589 }
590 }
591 } else {
592 // fast path
593 jau::nsize_t c = n;
594 const jau::nsize_t n1 = std::min(n, MaxBitCacheSize-m_bitCount); // remaining free cache portion
595 if( 0 < n1 ) {
596 const data_type m1 = (data_type(1) << n1) - data_type(1);
597 const jau::nsize_t s1 = m_bitCount; // LSB: left-shift to free bit-pos
598 m_bitCount += n1;
599 c -= n1;
600 m_bitCache |= (m1 & bits) << s1; // LSB
601 if( MaxBitCacheSize == m_bitCount ) {
602 if( !writeCache() ) {
603 return 0;
604 }
605 }
606 if( 0 == c ) {
607 return n1;
608 }
609 }
610 assert(0 == m_bitCount);
611 {
612 const jau::nsize_t n2 = std::min(c, MaxBitCacheSize); // full portion
613 const data_type m2 = (data_type(1) << n2) - data_type(1);
614 m_bitCount = n2;
615 c -= n2;
616 m_bitCache = (m2 & (bits >> n1)); // LSB
617 if( MaxBitCacheSize == m_bitCount ) {
618 if( !writeCache() ) {
619 return n1;
620 }
621 }
622 }
623 assert(0 == c);
624 (void)c;
625 m_bitCacheSizeRead = m_bitCount;
626 }
627 return n;
628 }
629
630 /**
631 * Read incoming `uint8_t` via readBits32().
632 *
633 * In case of a `int8_t` 2-complement signed value, simply cast the result.
634 *
635 * @param bits reference to result
636 * @return true if successful, otherwise false
637 */
638 [[nodiscard]] bool readUInt8(uint8_t &bits) noexcept {
639 if( 0 == m_bitCount && useFastPathTypes ) {
640 return m_bytes->read(bits);
641 }
642 uint64_t tmp;
643 if( 8 != readBits64(8, tmp) ) {
644 return false;
645 }
646 bits = static_cast<uint8_t>(tmp);
647 return true;
648 }
649
650 /**
651 * Write the given 8 bits via {@link #writeBits31(int, int)}.
652 * @return true if successful, otherwise false
653 */
654 [[nodiscard]] bool writeUInt8(uint8_t bits) noexcept {
655 if( 0 == m_bitCount && useFastPathTypes ) {
656 return m_bytes->write(bits);
657 } else {
658 return 8 == writeBits64(8, bits);
659 }
660 }
661
662 /**
663 * Read `uint16_t`.
664 *
665 * If stream byteOrder() != lb_endian_t::native, result is bytes swapped via jau::bswap().
666 * @param bits reference to result
667 * @return true if successful, otherwise false
668 */
669 [[nodiscard]] bool readUInt16(uint16_t& bits) noexcept {
670 if( 0 == m_bitCount && useFastPathTypes ) {
671 return m_bytes->readU16(bits);
672 }
673 uint64_t tmp;
674 if( 16 != readBits64(16, tmp) ) {
675 return false;
676 }
677 bits = static_cast<uint16_t>(tmp);
678 if( byteOrder() != lb_endian_t::native ) {
679 bits = jau::bswap(bits);
680 }
681 return true;
682 }
683 /**
684 * Read `int16_t`.
685 *
686 * If stream byteOrder() != lb_endian_t::native, result is bytes swapped via jau::bswap().
687 * @param bits reference to result
688 * @return true if successful, otherwise false
689 */
690 [[nodiscard]] bool readSInt16(int16_t& bits) noexcept { return readUInt16( *reinterpret_cast<uint16_t*>(&bits) ); }
691
692 /**
693 * Write `uint16_t`
694 *
695 * If stream byteOrder() != lb_endian_t::native, result is bytes swapped via jau::bswap().
696 * @param bits data to write
697 * @return true if successful, otherwise false
698 */
699 [[nodiscard]] bool writeUInt16(uint16_t bits) noexcept {
700 if( !canWrite() ) {
701 return false;
702 }
703 if( byteOrder() != lb_endian_t::native ) {
704 bits = jau::bswap(bits);
705 }
706 if( 0 == m_bitCount && useFastPathTypes ) {
707 // fast path
708 return 2 == m_bytes->write(&bits, 2);
709 } else {
710 return 16 == writeBits64(16, bits);
711 }
712 }
713
714 /**
715 * Read `uint32_t`.
716 *
717 * If stream byteOrder() != lb_endian_t::native, result is bytes swapped via jau::bswap().
718 * @param bits reference to result
719 * @return true if successful, otherwise false
720 */
721 [[nodiscard]] bool readU32(uint32_t& bits) noexcept {
722 if( 0 == m_bitCount && useFastPathTypes ) {
723 // fast path
724 if( canWrite() || 4 != m_bytes->read(&bits, 4) ) {
725 return false;
726 }
727 } else {
728 uint64_t tmp;
729 if( 32 != readBits64(32, tmp) ) {
730 return false;
731 }
732 bits = static_cast<uint32_t>(tmp);
733 }
734 if ( byteOrder() != lb_endian_t::native ) {
735 bits = jau::bswap(bits);
736 }
737 return true;
738 }
739
740 /**
741 * Write `uint32_t`.
742 *
743 * If stream byteOrder() != lb_endian_t::native, result is bytes swapped via jau::bswap().
744 * @param bits data to write
745 * @return true if successful, otherwise false
746 */
747 [[nodiscard]] bool writeU32(uint32_t bits) noexcept {
748 if( !canWrite() ) {
749 return false;
750 }
751 if ( byteOrder() != lb_endian_t::native ) {
752 bits = jau::bswap(bits);
753 }
754 if( 0 == m_bitCount && useFastPathTypes ) {
755 // fast path
756 return m_bytes->write(&bits, 4);
757 } else {
758 return 32 == writeBits64(32, bits);
759 }
760 }
761
762 std::string toString() const {
763 std::string s("Bitstream[");
764 return s.append(toStringImpl()).append("]");
765 }
766 std::string toStringImpl() const {
767 std::string s;
768 s.append(canWrite() ? "W" : "R");
769 if( !m_bytes->isOpen() ) {
770 s.append(" [closed]");
771 }
772 s.append(", order[byte ").append(jau::to_string(m_bytes->byteOrder()))
773 .append("], pos ")
774 .append(std::to_string(position())).append(" (").append(std::to_string(m_bytes->position()))
775 .append(" bytes), cache[size ").append(std::to_string(cachedBitCount())).append("/").append(std::to_string(m_bitCacheSizeRead))
776 .append(", pos ").append(std::to_string(cachedBitPos()))
777 .append("), data ").append(toHexBinaryString(m_bitCache)).append("]");
778 return s;
779 }
780
781 static std::string toHexBinaryString(uint64_t v, unsigned bitCount = MaxBitCacheSize) {
782 unsigned nibbles = 0 == bitCount ? 2 : (bitCount + 3) / 4;
783 std::string fmt("[%u: 0x%0");
784 fmt.append(std::to_string(nibbles)).append(PRIx64);
785 return jau::format_string(fmt, bitCount, v).append(", ").append(jau::toBitString(v)).append("]");
786 }
787 };
788
789 inline std::ostream& operator<<(std::ostream& os, const Bitstream& v) { return os << v.toString(); }
790
791 /**@}*/
792
793} // namespace jau::io
794
795#endif /* JAU_IO_BIT_STREAM_HPP_ */
#define E_FILE_LINE
Versatile Bitstream implementation supporting:
uint64_t markReadLimit() const noexcept
Returns the readLimit set via setMark().
bool writeUInt16(uint16_t bits) noexcept
Write uint16_t
size_type seek(size_type newPos) noexcept
Sets this stream's bit position.
bool writeU32(uint32_t bits) noexcept
Write uint32_t.
size_type mark() const noexcept
Returns the markpos set via setMark() or ByteStream::npos if unset.
static constexpr size_type npos
Invalid position constant, denoting unset mark() or invalid position.
bool writeUInt8(uint8_t bits) noexcept
Write the given 8 bits via writeBits31(int, int).
std::string toString() const
constexpr jau::nsize_t cachedBitPos() const noexcept
Return the next cached bit position.
bool setAccess(ioaccess_t access) noexcept
Changes the access-mode to write or read and resets position and cache to zero.
constexpr_cxx23 iomode_t mode() const noexcept
bool readSInt16(int16_t &bits) noexcept
Read int16_t.
data_type bitCache()
Returns the 64-bit MaxBitCacheSize cache buffer value.
constexpr jau::nsize_t bitCacheSizeRead() const noexcept
Returns filled read bit-cache-size, i.e.
bool setImmutable() noexcept
Changes the write-mode to read, sets the underlying ByteStream to read-only and resets position and c...
Bitstream(std::unique_ptr< ByteStream > &&stream, ioaccess_t access)
std::string toStringImpl() const
constexpr bool canWrite() const noexcept
Returns true in case stream is in write mode, false if in read mode.
bool writeBit(uint8_t bit) noexcept
Write given bit in least-significant-bit (LSB) first order.
static constexpr jau::nsize_t MaxBitCacheSize
Maximum read bitCacheSizeRead() and fixed 64-bit write cache size.
bool setMark(size_type readLimit) noexcept
Set markpos to current bit-position, allowing the stream to be seekMark().
bool readUInt8(uint8_t &bits) noexcept
Read incoming uint8_t via readBits32().
ByteStream & byteStream()
Returns the used underlying ByteStream.
bool readU32(uint32_t &bits) noexcept
Read uint32_t.
size_type skip(size_type n) noexcept
Skip given number of bits.
bool readUInt16(uint16_t &bits) noexcept
Read uint16_t.
jau::nsize_t readBits64(jau::nsize_t n, data_type &r) noexcept
Read incoming bits in least-significant-bit (LSB) first order.
constexpr jau::nsize_t cachedBitCount() const noexcept
Returns number of cached bits.
static std::string toHexBinaryString(uint64_t v, unsigned bitCount=MaxBitCacheSize)
constexpr ioaccess_t ioaccess() const noexcept
Returns ioaccess_t stream mode.
jau::io::ByteStream::size_type size_type
uint64_t size data type, bit position and count
int readBit() noexcept
Read incoming bit in least-significant-bit (LSB) first order.
void close() noexcept
Closing the underlying stream, implies flush().
constexpr_cxx23 lb_endian_t byteOrder() const noexcept
Returns endian byte-order of stream storage.
bool seekMark() noexcept
Seeks stream bit-position to markpos as set via setMark().
size_type flush() noexcept
Synchronizes underlying ByteStream output stream operations in writeMode(), or does nothing.
jau::nsize_t writeBits64(jau::nsize_t n, data_type bits) noexcept
Write given bits in least-significant-bit (LSB) first order.
size_type position() const noexcept
Returns the bit position in the stream.
Byte stream interface.
uint64_t size_type
uint64_t size data type, bit position and count
static constexpr size_type npos
Invalid position constant, denoting unset mark() or invalid position.
#define DBG_PRINT(...)
Use for environment-variable environment::DEBUG conditional debug messages, prefix '[elapsed_time] De...
Definition debug.hpp:72
static constexpr T bit_mask(size_t n) noexcept
Returns the T bit mask of n-bits, i.e.
constexpr uint16_t bswap(uint16_t const source) noexcept
Definition byte_util.hpp:88
lb_endian_t
Simplified reduced endian type only covering little- and big-endian.
@ native
Identifier for native platform type, one of the above.
#define constexpr_cxx23
std::ostream & operator<<(std::ostream &os, const T v)
constexpr E & write(E &store, const E bits, bool set) noexcept
If set==true, sets the bits in store, i.e.
iomode_t
Stream I/O mode, e.g.
std::string to_string(const ioaccess_t v) noexcept
Return std::string representation of the given ioaccess_t.
ioaccess_t
I/O read or write access.
@ read
Read intent.
@ write
Write intend.
constexpr size_t log2_byteshift(const size_t bytesize) noexcept
Returns log2(bytesize*8), e.g.
Definition int_math.hpp:150
uint_fast32_t nsize_t
Natural 'size_t' alternative using uint_fast32_t as its natural sized type.
Definition int_types.hpp:55
std::string toBitString(const void *data, const nsize_t length, const bit_order_t bitOrder=bit_order_t::msb, const PrefixOpt prefix=PrefixOpt::prefix, size_t bit_len=0) noexcept
Produce a binary string representation of the given lsb-first byte values.
constexpr std::string format_string(const std::string_view format, const Args &...args)
Safely returns a (non-truncated) string according to snprintf() formatting rules and variable number ...
Author: Sven Gothel sgothel@jausoft.com Copyright Gothel Software e.K.
Definition enum_util.hpp:65
Author: Sven Gothel sgothel@jausoft.com Copyright (c) 2024 Gothel Software e.K.
std::string to_string(const bit_order_t v) noexcept
Return std::string representation of the given bit_order_t.
STL namespace.
static const Mat4f m1(m1_0)
static const Mat4f m2(m2_0)