
/**
 * \file mcql/input/cborstreamreaderdebug.cpp
 * \date 03/01/2025
 * \author Olivier Langella
 * \brief read cbor stream for debug
 */

/*******************************************************************************
 * Copyright (c) 2025 Olivier Langella
 *<Olivier.Langella@universite-paris-saclay.fr>.
 *
 * This file is part of MassChroQ.
 *
 *     MassChroQ is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     (at your option) any later version.
 *
 *     MassChroQ is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU General Public License for more details.
 *
 *     You should have received a copy of the GNU General Public License
 *     along with MassChroQ.  If not, see <http://www.gnu.org/licenses/>.
 *
 ******************************************************************************/

#include "cborstreamreaderdebug.h"
#include <QIODevice>
#include <QLocale>

/*
 * To regenerate:
 *  curl -O https://www.iana.org/assignments/cbor-tags/cbor-tags.xml
 *  ./cbortag.py cbor-tags.xml
 *
 * The XHTML URL mentioned in the comment below is a human-readable version of
 * the same resource.
 */


using namespace Qt::StringLiterals;

// GENERATED CODE
struct CborTagDescription
{
  QCborTag tag;
  const char *description; // with space and parentheses
};

// Concise Binary Object Representation (CBOR) Tags
static const CborTagDescription tagDescriptions[] = {
  // from https://www.iana.org/assignments/cbor-tags/cbor-tags.xhtml
  {QCborTag(0), " (Standard date/time string; see Section 3.4.1 [RFC8949])"},
  {QCborTag(1), " (Epoch-based date/time; see Section 3.4.2 [RFC8949])"},
  {QCborTag(2), " (Positive bignum; see Section 3.4.3 [RFC8949])"},
  {QCborTag(3), " (Negative bignum; see Section 3.4.3 [RFC8949])"},
  {QCborTag(4), " (Decimal fraction; see Section 3.4.4 [RFC8949])"},
  {QCborTag(5), " (Bigfloat; see Section 3.4.4 [RFC8949])"},
  {QCborTag(16), " (COSE Single Recipient Encrypted Data Object [RFC9052])"},
  {QCborTag(17), " (COSE Mac w/o Recipients Object [RFC9052])"},
  {QCborTag(18), " (COSE Single Signer Data Object [RFC9052])"},
  {QCborTag(19), " (COSE standalone V2 countersignature [RFC9338])"},
  {QCborTag(21),
   " (Expected conversion to base64url encoding; see Section 3.4.5.2 "
   "[RFC8949])"},
  {QCborTag(22),
   " (Expected conversion to base64 encoding; see Section 3.4.5.2 [RFC8949])"},
  {QCborTag(23),
   " (Expected conversion to base16 encoding; see Section 3.4.5.2 [RFC8949])"},
  {QCborTag(24), " (Encoded CBOR data item; see Section 3.4.5.1 [RFC8949])"},
  {QCborTag(25), " (reference the nth previously seen string)"},
  {QCborTag(26),
   " (Serialised Perl object with classname and constructor arguments)"},
  {QCborTag(27),
   " (Serialised language-independent object with type name and constructor "
   "arguments)"},
  {QCborTag(28), " (mark value as (potentially) shared)"},
  {QCborTag(29), " (reference nth marked value)"},
  {QCborTag(30), " (Rational number)"},
  {QCborTag(31), " (Absent value in a CBOR Array)"},
  {QCborTag(32), " (URI; see Section 3.4.5.3 [RFC8949])"},
  {QCborTag(33), " (base64url; see Section 3.4.5.3 [RFC8949])"},
  {QCborTag(34), " (base64; see Section 3.4.5.3 [RFC8949])"},
  {QCborTag(35), " (Regular expression; see Section 2.4.4.3 [RFC7049])"},
  {QCborTag(36), " (MIME message; see Section 3.4.5.3 [RFC8949])"},
  {QCborTag(37), " (Binary UUID [RFC4122, Section 4.1.2])"},
  {QCborTag(38), " (Language-tagged string [RFC9290, Appendix A])"},
  {QCborTag(39), " (Identifier)"},
  {QCborTag(40), " (Multi-dimensional Array, row-major order [RFC8746])"},
  {QCborTag(41), " (Homogeneous Array [RFC8746])"},
  {QCborTag(42), " (IPLD content identifier)"},
  {QCborTag(43), " (YANG bits datatype; see Section 6.7. [RFC9254])"},
  {QCborTag(44), " (YANG enumeration datatype; see Section 6.6. [RFC9254])"},
  {QCborTag(45), " (YANG identityref datatype; see Section 6.10. [RFC9254])"},
  {QCborTag(46),
   " (YANG instance-identifier datatype; see Section 6.13. [RFC9254])"},
  {QCborTag(47),
   " (YANG Schema Item iDentifier (sid); see Section 3.2. [RFC9254])"},
  {QCborTag(52), " (IPv4, [prefixlen,IPv4], [IPv4,prefixpart] [RFC9164])"},
  {QCborTag(54), " (IPv6, [prefixlen,IPv6], [IPv6,prefixpart] [RFC9164])"},
  {QCborTag(61), " (CBOR Web Token (CWT) [RFC8392])"},
  {QCborTag(63), " (Encoded CBOR Sequence [RFC8742])"},
  {QCborTag(64), " (uint8 Typed Array [RFC8746])"},
  {QCborTag(65), " (uint16, big endian, Typed Array [RFC8746])"},
  {QCborTag(66), " (uint32, big endian, Typed Array [RFC8746])"},
  {QCborTag(67), " (uint64, big endian, Typed Array [RFC8746])"},
  {QCborTag(68), " (uint8 Typed Array, clamped arithmetic [RFC8746])"},
  {QCborTag(69), " (uint16, little endian, Typed Array [RFC8746])"},
  {QCborTag(70), " (uint32, little endian, Typed Array [RFC8746])"},
  {QCborTag(71), " (uint64, little endian, Typed Array [RFC8746])"},
  {QCborTag(72), " (sint8 Typed Array [RFC8746])"},
  {QCborTag(73), " (sint16, big endian, Typed Array [RFC8746])"},
  {QCborTag(74), " (sint32, big endian, Typed Array [RFC8746])"},
  {QCborTag(75), " (sint64, big endian, Typed Array [RFC8746])"},
  {QCborTag(76), " ((reserved) [RFC8746])"},
  {QCborTag(77), " (sint16, little endian, Typed Array [RFC8746])"},
  {QCborTag(78), " (sint32, little endian, Typed Array [RFC8746])"},
  {QCborTag(79), " (sint64, little endian, Typed Array [RFC8746])"},
  {QCborTag(80), " (IEEE 754 binary16, big endian, Typed Array [RFC8746])"},
  {QCborTag(81), " (IEEE 754 binary32, big endian, Typed Array [RFC8746])"},
  {QCborTag(82), " (IEEE 754 binary64, big endian, Typed Array [RFC8746])"},
  {QCborTag(83), " (IEEE 754 binary128, big endian, Typed Array [RFC8746])"},
  {QCborTag(84), " (IEEE 754 binary16, little endian, Typed Array [RFC8746])"},
  {QCborTag(85), " (IEEE 754 binary32, little endian, Typed Array [RFC8746])"},
  {QCborTag(86), " (IEEE 754 binary64, little endian, Typed Array [RFC8746])"},
  {QCborTag(87), " (IEEE 754 binary128, little endian, Typed Array [RFC8746])"},
  {QCborTag(96), " (COSE Encrypted Data Object [RFC9052])"},
  {QCborTag(97), " (COSE MACed Data Object [RFC9052])"},
  {QCborTag(98), " (COSE Signed Data Object [RFC9052])"},
  {QCborTag(100),
   " (Number of days since the epoch date 1970-01-01 [RFC8943])"},
  {QCborTag(101),
   " (alternatives as given by the uint + 128; see Section 9.1)"},
  {QCborTag(103), " (Geographic Coordinates)"},
  {QCborTag(104),
   " (Geographic Coordinate Reference System WKT or EPSG number)"},
  {QCborTag(110),
   " (relative object identifier (BER encoding); SDNV sequence [RFC9090])"},
  {QCborTag(111), " (object identifier (BER encoding) [RFC9090])"},
  {QCborTag(112),
   " (object identifier (BER encoding), relative to 1.3.6.1.4.1 [RFC9090])"},
  {QCborTag(120), " (Internet of Things Data Point)"},
  {QCborTag(260),
   " (Network Address (IPv4 or IPv6 or MAC Address) (DEPRECATED in favor of 52 "
   "and 54 for IP"
   " addresses) [RFC9164])"},
  {QCborTag(261),
   " (Network Address Prefix (IPv4 or IPv6 Address + Mask Length) (DEPRECATED "
   "in favor of 52"
   " and 54 for IP addresses) [RFC9164])"},
  {QCborTag(271),
   " (DDoS Open Threat Signaling (DOTS) signal channel object, as defined in "
   "[RFC9132])"},
  {QCborTag(1004), " (full-date string [RFC8943])"},
  {QCborTag(1040), " (Multi-dimensional Array, column-major order [RFC8746])"},
  {QCborTag(55799), " (Self-described CBOR; see Section 3.4.6 [RFC8949])"},
  {QCborTag(55800),
   " (indicates that the file contains CBOR Sequences [RFC9277])"},
  {QCborTag(55801),
   " (indicates that the file starts with a CBOR-Labeled Non-CBOR Data label. "
   "[RFC9277])"},
  {QCborTag(-1), nullptr}};
