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

KDE's Doxygen guidelines are available online.