Skip to content

Commit

Permalink
[webnfc] Support reading/writing 'unknown' type records
Browse files Browse the repository at this point in the history
Previously, TNF_UNKNOWN records were read and represented as 'opaque'
type records, and writing TNF_UNKNOWN records was not supported.

As part of effort to make web nfc more of a low level API, we have the
spec change w3c/web-nfc#373 that defines an
explicit mapping for
  TNF_UNKNOWN  <---> 'unknown' type NDEFRecord

This CL introduces the new 'unknown' type and supports reading/writing
them accordingly.

BUG=520391

Change-Id: I384256d35b665c740edb9bef221d3319bf7df063
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1886114
Commit-Queue: Leon Han <leon.han@intel.com>
Reviewed-by: Reilly Grant <reillyg@chromium.org>
Cr-Commit-Position: refs/heads/master@{#712001}
  • Loading branch information
Leon Han authored and Commit Bot committed Nov 2, 2019
1 parent 10cfbd0 commit 4d4e4f3
Show file tree
Hide file tree
Showing 8 changed files with 124 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ public final class NdefMessageUtils {
public static final String RECORD_TYPE_ABSOLUTE_URL = "absolute-url";
public static final String RECORD_TYPE_JSON = "json";
public static final String RECORD_TYPE_OPAQUE = "opaque";
public static final String RECORD_TYPE_UNKNOWN = "unknown";
public static final String RECORD_TYPE_SMART_POSTER = "smart-poster";

private static class PairOfDomainAndType {
Expand Down Expand Up @@ -122,6 +123,9 @@ private static android.nfc.NdefRecord toNdefRecord(NdefRecord record)
case RECORD_TYPE_JSON:
case RECORD_TYPE_OPAQUE:
return android.nfc.NdefRecord.createMime(record.mediaType, record.data);
case RECORD_TYPE_UNKNOWN:
return new android.nfc.NdefRecord(
android.nfc.NdefRecord.TNF_UNKNOWN, null, null, record.data);
case RECORD_TYPE_EMPTY:
return new android.nfc.NdefRecord(
android.nfc.NdefRecord.TNF_EMPTY, null, null, null);
Expand Down Expand Up @@ -158,7 +162,7 @@ record = createURLRecord(ndefRecord.toUri(), true /* isAbsUrl */);
record = createWellKnownRecord(ndefRecord);
break;
case android.nfc.NdefRecord.TNF_UNKNOWN:
record = createUnKnownRecord(ndefRecord.getPayload());
record = createUnknownRecord(ndefRecord.getPayload());
break;
case android.nfc.NdefRecord.TNF_EXTERNAL_TYPE:
record = createExternalTypeRecord(
Expand Down Expand Up @@ -269,10 +273,9 @@ private static NdefRecord createWellKnownRecord(android.nfc.NdefRecord record)
/**
* Constructs unknown known type NdefRecord
*/
private static NdefRecord createUnKnownRecord(byte[] payload) {
private static NdefRecord createUnknownRecord(byte[] payload) {
NdefRecord nfcRecord = new NdefRecord();
nfcRecord.recordType = RECORD_TYPE_OPAQUE;
nfcRecord.mediaType = OCTET_STREAM_MIME;
nfcRecord.recordType = RECORD_TYPE_UNKNOWN;
nfcRecord.data = payload;
return nfcRecord;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,7 @@ public void testNdefToMojoConversion() throws UnsupportedEncodingException {
assertNull(jsonMojoNdefMessage.data[0].lang);
assertEquals(TEST_JSON, new String(jsonMojoNdefMessage.data[0].data));

// Test Unknown record conversion.
// Test unknown record conversion.
android.nfc.NdefMessage unknownNdefMessage = new android.nfc.NdefMessage(
new android.nfc.NdefRecord(android.nfc.NdefRecord.TNF_UNKNOWN, null,
ApiCompatibilityUtils.getBytesUtf8(DUMMY_RECORD_ID),
Expand All @@ -331,8 +331,7 @@ public void testNdefToMojoConversion() throws UnsupportedEncodingException {
assertNull(unknownMojoNdefMessage.url);
assertEquals(1, unknownMojoNdefMessage.data.length);
assertEquals(
NdefMessageUtils.RECORD_TYPE_OPAQUE, unknownMojoNdefMessage.data[0].recordType);
assertEquals(OCTET_STREAM_MIME, unknownMojoNdefMessage.data[0].mediaType);
NdefMessageUtils.RECORD_TYPE_UNKNOWN, unknownMojoNdefMessage.data[0].recordType);
assertEquals(DUMMY_RECORD_ID, unknownMojoNdefMessage.data[0].id);
assertNull(unknownMojoNdefMessage.data[0].encoding);
assertNull(unknownMojoNdefMessage.data[0].lang);
Expand Down Expand Up @@ -501,6 +500,20 @@ public void testMojoToNdefConversion() throws InvalidNdefMessageException {
assertEquals(
android.nfc.NdefRecord.TNF_EXTERNAL_TYPE, jsonNdefMessage.getRecords()[1].getTnf());

// Test unknown record conversion.
NdefRecord unknownMojoNdefRecord = new NdefRecord();
unknownMojoNdefRecord.recordType = NdefMessageUtils.RECORD_TYPE_UNKNOWN;
unknownMojoNdefRecord.data = ApiCompatibilityUtils.getBytesUtf8(TEST_TEXT);
NdefMessage unknownMojoNdefMessage = createMojoNdefMessage(TEST_URL, unknownMojoNdefRecord);
android.nfc.NdefMessage unknownNdefMessage =
NdefMessageUtils.toNdefMessage(unknownMojoNdefMessage);
assertEquals(2, unknownNdefMessage.getRecords().length);
assertEquals(
android.nfc.NdefRecord.TNF_UNKNOWN, unknownNdefMessage.getRecords()[0].getTnf());
assertEquals(TEST_TEXT, new String(unknownNdefMessage.getRecords()[0].getPayload()));
assertEquals(android.nfc.NdefRecord.TNF_EXTERNAL_TYPE,
unknownNdefMessage.getRecords()[1].getTnf());

// Test external record conversion.
NdefRecord extMojoNdefRecord = new NdefRecord();
extMojoNdefRecord.recordType =
Expand Down
54 changes: 48 additions & 6 deletions third_party/blink/renderer/modules/nfc/ndef_record.cc
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,30 @@ WTF::Vector<uint8_t> GetUTF8DataFromString(const String& string) {
return data;
}

bool IsBufferSource(const ScriptValue& data) {
return !data.IsEmpty() && (data.V8Value()->IsArrayBuffer() ||
data.V8Value()->IsArrayBufferView());
}

WTF::Vector<uint8_t> GetBytesOfBufferSource(const ScriptValue& buffer_source) {
DCHECK(IsBufferSource(buffer_source));
WTF::Vector<uint8_t> bytes;
if (buffer_source.V8Value()->IsArrayBuffer()) {
DOMArrayBuffer* array_buffer =
V8ArrayBuffer::ToImpl(buffer_source.V8Value().As<v8::Object>());
bytes.Append(static_cast<uint8_t*>(array_buffer->Data()),
array_buffer->ByteLength());
} else if (buffer_source.V8Value()->IsArrayBufferView()) {
DOMArrayBufferView* array_buffer_view =
V8ArrayBufferView::ToImpl(buffer_source.V8Value().As<v8::Object>());
bytes.Append(static_cast<uint8_t*>(array_buffer_view->BaseAddress()),
array_buffer_view->byteLength());
} else {
NOTREACHED();
}
return bytes;
}

// https://w3c.github.io/web-nfc/#the-ndefrecordtype-string
// Derives a formatted custom type for the external type record from |input|.
// Returns a null string for an invalid |input|.
Expand Down Expand Up @@ -104,10 +128,10 @@ static NDEFRecord* CreateTextRecord(const ExecutionContext* execution_context,
// ExtractMIMETypeFromMediaType() ignores parameters of the MIME type.
String mime_type = ExtractMIMETypeFromMediaType(AtomicString(media_type));

// TODO(https://crbug.com/520391): Step 2-5, parse a MIME type on |media_type|
// to get 'lang' and 'charset' parameters. Now we ignore them and the embedder
// always uses "lang=en-US;charset=UTF-8" when pushing the record to a NFC
// tag.
// TODO(https://crbug.com/520391): Step 2-5, parse a MIME type on
// |media_type| to get 'lang' and 'charset' parameters. Now we ignore them
// and the embedder always uses "lang=en-US;charset=UTF-8" when pushing the
// record to a NFC tag.
if (mime_type.IsEmpty()) {
mime_type = "text/plain";
} else if (!mime_type.StartsWithIgnoringASCIICase("text/")) {
Expand Down Expand Up @@ -250,6 +274,19 @@ static NDEFRecord* CreateOpaqueRecord(const String& media_type,
std::move(bytes));
}

static NDEFRecord* CreateUnknownRecord(const String& media_type,
const ScriptValue& data,
ExceptionState& exception_state) {
if (!IsBufferSource(data)) {
exception_state.ThrowTypeError(
"The data for 'unknown' NDEFRecord must be a BufferSource.");
return nullptr;
}

return MakeGarbageCollected<NDEFRecord>("unknown", media_type,
GetBytesOfBufferSource(data));
}

static NDEFRecord* CreateExternalRecord(const String& custom_type,
const ScriptValue& data,
ExceptionState& exception_state) {
Expand Down Expand Up @@ -310,6 +347,9 @@ NDEFRecord* NDEFRecord::Create(const ExecutionContext* execution_context,
return CreateJsonRecord(init->mediaType(), init->data(), exception_state);
} else if (record_type == "opaque") {
return CreateOpaqueRecord(init->mediaType(), init->data(), exception_state);
} else if (record_type == "unknown") {
return CreateUnknownRecord(init->mediaType(), init->data(),
exception_state);
} else if (record_type == "smart-poster") {
// TODO(https://crbug.com/520391): Support creating smart-poster records.
exception_state.ThrowTypeError("smart-poster type is not supported yet");
Expand All @@ -320,8 +360,8 @@ NDEFRecord* NDEFRecord::Create(const ExecutionContext* execution_context,
return CreateExternalRecord(formated_type, init->data(), exception_state);
}

exception_state.ThrowTypeError("Unknown NDEFRecord type.");
return nullptr;
exception_state.ThrowTypeError("Invalid NDEFRecord type.");
return nullptr;
}

NDEFRecord::NDEFRecord(const String& record_type,
Expand Down Expand Up @@ -406,6 +446,7 @@ DOMArrayBuffer* NDEFRecord::arrayBuffer() const {
return nullptr;
}
DCHECK(record_type_ == "json" || record_type_ == "opaque" ||
record_type_ == "unknown" ||
!ValidateCustomRecordType(record_type_).IsNull());

return DOMArrayBuffer::Create(payload_data_.data(), payload_data_.size());
Expand All @@ -418,6 +459,7 @@ ScriptValue NDEFRecord::json(ScriptState* script_state,
return ScriptValue::CreateNull(script_state->GetIsolate());
}
DCHECK(record_type_ == "json" || record_type_ == "opaque" ||
record_type_ == "unknown" ||
!ValidateCustomRecordType(record_type_).IsNull());

ScriptState::Scope scope(script_state);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,13 @@
unmatchedScanOptions: {recordType: "json"},
message: createMessage([createOpaqueRecord(test_buffer_data)])
},
{
desc: "Test that reading data succeed when NDEFScanOptions'" +
" recordType is set to 'unknown'.",
scanOptions: {recordType: "unknown"},
unmatchedScanOptions: {recordType: "json"},
message: createMessage([createUnknownRecord(test_buffer_data)])
},
{
desc: "Test that reading data succeed when NDEFScanOptions'" +
" recordType is set to 'text'.",
Expand Down Expand Up @@ -102,6 +109,13 @@
message: createMessage([createOpaqueRecord(test_buffer_data)]),
unmatchedMessage: createMessage([createJsonRecord(test_json_data)])
},
{
desc: "Test that filtering 'unknown' record from different messages" +
" correctly with NDEFScanOptions' recordType is set to 'unknown'.",
scanOptions: {recordType: "unknown"},
message: createMessage([createUnknownRecord(test_buffer_data)]),
unmatchedMessage: createMessage([createUrlRecord(test_url_data)])
},
{
desc: "Test that filtering 'text' record from different messages" +
" correctly with NDEFScanOptions' recordType is set to 'text'.",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,30 @@
'json() has the same content with the original dictionary');
}, 'NDEFRecord constructor with JSON record type');

test(() => {
assert_throws(new TypeError, () => new NDEFRecord(
createUnknownRecord("A string is not a BufferSource")),
'Only BufferSource is allowed to be the record data.');

let buffer = new ArrayBuffer(4);
new Uint8Array(buffer).set([1, 2, 3, 4]);
// Feed ArrayBuffer.
{
const record = new NDEFRecord(createUnknownRecord(buffer));
assert_equals(record.recordType, 'unknown', 'recordType');
assert_array_equals(new Uint8Array(record.data.buffer), [1, 2, 3, 4],
'data has the same content with the original buffer');
}
// Feed ArrayBufferView.
{
let buffer_view = new Uint8Array(buffer, 1);
const record = new NDEFRecord(createUnknownRecord(buffer_view));
assert_equals(record.recordType, 'unknown', 'recordType');
assert_array_equals(new Uint8Array(record.data.buffer), [2, 3, 4],
'data has the same content with the original buffer view');
}
}, 'NDEFRecord constructor with unknown record type');

test(() => {
let buffer = new ArrayBuffer(4);
let buffer_view = new Uint8Array(buffer);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ PASS Reject promise with exceptions thrown from serializing the 'json' record da
PASS NDEFWriter.push should fail with TypeError when invalid target value is provided.
PASS Test that WebNFC API is not accessible from iframe context.
PASS NDEFWriter.push should succeed when NFC HW is enabled
PASS NDEFWriter.push NDEFMessage containing text, json, opaque, url, absolute-url and external records with default NDEFPushOptions.
PASS NDEFWriter.push NDEFMessage containing text, json, opaque, unknown, url, absolute-url and external records with default NDEFPushOptions.
PASS Test that NDEFWriter.push succeeds when message is DOMString.
PASS Test that NDEFWriter.push succeeds when message is ArrayBuffer.
PASS Test that NDEFWriter.push succeeds when message is ArrayBufferView.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,14 @@
createMessage([createOpaqueRecord(test_number_data)]),
createMessage([createOpaqueRecord(test_json_data)]),

// NDEFRecord must have data.
createMessage([createUnknownRecord()]),

// NDEFRecord.data for 'unknown' record must be BufferSource.
createMessage([createUnknownRecord(test_text_data)]),
createMessage([createUnknownRecord(test_number_data)]),
createMessage([createUnknownRecord(test_json_data)]),

// https://w3c.github.io/web-nfc/#dfn-map-external-data-to-ndef
// NDEFRecord must have data.
createMessage([createRecord('w3.org:xyz', '', undefined)]),
Expand Down Expand Up @@ -301,13 +309,14 @@
createJsonRecord(test_json_data),
createJsonRecord(test_number_data),
createOpaqueRecord(test_buffer_data),
createUnknownRecord(test_buffer_data),
createUrlRecord(test_url_data),
createUrlRecord(test_url_data, true),
createRecord('w3.org:xyz', '', test_buffer_data)],
test_message_origin);
await writer.push(message);
assertNDEFMessagesEqual(message, mockNFC.pushedMessage());
}, "NDEFWriter.push NDEFMessage containing text, json, opaque, url, absolute-url \
}, "NDEFWriter.push NDEFMessage containing text, json, opaque, unknown, url, absolute-url \
and external records with default NDEFPushOptions.");

nfc_test(async (t, mockNFC) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,10 @@ function createOpaqueRecord(buffer) {
return createRecord('opaque', 'application/octet-stream', buffer);
}

function createUnknownRecord(buffer) {
return createRecord('unknown', '', buffer);
}

function createUrlRecord(url, isAbsUrl) {
if (isAbsUrl) {
return createRecord('absolute-url', 'text/plain', url);
Expand Down

0 comments on commit 4d4e4f3

Please sign in to comment.