// END GENERATED CODE

enum
{
  // See RFC 7049 section 2.
  SmallValueBitLength = 5,
  SmallValueMask      = (1 << SmallValueBitLength) - 1, /* 0x1f */
  Value8Bit           = 24,
  Value16Bit          = 25,
  Value32Bit          = 26,
  Value64Bit          = 27
};

//! [0]
Q_DECLARE_OPERATORS_FOR_FLAGS(mcql::CborStreamReaderDebug::DumpOptions)

static int
cborNumberSize(quint64 value)
{
  int normalSize = 1;
  if(value > std::numeric_limits<quint32>::max())
    normalSize += 8;
  else if(value > std::numeric_limits<quint16>::max())
    normalSize += 4;
  else if(value > std::numeric_limits<quint8>::max())
    normalSize += 2;
  else if(value >= Value8Bit)
    normalSize += 1;
  return normalSize;
}

mcql::CborStreamReaderDebug::CborStreamReaderDebug(
  QFile *f, mcql::CborStreamReaderDebug::DumpOptions opts_)
  : opts(opts_)
{
    // try to mmap the file, this is faster
    char *ptr = reinterpret_cast<char *>(f->map(0, f->size(), QFile::MapPrivateOption));
    if (ptr) {
        // worked
        data = QByteArray::fromRawData(ptr, f->size());
        reader.addData(data);
    } else if ((opts & ShowAnnotated) || f->isSequential()) {
        // details requires full contents, so allocate memory
        data = f->readAll();
        reader.addData(data);
    } else {
        // just use the QIODevice
        reader.setDevice(f);
    }
}

