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
42template <typename T>
43static void dumpGadget(const T *gadget, const char* indent = "")
44{
45 dumpGadget(gadget, &T::staticMetaObject, indent);
46}
47
48static void dumpGadget(const void *gadget, const QMetaObject *mo, const char* indent)
49{
50 if (!gadget || !mo) {
51 return;
52 }
53 for (auto i = 0; i < mo->propertyCount(); ++i) {
54 const auto prop = mo->property(i);
55 if (!prop.isStored()) {
56 continue;
57 }
58 const auto value = prop.readOnGadget(gadget);
59 if (prop.isEnumType()) {
60 std::cout << indent << prop.name() << ": " << prop.enumerator().valueToKey(value.toInt()) << std::endl;
61 } else {
62 std::cout << indent << prop.name() << ": " << qPrintable(value.toString()) << std::endl;
63 }
64 if (const auto childMo = QMetaType(value.userType()).metaObject()) {
65 QByteArray childIndent(indent);
66 childIndent.push_back(' ');
67 dumpGadget(value.constData(), childMo, childIndent.constData());
68 } else if (value.canConvert<QVariantList>() && value.userType() != QMetaType::QString && value.userType() != QMetaType::QByteArray) {
69 auto iterable = value.value<QSequentialIterable>();
70 int idx = 0;
71 QByteArray childIndent(indent);
72 childIndent.append(" ");
73 for (const QVariant &v : iterable) {
74 if (QMetaType(v.userType()).metaObject()) {
75 std::cout << indent << " [" << idx++ << "]:" << std::endl;
76 dumpGadget(v.constData(), QMetaType(v.userType()).metaObject(), childIndent.constData());
77 } else {
78 std::cout << indent << " [" << idx++ << "]: " << qPrintable(v.toString()) << std::endl;
79 }
80 }
81 }
82 }
83}
84
85static void dumpSsbv3Ticket(const QByteArray &data)
86{
87 SSBv3Ticket ticket(data);
88
89 const auto typePrefix = QByteArray("type" + QByteArray::number(ticket.ticketTypeCode()));
90 for (auto i = 0; i < SSBv3Ticket::staticMetaObject.propertyCount(); ++i) {
91 const auto prop = SSBv3Ticket::staticMetaObject.property(i);
92 if (!prop.isStored()) {
93 continue;
94 }
95 if (std::strncmp(prop.name(), "type", 4) == 0 && std::strncmp(prop.name(), typePrefix.constData(), 5) != 0) {
96 continue;
97 }
98
99 const auto value = prop.readOnGadget(&ticket);
100 std::cout << prop.name() << ": " << qPrintable(value.toString()) << std::endl;
101 }
102
103 std::cout << std::endl;
104 std::cout << "Issuing day: " << qPrintable(ticket.issueDate().toString(Qt::ISODate)) << std::endl;
105 switch (ticket.ticketTypeCode()) {
106 case SSBv3Ticket::IRT_RES_BOA:
107 std::cout << "Departure day: " << qPrintable(ticket.type1DepartureDay().toString(Qt::ISODate)) << std::endl;
108 break;
109 case SSBv3Ticket::NRT:
110 std::cout << "Valid from: " << qPrintable(ticket.type2ValidFrom().toString(Qt::ISODate)) << std::endl;
111 std::cout << "Valid until: " << qPrintable(ticket.type2ValidUntil().toString(Qt::ISODate)) << std::endl;
112 break;
113 case SSBv3Ticket::GRT:
114 case SSBv3Ticket::RPT:
115 break;
116 }
117}
118
119static void dumpSsbv2Ticket(const QByteArray &data)
120{
121 SSBv2Ticket ticket(data);
122 dumpGadget(&ticket);
123}
124
125static void dumpSsbv1Ticket(const QByteArray &data)
126{
127 SSBv1Ticket ticket(data);
128 dumpGadget(&ticket);
129 std::cout << std::endl;
130 std::cout << "First day of validitiy: " << qPrintable(ticket.firstDayOfValidity().toString(Qt::ISODate)) << std::endl;
131 std::cout << "Departure time: " << qPrintable(ticket.departureTime().toString(Qt::ISODate)) << std::endl;
132}
133
134static void dumpElbTicketSegment(const ELBTicketSegment &segment)
135{
136 dumpGadget(&segment, " ");
137 std::cout << " Departure date: " << qPrintable(segment.departureDate().toString(Qt::ISODate)) << std::endl;
138}
139
140static void dumpElbTicket(const ELBTicket &ticket)
141{
142 const auto mo = &ELBTicket::staticMetaObject;
143 for (auto i = 0; i < mo->propertyCount(); ++i) {
144 const auto prop = mo->property(i);
145 if (!prop.isStored() || QMetaType(prop.userType()).metaObject()) {
146 continue;
147 }
148 const auto value = prop.readOnGadget(&ticket);
149 std::cout << prop.name() << ": " << qPrintable(value.toString()) << std::endl;
150 }
151 std::cout << "Emission date: " << qPrintable(ticket.emissionDate().toString(Qt::ISODate)) << std::endl;
152 std::cout << "Valid from: " << qPrintable(ticket.validFromDate().toString(Qt::ISODate)) << std::endl;
153 std::cout << "Valid until: " << qPrintable(ticket.validUntilDate().toString(Qt::ISODate)) << std::endl;
154 std::cout << std::endl << "Segment 1:" << std::endl;
155 dumpElbTicketSegment(ticket.segment1());
156 if (ticket.segment2().isValid()) {
157 std::cout << std::endl << "Segment 2:" << std::endl;
158 dumpElbTicketSegment(ticket.segment2());
159 }
160}
161
162static void dumpRawData(const char *data, std::size_t size)
163{
164 bool isText = true;
165 for (std::size_t i = 0; i < size && isText; ++i) {
166 isText = (uint8_t)*(data + i) >= 20;
167 }
168
169 if (isText) {
170 std::cout.write(data, size);
171 } else {
172 std::cout << "(hex) " << QByteArray(data, size).toHex().constData();
173 }
174}
175
176static void dumpUic9183(const QByteArray &data)
177{
178 Uic9183Parser parser;
179 parser.parse(data);
180 std::cout << " Header:" << std::endl;
181 const auto header = parser.header();
182 dumpGadget(&header, " ");
183
184 for (auto block = parser.firstBlock(); !block.isNull(); block = block.nextBlock()) {
185 std::cout << " Block: ";
186 std::cout.write(block.name(), 6);
187 std::cout << ", size: " << block.size() << ", version: " << block.version() << std::endl;
188
189 if (block.isA<Uic9183Head>()) {
190 Uic9183Head head(block);
191 dumpGadget(&head, " ");
192 } else if (block.isA<Uic9183TicketLayout>()) {
193 Uic9183TicketLayout tlay(block);
194 std::cout << " Layout standard: " << qPrintable(tlay.type()) << std::endl;
195 for (auto field = tlay.firstField(); !field.isNull(); field = field.next()) {
196 std::cout << " [row: " << field.row() << " column: " << field.column()
197 << " height: " << field.height() << " width: " << field.width()
198 << " format: " << field.format() << "]: " << qPrintable(field.text())
199 << std::endl;
200 }
201 } else if (block.isA<Uic9183Flex>()) {
202 Uic9183Flex flex(block);
203 const auto fcbProp = Uic9183Flex::staticMetaObject.property(Uic9183Flex::staticMetaObject.indexOfProperty("fcb"));
204 QVariant fcbData = fcbProp.readOnGadget(&flex);
205 dumpGadget(fcbData.constData(), QMetaType(fcbData.typeId()).metaObject(), " ");
206 } else if (block.isA<Vendor0080BLBlock>()) {
207 Vendor0080BLBlock vendor(block);
208 dumpGadget(&vendor, " ");
209 for (int i = 0; i < vendor.orderBlockCount(); ++i) {
210 const auto order = vendor.orderBlock(i);
211 std::cout << " Order block " << (i + 1) << ":" << std::endl;
212 dumpGadget(&order, " ");
213 }
214 std::cout << " S-blocks:" << std::endl;
215 for (auto sub = vendor.firstBlock(); !sub.isNull(); sub = sub.nextBlock()) {
216 std::cout << " ";
217 std::cout.write(sub.id(), 3);
218 std::cout << " (size: " << sub.size() << "): ";
219 dumpRawData(sub.content(), sub.contentSize());
220 std::cout << std::endl;
221 }
222 } else if (block.isA<Vendor0080VUBlock>()) {
223 Vendor0080VUBlock vendor(block);
224 dumpGadget(vendor.commonData(), " ");
225 for (int i = 0; i < (int)vendor.commonData()->numberOfTickets; ++i) {
226 const auto ticket = vendor.ticketData(i);
227 std::cout << " Ticket " << (i + 1) << ":" << std::endl;
228 dumpGadget(ticket, " ");
229 dumpGadget(&ticket->validityArea, " ");
230 std::cout << " payload: (hex) " << QByteArray((const char*)&ticket->validityArea + sizeof(VdvTicketValidityAreaData), ticket->validityAreaDataSize - sizeof(VdvTicketValidityAreaData)).toHex().constData() << std::endl;
231 }
232 } else {
233 std::cout << " Content: ";
234 dumpRawData(block.content(), block.contentSize());
235 std::cout << std::endl;
236 }
237 }
238}
239
240static void dumpVdv(const QByteArray &data)
241{
242 VdvTicketParser parser;
243 if (!parser.parse(data)) {
244 std::cerr << "failed to parse VDV ticket" << std::endl;
245 return;
246 }
247 const auto ticket = parser.ticket();
248 std::cout << " Header:" << std::endl;
249 dumpGadget(ticket.header(), " ");
250
251 std::cout << " Product data:" << std::endl;
252 for (auto block = ticket.productData().first(); block.isValid(); block = block.next()) {
253 std::cout << " Tag: 0x" << std::hex << block.type() << std::dec << " size: " << block.size() << std::endl;
254 switch (block.type()) {
255 case VdvTicketBasicData::Tag:
256 dumpGadget(block.contentAt<VdvTicketBasicData>(), " ");
257 break;
258 case VdvTicketTravelerData::Tag:
259 {
260 const auto traveler = block.contentAt<VdvTicketTravelerData>();
261 dumpGadget(traveler, " ");
262 std::cout << " name: " << qPrintable(QString::fromUtf8(traveler->name(), traveler->nameSize(block.contentSize()))) << std::endl;
263 break;
264 }
265 case VdvTicketValidityAreaData::Tag:
266 {
267 const auto area = block.contentAt<VdvTicketValidityAreaData>();
268
269 switch (area->type) {
270 case VdvTicketValidityAreaDataType31::Type:
271 {
272 const auto area31 = static_cast<const VdvTicketValidityAreaDataType31*>(area);
273 dumpGadget(area31, " ");
274 std::cout << " payload: (hex) " << QByteArray((const char*)block.contentData() + sizeof(VdvTicketValidityAreaDataType31), block.contentSize() - sizeof(VdvTicketValidityAreaDataType31)).toHex().constData() << std::endl;
275 break;
276 }
277 default:
278 dumpGadget(area, " ");
279 std::cout << " payload: (hex) " << QByteArray((const char*)block.contentData() + sizeof(VdvTicketValidityAreaData), block.contentSize() - sizeof(VdvTicketValidityAreaData)).toHex().constData() << std::endl;
280 break;
281 }
282 break;
283 }
284 default:
285 std::cout << " (hex) " << QByteArray((const char*)block.contentData(), block.contentSize()).toHex().constData() << std::endl;
286 }
287 }
288
289 std::cout << " Transaction data:" << std::endl;
290 dumpGadget(ticket.commonTransactionData(), " ");
291 std::cout << " Product-specific transaction data (" << ticket.productSpecificTransactionData().contentSize() << " bytes):" << std::endl;
292 for (auto block = ticket.productSpecificTransactionData().first(); block.isValid(); block = block.next()) {
293 std::cout << " Tag: " << block.type() << " size: " << block.size() << std::endl;
294 switch (block.type()) {
295 default:
296 std::cout << " (hex) " << QByteArray((const char*)block.contentData(), block.contentSize()).toHex().constData() << std::endl;
297 }
298 }
299
300 std::cout << " Issue data:" << std::endl;
301 dumpGadget(ticket.issueData(), " ");
302 std::cout << " Trailer:" << std::endl;
303 std::cout << " identifier: ";
304 std::cout.write(ticket.trailer()->identifier, 3);
305 std::cout << std::endl;
306 std::cout << " version: " << ticket.trailer()->version << std::endl;
307}
308
309void dumpIataBcbp(const QString &data, const QDateTime &contextDate)
310{
311 IataBcbp ticket(data);
312 if (!ticket.isValid()) {
313 std::cout << "invalid format" << std::endl;
314 return;
315 }
316
317 const auto ums = ticket.uniqueMandatorySection();
318 dumpGadget(&ums);
319 const auto ucs = ticket.uniqueConditionalSection();
320 dumpGadget(&ucs);
321 const auto issueDate = ucs.dateOfIssue(contextDate);
322 std::cout << "Date of issue: " << qPrintable(issueDate.toString(Qt::ISODate)) << std::endl;
323
324 for (auto i = 0; i < ums.numberOfLegs(); ++i) {
325 std::cout << "Leg " << (i + 1) << std::endl;
326 const auto rms = ticket.repeatedMandatorySection(i);
327 dumpGadget(&rms, " ");
328 const auto rcs = ticket.repeatedConditionalSection(i);
329 dumpGadget(&rcs, " ");
330 std::cout << " Airline use section: " << qPrintable(ticket.airlineUseSection(i)) << std::endl;
331 std::cout << " Date of flight: " << qPrintable(rms.dateOfFlight(issueDate.isValid() ? QDateTime(issueDate, {}) : contextDate).toString(Qt::ISODate)) << std::endl;
332 }
333
334 if (ticket.hasSecuritySection()) {
335 std::cout << "Security:" << std::endl;
336 const auto sec = ticket.securitySection();
337 dumpGadget(&sec, " ");
338 }
339}
340
341int main(int argc, char **argv)
342{
343 QCoreApplication::setApplicationName(QStringLiteral("ticket-barcode-dump"));
344 QCoreApplication::setApplicationVersion(QStringLiteral(KITINERARY_VERSION_STRING));
345 QCoreApplication::setOrganizationDomain(QStringLiteral("kde.org"));
346 QCoreApplication::setOrganizationName(QStringLiteral("KDE"));
347 QCoreApplication app(argc, argv);
348
349 QCommandLineParser parser;
350 parser.setApplicationDescription(QStringLiteral("Decode ticket barcode content."));
351 parser.addHelpOption();
352 parser.addVersionOption();
353 QCommandLineOption contextDateOpt(QStringLiteral("context-date"), QStringLiteral("Context to resolve incomplete dates."), QStringLiteral("yyyy-MM-dd"));
354 parser.addOption(contextDateOpt);
355 parser.addPositionalArgument(QStringLiteral("input"), QStringLiteral("File to read data from, omit for using stdin."));
356 parser.process(app);
357
358 auto contextDate = QDate::currentDate();
359 if (parser.isSet(contextDateOpt)) {
360 contextDate = QDate::fromString(parser.value(contextDateOpt), Qt::ISODate);
361 }
362
363 QFile file;
364 if (parser.positionalArguments().isEmpty()) {
365 file.open(stdin, QFile::ReadOnly);
366 } else {
367 file.setFileName(parser.positionalArguments().at(0));
368 if (!file.open(QFile::ReadOnly)) {
369 std::cerr << qPrintable(file.errorString()) << std::endl;
370 return 1;
371 }
372 }
373
374 const auto data = file.readAll();
375
376 if (IataBcbp::maybeIataBcbp(data)) {
377 std::cout << "IATA Barcoded Boarding Pass" << std::endl;
378 dumpIataBcbp(QString::fromUtf8(data), QDateTime(contextDate, {}));
379 } else if (SSBv3Ticket::maybeSSB(data)) {
380 std::cout << "ERA SSB Ticket" << std::endl;
381 dumpSsbv3Ticket(data);
382 } else if (SSBv2Ticket::maybeSSB(data)) {
383 std::cout << "ERA SSB Ticket" << std::endl;
384 dumpSsbv2Ticket(data);
385 } else if (SSBv1Ticket::maybeSSB(data)) {
386 std::cout << "ERA SSB Ticket" << std::endl;
387 dumpSsbv1Ticket(data);
388 } else if (Uic9183Parser::maybeUic9183(data)) {
389 std::cout << "UIC 918.3 Container" << std::endl;
390 dumpUic9183(data);
391 } else if (VdvTicketParser::maybeVdvTicket(data)) {
392 std::cout << "VDV Ticket" << std::endl;
393 dumpVdv(data);
394 } else if (auto ticket = ELBTicket::parse(data); ticket) {
395 std::cout << "ERA ELB Ticket" << std::endl;
396 dumpElbTicket(*ticket);
397 } else {
398 std::cout << "Unknown content" << std::endl;
399 return 1;
400 }
401
402 return 0;
403}
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 Jan 24 2025 11:52:36 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.