1/****************************************************************************
2**
3** Copyright (C) 2018 Intel Corporation.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the examples of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:BSD$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** BSD License Usage
18** Alternatively, you may use this file under the terms of the BSD license
19** as follows:
20**
21** "Redistribution and use in source and binary forms, with or without
22** modification, are permitted provided that the following conditions are
23** met:
24** * Redistributions of source code must retain the above copyright
25** notice, this list of conditions and the following disclaimer.
26** * Redistributions in binary form must reproduce the above copyright
27** notice, this list of conditions and the following disclaimer in
28** the documentation and/or other materials provided with the
29** distribution.
30** * Neither the name of The Qt Company Ltd nor the names of its
31** contributors may be used to endorse or promote products derived
32** from this software without specific prior written permission.
33**
34**
35** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
36** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
37** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
38** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
39** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
40** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
41** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
42** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
43** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
44** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
45** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
46**
47** $QT_END_LICENSE$
48**
49****************************************************************************/
50
51#include "cborconverter.h"
52
53#include <QCborStreamReader>
54#include <QCborStreamWriter>
55#include <QCborMap>
56#include <QCborArray>
57#include <QCborValue>
58#include <QDataStream>
59#include <QFloat16>
60#include <QFile>
61#include <QMetaType>
62#include <QTextStream>
63
64#include <stdio.h>
65
66static CborConverter cborConverter;
67static CborDiagnosticDumper cborDiagnosticDumper;
68
69static const char optionHelp[] =
70 "convert-float-to-int=yes|no Write integers instead of floating point, if no\n"
71 " loss of precision occurs on conversion.\n"
72 "float16=yes|always|no Write using half-precision floating point.\n"
73 " If 'always', won't check for loss of precision.\n"
74 "float32=yes|always|no Write using single-precision floating point.\n"
75 " If 'always', won't check for loss of precision.\n"
76 "signature=yes|no Prepend the CBOR signature to the file output.\n"
77 ;
78
79static const char diagnosticHelp[] =
80 "extended=no|yes Use extended CBOR diagnostic format.\n"
81 "line-wrap=yes|no Split output into multiple lines.\n"
82 ;
83
84QT_BEGIN_NAMESPACE
85
86QDataStream &operator<<(QDataStream &ds, QCborTag tag)
87{
88 return ds << quint64(tag);
89}
90
91QDataStream &operator>>(QDataStream &ds, QCborTag &tag)
92{
93 quint64 v;
94 ds >> v;
95 tag = QCborTag(v);
96 return ds;
97}
98
99QT_END_NAMESPACE
100
101// We can't use QCborValue::toVariant directly because that would destroy
102// non-string keys in CBOR maps (QVariantMap can't handle those). Instead, we
103// have our own set of converter functions so we can keep the keys properly.
104
105static QVariant convertCborValue(const QCborValue &value);
106
107static QVariant convertCborMap(const QCborMap &map)
108{
109 VariantOrderedMap result;
110 result.reserve(asize: map.size());
111 for (auto pair : map)
112 result.append(t: { convertCborValue(value: pair.first), convertCborValue(value: pair.second) });
113 return QVariant::fromValue(value: result);
114}
115
116static QVariant convertCborArray(const QCborArray &array)
117{
118 QVariantList result;
119 result.reserve(alloc: array.size());
120 for (auto value : array)
121 result.append(t: convertCborValue(value));
122 return result;
123}
124
125static QVariant convertCborValue(const QCborValue &value)
126{
127 if (value.isArray())
128 return convertCborArray(array: value.toArray());
129 if (value.isMap())
130 return convertCborMap(map: value.toMap());
131 return value.toVariant();
132}
133
134enum TrimFloatingPoint { Double, Float, Float16 };
135static QCborValue convertFromVariant(const QVariant &v, TrimFloatingPoint fpTrimming)
136{
137 if (v.userType() == QMetaType::QVariantList) {
138 const QVariantList list = v.toList();
139 QCborArray array;
140 for (const QVariant &v : list)
141 array.append(value: convertFromVariant(v, fpTrimming));
142
143 return array;
144 }
145
146 if (v.userType() == qMetaTypeId<VariantOrderedMap>()) {
147 const auto m = qvariant_cast<VariantOrderedMap>(v);
148 QCborMap map;
149 for (const auto &pair : m)
150 map.insert(key: convertFromVariant(v: pair.first, fpTrimming),
151 value_: convertFromVariant(v: pair.second, fpTrimming));
152 return map;
153 }
154
155 if (v.userType() == QMetaType::Double && fpTrimming != Double) {
156 float f = float(v.toDouble());
157 if (fpTrimming == Float16)
158 return float(qfloat16(f));
159 return f;
160 }
161
162 return QCborValue::fromVariant(variant: v);
163}
164
165QString CborDiagnosticDumper::name()
166{
167 return QStringLiteral("cbor-dump");
168}
169
170Converter::Direction CborDiagnosticDumper::directions()
171{
172 return Out;
173}
174
175Converter::Options CborDiagnosticDumper::outputOptions()
176{
177 return SupportsArbitraryMapKeys;
178}
179
180const char *CborDiagnosticDumper::optionsHelp()
181{
182 return diagnosticHelp;
183}
184
185bool CborDiagnosticDumper::probeFile(QIODevice *f)
186{
187 Q_UNUSED(f);
188 return false;
189}
190
191QVariant CborDiagnosticDumper::loadFile(QIODevice *f, Converter *&outputConverter)
192{
193 Q_UNREACHABLE();
194 Q_UNUSED(f);
195 Q_UNUSED(outputConverter);
196 return QVariant();
197}
198
199void CborDiagnosticDumper::saveFile(QIODevice *f, const QVariant &contents, const QStringList &options)
200{
201 QCborValue::DiagnosticNotationOptions opts = QCborValue::LineWrapped;
202 for (const QString &s : options) {
203 QStringList pair = s.split(sep: '=');
204 if (pair.size() == 2) {
205 if (pair.first() == "line-wrap") {
206 opts &= ~QCborValue::LineWrapped;
207 if (pair.last() == "yes") {
208 opts |= QCborValue::LineWrapped;
209 continue;
210 } else if (pair.last() == "no") {
211 continue;
212 }
213 }
214 if (pair.first() == "extended") {
215 opts &= ~QCborValue::ExtendedFormat;
216 if (pair.last() == "yes")
217 opts |= QCborValue::ExtendedFormat;
218 continue;
219 }
220 }
221
222 fprintf(stderr, format: "Unknown CBOR diagnostic option '%s'. Available options are:\n%s",
223 qPrintable(s), diagnosticHelp);
224 exit(EXIT_FAILURE);
225 }
226
227 QTextStream out(f);
228 out << convertFromVariant(v: contents, fpTrimming: Double).toDiagnosticNotation(opts)
229 << Qt::endl;
230}
231
232CborConverter::CborConverter()
233{
234 qRegisterMetaType<QCborTag>();
235 qRegisterMetaTypeStreamOperators<QCborTag>();
236 QMetaType::registerDebugStreamOperator<QCborTag>();
237}
238
239QString CborConverter::name()
240{
241 return "cbor";
242}
243
244Converter::Direction CborConverter::directions()
245{
246 return InOut;
247}
248
249Converter::Options CborConverter::outputOptions()
250{
251 return SupportsArbitraryMapKeys;
252}
253
254const char *CborConverter::optionsHelp()
255{
256 return optionHelp;
257}
258
259bool CborConverter::probeFile(QIODevice *f)
260{
261 if (QFile *file = qobject_cast<QFile *>(object: f)) {
262 if (file->fileName().endsWith(s: QLatin1String(".cbor")))
263 return true;
264 }
265 return f->isReadable() && f->peek(maxlen: 3) == QByteArray("\xd9\xd9\xf7", 3);
266}
267
268QVariant CborConverter::loadFile(QIODevice *f, Converter *&outputConverter)
269{
270 const char *ptr = nullptr;
271 if (auto file = qobject_cast<QFile *>(object: f))
272 ptr = reinterpret_cast<char *>(file->map(offset: 0, size: file->size()));
273
274 QByteArray mapped = QByteArray::fromRawData(ptr, size: ptr ? f->size() : 0);
275 QCborStreamReader reader(mapped);
276 if (!ptr)
277 reader.setDevice(f);
278
279 if (reader.isTag() && reader.toTag() == QCborKnownTags::Signature)
280 reader.next();
281
282 QCborValue contents = QCborValue::fromCbor(reader);
283 qint64 offset = reader.currentOffset();
284 if (reader.lastError()) {
285 fprintf(stderr, format: "Error loading CBOR contents (byte %lld): %s\n", offset,
286 qPrintable(reader.lastError().toString()));
287 fprintf(stderr, format: " bytes: %s\n",
288 (ptr ? mapped.mid(index: offset, len: 9) : f->read(maxlen: 9)).toHex(separator: ' ').constData());
289 exit(EXIT_FAILURE);
290 } else if (offset < mapped.size() || (!ptr && f->bytesAvailable())) {
291 fprintf(stderr, format: "Warning: bytes remaining at the end of the CBOR stream\n");
292 }
293
294 if (outputConverter == nullptr)
295 outputConverter = &cborDiagnosticDumper;
296 else if (outputConverter == null)
297 return QVariant();
298 else if (!outputConverter->outputOptions().testFlag(flag: SupportsArbitraryMapKeys))
299 return contents.toVariant();
300 return convertCborValue(value: contents);
301}
302
303void CborConverter::saveFile(QIODevice *f, const QVariant &contents, const QStringList &options)
304{
305 bool useSignature = true;
306 bool useIntegers = true;
307 enum { Yes, No, Always } useFloat16 = Yes, useFloat = Yes;
308
309 for (const QString &s : options) {
310 QStringList pair = s.split(sep: '=');
311 if (pair.size() == 2) {
312 if (pair.first() == "convert-float-to-int") {
313 if (pair.last() == "yes") {
314 useIntegers = true;
315 continue;
316 } else if (pair.last() == "no") {
317 useIntegers = false;
318 continue;
319 }
320 }
321
322 if (pair.first() == "float16") {
323 if (pair.last() == "no") {
324 useFloat16 = No;
325 continue;
326 } else if (pair.last() == "yes") {
327 useFloat16 = Yes;
328 continue;
329 } else if (pair.last() == "always") {
330 useFloat16 = Always;
331 continue;
332 }
333 }
334
335 if (pair.first() == "float32") {
336 if (pair.last() == "no") {
337 useFloat = No;
338 continue;
339 } else if (pair.last() == "yes") {
340 useFloat = Yes;
341 continue;
342 } else if (pair.last() == "always") {
343 useFloat = Always;
344 continue;
345 }
346 }
347
348 if (pair.first() == "signature") {
349 if (pair.last() == "yes") {
350 useSignature = true;
351 continue;
352 } else if (pair.last() == "no") {
353 useSignature = false;
354 continue;
355 }
356 }
357 }
358
359 fprintf(stderr, format: "Unknown CBOR format option '%s'. Valid options are:\n%s",
360 qPrintable(s), optionHelp);
361 exit(EXIT_FAILURE);
362 }
363
364 QCborValue v = convertFromVariant(v: contents,
365 fpTrimming: useFloat16 == Always ? Float16 : useFloat == Always ? Float : Double);
366 QCborStreamWriter writer(f);
367 if (useSignature)
368 writer.append(tag: QCborKnownTags::Signature);
369
370 QCborValue::EncodingOptions opts;
371 if (useIntegers)
372 opts |= QCborValue::UseIntegers;
373 if (useFloat != No)
374 opts |= QCborValue::UseFloat;
375 if (useFloat16 != No)
376 opts |= QCborValue::UseFloat16;
377 v.toCbor(writer, opt: opts);
378}
379
380

source code of qtbase/examples/corelib/serialization/convert/cborconverter.cpp