1 /*!
2 * \copyright Copyright (c) 2015-2021 Governikus GmbH & Co. KG, Germany
3 */
4
5 #include "ReaderConfigurationParser.h"
6
7 #include <QJsonArray>
8 #include <QJsonDocument>
9 #include <QJsonObject>
10 #include <QLoggingCategory>
11 #include <QVersionNumber>
12
13
14 using namespace governikus;
15
Q_DECLARE_LOGGING_CATEGORY(card_drivers)16 Q_DECLARE_LOGGING_CATEGORY(card_drivers)
17
18
19 ReaderConfigurationParser::EntryParser::EntryParser(const QJsonValue& pJsonValue)
20 : mJsonValue(pJsonValue)
21 {
22 }
23
24
~EntryParser()25 ReaderConfigurationParser::EntryParser::~EntryParser()
26 {
27 }
28
29
getDriverUrl(const QJsonObject & pObject) const30 QString ReaderConfigurationParser::EntryParser::getDriverUrl(const QJsonObject& pObject) const
31 {
32 const QJsonValue driversValue = pObject.value(QLatin1String("Drivers"));
33 if (!driversValue.isArray())
34 {
35 qCWarning(card_drivers) << "The value of 'Drivers' must be an array";
36 return QString();
37 }
38
39 const QJsonArray& driversArray = driversValue.toArray();
40 for (const auto& entry : driversArray)
41 {
42 const QJsonObject& obj = entry.toObject();
43 if (obj.isEmpty())
44 {
45 qCWarning(card_drivers) << "Drivers entry must be a valid object";
46 return QString();
47 }
48
49 const QJsonValue& platforms = obj.value(QLatin1String("Platforms"));
50 if (!platforms.isArray())
51 {
52 qCWarning(card_drivers) << "Invalid or missing Platforms tag";
53 return QString();
54 }
55
56 const QString& url = obj.value(QLatin1String("URL")).toString();
57 if (url.isEmpty())
58 {
59 qCWarning(card_drivers) << "Invalid or missing URL tag";
60 return QString();
61 }
62
63 if (matchPlatform(platforms.toArray()))
64 {
65 return url;
66 }
67 }
68
69 // No URL for this platform found, but entry is syntactically correct:
70 // return empty non-null string.
71 return QLatin1String("");
72 }
73
74
matchPlatform(const QJsonArray & pPlatforms,const QOperatingSystemVersion & pCurrentVersion) const75 bool ReaderConfigurationParser::EntryParser::matchPlatform(const QJsonArray& pPlatforms, const QOperatingSystemVersion& pCurrentVersion) const
76 {
77 QString currentOS;
78 switch (pCurrentVersion.type())
79 {
80 case QOperatingSystemVersion::Windows:
81 currentOS = QStringLiteral("win");
82 break;
83
84 case QOperatingSystemVersion::MacOS:
85 currentOS = QStringLiteral("mac");
86 break;
87
88 default:
89 currentOS = QStringLiteral("unknown");
90 }
91
92 const auto& parseSystemVersion = [&pCurrentVersion](const QString& pVersion){
93 if (pVersion.isEmpty())
94 {
95 return pCurrentVersion;
96 }
97
98 const auto& number = QVersionNumber::fromString(pVersion);
99 const auto minor = number.segmentCount() > 1 ? number.minorVersion() : -1;
100 const auto micro = number.segmentCount() > 2 ? number.microVersion() : -1;
101 return QOperatingSystemVersion(pCurrentVersion.type(), number.majorVersion(), minor, micro);
102 };
103
104 for (const auto& entry : pPlatforms)
105 {
106 const auto& obj = entry.toObject();
107 if (obj.value(QLatin1String("os")).toString() == currentOS)
108 {
109 const auto& min = obj.value(QLatin1String("min")).toString();
110 const auto& max = obj.value(QLatin1String("max")).toString();
111
112 if (pCurrentVersion >= parseSystemVersion(min) && pCurrentVersion <= parseSystemVersion(max))
113 {
114 return true;
115 }
116 }
117 }
118
119 return false;
120 }
121
122
parse() const123 ReaderConfigurationInfo ReaderConfigurationParser::EntryParser::parse() const
124 {
125 if (!mJsonValue.isObject())
126 {
127 return fail(QStringLiteral("Cannot parse Json value: object expected"));
128 }
129
130 const QJsonObject& object = mJsonValue.toObject();
131
132 bool parseOk = false;
133 const uint vendorId = object.value(QLatin1String("VendorId")).toString().toUInt(&parseOk, 16);
134 if (!parseOk)
135 {
136 return fail(QStringLiteral("Invalid or missing vendor id"));
137 }
138
139 const uint productId = object.value(QLatin1String("ProductId")).toString().toUInt(&parseOk, 16);
140 if (!parseOk)
141 {
142 return fail(QStringLiteral("Invalid or missing product id"));
143 }
144
145 const QString& name = object.value(QLatin1String("Name")).toString();
146 if (name.isEmpty())
147 {
148 return fail(QStringLiteral("Invalid or missing name"));
149 }
150
151 const QString& url = getDriverUrl(object);
152 if (url.isNull())
153 {
154 return fail(QStringLiteral("Invalid driver URL entry"));
155 }
156
157 // If Pattern is missing or empty => use name (exact match).
158 const QString& pattern = object.value(QLatin1String("Pattern")).toString();
159
160 const QString& icon = object.value(QLatin1String("Icon")).toString();
161 const QString& iconWithNPA = object.value(QLatin1String("IconWithNPA")).toString();
162
163 return ReaderConfigurationInfo(vendorId, productId, name, url, pattern, icon, iconWithNPA);
164 }
165
166
fail(const QString & pLogMessage) const167 ReaderConfigurationInfo ReaderConfigurationParser::EntryParser::fail(const QString& pLogMessage) const
168 {
169 qCWarning(card_drivers) << pLogMessage;
170
171 return ReaderConfigurationInfo();
172 }
173
174
parse(const QByteArray & pData)175 QVector<ReaderConfigurationInfo> ReaderConfigurationParser::parse(const QByteArray& pData)
176 {
177 QJsonParseError error;
178 QJsonDocument doc = QJsonDocument::fromJson(pData, &error);
179 if (error.error != QJsonParseError::NoError)
180 {
181 return fail(QStringLiteral("Json parsing failed. Error at offset %1: %2").arg(error.offset).arg(error.errorString()));
182 }
183
184 const QJsonObject& rootObject = doc.object();
185 if (rootObject.isEmpty())
186 {
187 return fail(QStringLiteral("Expected object at top level"));
188 }
189
190 if (!rootObject.contains(QStringLiteral("SupportedDevices")))
191 {
192 return fail(QStringLiteral("Root object does not contain a property named 'SupportedDevices'"));
193 }
194
195 // Root object OK. Look at child list.
196 const QJsonValue& devicesValue = rootObject.value(QStringLiteral("SupportedDevices"));
197 if (!devicesValue.isArray())
198 {
199 return fail(QStringLiteral("The value of 'SupportedDevices' must be an array"));
200 }
201
202 const QJsonArray& devicesArray = devicesValue.toArray();
203 QVector<ReaderConfigurationInfo> infos;
204 for (const auto& entry : devicesArray)
205 {
206 auto info = EntryParser(entry).parse();
207 if (info.isKnownReader())
208 {
209 if (hasUniqueId(info, infos))
210 {
211 infos += info;
212 }
213 else
214 {
215 const auto& json = QString::fromUtf8(QJsonDocument(entry.toObject()).toJson());
216 return fail(QStringLiteral("Invalid reader configuration entry: '%1' duplicate reader information").arg(json));
217 }
218 }
219 else
220 {
221 return fail(QStringLiteral("Invalid reader configuration entry: ") + QString::fromUtf8(QJsonDocument(entry.toObject()).toJson()));
222 }
223 }
224
225 return infos;
226 }
227
228
fail(const QString & pLogMessage)229 QVector<ReaderConfigurationInfo> ReaderConfigurationParser::fail(const QString& pLogMessage)
230 {
231 qCWarning(card_drivers) << pLogMessage;
232 return QVector<ReaderConfigurationInfo>();
233 }
234
235
hasUniqueId(const ReaderConfigurationInfo & pInfo,const QVector<ReaderConfigurationInfo> & pInfos)236 bool ReaderConfigurationParser::hasUniqueId(const ReaderConfigurationInfo& pInfo, const QVector<ReaderConfigurationInfo>& pInfos)
237 {
238 const uint vendorId = pInfo.getVendorId();
239 const uint productId = pInfo.getProductId();
240 const QString& name = pInfo.getName();
241 const QString& pattern = pInfo.getPattern();
242
243 for (const ReaderConfigurationInfo& info : pInfos)
244 {
245 if (vendorId > 0 && productId > 0 && vendorId == info.getVendorId() && productId == info.getProductId())
246 {
247 return false;
248 }
249
250 if (name == info.getName())
251 {
252 return false;
253 }
254
255 if (!pattern.isEmpty() && pattern == info.getPattern())
256 {
257 return false;
258 }
259 }
260
261 return true;
262 }
263