근거리 무선 통신(NFC)은 일반적으로 몇 센티미터 이내의 근접 거리에서 두 장치 간 무선 통신을 가능하게 합니다. NFC는 13.56 MHz에서 동작하는 근접 결합 장치의 단순 무선 상호 연결을 위한 인터페이스와 프로토콜을 정의하는 국제 표준(ISO/IEC 18092)입니다.

하드웨어 표준은 [[[NFC-STANDARDS]]]에 정의되어 있습니다.

이 문서는 NFC 기술을 기반으로 한 선택된 사용 사례를 가능하게 하는 API를 정의합니다. 이 사양의 현재 범위는 NDEF입니다.

저수준 I/O 연산(예: ISO-DEP, NFC-A/B, NFC-F)과 호스트 기반 카드 에뮬레이션(HCE)은 현재 범위 내에서 지원되지 않습니다.

구현자는 이 사양이 불안정한 것으로 간주된다는 점을 인지해야 합니다. 논의에 참여하지 않는 구현자들은 사양이 호환되지 않는 방식으로 변경되는 것을 경험할 수 있습니다. 이 사양이 결국 후보 권고(Candidate Recommendation) 단계에 도달하기 전에 구현을 고려하는 벤더는 GitHub의 저장소를 구독하고 논의에 참여해야 합니다.

이 문서는 단일 제품에 적용되는 적합성(conformance) 기준을 정의합니다: 해당 인터페이스를 구현하는 UA(사용자 에이전트).

소개

Web NFC 사용자 시나리오는 다음과 같습니다: 장치를 플라스틱 카드나 스티커와 같은 수동 전원 NFC 태그에 근접시켜 데이터를 읽거나 쓰는 것.

NFC는 자기 유도를 사용하여 작동합니다. 즉 판독기(능동적이고 전원이 공급되는 장치)가 작은 전하를 방출하여 자기장을 생성합니다. 이 자기장은 수동 장치에 전원을 공급하여 데이터를 통신하기 위한 전기적 임펄스로 변환합니다. 따라서 장치들이 범위 내에 있을 때 항상 읽기가 수행됩니다(NFC 아날로그 사양 및 NFC 디지털 프로토콜, NFC Forum, 2006 참조). P2P(peer-to-peer) 연결도 유사하게 작동하며, 장치는 주기적으로 소위 이니시에이터 모드로 전환하여 대상을 스캔한 다음 대상 모드로 되돌아갑니다. 대상이 발견되면 데이터는 태그의 경우와 동일한 방식으로 읽힙니다.

NFC는 기존 RFID 표준을 기반으로 하므로 많은 NFC 칩셋이 RFID 태그 읽기를 지원하지만, 일부는 단일 벤더에 의해서만 지원되며 NFC 표준의 일부가 아닐 수 있습니다. 따라서 이 문서는 NFC 데이터 교환 형식(NDEF)과 상호작용하는 방법을 명시합니다.

용어 및 관습

사용된 증강 백우스-나우르 형식(ABNF) 표기법은 [[RFC5234]]에 명시되어 있습니다.

NFC는 Near Field Communications의 약자로, 13.56 MHz에서 동작하는 근거리 무선 기술을 의미하며 10 cm 미만 거리에서 장치 간 통신을 가능하게 합니다. NFC 통신 프로토콜과 데이터 교환 형식은 ISO/IEC 14443 및 FeliCa를 포함한 기존 무선 주파수 식별(RFID) 표준을 기반으로 합니다. NFC 표준에는 ISO/IEC 18092[5] 및 NFC Forum이 정의한 표준이 포함됩니다. 전체 목록은 NFC Forum 기술 사양을 참조하십시오.

NFC adapter는 주어진 하드웨어 요소(NFC 칩)에 구현된 NFC 기능에 대한 접근을 제공하는 기본 플랫폼의 소프트웨어 엔터티입니다. 장치는 내장형 어댑터와 USB를 통해 연결된 하나 이상의 어댑터 등 여러 개의 NFC 어댑터를 가질 수 있습니다.

NFC tag는 수동 NFC 장치로서 blocklisted 되지 않은 장치입니다. NFC tag는 능동 NFC 장치가 근접 범위에 있을 때 자기 유도로 전원이 공급됩니다. NDEF를 지원하는 NFC tag는 단일 NDEF message를 포함합니다。

메시지를 읽는 방식은 동일 제조사의 판독기와 태그가 필요로 하는 독점 기술을 통해 발생할 수 있습니다. 또한 이러한 방식은 NDEF 메시지를 노출할 수도 있습니다.

NFC peer는 데이터를 교환하기 위해 다른 장치와 상호작용할 수 있는 능동 전원 장치입니다。

현재 명세상으로는 peer-to-peer는 지원되지 않습니다。

NFC deviceNFC peer 또는 NFC tag입니다。

NDEF는 NFC Forum Data Exchange Format의 약어로, [[!NFC-NDEF]]에 표준화된 경량 이진 메시지 형식입니다。

NDEF message는 하나 이상의 애플리케이션 정의 NDEF record를 캡슐화합니다. NDEF 메시지는 NFC tag에 저장되거나 NFC 지원 장치 간에 교환될 수 있습니다。

용어 NFC contentNFC tag로 전송되거나 그로부터 수신되는 모든 바이트를 나타냅니다. 현재 API에서 이는 NDEF message와 동의어입니다。

NFC 표준

NFC는 NFC Forum에서 표준화되며 [[NFC-STANDARDS]]에 설명되어 있습니다۔

NDEF 호환 태그 유형

NFC Forum은 NFC 장치와 함께 동작하기 위해 다섯 가지 서로 다른 태그 유형의 지원을 의무화했습니다. 운영 체제(예: Android)에서도 동일한 지원이 요구됩니다。

게다가, MIFARE Standard는 오래된 MIFARE Standard 위에서 NDEF가 작동하는 방식을 규정하며, 구현자들이 선택적으로 지원할 수 있습니다。

NDEF 매핑에 대한 주석은 다음에서 찾을 수 있습니다: MIFARE Classic as NFC Type MIFARE Classic Tag

  1. NFC Forum Type 1: 이 태그는 ISO/IEC 14443-3A (NFC-A)를 기반으로 합니다. 태그는 다시 쓸 수 있으며 읽기 전용으로 구성될 수 있습니다. 메모리 크기는 `96` 바이트에서 `2` K바이트 사이일 수 있습니다. 통신 속도는 `106` kbit/s 입니다. 다른 모든 유형과 달리, 이 태그들은 NFC 필드 내의 다중 태그 처리용 안티-콜리전 보호가 없습니다.
  2. NFC Forum Type 2: 이 태그는 ISO/IEC 14443-3A (NFC-A)를 기반으로 합니다. 태그는 다시 쓸 수 있으며 읽기 전용으로 구성될 수 있습니다. 메모리 크기는 `48` 바이트에서 `2` K바이트 사이일 수 있습니다. 통신 속도는 `106` kbit/s 입니다。
  3. NFC Forum Type 3: 이 태그는 일본 산업 표준(JIS) X 6319-4 (ISO/IEC 18092), 일반적으로 FeliCa로 알려진 것을 기반으로 합니다. 태그는 사전 구성되어 재기록 가능하거나 읽기 전용일 수 있습니다. 메모리는 `2` k바이트입니다. 통신 속도는 `212` kbit/s 또는 `424` kbit/s 입니다。
  4. NFC Forum Type 4: 이 태그는 ISO/IEC 14443-4 A/B (NFC A, NFC B)를 기반으로 하므로 통신을 위해 NFC-A 또는 NFC-B를 지원합니다. 또한 태그는 선택적으로 ISO-DEP(ISO/IEC 14443에 정의된 데이터 교환 프로토콜)를 지원할 수 있습니다 (ISO/IEC 14443-4:2008 Part 4: Transmission protocol). 태그는 사전 구성되어 재기록 가능하거나 읽기 전용일 수 있습니다. 가변 메모리, 최대 `32` k바이트. `106`, `212` 또는 `424` kbit/s의 세 가지 통신 속도를 지원합니다。
  5. NFC Forum Type 5: 이 태그는 ISO/IEC 15693 (NFC-V)을 기반으로 하며 장거리 RFID 판독기도 접근 가능한 ISO/IEC 15693 RF 태그에서 NDEF 메시지를 읽고 쓸 수 있게 합니다. NFC 통신은 단거리로 제한되며 ISO/IEC 18092의 능동 통신 모드를 사용할 수 있는데, 이 경우 송신 피어가 필드를 생성하여 전력 소비를 균형시키고 링크 안정성을 향상시킵니다. 가변 메모리, 최대 `64` k바이트. 통신 속도는 `26.48` kbit/s 입니다。
  6. MIFARE Standard: 이 태그는 종종 MIFARE Classic 또는 MIFARE Mini라는 브랜드 이름으로 판매되며, ISO/IEC 14443-3A(또는 NFC-A로도 알려짐, ISO/IEC 14443-3:2011 Part 3: Initialization and anticollision에 정의됨)를 기반으로 합니다. 태그는 다시 쓸 수 있으며 읽기 전용으로 구성될 수 있습니다. 메모리 크기는 `320`에서 `4` k바이트 사이일 수 있습니다. 통신 속도는 `106` kbit/s 입니다。

    MIFARE Standard는 NFC Forum 유형이 아니며 NXP 하드웨어를 사용하는 장치에서만 읽을 수 있습니다. 따라서 MIFARE Standard 기반 태그의 읽기/쓰기 지원은 비표준적이지만, 레거시 시스템에서의 인기와 사용 때문에 이 유형이 포함되어 있습니다。

NFC Forum에서 NDEF record에 대해 표준화한 데이터 유형 외에도, 버스 카드, 출입문 개폐기 등 많은 상용 제품이 MIFARE Standard를 기반으로 할 수 있으며, 이는 동작을 위해 특정 NFC 칩(카드와 판독기 제조사가 동일한 경우)을 필요로 합니다。

NDEF 레코드 및 필드

NDEF recordNDEF message의 일부입니다. 각 레코드는 데이터 페이로드와 관련 타입 정보를 포함하는 이진 구조입니다. 이 외에도 페이로드 크기, 데이터가 여러 레코드에 걸쳐 청크화되어 있는지 등의 데이터 구조에 관한 정보를 포함합니다。

일반적인 레코드는 다음과 같습니다:

처음 세 바이트(도형의 줄)만 필수입니다. 첫째는 헤더 바이트, 그 다음이 TYPE LENGTH fieldPAYLOAD LENGTH field이며, 둘 다 0일 수 있습니다。

TNF field(비트 `0-2`, 타입 이름 포맷)는 타입 이름의 형식을 나타내며 네이티브 NFC 소프트웨어 스택에서 종종 노출됩니다. 이 필드는 다음 NDEF 레코드 페이로드 타입을 나타내는 이진 값을 가질 수 있습니다:
TNF 값 설명
0 빈 레코드
1 NFC Forum [=well-known type record=]
2 MIME 타입 레코드
3 절대-URL 레코드
4 NFC Forum 외부 타입 레코드
5 알 수 없는 레코드
6 변경되지 않은 레코드
7 미래 사용을 위해 예약됨

IL field(비트 `3`, id 길이)는 ID LENGTH field가 존재하는지를 나타냅니다. IL field가 `0`이면 ID field도 존재하지 않습니다。

SR field(비트 `4`, short record)는 페이로드 길이가 `255` 바이트 이하인 짧은 레코드를 나타냅니다. 일반 레코드는 페이로드 길이가 `255` 바이트를 초과하여 최대 `4` GB까지 가능할 수 있습니다. 짧은 레코드는 길이를 표시하기 위해 한 바이트만 사용하고, 일반 레코드는 길이를 표시하기 위해 `4` 바이트(`2`^`32`-1 바이트)를 사용합니다。

CF field(비트 `5`, chunk flag)는 페이로드가 여러 레코드에 걸쳐 chunked되었는지를 나타냅니다。

Web NFC는 수신된 모든 청크된 레코드를 논리적 레코드로 변환하고 전송 시 필요하면 페이로드를 투명하게 청크합니다。

ME field(비트 `6`, message end)는 이 레코드가 NDEF message의 마지막인지 여부를 나타냅니다。

MB field(비트 `7`, message begin)는 이 레코드가 NDEF message의 첫 번째인지 여부를 나타냅니다。

TYPE LENGTH fieldTYPE field의 바이트 크기를 나타내는 부호 없는 8비트 정수입니다。

TYPE fieldTNF field의 값에 의해 결정되는 구조, 인코딩 및 형식을 통해 PAYLOAD field의 타입을 설명하는 전역적으로 고유하고 관리되는 식별자입니다。

[[[!NFC-RTD]]]는 TYPE field 이름 비교를 대소문자 구분 없이 해야 한다고 요구합니다。

ID LENGTH fieldID field의 바이트 크기를 나타내는 부호 없는 8비트 정수입니다。

ID field는 URI 참조 형태의 식별자([[RFC3986]])로서 고유하며, 절대 또는 상대일 수 있습니다(후자의 경우 애플리케이션이 기본 URI를 제공해야 합니다). 중간 및 종료 청크 레코드는 ID field를 가져서는 안 되며, 다른 레코드는 가질 수 있습니다。

PAYLOAD LENGTH fieldPAYLOAD field의 바이트 크기를 나타냅니다. SR field가 `1`이면 그 크기는 한 바이트이고, 그렇지 않으면 4바이트로, 각각 8비트 또는 32비트 부호 없는 정수를 나타냅니다。

PAYLOAD field는 애플리케이션 바이트를 담습니다. 데이터의 내부 구조는 NDEF에 대해 불투명합니다. 이후 논의되는 특정 경우에는 이 필드가 데이터로서 NDEF message를 포함할 수도 있습니다。

NDEF 레코드 타입

빈 NDEF 레코드 (TNF 0)

빈 레코드TYPE LENGTH field, ID LENGTH fieldPAYLOAD LENGTH field는 `0`이어야 하며, 따라서 TYPE field, ID fieldPAYLOAD field는 존재해서는 안 됩니다。

Well-known type records(TNF 1)

NFC Forum은 [[NFC-RTD]](리소스 타입 정의 사양)에서 텍스트, URL, 미디어 등과 같은 유용한 하위 레코드 타입의 소규모 집합을 표준화했습니다. 또한 스마트 포스터(선택적 임베디드 레코드 포함: url, text, signature 및 actions)와 핸오버 레코드와 같은 보다 복잡한 상호작용을 위해 설계된 레코드 타입도 있습니다。

well-known type recordsTYPE field에 저장된 타입 정보는 두 가지 종류일 수 있습니다: 로컬 타입글로벌 타입입니다。

잘 알려진 로컬 타입

NFC Forum의 로컬 타입은 NFC Forum 또는 애플리케이션에 의해 정의되며, 항상 소문자 문자나 숫자로 시작합니다. 이들은 보통 포함된 레코드의 로컬 컨텍스트 내에서만 고유한 짧은 문자열입니다. 타입의 의미가 포함된 레코드의 로컬 컨텍스트 밖에서는 중요하지 않고 저장 공간이 제한된 경우에 사용됩니다. 로컬 타입이 어떻게 사용되는지에 대한 예는 Smart poster를 참조하십시오。

[=로컬 타입=]은 따라서 포함 레코드 타입의 관점에서 정의되며 네임스페이스가 필요하지 않습니다. 이런 이유로 동일한 로컬 타입 이름이 다른 레코드 타입 내에서 다른 의미와 다른 페이로드 타입으로 사용될 수 있습니다。

잘 알려진 글로벌 타입

NFC Forum의 글로벌 타입은 NFC Forum에 의해 정의되고 관리되며 보통 대문자 문자로 시작합니다. 예: 텍스트는 "`T`", URL은 "`U`", 스마트 포스터는 "`Sp`", 서명은 "`Sig`", 핸오버 캐리어는 "`Hc`", 핸오버 요청은 "`Hr`", 핸오버 선택은 "`Hs`" 등。

텍스트 레코드
텍스트 레코드는 [[NDEF-TEXT]] 사양에 정의된 [=well-known type record=]입니다. TNF field는 `1`이고 TYPE field는 "`T`"(`0x54`)입니다. PAYLOAD field의 첫 바이트는 상태 바이트이며 그 다음에 US-ASCII 인코딩의 [=language tag=]가 옵니다. 페이로드의 나머지 부분은 상태 바이트가 나타내는 대로 UTF-8 또는 UTF-16으로 인코딩된 실제 텍스트입니다:
  • 비트 0~5는 [=language tag=]의 길이를 정의합니다.
  • 비트 6은 `0`입니다.
  • 비트 7이 `0`이면 페이로드는 UTF-8로 인코딩된 것이고, `1`이면 UTF-16으로 인코딩된 것입니다。
URI 레코드

URI 레코드는 [[NDEF-URI]]에 정의되어 있습니다. TNF field는 `1`이고 TYPE field는 "`U`"(`0x55`)입니다. PAYLOAD field의 첫 바이트는 URI 식별자 코드로, 약어 테이블의 인덱스이며 값들이 URI의 나머지 앞에 덧붙여집니다. 예를 들어 값 `0`은 덧붙임이 없음을 나타내고, `1`은 "`http://www.`", `0x04`는 "`https://`" 등을 나타냅니다. 페이로드의 나머지 부분은 나머지 URI를 UTF-8 문자열로 포함합니다(첫 바이트가 `0`이면 전체 URI를 의미합니다).

URI는 [[RFC3987]]에 정의되어 있으며 실제로 URN 또는 URL이 될 수 있는 UTF-8 인코딩 IRI입니다。