QCborError
mcql::CborStreamReaderDebug::dump()
{
  byteArrayEncoding << quint8(QCborKnownTags::ExpectedBase16);
  if(!reader.lastError())
    {
      if(opts & ShowAnnotated)
        dumpOneDetailed(0);
      else
        dumpOne(0);
    }

  QCborError err = reader.lastError();
  offset         = reader.currentOffset();
  if(err != QCborError::Code::NoError)
    {
      fflush(stdout);
      fprintf(stderr,
              "cbordump: decoding failed at %lld: %s\n",
              offset,
              qPrintable(err.toString()));
      if(!data.isEmpty())
        fprintf(stderr,
                " bytes at %lld: %s\n",
                offset,
                data.mid(offset, 9).toHex(' ').constData());
    }
  else
    {
      if(!opts.testFlag(ShowAnnotated))
        printf("\n");
      if(offset < data.size() ||
         (reader.device() && reader.device()->bytesAvailable()))
        fprintf(stderr,
                "Warning: bytes remaining at the end of the CBOR stream\n");
    }

  return err;
}

template <typename T>
static inline bool
canConvertTo(double v)
{
  using TypeInfo = std::numeric_limits<T>;
  // The [conv.fpint] (7.10 Floating-integral conversions) section of the
  // standard says only exact conversions are guaranteed. Converting
  // integrals to floating-point with loss of precision has implementation-
  // defined behavior whether the next higher or next lower is returned;
  // converting FP to integral is UB if it can't be represented.;
  static_assert(TypeInfo::is_integer);

  double supremum = ldexp(1, TypeInfo::digits);
  if(v >= supremum)
    return false;

  if(v < TypeInfo::min()) // either zero or a power of two, so it's exact
    return false;

  // we're in range
  return v == floor(v);
}

