iOS 11 - How does one read/parse the NDEF Message from CoreNFC?
Asked Answered
P

3

2

I have a bunch of tags that are URL tags that have the content "http://WEBSITE.com". Let's say WEBSITE is youtube so http://youtube.com. When I scan them on Android etc, it keeps the http or https.

I'm trying to scan these same tags using the Core NFC framework. I scan them and I get a bunch of bytes that I convert using NSSString initWithData with UTF8 Encoding. I get back \^Cyoutube.com. I want to get http://youtube.com.

How can I interept the payload to get what I need? If I'm to assume the http in front of the string, how am I supposed to know if it is http or https or even ftp?

Edit 1:

I'm having issues with the below answer code for pure text records. When making a text record for "hello world" I get the following outputs from the console:

2017-06-09 12:45:35.151806-0400 testNFC[2963:190724] Payload string:https://www.enhello world

2017-06-09 12:45:35.154959-0400 testNFC[2963:190724] Payload data:<02656e68 656c6c6f 20776f72 6c64>

To get the string I use

NSString *nfcMessage = [nfcType stringByAppendingString:[[[NSString alloc] initWithData:payload.payload encoding:NSUTF8StringEncoding] substringFromIndex:1]];

nfcType is the return from your function but for the None case I return @"";

I'm expecting to just get hello world.

Photolysis answered 8/6, 2017 at 20:40 Comment(0)
G
5

For this, you will first need to make sure you have properly formatted NDEF tags. You can use an Android phone or one of these reader accessories along with an NDEF writing app.

Implement the following methods:

- (NSString *)getType:(NSData *)NDEFData {

    NSString *firstByte = [self getFirstByte:NDEFData];

    if ([firstByte isEqualToString:@"00"]) {
        return @"None";
    } else if ([firstByte isEqualToString:@"01"]) {
        return @"http://www.";
    } else if ([firstByte isEqualToString:@"02"]) {
        return @"https://www.";
    } else if ([firstByte isEqualToString:@"03"]) {
        return @"http://";
    } else if ([firstByte isEqualToString:@"04"]) {
        return @"https://";
    } else if ([firstByte isEqualToString:@"05"]) {
        return @"tel:";
    } else if ([firstByte isEqualToString:@"06"]) {
        return @"mailto:";
    } else if ([firstByte isEqualToString:@"07"]) {
        return @"ftp://anonymous:anonymous@";
    } else if ([firstByte isEqualToString:@"08"]) {
        return @"ftp://ftp.";
    } else if ([firstByte isEqualToString:@"09"]) {
        return @"ftps://";
    } else if ([firstByte isEqualToString:@"0A"]) {
        return @"sftp://";
    } else if ([firstByte isEqualToString:@"0B"]) {
        return @"smb://";
    } else if ([firstByte isEqualToString:@"0C"]) {
        return @"nfs://";
    } else if ([firstByte isEqualToString:@"0D"]) {
        return @"ftp://";
    } else if ([firstByte isEqualToString:@"0E"]) {
        return @"dav://";
    } else if ([firstByte isEqualToString:@"0F"]) {
        return @"news:";
    } else if ([firstByte isEqualToString:@"10"]) {
        return @"telnet://";
    } else if ([firstByte isEqualToString:@"11"]) {
        return @"imap:";
    } else if ([firstByte isEqualToString:@"12"]) {
        return @"rtsp://";
    } else if ([firstByte isEqualToString:@"13"]) {
        return @"urn:";
    } else if ([firstByte isEqualToString:@"14"]) {
        return @"pop:";
    } else if ([firstByte isEqualToString:@"15"]) {
        return @"sip:";
    } else if ([firstByte isEqualToString:@"16"]) {
        return @"sips:";
    } else if ([firstByte isEqualToString:@"17"]) {
        return @"tftp:";
    } else if ([firstByte isEqualToString:@"18"]) {
        return @"btspp://";
    } else if ([firstByte isEqualToString:@"19"]) {
        return @"btl2cap://";
    } else if ([firstByte isEqualToString:@"1A"]) {
        return @"btgoep://";
    } else if ([firstByte isEqualToString:@"1B"]) {
        return @"tcpobex://";
    } else if ([firstByte isEqualToString:@"1C"]) {
        return @"irdaobex://";
    } else if ([firstByte isEqualToString:@"1D"]) {
        return @"file://";
    } else if ([firstByte isEqualToString:@"1E"]) {
        return @"urn:epc:id:";
    } else if ([firstByte isEqualToString:@"1F"]) {
        return @"urn:epc:tag:";
    } else if ([firstByte isEqualToString:@"20"]) {
        return @"urn:epc:pat:";
    } else if ([firstByte isEqualToString:@"21"]) {
        return @"urn:epc:raw:";
    } else if ([firstByte isEqualToString:@"22"]) {
        return @"urn:epc:";
    } else if ([firstByte isEqualToString:@"23"]) {
        return @"urn:nfc:";
    }

    return @"";

}

/*!
* gets the the NDEF content
*/
- (NSString *)getNDEFContent:(NSData *)data {
     NSString *dataString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
     return [dataString substringFromIndex:2];
}

/*!
 * gets the first byte of the input NSData
 */
- (NSString *)getFirstByte:(NSData *)data {
     return [[self dataToHexString:data] substringToIndex:2];
}

/*!
 * transforms NSData to NSString
 */