스마트 포스터 레코드
Smart poster는 [[NDEF-SMARTPOSTER]]에 정의되어 있으며 NDEF message를 페이로드로 포함하는 NDEF 레코드로서 주어진 웹 컨텐츠를 설명합니다. 포함되는 레코드는 다음과 같습니다:
  • smart poster 콘텐츠를 참조하는 단일 필수 URI record.

    [[NDEF-SMARTPOSTER]]는 애플리케이션이 동일한 NDEF message에 추가 URI records가 포함되어 있을 때 smart poster 레코드만을 사용해야 한다고 규정합니다。

  • 콘텐츠와 관련된 title record 역할을 하는 0개 이상의 Text records. 여러 개의 title record가 존재할 경우, 서로 다른 language tags를 가져야 합니다。 애플리케이션은 최종 사용자에게 표시할 하나의 title record를 선택해야 합니다。
  • 콘텐츠와 관련된 icon record 역할을 하는 0개 이상의 MIME type records. MIME type은 보통 "`image/jpg`", "`image/png`", "`image/gif`" 또는 "`video/mpeg`"입니다。 애플리케이션은 최종 사용자에게 표시할 하나의 icon record를 선택해야 합니다。
  • 선택적 type record 하나는 smart poster에 특화된 [=local type name=] "`t`"를 가지며, PAYLOAD fieldURI record가 참조하는 콘텐츠의 MIME 타입을 UTF-8로 인코딩한 값을 포함합니다。
  • 선택적 size record 하나는 smart poster에 특화된 [=local type name=] "`s`"를 가지며, PAYLOAD fieldsmart posterURI record에 있는 URL이 참조하는 객체의 크기를 나타내는 4바이트 32비트 부호 없는 정수를 포함합니다。
  • 선택적 action record 하나는 smart poster에 특화된 [=local type name=] "`act`"를 가지며, PAYLOAD field는 단일 바이트를 포함하고 그 값은 다음 의미를 가집니다:
    설명
    0 행동을 수행
    1 나중을 위해 저장
    2 편집을 위해 열기
    3..0xFF 미래 사용을 위해 예약됨

    action record가 없으면 smart poster 콘텐츠에 대한 기본 동작은 없습니다。

    NDEF 표준화 당시 값 `0`("행동 수행")은 SMS 전송, 통화 또는 브라우저 실행과 같은 사용 사례를 의도했습니다. 유사하게 값 `1`("나중을 위해 저장")은 SMS를 받은편지함에 저장, URL을 북마크에 저장 또는 전화번호를 연락처에 저장 등의 사용 사례를 의도했습니다. 또한 값 `2`("편집을 위해 열기")는 스마트 포스터 콘텐츠를 편집을 위한 기본 애플리케이션으로 열도록 의도되었습니다。

    구현은 여기서 정의된 동작에 대해 표준화된 동작을 구현할 필요가 없습니다. 이 API에서는 애플리케이션이 어떤 동작을 정의할지(위의 사용 사례를 포함할 수 있음)를 결정합니다. 다만 Web NFC는 값들만 제공합니다。

  • smart poster는 애플리케이션에 따라 처리할 수 있는 다른 레코드를 포함할 수도 있습니다。
아래 예시는 텍스트와 URL 레코드를 포함하는 스마트 포스터 레코드를 보여줍니다。
서명 레코드

NDEF Signature는 [[NDEF-SIGNATURE]]에 정의되어 있습니다。 그 TYPE field는 "`Sig`"(`0x53`, `0x69`, `0x67`)를 포함하고 PAYLOAD field는 버전, 서명 및 인증서 체인을 포함합니다。

현재 명세상에서는 이것이 지원되지 않습니다。

핸오버 레코드

NFC handover는 [[NFC-HANDOVER]]에 정의되어 있으며 Bluetooth 또는 WiFi와 같은 대체 통신 매체의 협상 및 활성화를 허용하는 해당 메시지 구조를 포함합니다。 협상된 통신 매체는 이후(별도로) 두 장치 간에 사진 전송, Bluetooth 프린터로의 인쇄 또는 텔레비전으로의 비디오 스트리밍과 같은 특정 활동을 수행하는 데 사용될 것입니다。

현재 명세상으로는 이것이 지원되지 않습니다。

MIME 타입 레코드 (TNF 2)

MIME 타입 레코드는 관련 MIME type과 함께 이진 데이터를 저장하는 레코드입니다。

절대-URL 레코드 (TNF 3)

절대-URL 레코드에서 TYPE field는 페이로드가 아니라 절대-URL 문자열을 포함합니다。

주: 일부 플랫폼(예: Windows Phone)은 페이로드에 추가 데이터를 저장했지만, 이러한 레코드의 모든 페이로드 데이터는 Android와 같은 다른 플랫폼에서는 무시됩니다. Android에서 이러한 레코드를 읽으면 Chrome에서 URL을 로드하려 시도하며, 클라이언트 애플리케이션용으로 의도된 것은 아닙니다。

외부 타입 레코드 (TNF 4)

NFC Forum의 외부 타입 레코드는 애플리케이션 지정 데이터 타입을 위한 것이며 [[[NFC-RTD]]]에 정의되어 있습니다。

외부 타입은 접두사 `"urn:nfc:ext:"` 뒤에 소유자 [=도메인=] 이름을 붙이고 `U+003A` (`:`)를 추가한 URN이며, 그 뒤에 0이 아닌 타입 이름이 옵니다. 예: `"urn:nfc:ext:w3.org:atype"`는 TYPE field에 `"w3.org:atype"`로 저장됩니다。

알 수 없는 타입 레코드 (TNF 5)

알 수 없는 레코드는 관련 MIME type 없이 불투명한 데이터를 저장하는 레코드로, 기본 MIME type로 `application/octet-stream`을 가정할 수 있습니다. [[NFC-NDEF]] 사양은 NDEF 파서가 페이로드를 처리하지 않고 저장하거나 전달할 것을 권장합니다。

변경되지 않은 타입 레코드 (TNF 6)

변경되지 않은 레코드는 청크된 데이터 세트의 레코드 청크로, 첫 레코드를 제외한 모든 레코드에 사용됩니다。 청크된 페이로드는 여러 NDEF record에 걸쳐 분산되며 다음 규칙을 따릅니다:
  • 초기 청크 레코드는 CF field가 설정되어 있으며, 전체 청크 페이로드의 타입으로 TYPE field가 설정되고 전체 청크 페이로드에 사용되는 식별자로 ID field를 가질 수 있습니다. 그 PAYLOAD LENGTH field는 이 레코드의 페이로드 청크 크기만을 나타냅니다。
  • 중간 청크 레코드는 CF field가 설정되어 있으며, 첫 번째 청크와 동일한 ID field를 가지며, 그들의 TYPE LENGTH fieldIL field는 `0`이어야 하고 TNF field는 `6`(unchanged)이어야 합니다。
  • 종료 청크 레코드는 이 플래그가 해제되어 있고, 나머지 규칙은 중간 청크 레코드와 동일합니다。
  • 청크된 페이로드는 단일 NDEF message에 포함되어야 하므로, 초기 및 중간 청크 레코드는 ME field를 설정할 수 없습니다。
첫 레코드:
중간 레코드:
마지막 레코드:

Web NFC의 모든 구현은 청크된 레코드를 단일 논리 레코드로 투명하게 노출해야 합니다。

사용 사례

몇 가지 NFC 사용자 시나리오가 여기Web NFC Use Cases 문서에 나열되어 있습니다. 기본적인 Web NFC 상호작용은 다음과 같습니다.

NFC tag 읽기

Web NFC를 사용하는 top-level browsing context의 {{Document}}가 보이는 동안 NDEF message를 포함하는 NFC tag를 읽습니다. 예: 웹 페이지가 사용자에게 NFC 태그를 탭하도록 지시하고 태그로부터 정보를 수신하는 경우입니다.

NFC tag에 쓰기

사용자가 NFC tag에 쓸 수 있는 웹 페이지를 엽니다. 쓰기 작업은 다음 중 하나일 수 있습니다:
  1. 포맷되지 않은 NFC tag에 쓰기.
  2. 비어 있지만 포맷된 NFC tag에 쓰기.
  3. 이미 NDEF message를 포함하고 있는 NFC tag에 쓰기.
  4. 다른 쓰기 가능한 NFC tag들(예: 일반 태그 덮어쓰기)에 쓰기.

NFC 태그에 대한 쓰기 작업은 항상 읽기 작업도 수반된다는 점에 유의하십시오.

NFC tag를 읽기 전용으로 만들기

사용자가 NFC tag를 영구적으로 읽기 전용으로 만들 수 있는 웹 페이지를 엽니다. 작업은 다음 중 하나일 수 있습니다:
  1. 포맷되지 않은 NFC tag를 영구적으로 읽기 전용으로 만들기.
  2. 비어 있지만 포맷된 NFC tag를 영구적으로 읽기 전용으로 만들기.
  3. 이미 NDEF message를 포함하고 있는 NFC tag를 영구적으로 읽기 전용으로 만들기.

NFC tag를 영구적으로 읽기 전용으로 만드는 작업은 항상 읽기 작업을 수반한다는 점에 유의하십시오.

여러 NFC adapter 지원

사용자는 내장 어댑터 외에 하나 이상의 외부 NFC adapter를 디바이스에 연결할 수 있습니다. 사용자는 어느 NFC adapter든 사용할 수 있습니다.

기능

Web NFC 사양의 주요 기능은 다음을 포함합니다:
  1. 단일 또는 복수의 NFC adapter를 가진 장치 지원. NFC 기능을 호출할 때 여러 어댑터가 존재하면 UA는 모든 NFC adapter를 병렬로 작동시킵니다.
  2. 수동(스마트 카드, 태그 등) NFC 장치와의 통신 지원.
  3. 검색된 수동 NFC 장치에 대해 사용자가 동작(예: 읽기, 쓰기 또는 transceive)을 수행하고, 그 과정에서 읽힌 페이로드에 NDEF message로 접근할 수 있도록 허용.
  4. 쓰기 가능한 태그와 같은 호환 장치가 범위에 들어왔을 때 NDEF record를 통해 페이로드를 NDEF message로 기록할 수 있도록 허용.

예제

이 섹션은 개발자가 이 사양의 다양한 기능을 어떻게 활용할 수 있는지 보여줍니다.

기능 지원 확인

Web NFC 지원 여부는 {{NDEFReader}} 객체를 확인하여 감지할 수 있습니다. 이 확인이 NFC 하드웨어의 사용 가능성을 보장하지는 않는다는 점에 유의하십시오.

      if ("NDEFReader" in window) { /* Scan and write NDEF Tags */ }
    

데이터 쓰기에 대한 일반 정보

데이터 쓰기는 일반적으로 간단하지만, NFC에서의 쓰기 동작 방식에는 몇 가지 주의점이 있습니다.

NFC 리더는 폴링(polling) 방식으로 동작하므로 태그에 쓰거나 영구적으로 읽기 전용으로 만들려면 먼저 태그를 찾아 읽어야 하며, 이는 폴링을 먼저 초기화해야 함을 의미합니다.

만약 `scan()`을 먼저 호출하여 폴링이 이미 시작되지 않았다면, `write()`와 `makeReadOnly()` 메서드는 태그가 발견되어 읽힐 때까지 일시적으로 폴링을 시작하고 작업을 시도합니다.

즉, 흐름은 태그가 처음 발견될 때 먼저 읽기가 수행되고 그 뒤에 쓰기 작업이 수행된다는 뜻입니다.

따라서 `scan()`이 실행 중이고 `reading` 이벤트에 대한 리스너가 있다면, `write()` 또는 `makeReadOnly()` 작업 중에 해당 이벤트가 한 번 디스패치될 수 있으며, 이는 의도하지 않은 동작일 수 있습니다.

다음 섹션에서는 이 동작을 처리하는 방법을 설명하기 전에 몇 가지 간단한 예제를 보여줍니다.

텍스트 문자열 쓰기

NFC 태그에 텍스트 문자열을 쓰는 것은 간단합니다.

      const ndef = new NDEFReader();
      ndef.write(
        "Hello World"
      ).then(() => {
        console.log("Message written.");
      }).catch(error => {
        console.log(`Write failed :-( try again: ${error}.`);
      });
    

URL 쓰기

URL 타입의 NDEF 레코드를 쓰려면 NDEFMessage를 사용하면 됩니다. 아래 예제는 async/await를 사용합니다.

      const ndef = new NDEFReader();
      try {
        await ndef.write({
          records: [{ recordType: "url", data: "https://w3c.github.io/web-nfc/" }]
        });
      } catch {
        console.log("Write failed :-( try again.");
      };
    

쓰기 중 초기 읽기 처리

쓰려면 태그를 찾아 읽어야 합니다. 이를 통해 기존 데이터나 일련번호를 확인하여 실제로 쓰고자 하는 태그인지 판단할 수 있습니다.

이런 이유로 `reading` 이벤트에서 `write()`를 호출하는 것이 권장됩니다. `makeReadOnly()`에도 동일하게 적용됩니다.

아래 예제는 공통 `reading` 핸들러와 단일 쓰기에만 사용되는 핸들러 사이의 조정을 보여줍니다.

      const ndef = new NDEFReader();
      let ignoreRead = false;

      ndef.onreading = (event) => {
        if (ignoreRead) {
          return; // write pending, ignore read.
        }

        console.log("We read a tag, but not during pending write!");
      };

      function write(data) {
        ignoreRead = true;
        return new Promise((resolve, reject) => {
          ndef.addEventListener("reading", event => {
            // Check if we want to write to this tag, or reject.
            ndef.write(data).then(resolve, reject).finally(() => ignoreRead = false);
          }, { once: true });
        });
      }

      await ndef.scan();
      try {
        await write("Hello World");
        console.log("We wrote to a tag!")
      } catch(err) {
        console.error("Something went wrong", err);
      }
    

타임아웃으로 쓰기 예약

쓰기 작업에 시간 제한을 설정하는 것이 유용할 때가 있습니다. 예: 사용자에게 태그를 터치하도록 요청하고 일정 시간 내에 태그가 발견되지 않으면 타임아웃시키는 경우입니다.

      const ndef = new NDEFReader();
      ndef.onreading = (event) => console.log("We read a tag!");

      function write(data, { timeout } = {}) {
        return new Promise((resolve, reject) => {
          const ctlr = new AbortController();
          ctlr.signal.onabort = () => reject("Time is up, bailing out!");
          setTimeout(() => ctlr.abort(), timeout);

          ndef.addEventListener("reading", event => {
            ndef.write(data, { signal: ctlr.signal }).then(resolve, reject);
          }, { once: true });
        });
      }

      await ndef.scan();
      try {
        // Let's wait for 5 seconds only.
        await write("Hello World", { timeout: 5_000 });
      } catch(err) {
        console.error("Something went wrong", err);
      } finally {
        console.log("We wrote to a tag!");
      }
    

스캔 오류 처리

이 예제는 {{NDEFReader/scan}} 프라미스가 거부되고 `readingerror`가 발생했을 때의 동작을 보여줍니다.

      const ndef = new NDEFReader();
      ndef.scan().then(() => {
        console.log("Scan started successfully.");
        ndef.onreadingerror = (event) => {
          console.log("Error! Cannot read data from the NFC tag. Try a different one?");
        };
        ndef.onreading = (event) => {
          console.log("NDEF message read.");
        };
      }).catch(error => {
        console.log(`Error! Scan failed to start: ${error}.`);
      });
    

한 번만 태그 읽기

이 예제는 단일 태그만 읽고 폴링을 중단하여 배터리 수명을 절약하는 편의 함수를 만드는 방법을 보여줍니다.

이 예제는 주어진 밀리초 이후 타임아웃하도록 쉽게 확장할 수 있습니다.

      const ndef = new NDEFReader();

      function read() {
        return new Promise((resolve, reject) => {
          const ctlr = new AbortController();
          ctlr.signal.onabort = reject;
          ndef.addEventListener("reading", event => {
            ctlr.abort();
            resolve(event);
          }, { once: true });
          ndef.scan({ signal: ctlr.signal }).catch(err => reject(err));
        });
      }

      read().then(({ serialNumber }) => {
        console.log(serialNumber);
      });
    

태그에서 데이터 읽고 빈 태그에 쓰기

이 예제는 태그에 저장될 수 있는 다양한 종류의 데이터를 읽는 방법을 보여줍니다. 태그가 포맷되지 않았거나 빈 레코드를 포함하면 "Hello World"라는 텍스트 메시지를 씁니다.

      const ndef = new NDEFReader();
      await ndef.scan();
      ndef.onreading = async ({ message }) => {
        if (message.records.length == 0 ||               // unformatted tag
            message.records[0].recordType == "empty") {  // empty record
          await ndef.write({
            records: [{ recordType: "text", data: "Hello World" }]
          });
          return;
        }

        const decoder = new TextDecoder();
        for (const record of message.records) {
          switch (record.recordType) {
            case "text":
              const textDecoder = new TextDecoder(record.encoding);
              console.log(`Text: ${textDecoder.decode(record.data)} (${record.lang})`);
              break;
            case "url":
              console.log(`URL: ${decoder.decode(record.data)}`);
              break;
            case "mime":
              if (record.mediaType === "application/json") {
                console.log(`JSON: ${JSON.parse(decoder.decode(record.data))}`);
              }
              else if (record.mediaType.startsWith("image/")) {
                const blob = new Blob([record.data], { type: record.mediaType });

                const img = document.createElement("img");
                img.src = URL.createObjectURL(blob);
                img.onload = () => window.URL.revokeObjectURL(this.src);

                document.body.appendChild(img);
              }
              else {
                console.log(`Media not handled`);
              }
              break;
            default:
              console.log(`Record not handled`);
          }
        }
      };
    

NFC 태그로 게임 진행 저장 및 복원

관련 데이터 소스를 필터링하려면 이 예제처럼 커스텀 레코드 식별자 "`/my-game-progress`"를 사용할 수 있습니다. 데이터를 읽으면 즉시 커스텀 NDEF 데이터 레이아웃으로 쓰기를 수행하여 게임 진행을 업데이트합니다.

      const ndef = new NDEFReader();
      await ndef.scan();
      ndef.onreading = async ({ message }) => {
        if (message.records[0]?.id !== "/my-game-progress")
          return;
        console.log(`Game state: ${ JSON.stringify(message.records) }`);

        const encoder = new TextEncoder();
        const newMessage = {
          records: [{
            id: "/my-game-progress",
            recordType: "mime",
            mediaType: "application/json",
            data: encoder.encode(JSON.stringify({
              level: 3,
              points: 4500,
              lives: 3
            }))
          }]
        };
        await ndef.write(newMessage);
        console.log("Message written");
      };
    

JSON(직렬화 및 역직렬화) 쓰기 및 읽기

