@@ -38,51 +38,160 @@ enum NdefHeaderType: UInt8 {
3838
3939public class Ndef {
4040
41- static let HEADER_SIZE = 7
41+ static let URL_HEADER_SIZE = 7
42+ static let TEXT_HEADER_SIZE = 9
4243
43- class func ndefDataForUrl( url: URL ) -> [ UInt8 ] {
44+ class func ndefDataForUrl( _ url: URL ) -> [ UInt8 ] {
4445
4546 // See pgs. 30-31 of AN12196
4647
48+ // NDEF File Format:
49+ //
50+ // Field | Length | Description
51+ // ---------------------------------------------------------------
52+ // NLEN | 2 bytes | Length of the NDEF message in big-endian format.
53+ // NDEF Message | NLEN bytes | NDEF message. See NFC Data Exchange Format (NDEF).
54+ //
55+ // https://docs.nordicsemi.com/bundle/ncs-latest/page/nrfxlib/nfc/doc/type_4_tag.html#t4t-format
56+ //
57+ var fileHeader : [ UInt8 ] = [
58+ 0x00 , // Placeholder for NLEN
59+ 0x00 , // Placeholder for NLEN
60+ ]
61+
62+ // NDEF Message header:
63+
4764 let flags : NdefHeaderFlags = [ . MB, . ME, . SR, . TNF_WELL_KNOWN]
4865 let type = NdefHeaderType . URL
4966
50- let header : [ UInt8 ] = [
51- 0x00 , // Placeholder for data size (two bytes MSB)
52- 0x00 ,
67+ var messageHeader : [ UInt8 ] = [
5368 flags. rawValue, // NDEF header flags
54- 0x01 , // Length of "type" field
55- 0x00 , // URL size placeholder
56- type. rawValue, // This will be a URL record
57- 0x00 // Just the URI (no prepended protocol)
69+ 0x01 , // Type length
70+ 0x00 , // URL length placeholder
71+ type. rawValue // Well-known type: URL
72+ ]
73+
74+ // Header for: Well-known-type(URL)
75+ //
76+ // Note: If you have a long URL that doesn't fit, you can change the typeHeader here.
77+ // For example, if you specify 0x02 for the typeHeader, it means:
78+ // - prepend `https://www.` to the URL content, saving a few bytes.
79+ //
80+ let typeHeader : [ UInt8 ] = [
81+ 0x00 // Just the URI (no prepended protocol)
5882 ]
5983
6084 let urlData = url. absoluteString. data ( using: . utf8) ?? Data ( )
6185 var urlBytes = Helper . bytesFromData ( data: urlData)
6286
63- let maxUrlSize = 255 - header. count
87+ let maxFileSize = 256 // As per NTAG 424 spcification for File #2
88+ let maxUrlSize = maxFileSize - ( fileHeader. count + messageHeader. count + typeHeader. count)
6489 if urlBytes. count > maxUrlSize {
6590 urlBytes = Array ( urlBytes [ 0 ..< maxUrlSize] )
6691 }
6792
68- var result : [ UInt8 ] = header + urlBytes
69- result [ 1 ] = UInt8 ( result. count - 2 ) // Length of everything that isn't the length
70- result [ 4 ] = UInt8 ( urlBytes. count + 1 ) // Everything after type field
93+ fileHeader [ 1 ] = UInt8 ( messageHeader. count + typeHeader. count + urlBytes. count)
94+ messageHeader [ 2 ] = UInt8 ( typeHeader. count + urlBytes. count)
95+
96+ let result : [ UInt8 ] = fileHeader + messageHeader + typeHeader + urlBytes
97+ return result
98+ }
99+
100+ class func ndefDataForText( _ text: String ) -> [ UInt8 ] {
101+
102+ // See pgs. 30-31 of AN12196
103+
104+ // NDEF File Format:
105+ //
106+ // Field | Length | Description
107+ // ---------------------------------------------------------------
108+ // NLEN | 2 bytes | Length of the NDEF message in big-endian format.
109+ // NDEF Message | NLEN bytes | NDEF message. See NFC Data Exchange Format (NDEF).
110+ //
111+ // https://docs.nordicsemi.com/bundle/ncs-latest/page/nrfxlib/nfc/doc/type_4_tag.html#t4t-format
112+ //
113+ var fileHeader : [ UInt8 ] = [
114+ 0x00 , // Placeholder for NLEN
115+ 0x00 // Placeholder for NLEN
116+ ]
117+
118+ // NDEF Message header:
119+
120+ let flags : NdefHeaderFlags = [ . MB, . ME, . SR, . TNF_WELL_KNOWN]
121+ let type = NdefHeaderType . TEXT
122+
123+ var messageHeader : [ UInt8 ] = [
124+ flags. rawValue, // NDEF header flags
125+ 0x01 , // Type length
126+ 0x00 , // Text length placeholder
127+ type. rawValue // Well-known type: TEXT
128+ ]
129+
130+ // Header for: Well-known-type(TEXT)
131+ //
132+ // RTD TEXT specification:
133+ //
134+ // Byte 0 bit pattern:
135+ //
136+ // | 7 | 6 | 5, 4, 3, 2, 1, 0 |
137+ // ----------------------------------------------
138+ // | UTF 8/16 | Reserved | Language code length |
139+ //
140+ // UTF-8 => 0
141+ // UTF-16 => 1
142+ //
143+ // Reserved => must be 0
144+ //
145+ // Language code should use ISO/IANA language code.
146+ // We will use "en" - although for our use case it will be ignored.
147+ //
148+ // Thus our bit pattern is:
149+ // 0b00000010 = 0x02
150+ //
151+ let typeHeader : [ UInt8 ] = [
152+ 0x02 , // UTF-8; langCode.length = 2
153+ 0x65 , // 'e'
154+ 0x6e // 'n'
155+ ]
156+
157+ let textData = text. data ( using: . utf8) ?? Data ( )
158+ var textBytes = Helper . bytesFromData ( data: textData)
159+
160+ let maxFileSize = 256 // As per NTAG 424 spcification for File #2
161+ let maxTextSize = maxFileSize - ( fileHeader. count + messageHeader. count + typeHeader. count)
162+ if textBytes. count > maxTextSize {
163+ textBytes = Array ( textBytes [ 0 ..< maxTextSize] )
164+ }
165+
166+ fileHeader [ 1 ] = UInt8 ( messageHeader. count + typeHeader. count + textBytes. count)
167+ messageHeader [ 2 ] = UInt8 ( typeHeader. count + textBytes. count)
71168
169+ let result : [ UInt8 ] = fileHeader + messageHeader + typeHeader + textBytes
72170 return result
73171 }
74172
173+ class func ndefDataForTemplate( _ template: Template ) -> [ UInt8 ] {
174+
175+ switch template. value {
176+ case . Left( let url) :
177+ return ndefDataForUrl ( url)
178+ case . Right( let text) :
179+ return ndefDataForText ( text)
180+ }
181+ }
182+
75183 struct Template {
76- let url : URL
184+ let value : Either < URL , String >
77185 let piccDataOffset : Int
78186 let cmacOffset : Int
79187
80- var urlString : String {
81- return url. absoluteString
82- }
83-
84- var urlData : Data {
85- return urlString. data ( using: . utf8) ?? Data ( )
188+ var valueString : String {
189+ switch value {
190+ case . Left( let url) :
191+ return url. absoluteString
192+ case . Right( let text) :
193+ return text
194+ }
86195 }
87196
88197 init ? ( baseUrl: URL ) {
@@ -93,7 +202,7 @@ public class Ndef {
93202
94203 var queryItems = comps. queryItems ?? [ ]
95204
96- // The `baseUrl` should NOT have either `picc_data` or `cmac` parameters.
205+ // The `baseUrl` SHOULD NOT have either `picc_data` or `cmac` parameters.
97206 // But just to be safe, we'll remove them if they're present.
98207 //
99208 queryItems. removeAll ( where: { item in
@@ -132,9 +241,33 @@ public class Ndef {
132241 }
133242 let offset2 = urlUtf8. distance ( from: urlUtf8. startIndex, to: range2. upperBound)
134243
135- self . url = resolvedUrl
136- self . piccDataOffset = offset1 + Ndef. HEADER_SIZE
137- self . cmacOffset = offset2 + Ndef. HEADER_SIZE
244+ self . value = Either . Left ( resolvedUrl)
245+ self . piccDataOffset = offset1 + Ndef. URL_HEADER_SIZE
246+ self . cmacOffset = offset2 + Ndef. URL_HEADER_SIZE
138247 }
139- }
248+
249+ init ( baseText: String ) {
250+
251+ // picc_data=(16_bytes_hexadecimal)
252+ // cmac=(8_bytes_hexadecimal)
253+
254+ let fullText = " \( baseText) ?picc_data=00000000000000000000000000000000&cmac=0000000000000000 "
255+ // +123456789 123456789 123456789 123456789 123456789
256+ // ^+11 ^+49
257+
258+ // Ultimately, the value gets encoded as UTF-8,
259+ // and the offsets are used as indexes within this UTF-8 representation.
260+ //
261+ // So we need to do our calculations within the string's utf8View.
262+
263+ let baseTextLength = baseText. utf8. count
264+ let offset1 = baseTextLength + 11
265+ let offset2 = baseTextLength + 49
266+
267+ self . value = Either . Right ( fullText)
268+ self . piccDataOffset = offset1 + Ndef. TEXT_HEADER_SIZE
269+ self . cmacOffset = offset2 + Ndef. TEXT_HEADER_SIZE
270+ }
271+
272+ } // </struct Template>
140273}
0 commit comments