KItinerary

ticket-barcode-dump.cpp
1/*
2 SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5*/
6
7#include "../lib/asn1/berelement.h"
8#include "../lib/era/elbticket.h"
9#include "../lib/era/fcbticket.h"
10#include "../lib/era/ssbv1ticket.h"
11#include "../lib/era/ssbv2ticket.h"
12#include "../lib/era/ssbv3ticket.h"
13#include "../lib/iata/iatabcbp.h"
14#include "../lib/uic9183/uic9183flex.h"
15#include "../lib/uic9183/uic9183head.h"
16#include "../lib/uic9183/uic9183header.h"
17#include "../lib/uic9183/vendor0080vublockdata.h"
18#include "../lib/vdv/vdvticketcontent.h"
19
20#include <kitinerary_version.h>
21
22#include <KItinerary/Uic9183Block>
23#include <KItinerary/Uic9183Parser>
24#include <KItinerary/Uic9183TicketLayout>
25#include <KItinerary/Vendor0080Block>
26#include <KItinerary/VdvTicket>
27#include <KItinerary/VdvTicketParser>
28
29#include <QCommandLineParser>
30#include <QCoreApplication>
31#include <QDebug>
32#include <QFile>
33#include <QMetaProperty>
34#include <QSequentialIterable>
35
36#include <iostream>
37
38#include <cstring>
39
40using namespace KItinerary;
41
42static void dumpRawData(const char *data, std::size_t size)
43{
44 bool isText = true;
45 for (std::size_t i = 0; i < size && isText; ++i) {
46 isText = (uint8_t)*(data + i) >= 20;
47 }
48
49 if (isText) {
50 std::cout.write(data, size);
51 } else {
52 std::cout << "(hex) " << QByteArray(data, size).toHex().constData();
53 }
54}
55
56template <typename T>
57static void dumpGadget(const T *gadget, const char* indent = "")
58{
59 dumpGadget(gadget, &T::staticMetaObject, indent);
60}
61
62static void dumpGadget(const void *gadget, const QMetaObject *mo, const char* indent)
63{
64 if (!gadget || !mo) {
65 return;
66 }
67 for (auto i = 0; i < mo->propertyCount(); ++i) {
68 const auto prop = mo->property(i);
69 if (!prop.isStored()) {
70 continue;
71 }
72 const auto value = prop.readOnGadget(gadget);
73 if (prop.isEnumType()) {
74 std::cout << indent << prop.name() << ": " << prop.enumerator().valueToKey(value.toInt()) << std::endl;
75 } else if (prop.typeId() == QMetaType::QByteArray) {
76 std::cout << indent << prop.name() << ": ";
77 const auto data = value.toByteArray();
78 dumpRawData(data.constData(), data.size());
79 std::cout << std::endl;
80 } else {
81 std::cout << indent << prop.name() << ": " << qPrintable(value.toString()) << std::endl;
82 }
83 if (const auto childMo = QMetaType(value.userType()).metaObject()) {
84 QByteArray childIndent(indent);
85 childIndent.push_back(' ');
86 dumpGadget(value.constData(), childMo, childIndent.constData());
87 } else if (value.canConvert<QVariantList>() && value.userType() != QMetaType::QString && value.userType() != QMetaType::QByteArray) {
88 auto iterable = value.value<QSequentialIterable>();
89 int idx = 0;
90 QByteArray childIndent(indent);
91 childIndent.append(" ");
92 for (const QVariant &v : iterable) {
93 if (QMetaType(v.userType()).metaObject()) {
94 std::cout << indent << " [" << idx++ << "]:" << std::endl;
95 dumpGadget(v.constData(), QMetaType(v.userType()).metaObject(), childIndent.constData());
96 } else {
97 std::cout << indent << " [" << idx++ << "]: " << qPrintable(v.toString()) << std::endl;
98 }
99 }
100 }
101 }
102}
103
104static void dumpSsbv3Ticket(const QByteArray &data)
105{
106 SSBv3Ticket ticket(data);
107
108 const auto typePrefix = QByteArray("type" + QByteArray::number(ticket.ticketTypeCode()));
109 for (auto i = 0; i < SSBv3Ticket::staticMetaObject.propertyCount(); ++i) {
110 const auto prop = SSBv3Ticket::staticMetaObject.property(i);
111 if (!prop.isStored()) {
112 continue;
113 }
114 if (std::strncmp(prop.name(), "type", 4) == 0 && std::strncmp(prop.name(), typePrefix.constData(), 5) != 0) {
115 continue;
116 }
117
118 const auto value = prop.readOnGadget(&ticket);
119 std::cout << prop.name() << ": " << qPrintable(value.toString()) << std::endl;
120 }
121
122 std::cout << std::endl;
123 std::cout << "Issuing day: " << qPrintable(ticket.issueDate().toString(Qt::ISODate)) << std::endl;
124 switch (ticket.ticketTypeCode()) {
125 case SSBv3Ticket::IRT_RES_BOA:
126 std::cout << "Departure day: " << qPrintable(ticket.type1DepartureDay().toString(Qt::ISODate)) << std::endl;
127 break;
128 case SSBv3Ticket::NRT:
129 std::cout << "Valid from: " << qPrintable(ticket.type2ValidFrom().toString(Qt::ISODate)) << std::endl;
130 std::cout << "Valid until: " << qPrintable(ticket.type2ValidUntil().toString(Qt::ISODate)) << std::endl;
131 break;
132 case SSBv3Ticket::GRT:
133 case SSBv3Ticket::RPT:
134 break;
135 }
136}
137
138static void dumpSsbv2Ticket(const QByteArray &data)
139{
140 SSBv2Ticket ticket(data);
141 dumpGadget(&ticket);
142}
143
144static void dumpSsbv1Ticket(const QByteArray &data)
145{
146 SSBv1Ticket ticket(data);
147 dumpGadget(&ticket);
148 std::cout << std::endl;
149 std::cout << "First day of validitiy: " << qPrintable(ticket.firstDayOfValidity().toString(Qt::ISODate)) << std::endl;
150 std::cout << "Departure time: " << qPrintable(ticket.departureTime().toString(Qt::ISODate)) << std::endl;
151}
152
153static void dumpElbTicketSegment(const ELBTicketSegment &segment)
154{
155 dumpGadget(&segment, " ");
156 std::cout << " Departure date: " << qPrintable(segment.departureDate().toString(Qt::ISODate)) << std::endl;
157}
158
159static void dumpElbTicket(const ELBTicket &ticket)
160{
161 const auto mo = &ELBTicket::staticMetaObject;
162 for (auto i = 0; i < mo->propertyCount(); ++i) {
163 const auto prop = mo->property(i);
164 if (!prop.isStored() || QMetaType(prop.userType()).metaObject()) {
165 continue;
166 }
167 const auto value = prop.readOnGadget(&ticket);
168 std::cout << prop.name() << ": " << qPrintable(value.toString()) << std::endl;
169 }
170 std::cout << "Emission date: " << qPrintable(ticket.emissionDate().toString(Qt::ISODate)) << std::endl;
171 std::cout << "Valid from: " << qPrintable(ticket.validFromDate().toString(Qt::ISODate)) << std::endl;
172 std::cout << "Valid until: " << qPrintable(ticket.validUntilDate().toString(Qt::ISODate)) << std::endl;
173 std::cout << std::endl << "Segment 1:" << std::endl;
174 dumpElbTicketSegment(ticket.segment1());
175 if (ticket.segment2().isValid()) {
176 std::cout << std::endl << "Segment 2:" << std::endl;
177 dumpElbTicketSegment(ticket.segment2());
178 }
179}
180
181static void dumpUic9183(const QByteArray &data)
182{
183 Uic9183Parser parser;
184 parser.parse(data);
185 std::cout << " Header:" << std::endl;
186 const auto header = parser.header();
187 dumpGadget(&header, " ");
188
189 for (auto block = parser.firstBlock(); !block.isNull(); block = block.nextBlock()) {
190 std::cout << " Block: ";
191 std::cout.write(block.name(), 6);
192 std::cout << ", size: " << block.size() << ", version: " << block.version() << std::endl;
193
194 if (block.isA<Uic9183Head>()) {
195 Uic9183Head head(block);
196 dumpGadget(&head, " ");
197 } else if (block.isA<Uic9183TicketLayout>()) {
198 Uic9183TicketLayout tlay(block);
199 std::cout << " Layout standard: " << qPrintable(tlay.type()) << std::endl;
200 for (auto field = tlay.firstField(); !field.isNull(); field = field.next()) {
201 std::cout << " [row: " << field.row() << " column: " << field.column()
202 << " height: " << field.height() << " width: " << field.width()
203 << " format: " << field.format() << "]: " << qPrintable(field.text())
204 << std::endl;
205 }
206 } else if (block.isA<Uic9183Flex>()) {
207 Uic9183Flex flex(block);
208 const auto fcbProp = Uic9183Flex::staticMetaObject.property(Uic9183Flex::staticMetaObject.indexOfProperty("fcb"));
209 QVariant fcbData = fcbProp.readOnGadget(&flex);
210 dumpGadget(fcbData.constData(), QMetaType(fcbData.typeId()).metaObject(), " ");
211 } else if (block.isA<Vendor0080BLBlock>()) {
212 Vendor0080BLBlock vendor(block);
213 dumpGadget(&vendor, " ");
214 for (int i = 0; i < vendor.orderBlockCount(); ++i) {
215 const auto order = vendor.orderBlock(i);
216 std::cout << " Order block " << (i + 1) << ":" << std::endl;
217 dumpGadget(&order, " ");
218 }
219 std::cout << " S-blocks:" << std::endl;
220 for (auto sub = vendor.firstBlock(); !sub.isNull(); sub = sub.nextBlock()) {
221 std::cout << " ";
222 std::cout.write(sub.id(), 3);
223 std::cout << " (size: " << sub.size() << "): ";
224 dumpRawData(sub.content(), sub.contentSize());
225 std::cout << std::endl;
226 }
227 } else if (block.isA<Vendor0080VUBlock>()) {
228 Vendor0080VUBlock vendor(block);
229 dumpGadget(vendor.commonData(), " ");
230 for (int i = 0; i < (int)vendor.commonData()->numberOfTickets; ++i) {
231 const auto ticket = vendor.ticketData(i);
232 std::cout << " Ticket " << (i + 1) << ":" << std::endl;
233 dumpGadget(ticket, " ");
234 dumpGadget(&ticket->validityArea, " ");
235 std::cout << " payload: (hex) " << QByteArray((const char*)&ticket->validityArea + sizeof(VdvTicketValidityAreaData), ticket->validityAreaDataSize - sizeof(VdvTicketValidityAreaData)).toHex().constData() << std::endl;
236 }
237 } else {
238 std::cout << " Content: ";
239 dumpRawData(block.content(), block.contentSize());
240 std::cout << std::endl;
241 }
242 }
243}
244
245static void dumpVdv(const QByteArray &data)
246{
247 VdvTicketParser parser;
248 if (!parser.parse(data)) {
249 std::cerr << "failed to parse VDV ticket" << std::endl;
250 return;
251 }
252 const auto ticket = parser.ticket();
253 std::cout << " Header:" << std::endl;
254 dumpGadget(ticket.header(), " ");
255
256 std::cout << " Product data:" << std::endl;
257 for (auto block = ticket.productData().first(); block.isValid(); block = block.next()) {
258 std::cout << " Tag: 0x" << std::hex << block.type() << std::dec << " size: " << block.size() << std::endl;
259 switch (block.type()) {
260 case VdvTicketBasicData::Tag:
261 dumpGadget(block.contentAt<VdvTicketBasicData>(), " ");
262 break;
263 case VdvTicketTravelerData::Tag:
264 {
265 const auto traveler = block.contentAt<VdvTicketTravelerData>();
266 dumpGadget(traveler, " ");
267 std::cout << " name: " << qPrintable(QString::fromUtf8(traveler->name(), traveler->nameSize(block.contentSize()))) << std::endl;
268 break;
269 }
270 case VdvTicketValidityAreaData::Tag:
271 {
272 const auto area = block.contentAt<VdvTicketValidityAreaData>();
273
274 switch (area->type) {
275 case VdvTicketValidityAreaDataType31::Type:
276 {
277 const auto area31 = static_cast<const VdvTicketValidityAreaDataType31*>(area);
278 dumpGadget(area31, " ");
279 std::cout << " payload: (hex) " << QByteArray((const char*)block.contentData() + sizeof(VdvTicketValidityAreaDataType31), block.contentSize() - sizeof(VdvTicketValidityAreaDataType31)).toHex().constData() << std::endl;
280 break;
281 }
282 default:
283 dumpGadget(area, " ");
284 std::cout << " payload: (hex) " << QByteArray((const char*)block.contentData() + sizeof(VdvTicketValidityAreaData), block.contentSize() - sizeof(VdvTicketValidityAreaData)).toHex().constData() << std::endl;
285 break;
286 }
287 break;
288 }
289 default:
290 std::cout << " (hex) " << QByteArray((const char*)block.contentData(), block.contentSize()).toHex().constData() << std::endl;
291 }
292 }
293
294 std::cout << " Transaction data:" << std::endl;
295 dumpGadget(ticket.commonTransactionData(), " ");
296 std::cout << " Product-specific transaction data (" << ticket.productSpecificTransactionData().contentSize() << " bytes):" << std::endl;
297 for (auto block = ticket.productSpecificTransactionData().first(); block.isValid(); block = block.next()) {
298 std::cout << " Tag: " << block.type() << " size: " << block.size() << std::endl;
299 switch (block.type()) {
300 default:
301 std::cout << " (hex) " << QByteArray((const char*)block.contentData(), block.contentSize()).toHex().constData() << std::endl;
302 }
303 }
304
305 std::cout << " Issue data:" << std::endl;
306 dumpGadget(ticket.issueData(), " ");
307 std::cout << " Trailer:" << std::endl;
308 std::cout << " identifier: ";
309 std::cout.write(ticket.trailer()->identifier, 3);
310 std::cout << std::endl;
311 std::cout << " version: " << ticket.trailer()->version << std::endl;
312}
313
314void dumpIataBcbp(const QString &data, const QDateTime &contextDate)
315{
316 IataBcbp ticket(data);
317 if (!ticket.isValid()) {
318 std::cout << "invalid format" << std::endl;
319 return;
320 }
321
322 const auto ums = ticket.uniqueMandatorySection();
323 dumpGadget(&ums);
324 const auto ucs = ticket.uniqueConditionalSection();
325 dumpGadget(&ucs);
326 const auto issueDate = ucs.dateOfIssue(contextDate);
327 std::cout << "Date of issue: " << qPrintable(issueDate.toString(Qt::ISODate)) << std::endl;
328
329 for (auto i = 0; i < ums.numberOfLegs(); ++i) {
330 std::cout << "Leg " << (i + 1) << std::endl;
331 const auto rms = ticket.repeatedMandatorySection(i);
332 dumpGadget(&rms, " ");
333 const auto rcs = ticket.repeatedConditionalSection(i);
334 dumpGadget(&rcs, " ");
335 std::cout << " Airline use section: " << qPrintable(ticket.airlineUseSection(i)) << std::endl;
336 std::cout << " Date of flight: " << qPrintable(rms.dateOfFlight(issueDate.isValid() ? QDateTime(issueDate, {}) : contextDate).toString(Qt::ISODate)) << std::endl;
337 }
338
339 if (ticket.hasSecuritySection()) {
340 std::cout << "Security:" << std::endl;
341 const auto sec = ticket.securitySection();
342 dumpGadget(&sec, " ");
343 }
344}
345
346int main(int argc, char **argv)
347{
348 QCoreApplication::setApplicationName(QStringLiteral("ticket-barcode-dump"));
349 QCoreApplication::setApplicationVersion(QStringLiteral(KITINERARY_VERSION_STRING));
350 QCoreApplication::setOrganizationDomain(QStringLiteral("kde.org"));
351 QCoreApplication::setOrganizationName(QStringLiteral("KDE"));
352 QCoreApplication app(argc, argv);
353
354 QCommandLineParser parser;
355 parser.setApplicationDescription(QStringLiteral("Decode ticket barcode content."));
356 parser.addHelpOption();
357 parser.addVersionOption();
358 QCommandLineOption contextDateOpt(QStringLiteral("context-date"), QStringLiteral("Context to resolve incomplete dates."), QStringLiteral("yyyy-MM-dd"));
359 parser.addOption(contextDateOpt);
360 parser.addPositionalArgument(QStringLiteral("input"), QStringLiteral("File to read data from, omit for using stdin."));
361 parser.process(app);
362
363 auto contextDate = QDate::currentDate();
364 if (parser.isSet(contextDateOpt)) {
365 contextDate = QDate::fromString(parser.value(contextDateOpt), Qt::ISODate);
366 }
367
368 QFile file;
369 if (parser.positionalArguments().isEmpty()) {
370 file.open(stdin, QFile::ReadOnly);
371 } else {
372 file.setFileName(parser.positionalArguments().at(0));
373 if (!file.open(QFile::ReadOnly)) {
374 std::cerr << qPrintable(file.errorString()) << std::endl;
375 return 1;
376 }
377 }
378
379 const auto data = file.readAll();
380
381 if (IataBcbp::maybeIataBcbp(data)) {
382 std::cout << "IATA Barcoded Boarding Pass" << std::endl;
383 dumpIataBcbp(QString::fromUtf8(data), QDateTime(contextDate, {}));
384 } else if (SSBv3Ticket::maybeSSB(data)) {
385 std::cout << "ERA SSB Ticket" << std::endl;
386 dumpSsbv3Ticket(data);
387 } else if (SSBv2Ticket::maybeSSB(data)) {
388 std::cout << "ERA SSB Ticket" << std::endl;
389 dumpSsbv2Ticket(data);
390 } else if (SSBv1Ticket::maybeSSB(data)) {
391 std::cout << "ERA SSB Ticket" << std::endl;
392 dumpSsbv1Ticket(data);
393 } else if (Uic9183Parser::maybeUic9183(data)) {
394 std::cout << "UIC 918.3 Container" << std::endl;
395 dumpUic9183(data);
396 } else if (VdvTicketParser::maybeVdvTicket(data)) {
397 std::cout << "VDV Ticket" << std::endl;
398 dumpVdv(data);
399 } else if (auto ticket = ELBTicket::parse(data); ticket) {
400 std::cout << "ERA ELB Ticket" << std::endl;
401 dumpElbTicket(*ticket);
402 } else {
403 std::cout << "Unknown content" << std::endl;
404 return 1;
405 }
406
407 return 0;
408}
Segment block of an ERA ELB ticket .
Definition elbticket.h:83
ERA (Element List Barcode) ELB ticket barcode.
Definition elbticket.h:34
A IATA BarCoded Boarding Pass (BCBP)
Definition iatabcbp.h:21
static bool maybeIataBcbp(const QByteArray &data)
Fast checks whether this might be an IATA BCBP.
Definition iatabcbp.cpp:146
ERA SSB ticket barcode (version 1).
Definition ssbv1ticket.h:21
static bool maybeSSB(const QByteArray &data)
Returns true if data might be an ERA SSB ticket.
ERA SSB ticket barcode (version 2).
Definition ssbv2ticket.h:21
static bool maybeSSB(const QByteArray &data)
Returns true if data might be an ERA SSB ticket.
ERA SSB ticket barcode (version 3).
Definition ssbv3ticket.h:20
static bool maybeSSB(const QByteArray &data)
Returns true if data might be an ERA SSB ticket.
bool isNull() const
Checks if the block is valid or empty/default constructed.
Represents a U_FLEX block holding different versions of an FCB payload.
Definition uic9183flex.h:24
U_HEAD block of a UIC 918.3 ticket container.
Definition uic9183head.h:21
Parser for UIC 918.3 and 918.3* train tickets.
static bool maybeUic9183(const QByteArray &data)
Quickly checks if might be UIC 918.3 content.
Uic9183Block firstBlock() const
First data block in this ticket.
Uic9183Header header() const
Header found before the compressed payload.
Parser for a U_TLAY block in a UIC 918-3 ticket container, such as a ERA TLB ticket.
Product specific data - basic information.
Parser for VDV tickets.
VdvTicket ticket() const
Returns the parsed ticket data.
bool parse(const QByteArray &data)
Tries to parse the ticket in data.
static bool maybeVdvTicket(const QByteArray &data)
Fast check if data might contain a VDV ticket.
Product specific data - traveler information.
Ticket validity area data block.
UIC 918.3 0080BL vendor data block.
UIC 918.3 0080VU vendor data block (DB local public transport extensions).
Classes for reservation/travel data models, data extraction and data augmentation.
Definition berelement.h:17
const char * constData() const const
QByteArray number(double n, char format, int precision)
QByteArray toHex(char separator) const const
QCommandLineOption addHelpOption()
bool addOption(const QCommandLineOption &option)
void addPositionalArgument(const QString &name, const QString &description, const QString &syntax)
QCommandLineOption addVersionOption()
bool isSet(const QCommandLineOption &option) const const
QStringList positionalArguments() const const
void process(const QCoreApplication &app)
void setApplicationDescription(const QString &description)
QString value(const QCommandLineOption &option) const const
void setApplicationName(const QString &application)
void setApplicationVersion(const QString &version)
void setOrganizationDomain(const QString &orgDomain)
void setOrganizationName(const QString &orgName)
QDate currentDate()
QDate fromString(QStringView string, QStringView format, QCalendar cal)
QString toString(QStringView format, QCalendar cal) const const
bool open(FILE *fh, OpenMode mode, FileHandleFlags handleFlags)
void setFileName(const QString &name)
QString errorString() const const
QByteArray readAll()
const_reference at(qsizetype i) const const
bool isEmpty() const const
QMetaProperty property(int index) const const
int propertyCount() const const
QVariant readOnGadget(const void *gadget) const const
const QMetaObject * metaObject() const const
QString fromUtf8(QByteArrayView str)
const void * constData() const const
int typeId() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Mar 14 2025 11:50:59 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.