JSON 데이터를 직렬화하고 역직렬화하여 저장하고 수신하는 것은 쉽습니다.

      const ndef = new NDEFReader();
      await ndef.scan();
      ndef.onreading = (event) => {
        const decoder = new TextDecoder();
        for (const record of event.message.records) {
          if (record.mediaType === "application/json") {
            const json = JSON.parse(decoder.decode(record.data));
            const article =/^[aeio]/i.test(json.title) ? "an" : "a";
            console.log(`${json.name} is ${article} ${json.title}`);
          }
        }
      };

      const encoder = new TextEncoder();
      await ndef.write({
        records: [
          {
            recordType: "mime",
            mediaType: "application/json",
            data: encoder.encode(JSON.stringify({
              name: "Benny Jensen",
              title: "Banker"
            }))
          },
          {
            recordType: "mime",
            mediaType: "application/json",
            data: encoder.encode(JSON.stringify({
              name: "Zoey Braun",
              title: "Engineer"
            }))
          }]
      });
    

데이터 쓰기 및 기존 데이터 출력

데이터를 쓰려면 NFC tag를 탭해야 합니다.

      const ndef = new NDEFReader();
      await ndef.scan();
      ndef.onreading = async (event) => {
        const decoder = new TextDecoder();
        for (const record of event.message.records) {
          console.log("Record type:  " + record.recordType);
          console.log("MIME type:    " + record.mediaType);
          console.log("=== data ===\n" + decoder.decode(record.data));
        }

        try {
          await ndef.write("Overriding data is fun!");
        } catch(error) {
          console.log(`Write failed :-( try again: ${error}.`);
        }
      };
    

NDEF 메시지 수신 중지

{{NDEFScanOptions/signal}}을 사용하여 3초 동안 NDEF 메시지를 읽습니다.

      const ndef = new NDEFReader();
      const ctrl = new AbortController();

      await ndef.scan({ signal: ctrl.signal });
      ndef.onreading = () => {
        console.log("NDEF message read.");
      };

      ctrl.signal.onabort = () => {
        console.log("We're done waiting for NDEF messages.");
      };

      // Stop listening to NDEF messages after 3s.
      setTimeout(() => ctrl.abort(), 3_000);
    

스마트 포스터 메시지 쓰기

      const ndef = new NDEFReader();
      const encoder = new TextEncoder();
      await ndef.write({ records: [
        {
          recordType: "smart-poster",  // Sp
          data: { records: [
            {
              recordType: "url",  // URL record for the Sp content
              data: "https://my.org/content/19911"
            },
            {
              recordType: "text",  // title record for the Sp content
              data: "Funny dance"
            },
            {
              recordType: ":t",  // type record, a local type to Sp
              data: encoder.encode("image/gif") // MIME type of the Sp content
            },
            {
              recordType: ":s",  // size record, a local type to Sp
              data: new Uint32Array([4096]) // byte size of Sp content
            },
            {
              recordType: ":act",  // action record, a local type to Sp
              // do the action, in this case open in the browser
              data: new Uint8Array([0])
            },
            {
              recordType: "mime", // icon record, a MIME type record
              mediaType: "image/png",
              data: await (await fetch("icon1.png")).arrayBuffer()
            },
            {
              recordType: "mime", // another icon record
              mediaType: "image/jpg",
              data: await (await fetch("icon2.jpg")).arrayBuffer()
            }
          ]}
        }
      ]});
    

NDEF 메시지를 페이로드로 갖는 외부 레코드 읽기

외부 타입 레코드는 애플리케이션 정의 레코드를 만드는 데 사용할 수 있으며, 이러한 레코드는 자체 NDEF records를 포함하는 NDEF message를 페이로드로 가질 수 있습니다. 이때 애플리케이션 컨텍스트에서 사용되는 local types를 포함할 수 있습니다.

스마트 포스터 레코드 타입도 페이로드로 NDEF message를 포함한다는 점에 유의하십시오.

NDEF는 레코드의 순서를 보장하지 않으므로, 관련 데이터를 캡슐화하기 위해 페이로드로 NDEF message를 갖는 외부 타입 레코드를 사용하는 것이 유용할 수 있습니다.

다음 예제는 텍스트 레코드와 스마트 포스터에서 차용한 정의의 로컬 타입 "act"(액션)를 포함하는 NDEF message를 페이로드로 갖는 소셜 포스트용 외부 레코드를 읽는 방법을 보여줍니다.

      const ndef = new NDEFReader();
      await ndef.scan();
      ndef.onreading = (event) => {
        const externalRecord = event.message.records.find(
          record => record.type == "example.com:smart-poster"
        );

        let action, text;

        for (const record of externalRecord.toRecords()) {
          if (record.recordType == "text") {
            const decoder = new TextDecoder(record.encoding);
            text = decoder.decode(record.data);
          } else if (record.recordType == ":act") {
            action = record.data.getUint8(0);
          }
        }

        switch (action) {
          case 0: // do the action
            console.log(`Post "${text}" to timeline`);
            break;
          case 1: // save for later
            console.log(`Save "${text}" as a draft`);
            break;
          case 2: // open for editing
            console.log(`Show editable post with "${text}"`);
            break;
        }
      };
    

NDEF 메시지를 페이로드로 갖는 외부 레코드 쓰기

외부 타입 레코드는 애플리케이션 정의 레코드를 생성하는 데 사용될 수 있으며, 이 레코드는 페이로드로 NDEF message를 포함할 수 있습니다.

      const ndef = new NDEFReader();
      await ndef.write({ records: [
        {
          recordType: "example.game:a",
          data: {
            records: [
              {
                recordType: "url",
                data: "https://example.game/42"
              },
              {
                recordType: "text",
                data: "Game context given here"
              },
              {
                recordType: "mime",
                mediaType: "image/png",
                data: getImageBytes(fromURL)
              }
            ]
          }
        }
      ]});
    

외부 레코드 내부의 unknown 레코드 쓰기 및 읽기

개발자가 의미를 알고 있으므로 MIME 타입을 지정하지 않아도 되는 경우, 외부 타입 레코드 내부에 unknown 타입 레코드를 사용하는 것이 유용할 수 있습니다.

      const encoder = new TextEncoder();
      const ndef = new NDEFReader();
      await ndef.write({ records: [
        {
          recordType: "example.com:shoppingItem", // External record
          data: {
            records: [
              {
                recordType: "unknown", // Shopping item name
                data: encoder.encode("Food")
              },
              {
                recordType: "unknown", // Shopping item description
                data: encoder.encode("Provide nutritional support for an organism.")
              }
            ]
          }
        }
      ]});
    
      const ndef = new NDEFReader();
      await ndef.scan();
      ndef.onreading = (event) => {
        const shoppingItemRecord = event.message.records[0];
        if (shoppingItemRecord?.recordType !== "example.com:shoppingItem")
          return;

        const [nameRecord, descriptionRecord] = shoppingItemRecord.toRecords();

        const decoder = new TextDecoder();
        console.log("Item name: " + decoder.decode(nameRecord.data));
        console.log("Item description: " + decoder.decode(descriptionRecord.data));
      };
    

NFC 태그를 영구적으로 읽기 전용으로 만들기

NFC 태그를 영구적으로 읽기 전용으로 만드는 것은 간단합니다.

      const ndef = new NDEFReader();
      ndef.makeReadOnly().then(() => {
        console.log("NFC tag has been made permanently read-only.");
      }).catch(error => {
        console.log(`Operation failed: ${error}`);
      });
    
      const ndef = new NDEFReader();
      try {
        await ndef.write("Hello world");
        console.log("Message written.");
        await ndef.makeReadOnly();
        console.log("NFC tag has been made permanently read-only after writing to it.");
      } catch (error) {
        console.log(`Operation failed: ${error}`);
      }
    

데이터 표현

NDEFMessage 인터페이스

모든 NDEF message의 내용은 NDEFMessage 인터페이스를 통해 노출됩니다:

      [SecureContext, Exposed=Window]
      interface NDEFMessage {
        constructor(NDEFMessageInit messageInit);
        readonly attribute FrozenArray<NDEFRecord> records;
      };

      dictionary NDEFMessageInit {
        required sequence<NDEFRecordInit> records;
      };
    

records 속성은 NDEF message를 정의하는 listNDEF record들을 나타냅니다.

NDEFMessageInit 딕셔너리는 NDEF message를 초기화하는 데 사용됩니다.

NDEFRecord 인터페이스

모든 NDEF record의 내용은 NDEFRecord 인터페이스를 통해 노출됩니다:

      [SecureContext, Exposed=Window]
      interface NDEFRecord {
        constructor(NDEFRecordInit recordInit);

        readonly attribute USVString recordType;
        readonly attribute USVString? mediaType;
        readonly attribute USVString? id;
        readonly attribute DataView? data;

        readonly attribute USVString? encoding;
        readonly attribute USVString? lang;

        sequence<NDEFRecord>? toRecords();
      };

      dictionary NDEFRecordInit {
        required USVString recordType;
        USVString mediaType;
        USVString id;

        USVString encoding;
        USVString lang;

        any data; // DOMString or BufferSource or NDEFMessageInit
      };
    

mediaType 속성은 NDEF record 페이로드의 MIME type을 나타냅니다.

recordType 속성은 NDEF record 타입을 나타냅니다.

id 속성은 record identifier를 나타내며, 이는 절대 또는 상대 URL입니다. 식별자의 고유성 요구는 생성자에 의해 보장되며, 본 사양 자체가 보장하지는 않습니다.

NFC NDEF 사양은 record identifier 대신 "message identifier"와 "payload identifier"라는 용어를 사용하지만, 식별자는 메시지(레코드들의 집합)가 아니라 각 레코드에 연결되며 페이로드가 없을 때도 존재할 수 있습니다.

encoding 속성은 페이로드가 텍스트 데이터인 경우에 페이로드 인코딩에 사용된 [=encoding/name|encoding name=]을 나타냅니다.

lang 속성은 페이로드가 인코딩된 경우의 [=language tag=]를 나타냅니다.

language tag는 [[BCP47]] 사양에 정의된 Language-Tag 생성 규칙과 일치하는 string입니다 (가능한 값의 권위 있는 목록은 IANA Language Subtag Registry 참조). 즉, 언어 범위는 하나 이상의 subtags로 구성되며 U+002D HYPHEN-MINUS("-")로 구분됩니다. 예를 들어 'en-AU'는 호주에서 쓰이는 영어를 나타내고, 'fr-CA'는 캐나다에서 쓰이는 프랑스어를 나타냅니다. [[RFC5646]] 섹션 2.2.9의 유효성 기준을 만족하고 IANA 레지스트리를 참조하지 않고도 검증할 수 있는 언어 태그는 구조적으로 유효한 것으로 간주됩니다.

data 속성은 PAYLOAD field 데이터를 나타냅니다.

toRecords() 메서드는 호출될 때, 해당 NDEF Record와 함께 convert NDEFRecord.data bytes를 실행한 결과를 반드시 반환해야 합니다.

NDEFRecordInit 딕셔너리는 record typerecordType과 선택적 record identifierid 및 페이로드 데이터인 dataNDEF record를 초기화하는 데 사용됩니다.

또한 특정 record types에만 적용되는 선택적 필드들이 추가로 존재합니다:
  • "mime": 선택적 MIME typemediaType.
  • "text": 선택적 [=encoding/label|encoding label=]인 encoding 및 [=language tag=]인 lang.

