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

Add basic bit layout for ERA SSB v1 tickets

That's what VR is using. Still needs support for decoding long numeric
properties.
parent 07b9925b
......@@ -32,6 +32,8 @@ set(kitinerary_lib_srcs
engine/extractorscriptengine.cpp
engine/scriptextractor.cpp
era/ssbticketbase.cpp
era/ssbv1ticket.cpp
era/ssbv3ticket.cpp
extractors/iatabcbpextractor.cpp
......
/*
SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "ssbticketbase.h"
#include <QDebug>
#include <QString>
using namespace KItinerary;
enum {
SSB_CHAR_WIDTH = 6,
};
SSBTicketBase::SSBTicketBase() = default;
SSBTicketBase::~SSBTicketBase() = default;
int SSBTicketBase::readNumber(int start, int length) const
{
if (start < 0 || length < 1 || start / 8 >= m_data.size() || (start + length) / 8 >= m_data.size() || length > 31) {
qWarning() << "invalid SSB read:" << start << length;
return {};
}
uint64_t num = 0;
for (int i = 0; i < 8; ++i) {
num <<= 8;
num |= (uint8_t)*(m_data.constData() + (start / 8) + i);
}
num <<= start % 8;
num >>= 64 - length;
return num;
}
QString SSBTicketBase::readString(int start, int length) const
{
QString res;
res.reserve(length);
for (int i = 0; i < length; ++i) {
auto n = readNumber(start + SSB_CHAR_WIDTH * i, SSB_CHAR_WIDTH);
if (n >= 36) {
continue;
}
if (n <= 9) {
res += QLatin1Char(n + '0');
} else {
res += QLatin1Char(n - 10 + 'A');
}
}
return res;
}
/*
SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KITINERARY_SSBTICKETBASE_H
#define KITINERARY_SSBTICKETBASE_H
#include "kitinerary_export.h"
#include <QByteArray>
namespace KItinerary {
/** Internal base class for ERA SSB tickets. */
class KITINERARY_EXPORT SSBTicketBase
{
protected:
SSBTicketBase();
~SSBTicketBase();
// start and length in bits
int readNumber(int start, int length) const;
QString readString(int start, int length) const;
QByteArray m_data;
};
#define SSB_NUM_PROPERTY(Name, Start, Len) \
public: \
inline int Name() const { return readNumber(Start, Len); } \
Q_PROPERTY(int Name READ Name)
#define SSB_STR_PROPERTY(Name, Start, Len) \
public: \
inline QString Name() const { return readString(Start, Len); } \
Q_PROPERTY(QString Name READ Name)
}
#endif // KITINERARY_SSBTICKETBASE_H
/*
SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "ssbv1ticket.h"
#include <QDebug>
#include <cstring>
using namespace KItinerary;
enum {
SSB_DATA_SIZE_MIN = 107,
SSB_DATA_SIZE_MAX = 111,
SSB_VERSION = 1,
};
SSBv1Ticket::SSBv1Ticket() = default;
SSBv1Ticket::SSBv1Ticket(const QByteArray &data)
{
if (maybeSSB(data)) {
m_data = data;
} else {
qWarning() << "Trying to construct an SSB ticket from invalid data!";
}
}
SSBv1Ticket::~SSBv1Ticket() = default;
bool SSBv1Ticket::isValid() const
{
return !m_data.isEmpty();
}
bool SSBv1Ticket::maybeSSB(const QByteArray& data)
{
if (data.size() < SSB_DATA_SIZE_MIN || data.size() > SSB_DATA_SIZE_MAX) {
return false;
}
return (data.at(0) >> 4) == SSB_VERSION;
}
QByteArray SSBv1Ticket::rawData() const
{
return m_data;
}
/*
SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KITINERARY_SSBV1TICKET_H
#define KITINERARY_SSBV1TICKET_H
#include "kitinerary_export.h"
#include "ssbticketbase.h"
#include <QMetaType>
namespace KItinerary {
/** ERA SSB ticket barcode (version 1).
* @see ERA TAP TSI Annex B.6 - Appendix C.1
*/
class KITINERARY_EXPORT SSBv1Ticket : protected SSBTicketBase
{
Q_GADGET
SSB_NUM_PROPERTY(version, 0, 4)
SSB_NUM_PROPERTY(issuerCode, 4, 14)
SSB_NUM_PROPERTY(rct2TypeIndicator, 18, 1)
SSB_NUM_PROPERTY(numberOfTickets, 19, 6)
SSB_NUM_PROPERTY(numberOfAdultPassengers, 25, 7)
SSB_NUM_PROPERTY(numberOfChildPassengers, 32, 7)
SSB_NUM_PROPERTY(firstDayOfValidity, 39, 9)
SSB_NUM_PROPERTY(lastDayOfValidity, 48, 9)
SSB_NUM_PROPERTY(customerNumberType, 57, 1)
SSB_NUM_PROPERTY(customerNumber, 58, 47) // ???
SSB_NUM_PROPERTY(departureStationType, 105, 1)
SSB_NUM_PROPERTY(departureStationNum, 106, 30)
SSB_STR_PROPERTY(departureStationAlpha, 106, 5)
SSB_NUM_PROPERTY(arrivalStationType, 136, 1)
SSB_NUM_PROPERTY(arrivalStationNum, 137, 30)
SSB_STR_PROPERTY(arrivalStationAlpha, 137, 5)
SSB_NUM_PROPERTY(departureTime, 167, 6)
SSB_NUM_PROPERTY(trainNumber, 173, 17)
SSB_NUM_PROPERTY(reservationReference, 190, 40)
SSB_NUM_PROPERTY(classOfTransport, 230, 6)
SSB_NUM_PROPERTY(coachNumber, 236, 10)
SSB_NUM_PROPERTY(seatNumber, 246, 7)
SSB_STR_PROPERTY(berthNumber, 253, 1)
SSB_NUM_PROPERTY(overbookingIndicator, 259, 1)
SSB_STR_PROPERTY(issuerPNRNumber, 260, 7)
SSB_NUM_PROPERTY(ticketType, 302, 4)
SSB_NUM_PROPERTY(specimen, 306, 1)
SSB_STR_PROPERTY(viaStations, 307, 5) // is that the correct encoding? page 131 of TAP TSI Annex B.6 could also be read as 6 times 5 bit content
Q_PROPERTY(QByteArray rawData READ rawData)
public:
SSBv1Ticket();
explicit SSBv1Ticket(const QByteArray &data);
~SSBv1Ticket();
/** Returns @c true if this is a valid SSB ticket. */
bool isValid() const;
/** Raw barcode data. */
QByteArray rawData() const;
/** Returns @c true if @p data might be an ERA SSB ticket. */
static bool maybeSSB(const QByteArray &data);
};
}
Q_DECLARE_METATYPE(KItinerary::SSBv1Ticket)
#endif // KITINERARY_SSBV1TICKET_H
......@@ -36,24 +36,6 @@ bool SSBv3Ticket::isValid() const
return !m_data.isEmpty();
}
int SSBv3Ticket::readNumber(int start, int length) const
{
if (start < 0 || length < 1 || start / 8 >= m_data.size() || (start + length) / 8 >= m_data.size() || length > 31) {
qWarning() << "invalid SSB read:" << start << length;
return {};
}
uint64_t num = 0;
for (int i = 0; i < 8; ++i) {
num <<= 8;
num |= (uint8_t)*(m_data.constData() + (start / 8) + i);
}
num <<= start % 8;
num >>= 64 - length;
return num;
}
QString SSBv3Ticket::readString(int start, int length) const
{
QString res;
......
......@@ -7,25 +7,17 @@
#pragma once
#include "kitinerary_export.h"
#include "ssbticketbase.h"
#include <QDateTime>
#include <QMetaType>
namespace KItinerary {
#define SSB_NUM_PROPERTY(Name, Start, Len) \
public: \
inline int Name() const { return readNumber(Start, Len); } \
Q_PROPERTY(int Name READ Name)
#define SSB_STR_PROPERTY(Name, Start, Len) \
public: \
inline QString Name() const { return readString(Start, Len); } \
Q_PROPERTY(QString Name READ Name)
/** ERA SSB ticket barcode (version 3).
* @see ERA TAP TSI TD B.12 Digital Security Elements For Rail Passenger Ticketing - §7 SSB - Small Structured Barcode
*/
class KITINERARY_EXPORT SSBv3Ticket {
class KITINERARY_EXPORT SSBv3Ticket : protected SSBTicketBase {
Q_GADGET
// low-level raw value access
// header
......@@ -131,16 +123,9 @@ public:
static bool maybeSSB(const QByteArray &data);
private:
// start and length in bits
int readNumber(int start, int length) const;
QString readString(int start, int length) const;
QByteArray m_data;
};
#undef SSB_NUM_PROPERTY
#undef SSB_STR_PROPERTY
}
Q_DECLARE_METATYPE(KItinerary::SSBv3Ticket)
......
......@@ -4,6 +4,7 @@
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "../lib/era/ssbv1ticket.h"
#include "../lib/era/ssbv3ticket.h"
#include "../lib/uic9183/uic9183head.h"
#include "../lib/uic9183/uic9183header.h"
......@@ -33,7 +34,7 @@
using namespace KItinerary;
static void dumpSsbTicket(const QByteArray &data)
static void dumpSsbv3Ticket(const QByteArray &data)
{
SSBv3Ticket ticket(data);
......@@ -62,6 +63,29 @@ static void dumpSsbTicket(const QByteArray &data)
}
}
static void dumpSsbv1Ticket(const QByteArray &data)
{
SSBv1Ticket ticket(data);
for (auto i = 0; i < SSBv1Ticket::staticMetaObject.propertyCount(); ++i) {
const auto prop = SSBv1Ticket::staticMetaObject.property(i);
const auto value = prop.readOnGadget(&ticket);
switch (value.type()) {
case QVariant::Int:
std::cout << prop.name() << ": " << value.toInt() << std::endl;
break;
case QVariant::ByteArray:
if (std::strcmp(prop.name(), "rawData") == 0) {
break;
}
[[fallthrough]];
default:
std::cout << prop.name() << ": " << qPrintable(value.toString()) << std::endl;
break;
}
}
}
static void dumpRawData(const char *data, std::size_t size)
{
bool isText = true;
......@@ -250,7 +274,10 @@ int main(int argc, char **argv)
// TODO
} else if (SSBv3Ticket::maybeSSB(data)) {
std::cout << "ERA SSB Ticket" << std::endl;
dumpSsbTicket(data);
dumpSsbv3Ticket(data);
} else if (SSBv1Ticket::maybeSSB(data)) {
std::cout << "ERA SSB Ticket" << std::endl;
dumpSsbv1Ticket(data);
} else if (Uic9183Parser::maybeUic9183(data)) {
std::cout << "UIC 918.3 Container" << std::endl;
dumpUic9183(data);
......
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