- (NSString *)dataToHexString:(NSData *)data;
{
    // get the length of the data
    NSUInteger bytesCount = data.length;
    if (bytesCount) {
        // string with all the Hex characters
        const char *hexChars = "0123456789ABCDEF";
        // put bytes into an array and initialize the response array
        const unsigned char *dataBuffer = data.bytes;
        char *chars = malloc(sizeof(char) * (bytesCount * 2 + 1));
        char *s = chars;
        // go through data bytes making the transformations so a hex will literally translate to a string, so for example 0x0A will translate to "0A"
        for (unsigned i = 0; i < bytesCount; ++i) {
            // get hexChars character at binary AND between the current byte and 0xF0 bitwise to the right by 4 index and assign it to the current chars pointer
            *s++ = hexChars[((*dataBuffer & 0xF0) >> 4)];
            // get hexChars character at binary AND between the current byte and 0x0F index and assign it to the current chars pointer
            *s++ = hexChars[(*dataBuffer & 0x0F)];
            dataBuffer++;
        }
        *s = '\0';
        // chars to string
        NSString *hexString = [NSString stringWithUTF8String:chars];
        free(chars);
        return hexString;
    }
    return @"";
}

And call the getType method:

[self getType:yourNDEFPayloadNSData]
  • I'm assuming that all the methods are in the same class and,

  • that the payload NSData is NDEF compliant, but I modeled the code based on the NFCNDEFPayload payload

Geum answered 8/6, 2017 at 22:21 Comment(11)
Two questions. 1. Is that every possible beginning extension possible? Where did you get the list associating the numbers with the extensions (ie 0C as nfs://)? 2. Am I correct in assuming that after I get the type from the function you gave, that I can get the contents of the payload by turning into a string and getting everything after index 2? I'm confused on if your function is just for the beginning or if it transforms the NSData completely into the final output, including the extension.Photolysis
1. Yes, it's every possible beginning extension, the rest are reserved. The list comes from the NFC Forum URI Record Type Definition Technical Specification (NFCForum-TS-RTD_URI_1.0 2006-07-24 ) archive.eet-china.com/www.eet-china.com/ARTICLES/2006AUG/PDF/…Geum
2. The code only returns the protocol string, it would need another function to get the rest of the data, and as you say you'd probably have to use substringFromIndex, I'll update the code.Geum
Thanks for that. Everything seems to be working. One final question, do you know how to determine if the device supports NFC? I don't want to display the NFC button on iPhone 6 and 6S devices.Photolysis
There's an NFCError type for this scenario is called NFCReaderErrorUnsupportedFeature but I haven't been able to get it, so my best guess right now is to try to create a session and wait for an error in the delegate.Geum
Sorry for constantly asking questions but I have two more regarding this answer. I made a text record in an Android app NFC Tools and put the text "hello world". When I scanned it using the above I got enhello world. Why is that? Also for your function dataToHexString, do you mind adding some comments explaining what you are doing? I like adding comments so I can understand what is going on but I'm having trouble understanding exactly what you are doing, especially in the for loop.Photolysis
It's hard for me to know why that's happening without seeing the data bytes of the payload, there are many reasons that could be happening.Geum
Thanks for the added comments to the code. I've edited the question to give the payload and NSString representation.Photolysis
This is not the most complete answer, because it does not deal with all the other NFC types. @Vince Yuan answer should be the accepted answer as it deals with all the common NFC types. It's also a lot simpler.Interoffice
Hey @Boris, thanks for your thorough answer, it really helped me out even though I'm in Android and not iOS. To you or anyone else reading, is there a more rigid way of figuring out what the first byte should return? Even if your code covers 100% of the options given the standard, it seems sketchy to not use some built in way to compare.Dingbat
@Photolysis "en" is the language code. There are many responses to that issue here on SO. Though this response is 18 months later so I assume you figured that out by now.Dingbat
S
3

NFC NDEF message payload is much more complex than you expected. But CoreNFC does not support parsing NFC NDEF message payload. I created an open source parser VYNFCKit to parse payload. Example projects are available in both Objective-C and Swift. Check my tutorial https://medium.com/@vinceyuan/reading-and-parsing-nfc-tag-on-ios-11-60f4bc7a11ea

Saree answered 4/9, 2017 at 3:41 Comment(2)
I tried this out and it works very well so far. A real life saver!Photolysis
I've been looking for a complete solution. I have been struggling to even find a complete spec. Should be the accepted answer.Interoffice
C
0

Regarding your EDIT1: You are using the wrong Record Type. You need to write a "Text Record", not a "URI Record". If you have an Android Phone at hand, you could use tools like "NFC TagWriter by NFC" to write the correct record. It might not be important for your use case, but think of interopability with Android Phones, resp. other apps. They would try to open "https://www.enhello world" instead of showing "Hello World", using EN encoding as a Text String.

Colombo answered 11/6, 2017 at 6:18 Comment(3)
I used on an Android phone the app NFC Tools and did the text record option. I see in the app "Record 0 - UTF-8 (en) : text/plain hello world That is the one that is giving me the https etc. I believe it's formatted right but I'm not 100%.Photolysis
You need to differentiate between the "type" reported above (which is the type of the URI record) and the Record Type itself (typeNameFormat in CoreNFC). This can be either one of the following enum: case absoluteURI, case empty case media case nfcExternal case nfcWellKnown case unchanged case unknownColombo
So you are thinking that CoreNFC will report back something other than absoluteURI for the typeNameFormat?Photolysis

© 2022 - 2024 — McMap. All rights reserved.