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