KHealthCertificate

shcparser.cpp
1 /*
2  SPDX-FileCopyrightText: 2021 Volker Krause <[email protected]>
3  SPDX-License-Identifier: LGPL-2.0-or-later
4 */
5 
6 #include "shcparser_p.h"
7 #include "jwtparser_p.h"
8 #include "kvaccinationcertificate.h"
9 #include "logging.h"
10 
11 #include <QByteArray>
12 #include <QDebug>
13 #include <QFile>
14 #include <QJsonArray>
15 #include <QJsonDocument>
16 #include <QJsonObject>
17 #include <QVariant>
18 
19 QVariant ShcParser::parse(const QByteArray &data)
20 {
21  if (!data.startsWith("shc:/")) {
22  return {};
23  }
24 
25  if (data.indexOf('/', 5) > 0) {
26  qCWarning(Log) << "SHC chunked data not supported yet!";
27  return {};
28  }
29 
30  QByteArray unpacked;
31  unpacked.reserve(data.size() / 2);
32  for (int i = 5; i < data.size() - 1; i += 2) {
33  unpacked.push_back((data[i] - '0') * 10 + (data[i + 1] - '0') + 45);
34  }
35 
36  JwtParser jwt;
37  jwt.parse(unpacked);
38 
39  const auto nbf = QDateTime::fromSecsSinceEpoch(jwt.payload().value(QLatin1String("nbf")).toDouble());
40  const auto vc = jwt.payload().value(QLatin1String("vc")).toObject();
41  const auto types = vc.value(QLatin1String("type")).toArray();
42  //qDebug().noquote() << QJsonDocument(vc).toJson();
43  for (const auto &type : types) {
44  if (type.toString() == QLatin1String("https://smarthealth.cards#immunization")) {
45  auto cert = parseImmunization(vc.value(QLatin1String("credentialSubject")).toObject());
46  cert.setCertificateIssueDate(nbf);
47  cert.setCertificateIssuer(jwt.payload().value(QLatin1String("iss")).toString());
48  cert.setRawData(data);
49  cert.setSignatureState(jwt.signatureState());
50  return cert;
51  }
52  }
53 
54  return {};
55 }
56 
57 KVaccinationCertificate ShcParser::parseImmunization(const QJsonObject &obj)
58 {
59  const auto entries = obj.value(QLatin1String("fhirBundle")).toObject().value(QLatin1String("entry")).toArray();
61  for (const auto &entryV : entries) {
62  const auto res = entryV.toObject().value(QLatin1String("resource")).toObject();
63  const auto resourceType = res.value(QLatin1String("resourceType")).toString();
64  if (resourceType == QLatin1String("Patient")) {
65  cert.setDateOfBirth(QDate::fromString(res.value(QLatin1String("birthDate")).toString(), Qt::ISODate));
66  const auto nameArray = res.value(QLatin1String("name")).toArray();
67  if (nameArray.size() != 1) {
68  return {};
69  }
70  const auto nameObj = nameArray.at(0).toObject();
71  const auto given = nameObj.value(QLatin1String("given")).toArray();
72  QStringList nameParts;
73  nameParts.reserve(given.size() + 1);
74  for (const auto &givenV : given) {
75  nameParts.push_back(givenV.toString());
76  }
77  nameParts.push_back(nameObj.value(QLatin1String("family")).toString());
78  cert.setName(nameParts.join(QLatin1Char(' ')));
79  }
80  else if (resourceType == QLatin1String("Immunization")) {
81  if (res.value(QLatin1String("status")).toString() != QLatin1String("completed")) {
82  continue;
83  }
84  const auto dt = QDate::fromString(res.value(QLatin1String("occurrenceDateTime")).toString(), Qt::ISODate);
85  if (cert.date().isValid() && cert.date() > dt) { // TODO alternatively, emit two certs, one for each dose?
86  cert.setDose(cert.dose() + 1);
87  continue;
88  }
89 
90  cert.setDate(dt);
91  cert.setDose(std::max(1, cert.dose() + 1));
92 
93  const auto vacCode = res.value(QLatin1String("vaccineCode")).toObject().value(QLatin1String("coding")).toArray();
94  if (vacCode.size() != 1) {
95  continue;
96  }
97  const auto vacObj = vacCode.at(0).toObject();
98  QJsonObject cvxData;
99  if (vacObj.value(QLatin1String("system")).toString() == QLatin1String("http://hl7.org/fhir/sid/cvx")) {
100  QFile cvxFile(QStringLiteral(":/org.kde.khealthcertificate/shc/hl7-cvx-codes.json"));
101  if (!cvxFile.open(QFile::ReadOnly)) {
102  qCWarning(Log) << cvxFile.errorString();
103  }
104  const auto cvxDb = QJsonDocument::fromJson(cvxFile.readAll()).object();
105  cvxData = cvxDb.value(vacObj.value(QLatin1String("code")).toString()).toObject();
106  }
107 
108  if (cvxData.isEmpty()) {
109  cert.setVaccine(vacObj.value(QLatin1String("system")).toString() + QLatin1Char('/') + vacObj.value(QLatin1String("code")).toString());
110  } else {
111  cert.setVaccine(cvxData.value(QLatin1String("n")).toString());
112  cert.setDisease(cvxData.value(QLatin1String("d")).toString());
113  cert.setManufacturer(cvxData.value(QLatin1String("m")).toString());
114  }
115  }
116  else {
117  qCDebug(Log) << "unhandled resource type:" << resourceType << res;
118  }
119  }
120  return cert;
121 }
QJsonDocument fromJson(const QByteArray &json, QJsonParseError *error)
void reserve(int size)
void push_back(const T &value)
void reserve(int alloc)
bool startsWith(const QByteArray &ba) const const
QDateTime fromSecsSinceEpoch(qint64 secs, Qt::TimeSpec spec, int offsetSeconds)
QString join(const QString &separator) const const
QDate fromString(const QString &string, Qt::DateFormat format)
int indexOf(char ch, int from) const const
bool isEmpty() const const
QString toString() const const
Type type(const QSqlDatabase &db)
QJsonObject toObject() const const
QJsonArray toArray() const const
void push_back(char ch)
A vaccination certificate.
QJsonValue value(const QString &key) const const
int size() const const
Types types(const QStringList &mimeTypes)
This file is part of the KDE documentation.
Documentation copyright © 1996-2021 The KDE developers.
Generated on Mon Oct 18 2021 23:21:56 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.