1/*
2 * Copyright (C) 2022 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15#include "ndef_message.h"
16#include "loghelper.h"
17#include "nfc_sdk_common.h"
18
19namespace OHOS {
20namespace NFC {
21namespace KITS {
22using KITS::NfcSdkCommon;
23NdefMessage::NdefMessage(std::vector<std::shared_ptr<NdefRecord>> ndefRecords)
24    : ndefRecordList_(std::move(ndefRecords))
25{
26}
27
28NdefMessage::~NdefMessage()
29{
30    ndefRecordList_.clear();
31}
32
33std::shared_ptr<NdefMessage> NdefMessage::GetNdefMessage(const std::string& data)
34{
35    std::vector<std::shared_ptr<NdefRecord>> ndefRecords = ParseRecord(data, false);
36    if (ndefRecords.empty()) {
37        ErrorLog("GetNdefMessage, ndefRecords invalid.");
38        return std::shared_ptr<NdefMessage>();
39    }
40    return GetNdefMessage(ndefRecords);
41}
42
43std::shared_ptr<NdefMessage> NdefMessage::GetNdefMessage(std::vector<std::shared_ptr<NdefRecord>> ndefRecords)
44{
45    return std::make_shared<NdefMessage>(std::move(ndefRecords));
46}
47
48std::string NdefMessage::GetTagRtdType(EmRtdType rtdtype)
49{
50    std::string rtd;
51    switch (rtdtype) {
52        case EmRtdType::RTD_TEXT:
53            rtd = "T";  // 0x54
54            break;
55        case EmRtdType::RTD_URI:
56            rtd = "U";  // 0x55
57            break;
58        case EmRtdType::RTD_SMART_POSTER:
59            rtd = "Sp";  // 0x53, 0x70
60            break;
61        case EmRtdType::RTD_ALTERNATIVE_CARRIER:
62            rtd = "ac";  // 0x61, 0x63
63            break;
64        case EmRtdType::RTD_HANDOVER_CARRIER:
65            rtd = "Hc";  // 0x48, 0x63
66            break;
67        case EmRtdType::RTD_HANDOVER_REQUEST:
68            rtd = "Hr";  // 0x48, 0x72
69            break;
70        case EmRtdType::RTD_HANDOVER_SELECT:
71            rtd = "Hs";  // 0x48, 0x73
72            break;
73        case EmRtdType::RTD_OHOS_APP:
74            rtd = "ohos.com:pkg";  // "ohos.com:pkg"
75            break;
76        default:
77            rtd.clear();
78            break;
79    }
80    return rtd;
81}
82
83std::vector<std::shared_ptr<NdefRecord>> NdefMessage::GetNdefRecords() const
84{
85    return ndefRecordList_;
86}
87
88std::shared_ptr<NdefRecord> NdefMessage::MakeUriRecord(const std::string& uriString)
89{
90    if (uriString.empty()) {
91        ErrorLog("MakeUriRecord, uriString invalid.");
92        return std::shared_ptr<NdefRecord>();
93    }
94
95    std::string payload = "00";
96    std::string uri = uriString;
97    for (size_t i = 1; i < g_uriPrefix.size() - 1; i++) {
98        if (!uriString.compare(0, g_uriPrefix[i].size(), g_uriPrefix[i])) {
99            payload = NfcSdkCommon::UnsignedCharToHexString(i & 0xFF);
100            uri = uriString.substr(g_uriPrefix[i].size());
101            break;
102        }
103    }
104
105    payload += NfcSdkCommon::StringToHexString(uri);
106
107    std::string id = "";
108    std::string tagRtdType = NfcSdkCommon::StringToHexString(GetTagRtdType(EmRtdType::RTD_URI));
109    return CreateNdefRecord(TNF_WELL_KNOWN, id, payload, tagRtdType);
110}
111
112std::shared_ptr<NdefRecord> NdefMessage::MakeTextRecord(const std::string& text, const std::string& locale)
113{
114    std::string tagRtdType = NfcSdkCommon::StringToHexString(GetTagRtdType(EmRtdType::RTD_TEXT));
115    std::string id = "";
116    int localeLen = locale.size() & 0xFF;
117    std::string payload = NfcSdkCommon::UnsignedCharToHexString(localeLen);
118    payload += NfcSdkCommon::StringToHexString(locale);
119    payload += NfcSdkCommon::StringToHexString(text);
120    return CreateNdefRecord(TNF_WELL_KNOWN, id, payload, tagRtdType);
121}
122
123std::shared_ptr<NdefRecord> NdefMessage::MakeMimeRecord(const std::string& mimeType, const std::string& mimeData)
124{
125    if (mimeType.empty() || mimeData.empty()) {
126        ErrorLog("MakeMimeRecord, mimeType or mimeData invalid.");
127        return std::shared_ptr<NdefRecord>();
128    }
129    std::string id = "";
130    size_t t = mimeType.find_first_of('/');
131    if (t == 0 || t == (mimeType.size() - 1)) {
132        ErrorLog("MakeMimeRecord, mimeType should have major and minor type if '/' exists.");
133        return std::shared_ptr<NdefRecord>();
134    }
135    return CreateNdefRecord(TNF_MIME_MEDIA, id, mimeData, NfcSdkCommon::StringToHexString(mimeType));
136}
137
138std::shared_ptr<NdefRecord> NdefMessage::MakeExternalRecord(const std::string& domainName,
139                                                            const std::string& serviceName,
140                                                            const std::string& externalData)
141{
142    if (domainName.empty() || serviceName.empty() || externalData.empty()) {
143        ErrorLog("MakeExternalRecord, domainName or serviceName invalid.");
144        return std::shared_ptr<NdefRecord>();
145    }
146
147    std::string domain = domainName;
148    std::string service = serviceName;
149    domain.erase(0, domain.find_first_not_of("\r\t\n "));
150    domain.erase(domain.find_last_not_of("\r\t\n ") + 1);
151    transform(domain.begin(), domain.end(), domain.begin(), ::tolower);
152    service.erase(0, service.find_first_not_of("\r\t\n "));
153    service.erase(service.find_last_not_of("\r\t\n ") + 1);
154    transform(service.begin(), service.end(), service.begin(), ::tolower);
155
156    if (domain.empty() || service.empty()) {
157        return std::shared_ptr<NdefRecord>();
158    }
159
160    std::string tagRtdType = NfcSdkCommon::StringToHexString(domain + ":" + service);
161    std::string id = "";
162
163    return CreateNdefRecord(TNF_EXTERNAL_TYPE, id, externalData, tagRtdType);
164}
165
166std::string NdefMessage::MessageToString(std::weak_ptr<NdefMessage> ndefMessage)
167{
168    std::string buffer;
169    if (ndefMessage.expired()) {
170        ErrorLog("MessageToString, ndefMessage invalid.");
171        return buffer;
172    }
173    for (size_t i = 0; i < ndefMessage.lock()->ndefRecordList_.size(); i++) {
174        bool bIsMB = (i == 0);                                                // first record
175        bool bIsME = (i == ndefMessage.lock()->ndefRecordList_.size() - 1);  // last record
176        NdefRecordToString(ndefMessage.lock()->ndefRecordList_.at(i), buffer, bIsMB, bIsME);
177    }
178    return buffer;
179}
180
181void NdefMessage::NdefRecordToString(std::weak_ptr<NdefRecord> record, std::string& buffer, bool bIsMB, bool bIsME)
182{
183    if (record.expired()) {
184        ErrorLog("NdefRecordToString, record invalid.");
185        return;
186    }
187    std::string payload = record.lock()->payload_;
188    uint32_t tnf = record.lock()->tnf_;
189    std::string id = record.lock()->id_;
190    std::string rtdType = record.lock()->tagRtdType_;
191    bool sr = NfcSdkCommon::GetHexStrBytesLen(payload) < SHORT_RECORD_SIZE;
192    bool il = (tnf == TNF_EMPTY) ? true : (NfcSdkCommon::GetHexStrBytesLen(id) > 0);
193    unsigned char flag = (unsigned char)((bIsMB ? FLAG_MB : 0) | (bIsME ? FLAG_ME : 0)
194        | (sr ? FLAG_SR : 0) | (il ? FLAG_IL : 0)) | (char)tnf;
195    buffer.append(NfcSdkCommon::UnsignedCharToHexString(flag));
196    buffer.append(NfcSdkCommon::IntToHexString(NfcSdkCommon::GetHexStrBytesLen(rtdType)));
197    if (sr) {
198        buffer.append(NfcSdkCommon::IntToHexString(NfcSdkCommon::GetHexStrBytesLen(payload)));
199    } else {
200        buffer.append(NfcSdkCommon::IntToHexString(NfcSdkCommon::GetHexStrBytesLen(payload)));
201    }
202    if (il) {
203        buffer.append(NfcSdkCommon::IntToHexString(NfcSdkCommon::GetHexStrBytesLen(id)));
204    }
205
206    buffer.append(rtdType);
207    buffer.append(id);
208    buffer.append(payload);
209}
210
211void NdefMessage::ParseRecordLayoutHead(RecordLayout& layout, unsigned char head)
212{
213    layout.mb = (head & FLAG_MB) != 0;
214    layout.me = (head & FLAG_ME) != 0;
215    layout.cf = (head & FLAG_CF) != 0;
216    layout.sr = (head & FLAG_SR) != 0;
217    layout.il = (head & FLAG_IL) != 0;
218    layout.tnf = static_cast<short>(head & FLAG_TNF);
219}
220
221bool NdefMessage::IsInvalidRecordLayoutHead(RecordLayout& layout, bool isChunkFound,
222    uint32_t parsedRecordSize, bool isMbMeIgnored)
223{
224    if (!layout.mb && parsedRecordSize == 0 && !isChunkFound && !isMbMeIgnored) {
225        ErrorLog("IsInvalidRecordLayoutHead, 1st error for mb and size.");
226        return true;
227    } else if (layout.mb && (parsedRecordSize != 0 || isChunkFound) && !isMbMeIgnored) {
228        ErrorLog("IsInvalidRecordLayoutHead, 2nd error for mb and size");
229        return true;
230    } else if (isChunkFound && layout.il) {
231        ErrorLog("IsInvalidRecordLayoutHead, 3rd error for il");
232        return true;
233    } else if (layout.cf && layout.me) {
234        ErrorLog("IsInvalidRecordLayoutHead, 4th error for cf and me");
235        return true;
236    } else if (isChunkFound && layout.tnf != TNF_UNCHANGED) {
237        ErrorLog("IsInvalidRecordLayoutHead, 5th error for tnf");
238        return true;
239    } else if (!isChunkFound && layout.tnf == TNF_UNCHANGED) {
240        ErrorLog("IsInvalidRecordLayoutHead, 6th error for tnf");
241        return true;
242    }
243    return false;
244}
245
246void NdefMessage::ParseRecordLayoutLength(RecordLayout& layout, bool isChunkFound,
247    const std::string& data, uint32_t& parsedDataIndex)
248{
249    layout.typeLength = NfcSdkCommon::GetByteFromHexStr(data, parsedDataIndex++) & 0xFF;
250    if (layout.sr) {
251        layout.payloadLength = NfcSdkCommon::GetByteFromHexStr(data, parsedDataIndex++) & 0xFF;
252    } else {
253        if (NfcSdkCommon::GetHexStrBytesLen(data) < parsedDataIndex + int(sizeof(int))) {
254            layout.payloadLength = 0;
255        } else {
256            std::string lenString = data.substr(parsedDataIndex * HEX_BYTE_LEN, sizeof(int) * HEX_BYTE_LEN);
257            layout.payloadLength = 0;
258            for (unsigned int i = 0; i < sizeof(int); i++) {
259                layout.payloadLength +=
260                    (NfcSdkCommon::GetByteFromHexStr(lenString, i) << ((sizeof(int) - i - 1) * ONE_BYTE_SHIFT));
261            }
262            parsedDataIndex += sizeof(int);
263        }
264    }
265    layout.idLength = layout.il ? (NfcSdkCommon::GetByteFromHexStr(data, parsedDataIndex++) & 0xFF) : 0;
266}
267
268bool NdefMessage::IsRecordLayoutLengthInvalid(RecordLayout& layout, bool isChunkFound)
269{
270    // for the middle chunks record, need the type length is zero.
271    if (isChunkFound && layout.typeLength != 0) {
272        ErrorLog("IsInvalidRecordLayoutHead, 1st error for typeLength");
273        return true;
274    }
275
276    // for the first chunk, expected has type.
277    if (layout.cf && !isChunkFound) {
278        if (layout.typeLength == 0 && layout.tnf != TNF_UNKNOWN) {
279            ErrorLog("IsInvalidRecordLayoutHead, 2nd error for typeLength and tnf");
280            return true;
281        }
282    }
283
284    if (layout.payloadLength > MAX_PAYLOAD_SIZE) {
285        ErrorLog("IsInvalidRecordLayoutHead, 3rd error for payloadLength");
286        return true;
287    }
288    return false;
289}
290
291std::string NdefMessage::ParseRecordType(RecordLayout& layout, const std::string& data, uint32_t& parsedDataIndex)
292{
293    if (layout.typeLength <= 0) {
294        ErrorLog("IsInvalidRecordLayoutHead, typeLength less than 0.");
295        return "";
296    }
297    if (NfcSdkCommon::GetHexStrBytesLen(data) < parsedDataIndex + layout.typeLength) {
298        ErrorLog("data len.%{public}d index.%{public}d rtdtype len.%{public}d error",
299            NfcSdkCommon::GetHexStrBytesLen(data), parsedDataIndex, layout.typeLength);
300        return "";
301    }
302    std::string type = data.substr(parsedDataIndex * HEX_BYTE_LEN, layout.typeLength * HEX_BYTE_LEN);
303    parsedDataIndex += layout.typeLength;
304    return type;
305}
306
307std::string NdefMessage::ParseRecordId(RecordLayout& layout, const std::string& data, uint32_t& parsedDataIndex)
308{
309    if (layout.idLength <= 0) {
310        ErrorLog("ParseRecordId, idLength <= 0");
311        return "";
312    }
313    if (NfcSdkCommon::GetHexStrBytesLen(data) < parsedDataIndex + layout.idLength) {
314        ErrorLog("data len.%{public}d index.%{public}d id len.%{public}d error",
315            NfcSdkCommon::GetHexStrBytesLen(data), parsedDataIndex, layout.idLength);
316        return "";
317    }
318    std::string id = data.substr(parsedDataIndex * HEX_BYTE_LEN, layout.idLength * HEX_BYTE_LEN);
319    parsedDataIndex += layout.idLength;
320    return id;
321}
322
323std::string NdefMessage::ParseRecordPayload(RecordLayout& layout, const std::string& data, uint32_t& parsedDataIndex)
324{
325    if (layout.payloadLength <= 0) {
326        ErrorLog("ParseRecordPayload, payloadLength <= 0");
327        return "";
328    }
329    if (NfcSdkCommon::GetHexStrBytesLen(data) < (parsedDataIndex + layout.payloadLength)) {
330        ErrorLog("data len.%{public}d index.%{public}d payload len.%{public}d error",
331            NfcSdkCommon::GetHexStrBytesLen(data), parsedDataIndex, layout.payloadLength);
332        return "";
333    }
334    std::string payload = data.substr(parsedDataIndex * HEX_BYTE_LEN, layout.payloadLength * HEX_BYTE_LEN);
335    parsedDataIndex += layout.payloadLength;
336    return payload;
337}
338
339void NdefMessage::SaveRecordChunks(RecordLayout& layout, bool isChunkFound,
340    std::vector<std::string>& chunks, char& chunkTnf, const std::string& payload)
341{
342    // handle for the first chunk.
343    if (layout.cf && !isChunkFound) {
344        chunks.clear();
345        chunkTnf = layout.tnf;
346    }
347
348    // save the payload for all(first/middle/last) chunk.
349    if (layout.cf || isChunkFound) {
350        chunks.push_back(payload);
351    }
352}
353
354std::string NdefMessage::MergePayloadByChunks(RecordLayout& layout, bool isChunkFound,
355    std::vector<std::string>& chunks, char chunkTnf, const std::string& payload)
356{
357    // it's the last chunk, merge the payload for NdefRecord.
358    if (!layout.cf && isChunkFound) {
359        std::string mergedPayload;
360        for (std::string n : chunks) {
361            mergedPayload += n;
362        }
363        layout.tnf = chunkTnf;
364        return mergedPayload;
365    }
366    return payload;
367}
368
369std::shared_ptr<NdefRecord> NdefMessage::CreateNdefRecord(short tnf, const std::string& id,
370    const std::string& payload, const std::string& tagRtdType)
371{
372    bool isValidTnf = CheckTnf(tnf, tagRtdType, id, payload);
373    if (!isValidTnf) {
374        ErrorLog("CreateNdefRecord, isValidTnf failed.");
375        return std::shared_ptr<NdefRecord>();
376    }
377    std::shared_ptr<NdefRecord> ndefRecord = std::make_shared<NdefRecord>();
378    ndefRecord->tnf_ = tnf;
379    ndefRecord->id_ = id;
380    ndefRecord->payload_ = payload;
381    ndefRecord->tagRtdType_ = tagRtdType;
382    return ndefRecord;
383}
384
385bool NdefMessage::CheckTnf(short tnf, const std::string& tagRtdType, const std::string& id, const std::string& payload)
386{
387    switch (tnf) {
388        case TNF_EMPTY:
389            if (!tagRtdType.empty() || !id.empty() || !payload.empty()) {
390                ErrorLog("CheckTnf, TNF_EMPTY error.");
391                return false;
392            }
393            break;
394        case TNF_WELL_KNOWN:
395        case TNF_MIME_MEDIA:
396        case TNF_ABSOLUTE_URI:
397        case TNF_EXTERNAL_TYPE:
398            return true;
399        case TNF_UNKNOWN:
400        case TNF_RESERVED:
401            if (tagRtdType.empty()) {
402                return false;
403            }
404            return true;
405        case TNF_UNCHANGED:
406            return false;
407        default:
408            break;
409    }
410    return false;
411}
412
413std::vector<std::shared_ptr<NdefRecord>> NdefMessage::ParseRecord(const std::string& data, bool isMbMeIgnored)
414{
415    std::vector<std::shared_ptr<NdefRecord>> recordList;
416    if (data.empty()) {
417        ErrorLog("ParseRecord, raw data empty.");
418        return recordList;
419    }
420
421    std::string tagRtdType;
422    std::string id;
423    std::vector<std::string> chunks;
424    bool isChunkFound = false;
425    char chunkTnf = 0;
426    bool isMessageEnd = false;
427    uint32_t parsedDataIndex = 0;
428    while (!isMessageEnd) {
429        RecordLayout layout;
430        ParseRecordLayoutHead(layout, NfcSdkCommon::GetByteFromHexStr(data, parsedDataIndex++));
431        isMessageEnd = layout.me;
432
433        if (data.size() / HEX_BYTE_LEN < parsedDataIndex) {
434            return recordList;
435        }
436        if ((data.size() / HEX_BYTE_LEN - parsedDataIndex) < MIN_RECORD_LEN && !isMessageEnd) {
437            return recordList;
438        }
439        if (IsInvalidRecordLayoutHead(layout, isChunkFound, recordList.size(), isMbMeIgnored)) {
440            return recordList;
441        }
442
443        ParseRecordLayoutLength(layout, isChunkFound, data, parsedDataIndex);
444
445        if (IsRecordLayoutLengthInvalid(layout, isChunkFound)) {
446            return recordList;
447        }
448
449        if (!isChunkFound) {
450            // don't parse the type and id for the middle chunks record, allowed them tobe empty.
451            tagRtdType = ParseRecordType(layout, data, parsedDataIndex);
452            id = ParseRecordId(layout, data, parsedDataIndex);
453        }
454
455        // parse the payload.
456        std::string payload = ParseRecordPayload(layout, data, parsedDataIndex);
457        SaveRecordChunks(layout, isChunkFound, chunks, chunkTnf, payload);
458        payload = MergePayloadByChunks(layout, isChunkFound, chunks, chunkTnf, payload);
459        if (NfcSdkCommon::GetHexStrBytesLen(payload) > MAX_PAYLOAD_SIZE) {
460            ErrorLog("ParseRecord, payload > MAX_PAYLOAD_SIZE");
461            return recordList;
462        }
463
464        // if not the last chunk, continue to parse again.
465        isChunkFound = layout.cf;
466        if (isChunkFound) {
467            continue;
468        }
469
470        // all chunks parsed end, add a new NdefRecord.
471        std::shared_ptr<NdefRecord> record = CreateNdefRecord(layout.tnf, id, payload, tagRtdType);
472        recordList.push_back(record);
473
474        // isMbMeIgnored is true, means that single record need tobe parsed.
475        if (isMbMeIgnored) {
476            break;
477        }
478    }
479    return recordList;
480}
481}  // namespace KITS
482}  // namespace NFC
483}  // namespace OHOS
484