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

Rework basic types used in VDV ticket structs

Most importantly this now makes the endian conversion transparent, even
for the unusual 3 byte numeric types we couldn't handle before. This also
adds implicit conversion for the date and date/time types.
parent 74a08fe1
/*
SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KITINERARY_VDVBASICTYPES_H
#define KITINERARY_VDVBASICTYPES_H
#include <QDateTime>
#include <cstdint>
/** @file vdvbasictypes.h
* Low-level data types used in VDV ticket structs.
*/
namespace KItinerary {
#pragma pack(push)
#pragma pack(1)
/** Two-digit BCD encoded number. */
template <int N>
struct VdvBcdNumber
{
static_assert(N > 0 && N <= 4);
uint8_t data[N];
inline constexpr uint32_t value() const
{
uint32_t v = 0;
for (int i = 0; i < N; ++i) {
v *= 100;
v += ((data[i] & 0xF0) >> 4) * 10 + (data[i] & 0x0F);
}
return v;
}
inline constexpr operator uint32_t() const { return value(); }
};
/** Date encoded as 8 BCD digits. */
struct VdvBcdDate
{
VdvBcdNumber<2> bcdYear;
VdvBcdNumber<1> bcdMonth;
VdvBcdNumber<1> bcdDay;
inline QDate value() const
{
return QDate(bcdYear, bcdMonth, bcdDay);
}
inline operator QDate() const { return value(); }
};
/** Big-endian numeric value. */
template <int N>
struct VdvNumber
{
static_assert(N > 0 && N <= 4);
uint8_t data[N];
inline constexpr uint32_t value() const
{
uint32_t v = 0;
for (int i = 0; i < N; ++i) {
v <<= 8;
v |= data[i];
}
return v;
}
inline constexpr operator uint32_t() const { return value(); }
};
/** Date/time representation encoded in 4 byte. */
struct VdvDateTimeCompact
{
VdvNumber<4> data;
inline QDateTime value() const
{
return QDateTime(
{
(int)((data & 0b1111'1110'0000'0000'0000'0000'0000'0000) >> 25) + 1990,
(int)(data & 0b0000'0001'1110'0000'0000'0000'0000'0000) >> 21,
(int)(data & 0b0000'0000'0001'1111'0000'0000'0000'0000) >> 16
}, {
(int)(data & 0b0000'0000'0000'0000'1111'1000'0000'0000) >> 11,
(int)(data & 0b0000'0000'0000'0000'0000'0111'1110'0000) >> 5,
(int)(data & 0b0000'0000'0000'0000'0000'0000'0001'1111) * 2
});
}
inline operator QDateTime() const { return value(); }
};
#pragma pack(pop)
}
#endif // KITINERARY_VDVBASICTYPES_H
......@@ -152,10 +152,10 @@ bool VdvCertificate::isSelfSigned() const
return memcmp(&certKey()->car, certKey()->chr.name, sizeof(VdvCaReference)) == 0;
}
QDate KItinerary::VdvCertificate::endOfValidity() const
QDate VdvCertificate::endOfValidity() const
{
const auto key = certKey();
return QDate(key->date.year(), key->date.month(), key->date.day());
return key->date;
}
BER::Element VdvCertificate::header() const
......
......@@ -8,8 +8,8 @@
#define KITINERARY_VDVDATA_P_H
#include "../tlv/berelement_p.h"
#include "vdvbasictypes.h"
#include <QtEndian>
#include <cstdint>
namespace KItinerary {
......@@ -35,38 +35,6 @@ enum {
#pragma pack(push)
#pragma pack(1)
/** Two-digit BCD encoded number. */
struct VdvBcdNumber
{
uint8_t data;
uint8_t value() const
{
return ((data & 0xF0) >> 4) * 10 + (data & 0x0F);
}
};
/** Date encoded as 8 BCD digits. */
struct VdvBcdDate
{
VdvBcdNumber bcdYear[2];
VdvBcdNumber bcdMonth;
VdvBcdNumber bcdDay;
inline uint16_t year() const
{
return bcdYear[0].value() * 100 + bcdYear[1].value();
}
inline uint8_t month() const
{
return bcdMonth.value();
}
inline uint8_t day() const
{
return bcdDay.value();
}
};
/** Certificate Authority Reference (CAR) content. */
struct VdvCaReference
{
......@@ -114,44 +82,13 @@ struct VdvCertificateKey {
}
};
/** Date/time representation encoded in 4 byte. */
struct VdvDateTimeCompact
{
uint32_t data;
inline int year() const
{
return ((qFromBigEndian(data) & 0b1111'1110'0000'0000'0000'0000'0000'0000) >> 25) + 1990;
}
inline int month() const
{
return (qFromBigEndian(data) & 0b0000'0001'1110'0000'0000'0000'0000'0000) >> 21;
}
inline int day() const
{
return (qFromBigEndian(data) & 0b0000'0000'0001'1111'0000'0000'0000'0000) >> 16;
}
inline int hour() const
{
return (qFromBigEndian(data) & 0b0000'0000'0000'0000'1111'1000'0000'0000) >> 11;
}
inline int minute() const
{
return (qFromBigEndian(data) & 0b0000'0000'0000'0000'0000'0111'1110'0000) >> 5;
}
inline int second() const
{
return (qFromBigEndian(data) & 0b0000'0000'0000'0000'0000'0000'0001'1111) * 2;
}
};
/** Ticket data header. */
struct VdvTicketHeader
{
uint32_t ticketId;
uint16_t kvpOrgId;
uint16_t productId;
uint16_t pvOrgId;
VdvNumber<4> ticketId;
VdvNumber<2> kvpOrgId;
VdvNumber<2> productId;
VdvNumber<2> pvOrgId;
VdvDateTimeCompact beginDt;
VdvDateTimeCompact endDt;
};
......@@ -159,24 +96,24 @@ struct VdvTicketHeader
/** Product specific data - basic information. */
struct VdvTicketBasicData
{
uint8_t paymentType;
uint8_t travelerType; // 1 adult, 2 child, 65 bike
uint8_t includedTravelerType1; // 0 none, 1 adult, 2 child, 251 family child
uint8_t includedTravelerCount1;
uint8_t includedTravelerType2;
uint8_t includedTravelerCount2;
uint8_t categroy;
uint8_t serviceClass; // 1 first class, 2 second class, 3 first class upgrade
uint8_t price[3]; // 24 bit big endian, price in Euro cent
uint16_t vat; // VAT rate in 0,01% steps
uint8_t priceCategory;
uint8_t productNumber[3];
VdvNumber<1> paymentType;
VdvNumber<1> travelerType; // 1 adult, 2 child, 65 bike
VdvNumber<1> includedTravelerType1; // 0 none, 1 adult, 2 child, 251 family child
VdvNumber<1> includedTravelerCount1;
VdvNumber<1> includedTravelerType2;
VdvNumber<1> includedTravelerCount2;
VdvNumber<1> categroy;
VdvNumber<1> serviceClass; // 1 first class, 2 second class, 3 first class upgrade
VdvNumber<3> price; // 24 bit big endian, price in Euro cent
VdvNumber<2> vat; // VAT rate in 0,01% steps
VdvNumber<1> priceCategory;
VdvNumber<3> productNumber;
};
/** Product specific data - traveler information. */
struct VdvTicketTravelerData
{
uint8_t gender;
VdvNumber<1> gender;
VdvBcdDate birthDate;
char nameBegin;
......@@ -193,26 +130,30 @@ struct VdvTicketTravelerData
/** Ticket transaction data block. */
struct VdvTicketTransactionData
{
uint16_t kvpOrgId;
uint8_t terminalId[5];
VdvNumber<2> kvpOrgId;
VdvNumber<1> terminalTypeCode;
VdvNumber<2> terminalNumber;
VdvNumber<2> terminalOrganizationNumber;
VdvDateTimeCompact dt;
uint8_t locationId[6];
VdvNumber<1> locationTypeCode;
VdvNumber<3> locationNumber;
VdvNumber<2> locationOrganizationNumber;
};
/** Ticket issuer data block. */
struct VdvTicketIssueData
{
uint32_t samSeq1;
uint8_t version;
uint32_t samSeq2;
uint8_t samId[3];
VdvNumber<4> samSeq1;
VdvNumber<1> version;
VdvNumber<4> samSeq2;
VdvNumber<3> samId;
};
/** Ticket trailer, after padding. */
struct VdvTicketTrailer
{
const char identifier[3];
uint16_t version;
VdvNumber<2> version;
};
#pragma pack(pop)
......
......@@ -66,7 +66,7 @@ VdvTicket::VdvTicket(const QByteArray &data)
offset += productBlock.size();
const auto transactionBlock = reinterpret_cast<const VdvTicketTransactionData*>(data.constData() + offset);
qDebug() << "transaction block:" << qFromBigEndian(transactionBlock->kvpOrgId);
qDebug() << "transaction block:" << transactionBlock->kvpOrgId;
offset += sizeof(VdvTicketTransactionData);
const auto prodTransactionBlock = BER::TypedElement<TagTicketProductTransactionData>(data, offset);
......@@ -77,7 +77,7 @@ VdvTicket::VdvTicket(const QByteArray &data)
offset += prodTransactionBlock.size();
const auto issueData = reinterpret_cast<const VdvTicketIssueData*>(data.constData() + offset);
qDebug() << issueData->version << QByteArray((const char*)&issueData->samId, 3).toHex();
qDebug() << issueData->version << issueData->samId;
offset += sizeof(VdvTicketIssueData);
// 0 padding to reach at least 111 bytes
......@@ -85,14 +85,14 @@ VdvTicket::VdvTicket(const QByteArray &data)
const auto trailer = reinterpret_cast<const VdvTicketTrailer*>(data.constData() + offset);
if (memcmp(trailer->identifier, "VDV", 3) != 0) {
qCWarning(Log) << "Invalid ticket trailer identifier:" << QByteArray(trailer->identifier, 3) << qFromBigEndian(trailer->version);
qCWarning(Log) << "Invalid ticket trailer identifier:" << QByteArray(trailer->identifier, 3) << trailer->version;
return;
}
d->m_data = data;
#if 1
const auto hdr = reinterpret_cast<const VdvTicketHeader*>(data.constData());
qDebug() << qFromBigEndian(hdr->productId) << qFromBigEndian(hdr->pvOrgId);
qDebug() << hdr->productId << hdr->pvOrgId;
// iterate over TLV content
auto tlv = productBlock.first();
while (tlv.isValid()) {
......@@ -110,11 +110,6 @@ VdvTicket::VdvTicket(const VdvTicket&) = default;
VdvTicket::~VdvTicket() = default;
VdvTicket& VdvTicket::operator=(const VdvTicket&) = default;
static QDateTime dtCompactToQdt(const VdvDateTimeCompact &dtc)
{
return QDateTime({dtc.year(), dtc.month(), dtc.day()}, {dtc.hour(), dtc.minute(), dtc.second()});
}
QDateTime VdvTicket::beginDateTime() const
{
if (d->m_data.isEmpty()) {
......@@ -122,7 +117,7 @@ QDateTime VdvTicket::beginDateTime() const
}
const auto hdr = reinterpret_cast<const VdvTicketHeader*>(d->m_data.constData());
return dtCompactToQdt(hdr->beginDt);
return hdr->beginDt;
}
QDateTime KItinerary::VdvTicket::endDateTime() const
......@@ -132,7 +127,7 @@ QDateTime KItinerary::VdvTicket::endDateTime() const
}
const auto hdr = reinterpret_cast<const VdvTicketHeader*>(d->m_data.constData());
return dtCompactToQdt(hdr->endDt);
return hdr->endDt;
}
int VdvTicket::issuerId() const
......@@ -142,7 +137,7 @@ int VdvTicket::issuerId() const
}
const auto hdr = reinterpret_cast<const VdvTicketHeader*>(d->m_data.constData());
return qFromBigEndian(hdr->kvpOrgId);
return hdr->kvpOrgId;
}
VdvTicket::ServiceClass VdvTicket::serviceClass() const
......@@ -172,7 +167,7 @@ Person VdvTicket::person() const
if (!tlv) {
return {};
}
qDebug() << "traveler:" << tlv->gender << QDate(tlv->birthDate.year(), tlv->birthDate.month(), tlv->birthDate.day()) << QByteArray(tlv->name(), tlv->nameSize(elem.contentSize()));
qDebug() << "traveler:" << tlv->gender << tlv->birthDate << QByteArray(tlv->name(), tlv->nameSize(elem.contentSize()));
const auto len = strnlen(tlv->name(), tlv->nameSize(elem.contentSize())); // name field can contain null bytes
if (len == 0) {
......@@ -212,5 +207,5 @@ QString VdvTicket::ticketNumber() const
}
const auto hdr = reinterpret_cast<const VdvTicketHeader*>(d->m_data.constData());
return QString::number(qFromBigEndian(hdr->ticketId));
return QString::number(hdr->ticketId);
}
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