#include "diality_payload.h" #include #include namespace { static void tlvAppend(QByteArray& out, quint8 type, const QByteArray& value) { const int len = value.size(); if (len < 0 || len > 255) return; // caller ensures bounds out.append(char(type)); out.append(char(len)); out.append(value); } static bool isReasonableAscii(const QString& s) { // Keep this permissive: allow printable ASCII plus a few common separators. // You can tighten this later if you want. for (const QChar& ch : s) { const ushort u = ch.unicode(); if (u < 0x20 || u > 0x7E) return false; } return true; } } // namespace namespace diality { bool parseKeyValueInput( const QString& input, QString* outPartId, QString* outLot, QString* outExp, QString* outSerial, QString* outError ) { if (outPartId) outPartId->clear(); if (outLot) outLot->clear(); if (outExp) outExp->clear(); if (outSerial) outSerial->clear(); const QString trimmed = input.trimmed(); if (trimmed.isEmpty()) { if (outError) *outError = "Input is empty"; return false; } const QStringList pairs = trimmed.split('|', Qt::SkipEmptyParts); for (const QString& p : pairs) { const int eq = p.indexOf('='); if (eq <= 0) continue; const QString k = p.left(eq).trimmed().toUpper(); const QString v = p.mid(eq + 1).trimmed(); if (k == "PID" || k == "PARTID") { if (outPartId) *outPartId = v; } else if (k == "LOT") { if (outLot) *outLot = v; } else if (k == "EXP") { if (outExp) *outExp = v; } else if (k == "SN" || k == "SERIAL") { if (outSerial) *outSerial = v; } } if (!outPartId || outPartId->isEmpty()) { if (outError) *outError = "Missing PID=..."; return false; } if (!outLot || outLot->isEmpty()) { if (outError) *outError = "Missing LOT=..."; return false; } if (!outExp || outExp->isEmpty()) { if (outError) *outError = "Missing EXP=..."; return false; } if (!outSerial || outSerial->isEmpty()) { if (outError) *outError = "Missing SN=..."; return false; } // Scanner wedge = safest to stay ASCII if (!isReasonableAscii(*outPartId) || !isReasonableAscii(*outLot) || !isReasonableAscii(*outExp) || !isReasonableAscii(*outSerial)) { if (outError) *outError = "Only printable ASCII supported for scanner/keyboard mode"; return false; } // Basic length guard to avoid huge symbols by accident if (outPartId->size() > 80 || outLot->size() > 80 || outExp->size() > 80 || outSerial->size() > 80) { if (outError) *outError = "One of the fields is too long (>80 chars)"; return false; } return true; } QByteArray buildTlvPayload( const QString& partId, const QString& lot, const QString& exp, const QString& serial, bool enableCompression, bool* outCompressed, QString* outError ) { if (outCompressed) *outCompressed = false; if (partId.trimmed().isEmpty()) { if (outError) *outError = "PartId cannot be empty"; return {}; } if (lot.trimmed().isEmpty()) { if (outError) *outError = "Lot cannot be empty"; return {}; } if (exp.trimmed().isEmpty()) { if (outError) *outError = "Exp cannot be empty"; return {}; } if (serial.trimmed().isEmpty()) { if (outError) *outError = "Serial cannot be empty"; return {}; } const QByteArray pidB = partId.toUtf8(); const QByteArray lotB = lot.toUtf8(); const QByteArray expB = exp.toUtf8(); const QByteArray snB = serial.toUtf8(); if (pidB.size() > 255 || lotB.size() > 255 || expB.size() > 255 || snB.size() > 255) { if (outError) *outError = "A field exceeds 255 bytes (TLV limit)"; return {}; } QByteArray body; tlvAppend(body, kT_PartId, pidB); tlvAppend(body, kT_Lot, lotB); tlvAppend(body, kT_ExpDate, expB); tlvAppend(body, kT_Serial, snB); QByteArray payload; payload.append(char(kPayloadVersion)); payload.append(char(0x00)); // flags if (enableCompression && body.size() >= 60) { const QByteArray c = qCompress(body, 9); if (!c.isEmpty() && (c.size() + 2) < (body.size() + 2)) { payload[1] = char(quint8(payload[1]) | kFlagCompressed); payload.append(c); if (outCompressed) *outCompressed = true; return payload; } } payload.append(body); return payload; } bool parseTlvPayload( const QByteArray& plaintext, ParsedFields* outFields, QString* outError ) { qDebug() << plaintext; if (!outFields) { if (outError) *outError = "Internal error"; return false; } *outFields = ParsedFields{}; if (plaintext.size() < 2) { if (outError) *outError = "Payload too short"; return false; } const quint8 pv = quint8(plaintext[0]); const quint8 pf = quint8(plaintext[1]); if (pv != kPayloadVersion) { if (outError) *outError = "Unsupported payload version"; return false; } QByteArray body = plaintext.mid(2); if (pf & kFlagCompressed) { body = qUncompress(body); if (body.isEmpty()) { if (outError) *outError = "Decompress failed"; return false; } } int i = 0; while (i + 2 <= body.size()) { const quint8 t = quint8(body[i++]); const int len = int(quint8(body[i++])); if (i + len > body.size()) { if (outError) *outError = "TLV length overflow"; return false; } const QByteArray v = body.mid(i, len); i += len; switch (t) { case kT_PartId: outFields->partId = QString::fromUtf8(v); break; case kT_Lot: outFields->lot = QString::fromUtf8(v); break; case kT_ExpDate: outFields->exp = QString::fromUtf8(v); break; case kT_Serial: outFields->serial = QString::fromUtf8(v); break; default: break; } } if (outFields->partId.isEmpty()) { if (outError) *outError = "Missing PartId"; return false; } if (outFields->lot.isEmpty()) { if (outError) *outError = "Missing Lot"; return false; } if (outFields->exp.isEmpty()) { if (outError) *outError = "Missing Exp"; return false; } if (outFields->serial.isEmpty()) { if (outError) *outError = "Missing Serial"; return false; } return true; } } // namespace diality