NDEFRecordInit의 데이터 타입에서 NDEF record 타입으로의 매핑은 데이터 처리를 다루는 알고리즘 단계들에 제시되어 있으며 [[[#steps-receiving]]] 및 [[[#writing-content]]] 섹션에 기술되어 있습니다.

convert NDEFRecord.data bytes를 수행하려면, |record:NDEFRecord|이 주어졌을 때 다음 단계들을 실행합니다:

  1. |bytes:byte sequence|를 record의 data 속성 값으로 둡니다.
  2. |recordType:record type|을 |record|의 recordType 속성 값으로 둡니다.
  3. |recordType| 값이 "`smart-poster`"이면, |bytes|와 `"smart-poster"`를 주고 parse records from bytes를 실행한 결과를 반환합니다.
  4. |recordType|에 대해 validate external type를 실행한 결과가 true이면, |bytes|와 `"external"`을 주고 parse records from bytes를 실행한 결과를 반환합니다.
  5. 그렇지 않으면, [= exception/throw =] {{"NotSupportedError"}}를 발생시킵니다.

record type 문자열

이 문자열은 NDEFRecord의 허용되는 레코드 타입을 정의합니다. [[[#data-mapping]]] 섹션은 이것이 NDEF record 타입으로 어떻게 매핑되는지 설명합니다.

표준화된 well known type name은 다음 중 하나일 수 있습니다:

"empty" 문자열
empty NDEFRecord를 나타내는 값입니다.
"text" 문자열
Text record를 나타내는 값입니다.
"url" 문자열
URI record를 나타내는 값입니다.
"smart-poster" 문자열
Smart poster 레코드를 나타내는 값입니다.
"absolute-url" 문자열
absolute-URL record를 나타내는 값입니다.
"mime" 문자열
MIME type record를 나타내는 값입니다.
"unknown" 문자열
unknown record를 나타내는 값입니다.

[=well known type names=] 외에도 조직이 커스텀 external type name을 생성할 수 있습니다. 이는 도메인 이름과 커스텀 타입 이름을 콜론 `U+003A` (`:`)로 구분하여 이어붙인 문자열입니다.

애플리케이션은 또한 local type name을 사용할 수 있으며, 이는 반드시 소문자 문자나 숫자로 시작해야 하는 문자열로, NFC Forum의 [=local type=]에 대한 타입을 나타냅니다. 이는 일반적으로 상위 NDEFRecord의 페이로드인 NDEFMessage 내의 레코드에서 사용됩니다(예: smart poster). local type의 컨텍스트는 이 레코드가 속한 페이로드인 NDEFMessage의 부모 레코드이며, local type name은 해당 컨텍스트에서 사용되는 다른 타입 이름들과 충돌하지 않아야 합니다.

Web NFC의 어떤 구현도 청크된 레코드를 단일 논리 레코드로 투명하게 노출해야 하므로, unchanged record는 명시적으로 표현되지 않습니다.

두 개의 well-known type records (모든 NFC Forum의 local typeglobal type 포함)는 문자 단위로 대소문자 구분 방식으로 비교되어야 합니다.

두 개의 external types는 문자 단위로 대소문자 구분 없이 비교되어야 합니다.

모든 well-known type recordexternal type의 이진 표현은 상대 URI(RFC 3986)로 기록되어야 하며, 네임스페이스 식별자(NID) "`nfc`"와 네임스페이스 특정 문자열(NSS) "`wkt`" 및 "`ext`" 접두사, 즉 "`urn:nfc:wkt:`" 및 "`urn:nfc:ext:`" 접두사를 생략해야 합니다. 예를 들어, "`urn:nfc:ext:company.com:a`"는 "`company.com:a`"로 저장되며, Text record의 well-known type record인 "`urn:nfc:wkt:T`"는 "`T`"로 저장됩니다.

데이터 매핑

[[[#writing-content]]] 섹션에서 사용되는 것처럼, NDEFRecordInit의 데이터 타입에서 NDEF record 타입으로의 매핑은 다음과 같습니다:

{{recordType}} {{mediaType}} {{data}} record type [=TNF field=] [=TYPE field=]
"`empty`" unused unused Empty record 0 unused
"`text`" unused {{BufferSource}} or
{{DOMString}}
[=Well-known type record=] 1 "`T`"
"`url`" unused {{DOMString}} [=Well-known type record=] 1 "`U`"
"`smart-poster`" unused {{NDEFMessageInit}} [=Well-known type record=] 1 "`Sp`"
[=local type name=] prefixed by a colon `U+003A` (`:`), e.g., "`:act`", "`:s`", and "`:t`" unused {{BufferSource}} or {{NDEFMessageInit}} [=Local type=] record* 1 [=local type name=], e.g., "`act`", "`s`", and "`t`"
"`mime`" [= MIME type =] {{BufferSource}} MIME type record 2 [= MIME type =]
"`absolute-url`" unused {{DOMString}} url Absolute-URL record 3 [=Absolute-URL=]
[=external type name=] unused {{BufferSource}} or
{{NDEFMessageInit}}
External type record 4 [=external type name=]
"`unknown`" unused {{BufferSource}} [=Unknown record=] 5 unused

* [=local type=] 레코드는 다른 레코드의 NDEFMessage 페이로드에 포함되어야 합니다.

들어오는 NDEF message들에 대해 사용되는 바와 같이, NDEF record 타입에서 NDEFRecord로의 매핑은 [[[#steps-receiving]]] 섹션에 설명된 대로 다음과 같습니다.

record type [=TNF field=]
[=TYPE field=] {{recordType}} {{mediaType}}
[=Empty record=] 0 unused "`empty`" null
[=Well-known type record=] 1 "`T`" "`text`" null
[=Well-known type record=] 1 "`U`" "`url`" null
[=Well-known type record=] 1 "`Sp`" "`smart-poster`" null
[=Local type=] record* 1 [=local type name=], e.g., "`act`", "`s`", and "`t`" [=local type name=] prefixed by a colon `U+003A` (`:`), e.g., "`:act`", "`:s`", and "`:t`" null
[=MIME type record=] 2 [=MIME type=] "`mime`" The MIME type used in the NDEF record
[=Absolute-URL record=] 3 URL "`absolute-url`" null
[=External type record=] 4 [=external type name=] [=external type name=] null
Unknown record 5 unused "`unknown`" null

NDEFReader 객체

NDEFReader는 태그와 같은 장치가 자기 유도 필드 범위에 있을 때 browsing context에 NFC 기능을 제공하여 NDEF messages를 읽을 수 있게 합니다. 또한, 범위 내의 NFC tagNDEF messages를 쓰는 데에도 사용됩니다.

    typedef (DOMString or BufferSource or NDEFMessageInit) NDEFMessageSource;

    [SecureContext, Exposed=Window]
    interface NDEFReader : EventTarget {
      constructor();

      attribute EventHandler onreading;
      attribute EventHandler onreadingerror;

      Promise<undefined> scan(optional NDEFScanOptions options={});
      Promise<undefined> write(NDEFMessageSource message,
                                     optional NDEFWriteOptions options={});
      Promise<undefined> makeReadOnly(optional NDEFMakeReadOnlyOptions options={});
    };

    [SecureContext, Exposed=Window]
    interface NDEFReadingEvent : Event {
      constructor(DOMString type, NDEFReadingEventInit readingEventInitDict);

      readonly attribute DOMString serialNumber;
      [SameObject] readonly attribute NDEFMessage message;
    };

    dictionary NDEFReadingEventInit : EventInit {
      DOMString? serialNumber = "";
      required NDEFMessageInit message;
    };
  

NDEFMessageSource는 {{NDEFReader/write()}} 메서드가 받아들이는 인자 타입을 나타내는 유니온 타입입니다.

NDEFReadingEvent는 새로운 NFC 판독 시 디스패치되는 이벤트입니다. serialNumber 속성은 충돌 방지와 식별에 사용되는 장치의 일련번호를 나타내며, 사용 불가한 경우 빈 문자열이 됩니다. messageNDEFMessage 객체입니다.

NDEFReadingEventInitmessage 멤버를 통해 일련번호와 NDEFMessageInit 데이터를 갖는 새로운 이벤트를 초기화하는 데 사용됩니다. serialNumber가 존재하지 않거나 null이면, 이벤트 초기화에 빈 문자열이 사용됩니다.

대부분의 태그는 안정적인 고유 식별자(UID)를 갖지만, 모두가 그런 것은 아니며 일부 태그는 읽을 때마다 무작위 숫자를 생성하기도 합니다. 일련번호는 보통 4개 또는 7개의 숫자로 구성되며, ‘:’로 구분됩니다.

{{NDEFReader}} 객체는 다음과 같은 내부 슬롯을 가집니다:

내부 슬롯 초기값 설명 (비규범적)
[[\WriteOptions]] null 쓰기를 위한 {{NDEFWriteOptions}} 값.
[[\WriteMessage]] null 쓰여질 {{NDEFMessage}}. 초기에는 설정되지 않음.

onreading은 새로운 판독이 가능함을 알리기 위해 호출되는 {{EventHandler}}입니다.

onreadingerror는 판독 중 오류가 발생했음을 알리기 위해 호출되는 {{EventHandler}}입니다.

설정 객체에 연관된 NFC 상태

NFC를 지원하는 browsing contextactive documentrelevant settings object에는 다음의 내부 슬롯을 갖는 연관된 NFC state 레코드가 있습니다:

내부 슬롯 초기값 설명 (비규범적)
[[\Suspended]] false NFC 기능이 일시 중단되었는지를 나타내는 불리언 플래그로, 초기값은 false입니다.
[[\ActivatedReaderList]] 비어 있는 set {{NDEFReader}} 인스턴스들의 set.
[[\PendingWrite]] empty |promise:Promise|와 |writer:NDEFReader|로 이루어진 튜플로, |promise|는 대기 중인 {{Promise}}를, |writer|는 {{NDEFReader}}를 보유합니다.
[[\PendingMakeReadOnly]] empty |promise:Promise|와 |writer:NDEFReader|로 이루어진 튜플로, |promise|는 대기 중인 {{Promise}}를, |writer|는 {{NDEFReader}}를 보유합니다.

activated reader objects[[\ActivatedReaderList]] 내부 슬롯의 값입니다.

pending write tuple[[\PendingWrite]] 내부 슬롯의 값입니다.

pending makeReadOnly tuple[[\PendingMakeReadOnly]] 내부 슬롯의 값입니다.

NFC is suspended[[\Suspended]] 내부 슬롯이 true일 때를 의미합니다.

suspend NFC 하려면, [[\Suspended]] 내부 슬롯을 true로 설정합니다.

resume NFC 하려면, [[\Suspended]] 내부 슬롯을 false로 설정합니다.

내부 슬롯은 이 사양에서 표기상의 목적으로만 사용되며, 구현이 이를 명시적인 내부 속성으로 매핑할 필요는 없습니다.

NFC 어댑터 처리

구현은 이 사양에 기술된 알고리즘 단계에 따라 여러 NFC adapter를 사용할 수 있습니다.

권한 획득

Web NFC API는 [=default powerful feature=]이며, [=powerful feature/name=]은 "nfc"입니다.

obtain permission 하려면, 다음 단계를 수행합니다:
  1. |state:PermissionState|를 "nfc"로 [=getting the current permission state=] 한 결과로 둡니다.
  2. |state|가 {{PermissionState["granted"]}}(즉, [[[PERMISSIONS]]] API를 통해 originglobal object에 권한이 부여됨)이라면 true를 반환합니다.
  3. 그렇지 않고 |state|가 {{PermissionState["prompt"]}}라면, 사용자에게 "nfc"에 대한 request permission to use를 선택적으로 요청합니다. 허가되면 true를 반환합니다.

    request permission 단계는 아직 명확히 정의되지 않았습니다. 이 시점에서 UA는 주어진 originglobal object에 대해 "nfc"에 사용할 정책을 사용자에게 묻고, 사용자가 허가하면 true를 반환합니다.

  4. false를 반환합니다.

가시성 변경 처리

이 사양에 대한 [=page visibility change steps=]은 문자열 |visibilityState|와 {{Document}} |document|가 주어졌을 때 다음과 같습니다:

  1. |visibilityState|가 `"visible"`이면, resume NFC하고 이 단계들을 중단합니다.
  2. 그렇지 않다면, 다음 단계들을 수행합니다:
    1. Suspend NFC.
    2. abort a pending write operation 시도를 합니다.
    3. abort a pending make read-only operation 시도를 합니다.

suspended라는 용어는 NFC 작업이 일시 중단된 상태를 의미하며, 이때 NDEFReader에 의해 어떤 NFC content도 쓰이지 않고, 일시 중단 중에는 어떤 {{NFC content}}도 어떤 {{NDEFReader}}에도 제공되지 않습니다.

대기 중인 쓰기 작업 중단

abort a pending write operation을 시도하려면, 다음 단계들을 실행합니다:
  1. pending write tuple |tuple|이 없다면, 이 단계들을 중단합니다.
  2. |tuple|의 writer가 이미 진행 중인 NFC 데이터 전송을 시작했다면, 이 단계들을 중단합니다.
  3. |tuple|의 promise를 {{"AbortError"}}로 거부하고 이 단계들을 중단합니다.

    프라미스를 거부하면 pending write tuple이 정리됩니다.

대기 중인 읽기 전용 설정 작업 중단

abort a pending make read-only operation을 시도하려면, 다음 단계들을 실행합니다:
  1. pending makeReadOnly tuple |tuple|이 없다면, 이 단계들을 중단합니다.
  2. |tuple|의 writer가 이미 NFC 태그를 영구적으로 읽기 전용으로 만들었다면, 이 단계들을 중단합니다.
  3. |tuple|의 promise를 {{"AbortError"}}로 거부하고 이 단계들을 중단합니다.

    프라미스를 거부하면 pending makeReadOnly tuple이 정리됩니다.

NFC 해제

environment settings object에서 release NFC하려면, 다음 단계들을 수행합니다:

  1. Suspend NFC.
  2. abort a pending write operation 시도를 합니다.
  3. abort a pending make read-only operation 시도를 합니다.
  4. activated reader objects를 비웁니다.
  5. 기반 플랫폼에서 NFC 리소스를 해제합니다.

UA는 문서의 relevant settings object에 대해 추가적인 unloading document cleanup steps으로 release NFC를 수행해야 합니다.

NDEFWriteOptions 딕셔너리

      dictionary NDEFWriteOptions {
        boolean overwrite = true;
        AbortSignal? signal;
      };
    

overwrite 속성의 값이 false이면, 쓰기 알고리즘NFC tag를 읽어 그 위에 NDEF 레코드가 있는지 확인하며, 있다면 대기 중인 쓰기를 실행하지 않습니다.

signal 속성은 {{NDEFReader/write()}} 작업을 중단할 수 있도록 합니다.

NDEFMakeReadOnlyOptions 딕셔너리

      dictionary NDEFMakeReadOnlyOptions {
        AbortSignal? signal;
      };
    

signal 속성은 {{NDEFReader/makeReadOnly()}} 작업을 중단할 수 있도록 합니다.

NDEFScanOptions 딕셔너리

        dictionary NDEFScanOptions {
          AbortSignal signal;
        };
      

signal 속성은 {{NDEFReader/scan()}} 작업을 중단할 수 있도록 합니다.

Writing content

이 섹션은 타이머가 만료되기 전에 다음으로 근접 범위에 들어왔을 때 NFC tagNDEF message를 쓰는 방법을 설명합니다. 어느 시점이든 현재 메시지가 전송되거나 쓰기가 중단될 때까지 하나의 origin에 대해 쓰기로 설정될 수 있는 NDEF message는 최대 하나뿐입니다.

write() 메서드

NDEFReader.write 메서드는 호출될 때 write a message 알고리즘을 반드시 실행해야 합니다:
  1. |p:Promise|를 새로운 {{Promise}} 객체로 둡니다.
  2. 현재 활성 top-level browsing context에서 실행 중이 아니면, |p|를 {{"InvalidStateError"}}로 거부하고 |p|를 반환합니다.
  3. |message:NDEFMessageSource|를 첫 번째 인자로 둡니다.
  4. |options:NDEFWriteOptions|를 두 번째 인자로 둡니다.
  5. |signal:AbortSignal|을, 존재한다면 |options|와 같은 이름의 딕셔너리 멤버로, 그렇지 않다면 null로 둡니다.
  6. |signal|이 [= AbortSignal/aborted =]라면, |signal|의 [=AbortSignal/abort reason=]으로 |p|를 거부하고 |p|를 반환합니다.
  7. |signal|이 null이 아니라면, 다음 중단 단계를 추가합니다:
    1. environment settings object에서 abort a pending write operation을 실행합니다.
  8. |p|에 [=promise/React=]합니다:
    1. |p|가 해결(이행 또는 거부)되었다면, 존재할 경우 pending write tuple을 정리합니다.
  9. |p|를 반환하고 다음 단계들을 in parallel로 실행합니다:
    1. obtain permission 단계가 false를 반환하면, |p|를 {{"NotAllowedError"}}로 거부하고 이 단계들을 중단합니다.
    2. 기반 NFC Adapter가 없거나 연결을 수립할 수 없다면, |p|를 {{"NotSupportedError"}}로 거부하고 이 단계들을 중단합니다.
    3. UA가 기반 NFC Adapter에 접근할 수 없다면(예: 사용자 환경설정), |p|를 {{"NotReadableError"}}로 거부하고 이 단계들을 중단합니다.
    4. 기반 NFC Adapter가 데이터 푸시를 지원하지 않으면, |p|를 {{"NotSupportedError"}}로 거부하고 이 단계들을 중단합니다.
    5. 구현은 |p|를 {{"NotSupportedError"}}로 거부하고 이 단계들을 중단할 수 있습니다.

      UA는 이 시점에서 메시지 쓰기를 중단할 수 있습니다. 종료 사유는 구현 상세에 따릅니다. 예를 들어, 구현이 요청된 작업을 지원할 수 없을 수 있습니다.

    6. |message|, `""`, `0`으로 create NDEF message를 호출한 결과로 UA가 생성할 NDEF message에 대한 표기인 |output|을 둡니다. 예외가 발생하면, 그 예외로 |p|를 거부하고 이 단계들을 중단합니다.
    7. abort a pending write operation을 시도합니다.

      쓰기는 이전에 설정된 모든 쓰기 작업을 대체합니다.

    8. `this`.[[\WriteOptions]]를 |options|로 설정합니다.
    9. `this`.[[\WriteMessage]]를 |output|으로 설정합니다.
    10. pending write tuple을 (`this`, |p|)로 설정합니다.
    11. NFC tag |device|가 통신 범위에 들어올 때마다 start the NFC write 단계를 실행합니다.

      NFC is suspended인 경우, 사용자가 프라미스를 중단하거나 NFC tag가 통신 범위에 들어올 때까지 계속 대기합니다.

start the NFC write를 수행하려면, 다음 단계들을 실행합니다:
  1. |p:Promise|를 pending write tuple의 promise로 둡니다.
  2. |writer|를 pending write tuple의 writer로 둡니다.
  3. |options:NDEFWriteOptions|를 |writer|.[[\WriteOptions]]로 둡니다.
  4. 근접 범위의 NFC tag가 포맷팅 또는 쓰기를 위한 NDEF 기술을 노출하지 않으면, |p|를 {{"NotSupportedError"}}로 거부하고 |p|를 반환합니다.
  5. NFC is not suspended임을 검증합니다.
  6. 성공 시 다음 단계들을 실행합니다:
    1. |device|가 NFC tag이고 |options|의 overwrite가 false이면, 태그를 읽어 태그에 NDEF 레코드가 있는지 확인합니다. 있다면 |p|를 {{"NotAllowedError"}}로 거부하고 |p|를 반환합니다.
    2. |output:NDEFMessage|를 |writer|.[[\WriteMessage]]로 둡니다.
    3. |device|와 통신 범위에 있는 NFC adapter를 사용해 |output|을 버퍼로 하여 |device|로 데이터 전송을 시작합니다.

      근접 범위의 NFC tag가 비포맷 상태이고 NDEF 포맷이 가능하면, 포맷한 뒤 |output|을 버퍼로써 기록합니다.

      여러 어댑터는 사용자가 순차적으로 사용하는 것이 좋습니다. 서로 다른 연결된 NFC adapter 두 개 이상에서 동시에 탭이 발생할 가능성은 매우 낮습니다. 만약 발생한다면, 사용자는 바람직하게는 한 번에 하나의 장치에 대해 성공할 때까지 탭을 반복해야 할 수 있습니다. 여기서의 오류는 작업을 반복할 필요가 있음을 나타냅니다. 그렇지 않으면 사용자가 모든 연결된 NFC adapter에서 작업이 성공했다고 생각할 수 있습니다.

    4. 전송이 실패하면, |p|를 {{"NetworkError"}}로 거부하고 이 단계들을 중단합니다.
    5. 전송이 완료되면, |p|를 이행합니다.

NDEF 메시지 생성

|source:NDEFMessageSource|, |context:string|, |recordsDepth:unsigned short|가 주어졌을 때 create NDEF message하려면, 다음 단계들을 실행합니다:
  1. |source:NDEFMessageSource|의 타입에 따라 분기합니다:
    {{DOMString}}
    • |textRecord|를 |recordType|을 "`text`"로, |data|를 |source|로 설정하여 초기화한 NDEFRecord로 둡니다.
    • |records|를 « |textRecord| » 목록으로 둡니다.
    • |source|의 records를 |records|로 설정합니다.
    {{BufferSource}}
    • |mimeRecord|를 |recordType|을 "`mime`", |data|를 |source|, |mediaType|을 "`application/octet-stream`"으로 설정하여 초기화한 NDEFRecord로 둡니다.
    • |records|를 « |mimeRecord| » 목록으로 둡니다.
    • |source|의 records를 |records|로 설정합니다.
    {{NDEFMessageInit}}
    • |source|의 records가 [= list/is empty =]라면, {{TypeError}}를 [= exception/throw =] 합니다.
    • |recordsDepth|를 1 증가시킵니다.
    • |recordsDepth| > `32`라면, {{TypeError}}를 [= exception/throw =] 합니다.
    unmatched type
    • {{TypeError}}를 [= exception/throw =] 합니다.
  2. 이 단계들의 결과로 UA가 생성할 NDEF message에 대한 표기를 |output|으로 둡니다.
  3. [= list/For each =] |record:NDEFRecordInit| ∈ |source|의 records list에 대해, 다음 단계들을 실행합니다:
    1. |record:NDEFRecordInit|, |context|, |recordsDepth|로 create NDEF record를 실행한 결과를 |ndef|로 두거나, 기반 플랫폼이 |ndef|에 동등한 값을 제공하도록 합니다. 알고리즘이 예외 |e|를 던지면, |promise|를 |e|로 거부하고 이 단계들을 중단합니다.
    2. |output|에 |ndef|를 추가합니다.
  4. |output|과 |context|로 check created records를 실행했을 때 |error: Error|를 던지면, |promise|를 |error|로 거부하고 이 단계들을 중단합니다.
  5. |output|을 반환합니다.

생성된 레코드 점검

|records: NDEFRecord sequence|와 |context: string|가 주어졌을 때 check created records를 수행하려면, 다음 단계들을 실행합니다:
  1. |context|가 `"smart-poster"`이고 |records|에 정확히 하나의 URI record가 없거나, 하나 초과의 type record, size record, action record가 있으면, {{TypeError}}를 [= exception/throw =] 합니다.
  2. |context|가 `"smart-poster"`이면, URI record를 |records|의 맨 앞으로 이동합니다.

Web NFC는 현재 smart poster 내에서 external typelocal type 레코드 쓰기를 허용합니다. 또한, empty records도 허용됩니다. 애플리케이션은 smart poster 내부의 추가 레코드를 무시할 수 있습니다.

아이콘 레코드의 미디어 타입은 `"image/"`나 `"video/"`로 제한될 수 있지만, [[NDEF-SMARTPOSTER]] 사양은 실제로 smart poster 내에서 다른 미디어 타입 레코드도 허용하며, 예를 들어 vCard 연락처 카드의 MIME types 같은 애플리케이션별 방식으로 처리될 수 있습니다.

NDEF 레코드 생성

|record:NDEFRecordInit|, |context:string|, |recordsDepth:unsigned short|가 주어졌을 때 create NDEF record를 수행하려면, 다음 단계들을 실행합니다:
  1. UA가 생성할 NDEF record의 표현을 |ndef|로 둡니다.
  2. |record|의 id가 undefined가 아니라면:
    • |identifier|를 |record|의 id로 둡니다.
    • |ndef|의 IL field를 `1`로 설정합니다.
    • |ndef|의 ID LENGTH field를 |identifier|의 길이로 설정합니다.
    • |ndef|의 ID field를 |identifier|로 설정합니다.
  3. |record|의 recordType에 따라 아래 지정된 알고리즘을 |record|, |ndef|, |context|, |recordsDepth|와 함께 호출하고 그 결과를 반환합니다. 예외 |e|가 발생하면, |promise|를 |e|로 거부하고 이 단계들을 중단합니다.
    "`empty`"
    "`text`"
    "`url`"
    "`mime`"
    "`smart-poster`"
    "`absolute-url`"
  4. |record|의 recordType이 콜론 `U+003A` (`:`)로 시작한다면:
    • |context|가 `""`(즉, |record|가 다른 NDEF record의 페이로드가 아님)이면, {{TypeError}}로 |promise|를 거부하고 이 단계들을 중단합니다.
    • |record|의 recordType에 대해 validate local type 단계를 실행한 결과가 false이면, {{TypeError}}로 |promise|를 거부하고 이 단계들을 중단합니다.
    • |record|, |ndef|, |context|, |recordsDepth|로 map local type to NDEF를 실행한 결과를 반환합니다. 예외 |e|가 발생하면, |promise|를 |e|로 거부하고 이 단계들을 중단합니다.
  5. |record|의 recordType에 대해 validate external type을 실행한 결과가 true이면, |record|, |ndef|, |context|, |recordsDepth|로 map external data to NDEF를 반환합니다. 예외 |e|가 발생하면, |promise|를 |e|로 거부하고 이 단계들을 중단합니다.
  6. 그렇지 않다면, {{TypeError}}를 [= exception/throw =] 하고 이 단계들을 중단합니다.

외부 타입 검증

[[NFC-RTD]]는 외부 타입이 발급 기관의 [=domain=] 이름, 콜론 `U+003A` (`:`), 그리고 최소 한 글자 이상의 타입 이름을 포함해야 함을 규정하며, 예로 "`w3.org:member`" 같은 형태이고 모두 ASCII 문자로 저장됩니다.

[[NFC-RTD]]는 또한 “`urn:nfc:ext:`” URN 접두사를 규정하지만, 이는 NDEF record에 저장되지 않으므로 Web NFC 애플리케이션은 external type records를 생성할 때 URN 접두사를 지정하지 않아야 합니다.

[[NFC-RTD]]는 예를 들어 NDEF messages를 읽을 때처럼 외부 타입 이름이 “`urn:nfc:ext:`” URN 접두사로 표현될 것을 요구합니다. 그러나 external type recordsTNF FIELD가 `0x04`로 설정되어 구분되므로, 타입 이름 충돌에 대한 위험은 보이지 않습니다. 또한 웹에서 URN 사용을 피하라는 W3C TAG 권고가 있습니다. 따라서 Web NFC는 NDEF messages를 읽거나 쓸 때 URN 접두사를 사용하지 않습니다.

|input:USVString|이 주어졌을 때 split external type을 수행하려면, 다음 단계들을 실행합니다:

  1. |input|에 `U+003A` (`:`)가 없으면, 실패를 반환합니다.
  2. |domain|을 |input|의 시작부터 첫 `U+003A` (`:`) 직전까지로 둡니다.
  3. |type|을 첫 `U+003A` (`:`) 이후부터 |input|의 끝까지(있다면)로 둡니다.
  4. |domain|과 |type|의 쌍을 반환합니다.

|input:USVString|이 주어졌을 때 validate external type을 수행하려면, 다음 단계들을 실행합니다:

  1. [=split external type=]을 실행한 결과로 |domain|과 |type|을 얻거나, 실패면 false를 반환합니다.
  2. |domain|이 [=valid domain string=]이 아니면 false를 반환합니다.
  3. |type|에 [=ASCII alphanumeric=]이 아닌 [=code points=]가 있거나, `U+0024` (`$`), `U+0027` (`'`), `U+0028` `LEFT PARENTHESIS` (`(`), `U+0029` `RIGHT PARENTHESIS` (`)`), `U+002A` (`*`), `U+002B` (`+`), `U+002C` (`,`), `U+002D` (`-`), `U+002E` (`.`), `U+003B` (`;`), `U+003D` (`=`), `U+0040` (`@`), `U+005F` (`_`)이 포함되어 있으면 false를 반환합니다.
  4. true를 반환합니다.

로컬 타입 검증

|input:USVString|이 주어졌을 때 validate local type을 수행하려면, 다음 단계들을 실행합니다:

  1. |localTypeName|을 |input|의 첫 `U+003A` (`:`) 이후부터 |input|의 끝까지로 둡니다.
  2. |localTypeName|이 {{USVString}}이 아니거나 길이가 255바이트를 초과하면 false를 반환합니다.
  3. |localTypeName|이 소문자 문자 또는 숫자로 시작하지 않으면 false를 반환합니다.
  4. |input|이 자신이 포함된 NDEF message에 정의된 어떤 NDEF recordrecord type과 같다면 false를 반환합니다.
  5. true를 반환합니다.

빈 레코드를 NDEF로 매핑

|record:NDEFRecordInit|, |ndef|가 주어졌을 때 map empty record to NDEF를 수행하려면, 다음 단계들을 실행합니다:
  1. |record|의 mediaType이 undefined가 아니면, {{TypeError}}를 [= exception/throw =] 합니다.
  2. |record|의 id가 undefined가 아니면, {{TypeError}}를 [= exception/throw =] 합니다.
  3. |ndef|의 TNF field를 `0`(empty record)으로 설정합니다.
  4. |ndef|의 IL field를 `0`으로 설정합니다.
  5. |ndef|의 TYPE LENGTH fieldPAYLOAD LENGTH field를 `0`으로 설정하고, TYPE fieldPAYLOAD field를 생략합니다.
  6. |ndef|를 반환합니다.

문자열을 NDEF로 매핑

|record:NDEFRecordInit|, |ndef|가 주어졌을 때 map text to NDEF를 수행하려면, 다음 단계들을 실행합니다:

이는 클라이언트가 특별히 [=well-known type record=]에 텍스트를 쓰고자 할 때 유용합니다. 다른 선택지로는 명시적인 텍스트 MIME type과 함께 "`mime`" 값을 사용하는 것이 있으며, 예를 들어 "`text/xml`" 또는 "`text/vcard`"를 사용할 때 더 나은 구분이 가능합니다.

  1. |record|의 mediaType이 undefined가 아니면, {{TypeError}}를 [= exception/throw =] 합니다.
  2. |record|의 data 타입이 {{DOMString}} 또는 {{BufferSource}}가 아니면, {{TypeError}}를 [= exception/throw =] 하고 이 단계들을 중단합니다.
  3. |documentLanguage:string|을 [=document element=]의 lang 속성으로 둡니다.
  4. |documentLanguage|가 빈 문자열이면, "`en`"으로 설정합니다.
  5. |language:string|을 |record|의 lang이 [= map/exists =]하면 그 값으로, 아니면 |documentLanguage|로 둡니다.
  6. |record|의 data 타입에 따라 분기합니다:
    {{DOMString}}
    1. |record|의 encoding이 undefined가 아니면서 "`utf-8`"이 아니면, {{TypeError}}를 [= exception/throw =] 하고 이 단계들을 중단합니다.
    2. |encoding label:string|을 "`utf-8`"로 둡니다.
    {{BufferSource}}
    1. |encoding label:string|을 |record|의 encoding이 [= map/exists =]하면 그 값으로, 아니면 "`utf-8`"로 둡니다.
    2. |encoding label|이 "`utf-8`", "`utf-16`", "`utf-16le`", "`utf-16be`" 중 하나가 아니면 {{TypeError}}를 [= exception/throw =] 합니다.
  7. |encoding name|을 |encoding label|에서 obtained한 [=encoding/name|name=]으로 둡니다.
  8. |header:byte|를 다음 방식으로 구성한 byte로 둡니다:
    1. |encoding name|이 UTF-8과 같으면 비트 `7`을 `0`으로, 아니면 `1`로 설정합니다.
    2. 비트 `6`을 `0`(예약됨)으로 설정합니다.
    3. |languageLength:octet|을 |language| string의 길이로 둡니다.
    4. |languageLength|를 6비트에 저장할 수 없으면 (|languageLength| > 63), {{SyntaxError}}를 [= exception/throw =] 합니다.
    5. 비트 `5`부터 비트 `0`까지를 |languageLength|로 설정합니다.
  9. |data:byte sequence|를 빈 [= byte sequence =]로 둡니다.
    1. |data|의 첫 번째 byte(위치 0)를 |header|로 설정합니다.
    2. |data|의 위치 1(두 번째 byte)부터 위치 |languageLength|까지를 |language|로 설정합니다.
    3. |record|의 data 타입에 따라 분기합니다:
      {{DOMString}}
      1. |record|의 dataUTF-8 encode를 실행하여 얻은 byte stream을 |stream:byte stream|으로 둡니다.
      2. Read를 사용해 |stream|에서 |data|로 바이트를 읽어들입니다 (위치 |languageLength| + 1부터), readend-of-stream을 반환할 때까지 계속합니다.
      {{BufferSource}}
      1. |record|의 data에서 바이트를 |data|에 설정합니다(위치 |languageLength| + 1부터).
  10. |length:unsigned long|을 |data|의 [=byte sequence/length=]로 설정합니다.
    1. |ndef|의 TNF field를 `1`([=well-known type record=])로 설정합니다.
    2. |ndef|의 TYPE field를 "`T`"(`0x54`)로 설정합니다.
    3. |ndef|의 PAYLOAD LENGTH field를 |length|로 설정합니다.
    4. |length| > `0`이면, |ndef|의 PAYLOAD field를 |data|로 설정합니다.
  11. |ndef|를 반환합니다.

URL을 NDEF로 매핑

|record:NDEFRecordInit|, |ndef|가 주어졌을 때 map a URL to NDEF를 수행하려면, 다음 단계들을 실행합니다:
  1. |record|의 mediaType이 undefined가 아니면, {{TypeError}}를 [= exception/throw =] 합니다.
  2. |record|의 data가 {{DOMString}}이 아니면, {{TypeError}}를 [= exception/throw =] 합니다.
  3. |url:URL|을 |record|의 dataparsing한 결과로 둡니다.
  4. |url|이 실패이면, {{SyntaxError}}를 [= exception/throw =] 합니다.
  5. |serializedURL:string|을 |url|의 serialization으로 둡니다.
  6. [[[NFC-STANDARDS]]]의 URI Record Type Definition 사양 3.2.2절에 정의된 URI 접두사를 |serializedURL|과 대조합니다.
  7. |prefixString:string|을 일치한 접두사 또는 empty string으로 둡니다.
  8. |prefixByte:byte|를 해당 접두사 번호 또는 `0`으로 둡니다.
  9. |shortenedURL:string|을 |serializedURL|에서 |prefixString|을 문자열 시작에서 제거한 값으로 둡니다.
  10. |data:byte sequence|를 빈 [= byte sequence =]로 둡니다.
    1. |data|의 첫 번째 byte를 |prefixByte|로 설정합니다.
    2. |shortenedURL|에 UTF-8 encode를 실행하여 얻은 byte stream을 |stream:byte stream|으로 둡니다.
    3. Read를 사용해 |stream|에서 |data|로 바이트를 읽어들입니다(위치 1부터), readend-of-stream을 반환할 때까지.
  11. |length:unsigned long|을 |data|의 [=byte sequence/length=]로 설정합니다.
  12. |ndef|의 TNF field를 `1`([=well-known type record=])로 설정합니다.
  13. |ndef|의 TYPE field를 "`U`"(`0x55`)로 설정합니다.
  14. |ndef|의 PAYLOAD LENGTH field를 |length|로 설정합니다.
  15. |length| > `0`이면, |ndef|의 PAYLOAD field를 |data|로 설정합니다.
  16. |ndef|를 반환합니다.

이진 데이터를 NDEF로 매핑

|record:NDEFRecordInit|, |ndef|가 주어졌을 때 map binary data to NDEF를 수행하려면, 다음 단계들을 실행합니다:
  1. |record|의 data 타입이 {{BufferSource}}가 아니면, {{TypeError}}를 [= exception/throw =] 합니다.
  2. |mimeType|을 |record|의 mediaType에 대해 parse a MIME type을 실행한 결과로 둡니다.
  3. |mimeType|이 실패이면, 타입이 "`application`", 서브타입이 "`octet-stream`"인 새로운 MIME type record를 |mimeTypeRecord|로 둡니다.
  4. |arrayBuffer|를 |record|의 data로 설정합니다.
  5. |length:unsigned long|을 |arrayBuffer|.[[\ArrayBufferByteLength]]로 설정합니다.
  6. |data:byte sequence|를 |arrayBuffer|.[[\ArrayBufferData]]로 설정합니다.
  7. |ndef|의 TNF field를 `2`(MIME type)로 설정합니다.
  8. |ndef|의 TYPE field를 |mimeType|을 입력으로 하여 serialize a MIME type을 실행한 결과로 설정합니다.
  9. |ndef|의 PAYLOAD LENGTH field를 |length|로 설정합니다.
  10. |length| > `0`이면, |ndef|의 PAYLOAD field를 |data|로 설정합니다.
  11. |ndef|를 반환합니다.

외부 데이터를 NDEF로 매핑

|record:NDEFRecordInit|, |ndef|, |recordsDepth:unsigned short|가 주어졌을 때 map external data to NDEF를 수행하려면, 다음 단계들을 실행합니다:
  1. |record|의 mediaType이 undefined가 아니면, {{TypeError}}를 [= exception/throw =] 합니다.
  2. |domain|과 |type|을 |record|의 recordType에 대해 [=split external type=]을 실행한 결과로 둡니다.
  3. |domain|을 |domain|과 true를 인자로 하여 domain to ASCII를 실행한 결과로 둡니다.
  4. |customTypeName|을 |domain|, "`:`", |type|을 이어붙인 값으로 둡니다.
  5. |customTypeName|이 {{USVString}}이 아니거나 길이가 255바이트를 초과하면, {{TypeError}}를 [= exception/throw =] 합니다.
  6. |ndef|의 TYPE field를 |customTypeName|으로 설정합니다.
  7. |record|의 data 타입이 {{BufferSource}} 또는 {{NDEFMessageInit}}가 아니면, {{TypeError}}를 [= exception/throw =] 합니다.
  8. |ndef|의 TNF field를 `4`(external type record)로 설정합니다.
  9. |record|의 data 타입이 {{BufferSource}}인 경우,
    1. |arrayBuffer|를 |record|의 data로 설정합니다.
    2. |length:unsigned long|을 |arrayBuffer|.[[\ArrayBufferByteLength]]로 설정합니다.
    3. |data:byte sequence|를 |arrayBuffer|.[[\ArrayBufferData]]로 설정합니다.
    4. |ndef|의 PAYLOAD LENGTH field를 |length|로 설정합니다.
    5. |length| > `0`이면, |ndef|의 PAYLOAD field를 |data|로 설정합니다.
  10. |record|의 data 타입이 {{NDEFMessageInit}}인 경우,
    1. |ndef|의 PAYLOAD field를 |record|의 data, `"external"`, |recordsDepth|으로 create NDEF message를 실행한 결과로 설정합니다.
    2. |ndef|의 PAYLOAD LENGTH field를 |ndef|의 PAYLOAD field 길이로 설정합니다.
  11. |ndef|를 반환합니다.

로컬 타입을 NDEF로 매핑

|record:NDEFRecordInit|, |ndef|, |context:string|, |recordsDepth:unsigned short|가 주어졌을 때 map local type to NDEF를 수행하려면, 다음 단계들을 실행합니다:
  1. |record|의 mediaType이 undefined가 아니면, {{TypeError}}를 [= exception/throw =] 합니다.
  2. |record|의 data 타입이 {{BufferSource}} 또는 {{NDEFMessageInit}}가 아니면, {{TypeError}}를 [= exception/throw =] 합니다.
  3. |ndef|의 TNF field를 `1`([=well-known type record=])로 설정합니다.
  4. |localTypeName|을 |record|의 recordType에서 첫 `U+003A` (`:`) 이후부터 |record|의 recordType 끝까지로 둡니다.
  5. |ndef|의 TYPE field를 |localTypeName|으로 설정하여 local type name을 나타냅니다.
  6. |context|가 `"smart-poster"`이고 |localTypeName|이 "`s`" (`0x73`)이며, |record|의 data 타입이 {{BufferSource}}가 아니거나 바이트 길이가 4를 초과하면, {{TypeError}}를 [= exception/throw =] 합니다.
  7. |context|가 `"smart-poster"`이고 |localTypeName|이 "`act`" (`0x61` `0x63` `0x74`) 이며, |record|의 data 타입이 {{BufferSource}}가 아니거나 바이트 길이가 정확히 1이 아니면, {{TypeError}}를 [= exception/throw =] 하고 이 단계들을 중단합니다.
  8. |record|의 data 타입이 {{BufferSource}}인 경우,
    1. |arrayBuffer|를 |record|의 data로 설정합니다.
    2. |length:unsigned long|을 |arrayBuffer|.[[\ArrayBufferByteLength]]로 설정합니다.
    3. |data:byte sequence|를 |arrayBuffer|.[[\ArrayBufferData]]로 설정합니다.
    4. |ndef|의 PAYLOAD LENGTH field를 |length|로 설정합니다.
    5. |length| > `0`이면, |ndef|의 PAYLOAD field를 |data|로 설정합니다.
  9. |record|의 data 타입이 {{NDEFMessageInit}}인 경우,
    1. |ndef|의 PAYLOAD field를 |record|의 data, `"local"`, |recordsDepth|으로 create NDEF message를 실행한 결과로 설정합니다.
    2. |ndef|의 PAYLOAD LENGTH field를 |ndef|의 PAYLOAD field 길이로 설정합니다.
  10. |ndef|를 반환합니다.

스마트 포스터를 NDEF로 매핑

|record:NDEFRecordInit|, |ndef|, |recordsDepth:unsigned short|가 주어졌을 때 map smart poster to NDEF를 수행하려면, 다음 단계들을 실행합니다:
  1. |record|의 mediaType이 undefined가 아니면, {{TypeError}}를 [= exception/throw =] 합니다.
  2. |record|의 data 타입이 {{NDEFMessageInit}}가 아니면, {{TypeError}}를 [= exception/throw =] 합니다.
  3. |ndef|의 TNF field를 `1`([=well-known type record=])로 설정합니다.
  4. |ndef|의 TYPE field를 "`Sp`"(`0x53` `0x70`)로 설정합니다.
  5. |ndef|의 PAYLOAD field를 |record|의 data, `"smart-poster"`, |recordsDepth|으로 create NDEF message를 실행한 결과로 설정합니다.
  6. |ndef|의 PAYLOAD LENGTH field를 |ndef|의 PAYLOAD field 길이로 설정합니다.
  7. |ndef|를 반환합니다.

absolute-URL을 NDEF로 매핑

|record:NDEFRecordInit|, |ndef|, |context:string|가 주어졌을 때 map absolute-URL to NDEF를 수행하려면, 다음 단계들을 실행합니다:
  1. |context|가 `"smart-poster"`이면, {{TypeError}}를 [= exception/throw =] 합니다.

    [[NDEF-SMARTPOSTER]] 사양은 smart poster 내에서 하나의 URL만 허용하며, 이는 단일 URI record여야 합니다.

  2. |record|의 mediaType이 undefined가 아니면, {{TypeError}}를 [= exception/throw =] 합니다.
  3. |record|의 data가 {{DOMString}}이 아니면, {{TypeError}}를 [= exception/throw =] 합니다.
  4. |record|의 dataparsing한 결과가 실패이면, {{SyntaxError}}를 [= exception/throw =] 합니다.
  5. |arrayBuffer|를 |record|의 data로 설정합니다.
  6. |data:byte sequence|를 |arrayBuffer|.[[\ArrayBufferData]]로 설정합니다.
  7. |ndef|의 TNF field를 `3`([=absolute-URL record=])으로 설정합니다.
  8. |ndef|의 TYPE field를 |data|로 설정합니다.
  9. |ndef|의 PAYLOAD LENGTH field를 `0`으로 설정하고 PAYLOAD field를 생략합니다.
  10. |ndef|를 반환합니다.

Making content read-only

이 섹션은 근접 범위에 있을 때 NFC tag를 영구적으로 읽기 전용으로 만드는 방법을 설명합니다. 어느 시점이든 NFC tag가 영구적으로 읽기 전용으로 되거나 작업이 중단될 때까지 하나의 origin에 대해 최대 하나의 요청만 존재할 수 있습니다.

makeReadOnly() 메서드

NDEFReader.makeReadOnly 메서드는 호출될 때 make an NFC tag permanently read-only 알고리즘을 반드시 실행해야 합니다:
  1. |p:Promise|를 새로운 {{Promise}} 객체로 둡니다.
  2. 현재 활성 top-level browsing context에서 실행 중이 아니면, |p|를 {{"InvalidStateError"}}로 거부하고 |p|를 반환합니다.
  3. |options:NDEFMakeReadOnlyOptions|를 두 번째 인자로 둡니다.
  4. |signal:AbortSignal|을, 존재한다면 |options|와 같은 이름의 딕셔너리 멤버로, 그렇지 않다면 null로 둡니다.
  5. |signal|이 [= AbortSignal/aborted =]라면, |signal|의 [=AbortSignal/abort reason=]으로 |p|를 거부하고 |p|를 반환합니다.
  6. |signal|이 null이 아니라면, 다음 중단 단계를 추가합니다:
    1. environment settings object에서 abort a pending make read-only operation을 실행합니다.
  7. |p|에 [=promise/React=]합니다:
    1. |p|가 해결(이행 또는 거부)되었다면, 존재할 경우 pending write tuple을 정리합니다.
  8. |p|를 반환하고 다음 단계들을 in parallel로 실행합니다:
    1. obtain permission 단계가 false를 반환하면, |p|를 {{"NotAllowedError"}}로 거부하고 이 단계들을 중단합니다.
    2. 기반 NFC Adapter가 없거나 연결을 수립할 수 없다면, |p|를 {{"NotSupportedError"}}로 거부하고 이 단계들을 중단합니다.
    3. UA가 기반 NFC Adapter에 접근할 수 없다면(예: 사용자 환경설정), |p|를 {{"NotReadableError"}}로 거부하고 이 단계들을 중단합니다.
    4. 구현은 |p|를 {{"NotSupportedError"}}로 거부하고 이 단계들을 중단할 수 있습니다.

      UA는 이 시점에서 작업을 중단할 수 있습니다. 종료 사유는 구현 상세에 따릅니다. 예를 들어, 구현이 요청된 작업을 지원할 수 없을 수 있습니다.

    5. abort a pending make read-only operation을 시도합니다.

      읽기 전용 설정 작업은 이전에 설정된 모든 읽기 전용 설정 작업을 대체합니다.

    6. pending makeReadOnly tuple을 (`this`, |p|)로 설정합니다.
    7. NFC tag |device|가 통신 범위에 들어올 때마다 start the NFC make read-only 단계를 실행합니다.

      NFC is suspended인 경우, 사용자가 프라미스를 중단하거나 NFC tag가 통신 범위에 들어올 때까지 계속 대기합니다.

start the NFC make read-only를 수행하려면, 다음 단계들을 실행합니다:
  1. |p:Promise|를 pending makeReadOnly tuple의 promise로 둡니다.
  2. 근접 범위의 NFC tag가 포맷팅을 위한 NDEF 기술을 노출하지 않으면, |p|를 {{"NotSupportedError"}}로 거부하고 |p|를 반환합니다.
  3. NFC is not suspended임을 검증합니다.
  4. 성공 시 다음 단계들을 실행합니다:
    1. 통신 범위에 있는 NFC adapter를 사용하여 |device|를 영구적으로 읽기 전용으로 만듭니다.
    2. 작업이 실패하면, |p|를 {{"NetworkError"}}로 거부하고 이 단계들을 중단합니다.
    3. 작업이 완료되면, |p|를 이행합니다.
    4. 이 작업은 일방향이며 되돌릴 수 없습니다. NFC 태그가 읽기 전용으로 설정되면 더 이상 쓸 수 없습니다.

콘텐츠 수신 대기

NFC content를 수신하려면, 클라이언트는 NDEFReader.scan()을 호출하여 {{NDEFReader}} 인스턴스를 활성화해야 합니다. 해당 인스턴스에 "`reading`" 이벤트 리스너를 부착하면, NFC content에 접근할 수 있습니다.

activated reader objects 안에 어떤 {{NDEFReader}} 인스턴스라도 있다면, UA는 연결된 모든 NFC 어댑터에서 NDEF message를 수신 대기해야 합니다.

scan() 메서드

들어오는 NFC content는 {{NDEFReader}} 인스턴스를 사용하여 매칭됩니다.

NDEFReader.scan 메서드가 호출되면, UA는 다음의 NFC listen algorithm을 반드시 실행해야 합니다:
  1. |p:Promise|를 새로운 {{Promise}} 객체로 둡니다.
  2. 현재 활성 top-level browsing context에서 실행 중이 아니면, |p|를 {{"InvalidStateError"}}로 거부하고 |p|를 반환합니다.
  3. |reader:NDEFReader|를 {{NDEFReader}} 인스턴스로 둡니다.
  4. |options|를 첫 번째 인자로 둡니다.
  5. |signal:AbortSignal|을, 존재한다면 |options|와 같은 이름의 딕셔너리 멤버로, 그렇지 않다면 null로 둡니다.
  6. |signal|이 [= AbortSignal/aborted =]라면, |signal|의 [=AbortSignal/abort reason=]으로 |p|를 거부하고 |p|를 반환합니다.
  7. |signal|이 null이 아니라면, 다음 clean up the pending scan 단계를 |signal|에 추가합니다:
    1. |reader|를 activated reader objects에서 제거합니다.
    2. activated reader objects가 [= list/is empty =]이면, 모든 NFC adapter에서 NDEF message 수신 대기 중지를 요청합니다.
  8. |p|를 반환하고 다음 단계들을 in parallel로 실행합니다:
    1. obtain permission 단계가 false를 반환하면, |p|를 {{"NotAllowedError"}}로 거부하고 이 단계들을 중단합니다.
    2. 기반 NFC Adapter가 없거나 연결을 수립할 수 없다면, |p|를 {{"NotSupportedError"}}로 거부하고 이 단계들을 중단합니다.
    3. UA가 기반 NFC Adapter에 접근할 수 없다면(예: 사용자 환경설정), |p|를 {{"NotReadableError"}}로 거부하고 이 단계들을 중단합니다.
    4. |reader|가 이미 activated reader objects에 있다면, |p|를 {{"InvalidStateError"}}로 거부하고 이 단계들을 중단합니다.
    5. |reader|를 activated reader objects에 추가합니다.
    6. |p|를 이행합니다.
    7. UA가 NFC 기술을 감지할 때마다, NFC reading algorithm을 실행합니다.

NFC 판독 알고리즘

NDEF 콘텐츠를 받기 위해, NFC reading algorithm을 실행합니다:
  1. NFC is suspended이면, 이 단계들을 중단합니다.
  2. 근접 범위의 NFC tag가 읽기 또는 포맷팅을 위한 NDEF 기술을 노출하지 않으면, 다음 하위 단계들을 실행합니다:
    1. [= list/For each =] activated reader objects의 {{NDEFReader}} 인스턴스 |reader:NDEFReader|마다 다음 하위 단계들을 실행합니다:
      1. |reader|에서 "`readingerror`"라는 이름의 Fire an event를 수행합니다.
    2. 이 단계들을 중단합니다.
  3. |serialNumber:serialNumber|를 일련의 숫자로 된 장치 식별자로, 사용 불가능하면 null로 둡니다.
  4. |serialNumber|가 null이 아니면, 각 숫자를 같은 순서로 ASCII hex digit으로 표현하여 U+003A(`:`)로 이어붙인 string으로 설정합니다.
  5. |message:NDEFMessage|를 새로운 NDEFMessage 객체로, |message|의 records를 빈 list로 설정하여 둡니다.
  6. 근접 범위의 NFC tag가 비포맷 상태이고 NDEF 포맷이 가능하면, |input|을 null로 둡니다. 그렇지 않으면, 수신된 NDEF message에 대한 표기를 |input|으로 둡니다.

    UA는 비포맷 NFC tagNDEF record가 없는 NDEF message로 표현해야 하며, 즉 {{NDEFMessage/records}} 속성에 대해 빈 배열입니다.

  7. |input|의 일부인 각 NDEF record에 대해 다음 하위 단계들을 실행합니다:
    1. 현재 NDEF record에 대한 표기를 |ndef|로 두고, |typeNameField:number|는 TNF field에 대응하며, |payload:byte sequence|는 PAYLOAD field 데이터에 대응합니다.
    2. |record:NDEFRecord|를 |ndef|와 `""`를 주어 parse an NDEF record를 실행한 결과로 둡니다.
    3. |record|가 null이 아니면, |message|의 records에 append합니다.
  8. 주어진 |serialNumber|와 |message|로 dispatch NFC content 단계를 실행합니다.

NFC 콘텐츠 디스패치

serialNumber 타입의 |serialNumber:serialNumber|와 NDEFMessage 타입의 |message:NDEFMessage|가 주어졌을 때 dispatch NFC content를 수행하려면, 다음 단계들을 실행합니다:

  1. [= list/For each =] activated reader objects의 {{NDEFReader}} 인스턴스 |reader:NDEFReader|마다,
    1. |reader|에서 NDEFReadingEvent를 사용해 "`reading`"이라는 이름의 fire an event를 수행하고, 그 serialNumber 속성을 |serialNumber|로, message 속성을 |message|로 초기화합니다.

콘텐츠 파싱

바이트에서 레코드 파싱

|bytes:byte sequence|와 |context: string|가 주어졌을 때 parse records from bytes를 수행하려면, 다음 단계들을 실행합니다:
  1. |bytes|의 길이가 `0`이면, null을 반환합니다.
  2. |records|를 빈 목록으로 둡니다.
  3. |bytes|에 읽지 않은 바이트가 있는 동안, 다음 하위 단계들을 실행합니다:
    1. 남은 |bytes|의 길이가 `3`보다 작으면, null을 반환합니다.
    2. 다음 단계 중 어느 하나에서 |bytes|의 남은 길이를 넘어 바이트를 읽어야 한다면, null을 반환합니다.
    3. 현재 NDEF record에 대한 표기를 |ndef|로 둡니다.
    4. |header:byte|를 |bytes|의 다음 바이트로 둡니다.
      1. |messageBegin:boolean|(MB field)을 |header|의 가장 왼쪽 비트(비트 7)로 둡니다.
      2. 이 하위 단계들의 첫 반복이고 |messageBegin|이 false이면, null을 반환합니다.
      3. |messageEnd:boolean|(ME field)를 |header|의 비트 6으로 둡니다.
      4. 청크된 레코드는 하위 레코드로 허용되지 않으므로, 비트 5 (CF field)는 무시됩니다.

      5. |shortRecord:boolean|(SR field)를 |header|의 비트 4로 둡니다.
      6. |hasIdLength:boolean|(IL field)를 |header|의 비트 3으로 둡니다.
      7. |ndef|의 |typeNameField:number|(TNF field)를 |header|의 비트 2-0의 정수값으로 둡니다.
    5. |typeLength:number|를 |bytes|의 다음 바이트 (TYPE LENGTH field)의 정수값으로 둡니다.
    6. |shortRecord|가 true이면, |payloadLength:number|를 |bytes|의 다음 바이트 (PAYLOAD LENGTH field)의 정수값으로 둡니다.
    7. 그렇지 않으면, |payloadLength|를 |bytes|의 다음 4바이트의 정수값으로 둡니다.
    8. |hasIdLength|가 true이면, |idLength:number|를 |bytes|의 다음 바이트 (ID LENGTH field)의 정수값으로, 아니면 `0`으로 둡니다.
    9. |typeLength| > 0이면, 다음 |typeLength|(TYPE field) 바이트에 UTF-8 decode를 실행한 결과를 |ndef|의 |type:string|으로, 아니면 |type|을 빈 문자열로 둡니다.
    10. |idLength| > 0이면, 다음 |idLength|(ID field) 바이트에 UTF-8 decode를 실행한 결과를 |ndef|의 |id:string|으로, 아니면 |ndef|의 |id|를 빈 문자열로 둡니다.
    11. |ndef|의 |payload|를 마지막 |payloadLength|(PAYLOAD field) 바이트의 byte sequence로 둡니다. 이는 `0` 바이트일 수 있습니다.
    12. |record:NDEFRecord|를 |ndef|와 |context|로 parse an NDEF record를 실행한 결과로 둡니다.
    13. |record|가 null이 아니면, |records|에 append합니다.
    14. |messageEnd|가 true이면,
      1. |records|와 |context|로 check parsed records를 실행했을 때 |error|를 던지면, |promise|를 |error|로 거부하고 이 단계들을 중단합니다.
      2. 그렇지 않으면 이 하위 단계들을 중단합니다(루프 종료).
  4. |records|를 반환합니다.

파싱된 레코드 점검

|records: NDEFRecord sequence|와 |context: string|가 주어졌을 때 check parsed records를 수행하려면, 다음 단계들을 실행합니다:
  1. |context|가 `"smart-poster"`이고 |records|에 정확히 하나의 URI record가 없거나, 하나 초과의 type record, size record, action record가 있으면, {{TypeError}}를 [= exception/throw =] 합니다.
  2. 그렇지 않으면 true를 반환합니다.

NDEF 레코드 파싱

|ndef|, |context:string|가 주어졌을 때 |record:NDEFRecord|로 parse an NDEF record를 수행하려면, 다음 단계들을 실행합니다:
  1. |record|의 id를 |ndef|의 |id:string|으로 설정합니다.
  2. |record|의 lang을 null로 설정합니다.
  3. |record|의 encoding을 null로 설정합니다.
  4. |ndef|의 |typeNameField:number|(TNF field)가 `0` (empty record)이면:
    1. |record|의 id를 null로 설정합니다.
    2. |record|의 recordType을 "`empty`"로 설정합니다.
    3. |record|의 mediaType을 null로 설정합니다.
    4. |record|의 data를 null로 설정합니다.
  5. |ndef|의 |typeNameField|가 `1`([=well-known type record=])이면,
    1. |ndef|의 |type:string|이 "`T`" (`0x54`)이면, |ndef|에 대해 parse an NDEF text record를 실행한 결과를 |record|로 설정합니다.
    2. |ndef|의 |type:string|이 "`U`" (`0x55`)이면, |ndef|에 대해 parse an NDEF URL record를 실행한 결과를 |record|로 설정합니다.
    3. |ndef|의 |type:string|이 "`Sp`" (`0x53` `0x70`)이면, |ndef|에 대해 parse an NDEF smart-poster record를 실행한 결과를 |record|로 설정합니다.
    4. |ndef|의 |type:string|이 "`s`" (`0x73`)이고 |context|가 `"smart-poster"`와 같으면, |ndef|에 대해 parse a smart-poster size record를 실행한 결과를 |record|로 설정합니다.
    5. |ndef|의 |type:string|이 "`t`" (`0x74`)이고 |context|가 `"smart-poster"`와 같으면, |ndef|에 대해 parse a smart-poster type record를 실행한 결과를 |record|로 설정합니다.
    6. |ndef|의 |type:string|이 "`act`" (`0x61` `0x63` `0x74`) 이고 |context|가 `"smart-poster"`와 같으면, |ndef|에 대해 parse a smart-poster action record를 실행한 결과를 |record|로 설정합니다.
    7. |ndef|의 |type:string|에 대해 validate local type 단계를 실행한 결과가 true이면,
      1. |context|가 `"external"` 또는 `"smart-poster"`가 아니면, {{TypeError}}를 [= exception/throw =] 합니다.
      2. |ndef|에 대해 parse a local type record를 실행한 결과를 |record|로 설정합니다.
    8. 그렇지 않으면 {{TypeError}}를 [= exception/throw =] 합니다.
  6. |ndef|의 |typeNameField|가 `2`(MIME type record)이면, |ndef|에 대해 parse an NDEF MIME type record를 실행한 결과를 |record|로 설정하거나, 기반 플랫폼이 |record| 객체 속성에 동등한 값을 제공하도록 합니다.
  7. |ndef|의 |typeNameField|가 `3`(absolute-URL record)이면, |ndef|에 대해 parse an NDEF absolute-URL record를 실행한 결과를 |record|로 설정합니다.
  8. |ndef|의 |typeNameField|가 `4`(external type record)이면, |ndef|에 대해 parse an NDEF external type record를 실행한 결과를 |record|로 설정하거나, 기반 플랫폼이 |record| 객체 속성에 동등한 값을 제공하도록 합니다.
  9. |ndef|의 |typeNameField|가 `5`(unknown record)이면, |ndef|에 대해 parse an NDEF unknown record를 실행한 결과를 |record|로 설정하거나, 기반 플랫폼이 |record| 객체 속성에 동등한 값을 제공하도록 합니다.
  10. 그렇지 않으면 {{TypeError}}를 [= exception/throw =] 합니다.

NDEF well-known `T` 레코드 파싱

|ndefRecord|가 주어졌을 때 |record:NDEFRecord|로 parse an NDEF text record를 수행하려면, 다음 단계들을 실행합니다:
  1. |record|의 recordType을 "`text`"로 설정합니다.
  2. |record|의 mediaType을 null로 설정합니다.
  3. |ndefRecord|의 PAYLOAD field가 존재하지 않으면, |record|의 data를 null로 설정하고 |record|를 반환합니다.
  4. |header:byte|를 |ndefRecord|의 PAYLOAD field의 첫 번째 byte로 둡니다.
  5. |languageLength:octet|을 |header|의 비트 `5`부터 비트 `0`까지의 값으로 둡니다.
  6. |language:string|을 두 번째 byte부터 |languageLength| + `1`번째 바이트까지(포함)에 대해 ASCII decode를 실행한 결과로 둡니다.
  7. |buffer:byte sequence|를 |ndefRecords|의 PAYLOAD field 중 나머지 부분의 byte sequence로 둡니다 (|header|와 |languageLength| 바이트를 제외).
  8. |record|의 lang을 |language|로 설정합니다.
  9. |record|의 encoding을, |header|의 비트 `7`이 `0`이면 "`utf-8`"로, 그렇지 않으면 "`utf-16be`"로 설정합니다.
  10. |record|의 data를 |buffer|로 설정합니다.
  11. |record|를 반환합니다.

NDEF well-known `U` 레코드 파싱

|ndefRecord|가 주어졌을 때 |record:NDEFRecord|로 parse an NDEF URL record를 수행하려면, 다음 단계들을 실행합니다:
  1. |record|의 recordType을 "`url`"로 설정합니다.
  2. |record|의 mediaType을 null로 설정합니다.
  3. |ndefRecord|의 PAYLOAD field가 존재하지 않으면, |record|의 data를 null로 설정하고 |record|를 반환합니다.
  4. |buffer:byte sequence|를 |ndefRecords|의 PAYLOAD fieldbyte sequence로 둡니다.
  5. |prefixByte:byte|를 |buffer|의 첫 번째 byte의 값으로 둡니다.
  6. |prefixByte|의 값이 [[[NFC-STANDARDS]]] URI Record Type Definition 사양 3.2.2절, 표 3의 URL 확장 코드와 일치하면,
    1. |prefixString:string|을 |prefixByte|의 값에 대응하는 byte sequence 값으로 둡니다.
    2. |record|의 data를 |prefixString|에 |buffer|를 이어붙인 값으로 설정합니다.
  7. 그렇지 않고 |prefixByte|에 대한 일치가 없으면, |record|의 data를 |buffer|로 설정합니다.
  8. |record|를 반환합니다.

NDEF well-known `Sp` 레코드 파싱

|ndefRecord|가 주어졌을 때 |record:NDEFRecord|로 parse an NDEF smart-poster record를 수행하려면, 다음 단계들을 실행합니다:
  1. |record|의 recordType을 "`smart-poster`"로 설정합니다.
  2. |record|의 mediaType을 null로 설정합니다.
  3. |ndefRecord|의 PAYLOAD field가 존재하지 않으면, |record|의 data를 null로 설정하고 |record|를 반환합니다.
  4. |buffer:byte sequence|를 |ndefRecords|의 PAYLOAD fieldbyte sequence로 둡니다.
  5. |record|의 data를 |buffer|로 설정합니다.
  6. |record|를 반환합니다.

    애플리케이션은 data에 대해 toRecords()를 호출해 이를 NDEF records로 파싱하거나, 직접 파싱할 수 있습니다.

|ndefRecord|가 주어졌을 때 |record:NDEFRecord|로 parse a smart-poster size record를 수행하려면, 다음 단계들을 실행합니다:
  1. |record|의 recordType을 "`:s`"로 설정합니다.
  2. |record|의 mediaType을 null로 설정합니다.
  3. |ndefRecord|의 PAYLOAD field가 정확히 4바이트가 아니면, {{TypeError}}를 [= exception/throw =] 합니다.
  4. |buffer:byte sequence|를 |ndefRecords|의 PAYLOAD fieldbyte sequence로 둡니다.
  5. |record|의 data를 |buffer|로 설정합니다.

    애플리케이션은 이 값을 32비트 부호 없는 정수로 파싱하여, smart-poster의 URI 레코드가 참조하는 객체의 크기를 나타내는 값으로 사용할 수 있습니다.

  6. |record|를 반환합니다.
|ndefRecord|가 주어졌을 때 |record:NDEFRecord|로 parse a smart-poster type record를 수행하려면, 다음 단계들을 실행합니다:
  1. |record|의 recordType을 "`:t`"로 설정합니다.
  2. |record|의 mediaType을 null로 설정합니다.
  3. |buffer:byte sequence|를 |ndefRecords|의 PAYLOAD fieldbyte sequence로 둡니다.

    애플리케이션은 이를 [[RFC2048]] 미디어 타입을 담은 문자열로 파싱하여, smart-poster의 URI 레코드가 참조하는 객체의 미디어 타입을 나타내게 할 수 있습니다.

  4. |record|의 data를 |buffer|로 설정합니다.
  5. |record|를 반환합니다.
|ndefRecord|가 주어졌을 때 |record:NDEFRecord|로 parse a smart-poster action record를 수행하려면, 다음 단계들을 실행합니다:
  1. |record|의 recordType을 "`:act`"로 설정합니다.
  2. |record|의 mediaType을 null로 설정합니다.
  3. |ndefRecord|의 PAYLOAD field가 정확히 1바이트가 아니면, {{TypeError}}를 [= exception/throw =] 합니다.
  4. |buffer:byte sequence|를 |ndefRecords|의 PAYLOAD fieldbyte sequence로 둡니다.
  5. |record|의 data를 |buffer|로 설정합니다.

    애플리케이션은 이 값을 8비트 부호 없는 정수로 파싱할 수 있으며, 값의 정의는 여기에 있습니다.

  6. |record|를 반환합니다.

로컬 타입 레코드 파싱

|ndef|가 주어졌을 때 |record:NDEFRecord|로 parse a local type record를 수행하려면, 다음 단계들을 실행합니다:
  1. |record|의 recordType을 "`:`"(U+003A)와 |ndef|의 |type:string|을 이어붙인 값으로 설정합니다.
  2. |record|의 mediaType을 null로 설정합니다.
  3. |buffer:byte sequence|를 |ndefRecords|의 PAYLOAD fieldbyte sequence로 둡니다.
  4. |record|의 data를 |buffer|로 설정합니다.
  5. |record|를 반환합니다.

NDEF MIME 타입 레코드 파싱

|ndefRecord|가 주어졌을 때 |record:NDEFRecord|로 parse an NDEF MIME type record를 수행하려면, 다음 단계들을 실행합니다:
  1. |record|의 recordType을 "`mime`"로 설정합니다.
  2. |record|의 mediaType을 |mimeType|을 입력으로 하여 serialize a MIME type을 실행한 결과로 설정합니다.
  3. |buffer:byte sequence|를, 존재하면 |ndefRecords|의 PAYLOAD fieldbyte sequence로, 그렇지 않으면 null로 둡니다.
  4. |record|의 data를 |buffer|로 설정합니다.
  5. |record|를 반환합니다.

NDEF absolute-URL 레코드 파싱

|ndefRecord|가 주어졌을 때 |record:NDEFRecord|로 parse an NDEF absolute-URL record를 수행하려면, 다음 단계들을 실행합니다:
  1. |record|의 recordType을 "`absolute-url`"로 설정합니다.
  2. |record|의 mediaType을 null로 설정합니다.
  3. |buffer:byte sequence|를 |ndefRecords|의 TYPE fieldbyte sequence로 둡니다.
  4. |record|의 data를 |buffer|로 설정합니다.
  5. |record|를 반환합니다.

NDEF 외부 타입 레코드 파싱

|ndefRecord|가 주어졌을 때 |record:NDEFRecord|로 parse an NDEF external type record를 수행하려면, 다음 단계들을 실행합니다:
  1. 레코드의 |ndefRecord|의 TYPE field에 대해 [=validate external type=]을 실행한 결과가 false이면, null을 반환합니다.
  2. |domain|과 |type|을 |ndefRecord|의 TYPE field 값으로 [=split external type=]을 실행한 결과로 둡니다.
  3. |domain|을 Unicode ToUnicode를 |domain_name|을 |domain|으로, |CheckHyphens| false, |CheckBidi| true, |CheckJoiners| true, |UseSTD3ASCIIRules| true, |Transitional_Processing| false로 설정하여 실행한 결과로 둡니다. 결과에 오류가 하나라도 포함되면, null을 반환합니다.
  4. |record|의 recordType을 |domain|, "`:`", |type|을 이어붙인 값으로 설정합니다.
  5. |record|의 mediaType을 null로 설정합니다.
  6. |buffer:byte sequence|를, 존재하면 |ndefRecords|의 PAYLOAD fieldbyte sequence로, 그렇지 않으면 null로 둡니다.
  7. |record|의 data를 |buffer|로 설정합니다.
  8. |record|를 반환합니다.

NDEF unknown 타입 레코드 파싱

|ndefRecord|가 주어졌을 때 |record:NDEFRecord|로 parse an NDEF unknown record를 수행하려면, 다음 단계들을 실행합니다:
  1. |record|의 recordType을 "`unknown`"으로 설정합니다.
  2. |record|의 mediaType을 null로 설정합니다.
  3. |buffer:byte sequence|를, 존재하면 |ndefRecords|의 PAYLOAD fieldbyte sequence로, 그렇지 않으면 null로 둡니다.
  4. |record|의 data를 |buffer|로 설정합니다.
  5. |record|를 반환합니다.

차단 목록

이 규격은 웹사이트가 접근할 수 있는 NFC 기기 집합을 제한하기 위해 차단 목록 파일에 의존합니다.

|url:URL|에서 차단 목록을 파싱한 결과는 다음 알고리즘으로 생성된 historical bytes 16진수 값들의 목록입니다:

  1. |url:URL|을 가져오고, 본문을 UTF-8로 디코딩한 값을 |contents:string|으로 둡니다.
  2. |contents|를 `"\n"`으로 분할한 값을 |lines:array|로 둡니다.
  3. list를 |result:list|로 둡니다.
  4. [= list/For each =] |line:string| ∈ |lines|에 대해 다음 하위 단계를 수행합니다:
    1. |line|이 비어 있으면 다음 줄로 넘어갑니다.
    2. |line|이 `"#"`로 시작하면 다음 줄로 넘어갑니다.
    3. |line|에 유효하지 않은 16진수 값이 포함되어 있으면 다음 줄로 넘어갑니다.
    4. Append |line|을 |result|에 추가합니다.
  5. |result|을 반환합니다.

blocklisthttps://github.com/w3c/web-nfc/blob/gh-pages/blocklist.txt 에서 parsing the blocklist한 결과입니다. UA는 차단 목록을 주기적으로 다시 가져와야 하지만, 그 주기는 규정되지 않습니다.

NFC deviceblocklist의 값에 해당 기기의 historical bytes 16진수 값이 포함되어 있으면 blocklisted 됩니다. ISO 14443-4 용어로, historical bytes는 RATS(Request for Answer To Select) 응답의 하위 집합입니다.

보안 및 프라이버시

신뢰 체인

구현은 사용자가 Web NFC API의 메서드를 승인할 때 부수 효과 없이 오직 그 동작만 실행되도록 보장해야 합니다.

기본적으로 NDEF는 태그에 데이터를 쓴 뒤 영구적인 읽기 전용으로 설정할 수 있게 해주는 것 너머로 내용을 신뢰할 방법을 제공하지 않습니다. 이는 공장 설정에서도 수행될 수 있습니다.

이 API를 통해 기록된 데이터는 기존 네이티브 NFC API와 마찬가지로 자동으로 서명되거나 암호화되지 않습니다. NDEF 메시지의 무결성과 진정성을 보호하기 위해 NFC Forum은 [[NDEF-SIGNATURE]]를 도입했습니다. NDEF signature와 키 관리의 사용 책임은 애플리케이션에 있습니다.

NFC를 통해 교환되는 데이터의 기밀성을 신뢰하기 위해, 애플리케이션은 암호화된 NFC content를 사용할 수 있습니다.

NFC를 통해 교환되는 데이터의 무결성을 신뢰하기 위해, 애플리케이션은 공개키 기반구조(PKI)에 기반한 NDEF signature를 사용할 수 있습니다.

일반적인 MIME 타입에 대한 보안 고려 사항은 [[RFC2048]] 및 [[RFC2046]]에 논의되어 있습니다.

프라이버시 영향과 구현 고려 사항

NFC 태그는 사람이 읽을 수 없는 또 다른 데이터 교환 방식이므로 바코드나 QR 코드와의 비교가 적절합니다. 이를 공유하는 것은 예기치 못한 프라이버시 및 보안 영향이 있을 수 있습니다. 웹사이트가 QR 코드를 읽으려면 이미지(카메라)를 캡처하는 방해적 UI가 사용되며, 이 이미지의 내용(QR 코드 포함)이 웹 페이지에 제공됨을 사용자에게 명확히 하여 스캔이 수행되고 있음을 분명히 합니다.

NFC로 태그를 스캔하려면 사용자가 스캐닝 장치(예: 휴대폰)를 NFC 태그에 근접(보통 5~10cm, 2~4인치)시켜야 합니다.

Web NFC 스캔이 활성화되어 있지 않을 때 태그를 스캔하면 호스트 OS가 처리합니다. 따라서 NFC 태그 스캔으로 URL과 앱을 실행하는 것은 Web NFC 자체가 처리하거나 지원하지 않습니다.

또한 Web NFC 스캔은 사용자 상호작용으로 활성화되어야 하며, 웹사이트가 포커스에 없거나 장치 화면이 꺼질 때 (즉, 잠금 해제 상태가 아닐 때) 스캔은 일시 중지됩니다. 이는 우발적 스캔이 발생하지 않도록 하기 위한 조치입니다.

Web NFC는 더 나아가, 스캐닝 장치를 NFC 태그 근처에 두면 데이터가 스캔될 것임을 UX 차원에서 사용자에게 매우 명확히 알리도록 구현을 권장합니다. 기본적으로 QR 코드 스캔의 UX 흐름을 모방합니다.

이를 위한 방법은 다양합니다. 예를 들어 사운드를 재생하거나, 스캔이 발생할 수 있는 동안 지속적인 UI(예: 언제든 취소 가능한 모달 대화상자)를 표시하는 방법이 있습니다.

구현은 업로드되려는 데이터를 표시하거나, 사용자가 승인할 때까지 읽은 데이터 공유를 보류하고, 사용자에게 어떤 레코드를 공유할지 선택할 수 있는 UI를 제공할 수도 있습니다.

스캔 중 읽기 및 쓰기

사용자가 태그를 스캔하면, 그 시점에 웹 애플리케이션은 태그의 데이터를 읽을 수 있고, 읽기 전용이 아니라면 태그에 데이터를 쓸 수도 있습니다. 개인 사용(예: 메이커 커뮤니티)의 소비자 스티커는 종종 잠금 해제(읽기 + 쓰기)되어 있는 반면, 상업적 배치의 NFC는 읽기 전용인 경우가 많습니다.

과거 프로토콜인 SNEP(Simple NDEF Exchange Protocol)는 능동 장치(예: 휴대폰)가 다른 능동 장치로부터 NDEF 데이터를 수신할 수 있게 했으나, Web NFC에서는 지원되지 않으며 지원되는 네이티브 플랫폼에서도 현재 폐기(deprecate) 중입니다.

더 최신 프로토콜인 TNEP(Tag NDEF Exchange Protocol)는 스캐너 장치(예: 휴대폰)와 IOT 장치 같은 능동 전원 장치 간의 양방향 통신을 허용합니다. 이는 현재 Web NFC에서 지원되지 않으며, 또한 입력 허용에 대한 제약이 있고 IOT 장치는 수락된 레코드가 유효함을 보장해야 합니다.

태그에 프라이버시 민감 데이터가 포함되어 있는 경우, 그러한 데이터는 사이트와 공유됩니다. 다만 UX가 사용자에게 데이터 교환을 먼저 확인하도록 요구한다면 즉시 공유되지 않을 수도 있습니다.

어떤 경우에는 태그/장치에 프라이버시 민감 데이터가 포함되어 있음이 명백할 수 있습니다. 예를 들어 NFC가 장착된 콘퍼런스 배지와 명함의 경우가 그렇습니다. NFC가 장착된 혈당 측정기의 경우에도 마찬가지로, 본인 또는 가까운 가족 중 누군가가 당뇨병 환자임을 시사할 수 있습니다.

다른 경우에는 그러한 일이 발생할 수 있음이 덜 명확할 수 있으나, 사용자가 앱이나 웹사이트를 사용하여 태그에 데이터를 쓸 때, 사용자도 모르게 사용자 ID와 같은 정보를 인코딩했을 수 있으며, 그 데이터는 이후 다른 어떤 사이트에서도 읽을 수 있습니다.

개인적이고 예기치 못한 데이터는 파일(예: 워드 프로세싱 문서, PDF, 카메라 이미지)에도 저장될 수 있으며, 파일 업로드 API를 통해 업로드됩니다. Web NFC API와 관련된 완화책은 파일 업로드와 관련된 완화책보다 강력하며, 데이터가 개인을 식별할 가능성은 더 낮습니다.

스캔 중 읽기 및 쓰기

태그의 스캔은 웹사이트가 태그를 식별하는 방법을 알고, 현실 세계에서 태그의 위치를 알고 있는 경우(예: 박물관 내부에 부착) 사용자 위치를 드러낼 수도 있습니다. 또한 FeliCa NFC 태그가 주로 일본에서 사용되는 점을 근거로 추론할 수도 있지만, Web NFC는 어떤 태그 기술이 사용되었는지 드러내지 않습니다.

이는 웹 광고 및 트래킹 모델을 현실 세계로 끌어들이지 않습니다. 사용자 행동이 필요하며 백그라운드에서 트리거될 수 없고, 적절한 UX를 통해 스캔이 활성 상태임이 명확해야 하기 때문입니다.

기존 데이터 덮어쓰기

NFC 태그에 쓰면 태그가 망가지거나 “벽돌(brick)”이 되는 것에 대한 우려도 있습니다. NFC 태그는 여러 사용자 애플리케이션이 읽을 수 있도록 설계되었으며, NDEF 태그는 영구적으로 읽기 전용으로 만들 수 있는 쉬운 방법을 갖도록 설계되었습니다. 심지어 공장 출하 시 그 상태로 설정될 수도 있습니다.

NDEF는 데이터를 읽고 쓰기 위한 단순 교환 형식이지, 양방향 통신을 위한 것이 아닙니다. NFC는 더 하위 기술을 기반으로 하는 여러 통신 형식을 지원(따라서 NDEF처럼 읽기 전용으로 잠기지 않음)하지만, 이들 어느 것도 Web NFC에서는 지원되지 않습니다.

NFC 사용 시 사용자에게 알려야 할 사항

이 섹션은 사용자가 NFC를 사용할 때 인지해야 할 몇 가지 사항을 자세히 설명합니다. 구현은 관련 NFC 동작이 수행되기 전 또는 수행 시 사용자가 이러한 사실을 이해하도록 돕는 것이 권장됩니다.

읽은 데이터는 사이트와 공유됩니다

사이트가 NFC 콘텐츠를 읽을 권한을 갖는 경우, 스캔된 태그의 데이터는 파일이나 이미지를 업로드하는 것과 유사한 방식으로 사이트와 공유됩니다. 다른 모든 사이트와 마찬가지로, 이 데이터를 사이트가 적절하고 의도한 방식으로 처리하는지 신뢰할지는 사용자에게 달려 있습니다.

읽기 전용이 아닌 태그의 데이터는 사이트가 수정 및 덮어쓸 수 있습니다

매장 등의 배치된 NFC 솔루션의 태그는 실수 또는 악의적 행위로 인해 수정되지 않도록 항상 읽기 전용으로 설정되어야 합니다.

개인용 태그와 스티커는 종종 공장 출하 시 잠금 해제(쓰기 가능)되어 있으며, 사용자는 이러한 태그가 스캔으로 인해 덮어쓰이거나 수정될 수 있음을 인지해야 합니다.

고정(예: 장착)된 태그를 읽으면 읽은 위치가 노출될 수 있습니다

고정 태그는 데이터에 자신의 ID나 위치를 인코딩할 수 있으며, 이를 읽으면 태그의 물리적 위치를 아는 사이트에 해당 정보가 노출됩니다. 이는 읽기가 수행된 위치를 추정할 수 있게 합니다. 여기에 서비스에 로그인한 상태가 결합되면, 사이트에 위치 정보가 공유될 수 있습니다.

기록된 데이터는 권한이 부여된 다른 앱과 사이트에서도 읽을 수 있습니다. 태그의 모든 NDEF 데이터는 적절한 접근 권한을 가진 어떤 앱이나 웹사이트에서도 읽을 수 있으므로, 그 의도가 아니라면 데이터는 오직 허가된 주체만 읽을 수 있도록 안전한 방식으로 암호화해야 합니다.

동일한 읽기 영역에 여러 태그가 동시에 존재할 수 있습니다

NFC는 한 번에 오직 하나의 태그만 읽을 수 있지만, 여러 태그를 감지하고 그중 하나를 통신할 태그로 선택할 수 있습니다.

이 사용 사례는 지갑 안에 여러 스마트 카드(NFC 기반)가 있고 카드를 꺼내고 싶지 않은 경우 등이 있습니다.

이는 주로 외부 하드웨어가 읽는 결제 카드와 교통 카드에 유용하며, Web NFC의 사용 사례는 아닙니다. Web NFC에서는 다음의 공격 벡터를 방지하기 위해, 여러 태그가 동시에 사용 가능한 경우에는 읽기를 허용하지 않습니다.

공격 벡터의 한 예로, 누군가 합법적인 태그 위에 악성 NFC 태그/스티커를 붙여 잘못된 앱/사이트를 로드하거나 올바른 앱/사이트에 잘못된 데이터를 주입하게 할 수 있습니다. 원래 태그의 데이터를 복제하고 수정하여 (예: 악성 앱/사이트를 로드하도록 URL을 바꾸거나, 올바른 앱/사이트에 악성 데이터를 주입하도록 데이터 변경) 이를 수행할 수 있습니다. 예: 태그가 원래는 https://example.com으로 이동시켜야 하는데, https://exаmple.com(키릴 문자 а 사용)으로 이동하도록 수정된 경우—겉보기에는 합법적으로 보이며, 사용자는 악성 사이트에 민감한 데이터를 제공하게 될 수 있습니다.

태그에서 웹사이트를 로드하는 것은 Web NFC의 범위를 벗어나지만, 위와 같은 공격 벡터 때문에 여러 태그가 사용 가능한 경우 사용자 에이전트가 URL을 자동으로 로드하지 않도록 권장됩니다.

여러 태그가 사용 가능한 경우 읽기를 허용하지 않음으로써, Web NFC는 사이트에 잘못된/악성 데이터를 주입하는 행위에 대해 잘 방어합니다. 기존 NFC 태그를 차폐하는 것은 페라이트 차폐가 필요하여 매우 눈에 띄기 때문에 쉽지 않습니다. 금속은 자기장을 방해하여 태그가 읽히지 않게 만듭니다.

자산(Assets)

보호해야 할 자산에는 다음이 포함됩니다:
  • 전체 NDEF message, 특히 전송 중 또는 저장 상태의 NDEF records(페이로드 및 헤더 포함). Web NFC로 트리거된 작업에 의해 덮어쓰일 때 데이터 공개 및 데이터 수정으로부터 보호되어야 합니다. 또한 솔루션에 연결된 태그를 파괴하는 등의 서비스 거부(DoS) 공격도 포함됩니다 (예: NFC 태그로 배치된 솔루션을 악의적 행위자가 파괴).
  • Web NFC를 사용하여 NFC content 작성자 또는 Web NFC를 사용하는 웹사이트가 직접 또는 간접적으로 파악할 수 있는 사용자 ID나 기타 프라이버시 민감 속성. 이 데이터는 직접 사용되거나 제3자에게 유출될 수 있습니다. 예: 사용자 위치, 장치 식별자, 사용자 식별자.
  • Web NFC를 사용하는 웹 페이지에 노출되는 사용자 데이터. 웹 페이지는 Web NFC 이외의 수단으로 사용자 데이터를 수집할 수도 있으나, 이 데이터를 NDEF 레코드에 포함하여 Web NFC를 통해 공유할 수 있습니다.
  • 사용자 장치의 무결성. NFC 태그를 읽는 행위가 사용자 장치의 손상을 초래할 수 있으며, 이는 다른 Web NFC 또는 플랫폼 자산의 손실로 이어질 수 있습니다.

공격자 모델

다음과 같은 공격자 패턴이 고려되었습니다:

위협

NFC 보안에 대한 소개는 여기 에서 확인할 수 있습니다. Web NFC에 대한 잠재적 위협은 아래와 같습니다.

지문 채취(Fingerprinting) 및 데이터 수집

위협 설명
악성 웹 페이지가 사용자 동의 없이 사용자 데이터, ID 또는 위치와 같은 기타 프라이버시 민감 속성을 수집하고 이를 제3자에게 노출(태그에 기록)합니다.
영향받는 자산
사용자 데이터, 사용자 ID 또는 기타 프라이버시 민감 속성
행위자
Web NFC를 사용하는 악성 웹 페이지 소유자, 악성 태그 소유자.
완화책 및 코멘트
사용자는 해당 웹 페이지에서 NFC로 공유될 수 있는 데이터가 무엇인지 인지할 수 있어야 합니다. 개인 데이터 접근에는 권한과 사용자 프롬프트를 사용하고, NFC에 노출되는 사용자 데이터를 최소화하세요. NFC 태그는 사용자의 허가 없이 사용자의 장치가 웹사이트로 이동하도록 유도해서는 안 됩니다. 단, 사이트가 포그라운드에 있거나 포그라운드로 전환되어 권한이 부여된 경우는 예외입니다. 사용자 에이전트는 Geolocation API에 나열된 보안 및 프라이버시 조치를 고려해야 합니다.

NFC 태그 수정

위협 설명
사용자 동의 없이 NFC 태그가 수정되며, 읽기 전용으로 만들어져 되돌릴 수 없는 경우도 있습니다. 이는 악성 태그를 통한 추가 공격을 가능하게 하거나, 하나 이상의 태그를 사용 불가능하게 만드는 서비스 거부(DoS) 공격이 될 수 있습니다.
영향받는 자산
저장 상태의 NDEF 메시지 레코드(페이로드 및 헤더 포함).
행위자
악성 웹 페이지 제작자, 악성 사용자.
완화책 및 코멘트
태그에 쓰기 및 읽기 전용으로 만들기에는 권한과 사용자 프롬프트가 필요합니다. 또는 특정 웹 페이지가 쓸 수 있는 태그를 제한하세요. 예: 웹 페이지는 자신의 origin과 연결할 수 있는 태그에만 쓸 수 있음. 또는 덮어쓰기를 허용하되, 쓰면 안 되는 태그는 읽기 전용으로 보호하세요. NFC 태그 수정을 감지하기 위해 NDEF signature를 사용하세요.

전송 중 NDEF 레코드 수정

위협 설명
Web NFC와 NFC adapter, 사용자 장치 사이에서 전송되는 NDEF record가 수정되어 다양한 중간자 공격이나 서비스 거부(DoS) 공격을 유발합니다. 또한 NDEF signature 레코드는 변경된 콘텐츠와 함께 제거되거나 교체될 수 있습니다.
영향받는 자산
전송 중인 NDEF record들.
행위자
악성 중간자 사용자.
완화책 및 코멘트
이 위협은 Web NFC 구현의 범위를 벗어납니다. 애플리케이션은 NDEF signature와 적절한 도구(서명 알고리즘, 인증서, 보안 정책)를 사용하여 NFC content를 보호할 수 있습니다. 또한 플랫폼 스택을 강화하세요.

NDEF 레코드 페이로드 노출

위협 설명
저장 상태(태그에 저장) 또는 Web NFC와 NFC adapter 사이 전송 중인 NDEF record의 기밀 페이로드가 무단 당사자에게 읽힙니다.
영향받는 자산
전송 중 및 저장 상태의 기밀 NDEF 메시지 페이로드.
행위자
악성 중간자 사용자, 악성 웹 페이지 제작자.
완화책 및 코멘트
기밀성을 보장하려면 페이로드 암호화와 안전한 통신을 사용하고, Web NFC와 NFC adapter 간 데이터 교환, 인증 및 인가를 보장하세요.

악성 NFC 태그를 통한 능동 공격

위협 설명
악성 태그가 자의 또는 타의로 장치에 의해 읽힐 수 있으며, 읽힌 데이터가 사용자 에이전트에 대한 공격 벡터가 될 수 있습니다. 예를 들어 장치에서 동작을 트리거하려 시도할 수 있으며, 이는 악성 웹 사이트를 여는 것, 장치를 공격하도록 준비된 이미지를 여는 것 등 위협이 될 수 있습니다.
영향받는 자산
사용자 장치의 무결성, 기타 모든 Web NFC 자산.
행위자
악성 태그 제작자.
완화책 및 코멘트
이는 기존 모든 NFC 태그에서 일반적으로 존재하는 문제입니다. 데이터는 애플리케이션 특수 데이터로 간주됩니다. 구현에는 보안 강화가 필요합니다. 짧은 거리와 읽기 시 임계 각도, 포커스 요구 사항 때문에 비자발적 접촉 가능성은 낮습니다. 스마트 포스터 및 기타 태그에 대한 자동 동작은 허용되어서는 안 됩니다. 사용자에게 NFC 통신 중 발생하는 내용을 인지시키고 이를 제어할 수 있는 권한을 제공해야 합니다. 예: smart poster의 콘텐츠 열기, NFC handover를 통한 (악성일 수 있는) WiFi 자동 연결 등. 신뢰할 수 없는 NFC 태그에서의 동작은 허용하지 마십시오. 신뢰는 NDEF signature 검사로 수립할 수 있습니다.

구현을 위한 보안 메커니즘

권한 획득

구현은 예를 들어 사용자에 의해 명시적으로 부여된 권한과 같은 메커니즘을 사용하여 obtain permission 해야 합니다. NFC 관련 권한을 구현하기 위해 UA가 [[[PERMISSIONS]]] API를 사용하는 것이 제안됩니다.

구현은 세션별/휘발성 권한을 사용할 수 있습니다.

NFC 동작 중 사용자 경고

구현은 웹 페이지가 NFC 어댑터에 접근할 때마다(예: 스캔이 진행 중일 때) 오버레이 대화상자를 표시하여 사용자에게 경고할 수 있습니다.

애플리케이션을 위한 보안 메커니즘

NFC content 암호화

NFC를 통해 교환되는 데이터의 기밀성을 신뢰하려면, 애플리케이션은 공개키 기반구조(PKI)에 기반한 키 관리와 함께 암호화된 NFC content를 사용할 수 있습니다. 키 관리는 Web NFC의 범위를 벗어납니다.

NDEF 레코드 서명

NFC를 통해 교환되는 데이터의 무결성을 신뢰하려면, 사용자 에이전트는 키 관리에 공개키 기반구조를 사용하는 NDEF signature를 사용할 수 있습니다.

NDEF signature 버전 1.0([[NFC-SECURITY]])으로 서명된 태그의 경우, 서명은 NDEF 헤더의 첫 바이트를 제외하고 TYPE field, ID field, PAYLOAD field에만 적용되어 공격 표면을 남깁니다. [[NFC-SECURITY]] 버전 2.0은 서명에 태그 하드웨어 속성을 포함하고 더 짧은 인증서를 허용합니다.

하나의 NDEF signature는 이후에 또 다른 NDEF signature가 나타나거나 NDEF message의 시작에 도달할 때까지 앞선 레코드를 포괄합니다.

알려진 취약성을 완화하기 위해, 애플리케이션이 항상 단일 NDEF signature로 전체 NDEF message에 서명하고, 서명 생성 및 검증을 위한 적절한 도구 체인과 보안 정책을 사용할 것을 권장합니다.

보안 정책

이 섹션은 구현을 위한 규범적 보안 정책을 나열합니다.

보안 컨텍스트

오직 secure contexts만이 NFC content에 접근할 수 있습니다. 브라우저는 개발 목적에 한해 이 규칙을 무시할 수 있습니다.

표시 중인 문서

Web NFC 기능은 top-level browsing context의 {{Document}}에서만 허용되며, 해당 {{Document/visibilityState}}는 `"visible"`이어야 합니다.

이는 또한 디스플레이가 꺼져 있거나 장치가 잠겨 있는 경우 UA가 NFC 라디오 접근을 차단해야 함을 의미합니다. 백그라운드 웹 페이지의 경우, NFC content 수신 및 쓰기는 suspended 되어야 합니다.

권한 제어

NFC tag를 영구적으로 읽기 전용으로 만드는 작업은 반드시 obtain permission 해야 하며, 그렇지 않으면 실패해야 합니다. [[[#making-content-read-only]]] 섹션을 참조하세요.

NFC content 읽기를 위한 리스너 설정은 obtain permission 하는 것이 좋습니다.

NFC contentNFC tag에 쓰는 작업은 반드시 obtain permission 해야 합니다. [[[#writing-content]]] 섹션을 참조하세요.

현재 탐색 세션을 넘어 보존되는 모든 권한은 반드시 철회 가능해야 합니다.

차단 목록

Web NFC는 취약한 NFC 장치에 대해 웹사이트가 이를 악용하지 못하도록 blocklist를 포함합니다.

물리적 위치 누출 위험 경고

NFC content를 수신하거나 기록할 때, UA는 해당 origin이 물리적 위치를 추론할 수 있음을 사용자에게 경고할 수 있습니다.

자동 처리 제한

NFC content의 페이로드 데이터가 신뢰할 수 없을 때, UA는 해당 콘텐츠의 자동 처리(예: NFC tag에서 발견된 URL로 웹 페이지 열기, 애플리케이션 설치 또는 기타 동작)를 사용자 승인 없이 수행해서는 안 됩니다.

NFC content 서명

다음 정책은 애플리케이션에서 구현할 것을 권장합니다.
  • smart poster는 동일 발급자의 단일 NDEF signature 레코드로 서명된 경우에만 신뢰할 수 있으며, 메시지의 첫 번째 레코드이거나, 다른 NDEF signature 앞에 위치해야 합니다.
  • NDEF message는 동일 발급자의 단일 NDEF signature 레코드로 서명된 경우에만 신뢰할 수 있습니다.
  • 사용자 에이전트는 NDEF signature 레코드를 검증하지 않고 노출합니다. 서명을 검증하고 NFC content에 서명하는 책임은 애플리케이션에 있습니다.
  • 애플리케이션은 NDEF signature 생성 및 검증을 위해 적절한 서명 알고리즘, 인증서 및 보안 정책을 사용해야 합니다. 또한 NDEF signature에 대한 알려진 공격(예: NDEF signature 제거, NFC content 수정과 함께 NDEF signature 교체, 레코드의 PAYLOAD LENGTH field 변경을 통한 레코드 재정렬 등)을 고려하십시오.

감사의 글

편집자는 이 문서에 기여한 Jeffrey Yasskin, Anne van Kesteren, Anssi Kostiainen, Domenic Denicola, Daniel Ehrenberg, Jonas Sicking, Don Coleman, Salvatore Iovene, Rijubrata Bhaumik, Wanming Lin, Han Leon, Ryan Sleevi, Balázs Engedy, Theodore Olsauskas-Warren, Reilly Grant, Diego González, Daniel Appelquist께 감사드립니다.

웹 플랫폼에 NFC를 노출하는 초기 work와 현재 접근 방식에 대한 지원에 대해 Luc Yriarte와 Samuel Ortiz에게 특별한 감사를 전합니다. 또한 보안 및 프라이버시 섹션에 기여한 Elena Reshetova에게 특별한 감사를 전합니다.