diff --git a/src/uic9183/uic9183block.cpp b/src/uic9183/uic9183block.cpp index f9411c1..67acf5a 100644 --- a/src/uic9183/uic9183block.cpp +++ b/src/uic9183/uic9183block.cpp @@ -1,93 +1,97 @@ /* Copyright (C) 2019 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 "uic9183block.h" using namespace KItinerary; enum { BlockHeaderSize = 12, BlockVersionOffset = 6, BlockVersionSize = 2, BlockSizeOffset = 8, BlockSizeSize = 4, }; Uic9183Block::Uic9183Block() = default; Uic9183Block::Uic9183Block(const Uic9183Block&) = default; Uic9183Block::Uic9183Block(Uic9183Block&&) = default; Uic9183Block& Uic9183Block::operator=(const Uic9183Block&) = default; Uic9183Block& Uic9183Block::operator=(Uic9183Block&&) = default; - Uic9183Block::Uic9183Block(const QByteArray &data, int offset) : m_data(data) , m_offset(offset) { } // 6x header name // 2x block version // 4x block size as string, including the header // followed by block payload (as 12 byte offset from header start) const char* Uic9183Block::name() const { if (isNull()) { return nullptr; } return m_data.constData() + m_offset; } const char* Uic9183Block::content() const { if (isNull()) { return nullptr; } return m_data.constData() + m_offset + BlockHeaderSize; } int Uic9183Block::size() const { if (m_data.size() < m_offset + BlockHeaderSize) { return 0; } return m_data.mid(m_offset + BlockSizeOffset, BlockSizeSize).toInt(); } int Uic9183Block::contentSize() const { return std::max(0, size() - BlockHeaderSize); } int Uic9183Block::version() const { if (isNull()) { return 0; } return m_data.mid(m_offset + BlockVersionOffset, BlockVersionSize).toInt(); } bool Uic9183Block::isNull() const { return (m_data.size() < m_offset + BlockHeaderSize) || (size() > m_data.size() + m_offset); } Uic9183Block Uic9183Block::nextBlock() const { return Uic9183Block(m_data, m_offset + size()); } + +QString Uic9183Block::contentText() const +{ + return QString::fromUtf8(content(), contentSize()); +} diff --git a/src/uic9183/uic9183block.h b/src/uic9183/uic9183block.h index 6cad556..ce8e32d 100644 --- a/src/uic9183/uic9183block.h +++ b/src/uic9183/uic9183block.h @@ -1,66 +1,75 @@ /* Copyright (C) 2019 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_UIC9183BLOCK_H #define KITINERARY_UIC9183BLOCK_H #include "kitinerary_export.h" #include +#include +#include namespace KItinerary { /** A data block from a UIC 918.3 ticket. */ class KITINERARY_EXPORT Uic9183Block { + Q_GADGET + /** Content as string, for use in JS. */ + Q_PROPERTY(QString contentText READ contentText) public: Uic9183Block(); explicit Uic9183Block(const QByteArray &data, int offset); Uic9183Block(const Uic9183Block&); Uic9183Block(Uic9183Block&&); Uic9183Block& operator=(const Uic9183Block&); Uic9183Block& operator=(Uic9183Block&&); /** Returns the block name (6 characters). * The name is either "U_" + 4 letter standard type or a 4 digit vendor id + 2 char vendor type */ const char *name() const; /** Returns the payload data (not including the block header). */ const char *content() const; /** Returns the size of the entire block data. */ int size() const; /** Returns the size of the content data. */ int contentSize() const; /** Returns the version number of this block. */ int version() const; /** Checks if the block is valid or empty/default constructed. */ bool isNull() const; /** Returns the next block in the ticket. * If there is no more block, a null block is returned. */ Uic9183Block nextBlock() const; private: + QString contentText() const; + QByteArray m_data; int m_offset = 0; }; } +Q_DECLARE_METATYPE(KItinerary::Uic9183Block) + #endif // KITINERARY_UIC9183BLOCK_H diff --git a/src/uic9183/uic9183parser.cpp b/src/uic9183/uic9183parser.cpp index eda8bf4..5d5c6c5 100644 --- a/src/uic9183/uic9183parser.cpp +++ b/src/uic9183/uic9183parser.cpp @@ -1,299 +1,306 @@ /* 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 "rct2ticket.h" #include "uic9183block.h" #include "uic9183ticketlayout.h" #include "vendor0080block.h" #include #include #include #include #include using namespace KItinerary; namespace KItinerary { class Uic9183ParserPrivate : public QSharedData { public: QByteArray m_payload; QDateTime m_contextDt; }; } +Uic9183Parser::Uic9183Parser() + : d(new Uic9183ParserPrivate) +{ +} + +Uic9183Parser::Uic9183Parser(const Uic9183Parser&) = default; +Uic9183Parser::~Uic9183Parser() = default; +Uic9183Parser& Uic9183Parser::operator=(const Uic9183Parser&) = default; Uic9183Block Uic9183Parser::firstBlock() const { return Uic9183Block(d->m_payload, 0); } Uic9183Block Uic9183Parser::findBlock(const char name[6]) const { auto block = firstBlock(); while (!block.isNull()) { if (strncmp(name, block.name(), 6) == 0) { return block; } block = block.nextBlock(); } return {}; } - -Uic9183Parser::Uic9183Parser() - : d(new Uic9183ParserPrivate) +QVariant Uic9183Parser::block(const QString &name) const { -} + if (name.size() != 6 || d->m_payload.isEmpty()) { + return {}; + } -Uic9183Parser::Uic9183Parser(const Uic9183Parser&) = default; -Uic9183Parser::~Uic9183Parser() = default; -Uic9183Parser& Uic9183Parser::operator=(const Uic9183Parser&) = default; + return QVariant::fromValue(findBlock(name.toUtf8().constData())); +} void Uic9183Parser::setContextDate(const QDateTime &contextDt) { d->m_contextDt = contextDt; } void Uic9183Parser::parse(const QByteArray &data) { d->m_payload.clear(); // header and signature block (64 byte total) if (!Uic9183Parser::maybeUic9183(data)) { qCWarning(Log) << "UIC 918-3 ticket too short or has wrong header/version."; return; } // 3x header // 2x version // 4x UIC code of the signing carrier // 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 Block (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 = findBlock("U_HEAD"); if (b.isNull() || b.version() != 1 || b.size() != 53) { return {}; } return QString::fromUtf8(b.content() + 4, 6); } QString Uic9183Parser::carrierId() const { const auto b = findBlock("U_HEAD"); if (b.isNull() || b.version() != 1 || b.size() != 53) { return {}; } return QString::fromUtf8(b.content(), 4); } Person Uic9183Parser::person() const { // Deutsche Bahn vendor block const auto b = Vendor0080BLBlock(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.content() + sblock.contentSize(); auto it = std::find(sblock.content(), endIt, '#'); if (it != endIt) { Person p; p.setGivenName(QString::fromUtf8(sblock.content(), std::distance(sblock.content(), 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.passengerName(); if (!name.isEmpty()) { Person p; p.setName(name); return p; } } return {}; } QString Uic9183Parser::outboundDepartureStationId() const { const auto b = Vendor0080BLBlock(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.contentSize() <= 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(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.contentSize() <= 7) { QString ibnr = QStringLiteral("ibnr:8000000"); const auto s = sblock.toString(); return ibnr.replace(ibnr.size() - s.size(), s.size(), s); } } return {}; } QString Uic9183Parser::seatingType() const { const auto b = Vendor0080BLBlock(findBlock("0080BL")); if (b.isValid()) { // S014 contains the class, possibly with a leading 'S' for some reason const auto sblock = b.findSubBlock("014"); if (!sblock.isNull()) { const auto s = sblock.toString(); return s.startsWith(QLatin1Char('S')) ? s.right(1) : s; } } const auto rct2 = rct2Ticket(); if (rct2.isValid()) { return rct2.outboundClass(); } return {}; } Uic9183TicketLayout Uic9183Parser::ticketLayout() const { const auto block = findBlock("U_TLAY"); Uic9183TicketLayout layout(block); return layout; } QVariant Uic9183Parser::ticketLayoutVariant() const { const auto layout = ticketLayout(); return layout.isValid() ? QVariant::fromValue(layout) : QVariant(); } Rct2Ticket Uic9183Parser::rct2Ticket() const { Rct2Ticket rct2(ticketLayout()); rct2.setContextDate(d->m_contextDt); return rct2; } 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/uic9183/uic9183parser.h b/src/uic9183/uic9183parser.h index ec21976..3dfdac6 100644 --- a/src/uic9183/uic9183parser.h +++ b/src/uic9183/uic9183parser.h @@ -1,119 +1,122 @@ /* 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 Rct2Ticket; class Uic9183Block; class Uic9183ParserPrivate; class Uic9183TicketLayout; /** 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(QString carrierId READ carrierId) Q_PROPERTY(KItinerary::Person person READ person) Q_PROPERTY(QString outboundDepartureStationId READ outboundDepartureStationId) Q_PROPERTY(QString outboundArrivalStationId READ outboundArrivalStationId) Q_PROPERTY(QString seatingType READ seatingType) /** U_TLAY ticket layout block, if present, @c null otherwise. */ Q_PROPERTY(QVariant ticketLayout READ ticketLayoutVariant) /** 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&); /** Date/time this ticket was first encountered. * This is used to recover a missing year in the ticket data. */ void setContextDate(const QDateTime &contextDt); void parse(const QByteArray &data); bool isValid() const; /** The booking reference. */ QString pnr() const; /** The UIC carrier code. */ QString carrierId() 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; /** @see Ticket::seatingType */ QString seatingType() const; /** U_TLAY ticket layout block. */ Uic9183TicketLayout ticketLayout() const; /** RCT2 ticket layout, if present. */ Rct2Ticket rct2Ticket() const; /** First data block in this ticket. * Useful for iterating over all blocks. */ Uic9183Block firstBlock() const; /** Returns the first block with the given name. * A null block is returned if no such block exists. */ Uic9183Block findBlock(const char name[6]) const; + /** Same as the above, but for JS usage. */ + Q_INVOKABLE QVariant block(const QString &name) const; + /** Quickly checks if @p might be UIC 918.3 content. * This prioritizes speed over correctness and is used in barcode content auto-detection. */ static bool maybeUic9183(const QByteArray &data); private: QVariant ticketLayoutVariant() const; QVariant rct2TicketVariant() const; QExplicitlySharedDataPointer d; }; } Q_DECLARE_METATYPE(KItinerary::Uic9183Parser) #endif // KITINERARY_UIC9183PARSER_H