diff --git a/src/iatabcbpparser.cpp b/src/iatabcbpparser.cpp index 5106d65..8993a4a 100644 --- a/src/iatabcbpparser.cpp +++ b/src/iatabcbpparser.cpp @@ -1,239 +1,247 @@ /* Copyright (C) 2018 Volker Krause This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "iatabcbpparser.h" #include "logging.h" #include #include #include #include #include #include #include #include using namespace KItinerary; namespace KItinerary { enum Constants { UniqueMandatorySize = 23, RepeatedMandatorySize = 37, FormatCode = 'M', BeginOfVersionNumber = '>' }; static QStringRef stripLeadingZeros(const QStringRef &s) { const auto it = std::find_if(s.begin(), s.end(), [](const QChar &c) { return c != QLatin1Char('0'); }); const auto d = std::distance(s.begin(), it); return s.mid(d); } static int readHexValue(const QStringRef &s, int width) { return s.mid(0, width).toInt(nullptr, 16); } static int parseRepeatedMandatorySection(const QStringRef& msg, FlightReservation& res) { if (msg.size() < 24) { // pre-checking data, technically incomplete, but we can make use of this nevertheless qCWarning(Log) << "IATA BCBP repeated mandatory section too short"; return -1; // error } res.setReservationNumber(msg.mid(0, 7).trimmed().toString()); Flight flight; Airport airport; airport.setIataCode(msg.mid(7, 3).toString()); flight.setDepartureAirport(airport); airport.setIataCode(msg.mid(10, 3).toString()); flight.setArrivalAirport(airport); Airline airline; airline.setIataCode(msg.mid(13, 3).trimmed().toString()); flight.setAirline(airline); flight.setFlightNumber(stripLeadingZeros(msg.mid(16, 5).trimmed()).toString()); // 3x Date of flight, as days since Jan 1st // we don't know the year here, so use 1970, will be filled up or discarded by the caller const auto days = msg.mid(21, 3).toInt() - 1; flight.setDepartureDay(QDate(1970, 1, 1).addDays(days)); res.setReservationFor(flight); if (msg.size() < RepeatedMandatorySize) { return 0; } // 1x Compartment code res.setAirplaneSeat(stripLeadingZeros(msg.mid(25, 4)).trimmed().toString()); res.setPassengerSequenceNumber(stripLeadingZeros(msg.mid(29, 5)).trimmed().toString()); // 1x Passenger status // field size of conditional section + airline use section return readHexValue(msg.mid(35), 2); } } QVector IataBcbpParser::parse(const QString& message, const QDate &externalIssueDate) { - if (message.size() < UniqueMandatorySize) { - qCWarning(Log) << "IATA BCBP code too short for unique mandatory section"; - return {}; - } - if (message.at(0) != QLatin1Char(FormatCode) || !message.at(1).isDigit()) { - qCWarning(Log) << "IATA BCBP code invalid unique mandatory section format"; + if (!IataBcbpParser::maybeIataBcbp(message)) { + qCWarning(Log) << "IATA BCBP code too short for unique mandatory section, or invalid mandatory section format"; return {}; } // parse unique mandatory section const auto legCount = message.at(1).toLatin1() - '0'; QVector result; result.reserve(legCount); FlightReservation res1; { Person person; const auto fullName = message.midRef(2, 20).trimmed(); const auto idx = fullName.indexOf(QLatin1Char('/')); if (idx > 0 && idx < fullName.size() - 1) { person.setFamilyName(fullName.left(idx).toString()); person.setGivenName(fullName.mid(idx + 1).toString()); } else { person.setName(fullName.toString()); } res1.setUnderName(person); } { Ticket ticket; ticket.setTicketToken(QStringLiteral("aztecCode:") + message); res1.setReservedTicket(ticket); } const auto varSize = parseRepeatedMandatorySection(message.midRef(UniqueMandatorySize), res1); if (varSize < 0) { // parser error return {}; } int index = UniqueMandatorySize + RepeatedMandatorySize; auto issueDate = externalIssueDate; if (varSize > 0) { if (message.size() < (index + varSize)) { qCWarning(Log) << "IATA BCBP code too short for conditional section in first leg" << varSize << message.size(); return {}; } // parse unique conditional section, if there is one, otherwise we skip all of this assuming "for airline use" if (message.at(index) == QLatin1Char(BeginOfVersionNumber)) { // 1x version number // 2x field size of unique conditional section const auto uniqCondSize = readHexValue(message.midRef(index + 2), 2); if (uniqCondSize + 4 > varSize) { qCWarning(Log) << "IATA BCBP unique conditional section has invalid size" << varSize << uniqCondSize; return {}; } // 1x passenger description // 1x source of checking // 1x source of boarding pass issuance // 4x date of issue of boarding pass // this only contains the last digit of the year (sic), but we assume it to be in the past // so this still gives us a 10 year range of correctly determined times if (uniqCondSize >= 11 && externalIssueDate.isValid() && message.at(index + 7).isDigit() && message.at(index + 10).isDigit()) { const auto year = message.at(index + 7).toLatin1() - '0'; const auto days = message.midRef(index + 8, 3).toInt() - 1; if (year < 0 || year > 9 || days < 0 || days > 365) { qCWarning(Log) << "IATA BCBP invalid boarding pass issue date format" << message.midRef(index + 7, 4); return {}; } auto currentYear = externalIssueDate.year() - externalIssueDate.year() % 10 + year; if (currentYear > externalIssueDate.year()) { currentYear -= 10; } issueDate = QDate(currentYear, 1, 1).addDays(days); } // 1x document type // 3x airline code of boarding pass issuer // 3x 13x baggage tag numbers // skip repeated conditional section, containing mainly bonus program data } // skip for airline use section index += varSize; } result.push_back(res1); // all following legs only contain repeated sections, copy content from the unique ones from the first leg for (int i = 1; i < legCount; ++i) { if (message.size() < (index + RepeatedMandatorySize)) { qCWarning(Log) << "IATA BCBP repeated mandatory section too short" << i; return {}; } FlightReservation res = res1; const auto varSize = parseRepeatedMandatorySection(message.midRef(index), res); if (varSize < 0) { // parser error return {}; } index += RepeatedMandatorySize; if (message.size() < (index + varSize)) { qCWarning(Log) << "IATA BCBP repeated conditional section too short" << i; return {}; } // skip repeated conditional section // skip for airline use section index += varSize; result.push_back(res); } // optional security section at the end, not interesting for us // complete departure dates with the now (hopefully known issue date) for (auto it = result.begin(); it != result.end(); ++it) { auto res = (*it).value(); auto flight = res.reservationFor().value(); if (issueDate.isValid()) { const auto days = flight.departureDay().dayOfYear() - 1; QDate date(issueDate.year(), 1, 1); date = date.addDays(days); if (date >= issueDate) { flight.setDepartureDay(date); } else { flight.setDepartureDay(QDate(issueDate.year() + 1, 1, 1).addDays(days)); } } else { flight.setDepartureDay(QDate()); } res.setReservationFor(flight); *it = res; } return result; } + +bool IataBcbpParser::maybeIataBcbp(const QString &message) +{ + if (message.size() < UniqueMandatorySize) { + return false; + } + if (message.at(0) != QLatin1Char(FormatCode) || !message.at(1).isDigit()) { + return false; + } + + return true; +} diff --git a/src/iatabcbpparser.h b/src/iatabcbpparser.h index 2a43ec3..52ba26b 100644 --- a/src/iatabcbpparser.h +++ b/src/iatabcbpparser.h @@ -1,50 +1,55 @@ /* Copyright (C) 2018 Volker Krause This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef KITINERARY_IATABCBPPARSER_H #define KITINERARY_IATABCBPPARSER_H #include "kitinerary_export.h" #include #include #include class QDate; class QString; class QVariant; namespace KItinerary { /** * Parser for IATA Bar Coded Boarding Pass messages. * @see https://www.iata.org/whatwedo/stb/Documents/BCBP-Implementation-Guide-5th-Edition-June-2016.pdf */ namespace IataBcbpParser { /** Parses the bar coded boarding pass message @p message into * a list of FlightReservation instances. - * @param issueDate The date the boarding pass was issued (or a sufficiently close approximation). + * @param externalIssueDate The date the boarding pass was issued (or a sufficiently close approximation). * This is necessary as by default the BCBP data only contains day and month of the flight, not the year. */ -KITINERARY_EXPORT QVector parse(const QString &message, const QDate &issueDate = QDate()); +KITINERARY_EXPORT QVector parse(const QString &message, const QDate &externalIssueDate = QDate()); + +/** Quickly test if @p message could be a IATA BCBP code. + * This optimizes for speed rather than correctness, for use in barcode content auto-detection. + */ +bool maybeIataBcbp(const QString &message); } } #endif // KITINERARY_IATABCBPPARSER_H diff --git a/src/uic9183parser.cpp b/src/uic9183parser.cpp index feb2507..ab377d6 100644 --- a/src/uic9183parser.cpp +++ b/src/uic9183parser.cpp @@ -1,585 +1,593 @@ /* Copyright (C) 2018 Volker Krause This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "uic9183parser.h" #include "logging.h" #include #include #include #include #include using namespace KItinerary; static int asciiToInt(const char *s, int size) { if (!s) { return 0; } int v = 0; for (int i = 0; i < size; ++i) { v *= 10; v += (*(s + i)) - '0'; } return v; } namespace KItinerary { class Uic9183Block { public: Uic9183Block() = default; Uic9183Block(const char *data, int size); const char *data() const { return m_data; } int version() const; int size() const { return m_size; } bool isNull() const { return m_size <= 12; } private: const char *m_data = nullptr; int m_size = 0; }; // 0080BL vendor block sub-block ("S block") // 1x 'S' // 3x field type // 4x field value length // nx field value class Vendor0080BLSubBlock { public: Vendor0080BLSubBlock() = default; Vendor0080BLSubBlock(const char *data, int size); bool isNull() const { return m_size <= 0 || !m_data; } int size() const { return m_size; } const char *id() const { return m_data + 1; } const char *data() const { return m_data + 8; } QString toString() const { return QString::fromUtf8(data(), size()); } private: const char *m_data = nullptr; int m_size = 0; }; // 0080BL vendor block (DB) (version 2/3, dynamic size) // 2x stuff // 1x number of certificate blocks // 22+8+8+8x (v2) or 8+8+10x (v3) certificate block // 2x number of sub blocks class Vendor0080BLBlock { public: Vendor0080BLBlock(const Uic9183Block &block); bool isValid() const; Vendor0080BLSubBlock findSubBlock(const char id[3]) const; private: static int subblockOffset(const Uic9183Block &block); Uic9183Block m_block; }; class Uic9183ParserPrivate : public QSharedData { public: // name is either "U_" + 4 letter type or a 4 digit vendor id + 2 char type Uic9183Block findBlock(const char name[6]) const; QByteArray m_payload; }; // 2x field line, number as ascii text // 2x field column // 2x field height // 2x field width // 1x field format // 4x text length // Nx text content class Rct2TicketField { public: Rct2TicketField() = default; Rct2TicketField(const char *data); // size of the field data, not size of the text content int size() const; int row() const; int column() const; int height() const; int width() const; QString text() const; private: const char *m_data = nullptr; }; class Rct2TicketPrivate : public QSharedData { public: QString fieldText(int row, int column, int width, int height = 1) const; QDate firstDayOfValidity() const; QDateTime parseTime(const QString &dateStr, const QString &timeStr) const; Uic9183Block block; }; } Uic9183Block::Uic9183Block(const char* data, int size) : m_data(data) , m_size(size) { } int Uic9183Block::version() const { return QByteArray(m_data + 6, 2).toInt(); } Vendor0080BLSubBlock::Vendor0080BLSubBlock(const char *data, int size) : m_data(data) , m_size(size) { } Vendor0080BLBlock::Vendor0080BLBlock(const Uic9183Block &block) { if (block.isNull()) { return; } if (block.version() != 2 && block.version() != 3) { qCWarning(Log) << "Unsupported version of 0080BL vendor block." << block.version(); return; } if (block.isNull() || block.size() < 15 || subblockOffset(block) > block.size()) { return; } m_block = block; } bool Vendor0080BLBlock::isValid() const { return !m_block.isNull(); } Vendor0080BLSubBlock Vendor0080BLBlock::findSubBlock(const char id[3]) const { for (int i = subblockOffset(m_block); i < m_block.size();) { if (*(m_block.data() + i) != 'S') { qCWarning(Log) << "0080BL invalid S-block format."; return {}; } const int subblockSize = QByteArray(m_block.data() + i + 4, 4).toInt(); if (subblockSize + i > m_block.size()) { qCWarning(Log) << "0080BL S-block size exceeds block size."; return {}; } Vendor0080BLSubBlock sb(m_block.data() + i, subblockSize); if (!sb.isNull() && strncmp(sb.id(), id, 3) == 0) { return sb; } i += subblockSize + 8; } return {}; } int Vendor0080BLBlock::subblockOffset(const Uic9183Block& block) { const auto certCount = *(block.data() + 14) - '0'; const auto certSize = block.version() == 2 ? 46 : 26; return 15 + certSize * certCount + 2; } Uic9183Block Uic9183ParserPrivate::findBlock(const char name[6]) const { // 6x header name // 2x block version // 4x block size as string, including the header for (int i = 0; i < m_payload.size() - 12;) { const int blockSize = m_payload.mid(i + 8, 4).toInt(); if (blockSize + i > m_payload.size()) { qCWarning(Log) << "UIC 918-3 block size exceeds payload size."; return {}; } if (strncmp(name, m_payload.data() + i, 6) == 0) { return {m_payload.data() + i, blockSize}; } i += blockSize; } return {}; } Rct2TicketField::Rct2TicketField(const char *data) : m_data(data) { } int Rct2TicketField::size() const { return asciiToInt(m_data + 9, 4) + 13; } int Rct2TicketField::row() const { return asciiToInt(m_data, 2); } int Rct2TicketField::column() const { return asciiToInt(m_data + 2, 2); } int Rct2TicketField::height() const { return asciiToInt(m_data + 4, 2); } int Rct2TicketField::width() const { return asciiToInt(m_data + 6, 2); } QString Rct2TicketField::text() const { return QString::fromUtf8(m_data + 13, asciiToInt(m_data + 9, 4)); } QString Rct2TicketPrivate::fieldText(int row, int column, int width, int height) const { QString s; for (int i = 20; i < block.size();) { Rct2TicketField f(block.data() + i); i += f.size(); if (f.row() + f.height() - 1 < row || f.row() > row + height - 1) { continue; } if (f.column() + f.width() - 1 < column || f.column() > column + width - 1) { continue; } //qDebug() << "Field:" << f.height() << f.column() << f.height() << f.width() << f.size() << f.text(); // split field into lines // TODO this needs to follow the RCT2 word-wrapping algorithm? const auto content = f.text(); const auto lines = content.splitRef(QLatin1Char('\n')); // cut out the right part of the line for (int i = 0; i < lines.size(); ++i) { if (f.row() + i < row) { continue; } if (f.row() + i > row + height - 1) { break; } // TODO also truncate by w const auto offset = column - f.column(); if (offset >= 0) { s += lines.at(i).mid(offset).left(width); } else { s += lines.at(i); // TODO left padding by offset, truncate by width + offset } } } //qDebug() << "Result:" << x << y << w << h << s; return s; } QDate Rct2TicketPrivate::firstDayOfValidity() const { const auto f = fieldText(3, 1, 48); const auto it = std::find_if(f.begin(), f.end(), [](QChar c) { return c.isDigit(); }); if (it == f.end()) { return {}; } const auto dtStr = f.midRef(std::distance(f.begin(), it)); auto dt = QDate::fromString(dtStr.left(10).toString(), QStringLiteral("dd.MM.yyyy")); if (dt.isValid()) { return dt; } dt = QDate::fromString(dtStr.left(8).toString(), QStringLiteral("dd.MM.yy")); if (dt.isValid()) { if (dt.year() < 2000) { dt.setDate(dt.year() + 100, dt.month(), dt.day()); } return dt; } dt = QDate::fromString(dtStr.left(4).toString(), QStringLiteral("yyyy")); return dt; } QDateTime Rct2TicketPrivate::parseTime(const QString &dateStr, const QString &timeStr) const { const auto d = QDate::fromString(dateStr, QStringLiteral("dd.MM")); const auto t = QTime::fromString(timeStr, QStringLiteral("hh:mm")); return QDateTime({firstDayOfValidity().year(), d.month(), d.day()}, t); } // 6x "U_TLAY" // 2x version (always "01") // 4x record length, numbers as ASCII text // 4x ticket layout type ("RCT2") // 4x field count // Nx fields (see Rct2TicketField) Rct2Ticket::Rct2Ticket() : d(new Rct2TicketPrivate) { } Rct2Ticket::Rct2Ticket(Uic9183Block block) : d(new Rct2TicketPrivate) { d->block = block; qDebug() << QByteArray(block.data(), block.size()); } Rct2Ticket::Rct2Ticket(const Rct2Ticket&) = default; Rct2Ticket::~Rct2Ticket() = default; Rct2Ticket& Rct2Ticket::operator=(const Rct2Ticket&) = default; bool Rct2Ticket::isValid() const { return !d->block.isNull() && d->block.size() > 34 && std::strncmp(d->block.data() + 6, "01", 2) == 0 && std::strncmp(d->block.data() + 12, "RCT2", 4) == 0; } QDate Rct2Ticket::firstDayOfValidity() const { return d->firstDayOfValidity(); } QDateTime Rct2Ticket::outboundDepartureTime() const { return d->parseTime(d->fieldText(6, 1, 5), d->fieldText(6, 7, 5)); } QDateTime Rct2Ticket::outboundArrivalTime() const { return d->parseTime(d->fieldText(6, 52, 5), d->fieldText(6, 58, 5)); } QString Rct2Ticket::outboundDepartureStation() const { return d->fieldText(6, 13, 17).trimmed(); } QString Rct2Ticket::outboundArrivalStation() const { return d->fieldText(6, 34, 17).trimmed(); } QString Rct2Ticket::outboundClass() const { return d->fieldText(6, 66, 5).trimmed(); } Uic9183Parser::Uic9183Parser() : d(new Uic9183ParserPrivate) { } Uic9183Parser::Uic9183Parser(const Uic9183Parser&) = default; Uic9183Parser::~Uic9183Parser() = default; Uic9183Parser& Uic9183Parser::operator=(const Uic9183Parser&) = default; void Uic9183Parser::parse(const QByteArray &data) { d->m_payload.clear(); // header and signature block (64 byte total) - if (data.size() < 64) { - qCWarning(Log) << "UIC 918-3 ticket too short."; + if (!Uic9183Parser::maybeUic9183(data)) { + qCWarning(Log) << "UIC 918-3 ticket too short or has wrong header/version."; return; } // 3x header - if (!data.startsWith("#UT") && !data.startsWith("OTI")) { - qCWarning(Log) << "Data does not contain a UIC 918-3 ticket."; - return; - } // 2x version - if (data.at(3) != '0' || data.at(4) != '1') { - qCWarning(Log) << "Unsupported UIC918-3 ticket version."; - return; - } // 4x issuer UIC carrier code // 5x signature key id // 50x ASN.1 signature // zlib compressed payload if (data.size() < 64 + 8) { qCWarning(Log) << "UIC 918-3 payload too short."; return; } // 4x compressed payload size as string // 2x zlib header 0x789C if (data[68] != 0x78 || ((uchar)data[69] != 0x9C && (uchar)data[69] != 0xDA)) { qCWarning(Log) << "UIC 918-3 payload has wrong zlib header."; return; } // nx zlib payload d->m_payload.resize(4096); z_stream stream; stream.zalloc = nullptr; stream.zfree = nullptr; stream.opaque = nullptr; stream.avail_in = data.size() - 68; stream.next_in = reinterpret_cast(const_cast(data.data() + 68)); stream.avail_out = d->m_payload.size(); stream.next_out = reinterpret_cast(d->m_payload.data()); inflateInit(&stream); const auto res = inflate(&stream, Z_NO_FLUSH); switch (res) { case Z_OK: case Z_STREAM_END: break; // all good default: qCWarning(Log) << "UIC 918.3 payload zlib decompression failed" << stream.msg; return; } inflateEnd(&stream); d->m_payload.truncate(d->m_payload.size() - stream.avail_out); //qCDebug(Log) << res << d->m_payload << stream.avail_out; } bool Uic9183Parser::isValid() const { return !d->m_payload.isEmpty(); } // U_HEAD (version 1, size 53) // 4x issuing carrier id // 6x PNR // 20x unique ticket key // 12x issuing date/time as ddMMyyyyHHMM, as UTC // 1x flags // 2x ticket language // 2x secondary ticket language QString Uic9183Parser::pnr() const { const auto b = d->findBlock("U_HEAD"); if (b.isNull() || b.version() != 1 || b.size() != 53) { return {}; } return QString::fromUtf8(b.data() + 16, 6); } Person Uic9183Parser::person() const { // Deutsche Bahn vendor block const auto b = Vendor0080BLBlock(d->findBlock("0080BL")); if (b.isValid()) { // S028 contains family and given name separated by a '#', UTF-8 encoded auto sblock = b.findSubBlock("028"); if (!sblock.isNull()) { const auto endIt = sblock.data() + sblock.size(); auto it = std::find(sblock.data(), endIt, '#'); if (it != endIt) { Person p; p.setGivenName(QString::fromUtf8(sblock.data(), std::distance(sblock.data(), it))); ++it; p.setFamilyName(QString::fromUtf8(it, std::distance(it, endIt))); return p; } } // S023 contains the full name, UTF-8 encoded sblock = b.findSubBlock("023"); if (!sblock.isNull()) { Person p; p.setName(sblock.toString()); return p; } } // RCT2 tickets const auto rct2 = rct2Ticket(); if (rct2.isValid()) { const auto name = rct2.d->fieldText(0, 52, 19); if (!name.isEmpty()) { Person p; p.setName(name); return p; } } return {}; } QString Uic9183Parser::outboundDepartureStationId() const { const auto b = Vendor0080BLBlock(d->findBlock("0080BL")); if (b.isValid()) { // S035 contains the IBNR, possible with leading '80' country code and leading 0 stripped const auto sblock = b.findSubBlock("035"); if (!sblock.isNull() && sblock.size() <= 7) { QString ibnr = QStringLiteral("ibnr:8000000"); const auto s = sblock.toString(); return ibnr.replace(ibnr.size() - s.size(), s.size(), s); } } return {}; } QString Uic9183Parser::outboundArrivalStationId() const { const auto b = Vendor0080BLBlock(d->findBlock("0080BL")); if (b.isValid()) { // S036 contains the IBNR, possible with leading '80' country code and leading 0 stripped const auto sblock = b.findSubBlock("036"); if (!sblock.isNull() && sblock.size() <= 7) { QString ibnr = QStringLiteral("ibnr:8000000"); const auto s = sblock.toString(); return ibnr.replace(ibnr.size() - s.size(), s.size(), s); } } return {}; } Rct2Ticket Uic9183Parser::rct2Ticket() const { return Rct2Ticket(d->findBlock("U_TLAY")); } QVariant Uic9183Parser::rct2TicketVariant() const { const auto rct2 = rct2Ticket(); if (rct2.isValid()) { return QVariant::fromValue(rct2); } return {}; } +bool Uic9183Parser::maybeUic9183(const QByteArray& data) +{ + if (data.size() < 64) { + return false; + } + + if (!data.startsWith("#UT") && !data.startsWith("OTI")) { + return false; + } + + if (data.at(3) != '0' || data.at(4) != '1') { + return false; + } + + return true; +} #include "moc_uic9183parser.cpp" diff --git a/src/uic9183parser.h b/src/uic9183parser.h index 0426111..ab93532 100644 --- a/src/uic9183parser.h +++ b/src/uic9183parser.h @@ -1,128 +1,133 @@ /* Copyright (C) 2018 Volker Krause This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef KITINERARY_UIC9183PARSER_H #define KITINERARY_UIC9183PARSER_H #include "kitinerary_export.h" #include #include #include class QDateTime; namespace KItinerary { class Rct2TicketPrivate; class Uic9183Block; class Uic9183Parser; /** RCT2 ticket layout payload of an UIC 918.3 ticket token. */ class KITINERARY_EXPORT Rct2Ticket { Q_GADGET Q_PROPERTY(QDate firstDayOfValidity READ firstDayOfValidity) Q_PROPERTY(QDateTime outboundDepartureTime READ outboundDepartureTime) Q_PROPERTY(QDateTime outboundArrivalTime READ outboundArrivalTime) Q_PROPERTY(QString outboundDepartureStation READ outboundDepartureStation) Q_PROPERTY(QString outboundArrivalStation READ outboundArrivalStation) Q_PROPERTY(QString outboundClass READ outboundClass) public: Rct2Ticket(); Rct2Ticket(const Rct2Ticket&); ~Rct2Ticket(); Rct2Ticket& operator=(const Rct2Ticket&); /** Returns whether this is a valid RCT2 ticket layout block. */ bool isValid() const; /** First day the ticket is valid. */ QDate firstDayOfValidity() const; /** Departure time of the outbound segment. */ QDateTime outboundDepartureTime() const; /** Arrival time of the outbound segment. */ QDateTime outboundArrivalTime() const; /** Departure station of the outbound segment. */ QString outboundDepartureStation() const; /** Arrival station of the outbound segement. */ QString outboundArrivalStation() const; /** Class of the outbound segment. */ QString outboundClass() const; private: friend class Uic9183Parser; Rct2Ticket(Uic9183Block block); QExplicitlySharedDataPointer d; }; class Uic9183ParserPrivate; /** Parser for UIC 918.3 and 918.3* train tickets. * * @see http://www.era.europa.eu/Document-Register/Documents/ERA_Technical_Document_TAP_B_7_v1.2.pdf * for information about the general UIC 918-3 structure * @see http://www.era.europa.eu/Document-Register/Documents/ERA_Technical_Document_TAP_B_6_v1_2.pdf * for information about the U_TLAY block * @see https://www.bahn.de/p/view/angebot/regio/barcode.shtml * for information about the 0080VU vendor block */ class KITINERARY_EXPORT Uic9183Parser { Q_GADGET Q_PROPERTY(QString pnr READ pnr) Q_PROPERTY(KItinerary::Person person READ person) Q_PROPERTY(QString outboundDepartureStationId READ outboundDepartureStationId) Q_PROPERTY(QString outboundArrivalStationId READ outboundArrivalStationId) /** RCT2 ticket layout block, if present, @c null otherwise. */ Q_PROPERTY(QVariant rct2Ticket READ rct2TicketVariant) public: Uic9183Parser(); Uic9183Parser(const Uic9183Parser&); ~Uic9183Parser(); Uic9183Parser& operator=(const Uic9183Parser&); void parse(const QByteArray &data); bool isValid() const; /** The booking reference. */ QString pnr() const; /** The person this ticket is issued to. */ Person person() const; /** Station identifier for the departure station of the outbound trip. */ QString outboundDepartureStationId() const; /** Station identifier for the arrival station of the outbound trip. */ QString outboundArrivalStationId() const; /** RCT2 ticket layout, if present. */ Rct2Ticket rct2Ticket() const; + /** Quickly checks if @p might be UIC 918.3 content. + * This priorizes speed over correctness and is used in barcode content auto-detection. + */ + static bool maybeUic9183(const QByteArray &data); + private: QVariant rct2TicketVariant() const; QExplicitlySharedDataPointer d; }; } Q_DECLARE_METATYPE(KItinerary::Rct2Ticket) Q_DECLARE_METATYPE(KItinerary::Uic9183Parser) #endif // KITINERARY_UIC9183PARSER_H