blob: b4c488b7c0f8a52753302c473e9b5d647317af38 [file] [log] [blame]
Nick Pellydc993792010-10-04 11:17:25 -07001/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.nfc;
18
Nick Pellye0180d02011-06-07 17:27:44 -070019import android.net.Uri;
Nick Pellydc993792010-10-04 11:17:25 -070020import android.os.Parcel;
21import android.os.Parcelable;
Nick Pellya356bf12011-12-13 15:36:31 -080022import java.nio.BufferUnderflowException;
23import java.nio.ByteBuffer;
Nick Pellye0180d02011-06-07 17:27:44 -070024import java.nio.charset.Charsets;
Nick Pellya356bf12011-12-13 15:36:31 -080025import java.util.ArrayList;
Nick Pellye0180d02011-06-07 17:27:44 -070026import java.util.Arrays;
Nick Pellya356bf12011-12-13 15:36:31 -080027import java.util.List;
Nick Pellydc993792010-10-04 11:17:25 -070028
29/**
Nick Pellya356bf12011-12-13 15:36:31 -080030 * Represents an immutable NDEF Record.
31 * <p>
32 * NDEF (NFC Data Exchange Format) is a light-weight binary format,
33 * used to encapsulate typed data. It is specified by the NFC Forum,
34 * for transmission and storage with NFC, however it is transport agnostic.
35 * <p>
36 * NDEF defines messages and records. An NDEF Record contains
37 * typed data, such as MIME-type media, a URI, or a custom
38 * application payload. An NDEF Message is a container for
39 * one or more NDEF Records.
40 * <p>
41 * This class represents logical (complete) NDEF Records, and can not be
42 * used to represent chunked (partial) NDEF Records. However
43 * {@link NdefMessage#NdefMessage(byte[])} can be used to parse a message
44 * containing chunked records, and will return a message with unchunked
45 * (complete) records.
46 * <p>
47 * A logical NDEF Record always contains a 3-bit TNF (Type Name Field)
48 * that provides high level typing for the rest of the record. The
49 * remaining fields are variable length and not always present:
Nick Pellydc993792010-10-04 11:17:25 -070050 * <ul>
Nick Pellya356bf12011-12-13 15:36:31 -080051 * <li><em>type</em>: detailed typing for the payload</li>
52 * <li><em>id</em>: identifier meta-data, not commonly used</li>
53 * <li><em>payload</em>: the actual payload</li>
Nick Pellydc993792010-10-04 11:17:25 -070054 * </ul>
Nick Pellya356bf12011-12-13 15:36:31 -080055 * <p>
56 * Helpers such as {@link NdefRecord#createUri}, {@link NdefRecord#createMime}
57 * and {@link NdefRecord#createExternal} are included to create well-formatted
58 * NDEF Records with correctly set tnf, type, id and payload fields, please
59 * use these helpers whenever possible.
60 * <p>
61 * Use the constructor {@link #NdefRecord(short, byte[], byte[], byte[])}
62 * if you know what you are doing and what to set the fields individually.
63 * Only basic validation is performed with this constructor, so it is possible
64 * to create records that do not confirm to the strict NFC Forum
65 * specifications.
66 * <p>
67 * The binary representation of an NDEF Record includes additional flags to
68 * indicate location with an NDEF message, provide support for chunking of
69 * NDEF records, and to pack optional fields. This class does not expose
70 * those details. To write an NDEF Record as binary you must first put it
71 * into an @{link NdefMessage}, then call {@link NdefMessage#toByteArray()}.
72 * <p class="note">
73 * {@link NdefMessage} and {@link NdefRecord} implementations are
74 * always available, even on Android devices that do not have NFC hardware.
75 * <p class="note">
76 * {@link NdefRecord}s are intended to be immutable (and thread-safe),
77 * however they may contain mutable fields. So take care not to modify
78 * mutable fields passed into constructors, or modify mutable fields
79 * obtained by getter methods, unless such modification is explicitly
80 * marked as safe.
81 *
82 * @see NfcAdapter#ACTION_NDEF_DISCOVERED
83 * @see NdefMessage
Nick Pellydc993792010-10-04 11:17:25 -070084 */
Nick Pelly11b075e2010-10-28 13:39:37 -070085public final class NdefRecord implements Parcelable {
Nick Pellydc993792010-10-04 11:17:25 -070086 /**
Nick Pellya356bf12011-12-13 15:36:31 -080087 * Indicates the record is empty.<p>
88 * Type, id and payload fields are empty in a {@literal TNF_EMPTY} record.
Nick Pellydc993792010-10-04 11:17:25 -070089 */
90 public static final short TNF_EMPTY = 0x00;
91
92 /**
Nick Pellya356bf12011-12-13 15:36:31 -080093 * Indicates the type field contains a well-known RTD type name.<p>
94 * Use this tnf with RTD types such as {@link #RTD_TEXT}, {@link #RTD_URI}.
Nick Pellydc993792010-10-04 11:17:25 -070095 * <p>
Nick Pellya356bf12011-12-13 15:36:31 -080096 * The RTD type name format is specified in NFCForum-TS-RTD_1.0.
97 *
98 * @see #RTD_URI
99 * @see #RTD_TEXT
100 * @see #RTD_SMART_POSTER
101 * @see #createUri
Nick Pellydc993792010-10-04 11:17:25 -0700102 */
103 public static final short TNF_WELL_KNOWN = 0x01;
104
105 /**
Nick Pellya356bf12011-12-13 15:36:31 -0800106 * Indicates the type field contains a media-type BNF
107 * construct, defined by RFC 2046.<p>
108 * Use this with MIME type names such as {@literal "image/jpeg"}, or
109 * using the helper {@link #createMime}.
110 *
111 * @see #createMime
Nick Pellydc993792010-10-04 11:17:25 -0700112 */
113 public static final short TNF_MIME_MEDIA = 0x02;
114
115 /**
Nick Pellya356bf12011-12-13 15:36:31 -0800116 * Indicates the type field contains an absolute-URI
117 * BNF construct defined by RFC 3986.<p>
118 * When creating new records prefer {@link #createUri},
119 * since it offers more compact URI encoding
120 * ({@literal #RTD_URI} allows compression of common URI prefixes).
121 *
122 * @see #createUri
Nick Pellydc993792010-10-04 11:17:25 -0700123 */
124 public static final short TNF_ABSOLUTE_URI = 0x03;
125
126 /**
Nick Pellya356bf12011-12-13 15:36:31 -0800127 * Indicates the type field contains an external type name.<p>
128 * Used to encode custom payloads. When creating new records
129 * use the helper {@link #createExternal}.<p>
130 * The external-type RTD format is specified in NFCForum-TS-RTD_1.0.<p>
Nick Pellydc993792010-10-04 11:17:25 -0700131 * <p>
132 * Note this TNF should not be used with RTD_TEXT or RTD_URI constants.
133 * Those are well known RTD constants, not external RTD constants.
Nick Pellya356bf12011-12-13 15:36:31 -0800134 *
135 * @see #createExternal
Nick Pellydc993792010-10-04 11:17:25 -0700136 */
137 public static final short TNF_EXTERNAL_TYPE = 0x04;
138
139 /**
Nick Pellya356bf12011-12-13 15:36:31 -0800140 * Indicates the payload type is unknown.<p>
141 * NFC Forum explains this should be treated similarly to the
142 * "application/octet-stream" MIME type. The payload
143 * type is not explicitly encoded within the record.
Nick Pellydc993792010-10-04 11:17:25 -0700144 * <p>
Nick Pellya356bf12011-12-13 15:36:31 -0800145 * The type field is empty in an {@literal TNF_UNKNOWN} record.
Nick Pellydc993792010-10-04 11:17:25 -0700146 */
147 public static final short TNF_UNKNOWN = 0x05;
148
149 /**
150 * Indicates the payload is an intermediate or final chunk of a chunked
Nick Pellya356bf12011-12-13 15:36:31 -0800151 * NDEF Record.<p>
152 * {@literal TNF_UNCHANGED} can not be used with this class
153 * since all {@link NdefRecord}s are already unchunked, however they
154 * may appear in the binary format.
Nick Pellydc993792010-10-04 11:17:25 -0700155 */
156 public static final short TNF_UNCHANGED = 0x06;
157
158 /**
159 * Reserved TNF type.
160 * <p>
161 * The NFC Forum NDEF Specification v1.0 suggests for NDEF parsers to treat this
162 * value like TNF_UNKNOWN.
163 * @hide
164 */
165 public static final short TNF_RESERVED = 0x07;
166
167 /**
Nick Pellya356bf12011-12-13 15:36:31 -0800168 * RTD Text type. For use with {@literal TNF_WELL_KNOWN}.
169 * @see #TNF_WELL_KNOWN
Nick Pellydc993792010-10-04 11:17:25 -0700170 */
171 public static final byte[] RTD_TEXT = {0x54}; // "T"
172
173 /**
Nick Pellya356bf12011-12-13 15:36:31 -0800174 * RTD URI type. For use with {@literal TNF_WELL_KNOWN}.
175 * @see #TNF_WELL_KNOWN
Nick Pellydc993792010-10-04 11:17:25 -0700176 */
177 public static final byte[] RTD_URI = {0x55}; // "U"
178
179 /**
Nick Pellya356bf12011-12-13 15:36:31 -0800180 * RTD Smart Poster type. For use with {@literal TNF_WELL_KNOWN}.
181 * @see #TNF_WELL_KNOWN
Nick Pellydc993792010-10-04 11:17:25 -0700182 */
183 public static final byte[] RTD_SMART_POSTER = {0x53, 0x70}; // "Sp"
184
185 /**
Nick Pellya356bf12011-12-13 15:36:31 -0800186 * RTD Alternative Carrier type. For use with {@literal TNF_WELL_KNOWN}.
187 * @see #TNF_WELL_KNOWN
Nick Pellydc993792010-10-04 11:17:25 -0700188 */
189 public static final byte[] RTD_ALTERNATIVE_CARRIER = {0x61, 0x63}; // "ac"
190
191 /**
Nick Pellya356bf12011-12-13 15:36:31 -0800192 * RTD Handover Carrier type. For use with {@literal TNF_WELL_KNOWN}.
193 * @see #TNF_WELL_KNOWN
Nick Pellydc993792010-10-04 11:17:25 -0700194 */
195 public static final byte[] RTD_HANDOVER_CARRIER = {0x48, 0x63}; // "Hc"
196
197 /**
Nick Pellya356bf12011-12-13 15:36:31 -0800198 * RTD Handover Request type. For use with {@literal TNF_WELL_KNOWN}.
199 * @see #TNF_WELL_KNOWN
Nick Pellydc993792010-10-04 11:17:25 -0700200 */
201 public static final byte[] RTD_HANDOVER_REQUEST = {0x48, 0x72}; // "Hr"
202
203 /**
Nick Pellya356bf12011-12-13 15:36:31 -0800204 * RTD Handover Select type. For use with {@literal TNF_WELL_KNOWN}.
205 * @see #TNF_WELL_KNOWN
Nick Pellydc993792010-10-04 11:17:25 -0700206 */
207 public static final byte[] RTD_HANDOVER_SELECT = {0x48, 0x73}; // "Hs"
208
Martijn Coenena37fcbc2011-08-05 09:56:17 -0700209 /**
Nick Pellya356bf12011-12-13 15:36:31 -0800210 * RTD Android app type. For use with {@literal TNF_EXTERNAL}.
Martijn Coenena37fcbc2011-08-05 09:56:17 -0700211 * <p>
212 * The payload of a record with type RTD_ANDROID_APP
213 * should be the package name identifying an application.
214 * Multiple RTD_ANDROID_APP records may be included
215 * in a single {@link NdefMessage}.
216 * <p>
217 * Use {@link #createApplicationRecord(String)} to create
218 * RTD_ANDROID_APP records.
219 * @hide
220 */
Martijn Coenena37fcbc2011-08-05 09:56:17 -0700221 public static final byte[] RTD_ANDROID_APP = "android.com:pkg".getBytes();
222
Nick Pelly590b73b2010-10-12 13:00:50 -0700223 private static final byte FLAG_MB = (byte) 0x80;
224 private static final byte FLAG_ME = (byte) 0x40;
225 private static final byte FLAG_CF = (byte) 0x20;
226 private static final byte FLAG_SR = (byte) 0x10;
227 private static final byte FLAG_IL = (byte) 0x08;
228
Nick Pellye0180d02011-06-07 17:27:44 -0700229 /**
Nick Pellya356bf12011-12-13 15:36:31 -0800230 * NFC Forum "URI Record Type Definition"<p>
Nick Pellye0180d02011-06-07 17:27:44 -0700231 * This is a mapping of "URI Identifier Codes" to URI string prefixes,
232 * per section 3.2.2 of the NFC Forum URI Record Type Definition document.
233 */
234 private static final String[] URI_PREFIX_MAP = new String[] {
235 "", // 0x00
236 "http://www.", // 0x01
237 "https://www.", // 0x02
238 "http://", // 0x03
239 "https://", // 0x04
240 "tel:", // 0x05
241 "mailto:", // 0x06
242 "ftp://anonymous:anonymous@", // 0x07
243 "ftp://ftp.", // 0x08
244 "ftps://", // 0x09
245 "sftp://", // 0x0A
246 "smb://", // 0x0B
247 "nfs://", // 0x0C
248 "ftp://", // 0x0D
249 "dav://", // 0x0E
250 "news:", // 0x0F
251 "telnet://", // 0x10
252 "imap:", // 0x11
253 "rtsp://", // 0x12
254 "urn:", // 0x13
255 "pop:", // 0x14
256 "sip:", // 0x15
257 "sips:", // 0x16
258 "tftp:", // 0x17
259 "btspp://", // 0x18
260 "btl2cap://", // 0x19
261 "btgoep://", // 0x1A
262 "tcpobex://", // 0x1B
263 "irdaobex://", // 0x1C
264 "file://", // 0x1D
265 "urn:epc:id:", // 0x1E
266 "urn:epc:tag:", // 0x1F
267 "urn:epc:pat:", // 0x20
268 "urn:epc:raw:", // 0x21
269 "urn:epc:", // 0x22
270 };
271
Nick Pellya356bf12011-12-13 15:36:31 -0800272 private static final int MAX_PAYLOAD_SIZE = 10 * (1 << 20); // 10 MB payload limit
273
274 private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
275
Nick Pelly590b73b2010-10-12 13:00:50 -0700276 private final short mTnf;
277 private final byte[] mType;
278 private final byte[] mId;
279 private final byte[] mPayload;
280
Nick Pellydc993792010-10-04 11:17:25 -0700281 /**
Nick Pellya356bf12011-12-13 15:36:31 -0800282 * Create a new Android Application Record (AAR).
Nick Pellydc993792010-10-04 11:17:25 -0700283 * <p>
Nick Pellya356bf12011-12-13 15:36:31 -0800284 * This record indicates to other Android devices the package
285 * that should be used to handle the entire NDEF message.
286 * You can embed this record anywhere into your message
287 * to ensure that the intended package receives the message.
288 * <p>
289 * When an Android device dispatches an {@link NdefMessage}
290 * containing one or more Android application records,
291 * the applications contained in those records will be the
292 * preferred target for the {@link NfcAdapter#ACTION_NDEF_DISCOVERED}
293 * intent, in the order in which they appear in the message.
294 * This dispatch behavior was first added to Android in
295 * Ice Cream Sandwich.
296 * <p>
297 * If none of the applications have a are installed on the device,
298 * a Market link will be opened to the first application.
299 * <p>
300 * Note that Android application records do not overrule
301 * applications that have called
302 * {@link NfcAdapter#enableForegroundDispatch}.
303 *
304 * @param packageName Android package name
305 * @return Android application NDEF record
306 */
307 public static NdefRecord createApplicationRecord(String packageName) {
308 if (packageName.length() == 0) {
309 throw new IllegalArgumentException("empty package name");
310 }
311 return new NdefRecord(TNF_EXTERNAL_TYPE, RTD_ANDROID_APP, null,
312 packageName.getBytes(Charsets.UTF_8));
313 }
314
315 /**
316 * Create a new NDEF Record containing a URI.<p>
317 * Use this method to encode a URI (or URL) into an NDEF Record.<p>
318 * Uses the well known URI type representation: {@link #TNF_WELL_KNOWN}
319 * and {@link #RTD_URI}. This is the most efficient encoding
320 * of a URI into NDEF.<p>
321 * Reference specification: NFCForum-TS-RTD_URI_1.0
322 *
323 * @param uri URI to encode.
324 * @return an NDEF Record containing the URI
325 * @throws IllegalArugmentException if a valid record cannot be created
326 */
327 public static NdefRecord createUri(Uri uri) {
328 return createUri(uri.toString());
329 }
330
331 /**
332 * Create a new NDEF Record containing a URI.<p>
333 * Use this method to encode a URI (or URL) into an NDEF Record.<p>
334 * Uses the well known URI type representation: {@link #TNF_WELL_KNOWN}
335 * and {@link #RTD_URI}. This is the most efficient encoding
336 * of a URI into NDEF.<p>
337 * Reference specification: NFCForum-TS-RTD_URI_1.0
338 *
339 * @param uriString string URI to encode.
340 * @return an NDEF Record containing the URI
341 * @throws IllegalArugmentException if a valid record cannot be created
342 */
343 public static NdefRecord createUri(String uriString) {
344 if (uriString.length() == 0) {
345 throw new IllegalArgumentException("empty uriString");
346 }
347
348 byte prefix = 0;
349 for (int i = 1; i < URI_PREFIX_MAP.length; i++) {
350 if (uriString.startsWith(URI_PREFIX_MAP[i])) {
351 prefix = (byte) i;
352 uriString = uriString.substring(URI_PREFIX_MAP[i].length());
353 break;
354 }
355 }
356 byte[] uriBytes = uriString.getBytes(Charsets.UTF_8);
357 byte[] recordBytes = new byte[uriBytes.length + 1];
358 recordBytes[0] = prefix;
359 System.arraycopy(uriBytes, 0, recordBytes, 1, uriBytes.length);
360 return new NdefRecord(TNF_WELL_KNOWN, RTD_URI, null, recordBytes);
361 }
362
363 /**
364 * Create a new NDEF Record containing MIME data.<p>
365 * Use this method to encode MIME-typed data into an NDEF Record,
366 * such as "text/plain", or "image/jpeg".<p>
367 * Expects US-ASCII characters in mimeType. The encoding of the
368 * mimeData depends on the mimeType.<p>
369 * For efficiency, This method might not make an internal copy of the
370 * mimeData byte array, so take care not
371 * to re-use the mimeData byte array while still using the returned
372 * NdefRecord.
373 *
374 * @param mimeType MIME type, expects US-ASCII characters only
375 * @param mimeData MIME data as bytes
376 * @return an NDEF Record containing the MIME-typed data
377 * @throws IllegalArugmentException if a valid record cannot be created
378 */
379 public static NdefRecord createMime(String mimeType, byte[] mimeData) {
380 if (mimeType.length() == 0) {
381 throw new IllegalArgumentException("empty mimeType");
382 }
383
384 return new NdefRecord(TNF_MIME_MEDIA, mimeType.getBytes(Charsets.US_ASCII), null,
385 mimeData);
386 }
387
388 /**
389 * Create a new NDEF Record containing external (application-specific) data.<p>
390 * Use this method to encode application specific data into an NDEF Record.
391 * The data is typed by a domain name (usually your Android package name) and
392 * a domain-specific type. This data is packaged into a "NFC Forum External
393 * Type" NDEF Record.<p>
394 * Both the domain and type used to construct an external record are case
395 * insensitive, and this implementation will encode all characters to lower
396 * case. Only a subset of ASCII characters are allowed for the domain
397 * and type. There are no restrictions on the payload data.<p>
398 * For efficiency, This method might not make an internal copy of the
399 * data byte array, so take care not
400 * to re-use the data byte array while still using the returned
401 * NdefRecord.
402 *
403 * Reference specification: NFCForum-TS-RTD_1.0
404 * @param domain domain-name of issuing organization
405 * @param type domain-specific type of data
406 * @param data payload as bytes
407 * @throws IllegalArugmentException if a valid record cannot be created
408 */
409 public static NdefRecord createExternal(String domain, String type, byte[] data) {
410 if (domain.length() == 0 || type.length() == 0) {
411 throw new IllegalArgumentException("empty domain or type");
412 }
413 byte[] byteDomain = domain.getBytes(Charsets.US_ASCII);
414 ensureValidDomain(byteDomain);
415 toLowerCase(byteDomain);
416 byte[] byteType = type.getBytes(Charsets.US_ASCII);
417 ensureValidWkt(byteType);
418 toLowerCase(byteType);
419
420 byte[] b = new byte[byteDomain.length + 1 + byteType.length];
421 System.arraycopy(byteDomain, 0, b, 0, byteDomain.length);
422 b[byteDomain.length] = ':';
423 System.arraycopy(byteType, 0, b, byteDomain.length + 1, byteType.length);
424
425 return new NdefRecord(TNF_EXTERNAL_TYPE, b, null, data);
426 }
427
428 /**
429 * Construct an NDEF Record from its component fields.<p>
430 * Recommend to use helpers such as {#createUri} or
431 * {{@link #createExternal} where possible, since they perform
432 * stricter validation that the record is correctly formatted
433 * as per NDEF specifications. However if you know what you are
434 * doing then this constructor offers the most flexibility.<p>
435 * An {@link NdefRecord} represents a logical (complete)
436 * record, and cannot represent NDEF Record chunks.<p>
437 * Basic validation of the tnf, type, id and payload is performed
438 * as per the following rules:
439 * <ul>
440 * <li>The tnf paramter must be a 3-bit value.</li>
441 * <li>Records with a tnf of {@link #TNF_EMPTY} cannot have a type,
442 * id or payload.</li>
443 * <li>Records with a tnf of {@link #TNF_UNKNOWN} or {@literal 0x07}
444 * cannot have a type.</li>
445 * <li>Records with a tnf of {@link #TNF_UNCHANGED} are not allowed
446 * since this class only represents complete (unchunked) records.</li>
447 * </ul>
448 * This minimal validation is specified by
449 * NFCForum-TS-NDEF_1.0 section 3.2.6 (Type Name Format).<p>
450 * If any of the above validation
451 * steps fail then {@link IllegalArgumentException} is thrown.<p>
452 * Deep inspection of the type, id and payload fields is not
453 * performed, so it is possible to create NDEF Records
454 * that conform to section 3.2.6
455 * but fail other more strict NDEF specification requirements. For
456 * example, the payload may be invalid given the tnf and type.
457 * <p>
458 * To omit a type, id or payload field, set the parameter to an
459 * empty byte array or null.
Nick Pellydc993792010-10-04 11:17:25 -0700460 *
461 * @param tnf a 3-bit TNF constant
Nick Pellya356bf12011-12-13 15:36:31 -0800462 * @param type byte array, containing zero to 255 bytes, or null
463 * @param id byte array, containing zero to 255 bytes, or null
Nick Pellydc993792010-10-04 11:17:25 -0700464 * @param payload byte array, containing zero to (2 ** 32 - 1) bytes,
Nick Pellya356bf12011-12-13 15:36:31 -0800465 * or null
466 * @throws IllegalArugmentException if a valid record cannot be created
Nick Pellydc993792010-10-04 11:17:25 -0700467 */
468 public NdefRecord(short tnf, byte[] type, byte[] id, byte[] payload) {
Nick Pellya356bf12011-12-13 15:36:31 -0800469 /* convert nulls */
470 if (type == null) type = EMPTY_BYTE_ARRAY;
471 if (id == null) id = EMPTY_BYTE_ARRAY;
472 if (payload == null) payload = EMPTY_BYTE_ARRAY;
Martijn Coenen8bede172011-04-08 16:42:22 +0200473
Nick Pellya356bf12011-12-13 15:36:31 -0800474 String message = validateTnf(tnf, type, id, payload);
475 if (message != null) {
476 throw new IllegalArgumentException(message);
Nick Pelly590b73b2010-10-12 13:00:50 -0700477 }
478
Nick Pelly590b73b2010-10-12 13:00:50 -0700479 mTnf = tnf;
Nick Pellya356bf12011-12-13 15:36:31 -0800480 mType = type;
481 mId = id;
482 mPayload = payload;
Nick Pellydc993792010-10-04 11:17:25 -0700483 }
484
485 /**
Nick Pellya356bf12011-12-13 15:36:31 -0800486 * Construct an NDEF Record from raw bytes.<p>
487 * This method is deprecated, use {@link NdefMessage#NdefMessage(byte[])}
488 * instead. This is because it does not make sense to parse a record:
489 * the NDEF binary format is only defined for a message, and the
490 * record flags MB and ME do not make sense outside of the context of
491 * an entire message.<p>
492 * This implementation will attempt to parse a single record by ignoring
493 * the MB and ME flags, and otherwise following the rules of
494 * {@link NdefMessage#NdefMessage(byte[])}.<p>
Nick Pellydc993792010-10-04 11:17:25 -0700495 *
Nick Pellya356bf12011-12-13 15:36:31 -0800496 * @param data raw bytes to parse
497 * @throws FormatException if the data cannot be parsed into a valid record
498 * @deprecated use {@link NdefMessage#NdefMessage(byte[])} instead.
Nick Pellydc993792010-10-04 11:17:25 -0700499 */
Nick Pellya356bf12011-12-13 15:36:31 -0800500 @Deprecated
Sylvain Fonteneaudd7341f2010-10-17 15:32:33 -0700501 public NdefRecord(byte[] data) throws FormatException {
Nick Pellya356bf12011-12-13 15:36:31 -0800502 ByteBuffer buffer = ByteBuffer.wrap(data);
503 NdefRecord[] rs = parse(buffer, true);
504
505 if (buffer.remaining() > 0) {
506 throw new FormatException("data too long");
Sylvain Fonteneaudd7341f2010-10-17 15:32:33 -0700507 }
Nick Pellya356bf12011-12-13 15:36:31 -0800508
509 mTnf = rs[0].mTnf;
510 mType = rs[0].mType;
511 mId = rs[0].mId;
512 mPayload = rs[0].mPayload;
Nick Pellydc993792010-10-04 11:17:25 -0700513 }
514
515 /**
516 * Returns the 3-bit TNF.
517 * <p>
518 * TNF is the top-level type.
519 */
520 public short getTnf() {
Nick Pelly590b73b2010-10-12 13:00:50 -0700521 return mTnf;
Nick Pellydc993792010-10-04 11:17:25 -0700522 }
523
524 /**
525 * Returns the variable length Type field.
526 * <p>
527 * This should be used in conjunction with the TNF field to determine the
528 * payload format.
Nick Pellya356bf12011-12-13 15:36:31 -0800529 * <p>
530 * Returns an empty byte array if this record
531 * does not have a type field.
Nick Pellydc993792010-10-04 11:17:25 -0700532 */
533 public byte[] getType() {
Nick Pelly590b73b2010-10-12 13:00:50 -0700534 return mType.clone();
Nick Pellydc993792010-10-04 11:17:25 -0700535 }
536
537 /**
538 * Returns the variable length ID.
Nick Pellya356bf12011-12-13 15:36:31 -0800539 * <p>
540 * Returns an empty byte array if this record
541 * does not have an id field.
Nick Pellydc993792010-10-04 11:17:25 -0700542 */
543 public byte[] getId() {
Nick Pelly590b73b2010-10-12 13:00:50 -0700544 return mId.clone();
Nick Pellydc993792010-10-04 11:17:25 -0700545 }
546
547 /**
548 * Returns the variable length payload.
Nick Pellya356bf12011-12-13 15:36:31 -0800549 * <p>
550 * Returns an empty byte array if this record
551 * does not have a payload field.
Nick Pellydc993792010-10-04 11:17:25 -0700552 */
553 public byte[] getPayload() {
Nick Pelly590b73b2010-10-12 13:00:50 -0700554 return mPayload.clone();
Nick Pellydc993792010-10-04 11:17:25 -0700555 }
556
557 /**
Nick Pellya356bf12011-12-13 15:36:31 -0800558 * Return this NDEF Record as a byte array.<p>
559 * This method is deprecated, use {@link NdefMessage#toByteArray}
560 * instead. This is because the NDEF binary format is not defined for
561 * a record outside of the context of a message: the MB and ME flags
562 * cannot be set without knowing the location inside a message.<p>
563 * This implementation will attempt to serialize a single record by
564 * always setting the MB and ME flags (in other words, assume this
565 * is a single-record NDEF Message).<p>
566 *
567 * @deprecated use {@link NdefMessage#toByteArray()} instead
568 */
569 @Deprecated
570 public byte[] toByteArray() {
571 ByteBuffer buffer = ByteBuffer.allocate(getByteLength());
572 writeToByteBuffer(buffer, true, true);
573 return buffer.array();
574 }
575
576 /**
Nick Pellye0180d02011-06-07 17:27:44 -0700577 * Helper to return the NdefRecord as a URI.
578 * TODO: Consider making a member method instead of static
579 * TODO: Consider more validation that this is a URI record
580 * TODO: Make a public API
581 * @hide
582 */
583 public static Uri parseWellKnownUriRecord(NdefRecord record) throws FormatException {
584 byte[] payload = record.getPayload();
585 if (payload.length < 2) {
586 throw new FormatException("Payload is not a valid URI (missing prefix)");
587 }
588
589 /*
590 * payload[0] contains the URI Identifier Code, per the
591 * NFC Forum "URI Record Type Definition" section 3.2.2.
592 *
593 * payload[1]...payload[payload.length - 1] contains the rest of
594 * the URI.
595 */
596 int prefixIndex = (payload[0] & 0xff);
597 if (prefixIndex < 0 || prefixIndex >= URI_PREFIX_MAP.length) {
598 throw new FormatException("Payload is not a valid URI (invalid prefix)");
599 }
600 String prefix = URI_PREFIX_MAP[prefixIndex];
601 byte[] fullUri = concat(prefix.getBytes(Charsets.UTF_8),
602 Arrays.copyOfRange(payload, 1, payload.length));
603 return Uri.parse(new String(fullUri, Charsets.UTF_8));
604 }
605
606 private static byte[] concat(byte[]... arrays) {
607 int length = 0;
608 for (byte[] array : arrays) {
609 length += array.length;
610 }
611 byte[] result = new byte[length];
612 int pos = 0;
613 for (byte[] array : arrays) {
614 System.arraycopy(array, 0, result, pos, array.length);
615 pos += array.length;
616 }
617 return result;
618 }
619
620 /**
Nick Pellya356bf12011-12-13 15:36:31 -0800621 * Main parsing method.<p>
622 * Expects NdefMessage to begin immediately, allows trailing data.<p>
623 * Currently has strict validation of all fields as per NDEF 1.0
624 * specification section 2.5. We will attempt to keep this as strict as
625 * possible to encourage well-formatted NDEF.<p>
626 * Always returns 1 or more NdefRecord's, or throws FormatException.
627 *
628 * @param buffer ByteBuffer to read from
629 * @param ignoreMbMe ignore MB and ME flags, and read only 1 complete record
630 * @return one or more records
631 * @throws FormatException on any parsing error
Nick Pellydc993792010-10-04 11:17:25 -0700632 */
Nick Pellya356bf12011-12-13 15:36:31 -0800633 static NdefRecord[] parse(ByteBuffer buffer, boolean ignoreMbMe) throws FormatException {
634 List<NdefRecord> records = new ArrayList<NdefRecord>();
635
636 try {
637 byte[] type = null;
638 byte[] id = null;
639 byte[] payload = null;
640 ArrayList<byte[]> chunks = new ArrayList<byte[]>();
641 boolean inChunk = false;
642 short chunkTnf = -1;
643 boolean me = false;
644
645 while (!me) {
646 byte flag = buffer.get();
647
648 boolean mb = (flag & NdefRecord.FLAG_MB) != 0;
649 me = (flag & NdefRecord.FLAG_ME) != 0;
650 boolean cf = (flag & NdefRecord.FLAG_CF) != 0;
651 boolean sr = (flag & NdefRecord.FLAG_SR) != 0;
652 boolean il = (flag & NdefRecord.FLAG_IL) != 0;
653 short tnf = (short)(flag & 0x07);
654
655 if (!mb && records.size() == 0 && !inChunk && !ignoreMbMe) {
656 throw new FormatException("expected MB flag");
657 } else if (mb && records.size() != 0 && !ignoreMbMe) {
658 throw new FormatException("unexpected MB flag");
659 } else if (inChunk && il) {
660 throw new FormatException("unexpected IL flag in non-leading chunk");
661 } else if (cf && me) {
662 throw new FormatException("unexpected ME flag in non-trailing chunk");
663 } else if (inChunk && tnf != NdefRecord.TNF_UNCHANGED) {
664 throw new FormatException("expected TNF_UNCHANGED in non-leading chunk");
665 } else if (!inChunk && tnf == NdefRecord.TNF_UNCHANGED) {
666 throw new FormatException("" +
667 "unexpected TNF_UNCHANGED in first chunk or unchunked record");
668 }
669
670 int typeLength = buffer.get() & 0xFF;
671 long payloadLength = sr ? (buffer.get() & 0xFF) : (buffer.getInt() & 0xFFFFFFFFL);
672 int idLength = il ? (buffer.get() & 0xFF) : 0;
673
674 if (inChunk && typeLength != 0) {
675 throw new FormatException("expected zero-length type in non-leading chunk");
676 }
677
678 if (!inChunk) {
679 type = (typeLength > 0 ? new byte[typeLength] : EMPTY_BYTE_ARRAY);
680 id = (idLength > 0 ? new byte[idLength] : EMPTY_BYTE_ARRAY);
681 buffer.get(type);
682 buffer.get(id);
683 }
684
685 ensureSanePayloadSize(payloadLength);
686 payload = (payloadLength > 0 ? new byte[(int)payloadLength] : EMPTY_BYTE_ARRAY);
687 buffer.get(payload);
688
689 if (cf && !inChunk) {
690 // first chunk
691 chunks.clear();
692 chunkTnf = tnf;
693 }
694 if (cf || inChunk) {
695 // any chunk
696 chunks.add(payload);
697 }
698 if (!cf && inChunk) {
699 // last chunk, flatten the payload
700 payloadLength = 0;
701 for (byte[] p : chunks) {
702 payloadLength += p.length;
703 }
704 ensureSanePayloadSize(payloadLength);
705 payload = new byte[(int)payloadLength];
706 int i = 0;
707 for (byte[] p : chunks) {
708 System.arraycopy(p, 0, payload, i, p.length);
709 i += p.length;
710 }
711 tnf = chunkTnf;
712 }
713 if (cf) {
714 // more chunks to come
715 inChunk = true;
716 continue;
717 } else {
718 inChunk = false;
719 }
720
721 String error = validateTnf(tnf, type, id, payload);
722 if (error != null) {
723 throw new FormatException(error);
724 }
725 records.add(new NdefRecord(tnf, type, id, payload));
726 if (ignoreMbMe) { // for parsing a single NdefRecord
727 break;
728 }
729 }
730 } catch (BufferUnderflowException e) {
731 throw new FormatException("expected more data", e);
732 }
733 return records.toArray(new NdefRecord[records.size()]);
Nick Pellydc993792010-10-04 11:17:25 -0700734 }
735
Nick Pellya356bf12011-12-13 15:36:31 -0800736 private static void ensureSanePayloadSize(long size) throws FormatException {
737 if (size > MAX_PAYLOAD_SIZE) {
738 throw new FormatException(
739 "payload above max limit: " + size + " > " + MAX_PAYLOAD_SIZE);
740 }
741 }
742
743 /**
744 * Perform simple validation that the tnf is valid.<p>
745 * Validates the requirements of NFCForum-TS-NDEF_1.0 section
746 * 3.2.6 (Type Name Format). This just validates that the tnf
747 * is valid, and that the relevant type, id and payload
748 * fields are present (or empty) for this tnf. It does not
749 * perform any deep inspection of the type, id and payload fields.<p>
750 * Also does not allow TNF_UNCHANGED since this class is only used
751 * to present logical (unchunked) records.
752 *
753 * @return null if valid, or a string error if invalid.
754 */
755 static String validateTnf(short tnf, byte[] type, byte[] id, byte[] payload) {
756 switch (tnf) {
757 case TNF_EMPTY:
758 if (type.length != 0 || id.length != 0 || payload.length != 0) {
759 return "unexpected data in TNF_EMPTY record";
760 }
761 return null;
762 case TNF_WELL_KNOWN:
763 case TNF_MIME_MEDIA:
764 case TNF_ABSOLUTE_URI:
765 case TNF_EXTERNAL_TYPE:
766 return null;
767 case TNF_UNKNOWN:
768 case TNF_RESERVED:
769 if (type.length != 0) {
770 return "unexpected type field in TNF_UNKNOWN or TNF_RESERVEd record";
771 }
772 return null;
773 case TNF_UNCHANGED:
774 return "unexpected TNF_UNCHANGED in first chunk or logical record";
775 default:
776 return String.format("unexpected tnf value: 0x%02x", tnf);
777 }
778 }
779
780 /**
781 * Serialize record for network transmission.<p>
782 * Uses specified MB and ME flags.<p>
783 * Does not chunk records.
784 */
785 void writeToByteBuffer(ByteBuffer buffer, boolean mb, boolean me) {
786 boolean sr = mPayload.length < 256;
787 boolean il = mId.length > 0;
788
789 byte flags = (byte)((mb ? FLAG_MB : 0) | (me ? FLAG_ME : 0) |
790 (sr ? FLAG_SR : 0) | (il ? FLAG_IL : 0) | mTnf);
791 buffer.put(flags);
792
793 buffer.put((byte)mType.length);
794 if (sr) {
795 buffer.put((byte)mPayload.length);
796 } else {
797 buffer.putInt(mPayload.length);
798 }
799 if (il) {
800 buffer.put((byte)mId.length);
801 }
802
803 buffer.put(mType);
804 buffer.put(mId);
805 buffer.put(mPayload);
806 }
807
808 /**
809 * Get byte length of serialized record.
810 */
811 int getByteLength() {
812 int length = 3 + mType.length + mId.length + mPayload.length;
813
814 boolean sr = mPayload.length < 256;
815 boolean il = mId.length > 0;
816
817 if (!sr) length += 3;
818 if (il) length += 1;
819
820 return length;
821 }
822
823 @Override
Nick Pellydc993792010-10-04 11:17:25 -0700824 public int describeContents() {
825 return 0;
826 }
827
Nick Pellya356bf12011-12-13 15:36:31 -0800828 @Override
Nick Pellydc993792010-10-04 11:17:25 -0700829 public void writeToParcel(Parcel dest, int flags) {
Nick Pelly590b73b2010-10-12 13:00:50 -0700830 dest.writeInt(mTnf);
831 dest.writeInt(mType.length);
832 dest.writeByteArray(mType);
833 dest.writeInt(mId.length);
834 dest.writeByteArray(mId);
835 dest.writeInt(mPayload.length);
836 dest.writeByteArray(mPayload);
Nick Pellydc993792010-10-04 11:17:25 -0700837 }
838
839 public static final Parcelable.Creator<NdefRecord> CREATOR =
840 new Parcelable.Creator<NdefRecord>() {
Nick Pellya356bf12011-12-13 15:36:31 -0800841 @Override
Nick Pellydc993792010-10-04 11:17:25 -0700842 public NdefRecord createFromParcel(Parcel in) {
Nick Pelly590b73b2010-10-12 13:00:50 -0700843 short tnf = (short)in.readInt();
844 int typeLength = in.readInt();
845 byte[] type = new byte[typeLength];
846 in.readByteArray(type);
847 int idLength = in.readInt();
848 byte[] id = new byte[idLength];
849 in.readByteArray(id);
850 int payloadLength = in.readInt();
851 byte[] payload = new byte[payloadLength];
852 in.readByteArray(payload);
853
Nick Pellya356bf12011-12-13 15:36:31 -0800854 return new NdefRecord(tnf, type, id, payload);
Nick Pellydc993792010-10-04 11:17:25 -0700855 }
Nick Pellya356bf12011-12-13 15:36:31 -0800856 @Override
Nick Pellydc993792010-10-04 11:17:25 -0700857 public NdefRecord[] newArray(int size) {
Nick Pelly590b73b2010-10-12 13:00:50 -0700858 return new NdefRecord[size];
Nick Pellydc993792010-10-04 11:17:25 -0700859 }
860 };
Nick Pelly590b73b2010-10-12 13:00:50 -0700861
Nick Pellya356bf12011-12-13 15:36:31 -0800862 @Override
863 public int hashCode() {
864 final int prime = 31;
865 int result = 1;
866 result = prime * result + Arrays.hashCode(mId);
867 result = prime * result + Arrays.hashCode(mPayload);
868 result = prime * result + mTnf;
869 result = prime * result + Arrays.hashCode(mType);
870 return result;
871 }
872
873 /**
874 * Returns true if the specified NDEF Record contains
875 * identical tnf, type, id and payload fields.
876 */
877 @Override
878 public boolean equals(Object obj) {
879 if (this == obj) return true;
880 if (obj == null) return false;
881 if (getClass() != obj.getClass()) return false;
882 NdefRecord other = (NdefRecord) obj;
883 if (!Arrays.equals(mId, other.mId)) return false;
884 if (!Arrays.equals(mPayload, other.mPayload)) return false;
885 if (mTnf != other.mTnf) return false;
886 return Arrays.equals(mType, other.mType);
887 }
888
889 @Override
890 public String toString() {
891 StringBuilder b = new StringBuilder(String.format("NdefRecord tnf=%X", mTnf));
892 if (mType.length > 0) b.append(" type=").append(bytesToString(mType));
893 if (mId.length > 0) b.append(" id=").append(bytesToString(mId));
894 if (mPayload.length > 0) b.append(" payload=").append(bytesToString(mPayload));
895 return b.toString();
896 }
897
898 private static StringBuilder bytesToString(byte[] bs) {
899 StringBuilder s = new StringBuilder();
900 for (byte b : bs) {
901 s.append(String.format("%02X", b));
902 }
903 return s;
904 }
905
906 /** Ensure valid 'DNS-char' as per RFC2234 */
907 private static void ensureValidDomain(byte[] bs) {
908 for (int i = 0; i < bs.length; i++) {
909 byte b = bs[i];
910 if ((b >= 'A' && b <= 'Z') ||
911 (b >= 'a' && b <= 'z') ||
912 (b >= '0' && b <= '9') ||
913 b == '.' || b == '-') {
914 continue;
915 }
916 throw new IllegalArgumentException("invalid character in domain");
917 }
918 }
919
920 /** Ensure valid 'WKT-char' as per RFC2234 */
921 private static void ensureValidWkt(byte[] bs) {
922 for (int i = 0; i < bs.length; i++) {
923 byte b = bs[i];
924 if ((b >= 'A' && b <= 'Z') ||
925 (b >= 'a' && b <= 'z') ||
926 (b >= '0' && b <= '9') ||
927 b == '(' || b == ')' || b == '+' || b == ',' || b == '-' ||
928 b == ':' || b == '=' || b == '@' || b == ';' || b == '$' ||
929 b == '_' || b == '!' || b == '*' || b == '\'' || b == '.') {
930 continue;
931 }
932 throw new IllegalArgumentException("invalid character in type");
933 }
934 }
935
936 private static void toLowerCase(byte[] b) {
937 for (int i = 0; i < b.length; i++) {
938 if (b[i] >= 'A' && b[i] <= 'Z') {
939 b[i] += 0x20;
940 }
941 }
942 }
Martijn Coenen8bede172011-04-08 16:42:22 +0200943}