Commit 2d3ed06a authored by Volker Krause's avatar Volker Krause
Browse files

Extend VDV ticket parsing and generic extraction

parent 7c10ad33
......@@ -56,12 +56,32 @@ private Q_SLOTS:
void testTicket()
{
const auto data = QByteArray::fromHex("00f569d018f111d017d43b7d68003b8268008532da110000030000000000000992000000000000db0c000000000000000000000000dc0f1117d40000000004b000000000000018f110007a18f13b7974cfd400000017d48a00000105d601000105d602474200000000005644561400");
auto data = QByteArray::fromHex("00f569d018f111d017d43b7d68003b8268008532da110000030000000002000992000000000000db0c000000000000000000000000dc0f1117d40000000004b000000000000018f110007a18f13b7974cfd400000017d48a00000105d601000105d602474200000000005644561400");
VdvTicket ticket(data);
QCOMPARE(ticket.issuerId(), 6385);
QCOMPARE(ticket.beginDateTime(), QDateTime({2019, 11, 29}, {13, 0}));
QCOMPARE(ticket.endDateTime(), QDateTime({2019, 12, 2}, {13, 0}));
QCOMPARE(ticket.serviceClass(), VdvTicket::SecondClass);
QCOMPARE(ticket.person(), Person());
QCOMPARE(ticket.ticketNumber(), QStringLiteral("16083408"));
data = QByteArray::fromHex("001a4bab1874283e184434ba000134bb18008541da110001000000000003000000000000001a4bdb1502199610144B6174696523447261676F6E00000000dc150000000000000000000000000000000000000000001874110064187434b87128ff7a126918748a0000062e9e0100062e9e007d895644561107");
ticket = VdvTicket(data);
QCOMPARE(ticket.issuerId(), 6260);
QCOMPARE(ticket.beginDateTime(), QDateTime({2016, 5, 26}, {0, 0, 2}));
QCOMPARE(ticket.endDateTime(), QDateTime({2016, 5, 27}, {3, 0}));
QCOMPARE(ticket.serviceClass(), VdvTicket::FirstClassUpgrade);
QCOMPARE(ticket.person().familyName(), QStringLiteral("Dragon"));
QCOMPARE(ticket.person().givenName(), QStringLiteral("Katie"));
QCOMPARE(ticket.ticketNumber(), QStringLiteral("1723307"));
data = QByteArray::fromHex("00f569d018f111d017d43b7d68003b8268008532da110000030000000000000992000000000000db0c00000000004B33654044346Edc0f1117d40000000004b000000000000018f110007a18f13b7974cfd400000017d48a00000105d601000105d602474200000000005644561400");
ticket = VdvTicket(data);
QCOMPARE(ticket.serviceClass(), VdvTicket::UnknownClass);
QCOMPARE(ticket.person().familyName(), QStringLiteral("D"));
QCOMPARE(ticket.person().givenName(), QStringLiteral("K"));
QCOMPARE(ticket.ticketNumber(), QStringLiteral("16083408"));
}
};
......
......@@ -17,9 +17,12 @@
#include "genericvdvextractor_p.h"
#include <KItinerary/JsonLdDocument>
#include <KItinerary/VdvTicket>
#include <KItinerary/VdvTicketParser>
#include <KLocalizedString>
#include <QByteArray>
#include <QJsonArray>
#include <QJsonObject>
......@@ -43,23 +46,32 @@ QJsonArray GenericVdvExtractor::extract(const QByteArray &data)
trip.insert(QStringLiteral("provider"), org);
QJsonObject seat;
seat.insert(QStringLiteral("@type"), QLatin1String("Seat"));
// seat.insert(QStringLiteral("seatingType"), vdv.serviceClass());
switch (vdv.serviceClass()) {
case VdvTicket::FirstClass:
case VdvTicket::FirstClassUpgrade:
seat.insert(QStringLiteral("seatingType"), QStringLiteral("1"));
break;
case VdvTicket::SecondClass:
seat.insert(QStringLiteral("seatingType"), QStringLiteral("2"));
break;
default:
break;
}
QJsonObject ticket;
ticket.insert(QStringLiteral("@type"), QLatin1String("Ticket"));
ticket.insert(QStringLiteral("ticketToken"), QString(QLatin1String("aztecbin:") + QString::fromLatin1(data.toBase64())));
ticket.insert(QStringLiteral("ticketedSeat"), seat);
QJsonObject person;
person.insert(QStringLiteral("@type"), QLatin1String("Person"));
// person.insert(QStringLiteral("name"), vdv.passengerName());
if (vdv.serviceClass() == VdvTicket::FirstClassUpgrade) {
ticket.insert(QStringLiteral("name"), i18n("Upgrade"));
}
QJsonObject res;
res.insert(QStringLiteral("@type"), QLatin1String("TrainReservation"));
res.insert(QStringLiteral("reservationFor"), trip);
// res.insert(QStringLiteral("reservationNumber"), vdv.ticketNumber());
res.insert(QStringLiteral("reservationNumber"), vdv.ticketNumber());
res.insert(QStringLiteral("reservedTicket"), ticket);
res.insert(QStringLiteral("underName"), person);
res.insert(QStringLiteral("underName"), JsonLdDocument::toJson(vdv.person()));
return {res};
}
......@@ -28,9 +28,21 @@ class VdvTicketPrivate : public QSharedData
{
public:
QByteArray m_data;
template <typename T> const T* productData() const;
};
}
template <typename T>
const T* VdvTicketPrivate::productData() const
{
if (m_data.isEmpty()) {
return nullptr;
}
const auto productBlock = reinterpret_cast<const VdvTicketProductData*>(m_data.constData() + sizeof(VdvTicketHeader));
return productBlock->contentByTag<T>();
}
VdvTicket::VdvTicket()
: d(new VdvTicketPrivate)
......@@ -46,7 +58,6 @@ VdvTicket::VdvTicket(const QByteArray &data)
}
static_assert(sizeof(VdvTicketHeader) < MinimumTicketDataSize, "");
const auto hdr = reinterpret_cast<const VdvTicketHeader*>(data.constData());
int offset = sizeof(VdvTicketHeader);
const auto productBlock = reinterpret_cast<const VdvTicketProductData*>(data.constData() + offset);
......@@ -71,21 +82,19 @@ VdvTicket::VdvTicket(const QByteArray &data)
qDebug() << issueData->version << QByteArray((const char*)&issueData->samId, 3).toHex();
offset += sizeof(VdvTicketIssueData);
qDebug() << "padding:" << std::max(111 - offset, 0);
// 0 padding to reach at least 111 bytes
offset += std::max(111 - offset - (int)sizeof(VdvTicketTrailer), 0);
const auto trailer = reinterpret_cast<const VdvTicketTrailer*>(data.constData() + offset);
qDebug() << QByteArray(trailer->identifier, 3) << qFromBigEndian(trailer->version);
if (memcmp(trailer->identifier, "VDV", 3) != 0) {
qCWarning(Log) << "Invalid ticket trailer identifier.";
qCWarning(Log) << "Invalid ticket trailer identifier:" << QByteArray(trailer->identifier, 3) << qFromBigEndian(trailer->version);
return;
}
d->m_data = data;
// TODO temporary
qDebug() << qFromBigEndian(hdr->ticketId) << issuerId() << qFromBigEndian(hdr->productId) << qFromBigEndian(hdr->pvOrgId);
qDebug() << "begin:" << beginDateTime();
qDebug() << "end:" << endDateTime();
#if 0
const auto hdr = reinterpret_cast<const VdvTicketHeader*>(data.constData());
qDebug() << qFromBigEndian(hdr->productId) << qFromBigEndian(hdr->pvOrgId);
// iterate over TLV content
auto tlv = productBlock->first();
while (tlv) {
......@@ -94,12 +103,9 @@ VdvTicket::VdvTicket(const QByteArray &data)
}
const auto basicData = productBlock->contentByTag<VdvTicketBasicData>();
if (basicData) {
qDebug() << "traveler type:" << basicData->travelerType << "class:" << basicData->serviceClass;
}
const auto travelerData = productBlock->contentByTag<VdvTicketTravelerData>();
if (travelerData) {
qDebug() << "traveler:" << travelerData->gender << QDate(travelerData->birthDate.year(), travelerData->birthDate.month(), travelerData->birthDate.day()) << QByteArray(travelerData->name(), travelerData->nameSize());
qDebug() << "traveler type:" << basicData->travelerType;
}
#endif
}
VdvTicket::VdvTicket(const VdvTicket&) = default;
......@@ -140,3 +146,72 @@ int VdvTicket::issuerId() const
const auto hdr = reinterpret_cast<const VdvTicketHeader*>(d->m_data.constData());
return qFromBigEndian(hdr->kvpOrgId);
}
VdvTicket::ServiceClass VdvTicket::serviceClass() const
{
const auto tlv = d->productData<VdvTicketBasicData>();
if (!tlv) {
return UnknownClass;
}
switch (tlv->serviceClass) {
case 0:
return UnknownClass;
case 1:
return FirstClass;
case 2:
return SecondClass;
case 3:
return FirstClassUpgrade;
}
qCDebug(Log) << "Unknown service class:" << tlv->serviceClass;
return UnknownClass;
}
Person VdvTicket::person() const
{
const auto tlv = d->productData<VdvTicketTravelerData>();
if (!tlv) {
return {};
}
qDebug() << "traveler:" << tlv->gender << QDate(tlv->birthDate.year(), tlv->birthDate.month(), tlv->birthDate.day()) << QByteArray(tlv->name(), tlv->nameSize());
const auto len = strnlen(tlv->name(), tlv->nameSize()); // name field can contain null bytes
if (len == 0) {
return {};
}
const auto name = QString::fromUtf8(tlv->name(), len);
Person p;
const auto idxHash = name.indexOf(QLatin1Char('#'));
const auto idxAt = name.indexOf(QLatin1Char('@'));
// encoding as first#last
if (idxHash > 0) {
p.setFamilyName(name.mid(idxHash + 1));
p.setGivenName(name.left(idxHash));
}
// encoding as f1<len>fn@l1<len>ln
else if (idxAt > 0) {
p.setFamilyName(name.at(idxAt + 1));
p.setGivenName(name.at(0));
}
// unknown encoding
else {
p.setName(name);
}
return p;
}
QString VdvTicket::ticketNumber() const
{
if (d->m_data.isEmpty()) {
return {};
}
const auto hdr = reinterpret_cast<const VdvTicketHeader*>(d->m_data.constData());
return QString::number(qFromBigEndian(hdr->ticketId));
}
......@@ -20,6 +20,8 @@
#include "kitinerary_export.h"
#include <KItinerary/Person>
#include <QDateTime>
#include <QExplicitlySharedDataPointer>
#include <QMetaType>
......@@ -41,6 +43,12 @@ class KITINERARY_EXPORT VdvTicket
/** VDV organization identifier of the ticket issuer. */
Q_PROPERTY(int issuerId READ issuerId)
/** Service class for this ticket. */
Q_PROPERTY(ServiceClass serviceClass READ serviceClass)
/** The person this ticket is valid for. */
Q_PROPERTY(KItinerary::Person person READ person)
/** Ticket number. */
Q_PROPERTY(QString ticketNumber READ ticketNumber)
public:
VdvTicket();
......@@ -53,6 +61,18 @@ public:
QDateTime endDateTime() const;
int issuerId() const;
enum ServiceClass {
UnknownClass = 0,
FirstClass = 1,
SecondClass = 2,
FirstClassUpgrade = 3
};
Q_ENUM(ServiceClass)
ServiceClass serviceClass() const;
Person person() const;
QString ticketNumber() const;
private:
QExplicitlySharedDataPointer<VdvTicketPrivate> d;
};
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment