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

KDE's Doxygen guidelines are available online.