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(map.size());
111 for (auto pair : map)
112 result.append({ convertCborValue(pair.first), convertCborValue(pair.second) });
113 return QVariant::fromValue(result);
114}
115
116static QVariant convertCborArray(const QCborArray &array)
117{
118 QVariantList result;
119 result.reserve(array.size());
120 for (auto value : array)
121 result.append(convertCborValue(value));
122 return result;
123}
124
125static QVariant convertCborValue(const QCborValue &value)
126{
127 if (value.isArray())
128 return convertCborArray(value.toArray());
129 if (value.isMap())
130 return convertCborMap(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(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(convertFromVariant(pair.first, fpTrimming),
151 convertFromVariant(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(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('=');
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, "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(contents, Double).toDiagnosticNotation(opts)
229 << Qt::endl;
230}
231
232CborConverter::CborConverter()
233{
234 qRegisterMetaType<QCborTag>();
235}
236
237QString CborConverter::name()
238{
239 return "cbor";
240}
241
242Converter::Direction CborConverter::directions()
243{
244 return InOut;
245}
246
247Converter::Options CborConverter::outputOptions()
248{
249 return SupportsArbitraryMapKeys;
250}
251
252const char *CborConverter::optionsHelp()
253{
254 return optionHelp;
255}
256
257bool CborConverter::probeFile(QIODevice *f)
258{
259 if (QFile *file = qobject_cast<QFile *>(f)) {
260 if (file->fileName().endsWith(QLatin1String(".cbor")))
261 return true;
262 }
263 return f->isReadable() && f->peek(3) == QByteArray("\xd9\xd9\xf7", 3);
264}
265
266QVariant CborConverter::loadFile(QIODevice *f, Converter *&outputConverter)
267{
268 const char *ptr = nullptr;
269 if (auto file = qobject_cast<QFile *>(f))
270 ptr = reinterpret_cast<char *>(file->map(0, file->size()));
271
272 QByteArray mapped = QByteArray::fromRawData(ptr, ptr ? f->size() : 0);
273 QCborStreamReader reader(mapped);
274 if (!ptr)
275 reader.setDevice(f);
276
277 if (reader.isTag() && reader.toTag() == QCborKnownTags::Signature)
278 reader.next();
279
280 QCborValue contents = QCborValue::fromCbor(reader);
281 qint64 offset = reader.currentOffset();
282 if (reader.lastError()) {
283 fprintf(stderr, "Error loading CBOR contents (byte %lld): %s\n", offset,
284 qPrintable(reader.lastError().toString()));
285 fprintf(stderr, " bytes: %s\n",
286 (ptr ? mapped.mid(offset, 9) : f->read(9)).toHex(' ').constData());
287 exit(EXIT_FAILURE);
288 } else if (offset < mapped.size() || (!ptr && f->bytesAvailable())) {
289 fprintf(stderr, "Warning: bytes remaining at the end of the CBOR stream\n");
290 }
291
292 if (outputConverter == nullptr)
293 outputConverter = &cborDiagnosticDumper;
294 else if (outputConverter == null)
295 return QVariant();
296 else if (!outputConverter->outputOptions().testFlag(SupportsArbitraryMapKeys))
297 return contents.toVariant();
298 return convertCborValue(contents);
299}
300
301void CborConverter::saveFile(QIODevice *f, const QVariant &contents, const QStringList &options)
302{
303 bool useSignature = true;
304 bool useIntegers = true;
305 enum { Yes, No, Always } useFloat16 = Yes, useFloat = Yes;
306
307 for (const QString &s : options) {
308 QStringList pair = s.split('=');
309 if (pair.size() == 2) {
310 if (pair.first() == "convert-float-to-int") {
311 if (pair.last() == "yes") {
312 useIntegers = true;
313 continue;
314 } else if (pair.last() == "no") {
315 useIntegers = false;
316 continue;
317 }
318 }
319
320 if (pair.first() == "float16") {
321 if (pair.last() == "no") {
322 useFloat16 = No;
323 continue;
324 } else if (pair.last() == "yes") {
325 useFloat16 = Yes;
326 continue;
327 } else if (pair.last() == "always") {
328 useFloat16 = Always;
329 continue;
330 }
331 }
332
333 if (pair.first() == "float32") {
334 if (pair.last() == "no") {
335 useFloat = No;
336 continue;
337 } else if (pair.last() == "yes") {
338 useFloat = Yes;
339 continue;
340 } else if (pair.last() == "always") {
341 useFloat = Always;
342 continue;
343 }
344 }
345
346 if (pair.first() == "signature") {
347 if (pair.last() == "yes") {
348 useSignature = true;
349 continue;
350 } else if (pair.last() == "no") {
351 useSignature = false;
352 continue;
353 }
354 }
355 }
356
357 fprintf(stderr, "Unknown CBOR format option '%s'. Valid options are:\n%s",
358 qPrintable(s), optionHelp);
359 exit(EXIT_FAILURE);
360 }
361
362 QCborValue v = convertFromVariant(contents,
363 useFloat16 == Always ? Float16 : useFloat == Always ? Float : Double);
364 QCborStreamWriter writer(f);
365 if (useSignature)
366 writer.append(QCborKnownTags::Signature);
367
368 QCborValue::EncodingOptions opts;
369 if (useIntegers)
370 opts |= QCborValue::UseIntegers;
371 if (useFloat != No)
372 opts |= QCborValue::UseFloat;
373 if (useFloat16 != No)
374 opts |= QCborValue::UseFloat16;
375 v.toCbor(writer, opts);
376}
377
378