Commit 054bb966 authored by Volker Krause's avatar Volker Krause
Browse files

Refactor UIC 918.3 header code

This adds support for version 2 according to ERA TAP TSI TD B.12 with its
enlarged signature field.

This also factors out the low-level decoding utility functions as we need
them independent of the Uic9183Block structure.
parent 0a8ec2b7
......@@ -57,8 +57,10 @@ set(kitinerary_lib_srcs
uic9183/rct2ticket.cpp
uic9183/uic9183block.cpp
uic9183/uic9183head.cpp
uic9183/uic9183header.cpp
uic9183/uic9183parser.cpp
uic9183/uic9183ticketlayout.cpp
uic9183/uic9183utils.cpp
uic9183/vendor0080block.cpp
uic9183/vendor0080vublockdata.cpp
......
......@@ -92,27 +92,3 @@ QString Uic9183Block::contentText() const
{
return QString::fromUtf8(content(), contentSize());
}
int Uic9183Block::readAsciiEncodedNumber(int offset, int length) const
{
if (isNull() || offset < 0 || length < 1 || m_offset + BlockHeaderSize + offset + length > m_data.size()) {
qCWarning(Log) << "invalid UIC 918.3 block content read:" << offset << length << m_offset << m_data.size();
return {};
}
int v = 0;
for (int i = 0; i < length; ++i) {
v *= 10;
v += (*(content() + offset + i)) - '0';
}
return v;
}
QString Uic9183Block::readUtf8String(int offset, int length) const
{
if (isNull() || offset < 0 || length < 1 || m_offset + BlockHeaderSize + offset + length > m_data.size()) {
qCWarning(Log) << "invalid UIC 918.3 block content read:" << offset << length << m_offset << m_data.size();
return {};
}
return QString::fromUtf8(content() + offset, length);
}
......@@ -59,11 +59,6 @@ public:
*/
Uic9183Block nextBlock() const;
/** Reads a number from content at offset @p offset and @p length in ASCII encoding. */
int readAsciiEncodedNumber(int offset, int length) const;
/** Reads a string from the content starting at offset @p offset with @p length, assuming UTF-8 encoding. */
QString readUtf8String(int offset, int length) const;
private:
QString contentText() const;
......
......@@ -20,16 +20,16 @@ using namespace KItinerary;
Uic9183Head::Uic9183Head(const Uic9183Block &block)
{
if (block.version() == 1 && block.size() == 53) {
m_block = block;
m_data = block;
}
}
bool Uic9183Head::isValid() const
{
return !m_block.isNull();
return !m_data.isNull();
}
QDateTime Uic9183Head::issuingDateTime() const
{
return QDateTime::fromString(m_block.readUtf8String(24, 12), QStringLiteral("ddMMyyyyhhmm"));
return QDateTime::fromString(Uic9183Utils::readUtf8String(m_data, 24, 12), QStringLiteral("ddMMyyyyhhmm"));
}
......@@ -9,32 +9,25 @@
#include "kitinerary_export.h"
#include "uic9183block.h"
#include "uic9183utils.h"
#include <QDateTime>
namespace KItinerary {
#define HEAD_NUM_PROPERTY(Name, Offset, Length) \
public: \
inline int Name() const { return m_block.readAsciiEncodedNumber(Offset, Length); } \
Q_PROPERTY(int Name READ Name)
#define HEAD_STR_PROPERTY(Name, Offset, Length) \
inline QString Name() const { return m_block.readUtf8String(Offset, Length); } \
Q_PROPERTY(QString Name READ Name)
/** U_HEAD block of a UIC 918.3 ticket container.
* @see ERA TAP TSI TD B.12 Digital Security Elements For Rail Passenger Ticketing - §10.3.1 Main record (U_HEAD)
*/
class KITINERARY_EXPORT Uic9183Head
{
Q_GADGET
HEAD_NUM_PROPERTY(issuerCompanyCodeNumeric, 0, 4)
HEAD_STR_PROPERTY(issuerCompanyCodeString, 0, 4)
HEAD_STR_PROPERTY(ticketKey, 4, 20)
UIC_NUM_PROPERTY(issuerCompanyCodeNumeric, 0, 4)
UIC_STR_PROPERTY(issuerCompanyCodeString, 0, 4)
UIC_STR_PROPERTY(ticketKey, 4, 20)
Q_PROPERTY(QDateTime issuingDateTime READ issuingDateTime)
HEAD_NUM_PROPERTY(flags, 36, 1)
HEAD_STR_PROPERTY(primaryLanguage, 37, 2);
HEAD_STR_PROPERTY(secondaryLanguage, 39, 2);
UIC_NUM_PROPERTY(flags, 36, 1)
UIC_STR_PROPERTY(primaryLanguage, 37, 2);
UIC_STR_PROPERTY(secondaryLanguage, 39, 2);
public:
Uic9183Head(const Uic9183Block &block);
......@@ -46,11 +39,9 @@ public:
static constexpr const char RecordId[] = "U_HEAD";
private:
Uic9183Block m_block;
Uic9183Block m_data;
};
#undef HEAD_NUM_PROPERTY
}
#endif // KITINERARY_UIC9183HEAD_H
/*
SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "uic9183header.h"
#include "logging.h"
#include <cstdint>
using namespace KItinerary;
enum {
PrefixSize = 14,
SignatureSizeV1 = 50,
SignatureSizeV2 = 64,
SuffixSize = 4,
ZlibHeaderSize = 2
};
Uic9183Header::Uic9183Header() = default;
Uic9183Header::Uic9183Header(const QByteArray& data)
{
if (data.size() < (PrefixSize + SignatureSizeV1 + SuffixSize + ZlibHeaderSize)) {
return;
}
if (!data.startsWith("#UT") && !data.startsWith("OTI")) {
return;
}
const auto version = Uic9183Utils::readAsciiEncodedNumber(data, 3, 2);
if (version != 1 && version != 2) {
return;
}
const auto offset = PrefixSize + (version == 1 ? SignatureSizeV1 : SignatureSizeV2) + SuffixSize;
if (data.size() < offset + ZlibHeaderSize) {
return;
}
// check for zlib headers 0x789C or 0x78DA
if ((uint8_t)data[offset] != 0x78 || ((uint8_t)data[offset + 1] != 0x9C && (uint8_t)data[offset + 1] != 0xDA)) {
qCWarning(Log) << "UIC 918-3 payload has wrong zlib header.";
return;
}
m_data = data;
}
bool Uic9183Header::isValid() const
{
return !m_data.isEmpty();
}
int Uic9183Header::signatureSize() const
{
switch (version()) {
case 1: return 50;
case 2: return 64;
};
return 0;
}
int Uic9183Header::compressedMessageSize() const
{
return Uic9183Utils::readAsciiEncodedNumber(m_data, 14 + signatureSize(), 4);
}
int Uic9183Header::compressedMessageOffset() const
{
return PrefixSize + (version() == 1 ? SignatureSizeV1 : SignatureSizeV2) + SuffixSize;
}
/*
SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KITINERARY_UIC9183HEADER_H
#define KITINERARY_UIC9183HEADER_H
#include "kitinerary_export.h"
#include "uic9183utils.h"
#include <QByteArray>
#include <qobjectdefs.h>
namespace KItinerary {
/** Header of an UIC 918.3 ticket. */
class KITINERARY_EXPORT Uic9183Header
{
Q_GADGET
UIC_STR_PROPERTY(messageType, 0, 3)
UIC_NUM_PROPERTY(version, 3, 2)
UIC_NUM_PROPERTY(signerCompanyCodeNumeric, 5, 4)
UIC_STR_PROPERTY(signerCompanyCode, 5, 4)
UIC_STR_PROPERTY(signatureKeyId, 9, 5)
Q_PROPERTY(int signatureSize READ signatureSize)
Q_PROPERTY(int compressedMessageSize READ compressedMessageSize)
public:
Uic9183Header();
Uic9183Header(const QByteArray &data);
bool isValid() const;
int signatureSize() const;
int compressedMessageSize() const;
int compressedMessageOffset() const;
private:
QByteArray m_data;
};
}
#endif // KITINERARY_UIC9183HEADER_H
......@@ -9,6 +9,7 @@
#include "rct2ticket.h"
#include "uic9183block.h"
#include "uic9183head.h"
#include "uic9183header.h"
#include "uic9183ticketlayout.h"
#include "vendor0080block.h"
......@@ -27,6 +28,7 @@ namespace KItinerary {
class Uic9183ParserPrivate : public QSharedData
{
public:
QByteArray m_data;
QByteArray m_payload;
QDateTime m_contextDt;
};
......@@ -72,39 +74,23 @@ void Uic9183Parser::setContextDate(const QDateTime &contextDt)
void Uic9183Parser::parse(const QByteArray &data)
{
d->m_data.clear();
d->m_payload.clear();
// header and signature block (64 byte total)
if (!Uic9183Parser::maybeUic9183(data)) {
qCWarning(Log) << "UIC 918-3 ticket too short or has wrong header/version.";
Uic9183Header header(data);
if (!header.isValid()) {
return;
}
// 3x header
// 2x version
// 4x UIC code of the signing carrier
// 5x signature key id
// 50x ASN.1 signature
// zlib compressed payload
if (data.size() < 64 + 8) {
qCWarning(Log) << "UIC 918-3 payload too short.";
return;
}
// 4x compressed payload size as string
// 2x zlib header 0x789C
if (data[68] != 0x78 || ((uchar)data[69] != 0x9C && (uchar)data[69] != 0xDA)) {
qCWarning(Log) << "UIC 918-3 payload has wrong zlib header.";
return;
}
// nx zlib payload
d->m_data = data;
d->m_payload.resize(4096);
z_stream stream;
stream.zalloc = nullptr;
stream.zfree = nullptr;
stream.opaque = nullptr;
stream.avail_in = data.size() - 68;
stream.next_in = reinterpret_cast<unsigned char*>(const_cast<char*>(data.data() + 68));
stream.avail_in = data.size() - header.compressedMessageOffset();
stream.next_in = reinterpret_cast<unsigned char*>(const_cast<char*>(data.data() + header.compressedMessageOffset()));
stream.avail_out = d->m_payload.size();
stream.next_out = reinterpret_cast<unsigned char*>(d->m_payload.data());
......@@ -255,21 +241,15 @@ QVariant Uic9183Parser::rct2TicketVariant() const
return {};
}
bool Uic9183Parser::maybeUic9183(const QByteArray& data)
Uic9183Header Uic9183Parser::header() const
{
if (data.size() < 64) {
return false;
}
if (!data.startsWith("#UT") && !data.startsWith("OTI")) {
return false;
}
if (data.at(3) != '0' || data.at(4) != '1') {
return false;
}
return Uic9183Header(d->m_data);
}
return true;
bool Uic9183Parser::maybeUic9183(const QByteArray& data)
{
Uic9183Header h(data);
return h.isValid();
}
#include "moc_uic9183parser.cpp"
......@@ -20,6 +20,7 @@ namespace KItinerary {
class Rct2Ticket;
class Uic9183Block;
class Uic9183Header;
class Uic9183ParserPrivate;
class Uic9183TicketLayout;
......@@ -101,6 +102,9 @@ public:
/** Same as the above, but for JS usage. */
Q_INVOKABLE QVariant block(const QString &name) const;
/** Header found before the compressed payload. */
Uic9183Header header() const;
/** Quickly checks if @p might be UIC 918.3 content.
* This prioritizes speed over correctness and is used in barcode content auto-detection.
*/
......
......@@ -6,6 +6,7 @@
#include "uic9183ticketlayout.h"
#include "uic9183block.h"
#include "uic9183utils.h"
#include "logging.h"
#include <QDateTime>
......@@ -179,7 +180,7 @@ Uic9183TicketLayout& Uic9183TicketLayout::operator=(const Uic9183TicketLayout&)
QString Uic9183TicketLayout::type() const
{
return d->block.readUtf8String(0, 4);
return Uic9183Utils::readUtf8String(d->block, 0, 4);
}
bool Uic9183TicketLayout::isValid() const
......
/*
SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "uic9183utils.h"
#include "uic9183block.h"
#include "logging.h"
using namespace KItinerary;
int Uic9183Utils::readAsciiEncodedNumber(const char* data, int size, int offset, int length)
{
if (!data || offset < 0 || length < 1 || size < 1 || offset + length > size) {
qCWarning(Log) << "Invalid UIC 918.3 read" << offset << length << size;
return {};
}
int v = 0;
for (int i = 0; i < length; ++i) {
v *= 10;
v += (*(data + offset + i)) - '0';
}
return v;
}
int Uic9183Utils::readAsciiEncodedNumber(const QByteArray &data, int offset, int length)
{
return readAsciiEncodedNumber(data.constData(), data.size(), offset, length);
}
int Uic9183Utils::readAsciiEncodedNumber(const Uic9183Block &block, int offset, int length)
{
return readAsciiEncodedNumber(block.content(), block.contentSize(), offset, length);
}
QString Uic9183Utils::readUtf8String(const char* data, int size, int offset, int length)
{
if (!data || offset < 0 || length < 1 || size < 1 || offset + length > size) {
qCWarning(Log) << "Invalid UIC 918.3 read" << offset << length << size;
return {};
}
return QString::fromUtf8(data + offset, length);
}
QString Uic9183Utils::readUtf8String(const QByteArray& data, int offset, int length)
{
return readUtf8String(data.constData(), data.size(), offset, length);
}
QString Uic9183Utils::readUtf8String(const Uic9183Block &block, int offset, int length)
{
return readUtf8String(block.content(), block.contentSize(), offset, length);
}
/*
SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KITINERARY_UIC9183UTILS_H
#define KITINERARY_UIC9183UTILS_H
#include <QString>
namespace KItinerary {
class Uic9183Block;
/*
* Low-level decoding utilities for UIC 918.3 ticket content.
*/
namespace Uic9183Utils
{
/** Reads an ASCII encoded numerical value. */
int readAsciiEncodedNumber(const char *data, int size, int offset, int length);
int readAsciiEncodedNumber(const QByteArray &data, int offset, int length);
int readAsciiEncodedNumber(const Uic9183Block &block, int offset, int length);
/** Reads a UTF8 encoded string. */
QString readUtf8String(const char *data, int size, int offset, int length);
QString readUtf8String(const QByteArray &data, int offset, int length);
QString readUtf8String(const Uic9183Block &block, int offset, int length);
}
#define UIC_NUM_PROPERTY(Name, Offset, Length) \
public: \
inline int Name() const { return Uic9183Utils::readAsciiEncodedNumber(m_data, Offset, Length); } \
Q_PROPERTY(int Name READ Name)
#define UIC_STR_PROPERTY(Name, Offset, Length) \
public: \
inline QString Name() const { return Uic9183Utils::readUtf8String(m_data, Offset, Length); } \
Q_PROPERTY(QString Name READ Name)
}
#endif // KITINERARY_UIC9183UTILS_H
......@@ -6,6 +6,7 @@
#include "vendor0080block.h"
#include "vendor0080vublockdata.h"
#include "uic9183utils.h"
#include "logging.h"
#include <QString>
......@@ -118,9 +119,9 @@ QDate Vendor0080BLOrderBlock::validFrom() const
{
switch (m_block.version()) {
case 2:
return QDate::fromString(m_block.readUtf8String(m_offset + 22, 8), QStringLiteral("ddMMyyyy"));
return QDate::fromString(Uic9183Utils::readUtf8String(m_block, m_offset + 22, 8), QStringLiteral("ddMMyyyy"));
case 3:
return QDate::fromString(m_block.readUtf8String(m_offset, 8), QStringLiteral("ddMMyyyy"));
return QDate::fromString(Uic9183Utils::readUtf8String(m_block, m_offset, 8), QStringLiteral("ddMMyyyy"));
}
return {};
}
......@@ -129,9 +130,9 @@ QDate Vendor0080BLOrderBlock::validTo() const
{
switch (m_block.version()) {
case 2:
return QDate::fromString(m_block.readUtf8String(m_offset + 22 + 8, 8), QStringLiteral("ddMMyyyy"));
return QDate::fromString(Uic9183Utils::readUtf8String(m_block, m_offset + 22 + 8, 8), QStringLiteral("ddMMyyyy"));
case 3:
return QDate::fromString(m_block.readUtf8String(m_offset + 8, 8), QStringLiteral("ddMMyyyy"));
return QDate::fromString(Uic9183Utils::readUtf8String(m_block, m_offset + 8, 8), QStringLiteral("ddMMyyyy"));
}
return {};
}
......@@ -140,9 +141,9 @@ QString Vendor0080BLOrderBlock::serialNumber() const
{
switch (m_block.version()) {
case 2:
return m_block.readUtf8String(m_offset + 22 + 8 + 8, 8);
return Uic9183Utils::readUtf8String(m_block, m_offset + 22 + 8 + 8, 8);
case 3:
return m_block.readUtf8String(m_offset + 8 + 8, 10);
return Uic9183Utils::readUtf8String(m_block, m_offset + 8 + 8, 10);
}
return {};
}
......@@ -175,7 +176,7 @@ bool Vendor0080BLBlock::isValid() const
int Vendor0080BLBlock::orderBlockCount() const
{
return m_block.readAsciiEncodedNumber(2, 1);
return Uic9183Utils::readAsciiEncodedNumber(m_block, 2, 1);
}
Vendor0080BLOrderBlock Vendor0080BLBlock::orderBlock(int i) const
......
......@@ -6,6 +6,7 @@
#include "../lib/era/ssbticket.h"
#include "../lib/uic9183/uic9183head.h"
#include "../lib/uic9183/uic9183header.h"
#include "../lib/uic9183/vendor0080vublockdata.h"
#include "../lib/vdv/vdvticketcontent.h"
#include "../lib/tlv/berelement_p.h"
......@@ -92,7 +93,9 @@ static void dumpUic9183(const QByteArray &data)
{
Uic9183Parser parser;
parser.parse(data);
// TODO dump header
std::cout << " Header:" << std::endl;
const auto header = parser.header();
dumpGadget(&header, " ");
for (auto block = parser.firstBlock(); !block.isNull(); block = block.nextBlock()) {
std::cout << " Block: ";
......
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