File indexing completed on 2024-11-24 04:46:24
0001 /* 0002 SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "../lib/asn1/berelement.h" 0008 #include "../lib/era/elbticket.h" 0009 #include "../lib/era/fcbticket.h" 0010 #include "../lib/era/ssbv1ticket.h" 0011 #include "../lib/era/ssbv2ticket.h" 0012 #include "../lib/era/ssbv3ticket.h" 0013 #include "../lib/iata/iatabcbp.h" 0014 #include "../lib/uic9183/uic9183head.h" 0015 #include "../lib/uic9183/uic9183header.h" 0016 #include "../lib/uic9183/vendor0080vublockdata.h" 0017 #include "../lib/vdv/vdvticketcontent.h" 0018 0019 #include <kitinerary_version.h> 0020 0021 #include <KItinerary/Uic9183Block> 0022 #include <KItinerary/Uic9183Parser> 0023 #include <KItinerary/Uic9183TicketLayout> 0024 #include <KItinerary/Vendor0080Block> 0025 #include <KItinerary/VdvTicket> 0026 #include <KItinerary/VdvTicketParser> 0027 0028 #include <QCommandLineParser> 0029 #include <QCoreApplication> 0030 #include <QDebug> 0031 #include <QFile> 0032 #include <QMetaProperty> 0033 #include <QSequentialIterable> 0034 0035 #include <iostream> 0036 0037 #include <cstring> 0038 0039 using namespace KItinerary; 0040 0041 template <typename T> 0042 static void dumpGadget(const T *gadget, const char* indent = "") 0043 { 0044 dumpGadget(gadget, &T::staticMetaObject, indent); 0045 } 0046 0047 static void dumpGadget(const void *gadget, const QMetaObject *mo, const char* indent) 0048 { 0049 if (!gadget || !mo) { 0050 return; 0051 } 0052 for (auto i = 0; i < mo->propertyCount(); ++i) { 0053 const auto prop = mo->property(i); 0054 if (!prop.isStored()) { 0055 continue; 0056 } 0057 const auto value = prop.readOnGadget(gadget); 0058 if (prop.isEnumType()) { 0059 std::cout << indent << prop.name() << ": " << prop.enumerator().valueToKey(value.toInt()) << std::endl; 0060 } else { 0061 std::cout << indent << prop.name() << ": " << qPrintable(value.toString()) << std::endl; 0062 } 0063 if (const auto childMo = QMetaType(value.userType()).metaObject()) { 0064 QByteArray childIndent(indent); 0065 childIndent.push_back(' '); 0066 dumpGadget(value.constData(), childMo, childIndent.constData()); 0067 } else if (value.canConvert<QVariantList>() && value.userType() != QMetaType::QString && value.userType() != QMetaType::QByteArray) { 0068 auto iterable = value.value<QSequentialIterable>(); 0069 int idx = 0; 0070 QByteArray childIndent(indent); 0071 childIndent.append(" "); 0072 for (const QVariant &v : iterable) { 0073 if (QMetaType(v.userType()).metaObject()) { 0074 std::cout << indent << " [" << idx++ << "]:" << std::endl; 0075 dumpGadget(v.constData(), QMetaType(v.userType()).metaObject(), childIndent.constData()); 0076 } else { 0077 std::cout << indent << " [" << idx++ << "]: " << qPrintable(v.toString()) << std::endl; 0078 } 0079 } 0080 } 0081 } 0082 } 0083 0084 static void dumpSsbv3Ticket(const QByteArray &data) 0085 { 0086 SSBv3Ticket ticket(data); 0087 0088 const auto typePrefix = QByteArray("type" + QByteArray::number(ticket.ticketTypeCode())); 0089 for (auto i = 0; i < SSBv3Ticket::staticMetaObject.propertyCount(); ++i) { 0090 const auto prop = SSBv3Ticket::staticMetaObject.property(i); 0091 if (!prop.isStored()) { 0092 continue; 0093 } 0094 if (std::strncmp(prop.name(), "type", 4) == 0 && std::strncmp(prop.name(), typePrefix.constData(), 5) != 0) { 0095 continue; 0096 } 0097 0098 const auto value = prop.readOnGadget(&ticket); 0099 std::cout << prop.name() << ": " << qPrintable(value.toString()) << std::endl; 0100 } 0101 0102 std::cout << std::endl; 0103 std::cout << "Issuing day: " << qPrintable(ticket.issueDate().toString(Qt::ISODate)) << std::endl; 0104 switch (ticket.ticketTypeCode()) { 0105 case SSBv3Ticket::IRT_RES_BOA: 0106 std::cout << "Departure day: " << qPrintable(ticket.type1DepartureDay().toString(Qt::ISODate)) << std::endl; 0107 break; 0108 case SSBv3Ticket::NRT: 0109 std::cout << "Valid from: " << qPrintable(ticket.type2ValidFrom().toString(Qt::ISODate)) << std::endl; 0110 std::cout << "Valid until: " << qPrintable(ticket.type2ValidUntil().toString(Qt::ISODate)) << std::endl; 0111 break; 0112 case SSBv3Ticket::GRT: 0113 case SSBv3Ticket::RPT: 0114 break; 0115 } 0116 } 0117 0118 static void dumpSsbv2Ticket(const QByteArray &data) 0119 { 0120 SSBv2Ticket ticket(data); 0121 dumpGadget(&ticket); 0122 } 0123 0124 static void dumpSsbv1Ticket(const QByteArray &data) 0125 { 0126 SSBv1Ticket ticket(data); 0127 dumpGadget(&ticket); 0128 std::cout << std::endl; 0129 std::cout << "First day of validitiy: " << qPrintable(ticket.firstDayOfValidity().toString(Qt::ISODate)) << std::endl; 0130 std::cout << "Departure time: " << qPrintable(ticket.departureTime().toString(Qt::ISODate)) << std::endl; 0131 } 0132 0133 static void dumpElbTicketSegment(const ELBTicketSegment &segment) 0134 { 0135 dumpGadget(&segment, " "); 0136 std::cout << " Departure date: " << qPrintable(segment.departureDate().toString(Qt::ISODate)) << std::endl; 0137 } 0138 0139 static void dumpElbTicket(const ELBTicket &ticket) 0140 { 0141 const auto mo = &ELBTicket::staticMetaObject; 0142 for (auto i = 0; i < mo->propertyCount(); ++i) { 0143 const auto prop = mo->property(i); 0144 if (!prop.isStored() || QMetaType(prop.userType()).metaObject()) { 0145 continue; 0146 } 0147 const auto value = prop.readOnGadget(&ticket); 0148 std::cout << prop.name() << ": " << qPrintable(value.toString()) << std::endl; 0149 } 0150 std::cout << "Emission date: " << qPrintable(ticket.emissionDate().toString(Qt::ISODate)) << std::endl; 0151 std::cout << "Valid from: " << qPrintable(ticket.validFromDate().toString(Qt::ISODate)) << std::endl; 0152 std::cout << "Valid until: " << qPrintable(ticket.validUntilDate().toString(Qt::ISODate)) << std::endl; 0153 std::cout << std::endl << "Segment 1:" << std::endl; 0154 dumpElbTicketSegment(ticket.segment1()); 0155 if (ticket.segment2().isValid()) { 0156 std::cout << std::endl << "Segment 2:" << std::endl; 0157 dumpElbTicketSegment(ticket.segment2()); 0158 } 0159 } 0160 0161 static void dumpRawData(const char *data, std::size_t size) 0162 { 0163 bool isText = true; 0164 for (std::size_t i = 0; i < size && isText; ++i) { 0165 isText = (uint8_t)*(data + i) >= 20; 0166 } 0167 0168 if (isText) { 0169 std::cout.write(data, size); 0170 } else { 0171 std::cout << "(hex) " << QByteArray(data, size).toHex().constData(); 0172 } 0173 } 0174 0175 static void dumpUic9183(const QByteArray &data) 0176 { 0177 Uic9183Parser parser; 0178 parser.parse(data); 0179 std::cout << " Header:" << std::endl; 0180 const auto header = parser.header(); 0181 dumpGadget(&header, " "); 0182 0183 for (auto block = parser.firstBlock(); !block.isNull(); block = block.nextBlock()) { 0184 std::cout << " Block: "; 0185 std::cout.write(block.name(), 6); 0186 std::cout << ", size: " << block.size() << ", version: " << block.version() << std::endl; 0187 0188 if (block.isA<Uic9183Head>()) { 0189 Uic9183Head head(block); 0190 dumpGadget(&head, " "); 0191 } else if (block.isA<Uic9183TicketLayout>()) { 0192 Uic9183TicketLayout tlay(block); 0193 std::cout << " Layout standard: " << qPrintable(tlay.type()) << std::endl; 0194 for (auto field = tlay.firstField(); !field.isNull(); field = field.next()) { 0195 std::cout << " [row: " << field.row() << " column: " << field.column() 0196 << " height: " << field.height() << " width: " << field.width() 0197 << " format: " << field.format() << "]: " << qPrintable(field.text()) 0198 << std::endl; 0199 } 0200 } else if (block.isA<Fcb::UicRailTicketData>()) { 0201 Fcb::UicRailTicketData fcb(block); 0202 dumpGadget(&fcb, " "); 0203 } else if (block.isA<Vendor0080BLBlock>()) { 0204 Vendor0080BLBlock vendor(block); 0205 dumpGadget(&vendor, " "); 0206 for (int i = 0; i < vendor.orderBlockCount(); ++i) { 0207 const auto order = vendor.orderBlock(i); 0208 std::cout << " Order block " << (i + 1) << ":" << std::endl; 0209 dumpGadget(&order, " "); 0210 } 0211 std::cout << " S-blocks:" << std::endl; 0212 for (auto sub = vendor.firstBlock(); !sub.isNull(); sub = sub.nextBlock()) { 0213 std::cout << " "; 0214 std::cout.write(sub.id(), 3); 0215 std::cout << " (size: " << sub.size() << "): "; 0216 dumpRawData(sub.content(), sub.contentSize()); 0217 std::cout << std::endl; 0218 } 0219 } else if (block.isA<Vendor0080VUBlock>()) { 0220 Vendor0080VUBlock vendor(block); 0221 dumpGadget(vendor.commonData(), " "); 0222 for (int i = 0; i < (int)vendor.commonData()->numberOfTickets; ++i) { 0223 const auto ticket = vendor.ticketData(i); 0224 std::cout << " Ticket " << (i + 1) << ":" << std::endl; 0225 dumpGadget(ticket, " "); 0226 dumpGadget(&ticket->validityArea, " "); 0227 std::cout << " payload: (hex) " << QByteArray((const char*)&ticket->validityArea + sizeof(VdvTicketValidityAreaData), ticket->validityAreaDataSize - sizeof(VdvTicketValidityAreaData)).toHex().constData() << std::endl; 0228 } 0229 } else { 0230 std::cout << " Content: "; 0231 dumpRawData(block.content(), block.contentSize()); 0232 std::cout << std::endl; 0233 } 0234 } 0235 } 0236 0237 static void dumpVdv(const QByteArray &data) 0238 { 0239 VdvTicketParser parser; 0240 if (!parser.parse(data)) { 0241 std::cerr << "failed to parse VDV ticket" << std::endl; 0242 return; 0243 } 0244 const auto ticket = parser.ticket(); 0245 std::cout << " Header:" << std::endl; 0246 dumpGadget(ticket.header(), " "); 0247 0248 std::cout << " Product data:" << std::endl; 0249 for (auto block = ticket.productData().first(); block.isValid(); block = block.next()) { 0250 std::cout << " Tag: 0x" << std::hex << block.type() << std::dec << " size: " << block.size() << std::endl; 0251 switch (block.type()) { 0252 case VdvTicketBasicData::Tag: 0253 dumpGadget(block.contentAt<VdvTicketBasicData>(), " "); 0254 break; 0255 case VdvTicketTravelerData::Tag: 0256 { 0257 const auto traveler = block.contentAt<VdvTicketTravelerData>(); 0258 dumpGadget(traveler, " "); 0259 std::cout << " name: " << qPrintable(QString::fromUtf8(traveler->name(), traveler->nameSize(block.contentSize()))) << std::endl; 0260 break; 0261 } 0262 case VdvTicketValidityAreaData::Tag: 0263 { 0264 const auto area = block.contentAt<VdvTicketValidityAreaData>(); 0265 0266 switch (area->type) { 0267 case VdvTicketValidityAreaDataType31::Type: 0268 { 0269 const auto area31 = static_cast<const VdvTicketValidityAreaDataType31*>(area); 0270 dumpGadget(area31, " "); 0271 std::cout << " payload: (hex) " << QByteArray((const char*)block.contentData() + sizeof(VdvTicketValidityAreaDataType31), block.contentSize() - sizeof(VdvTicketValidityAreaDataType31)).toHex().constData() << std::endl; 0272 break; 0273 } 0274 default: 0275 dumpGadget(area, " "); 0276 std::cout << " payload: (hex) " << QByteArray((const char*)block.contentData() + sizeof(VdvTicketValidityAreaData), block.contentSize() - sizeof(VdvTicketValidityAreaData)).toHex().constData() << std::endl; 0277 break; 0278 } 0279 break; 0280 } 0281 default: 0282 std::cout << " (hex) " << QByteArray((const char*)block.contentData(), block.contentSize()).toHex().constData() << std::endl; 0283 } 0284 } 0285 0286 std::cout << " Transaction data:" << std::endl; 0287 dumpGadget(ticket.commonTransactionData(), " "); 0288 std::cout << " Product-specific transaction data (" << ticket.productSpecificTransactionData().contentSize() << " bytes):" << std::endl; 0289 for (auto block = ticket.productSpecificTransactionData().first(); block.isValid(); block = block.next()) { 0290 std::cout << " Tag: " << block.type() << " size: " << block.size() << std::endl; 0291 switch (block.type()) { 0292 default: 0293 std::cout << " (hex) " << QByteArray((const char*)block.contentData(), block.contentSize()).toHex().constData() << std::endl; 0294 } 0295 } 0296 0297 std::cout << " Issue data:" << std::endl; 0298 dumpGadget(ticket.issueData(), " "); 0299 std::cout << " Trailer:" << std::endl; 0300 std::cout << " identifier: "; 0301 std::cout.write(ticket.trailer()->identifier, 3); 0302 std::cout << std::endl; 0303 std::cout << " version: " << ticket.trailer()->version << std::endl; 0304 } 0305 0306 void dumpIataBcbp(const QString &data, const QDateTime &contextDate) 0307 { 0308 IataBcbp ticket(data); 0309 if (!ticket.isValid()) { 0310 std::cout << "invalid format" << std::endl; 0311 return; 0312 } 0313 0314 const auto ums = ticket.uniqueMandatorySection(); 0315 dumpGadget(&ums); 0316 const auto ucs = ticket.uniqueConditionalSection(); 0317 dumpGadget(&ucs); 0318 const auto issueDate = ucs.dateOfIssue(contextDate); 0319 std::cout << "Date of issue: " << qPrintable(issueDate.toString(Qt::ISODate)) << std::endl; 0320 0321 for (auto i = 0; i < ums.numberOfLegs(); ++i) { 0322 std::cout << "Leg " << (i + 1) << std::endl; 0323 const auto rms = ticket.repeatedMandatorySection(i); 0324 dumpGadget(&rms, " "); 0325 const auto rcs = ticket.repeatedConditionalSection(i); 0326 dumpGadget(&rcs, " "); 0327 std::cout << " Airline use section: " << qPrintable(ticket.airlineUseSection(i)) << std::endl; 0328 std::cout << " Date of flight: " << qPrintable(rms.dateOfFlight(issueDate.isValid() ? QDateTime(issueDate, {}) : contextDate).toString(Qt::ISODate)) << std::endl; 0329 } 0330 0331 if (ticket.hasSecuritySection()) { 0332 std::cout << "Security:" << std::endl; 0333 const auto sec = ticket.securitySection(); 0334 dumpGadget(&sec, " "); 0335 } 0336 } 0337 0338 int main(int argc, char **argv) 0339 { 0340 QCoreApplication::setApplicationName(QStringLiteral("ticket-barcode-dump")); 0341 QCoreApplication::setApplicationVersion(QStringLiteral(KITINERARY_VERSION_STRING)); 0342 QCoreApplication::setOrganizationDomain(QStringLiteral("kde.org")); 0343 QCoreApplication::setOrganizationName(QStringLiteral("KDE")); 0344 QCoreApplication app(argc, argv); 0345 0346 QCommandLineParser parser; 0347 parser.setApplicationDescription(QStringLiteral("Decode ticket barcode content.")); 0348 parser.addHelpOption(); 0349 parser.addVersionOption(); 0350 QCommandLineOption contextDateOpt(QStringLiteral("context-date"), QStringLiteral("Context to resolve incomplete dates."), QStringLiteral("yyyy-MM-dd")); 0351 parser.addOption(contextDateOpt); 0352 parser.addPositionalArgument(QStringLiteral("input"), QStringLiteral("File to read data from, omit for using stdin.")); 0353 parser.process(app); 0354 0355 auto contextDate = QDate::currentDate(); 0356 if (parser.isSet(contextDateOpt)) { 0357 contextDate = QDate::fromString(parser.value(contextDateOpt), Qt::ISODate); 0358 } 0359 0360 QFile file; 0361 if (parser.positionalArguments().isEmpty()) { 0362 file.open(stdin, QFile::ReadOnly); 0363 } else { 0364 file.setFileName(parser.positionalArguments().at(0)); 0365 if (!file.open(QFile::ReadOnly)) { 0366 std::cerr << qPrintable(file.errorString()) << std::endl; 0367 return 1; 0368 } 0369 } 0370 0371 const auto data = file.readAll(); 0372 0373 if (IataBcbp::maybeIataBcbp(data)) { 0374 std::cout << "IATA Barcoded Boarding Pass" << std::endl; 0375 dumpIataBcbp(QString::fromUtf8(data), QDateTime(contextDate, {})); 0376 } else if (SSBv3Ticket::maybeSSB(data)) { 0377 std::cout << "ERA SSB Ticket" << std::endl; 0378 dumpSsbv3Ticket(data); 0379 } else if (SSBv2Ticket::maybeSSB(data)) { 0380 std::cout << "ERA SSB Ticket" << std::endl; 0381 dumpSsbv2Ticket(data); 0382 } else if (SSBv1Ticket::maybeSSB(data)) { 0383 std::cout << "ERA SSB Ticket" << std::endl; 0384 dumpSsbv1Ticket(data); 0385 } else if (Uic9183Parser::maybeUic9183(data)) { 0386 std::cout << "UIC 918.3 Container" << std::endl; 0387 dumpUic9183(data); 0388 } else if (VdvTicketParser::maybeVdvTicket(data)) { 0389 std::cout << "VDV Ticket" << std::endl; 0390 dumpVdv(data); 0391 } else if (auto ticket = ELBTicket::parse(data); ticket) { 0392 std::cout << "ERA ELB Ticket" << std::endl; 0393 dumpElbTicket(*ticket); 0394 } else { 0395 std::cout << "Unknown content" << std::endl; 0396 return 1; 0397 } 0398 0399 return 0; 0400 }