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 }