Commit c12a5f28 authored by Volker Krause's avatar Volker Krause
Browse files

Progress ticket parsing

Top-level structures should be covered now, and we validate the input data
against those.
parent 96f5bf66
......@@ -53,6 +53,16 @@ private Q_SLOTS:
QFETCH(bool, isVdv);
QCOMPARE(VdvTicketParser::maybeVdvTicket(input), isVdv);
}
void testTicket()
{
const auto data = QByteArray::fromHex("00f569d018f111d017d43b7d68003b8268008532da110000030000000000000992000000000000db0c000000000000000000000000dc0f1117d40000000004b000000000000018f110007a18f13b7974cfd400000017d48a00000105d601000105d602474200000000005644561400");
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}));
}
};
QTEST_APPLESS_MAIN(VdvTicketTest)
......
......@@ -29,6 +29,8 @@ enum : uint8_t {
TagCaReference = 0x42,
TagOneByteSize = 0x81,
TagTwoByteSize = 0x82,
TagTicketProductData = 0x85,
TagTicketProductTransactionData = 0x8A,
};
enum : uint16_t {
......@@ -38,6 +40,10 @@ enum : uint16_t {
TagCertificateContent = 0x5F4E,
};
enum {
MinimumTicketDataSize = 111,
};
#pragma pack(push)
#pragma pack(1)
......@@ -265,6 +271,38 @@ struct VdvTicketHeader
VdvDateTimeCompact endDt;
};
/** Product-specific ticket data block.
* Contains a set of TLV elements.
*/
struct VdvTicketProductData : public VdvSimpleDataBlock<uint8_t, TagTicketProductData> {};
/** Ticket transaction data block. */
struct VdvTicketTransactionData
{
uint16_t kvpOrgId;
uint8_t terminalId[5];
VdvDateTimeCompact dt;
uint8_t locationId[6];
};
/** Product-specific transaction data block (variable length). */
struct VdvTicketProductTransactionData : public VdvSimpleDataBlock<uint8_t, TagTicketProductTransactionData> {};
/** Ticket issuer data block. */
struct VdvTicketIssueData
{
uint32_t samSeq1;
uint8_t version;
uint32_t samSeq2;
uint8_t samId[3];
};
/** Ticket trailer, after padding. */
struct VdvTicketTrailer
{
const char identifier[3];
uint16_t version;
};
#pragma pack(pop)
}
......
......@@ -17,6 +17,7 @@
#include "vdvticket.h"
#include "vdvdata_p.h"
#include "logging.h"
#include <QDebug>
......@@ -39,23 +40,58 @@ VdvTicket::VdvTicket()
VdvTicket::VdvTicket(const QByteArray &data)
: d(new VdvTicketPrivate)
{
qDebug() << data.toHex() << data.size();
if ((unsigned)data.size() < sizeof(VdvTicketHeader)) {
qWarning() << "Ticket data too small";
if (data.size() < MinimumTicketDataSize) {
qCWarning(Log) << "Ticket data too small" << data.size();
return;
}
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);
if (!productBlock->isValid() || productBlock->size() + offset > data.size()) {
qCWarning(Log) << "Invalid product block" << productBlock->isValid() << productBlock->size() << offset << data.size();
return;
}
offset += productBlock->size();
const auto transactionBlock = reinterpret_cast<const VdvTicketTransactionData*>(data.constData() + offset);
qDebug() << "transaction block:" << qFromBigEndian(transactionBlock->kvpOrgId);
offset += sizeof(VdvTicketTransactionData);
const auto prodTransactionBlock = reinterpret_cast<const VdvTicketProductTransactionData*>(data.constData() + offset);
if (!prodTransactionBlock->isValid() || prodTransactionBlock->size() + offset > data.size()) {
qCWarning(Log) << "Invalid product transaction block" << prodTransactionBlock->isValid() << prodTransactionBlock->size() << offset << data.size();
return;
}
offset += prodTransactionBlock->size();
const auto issueData = reinterpret_cast<const VdvTicketIssueData*>(data.constData() + offset);
qDebug() << issueData->version << QByteArray((const char*)&issueData->samId, 3).toHex();
offset += sizeof(VdvTicketIssueData);
qDebug() << "padding:" << std::max(111 - offset, 0);
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.";
return;
}
d->m_data = data;
const auto hdr = reinterpret_cast<const VdvTicketHeader*>(d->m_data.constData());
qDebug() << qFromBigEndian(hdr->ticketId) << qFromBigEndian(hdr->kvpOrgId) << qFromBigEndian(hdr->productId) << qFromBigEndian(hdr->pvOrgId);
// TODO temporary
qDebug() << qFromBigEndian(hdr->ticketId) << issuerId() << qFromBigEndian(hdr->productId) << qFromBigEndian(hdr->pvOrgId);
qDebug() << "begin:" << beginDateTime();
qDebug() << "end:" << endDateTime();
// iterate over TLV blocks
int offset = sizeof(VdvTicketHeader);
while (offset < d->m_data.size() - 1) {
qDebug() << "tag:" << (uint8_t)d->m_data[offset] << "size:" << (uint8_t)d->m_data[offset + 1] << "remaining:" << (d->m_data.size() - offset - (uint8_t)d->m_data[offset + 1]);
offset += (uint8_t)d->m_data[offset + 1] + 2;
// iterate over TLV content
int tlvOff = 0;
while (tlvOff < productBlock->contentSize()) {
const auto tlvSize = (uint8_t)productBlock->contentData()[tlvOff + 1];
qDebug() << "tag:" << (uint8_t)productBlock->contentData()[tlvOff] << "size:" << tlvSize << "content:" << QByteArray((const char*)productBlock->contentData() + tlvOff + 2, tlvSize).toHex();
tlvOff += tlvSize + 2;
}
}
......@@ -87,3 +123,13 @@ QDateTime KItinerary::VdvTicket::endDateTime() const
const auto hdr = reinterpret_cast<const VdvTicketHeader*>(d->m_data.constData());
return dtCompactToQdt(hdr->endDt);
}
int VdvTicket::issuerId() const
{
if (d->m_data.isEmpty()) {
return 0;
}
const auto hdr = reinterpret_cast<const VdvTicketHeader*>(d->m_data.constData());
return qFromBigEndian(hdr->kvpOrgId);
}
......@@ -39,6 +39,9 @@ class KITINERARY_EXPORT VdvTicket
/** End of the validity of this ticket. */
Q_PROPERTY(QDateTime endDateTime READ endDateTime)
/** VDV organization identifier of the ticket issuer. */
Q_PROPERTY(int issuerId READ issuerId)
public:
VdvTicket();
VdvTicket(const QByteArray &data);
......@@ -48,6 +51,7 @@ public:
QDateTime beginDateTime() const;
QDateTime endDateTime() const;
int issuerId() const;
private:
QExplicitlySharedDataPointer<VdvTicketPrivate> d;
......
......@@ -37,6 +37,8 @@ namespace KItinerary {
* which isn't entirely surprising given this also exists in a NFC card variant.
*
* Do not use directly, only installed for use in tooling.
*
* @see https://de.wikipedia.org/wiki/VDV-Kernapplikation
*/
class KITINERARY_EXPORT VdvTicketParser
{
......
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