static QString
fpToString(double v, QLatin1StringView suffix = ""_L1)
{
  if(qIsInf(v))
    return v < 0 ? "-inf"_L1 : "inf"_L1;
  if(qIsNaN(v))
    return "nan"_L1;
  if(canConvertTo<qint64>(v))
    return QString::number(qint64(v)) + ".0"_L1 + suffix;
  if(canConvertTo<quint64>(v))
    return QString::number(quint64(v)) + ".0"_L1 + suffix;

  QString s = QString::number(v, 'g', QLocale::FloatingPointShortest);
  if(!s.contains(u'.') && !s.contains(u'e'))
    s += u'.';
  if(suffix.size())
    s += suffix;
  return s;
};

void
mcql::CborStreamReaderDebug::dumpOne(int nestingLevel)
{
  QString indent(1, u' ');
  QString indented = indent;
  if(!opts.testFlag(ShowCompact))
    {
      indent   = u'\n' + QString(4 * nestingLevel, u' ');
      indented = u'\n' + QString(4 + 4 * nestingLevel, u' ');
    }

  switch(reader.type())
    {
      case QCborStreamReader::UnsignedInteger:
        {
          quint64 u = reader.toUnsignedInteger();
          printf("%llu", u);
          reader.next();
          printWidthIndicator(u);
          return;
        }

      case QCborStreamReader::NegativeInteger:
        {
          quint64 n = quint64(reader.toNegativeInteger());
          if(n == 0) // -2^64 (wrapped around)
            printf("-18446744073709551616");
          else
            printf("-%llu", n);
          reader.next();
          printWidthIndicator(n);
          return;
        }

      case QCborStreamReader::ByteArray:
      case QCborStreamReader::String:
        {
          bool isLengthKnown = reader.isLengthKnown();
          if(!isLengthKnown)
            {
              printf("(_ ");
              ++offset;
            }

          QString comma;
          if(reader.isByteArray())
            {
              auto r = reader.readByteArray();
              while(r.status == QCborStreamReader::Ok)
                {
                  printf("%s", qPrintable(comma));
                  printByteArray(r.data);
                  printStringWidthIndicator(r.data.size());

                  r     = reader.readByteArray();
                  comma = u',' + indented;
                }
            }
          else
            {
              auto r = reader.readString();
              while(r.status == QCborStreamReader::Ok)
                {
                  printf("%s\"%s\"", qPrintable(comma), qPrintable(r.data));
                  printStringWidthIndicator(r.data.toUtf8().size());

                  r     = reader.readString();
                  comma = u',' + indented;
                }
            }

          if(!isLengthKnown && !reader.lastError())
            printf(")");
          break;
        }

      case QCborStreamReader::Array:
      case QCborStreamReader::Map:
        {
          const char *delimiters = (reader.isArray() ? "[]" : "{}");
          printf("%c", delimiters[0]);

          if(reader.isLengthKnown())
            {
              quint64 len = reader.length();
              reader.enterContainer();
              printWidthIndicator(len, ' ');
            }
          else
            {
              reader.enterContainer();
              offset = reader.currentOffset();
              printf("_ ");
            }

          const char *comma = "";
          while(!reader.lastError() && reader.hasNext())
            {
              printf("%s%s", comma, qPrintable(indented));
              comma = ",";
              dumpOne(nestingLevel + 1);

              if(reader.parentContainerType() != QCborStreamReader::Map)
                continue;
              if(reader.lastError())
                break;
              printf(": ");
              dumpOne(nestingLevel + 1);
            }

          if(!reader.lastError())
            {
              reader.leaveContainer();
              printf("%s%c", qPrintable(indent), delimiters[1]);
            }
          break;
        }

      case QCborStreamReader::Tag:
        {
          QCborTag tag = reader.toTag();
          printf("%llu", quint64(tag));

          if(tag == QCborKnownTags::ExpectedBase16 ||
             tag == QCborKnownTags::ExpectedBase64 ||
             tag == QCborKnownTags::ExpectedBase64url)
            byteArrayEncoding.push(quint8(tag));

          if(reader.next())
            {
              printWidthIndicator(quint64(tag));
              printf("(");
              dumpOne(nestingLevel); // same level!
              printf(")");
            }

          if(tag == QCborKnownTags::ExpectedBase16 ||
             tag == QCborKnownTags::ExpectedBase64 ||
             tag == QCborKnownTags::ExpectedBase64url)
            byteArrayEncoding.pop();
          break;
        }

      case QCborStreamReader::SimpleType:
        switch(reader.toSimpleType())
          {
            case QCborSimpleType::False:
              printf("false");
              break;
            case QCborSimpleType::True:
              printf("true");
              break;
            case QCborSimpleType::Null:
              printf("null");
              break;
            case QCborSimpleType::Undefined:
              printf("undefined");
              break;
            default:
              printf("simple(%u)", quint8(reader.toSimpleType()));
              break;
          }
        reader.next();
        break;

      case QCborStreamReader::Float16:
        printf("%s", qPrintable(fpToString(reader.toFloat16(), "f16"_L1)));
        reader.next();
        break;
      case QCborStreamReader::Float:
        printf("%s", qPrintable(fpToString(reader.toFloat(), "f"_L1)));
        reader.next();
        break;
      case QCborStreamReader::Double:
        printf("%s", qPrintable(fpToString(reader.toDouble())));
        reader.next();
        break;
      case QCborStreamReader::Invalid:
        return;
    }

  offset = reader.currentOffset();
}

void
mcql::CborStreamReaderDebug::dumpOneDetailed(int nestingLevel)
{
  auto tagDescription = [](QCborTag tag) {
    for(auto entry : tagDescriptions)
      {
        if(entry.tag == tag)
          return entry.description;
        if(entry.tag > tag)
          break;
      }
    return "";
  };
  auto printOverlong = [](int actualSize, quint64 value) {
    if(cborNumberSize(value) != actualSize)
      printf(" (overlong)");
  };
  auto print = [&](const char *descr, const char *fmt, ...) {
    qint64 prevOffset = offset;
    offset            = reader.currentOffset();
    if(prevOffset == offset)
      return;

    QByteArray bytes = data.mid(prevOffset, offset - prevOffset);
    QByteArray indent(nestingLevel * 2, ' ');
    printf("%-50s # %s ", (indent + bytes.toHex(' ')).constData(), descr);

    va_list va;
    va_start(va, fmt);
    vprintf(fmt, va);
    va_end(va);

    if(strstr(fmt, "%ll"))
      {
        // Only works because all callers below that use %ll, use it as the
        // first arg
        va_start(va, fmt);
        quint64 value = va_arg(va, quint64);
        va_end(va);
        printOverlong(bytes.size(), value);
      }

    puts("");
  };

  auto printFp = [=](const char *descr, double d) {
    QString s = fpToString(d);
    if(s.size() <= 6)
      return print(descr, "%s", qPrintable(s));
    return print(descr, "%a", d);
  };

  auto printString = [&](const char *descr) {
    constexpr qsizetype ChunkSizeLimit = std::numeric_limits<int>::max();
    QByteArray indent(nestingLevel * 2, ' ');
    const char *chunkStr = (reader.isLengthKnown() ? "" : "chunk ");
    int width            = 48 - indent.size();
    int bytesPerLine     = qMax(width / 3, 5);

    qsizetype size = reader.currentStringChunkSize();
    if(size < 0)
      return; // error
    if(size >= ChunkSizeLimit)
      {
        fprintf(stderr, "String length too big, %lli\n", qint64(size));
        exit(EXIT_FAILURE);
      }

    // if asking for the current string chunk changes the offset, then it
    // was chunked
    print(descr, "(indeterminate length)");

    QByteArray bytes(size, Qt::Uninitialized);
    auto r = reader.readStringChunk(bytes.data(), bytes.size());
    while(r.status == QCborStreamReader::Ok)
      {
        // We'll have to decode the length's width directly from CBOR...
        const char *lenstart         = data.constData() + offset;
        const char *lenend           = lenstart + 1;
        quint8 additionalInformation = (*lenstart & SmallValueMask);

        // Decode this number directly from CBOR (see RFC 7049 section 2)
        if(additionalInformation >= Value8Bit)
          {
            if(additionalInformation == Value8Bit)
              lenend += 1;
            else if(additionalInformation == Value16Bit)
              lenend += 2;
            else if(additionalInformation == Value32Bit)
              lenend += 4;
            else
              lenend += 8;
          }

        {
          QByteArray lenbytes =
            QByteArray::fromRawData(lenstart, lenend - lenstart);
          printf("%-50s # %s %slength %llu",
                 (indent + lenbytes.toHex(' ')).constData(),
                 descr,
                 chunkStr,
                 quint64(size));
          printOverlong(lenbytes.size(), size);
          puts("");
        }

        offset = reader.currentOffset();

        for(int i = 0; i < r.data; i += bytesPerLine)
          {
            QByteArray section = bytes.mid(i, bytesPerLine);
            printf(
              "  %s%s", indent.constData(), section.toHex(' ').constData());

            // print the decode
            QByteArray spaces(width > 0 ? width - section.size() * 3 + 1 : 0,
                              ' ');
            printf("%s # \"", spaces.constData());
            auto ptr = reinterpret_cast<const uchar *>(section.constData());
            for(int j = 0; j < section.size(); ++j)
              printf("%c", ptr[j] >= 0x80 || ptr[j] < 0x20 ? '.' : ptr[j]);

            puts("\"");
          }

        // get the next chunk
        size = reader.currentStringChunkSize();
        if(size < 0)
          return; // error
        if(size >= ChunkSizeLimit)
          {
            fprintf(stderr, "String length too big, %lli\n", qint64(size));
            exit(EXIT_FAILURE);
          }
        bytes.resize(size);
        r = reader.readStringChunk(bytes.data(), bytes.size());
      }
  };

  if(reader.lastError())
    return;

  switch(reader.type())
    {
      case QCborStreamReader::UnsignedInteger:
        {
          quint64 u = reader.toUnsignedInteger();
          reader.next();
          if(u < 65536 || (u % 100000) == 0)
            print("Unsigned integer", "%llu", u);
          else
            print("Unsigned integer", "0x%llx", u);
          return;
        }

      case QCborStreamReader::NegativeInteger:
        {
          quint64 n = quint64(reader.toNegativeInteger());
          reader.next();
          print(
            "Negative integer", n == 0 ? "-18446744073709551616" : "-%llu", n);
          return;
        }

      case QCborStreamReader::ByteArray:
      case QCborStreamReader::String:
        {
          bool isLengthKnown = reader.isLengthKnown();
          const char *descr =
            (reader.isString() ? "Text string" : "Byte string");
          if(!isLengthKnown)
            ++nestingLevel;

          printString(descr);
          if(reader.lastError())
            return;

          if(!isLengthKnown)
            {
              --nestingLevel;
              print("Break", "");
            }
          break;
        }

      case QCborStreamReader::Array:
      case QCborStreamReader::Map:
        {
          const char *descr = (reader.isArray() ? "Array" : "Map");
          if(reader.isLengthKnown())
            {
              quint64 len = reader.length();
              reader.enterContainer();
              print(descr, "length %llu", len);
            }
          else
            {
              reader.enterContainer();
              print(descr, "(indeterminate length)");
            }

          while(!reader.lastError() && reader.hasNext())
            dumpOneDetailed(nestingLevel + 1);

          if(!reader.lastError())
            {
              reader.leaveContainer();
              print("Break", "");
            }
          break;
        }

      case QCborStreamReader::Tag:
        {
          QCborTag tag = reader.toTag();
          reader.next();
          print("Tag", "%llu%s", quint64(tag), tagDescription(tag));
          dumpOneDetailed(nestingLevel + 1);
          break;
        }

      case QCborStreamReader::SimpleType:
        {
          QCborSimpleType st = reader.toSimpleType();
          reader.next();
          switch(st)
            {
              case QCborSimpleType::False:
                print("Simple Type", "false");
                break;
              case QCborSimpleType::True:
                print("Simple Type", "true");
                break;
              case QCborSimpleType::Null:
                print("Simple Type", "null");
                break;
              case QCborSimpleType::Undefined:
                print("Simple Type", "undefined");
                break;
              default:
                print("Simple Type", "%u", quint8(st));
                break;
            }
          break;
        }

      case QCborStreamReader::Float16:
        {
          double d = reader.toFloat16();
          reader.next();
          printFp("Float16", d);
          break;
        }
      case QCborStreamReader::Float:
        {
          double d = reader.toFloat();
          reader.next();
          printFp("Float", d);
          break;
        }
      case QCborStreamReader::Double:
        {
          double d = reader.toDouble();
          reader.next();
          printFp("Double", d);
          break;
        }
      case QCborStreamReader::Invalid:
        return;
    }

  offset = reader.currentOffset();
}

