mirror of
https://github.com/RfidResearchGroup/proxmark3.git
synced 2026-10-23 08:43:07 +00:00
feat: hf mfu ndefwrite command
This commit is contained in:
1 parent
d3326dde78
commit
cdca934f7e
6 files changed
+908
-3
No files matched your search
@@ -3,6 +3,7 @@ All notable changes to this project will be documented in this file.
|
||||
This project uses the changelog in accordance with [keepchangelog](http://keepachangelog.com/). Please use this to write notable changes, which is not the same as git commit log...
|
||||
|
||||
## [unreleased][unreleased]
|
||||
- Added `nfc encode` and `hf mfu ndefwrite` - build NDEF URI/Text/AAR records and write them to MIFARE Ultralight / NTAG tags, plus a reusable NDEF encoder in `nfc/ndef.c` (@0x6r1an0y)
|
||||
- Fixed `hf iclass sam` - removed the old implementation (@iceman1001)
|
||||
- Fixed Proxmark5 (PM5/AT32) failing to re-enter the bootloader (@nemanjan00)
|
||||
- Fixed `hw fpga config` on Proxmark5 (PM5/AT32) now powers on the FPGA 24MHz clock (@nemanjan00)
|
||||
|
||||
@@ -7402,6 +7402,431 @@ int CmdHF14MfuNDEFRead(const char *Cmd) {
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
// Build a NDEF message from the CLI options and write it into the data area of a
|
||||
// MIFARE Ultralight / NTAG tag. The tag has to be NDEF formatted already, this
|
||||
// command never touches the Capability Container in block 3.
|
||||
int CmdHF14MfuNDEFWrite(const char *Cmd) {
|
||||
|
||||
int ak_len = 0;
|
||||
bool has_auth_key = false;
|
||||
bool has_pwd = false;
|
||||
bool swap_endian = false;
|
||||
|
||||
iso14a_card_select_t card;
|
||||
uint8_t data[16] = {0x00};
|
||||
uint8_t authenticationkey[16] = {0x00};
|
||||
uint8_t *auth_key_ptr = authenticationkey;
|
||||
uint8_t pack[4] = {0, 0, 0, 0};
|
||||
|
||||
CLIParserContext *ctx;
|
||||
CLIParserInit(&ctx, "hf mfu ndefwrite",
|
||||
"Write NFC Data Exchange Format (NDEF) records to a MIFARE Ultralight / NTAG tag.\n"
|
||||
"The tag must already be NDEF formatted, ie carry a Capability Container in block 3.\n"
|
||||
"\n"
|
||||
"Combine several of --uri, --text and --aar to build a multi record message,\n"
|
||||
"records are added in that order. Alternatively supply raw NDEF bytes with -d\n"
|
||||
"or -f, those get wrapped in a TLV container automatically when they are not\n"
|
||||
"already. Use `nfc encode` to build such raw bytes offline.\n"
|
||||
"\n"
|
||||
"Note: the tag is re-selected and re-authenticated for every block written,\n"
|
||||
"so a large message takes a while.",
|
||||
"hf mfu ndefwrite --uri https://proxmark.com\n"
|
||||
"hf mfu ndefwrite --uri tel:+123456789\n"
|
||||
"hf mfu ndefwrite --text \"hello world\"\n"
|
||||
"hf mfu ndefwrite --aar com.example.app\n"
|
||||
"hf mfu ndefwrite --uri https://proxmark.com --aar com.example.app\n"
|
||||
"hf mfu ndefwrite --uri https://proxmark.com -k ffffffff\n"
|
||||
"hf mfu ndefwrite -d 0311D1010D550270726F786D61726B2E636F6DFE\n"
|
||||
"hf mfu ndefwrite -f myfilename"
|
||||
);
|
||||
|
||||
void *argtable[] = {
|
||||
arg_param_begin,
|
||||
arg_str0("k", "key", "<hex>", "Authentication key (UL-C/UL-AES 16 bytes, EV1/NTAG 4 bytes)"),
|
||||
arg_lit0("l", NULL, "Swap entered key endianness"),
|
||||
arg_str0(NULL, "uri", "<str>", "URI record. URL, tel:, mailto:, ..."),
|
||||
arg_str0(NULL, "text", "<str>", "Text record"),
|
||||
arg_str0(NULL, "lang", "<str>", "language code for the text record (default: en)"),
|
||||
arg_str0(NULL, "aar", "<str>", "Android Application Record, ie an app package name"),
|
||||
arg_str0("d", "data", "<hex>", "Raw NDEF bytes to write"),
|
||||
arg_str0("f", "file", "<fn>", "Raw NDEF file to write"),
|
||||
arg_lit0("v", "verbose", "Verbose output"),
|
||||
arg_lit0(NULL, "schann", "use secure channel. Must have key"),
|
||||
arg_param_end
|
||||
};
|
||||
CLIExecWithReturn(ctx, Cmd, argtable, true);
|
||||
|
||||
CLIGetHexWithReturn(ctx, 1, authenticationkey, &ak_len);
|
||||
swap_endian = arg_get_lit(ctx, 2);
|
||||
|
||||
// CLIParamStrToBuf copies the trailing NUL as well but only rejects lengths
|
||||
// strictly above maxdatalen, so leave room for that byte
|
||||
int urilen = 0;
|
||||
char uri[1024] = {0};
|
||||
CLIParamStrToBuf(arg_get_str(ctx, 3), (uint8_t *)uri, sizeof(uri) - 1, &urilen);
|
||||
|
||||
int textlen = 0;
|
||||
char text[1024] = {0};
|
||||
CLIParamStrToBuf(arg_get_str(ctx, 4), (uint8_t *)text, sizeof(text) - 1, &textlen);
|
||||
|
||||
int langlen = 0;
|
||||
char lang[32] = {0};
|
||||
CLIParamStrToBuf(arg_get_str(ctx, 5), (uint8_t *)lang, sizeof(lang) - 1, &langlen);
|
||||
|
||||
int aarlen = 0;
|
||||
char aar[256] = {0};
|
||||
CLIParamStrToBuf(arg_get_str(ctx, 6), (uint8_t *)aar, sizeof(aar) - 1, &aarlen);
|
||||
|
||||
int rawlen = 0;
|
||||
uint8_t raw[MFU_NDEF_MAX_BYTES] = {0};
|
||||
CLIGetHexWithReturn(ctx, 7, raw, &rawlen);
|
||||
|
||||
int fnlen = 0;
|
||||
char filename[FILE_PATH_SIZE] = {0};
|
||||
CLIParamStrToBuf(arg_get_str(ctx, 8), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen);
|
||||
|
||||
bool verbose = arg_get_lit(ctx, 9);
|
||||
bool use_schann = arg_get_lit(ctx, 10);
|
||||
CLIParserFree(ctx);
|
||||
|
||||
bool has_records = (urilen || textlen || aarlen);
|
||||
int sources = (has_records ? 1 : 0) + (rawlen ? 1 : 0) + (fnlen ? 1 : 0);
|
||||
if (sources == 0) {
|
||||
PrintAndLogEx(ERR, "Nothing to write. See `" _YELLOW_("hf mfu ndefwrite -h") "`");
|
||||
return PM3_EINVARG;
|
||||
}
|
||||
if (sources > 1) {
|
||||
PrintAndLogEx(ERR, "Use either the record options, -d or -f, not a mix of them");
|
||||
return PM3_EINVARG;
|
||||
}
|
||||
|
||||
if ((langlen != 0) && (textlen == 0)) {
|
||||
PrintAndLogEx(WARNING, "--lang only applies to a text record, ignoring");
|
||||
}
|
||||
|
||||
switch (ak_len) {
|
||||
case 0:
|
||||
break;
|
||||
case 4:
|
||||
has_pwd = true;
|
||||
break;
|
||||
case 16:
|
||||
has_auth_key = true;
|
||||
break;
|
||||
default:
|
||||
PrintAndLogEx(WARNING, "ERROR: Key is incorrect length\n");
|
||||
return PM3_EINVARG;
|
||||
}
|
||||
|
||||
if (use_schann && (has_auth_key == false)) {
|
||||
PrintAndLogEx(WARNING, "Secure channel must be called with key");
|
||||
return PM3_EINVARG;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// build the TLV block that goes onto the tag
|
||||
// ------------------------------------------------------------------
|
||||
uint8_t tlv[MFU_NDEF_MAX_BYTES] = {0};
|
||||
size_t tlv_len = 0;
|
||||
int res = PM3_SUCCESS;
|
||||
|
||||
if (has_records) {
|
||||
|
||||
NDEFRecordDesc_t recs[3] = {{0}};
|
||||
size_t count = 0;
|
||||
|
||||
uint8_t p_uri[sizeof(uri) + 1] = {0};
|
||||
size_t p_uri_len = 0;
|
||||
if (urilen) {
|
||||
res = NDEFEncodePayloadURI(uri, p_uri, sizeof(p_uri), &p_uri_len);
|
||||
if (res != PM3_SUCCESS) {
|
||||
PrintAndLogEx(ERR, "Failed to encode URI record");
|
||||
return res;
|
||||
}
|
||||
recs[count].tnf = tnfWellKnownRecord;
|
||||
recs[count].type = (const uint8_t *)NDEF_TYPE_URI;
|
||||
recs[count].typeLen = strlen(NDEF_TYPE_URI);
|
||||
recs[count].payload = p_uri;
|
||||
recs[count].payloadLen = p_uri_len;
|
||||
count++;
|
||||
}
|
||||
|
||||
uint8_t p_text[sizeof(text) + sizeof(lang) + 1] = {0};
|
||||
size_t p_text_len = 0;
|
||||
if (textlen) {
|
||||
res = NDEFEncodePayloadText(text, lang, p_text, sizeof(p_text), &p_text_len);
|
||||
if (res != PM3_SUCCESS) {
|
||||
PrintAndLogEx(ERR, "Failed to encode Text record");
|
||||
return res;
|
||||
}
|
||||
recs[count].tnf = tnfWellKnownRecord;
|
||||
recs[count].type = (const uint8_t *)NDEF_TYPE_TEXT;
|
||||
recs[count].typeLen = strlen(NDEF_TYPE_TEXT);
|
||||
recs[count].payload = p_text;
|
||||
recs[count].payloadLen = p_text_len;
|
||||
count++;
|
||||
}
|
||||
|
||||
uint8_t p_aar[sizeof(aar)] = {0};
|
||||
size_t p_aar_len = 0;
|
||||
if (aarlen) {
|
||||
res = NDEFEncodePayloadAAR(aar, p_aar, sizeof(p_aar), &p_aar_len);
|
||||
if (res != PM3_SUCCESS) {
|
||||
PrintAndLogEx(ERR, "Failed to encode Android Application Record");
|
||||
return res;
|
||||
}
|
||||
recs[count].tnf = tnfExternalRecord;
|
||||
recs[count].type = (const uint8_t *)NDEF_ANDROID_AAR;
|
||||
recs[count].typeLen = strlen(NDEF_ANDROID_AAR);
|
||||
recs[count].payload = p_aar;
|
||||
recs[count].payloadLen = p_aar_len;
|
||||
count++;
|
||||
}
|
||||
|
||||
uint8_t msg[MFU_NDEF_MAX_BYTES] = {0};
|
||||
size_t msg_len = 0;
|
||||
res = NDEFEncodeMessage(recs, count, msg, sizeof(msg), &msg_len);
|
||||
if (res != PM3_SUCCESS) {
|
||||
PrintAndLogEx(ERR, "Failed to encode NDEF message");
|
||||
return res;
|
||||
}
|
||||
|
||||
res = NDEFEncodeTLV(msg, msg_len, tlv, sizeof(tlv), &tlv_len);
|
||||
if (res != PM3_SUCCESS) {
|
||||
PrintAndLogEx(ERR, "Failed to wrap NDEF message in a TLV container");
|
||||
return res;
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
uint8_t *src = raw;
|
||||
size_t src_len = (size_t)rawlen;
|
||||
uint8_t *dump = NULL;
|
||||
|
||||
if (fnlen) {
|
||||
size_t bytes_read = 0;
|
||||
res = pm3_load_dump(filename, (void **)&dump, &bytes_read, sizeof(raw));
|
||||
if (res != PM3_SUCCESS) {
|
||||
return res;
|
||||
}
|
||||
src = dump;
|
||||
src_len = bytes_read;
|
||||
}
|
||||
|
||||
if (src_len == 0) {
|
||||
PrintAndLogEx(ERR, "No NDEF data supplied");
|
||||
free(dump);
|
||||
return PM3_EINVARG;
|
||||
}
|
||||
|
||||
// is it a TLV block already?
|
||||
bool is_tlv = false;
|
||||
switch (src[0]) {
|
||||
case 0x00:
|
||||
case 0x01:
|
||||
case 0x02:
|
||||
case 0x03:
|
||||
case 0xFD:
|
||||
case 0xFE:
|
||||
is_tlv = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (is_tlv) {
|
||||
if (src_len > sizeof(tlv)) {
|
||||
PrintAndLogEx(ERR, "NDEF data too large, %zu bytes", src_len);
|
||||
free(dump);
|
||||
return PM3_EINVARG;
|
||||
}
|
||||
memcpy(tlv, src, src_len);
|
||||
tlv_len = src_len;
|
||||
} else {
|
||||
if (verbose) {
|
||||
PrintAndLogEx(INFO, "Raw data is not TLV wrapped, adding container");
|
||||
}
|
||||
res = NDEFEncodeTLV(src, src_len, tlv, sizeof(tlv), &tlv_len);
|
||||
if (res != PM3_SUCCESS) {
|
||||
PrintAndLogEx(ERR, "Failed to wrap NDEF message in a TLV container");
|
||||
free(dump);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
free(dump);
|
||||
}
|
||||
|
||||
if (verbose) {
|
||||
PrintAndLogEx(INFO, "TLV block [%zu]...", tlv_len);
|
||||
print_hex_noascii_break(tlv, tlv_len, 32);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// find the tag and check the message fits
|
||||
// ------------------------------------------------------------------
|
||||
uint64_t tagtype = GetHF14AMfU_Type();
|
||||
if (tagtype == MFU_TT_UL_ERROR) {
|
||||
PrintAndLogEx(WARNING, "No Ultralight / NTAG based tag found");
|
||||
return PM3_ESOFT;
|
||||
}
|
||||
|
||||
ul_print_type(tagtype, 0);
|
||||
|
||||
if (swap_endian) {
|
||||
if (ak_len == 16) {
|
||||
if (((tagtype & MFU_TT_UL_C) == MFU_TT_UL_C)) {
|
||||
auth_key_ptr = SwapEndian64(authenticationkey, ak_len, 8);
|
||||
} else if (((tagtype & MFU_TT_UL_AES) == MFU_TT_UL_AES)) {
|
||||
auth_key_ptr = SwapEndian64(authenticationkey, ak_len, 16);
|
||||
}
|
||||
} else if (ak_len == 4) {
|
||||
auth_key_ptr = SwapEndian64(authenticationkey, ak_len, 4);
|
||||
}
|
||||
}
|
||||
|
||||
if (ul_auth_select(&card, tagtype, (has_auth_key || has_pwd), auth_key_ptr, pack, sizeof(pack), use_schann) == PM3_ESOFT) {
|
||||
return PM3_ESOFT;
|
||||
}
|
||||
|
||||
// read blocks 0..3 to get at the Capability Container
|
||||
int status = ul_read(0, data, sizeof(data), use_schann);
|
||||
if (status <= 0) {
|
||||
DropField();
|
||||
PrintAndLogEx(ERR, "Error: tag didnt answer to READ");
|
||||
return PM3_ESOFT;
|
||||
}
|
||||
|
||||
if (status != 16) {
|
||||
DropField();
|
||||
PrintAndLogEx(ERR, "Error: tag returned %d bytes, need 16 to read the NDEF Container", status);
|
||||
return PM3_ESOFT;
|
||||
}
|
||||
|
||||
if (ndef_print_CC(data + 12) == PM3_ESOFT) {
|
||||
DropField();
|
||||
PrintAndLogEx(ERR, "Error: tag didnt contain a NDEF Container");
|
||||
PrintAndLogEx(HINT, "Hint: the tag needs to be NDEF formatted first");
|
||||
return PM3_ESOFT;
|
||||
}
|
||||
|
||||
// size of the NDEF data area
|
||||
uint16_t ndef_size = ndef_get_maxsize(data + 12);
|
||||
if (ndef_size == 0) {
|
||||
DropField();
|
||||
PrintAndLogEx(ERR, "Error: tag announces an empty NDEF data area");
|
||||
return PM3_ESOFT;
|
||||
}
|
||||
|
||||
// The data area starts at block 4, so cap the announced size to what the
|
||||
// identified tag can physically hold. UL_MEMORY_ARRAY holds the last valid
|
||||
// block number, hence the +1.
|
||||
for (uint8_t idx = 1; idx < ARRAYLEN(UL_TYPES_ARRAY); idx++) {
|
||||
if ((tagtype & UL_TYPES_ARRAY[idx]) == UL_TYPES_ARRAY[idx]) {
|
||||
|
||||
int avail = (UL_MEMORY_ARRAY[idx] + 1 - MFU_NDEF_FIRST_BLOCK) * MFU_BLOCK_SIZE;
|
||||
if (avail > 0 && ndef_size > avail) {
|
||||
PrintAndLogEx(INFO, "NDEF data area (%u bytes) is larger than the tag (%d bytes), using tag size"
|
||||
, ndef_size
|
||||
, avail
|
||||
);
|
||||
ndef_size = avail;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// MLEN can announce up to 2040 bytes but WRITE addresses blocks with a single
|
||||
// byte, so block 255 is the last one we can reach.
|
||||
if (ndef_size > MFU_NDEF_MAX_BYTES) {
|
||||
ndef_size = MFU_NDEF_MAX_BYTES;
|
||||
}
|
||||
|
||||
if (tlv_len > ndef_size) {
|
||||
DropField();
|
||||
PrintAndLogEx(ERR, "NDEF message is too large for this tag");
|
||||
PrintAndLogEx(ERR, " message..... " _RED_("%zu") " bytes", tlv_len);
|
||||
PrintAndLogEx(ERR, " tag holds... " _GREEN_("%u") " bytes", ndef_size);
|
||||
return PM3_EINVARG;
|
||||
}
|
||||
|
||||
DropField();
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// write it out, one block at a time
|
||||
// ------------------------------------------------------------------
|
||||
uint8_t keytype = 0;
|
||||
if (has_auth_key || has_pwd) {
|
||||
if ((tagtype & MFU_TT_UL_C) == MFU_TT_UL_C) {
|
||||
keytype = 1; // UL_C auth
|
||||
} else if ((tagtype & MFU_TT_UL_AES) == MFU_TT_UL_AES) {
|
||||
keytype = 3; // UL_AES auth
|
||||
} else {
|
||||
keytype = 2; // UL_EV1/NTAG auth
|
||||
}
|
||||
}
|
||||
|
||||
// pad the tail so the last block is complete
|
||||
size_t padded_len = (tlv_len + (MFU_BLOCK_SIZE - 1)) & ~((size_t)MFU_BLOCK_SIZE - 1);
|
||||
uint16_t blocks = (uint16_t)(padded_len / MFU_BLOCK_SIZE);
|
||||
|
||||
PrintAndLogEx(INFO, "Writing " _YELLOW_("%u") " blocks ( %zu bytes ) from block " _YELLOW_("%d")
|
||||
, blocks
|
||||
, padded_len
|
||||
, MFU_NDEF_FIRST_BLOCK
|
||||
);
|
||||
PrintAndLogEx(INFO, "Press " _GREEN_("<Enter>") " to abort");
|
||||
|
||||
for (uint16_t i = 0; i < blocks; i++) {
|
||||
|
||||
if (kbd_enter_pressed()) {
|
||||
PrintAndLogEx(NORMAL, "");
|
||||
PrintAndLogEx(WARNING, "aborted via keyboard!");
|
||||
if (i > 0) {
|
||||
PrintAndLogEx(WARNING, "tag holds a partially written NDEF message, blocks %d..%u"
|
||||
, MFU_NDEF_FIRST_BLOCK
|
||||
, MFU_NDEF_FIRST_BLOCK + i - 1
|
||||
);
|
||||
}
|
||||
return PM3_EOPABORTED;
|
||||
}
|
||||
|
||||
uint8_t blockno = (uint8_t)(MFU_NDEF_FIRST_BLOCK + i);
|
||||
|
||||
res = mfu_write_block(tlv + (i * MFU_BLOCK_SIZE), MFU_BLOCK_SIZE, keytype, auth_key_ptr, blockno, use_schann);
|
||||
if (res != PM3_SUCCESS) {
|
||||
PrintAndLogEx(NORMAL, "");
|
||||
|
||||
// a protected tag simply stops answering, so the write times out
|
||||
// rather than coming back with an explicit error
|
||||
if (res == PM3_ETIMEOUT) {
|
||||
PrintAndLogEx(FAILED, "Write block %u ( " _RED_("timeout") " )", blockno);
|
||||
} else {
|
||||
PrintAndLogEx(FAILED, "Write block %u ( " _RED_("fail") " )", blockno);
|
||||
}
|
||||
PrintAndLogEx(HINT, "Hint: Check password / key!");
|
||||
|
||||
if (i > 0) {
|
||||
PrintAndLogEx(WARNING, "tag holds a partially written NDEF message, blocks %d..%u"
|
||||
, MFU_NDEF_FIRST_BLOCK
|
||||
, blockno - 1
|
||||
);
|
||||
}
|
||||
return PM3_ESOFT;
|
||||
}
|
||||
|
||||
PrintAndLogEx(INPLACE, "Block %u / %u", i + 1, blocks);
|
||||
}
|
||||
|
||||
DropField();
|
||||
|
||||
PrintAndLogEx(NORMAL, "");
|
||||
PrintAndLogEx(SUCCESS, "Write ( " _GREEN_("ok") " )");
|
||||
PrintAndLogEx(HINT, "Hint: Try `" _YELLOW_("hf mfu ndefread") "` to verify");
|
||||
PrintAndLogEx(NORMAL, "");
|
||||
return PM3_SUCCESS;
|
||||
}
|
||||
// utility function. Retrieves emulator memory
|
||||
static int GetMfuDumpFromEMul(mfu_dump_t **buf) {
|
||||
|
||||
@@ -8274,6 +8699,7 @@ static command_t CommandTable[] = {
|
||||
{"incr", CmdHF14AMfUIncr, IfPm3Iso14443a, "Increments Ev1/NTAG counter"},
|
||||
{"info", CmdHF14AMfUInfo, IfPm3Iso14443a, "Tag information"},
|
||||
{"ndefread", CmdHF14MfuNDEFRead, IfPm3Iso14443a, "Prints NDEF records from card"},
|
||||
{"ndefwrite", CmdHF14MfuNDEFWrite, IfPm3Iso14443a, "Write NDEF records to card"},
|
||||
{"rdbl", CmdHF14AMfURdBl, IfPm3Iso14443a, "Read block"},
|
||||
{"restore", CmdHF14AMfURestore, IfPm3Iso14443a, "Restore a dump file onto a tag"},
|
||||
{"tamper", CmdHF14MfUTamper, IfPm3Iso14443a, "NTAG 213TT - Configure the tamper feature"},
|
||||
|
||||
@@ -57,7 +57,8 @@ int mfu_get_version_uid(uint8_t *version, uint8_t *uid);
|
||||
int mfuc_test_authentication_support(void);
|
||||
|
||||
int CmdHFMFUltra(const char *Cmd);
|
||||
int CmdHF14MfuNDEFRead(const char *Cmd);
|
||||
int CmdHF14MfuNDEFRead(const char *Cmd); // used by cmdnfc.c
|
||||
int CmdHF14MfuNDEFWrite(const char *Cmd); // used by cmdnfc.c
|
||||
int CmdHF14MfUTamper(const char *Cmd);
|
||||
|
||||
#define MFU_TT_UNKNOWN 0x0ULL
|
||||
|
||||
+183
-2
@@ -178,6 +178,183 @@ static int CmdNfcDecode(const char *Cmd) {
|
||||
return res;
|
||||
}
|
||||
|
||||
static int CmdNfcEncode(const char *Cmd) {
|
||||
|
||||
#ifndef MAX_NDEF_LEN
|
||||
#define MAX_NDEF_LEN 2048
|
||||
#endif
|
||||
|
||||
CLIParserContext *ctx;
|
||||
CLIParserInit(&ctx, "nfc encode",
|
||||
"Encode NFC Data Exchange Format (NDEF) records.\n"
|
||||
"Combine several options to build a multi record message,\n"
|
||||
"records are added in the order listed below.\n"
|
||||
"\n"
|
||||
"By default a bare NDEF message is emitted. Use `--tlv` to wrap it in a\n"
|
||||
"NFC Forum Type 2 tag container ( 03 <len> ... FE ), which is what the\n"
|
||||
"`-d` parameter of `hf mf ndefwrite` and `hf 14a ndefwrite` expects.\n"
|
||||
"`hf mfu ndefwrite` adds the container itself, so it does not need `--tlv`",
|
||||
"nfc encode --uri https://proxmark.com\n"
|
||||
"nfc encode --uri tel:+123456789\n"
|
||||
"nfc encode --text \"hello world\"\n"
|
||||
"nfc encode --aar com.example.app\n"
|
||||
"nfc encode --uri https://proxmark.com --aar com.example.app\n"
|
||||
"nfc encode --uri https://proxmark.com --tlv -f myfilename"
|
||||
);
|
||||
|
||||
void *argtable[] = {
|
||||
arg_param_begin,
|
||||
arg_str0(NULL, "uri", "<str>", "URI record. URL, tel:, mailto:, ..."),
|
||||
arg_str0(NULL, "text", "<str>", "Text record"),
|
||||
arg_str0(NULL, "lang", "<str>", "language code for the text record (default: en)"),
|
||||
arg_str0(NULL, "aar", "<str>", "Android Application Record, ie an app package name"),
|
||||
arg_lit0(NULL, "tlv", "wrap message in a NFC Forum Type 2 tag TLV container"),
|
||||
arg_str0("f", "file", "<fn>", "save raw bytes to file"),
|
||||
arg_lit0("v", "verbose", "verbose output"),
|
||||
arg_param_end
|
||||
};
|
||||
CLIExecWithReturn(ctx, Cmd, argtable, false);
|
||||
|
||||
// CLIParamStrToBuf copies the trailing NUL as well but only rejects lengths
|
||||
// strictly above maxdatalen, so leave room for that byte
|
||||
int urilen = 0;
|
||||
char uri[1024] = {0};
|
||||
CLIParamStrToBuf(arg_get_str(ctx, 1), (uint8_t *)uri, sizeof(uri) - 1, &urilen);
|
||||
|
||||
int textlen = 0;
|
||||
char text[1024] = {0};
|
||||
CLIParamStrToBuf(arg_get_str(ctx, 2), (uint8_t *)text, sizeof(text) - 1, &textlen);
|
||||
|
||||
int langlen = 0;
|
||||
char lang[32] = {0};
|
||||
CLIParamStrToBuf(arg_get_str(ctx, 3), (uint8_t *)lang, sizeof(lang) - 1, &langlen);
|
||||
|
||||
int aarlen = 0;
|
||||
char aar[256] = {0};
|
||||
CLIParamStrToBuf(arg_get_str(ctx, 4), (uint8_t *)aar, sizeof(aar) - 1, &aarlen);
|
||||
|
||||
bool use_tlv = arg_get_lit(ctx, 5);
|
||||
|
||||
int fnlen = 0;
|
||||
char filename[FILE_PATH_SIZE] = {0};
|
||||
CLIParamStrToBuf(arg_get_str(ctx, 6), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen);
|
||||
|
||||
bool verbose = arg_get_lit(ctx, 7);
|
||||
CLIParserFree(ctx);
|
||||
|
||||
if ((urilen == 0) && (textlen == 0) && (aarlen == 0)) {
|
||||
PrintAndLogEx(ERR, "Must specify at least one record. See `" _YELLOW_("nfc encode -h") "`");
|
||||
return PM3_EINVARG;
|
||||
}
|
||||
|
||||
if ((langlen != 0) && (textlen == 0)) {
|
||||
PrintAndLogEx(WARNING, "`--lang` only applies to a text record, ignoring");
|
||||
}
|
||||
|
||||
NDEFRecordDesc_t recs[3] = {{0}};
|
||||
size_t count = 0;
|
||||
int res = PM3_SUCCESS;
|
||||
|
||||
uint8_t p_uri[sizeof(uri) + 1] = {0};
|
||||
size_t p_uri_len = 0;
|
||||
if (urilen) {
|
||||
res = NDEFEncodePayloadURI(uri, p_uri, sizeof(p_uri), &p_uri_len);
|
||||
if (res != PM3_SUCCESS) {
|
||||
PrintAndLogEx(ERR, "Failed to encode URI record");
|
||||
return res;
|
||||
}
|
||||
recs[count].tnf = tnfWellKnownRecord;
|
||||
recs[count].type = (const uint8_t *)NDEF_TYPE_URI;
|
||||
recs[count].typeLen = strlen(NDEF_TYPE_URI);
|
||||
recs[count].payload = p_uri;
|
||||
recs[count].payloadLen = p_uri_len;
|
||||
count++;
|
||||
}
|
||||
|
||||
uint8_t p_text[sizeof(text) + sizeof(lang) + 1] = {0};
|
||||
size_t p_text_len = 0;
|
||||
if (textlen) {
|
||||
res = NDEFEncodePayloadText(text, lang, p_text, sizeof(p_text), &p_text_len);
|
||||
if (res != PM3_SUCCESS) {
|
||||
PrintAndLogEx(ERR, "Failed to encode Text record");
|
||||
return res;
|
||||
}
|
||||
recs[count].tnf = tnfWellKnownRecord;
|
||||
recs[count].type = (const uint8_t *)NDEF_TYPE_TEXT;
|
||||
recs[count].typeLen = strlen(NDEF_TYPE_TEXT);
|
||||
recs[count].payload = p_text;
|
||||
recs[count].payloadLen = p_text_len;
|
||||
count++;
|
||||
}
|
||||
|
||||
uint8_t p_aar[sizeof(aar)] = {0};
|
||||
size_t p_aar_len = 0;
|
||||
if (aarlen) {
|
||||
res = NDEFEncodePayloadAAR(aar, p_aar, sizeof(p_aar), &p_aar_len);
|
||||
if (res != PM3_SUCCESS) {
|
||||
PrintAndLogEx(ERR, "Failed to encode Android Application Record");
|
||||
return res;
|
||||
}
|
||||
recs[count].tnf = tnfExternalRecord;
|
||||
recs[count].type = (const uint8_t *)NDEF_ANDROID_AAR;
|
||||
recs[count].typeLen = strlen(NDEF_ANDROID_AAR);
|
||||
recs[count].payload = p_aar;
|
||||
recs[count].payloadLen = p_aar_len;
|
||||
count++;
|
||||
}
|
||||
|
||||
uint8_t msg[MAX_NDEF_LEN] = {0};
|
||||
size_t msglen = 0;
|
||||
res = NDEFEncodeMessage(recs, count, msg, sizeof(msg), &msglen);
|
||||
if (res != PM3_SUCCESS) {
|
||||
PrintAndLogEx(ERR, "Failed to encode NDEF message");
|
||||
return res;
|
||||
}
|
||||
|
||||
uint8_t tlv[MAX_NDEF_LEN] = {0};
|
||||
uint8_t *out = msg;
|
||||
size_t outlen = msglen;
|
||||
|
||||
if (use_tlv) {
|
||||
size_t tlvlen = 0;
|
||||
res = NDEFEncodeTLV(msg, msglen, tlv, sizeof(tlv), &tlvlen);
|
||||
if (res != PM3_SUCCESS) {
|
||||
PrintAndLogEx(ERR, "Failed to wrap NDEF message in a TLV container");
|
||||
return res;
|
||||
}
|
||||
out = tlv;
|
||||
outlen = tlvlen;
|
||||
}
|
||||
|
||||
PrintAndLogEx(NORMAL, "");
|
||||
PrintAndLogEx(INFO, "--- " _CYAN_("NDEF encoding") " ----------------");
|
||||
PrintAndLogEx(SUCCESS, "Records......... " _YELLOW_("%zu"), count);
|
||||
PrintAndLogEx(SUCCESS, "Message size.... " _YELLOW_("%zu") " bytes", msglen);
|
||||
if (use_tlv) {
|
||||
PrintAndLogEx(SUCCESS, "TLV size........ " _YELLOW_("%zu") " bytes", outlen);
|
||||
}
|
||||
|
||||
PrintAndLogEx(NORMAL, "");
|
||||
if (outlen > 64) {
|
||||
print_hex_noascii_break(out, outlen, 32);
|
||||
} else {
|
||||
PrintAndLogEx(SUCCESS, _GREEN_("%s"), sprint_hex_inrow(out, outlen));
|
||||
}
|
||||
|
||||
if (fnlen) {
|
||||
saveFile(filename, ".bin", out, outlen);
|
||||
}
|
||||
|
||||
// round trip it back through the decoder, so the user can see what a reader will see
|
||||
if (use_tlv) {
|
||||
NDEFDecodeAndPrint(out, outlen, verbose);
|
||||
} else {
|
||||
NDEFRecordsDecodeAndPrint(out, outlen, verbose);
|
||||
}
|
||||
|
||||
return PM3_SUCCESS;
|
||||
}
|
||||
|
||||
static int CmdNFCType1Read(const char *Cmd) {
|
||||
return CmdHFTopazInfo(Cmd);
|
||||
}
|
||||
@@ -210,6 +387,10 @@ static int CmdNFCType2Read(const char *Cmd) {
|
||||
return CmdHF14MfuNDEFRead(Cmd);
|
||||
}
|
||||
|
||||
static int CmdNFCType2Write(const char *Cmd) {
|
||||
return CmdHF14MfuNDEFWrite(Cmd);
|
||||
}
|
||||
|
||||
static int CmdNFCType2Help(const char *Cmd);
|
||||
|
||||
static command_t CommandNFCType2Table[] = {
|
||||
@@ -217,7 +398,7 @@ static command_t CommandNFCType2Table[] = {
|
||||
{"--------", CmdNFCType2Help, AlwaysAvailable, "-------------- " _CYAN_("NFC Forum Tag Type 2") " ---------------"},
|
||||
// {"format", CmdNFCType2Format, IfPm3Iso14443a, "format ISO-14443-a tag as NFC Tag"},
|
||||
{"read", CmdNFCType2Read, IfPm3Iso14443a, "read NFC Forum Tag Type 2"},
|
||||
// {"write", CmdNFCType2Write, IfPm3Iso14443a, "write NFC Forum Tag Type 2"},
|
||||
{"write", CmdNFCType2Write, IfPm3Iso14443a, "write NFC Forum Tag Type 2"},
|
||||
{"--------", CmdNFCType2Help, AlwaysAvailable, "--------------------- " _CYAN_("General") " ---------------------"},
|
||||
{"help", CmdNFCType2Help, AlwaysAvailable, "This help"},
|
||||
{NULL, NULL, NULL, NULL}
|
||||
@@ -459,7 +640,7 @@ static command_t CommandTable[] = {
|
||||
{"--------", CmdHelp, AlwaysAvailable, "--------------------- " _CYAN_("General") " ---------------------"},
|
||||
{"help", CmdHelp, AlwaysAvailable, "This help"},
|
||||
{"decode", CmdNfcDecode, AlwaysAvailable, "Decode NDEF records"},
|
||||
// {"encode", CmdNfcEncode, AlwaysAvailable, "Encode NDEF records"},
|
||||
{"encode", CmdNfcEncode, AlwaysAvailable, "Encode NDEF records"},
|
||||
{NULL, NULL, NULL, NULL}
|
||||
};
|
||||
|
||||
|
||||
@@ -998,6 +998,17 @@ static int ndefDecodeExternal_record(NDEFHeader_t *ndef) {
|
||||
return PM3_SUCCESS;
|
||||
}
|
||||
|
||||
// Android Application Record
|
||||
if ((ndef->TypeLen == strlen(NDEF_ANDROID_AAR)) &&
|
||||
(strncmp((char *)ndef->Type, NDEF_ANDROID_AAR, ndef->TypeLen) == 0)) {
|
||||
PrintAndLogEx(INFO
|
||||
, " Android app... " _GREEN_("%.*s")
|
||||
, (int)ndef->PayloadLen
|
||||
, ndef->Payload
|
||||
);
|
||||
return PM3_SUCCESS;
|
||||
}
|
||||
|
||||
PrintAndLogEx(INFO
|
||||
, " URN... " _GREEN_("urn:nfc:ext:%.*s")
|
||||
, (int)ndef->TypeLen
|
||||
@@ -1393,3 +1404,257 @@ int NDEFGetTotalLength(uint8_t *ndef, size_t ndeflen, size_t *outlen) {
|
||||
*outlen = idx;
|
||||
return PM3_SUCCESS;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// NDEF encoding
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
// Find the longest matching abbreviation from URI_s[].
|
||||
// Returns the prefix code and sets prefixLen to how many bytes of uri it covers.
|
||||
// Code 0x00 means "no abbreviation", the whole string gets stored verbatim.
|
||||
static uint8_t ndefEncodeURIPrefix(const char *uri, size_t *prefixLen) {
|
||||
|
||||
uint8_t code = 0;
|
||||
size_t best = 0;
|
||||
|
||||
for (uint8_t i = 1; i < ARRAYLEN(URI_s); i++) {
|
||||
size_t n = strlen(URI_s[i]);
|
||||
if ((n > best) && (strncmp(uri, URI_s[i], n) == 0)) {
|
||||
best = n;
|
||||
code = i;
|
||||
}
|
||||
}
|
||||
|
||||
*prefixLen = best;
|
||||
return code;
|
||||
}
|
||||
|
||||
int NDEFEncodePayloadURI(const char *uri, uint8_t *buf, size_t bufLen, size_t *outLen) {
|
||||
|
||||
if ((uri == NULL) || (buf == NULL) || (outLen == NULL)) {
|
||||
return PM3_EINVARG;
|
||||
}
|
||||
|
||||
size_t uri_len = strlen(uri);
|
||||
if (uri_len == 0) {
|
||||
return PM3_EINVARG;
|
||||
}
|
||||
|
||||
size_t prefix_len = 0;
|
||||
uint8_t code = ndefEncodeURIPrefix(uri, &prefix_len);
|
||||
|
||||
size_t n = 1 + (uri_len - prefix_len);
|
||||
if (n > bufLen) {
|
||||
return PM3_EOVFLOW;
|
||||
}
|
||||
|
||||
buf[0] = code;
|
||||
memcpy(buf + 1, uri + prefix_len, uri_len - prefix_len);
|
||||
|
||||
*outLen = n;
|
||||
return PM3_SUCCESS;
|
||||
}
|
||||
|
||||
int NDEFEncodePayloadText(const char *text, const char *lang, uint8_t *buf, size_t bufLen, size_t *outLen) {
|
||||
|
||||
if ((text == NULL) || (buf == NULL) || (outLen == NULL)) {
|
||||
return PM3_EINVARG;
|
||||
}
|
||||
|
||||
if ((lang == NULL) || (strlen(lang) == 0)) {
|
||||
lang = "en";
|
||||
}
|
||||
|
||||
size_t lang_len = strlen(lang);
|
||||
size_t text_len = strlen(text);
|
||||
|
||||
// the status byte carries the language code length in its lower 6 bits
|
||||
if (lang_len > 0x3F) {
|
||||
return PM3_EINVARG;
|
||||
}
|
||||
|
||||
size_t n = 1 + lang_len + text_len;
|
||||
if (n > bufLen) {
|
||||
return PM3_EOVFLOW;
|
||||
}
|
||||
|
||||
// bit 7 clear == UTF-8
|
||||
buf[0] = (uint8_t)(lang_len & 0x3F);
|
||||
memcpy(buf + 1, lang, lang_len);
|
||||
memcpy(buf + 1 + lang_len, text, text_len);
|
||||
|
||||
*outLen = n;
|
||||
return PM3_SUCCESS;
|
||||
}
|
||||
|
||||
int NDEFEncodePayloadAAR(const char *pkg, uint8_t *buf, size_t bufLen, size_t *outLen) {
|
||||
|
||||
if ((pkg == NULL) || (buf == NULL) || (outLen == NULL)) {
|
||||
return PM3_EINVARG;
|
||||
}
|
||||
|
||||
size_t n = strlen(pkg);
|
||||
if (n == 0) {
|
||||
return PM3_EINVARG;
|
||||
}
|
||||
|
||||
if (n > bufLen) {
|
||||
return PM3_EOVFLOW;
|
||||
}
|
||||
|
||||
memcpy(buf, pkg, n);
|
||||
|
||||
*outLen = n;
|
||||
return PM3_SUCCESS;
|
||||
}
|
||||
|
||||
int NDEFEncodeRecord(const NDEFRecordDesc_t *rec, bool mb, bool me, uint8_t *buf, size_t bufLen, size_t *outLen) {
|
||||
|
||||
if ((rec == NULL) || (buf == NULL) || (outLen == NULL)) {
|
||||
return PM3_EINVARG;
|
||||
}
|
||||
|
||||
if ((rec->typeLen > 255) || (rec->idLen > 255)) {
|
||||
return PM3_EINVARG;
|
||||
}
|
||||
|
||||
#if SIZE_MAX > 0xFFFFFFFF
|
||||
// the long form payload length field is only four bytes wide, a larger payload
|
||||
// cannot be expressed. unreachable where size_t is no wider than the field.
|
||||
if (rec->payloadLen > 0xFFFFFFFF) {
|
||||
return PM3_EINVARG;
|
||||
}
|
||||
#endif
|
||||
|
||||
if ((rec->typeLen && (rec->type == NULL)) ||
|
||||
(rec->idLen && (rec->id == NULL)) ||
|
||||
(rec->payloadLen && (rec->payload == NULL))) {
|
||||
return PM3_EINVARG;
|
||||
}
|
||||
|
||||
bool sr = (rec->payloadLen < 256);
|
||||
bool il = (rec->idLen > 0);
|
||||
|
||||
size_t head = 1 + 1 + (sr ? 1 : 4) + (il ? 1 : 0) + rec->typeLen + rec->idLen;
|
||||
|
||||
// keep the total from wrapping before it reaches the bufLen check below
|
||||
if (rec->payloadLen > SIZE_MAX - head) {
|
||||
return PM3_EINVARG;
|
||||
}
|
||||
|
||||
size_t n = head + rec->payloadLen;
|
||||
if (n > bufLen) {
|
||||
return PM3_EOVFLOW;
|
||||
}
|
||||
|
||||
size_t idx = 0;
|
||||
|
||||
// MB | ME | CF | SR | IL | TNF. chunked records are never emitted
|
||||
buf[idx] = (uint8_t)(rec->tnf & 0x07);
|
||||
if (mb) {
|
||||
buf[idx] |= 0x80;
|
||||
}
|
||||
if (me) {
|
||||
buf[idx] |= 0x40;
|
||||
}
|
||||
if (sr) {
|
||||
buf[idx] |= 0x10;
|
||||
}
|
||||
if (il) {
|
||||
buf[idx] |= 0x08;
|
||||
}
|
||||
idx++;
|
||||
|
||||
buf[idx++] = (uint8_t)rec->typeLen;
|
||||
|
||||
if (sr) {
|
||||
buf[idx++] = (uint8_t)rec->payloadLen;
|
||||
} else {
|
||||
buf[idx++] = (uint8_t)(rec->payloadLen >> 24);
|
||||
buf[idx++] = (uint8_t)(rec->payloadLen >> 16);
|
||||
buf[idx++] = (uint8_t)(rec->payloadLen >> 8);
|
||||
buf[idx++] = (uint8_t)(rec->payloadLen);
|
||||
}
|
||||
|
||||
if (il) {
|
||||
buf[idx++] = (uint8_t)rec->idLen;
|
||||
}
|
||||
|
||||
if (rec->typeLen) {
|
||||
memcpy(buf + idx, rec->type, rec->typeLen);
|
||||
idx += rec->typeLen;
|
||||
}
|
||||
|
||||
if (rec->idLen) {
|
||||
memcpy(buf + idx, rec->id, rec->idLen);
|
||||
idx += rec->idLen;
|
||||
}
|
||||
|
||||
if (rec->payloadLen) {
|
||||
memcpy(buf + idx, rec->payload, rec->payloadLen);
|
||||
idx += rec->payloadLen;
|
||||
}
|
||||
|
||||
*outLen = idx;
|
||||
return PM3_SUCCESS;
|
||||
}
|
||||
|
||||
int NDEFEncodeMessage(const NDEFRecordDesc_t *recs, size_t count, uint8_t *buf, size_t bufLen, size_t *outLen) {
|
||||
|
||||
if ((recs == NULL) || (buf == NULL) || (outLen == NULL) || (count == 0)) {
|
||||
return PM3_EINVARG;
|
||||
}
|
||||
|
||||
size_t idx = 0;
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
|
||||
size_t n = 0;
|
||||
int res = NDEFEncodeRecord(&recs[i], (i == 0), (i == (count - 1)), buf + idx, bufLen - idx, &n);
|
||||
if (res != PM3_SUCCESS) {
|
||||
return res;
|
||||
}
|
||||
idx += n;
|
||||
}
|
||||
|
||||
*outLen = idx;
|
||||
return PM3_SUCCESS;
|
||||
}
|
||||
|
||||
int NDEFEncodeTLV(const uint8_t *msg, size_t msgLen, uint8_t *buf, size_t bufLen, size_t *outLen) {
|
||||
|
||||
if ((msg == NULL) || (buf == NULL) || (outLen == NULL) || (msgLen == 0)) {
|
||||
return PM3_EINVARG;
|
||||
}
|
||||
|
||||
// 0xFFFF is reserved, and the terminator has to fit as well
|
||||
if (msgLen > 0xFFFE) {
|
||||
return PM3_EINVARG;
|
||||
}
|
||||
|
||||
// one byte length form up to 254, three byte form above that
|
||||
size_t hdr = (msgLen < 0xFF) ? 2 : 4;
|
||||
|
||||
size_t n = hdr + msgLen + 1;
|
||||
if (n > bufLen) {
|
||||
return PM3_EOVFLOW;
|
||||
}
|
||||
|
||||
size_t idx = 0;
|
||||
buf[idx++] = 0x03;
|
||||
|
||||
if (msgLen < 0xFF) {
|
||||
buf[idx++] = (uint8_t)msgLen;
|
||||
} else {
|
||||
buf[idx++] = 0xFF;
|
||||
buf[idx++] = (uint8_t)(msgLen >> 8);
|
||||
buf[idx++] = (uint8_t)(msgLen);
|
||||
}
|
||||
|
||||
memcpy(buf + idx, msg, msgLen);
|
||||
idx += msgLen;
|
||||
|
||||
buf[idx++] = 0xFE;
|
||||
|
||||
*outLen = idx;
|
||||
return PM3_SUCCESS;
|
||||
}
|
||||
@@ -77,4 +77,35 @@ typedef struct {
|
||||
int NDEFDecodeAndPrint(uint8_t *ndef, size_t ndefLen, bool verbose);
|
||||
int NDEFRecordsDecodeAndPrint(uint8_t *ndefRecord, size_t ndefRecordLen, bool verbose);
|
||||
int NDEFGetTotalLength(uint8_t *ndef, size_t ndeflen, size_t *outlen);
|
||||
|
||||
// NFC Forum well known types
|
||||
#define NDEF_TYPE_URI "U"
|
||||
#define NDEF_TYPE_TEXT "T"
|
||||
|
||||
// Android Application Record, TNF external
|
||||
#define NDEF_ANDROID_AAR "android.com:pkg"
|
||||
|
||||
// Describes one record to be encoded.
|
||||
// Field order intentionally matches gst_ndef_record_t in cmdhfgst.c so the two
|
||||
// can eventually be folded together.
|
||||
typedef struct {
|
||||
TypeNameFormat_t tnf;
|
||||
const uint8_t *type;
|
||||
size_t typeLen;
|
||||
const uint8_t *id;
|
||||
size_t idLen;
|
||||
const uint8_t *payload;
|
||||
size_t payloadLen;
|
||||
} NDEFRecordDesc_t;
|
||||
|
||||
// payload builders
|
||||
int NDEFEncodePayloadURI(const char *uri, uint8_t *buf, size_t bufLen, size_t *outLen);
|
||||
int NDEFEncodePayloadText(const char *text, const char *lang, uint8_t *buf, size_t bufLen, size_t *outLen);
|
||||
int NDEFEncodePayloadAAR(const char *pkg, uint8_t *buf, size_t bufLen, size_t *outLen);
|
||||
|
||||
// record / message / TLV assembly
|
||||
int NDEFEncodeRecord(const NDEFRecordDesc_t *rec, bool mb, bool me, uint8_t *buf, size_t bufLen, size_t *outLen);
|
||||
int NDEFEncodeMessage(const NDEFRecordDesc_t *recs, size_t count, uint8_t *buf, size_t bufLen, size_t *outLen);
|
||||
int NDEFEncodeTLV(const uint8_t *msg, size_t msgLen, uint8_t *buf, size_t bufLen, size_t *outLen);
|
||||
|
||||
#endif // _NDEF_H_
|
||||
Reference in new issue
Block a user