void
mcql::CborStreamReaderDebug::printByteArray(const QByteArray &ba)
{
  switch(byteArrayEncoding.top())
    {
      default:
        printf("h'%s'", ba.toHex(' ').constData());
        break;

      case quint8(QCborKnownTags::ExpectedBase64):
        printf("b64'%s'", ba.toBase64().constData());
        break;

      case quint8(QCborKnownTags::ExpectedBase64url):
        printf("b64'%s'",
               ba.toBase64(QByteArray::Base64UrlEncoding |
                           QByteArray::OmitTrailingEquals)
                 .constData());
        break;
    }
}

void
printIndicator(quint64 value, qint64 previousOffset, qint64 offset, char space)
{
  int normalSize = cborNumberSize(value);
  int actualSize = offset - previousOffset;

  if(actualSize != normalSize)
    {
      Q_ASSERT(actualSize > 1);
      actualSize -= 2;
      printf("_%d", qPopulationCount(uint(actualSize)));
      if(space)
        printf("%c", space);
    }
}

void
mcql::CborStreamReaderDebug::printWidthIndicator(quint64 value, char space)
{
  qint64 previousOffset = offset;
  offset                = reader.currentOffset();
  if(opts & ShowWidthIndicators)
    printIndicator(value, previousOffset, offset, space);
}

void
mcql::CborStreamReaderDebug::printStringWidthIndicator(quint64 value)
{
  qint64 previousOffset = offset;
  offset                = reader.currentOffset();
  if(opts & ShowWidthIndicators)
    printIndicator(value, previousOffset, offset - uint(value), '\0');
}
