Full Code of pokusew/nfc-pcsc for AI

master f2284c001566 cached
42 files
131.4 KB
39.5k tokens
115 symbols
1 requests
Download .txt
Repository: pokusew/nfc-pcsc
Branch: master
Commit: f2284c001566
Files: 42
Total size: 131.4 KB

Directory structure:
gitextract_nlchtnd6/

├── .babelrc
├── .editorconfig
├── .github/
│   └── workflows/
│       └── ci.yml
├── .gitignore
├── .idea/
│   ├── codeStyles/
│   │   ├── Project.xml
│   │   └── codeStyleConfig.xml
│   ├── encodings.xml
│   ├── jsLibraryMappings.xml
│   ├── misc.xml
│   ├── modules.xml
│   ├── nfc-pcsc.iml
│   ├── vcs.xml
│   └── watcherTasks.xml
├── .npmignore
├── LICENSE.md
├── README.md
├── ava.config.js
├── examples/
│   ├── BitSet.js
│   ├── basic.js
│   ├── from-readme-3.js
│   ├── led.js
│   ├── mifare-classic.js
│   ├── mifare-desfire.js
│   ├── mifare-ultralight-c.js
│   ├── mifare-ultralight-ntag.js
│   ├── mini-logger.js
│   ├── ndef.js
│   ├── pretty-logger.js
│   ├── read-write.js
│   ├── uid-logger.js
│   ├── utils.js
│   └── without-auto.js
├── package.json
├── src/
│   ├── ACR122Reader.js
│   ├── NFC.js
│   ├── Reader.js
│   ├── errors.js
│   └── index.js
└── test/
    ├── README.md
    ├── _node-version-test.js
    ├── helpers/
    │   └── pcsclite-mock.js
    └── tests.js

================================================
FILE CONTENTS
================================================

================================================
FILE: .babelrc
================================================
{
	"plugins": [
		"@babel/plugin-transform-modules-commonjs",
		"@babel/plugin-syntax-object-rest-spread",
		"@babel/plugin-transform-class-properties"
	]
}


================================================
FILE: .editorconfig
================================================
# EditorConfig
# https://www.editorconfig.org/
root = true

[*]
indent_style = tab
indent_size = 4
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[{package.json,*.yaml,*.yml}]
indent_style = space
indent_size = 2

[*.md]
trim_trailing_whitespace = false

# JetBrains IDEs do NOT use empty final newline
# for their metadata files (e.g., .xml/.iml files in .idea dir and .iml everywhere).
# Also, using `**/.idea/**/*` stopped working in recent versions,
# so now `.idea/**/*` is used instead.
[{.idea/**/*,*.iml}]
indent_style = space
indent_size = 2
insert_final_newline = false


================================================
FILE: .github/workflows/ci.yml
================================================
#
# GitHub Actions Workflow
#   reference: https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions
#
#   useful example: https://github.com/mikeal/bundle-size-action/blob/master/.github/workflows/mikeals-workflow.yml
#

name: CI

on: [ push, pull_request ]

jobs:

  build:

    runs-on: ${{ matrix.os }}

    strategy:
      fail-fast: false
      matrix:
        # https://github.com/actions/runner-images#available-images
        # macos-latest-large is x64
        # macos-latest is arm64
        os: [ ubuntu-latest, windows-latest ]
        node-version: [ 8.x, 10.x, 12.x, 13.x, 14.x, 16.x, 18.x, 20.x ]
        exclude:
          # exclude Node.js 8.x on ubuntu-latest
          # node-gyp fails during in gyp's Python code:
          #   AttributeError: module 'collections' has no attribute 'MutableSet'
          # TODO: try to fix
          - os: ubuntu-latest
            node-version: 8.x
          # temporarily exclude Node.js 8.x-14.x on windows-latest
          # node-gyp cannot find compatible Microsoft Visual Studio
          # TODO: try to fix (install manually an older MVS)
          - os: windows-latest
            node-version: 8.x
          - os: windows-latest
            node-version: 10.x
          - os: windows-latest
            node-version: 12.x
          - os: windows-latest
            node-version: 13.x
          - os: windows-latest
            node-version: 14.x
        include:
          # macos-latest is arm64 which supports only the latest Node.js versions
          - os: macos-latest
            node-version: 18.x
          - os: macos-latest
            node-version: 20.x

    steps:

      # https://github.com/actions/checkout
      - uses: actions/checkout@v4

      - name: Use Node.js ${{ matrix.node-version }}
        # https://github.com/actions/setup-node
        uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}

      - name: Install pcsclite
        run: sudo apt-get install -y libpcsclite1 libpcsclite-dev pcscd
        if: matrix.os == 'ubuntu-latest'

      - name: Install dependencies
        run: npm install --verbose

      - name: Build dist
        run: npm run build

      - name: Run basic test
        run: node test/_node-version-test.js

      - name: Run tests
        run: npm test
        # TODO: enable once tests do not get stuck on Windows
        # AVA supports only officially supported Node.js versions
        if: matrix.os != 'windows-latest' && contains(fromJSON('["18.x", "20.x"]'), matrix.node-version)


================================================
FILE: .gitignore
================================================
### GENERAL

# Windows
Thumbs.db
Desktop.ini

# macOS
.DS_Store
.supported

# common code editors
.atom/
.vscode/

# NetBeans
nbproject/private/

# JetBrains IDEs
# see https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
**/.idea/workspace.xml
**/.idea/tasks.xml
**/.idea/usage.statistics.xml
**/.idea/shelf/
**/.idea/httpRequests/
**/.idea/dataSources/
**/.idea/dataSources.local.xml
captures

# common files
*.log


### PROJECT

# node, npm
node_modules/

# built source (pushed into npm)
/dist/


================================================
FILE: .idea/codeStyles/Project.xml
================================================
<component name="ProjectCodeStyleConfiguration">
  <code_scheme name="Project" version="173">
    <option name="OTHER_INDENT_OPTIONS">
      <value>
        <option name="USE_TAB_CHARACTER" value="true" />
      </value>
    </option>
    <DartCodeStyleSettings>
      <option name="DELEGATE_TO_DARTFMT" value="false" />
    </DartCodeStyleSettings>
    <HTMLCodeStyleSettings>
      <option name="HTML_ATTRIBUTE_WRAP" value="0" />
      <option name="HTML_TEXT_WRAP" value="0" />
      <option name="HTML_SPACE_INSIDE_EMPTY_TAG" value="true" />
      <option name="HTML_DO_NOT_INDENT_CHILDREN_OF" value="" />
      <option name="HTML_ENFORCE_QUOTES" value="true" />
    </HTMLCodeStyleSettings>
    <JSCodeStyleSettings version="0">
      <option name="USE_DOUBLE_QUOTES" value="false" />
      <option name="ENFORCE_TRAILING_COMMA" value="WhenMultiline" />
      <option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
      <option name="SPACES_WITHIN_IMPORTS" value="true" />
    </JSCodeStyleSettings>
    <TypeScriptCodeStyleSettings version="0">
      <option name="SPACES_WITHIN_IMPORTS" value="true" />
    </TypeScriptCodeStyleSettings>
    <codeStyleSettings language="CMake">
      <indentOptions>
        <option name="USE_TAB_CHARACTER" value="true" />
      </indentOptions>
    </codeStyleSettings>
    <codeStyleSettings language="CSS">
      <indentOptions>
        <option name="USE_TAB_CHARACTER" value="true" />
      </indentOptions>
    </codeStyleSettings>
    <codeStyleSettings language="CoffeeScript">
      <indentOptions>
        <option name="INDENT_SIZE" value="4" />
        <option name="CONTINUATION_INDENT_SIZE" value="4" />
        <option name="TAB_SIZE" value="4" />
        <option name="USE_TAB_CHARACTER" value="true" />
      </indentOptions>
    </codeStyleSettings>
    <codeStyleSettings language="Dart">
      <option name="RIGHT_MARGIN" value="300" />
      <option name="CALL_PARAMETERS_WRAP" value="0" />
      <option name="METHOD_PARAMETERS_WRAP" value="0" />
      <option name="BINARY_OPERATION_WRAP" value="0" />
      <option name="TERNARY_OPERATION_WRAP" value="0" />
      <option name="TERNARY_OPERATION_SIGNS_ON_NEXT_LINE" value="false" />
      <option name="ASSIGNMENT_WRAP" value="0" />
      <indentOptions>
        <option name="INDENT_SIZE" value="4" />
        <option name="TAB_SIZE" value="4" />
        <option name="USE_TAB_CHARACTER" value="true" />
      </indentOptions>
    </codeStyleSettings>
    <codeStyleSettings language="Gherkin">
      <indentOptions>
        <option name="INDENT_SIZE" value="4" />
        <option name="USE_TAB_CHARACTER" value="true" />
      </indentOptions>
    </codeStyleSettings>
    <codeStyleSettings language="Groovy">
      <indentOptions>
        <option name="USE_TAB_CHARACTER" value="true" />
      </indentOptions>
    </codeStyleSettings>
    <codeStyleSettings language="HTML">
      <option name="RIGHT_MARGIN" value="120" />
      <indentOptions>
        <option name="USE_TAB_CHARACTER" value="true" />
      </indentOptions>
    </codeStyleSettings>
    <codeStyleSettings language="Haml">
      <indentOptions>
        <option name="INDENT_SIZE" value="4" />
        <option name="USE_TAB_CHARACTER" value="true" />
      </indentOptions>
    </codeStyleSettings>
    <codeStyleSettings language="JAVA">
      <indentOptions>
        <option name="USE_TAB_CHARACTER" value="true" />
      </indentOptions>
    </codeStyleSettings>
    <codeStyleSettings language="JSON">
      <indentOptions>
        <option name="INDENT_SIZE" value="4" />
        <option name="USE_TAB_CHARACTER" value="true" />
      </indentOptions>
    </codeStyleSettings>
    <codeStyleSettings language="Jade">
      <indentOptions>
        <option name="USE_TAB_CHARACTER" value="true" />
      </indentOptions>
    </codeStyleSettings>
    <codeStyleSettings language="JavaScript">
      <option name="ELSE_ON_NEW_LINE" value="true" />
      <indentOptions>
        <option name="USE_TAB_CHARACTER" value="true" />
      </indentOptions>
    </codeStyleSettings>
    <codeStyleSettings language="LESS">
      <indentOptions>
        <option name="INDENT_SIZE" value="4" />
        <option name="USE_TAB_CHARACTER" value="true" />
      </indentOptions>
    </codeStyleSettings>
    <codeStyleSettings language="Lua">
      <indentOptions>
        <option name="USE_TAB_CHARACTER" value="true" />
      </indentOptions>
    </codeStyleSettings>
    <codeStyleSettings language="ObjectiveC">
      <indentOptions>
        <option name="USE_TAB_CHARACTER" value="true" />
      </indentOptions>
    </codeStyleSettings>
    <codeStyleSettings language="Python">
      <indentOptions>
        <option name="USE_TAB_CHARACTER" value="true" />
      </indentOptions>
    </codeStyleSettings>
    <codeStyleSettings language="SASS">
      <indentOptions>
        <option name="INDENT_SIZE" value="4" />
        <option name="USE_TAB_CHARACTER" value="true" />
      </indentOptions>
    </codeStyleSettings>
    <codeStyleSettings language="SCSS">
      <indentOptions>
        <option name="INDENT_SIZE" value="4" />
        <option name="USE_TAB_CHARACTER" value="true" />
      </indentOptions>
    </codeStyleSettings>
    <codeStyleSettings language="Stylus">
      <indentOptions>
        <option name="INDENT_SIZE" value="4" />
        <option name="USE_TAB_CHARACTER" value="true" />
      </indentOptions>
    </codeStyleSettings>
    <codeStyleSettings language="Swift">
      <indentOptions>
        <option name="USE_TAB_CHARACTER" value="true" />
      </indentOptions>
    </codeStyleSettings>
    <codeStyleSettings language="TypeScript">
      <indentOptions>
        <option name="USE_TAB_CHARACTER" value="true" />
      </indentOptions>
    </codeStyleSettings>
    <codeStyleSettings language="XML">
      <indentOptions>
        <option name="USE_TAB_CHARACTER" value="true" />
      </indentOptions>
    </codeStyleSettings>
    <codeStyleSettings language="kotlin">
      <indentOptions>
        <option name="USE_TAB_CHARACTER" value="true" />
      </indentOptions>
    </codeStyleSettings>
  </code_scheme>
</component>

================================================
FILE: .idea/codeStyles/codeStyleConfig.xml
================================================
<component name="ProjectCodeStyleConfiguration">
  <state>
    <option name="USE_PER_PROJECT_SETTINGS" value="true" />
  </state>
</component>

================================================
FILE: .idea/encodings.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="Encoding">
    <file url="PROJECT" charset="UTF-8" />
  </component>
</project>

================================================
FILE: .idea/jsLibraryMappings.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="JavaScriptLibraryMappings">
    <includedPredefinedLibrary name="Node.js Core" />
  </component>
</project>

================================================
FILE: .idea/misc.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="JavaScriptSettings">
    <option name="languageLevel" value="ES6" />
  </component>
</project>

================================================
FILE: .idea/modules.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="ProjectModuleManager">
    <modules>
      <module fileurl="file://$PROJECT_DIR$/.idea/nfc-pcsc.iml" filepath="$PROJECT_DIR$/.idea/nfc-pcsc.iml" />
    </modules>
  </component>
</project>

================================================
FILE: .idea/nfc-pcsc.iml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
  <component name="NewModuleRootManager">
    <content url="file://$MODULE_DIR$">
      <sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
    </content>
    <orderEntry type="inheritedJdk" />
    <orderEntry type="sourceFolder" forTests="false" />
  </component>
</module>

================================================
FILE: .idea/vcs.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="VcsDirectoryMappings">
    <mapping directory="$PROJECT_DIR$" vcs="Git" />
  </component>
</project>

================================================
FILE: .idea/watcherTasks.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="ProjectTasksOptions" suppressed-tasks="Babel" />
</project>

================================================
FILE: .npmignore
================================================
# Keeping files out of your package
# docs: https://docs.npmjs.com/misc/developers#keeping-files-out-of-your-package
# note: when both .npmignore and .gitignore are present, only .npmignore has effect
# related: https://stackoverflow.com/questions/24942161/does-npm-ignore-files-listed-in-gitignore

### GENERAL

# Windows
Thumbs.db
Desktop.ini

# macOS
.DS_Store
.supported

# common code editors
.atom/
.vscode/

# NetBeans
nbproject/private/

# JetBrains IDEs
# no need to ship metadata
.idea/

# common files
*.log


### PROJECT

# node, npm
node_modules/


================================================
FILE: LICENSE.md
================================================
MIT License

Copyright (c) 2016-present Martin Endler

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
# nfc-pcsc

[![npm](https://img.shields.io/npm/v/nfc-pcsc.svg)](https://www.npmjs.com/package/nfc-pcsc)
[![build status](https://img.shields.io/github/actions/workflow/status/pokusew/nfc-pcsc/ci.yml?logo=github)](https://github.com/pokusew/nfc-pcsc/actions/workflows/ci.yml)
[![nfc-pcsc channel on discord](https://img.shields.io/badge/discord-join%20chat-61dafb.svg)](https://discord.gg/bg3yazg)

Easy **reading and writing NFC tags and cards** in Node.js

Built-in support for auto-reading **card UIDs** and reading tags emulated with [**Android HCE**](https://developer.android.com/guide/topics/connectivity/nfc/hce.html).

> **NOTE:** Reading tag UID and methods for writing and reading tag content **depend on NFC reader commands support**.
It is tested to work with **ACR122 USB reader** but it should work with **all PC/SC compliant devices**.  
When detecting tags does not work see [Alternative usage](#alternative-usage).

This library uses pcsclite native bindings [pokusew/node-pcsclite](https://github.com/pokusew/node-pcsclite) under the hood.

**Psst!** Problems upgrading to 0.6.0? Check out [this migration note](#migration-from-older-versions-to-060).


<!-- _**Psst!** You are browsing the documentation for the master branch, [look here](https://github.com/pokusew/nfc-pcsc/tree/v0.6.0) to see the usage of latest published version._ -->


## Content

<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->


- [Installation](#installation)
- [Flow of handling tags](#flow-of-handling-tags)
- [Basic usage](#basic-usage)
	- [Running examples locally](#running-examples-locally)
- [Alternative usage](#alternative-usage)
- [Reading and writing data](#reading-and-writing-data)
- [More examples](#more-examples)
- [FAQ](#faq)
  - [Migration from older versions to 0.6.0](#migration-from-older-versions-to-060)
  - [Can I use this library in my Electron app?](#can-i-use-this-library-in-my-electron-app)
  - [Can I use this library in my angular-electron app?](#can-i-use-this-library-in-my-angular-electron-app)
  - [Do I have to use Babel in my app too?](#do-i-have-to-use-babel-in-my-app-too)
  - [Which Node.js versions are supported?](#which-nodejs-versions-are-supported)
  - [How do I require/import this library?](#how-do-i-requireimport-this-library)
  - [Can I read a NDEF formatted tag?](#can-i-read-a-ndef-formatted-tag)
  - [Can I use this library in my React Native app?](#can-i-use-this-library-in-my-react-native-app)
- [Frequent errors](#frequent-errors)
  - [TypeError: NFC is not a constructor](#typeerror-nfc-is-not-a-constructor)
  - [Transaction failed error when using `CONNECT_MODE_DIRECT`](#transaction-failed-error-when-using-connect_mode_direct)
  - [MIFARE Classic: Authentication Error after Multiple Writes](#mifare-classic-authentication-error-after-multiple-writes)
  - [Reading data from a type 4 tags inside a Elsys.se sensors](#reading-data-from-a-type-4-tags-inside-a-elsysse-sensors)
- [License](#license)

<!-- END doctoc generated TOC please keep comment here to allow auto update -->


## Installation

**Requirements:** **at least Node.js 8 or newer** (see [this FAQ](#which-nodejs-versions-are-supported) for more info)

**Note:** This library can be used only in **Node.js** environments on Linux/UNIX, macOS and Windows. Read why [here](#can-i-use-this-library-in-my-react-native-app).

1. **Node Native Modules build tools**

    Because this library (via [pokusew/node-pcsclite](https://github.com/pokusew/node-pcsclite) under the hood) uses Node Native Modules (C++ Addons),
    which are automatically built (using [node-gyp](https://github.com/nodejs/node-gyp))
    when installing via npm or yarn, you need to have installed **C/C++ compiler
    toolchain and some other tools** depending on your OS.
    
    **Please refer to the [node-gyp > Installation](https://github.com/nodejs/node-gyp#installation)**
    for the list of required tools depending on your OS and steps how to install them.

2. **PC/SC API in your OS**

    On **macOS** and **Windows** you **don't have to install** anything,
    **pcsclite API** is provided by the OS.
    
    On Linux/UNIX you'd probably need to install pcsclite library and daemon**.

    > For example, in Debian/Ubuntu:
    > ```bash
    > apt-get install libpcsclite1 libpcsclite-dev
    > ```
    > To run any code you will also need to have installed the pcsc daemon:
    > ```bash
    > apt-get install pcscd
    > ```

3. **Once you have all needed libraries, you can install nfc-pcsc using npm:**

    ```bash
    npm install nfc-pcsc --save
    ```
    
    or using Yarn:
    
    ```bash
    yarn add nfc-pcsc
    ```


## Flow of handling tags

When a NFC tag (card) is attached to the reader, the following is done:

1. it tries to find out the standard of card (`TAG_ISO_14443_3` or `TAG_ISO_14443_4`)

2. it will connect to the card, so any other card specific commands could be sent

3. handling of card
	
	- when `autoProcessing` is true (default value) it will handle card by the standard:  
		
		`TAG_ISO_14443_3` *(MIFARE Ultralight, 1K ...)*: sends GET_DATA command to retrieve **card UID**  
		`TAG_ISO_14443_4` *(e.g.: Android HCE)*: sends SELECT_APDU command to retrieve data by file
		
		**then `card` event is fired, for which you can listen and then you can read or write data on the card**  
		see [Basic usage](#basic-usage) how to do it
		
	- when `autoProcessing` is false (default value) it will only fire `card` event  
	  then you can send whatever commands you want using `reader.transmit` method  
	  see [Alternative usage](#alternative-usage) how to do it
	  
4. you can read data, write data and send other commands


## Basic usage

> ### Running examples locally
> If you want see it in action, clone this repository, install dependencies with npm and run `npm run example`.
> Of course, instead of npm you can Yarn if you want.
> See scripts section of [package.json](/package.json) for all available examples run commands.
> ```bash
> git clone https://github.com/pokusew/nfc-pcsc.git
> cd nfc-pcsc
> npm install
> npm run example
> ```

You can use this library in any Node.js 8+ environment (even in an Electron app). 

```javascript
// in ES6
import { NFC } from 'nfc-pcsc';

// without Babel in ES2015
const { NFC } = require('nfc-pcsc');

const nfc = new NFC(); // optionally you can pass logger

nfc.on('reader', reader => {

	console.log(`${reader.reader.name}  device attached`);

	// enable when you want to auto-process ISO 14443-4 tags (standard=TAG_ISO_14443_4)
	// when an ISO 14443-4 is detected, SELECT FILE command with the AID is issued
	// the response is available as card.data in the card event
	// see examples/basic.js line 17 for more info
	// reader.aid = 'F222222222';

	reader.on('card', card => {

		// card is object containing following data
		// [always] String type: TAG_ISO_14443_3 (standard nfc tags like MIFARE) or TAG_ISO_14443_4 (Android HCE and others)
		// [always] String standard: same as type
		// [only TAG_ISO_14443_3] String uid: tag uid
		// [only TAG_ISO_14443_4] Buffer data: raw data from select APDU response

		console.log(`${reader.reader.name}  card detected`, card);

	});

	reader.on('card.off', card => {
		console.log(`${reader.reader.name}  card removed`, card);
	});

	reader.on('error', err => {
		console.log(`${reader.reader.name}  an error occurred`, err);
	});

	reader.on('end', () => {
		console.log(`${reader.reader.name}  device removed`);
	});

});

nfc.on('error', err => {
	console.log('an error occurred', err);
});
```


## Alternative usage

You can **disable auto processing of tags** and process them yourself.
It may be useful when you are using other than ACR122 USB reader or non-standard tags.

```javascript
// in ES6
import { NFC } from 'nfc-pcsc';

// without Babel in ES2015
const { NFC } = require('nfc-pcsc');

const nfc = new NFC(); // optionally you can pass logger

nfc.on('reader', reader => {

	// disable auto processing
	reader.autoProcessing = false;

	console.log(`${reader.reader.name}  device attached`);

	reader.on('card', card => {

		// card is object containing following data
		// String standard: TAG_ISO_14443_3 (standard nfc tags like MIFARE Ultralight) or TAG_ISO_14443_4 (Android HCE and others)
		// String type: same as standard
		// Buffer atr

		console.log(`${reader.reader.name}  card inserted`, card);

		// you can use reader.transmit to send commands and retrieve data
		// see https://github.com/pokusew/nfc-pcsc/blob/master/src/Reader.js#L291

	});
	
	reader.on('card.off', card => {	
		console.log(`${reader.reader.name}  card removed`, card);
	});

	reader.on('error', err => {
		console.log(`${reader.reader.name}  an error occurred`, err);
	});

	reader.on('end', () => {
		console.log(`${reader.reader.name}  device removed`);
	});

});

nfc.on('error', err => {
	console.log('an error occurred', err);
});
```


## Reading and writing data

You can read from and write to numerous NFC tags including MIFARE Ultralight (tested), MIFARE Classic, MIFARE DESFire, ...

> Actually, you can even read/write any possible non-standard NFC tag and card, via sending APDU commands according card's technical documentation via `reader.transmit`.

Here is **a simple example** showing reading and writing data to simple card **without authenticating** (e.g. MIFARE Ultralight):  
_See [Basic usage](#basic-usage) how to set up reader or [look here for full code](/examples/from-readme-3.js)_

```javascript
reader.on('card', async card => {

	console.log();
	console.log(`card detected`, card);

	// example reading 12 bytes assuming containing text in utf8
	try {

		// reader.read(blockNumber, length, blockSize = 4, packetSize = 16)
		const data = await reader.read(4, 12); // starts reading in block 4, continues to 5 and 6 in order to read 12 bytes
		console.log(`data read`, data);
		const payload = data.toString(); // utf8 is default encoding
		console.log(`data converted`, payload);

	} catch (err) {
		console.error(`error when reading data`, err);
	}

	// example write 12 bytes containing text in utf8
	try {

		const data = Buffer.allocUnsafe(12);
		data.fill(0);
		const text = (new Date()).toTimeString();
		data.write(text); // if text is longer than 12 bytes, it will be cut off
		// reader.write(blockNumber, data, blockSize = 4)
		await reader.write(4, data); // starts writing in block 4, continues to 5 and 6 in order to write 12 bytes
		console.log(`data written`);

	} catch (err) {
		console.error(`error when writing data`, err);
	}

});
```

## More examples

📦📦📦 You can find more examples in [examples folder](/examples), including:

* [read-write.js](/examples/read-write.js) – detecting, reading and writing cards standard ISO/IEC 14443-3 cards (NTAG, MIFARE Ultralight, ...)
* [mifare-classic.js](/examples/mifare-classic.js) – authenticating, reading and writing MIFARE Classic cards
* [mifare-desfire.js](/examples/mifare-desfire.js) – authenticating and accessing data on MIFARE DESFire cards
* [mifare-ultralight-c.js](/examples/mifare-ultralight-ntag.js) – an example implementation of MIFARE Ultralight C (3DES authentication)
* [mifare-ultralight-ntag.js](/examples/mifare-ultralight-ntag.js) – an example implementation of Mifare Ultralight EV1 and NTAG specific commands
* [basic.js](/examples/basic.js) – reader events explanation
* [led.js](/examples/led.js) – controlling LED and buzzer of ACR122U reader
* [uid-logger.js](/examples/uid-logger.js) – logs uid when a card is detected

Feel free to open pull request, if you have any useful example, that you'd like to add. 


## FAQ

### Migration from older versions to 0.6.0

There was a **breaking change in 0.6.0**, as the default export was removed _(because of non-standard behaviour of ES6 modules in ES5 env (see [#12](https://github.com/pokusew/nfc-pcsc/issues/12) and [v0.6.0 release changelog](https://github.com/pokusew/nfc-pcsc/releases/tag/v0.6.0)))_.

You have to **update all requires or imports** of this library to the following _(note the brackets around NFC)_:
```javascript
// in ES6 environment
import { NFC } from 'nfc-pcsc';

// in ES2015 environment
const { NFC } = require('nfc-pcsc');
```

### Can I use this library in my [Electron](https://electron.atom.io/) app?

**Yes, you can!** It works well.

**But please note**, that this library uses [Node Native Modules](https://nodejs.org/api/addons.html) (underlying library [pokusew/node-pcsclite](https://github.com/pokusew/node-pcsclite) which provides access to PC/SC API).

Read carefully **[Using Native Node Modules](https://electron.atom.io/docs/tutorial/using-native-node-modules/) guide in Electron documentation** to fully understand the problematic.

**Note**, that because of Node Native Modules, you must build your app on target platform (you must run Windows build on Windows machine, etc.).  
You can use CI/CD server to build your app for certain platforms.  
For Windows, I recommend you to use [AppVeyor](https://appveyor.com/).  
For macOS and Linux build, there are plenty of services to choose from, for example [CircleCI](https://circleci.com/), [Travis CI](https://travis-ci.com/) [CodeShip](https://codeship.com/).

### Can I use this library in my [angular-electron](https://github.com/maximegris/angular-electron) app?

**Yes, you can!** But as this library uses Node Native Modules, you must change some config in `package.json` and `webpack.config.js` as described in [this comment](https://github.com/pokusew/nfc-pcsc/issues/24#issuecomment-327038188).

### Do I have to use Babel in my app too?

**No, you don't have to.** This library works great **in any Node.js 8+ environment** (even in an **Electron** app).

> Psst! Instead of using **async/await** (like in examples), you can use Promises.
> ```
> reader
>   .read(...)
>   .then(data => ...)
>   .catch(err => ...))
> ```

Babel is used under the hood to transpile features, that are not supported in **Node.js 8** (for example ES6 modules – import/export, see [.babelrc](/.babelrc) for list of used plugins). The transpiled code (in the dist folder) is then published into npm and when you install and require the library, the transpiled code is used, so you don't have to worry about anything.

### Which Node.js versions are supported?

nfc-pcsc officially supports the following Node.js versions: **8.x, 9.x, 10.x, 11.x, 12.x, 13.x, 14.x, 16.x, 18.x, 20.x**.

### How do I require/import this library?

```javascript
// in ES6 environment
import { NFC } from 'nfc-pcsc';

// in ES2015 environment
const { NFC } = require('nfc-pcsc');
```

If you want to import uncompiled source and transpile it yourself (not recommended), you can do it as follows:

```javascript
import { NFC } from 'nfc-pcsc/src';
```

### Can I read a NDEF formatted tag?

**Yes, you can!** You can read raw byte card data with `reader.read` method, and then you can parse it with any NDEF parser, e.g. [TapTrack/NdefJS](https://github.com/TapTrack/NdefJS).

**Psst!** There is also an example ([ndef.js](/examples/ndef.js)), but it is not finished yet. Feel free to contribute.

### Can I use this library in my React Native app?

Short answer: **NO**

Explanation: **Mobile support is virtually impossible** because nfc-pcsc uses **Node Native Modules**
to access system **PC/SC API** _(actually under the hood, the pcsclite native binding
is implemented in [@pokusew/pcsclite](https://github.com/pokusew/node-pcsclite))_.
So the **Node.js runtime and PC/SC API** are required for nfc-pcsc to run.
That makes it possible to use it on the most of OS (Windows, macOS, Linux)
**directly in Node.js** or in **Electron.js and NW.js** desktop apps.


## Frequent errors

### TypeError: NFC is not a constructor

No worry, just check that you import/require the library like this _(note the brackets around NFC)_:
```javascript
// in ES6 environment
import { NFC } from 'nfc-pcsc';

// in ES2015 environment
const { NFC } = require('nfc-pcsc');
```

Take a look at [How do I require/import this library?](#how-do-i-requireimport-this-library) section for more info.

> **Note**, that `const NFC = require('nfc-pcsc');` or `import NFC from 'nfc-pcsc'` (NFC without brackets) won't work, because there is no default export.  
It was removed for non-standard behaviour of ES6 modules in ES5 env (see [#12](https://github.com/pokusew/nfc-pcsc/issues/12) and [v0.6.0 release changelog](https://github.com/pokusew/nfc-pcsc/releases/tag/v0.6.0))

### Transaction failed error when using `CONNECT_MODE_DIRECT`

No worry, just needs a proper configuration, see [explanation and instructions here](https://github.com/pokusew/nfc-pcsc/issues/13#issuecomment-302482621).

### MIFARE Classic: Authentication Error after Multiple Writes

No worry, you have probably modified a sector trailer instead of a data block, see [explanation and instructions here](https://github.com/pokusew/nfc-pcsc/issues/16#issuecomment-304989178).

### Reading data from a type 4 tags inside a [Elsys.se](https://www.elsys.se/en/) sensors

According to [@martijnthe](https://github.com/martijnthe)'s findings, it seems to be necessary to change the CLASS of READ BINARY APDU command
from the default value of `0xFF` to `0x00` in order to make a successful read.

If you experience the same problems, you can try setting the fourth argument (readClass) of the
[`reader.read(blockNumber, length, blockSize, packetSize, readClass)`](https://github.com/pokusew/nfc-pcsc/blob/master/src/Reader.js#L493) method to value `0x00`.

Relevant conversation: https://github.com/pokusew/nfc-pcsc/pull/55#issuecomment-450120232


## License

[MIT](/LICENSE.md)


================================================
FILE: ava.config.js
================================================
"use strict";

// run AVA
//   npx ava --match='*foo'
//   ./node_modules/.bin/ava --match='*foo'
// see https://github.com/avajs/ava/blob/main/docs/05-command-line.md

// https://github.com/avajs/ava/blob/main/docs/06-configuration.md
module.exports = {
	// https://github.com/avajs/ava/blob/main/docs/recipes/typescript.md
	extensions: {
		// https://github.com/avajs/ava/blob/main/docs/06-configuration.md#configuring-module-formats
		js: true,
		ts: 'commonjs',
	},
	// extensions: ['.js', '.ts'],
	require: [
		'@babel/register',
	],
	files: [
		'./test/**/*',
	],
	watchMode: {
		ignoreChanges: [
			//
			// some files and directories are ignored by default,
			// see https://github.com/avajs/ava/blob/main/docs/recipes/watch-mode.md#ignoring-changes
			//
			// note:
			//   AVA dependency tracking (https://github.com/avajs/ava/blob/main/docs/recipes/watch-mode.md#dependency-tracking)
			//   currently does not seem to work. However, since AVA v6 it should work.
			//   See
			//     https://github.com/avajs/ava/issues/2388
			//     https://github.com/avajs/ava/pull/3123
			//     https://github.com/avajs/ava/issues/2905
			//
			// use the following to debug:
			//   DEBUG=ava:watcher npx ava --watch
			//   or
			//   DEBUG=ava:* npx ava --watch
			//
			'./.idea/',
			'./temp/',
			'./dist/',
		],
	},
};


================================================
FILE: examples/BitSet.js
================================================
"use strict";

import { column } from './utils';


// TODO: cover with tests
export class BitSet {

	/**
	 * Creates a new BitSet (bit view for the Buffer instance)
	 * If an existing Buffer instance is given, then it will be used and no additional memory will be allocated.
	 * If an integer is given, then a new Buffer instance will be created allocating specified memory (bitsLength / 8)
	 * @param bitsLength bit length or existing Buffer instance
	 */
	constructor(bitsLength) {
		this.b = (bitsLength instanceof Buffer) ? bitsLength : Buffer.allocUnsafe(bitsLength / 8).fill(0);
	}

	clone() {
		// copies data into new buffer, allocates new memory
		return Buffer.from(this.b);
	}

	get buffer() {
		return this.b;
	}

	static getBufferPos(pos) {
		return Math.trunc(pos / 8);
	}

	static getMask(pos) {
		return 1 << (pos % 8);
	}

	set(pos) {
		this.b[BitSet.getBufferPos(pos)] |= BitSet.getMask(pos);
	}

	test(pos) {
		return (this.b[BitSet.getBufferPos(pos)] & BitSet.getMask(pos)) !== 0;
	}

	clear(pos) {
		this.b[BitSet.getBufferPos(pos)] &= ~BitSet.getMask(pos);
	}

	toggle(pos) {
		this.b[BitSet.getBufferPos(pos)] ^= BitSet.getMask(pos);
	}

	toArray(useBooleans = true) {
		const s = this.b.length * 8;
		const a = [];
		for (let pos = 0; pos < s; pos++) {
			if (useBooleans) {
				a.push(this.test(pos));
			}
			else {
				a.push(this.test(pos) ? 1 : 0);
			}
		}
		return a;
	}

	print(name, appendBlankLine = true) {

		const s = this.b.length * 8;

		let l1 = ' |       data:';
		let l2 = ' |            ';
		let l3 = ' | bit number:';

		for (let pos = s - 1; pos >= 0; pos--) {
			// console.log(pos);
			const numberString = column(pos, null, 3, 1);
			l1 += column(this.test(pos) ? 1 : 0, numberString.length - 1, 3, 1);
			l2 += column('↑', numberString.length - 1, 3, 1);
			l3 += numberString;
		}

		console.log(`BitSet:${name ? ' ' + name : ''}`, this.b);
		console.log(l1);
		console.log(l2);
		console.log(l3);
		if (appendBlankLine) {
			console.log();
		}

	}

}


================================================
FILE: examples/basic.js
================================================
"use strict";

// #############
// Example: Basic usage
// - see "Basic usage" section in README for an explanation
// #############

import { NFC } from '../src/index';


const nfc = new NFC(); // optionally you can pass logger

nfc.on('reader', reader => {

	console.log(`${reader.reader.name}  device attached`);

	// enable when you want to auto-process ISO 14443-4 tags (standard=TAG_ISO_14443_4)
	// when an ISO 14443-4 is detected, SELECT FILE command with the AID is issued
	// the response is available as card.data in the card event
	// you can set reader.aid to:
	// 1. a HEX string (which will be parsed automatically to Buffer)
	reader.aid = 'F222222222';
	// 2. an instance of Buffer containing the AID bytes
	// reader.aid = Buffer.from('F222222222', 'hex');
	// 3. a function which must return an instance of a Buffer when invoked with card object (containing standard and atr)
	//    the function may generate AIDs dynamically based on the detected card
	// reader.aid = ({ standard, atr }) => {
	//
	// 	return Buffer.from('F222222222', 'hex');
	//
	// };

	reader.on('card', card => {

		// card is object containing following data
		// [always] String type: TAG_ISO_14443_3 (standard nfc tags like MIFARE) or TAG_ISO_14443_4 (Android HCE and others)
		// [always] String standard: same as type
		// [only TAG_ISO_14443_3] String uid: tag uid
		// [only TAG_ISO_14443_4] Buffer data: raw data from select APDU response

		console.log(`${reader.reader.name}  card detected`, card);

	});

	reader.on('card.off', card => {
		console.log(`${reader.reader.name}  card removed`, card);
	});

	reader.on('error', err => {
		console.log(`${reader.reader.name}  an error occurred`, err);
	});

	reader.on('end', () => {
		console.log(`${reader.reader.name}  device removed`);
	});

});

nfc.on('error', err => {
	console.log('an error occurred', err);
});


================================================
FILE: examples/from-readme-3.js
================================================
"use strict";

// #############
// Example from "Reading and writing data" section of project's README
// #############

import { NFC } from '../src/index';


const nfc = new NFC();

nfc.on('reader', reader => {

	console.log(`${reader.reader.name}  device attached`);

	reader.on('card', async card => {

		console.log();
		console.log(`card detected`, card);

		// example reading 12 bytes assuming containing text in utf8
		try {

			// reader.read(blockNumber, length, blockSize = 4, packetSize = 16)
			const data = await reader.read(4, 12); // starts reading in block 4, continues to 5 and 6 in order to read 12 bytes
			console.log(`data read`, data);
			const payload = data.toString(); // utf8 is default encoding
			console.log(`data converted`, payload);

		} catch (err) {
			console.error(`error when reading data`, err);
		}

		// example write 12 bytes containing text in utf8
		try {

			const data = Buffer.allocUnsafe(12);
			data.fill(0);
			const text = (new Date()).toTimeString();
			data.write(text); // if text is longer than 12 bytes, it will be cut off
			// reader.write(blockNumber, data, blockSize = 4)
			await reader.write(4, data); // starts writing in block 4, continues to 5 and 6 in order to write 12 bytes
			console.log(`data written`);

		} catch (err) {
			console.error(`error when writing data`, err);
		}

	});

	reader.on('error', err => {
		console.log(`${reader.reader.name}  an error occurred`, err);
	});

	reader.on('end', () => {
		console.log(`${reader.reader.name}  device removed`);
	});

});

nfc.on('error', err => {
	console.log('an error occurred', err);
});


================================================
FILE: examples/led.js
================================================
"use strict";

// #############
// Example: Controlling LED and buzzer on ACR122U
// - what is covered:
//   - custom led blinks
//   - custom buzzer output
//   - repeated beeping on unsuccessful read/write operation
// - TODO:
//   - document how to allow escape commands (direct communication without card)
//   - meanwhile please see https://github.com/pokusew/nfc-pcsc/issues/13
// #############

import { NFC, CONNECT_MODE_DIRECT } from '../src/index';
import pretty from './pretty-logger';


const nfc = new NFC(pretty); // const nfc = new NFC(pretty); // optionally you can pass logger to see internal debug logs

nfc.on('reader', async reader => {

	pretty.info(`device attached`, reader);

	try {
		await reader.connect(CONNECT_MODE_DIRECT);
		await reader.setBuzzerOutput(false);
		await reader.disconnect();
	} catch (err) {
		pretty.info(`initial sequence error`, reader, err);
	}

	reader.on('card', async card => {

		pretty.info(`card detected`, reader, card);

		try {

			// red error
			await reader.led(0b01011101, [0x02, 0x01, 0x05, 0x01]);

			// green success
			await reader.led(0b00101110, [0x01, 0x00, 0x01, 0x01]);

		} catch (err) {
			pretty.error(`error when writing led`, reader, err);
		}

	});

	reader.on('error', err => {
		pretty.error(`an error occurred`, reader, err);
	});

	reader.on('end', () => {
		pretty.info(`device removed`, reader);
	});

});

nfc.on('error', err => {
	pretty.error(`an error occurred`, err);
});


================================================
FILE: examples/mifare-classic.js
================================================
"use strict";

// #############
// Example: MIFARE Classic
// - should work well with any compatible PC/SC card reader
// - what is covered:
//   - authentication
//   - reading data from card
//   - writing data to card
// - what is NOT covered yet:
//   - using sector trailers to update access rights
// #############

// ## Note about the card's data structure
//
// ### MIFARE Classic EV1 1K
// - 1024 × 8 bit EEPROM memory
// - 16 sectors of 4 blocks
// - see https://www.nxp.com/docs/en/data-sheet/MF1S50YYX_V1.pdf
//
// ### MIFARE Classic EV1 4K
// - 4096 × 8 bit EEPROM memory
// - 32 sectors of 4 blocks and 8 sectors of 16 blocks
// - see https://www.nxp.com/docs/en/data-sheet/MF1S70YYX_V1.pdf
//
// One block contains 16 bytes.
// Don't forget specify the blockSize argument blockSize=16 in reader.read and reader.write calls.
// The smallest amount of data to write is one block. You can write only the entire blocks (card limitation).
//
// sector 0
// 	block 0 - manufacturer data (read only)
// 	block 1 - data block
// 	block 2 - data block
// 	block 3 - sector trailer 0
// 		bytes 00-05: Key A (default 0xFFFFFFFFFFFF) (6 bytes)
// 		bytes 06-09: Access Bits (default 0xFF0780) (4 bytes)
// 		bytes 10-15: Key B (optional) (default 0xFFFFFFFFFFFF) (6 bytes)
// sector 1:
// 	block 4 - data block
// 	block 5 - data block
// 	block 6 - data block
// 	block 7 - sector trailer 1
// sector 2:
// 	block 8 - data block
// 	block 9 - data block
// 	block 10 - data block
// 	block 11 - sector trailer 2
// ... and so on ...

import { NFC, TAG_ISO_14443_3, TAG_ISO_14443_4, KEY_TYPE_A, KEY_TYPE_B } from '../src/index';
import pretty from './pretty-logger';


const nfc = new NFC(); // const nfc = new NFC(pretty); // optionally you can pass logger to see internal debug logs

nfc.on('reader', async reader => {

	pretty.info(`device attached`, reader);

	reader.on('card', async card => {

		// MIFARE Classic is ISO/IEC 14443-3 tag
		// skip other standards
		if (card.type !== TAG_ISO_14443_3) {
			return;
		}

		pretty.info(`card detected`, reader, card);

		// Reading and writing data from/to MIFARE Classic cards (e.g. MIFARE 1K) ALWAYS requires authentication!

		// How does the MIFARE Classic authentication work?
		// 1. You authenticate to a specific sector using a specific key (key + keyType).
		// 2. After the successful authentication, you are granted permissions according to the access conditions
		//    for the given key (access conditions are specified in the trailer section of each sector).
		//    Depending on the access conditions, you can read from / write to the blocks of this sector.
		// 3. If you want to access data in another sectors, you have to authenticate to that sector.
		//    Then you can access the data from the block within that sector (only from that sector).
		// summary: MIFARE Classic will only grant permissions based on the last authentication attempt.
		//          Consequently, if multiple reader.authenticate(...) commands are used,
		//          only the last one has an effect on all subsequent read/write operations.

		// reader.authenticate(blockNumber, keyType, key, obsolete = false)
		// - blockNumber - the number of any block withing the sector we want to authenticate
		// - keyType - type of key - either KEY_TYPE_A or KEY_TYPE_B
		// - key - 6 bytes - a Buffer instance, an array of bytes, or 12-chars HEX string
		// - obsolete - (default - false for PC/SC V2.07) use true for PC/SC V2.01

		// Don't forget to fill YOUR keys and types! (default ones are stated below)
		const key = 'FFFFFFFFFFFF'; // key must be a 12-chars HEX string, an instance of Buffer, or array of bytes
		const keyType = KEY_TYPE_A;

		try {

			// we want to authenticate sector 1
			// authenticating one block within the sector will authenticate all blocks within that sector
			// so in our case, we choose block 4 that is within the sector 1, all blocks (4, 5, 6, 7)
			// will be authenticated with the given key
			await reader.authenticate(4, keyType, key);

			// Note: writing might require to authenticate with a different key (based on the sector access conditions)

			pretty.info(`sector 1 successfully authenticated`, reader);

		} catch (err) {
			pretty.error(`error when authenticating block 4 within the sector 1`, reader, err);
			return;
		}


		// example reading 16 bytes (one block) assuming containing 32bit integer
		// !!! note that we don't need 16 bytes - 32bit integer takes only 4 bytes !!!
		try {

			// reader.read(blockNumber, length, blockSize = 4, packetSize = 16)
			// - blockNumber - memory block number where to start reading
			// - length - how many bytes to read
			// - blockSize - 4 for MIFARE Ultralight, 16 for MIFARE Classic
			// ! Caution! length must be divisible by blockSize
			// ! Caution! MIFARE Classic cards have sector trailers
			//   containing access bits instead of data, each last block in sector is sector trailer
			//   (e.g. block 3, 7, 11, 14)
			//   see memory structure above or https://github.com/pokusew/nfc-pcsc/issues/16#issuecomment-304989178

			const data = await reader.read(4, 16, 16); // blockSize=16 must specified for MIFARE Classic cards

			pretty.info(`data read`, reader, data);

			const payload = data.readInt32BE(0);

			pretty.info(`data converted`, reader, payload);

		} catch (err) {
			pretty.error(`error when reading data`, reader, err);
		}


		// example write 16 bytes containing 32bit integer
		// !!! note that we don't need 16 bytes - 32bit integer takes just 4 bytes !!!
		try {

			// reader.write(blockNumber, data, blockSize = 4, packetSize = 16)
			// - blockNumber - memory block number where to start writing
			// - data - what to write
			// - blockSize - 4 for MIFARE Ultralight, 16 for MIFARE Classic
			// ! Caution! data.length must be divisible by blockSize
			// ! Caution! MIFARE Classic cards have sector trailers
			//   containing access bits instead of data, each last block in sector is sector trailer
			//   (e.g. block 3, 7, 11, 14)
			//   ee memory structure above or https://github.com/pokusew/nfc-pcsc/issues/16#issuecomment-304989178

			const data = Buffer.allocUnsafe(16);
			data.fill(0);
			const randomNumber = Math.round(Math.random() * 1000);
			data.writeInt32BE(randomNumber, 0);

			await reader.write(4, data, 16); // blockSize=16 must specified for MIFARE Classic cards

			pretty.info(`data written`, reader, randomNumber, data);

		} catch (err) {
			pretty.error(`error when writing data`, reader, err);
		}


	});

	reader.on('error', err => {
		pretty.error(`an error occurred`, reader, err);
	});

	reader.on('end', () => {
		pretty.info(`device removed`, reader);
	});


});

nfc.on('error', err => {
	pretty.error(`an error occurred`, err);
});


================================================
FILE: examples/mifare-desfire.js
================================================
"use strict";

// #############
// Example: MIFARE DESFire
// - what is covered:
//   - 3DES authentication
//   - reading data files
// - known issue:
//   - [mac0S Sierra and greater] when an error occurs during the authentication process,
//     the NFC must be reinitialized or the reader reconnected
//     in order to allow subsequent successful operations (TODO: add appropriate links, fix and test)
// #############

import { NFC } from '../src/index';
import pretty from './pretty-logger';
import crypto from 'crypto';


// config
const desfire = {
	key: '00000000000000000000000000000000',
	appId: [0x00, 0x00, 0x00],
	keyId: [0x00],
	read: { // supply location of an existing data
		fileId: [0x02],
		offset: [0x00, 0x00, 0x00],
		length: [14, 0x00, 0x00],
	},
};


function decrypt(key, data, iv = Buffer.alloc(8).fill(0)) {

	const decipher = crypto.createDecipheriv('DES-EDE-CBC', key, iv);
	decipher.setAutoPadding(false);

	return Buffer.concat([decipher.update(data), decipher.final()]);

}

function encrypt(key, data, iv = Buffer.alloc(8).fill(0)) {

	const decipher = crypto.createCipheriv('DES-EDE-CBC', key, iv);
	decipher.setAutoPadding(false);

	return Buffer.concat([decipher.update(data), decipher.final()]);

}


const nfc = new NFC();

nfc.on('reader', async reader => {

	pretty.info(`device attached`, reader);

	// we have to handle MIFARE DESFire
	reader.autoProcessing = false;

	// just handy shortcut to send data
	const send = async (cmd, comment = null, responseMaxLength = 40) => {

		const b = Buffer.from(cmd);

		console.log((comment ? `[${comment}] ` : '') + `sending`, b);

		const data = await reader.transmit(b, responseMaxLength);

		console.log((comment ? `[${comment}] ` : '') + `received data`, data);

		return data;

	};

	const wrap = (cmd, dataIn) => ([0x90, cmd, 0x00, 0x00, dataIn.length, ...dataIn, 0x00]);

	reader.on('card', async card => {

		pretty.info(`card detected`, reader, card);

		const selectApplication = async () => {

			// 1: [0x5A] SelectApplication(appId) [4 bytes] - Selects one specific application for further access
			// DataIn: appId (3 bytes)
			const res = await send(wrap(0x5a, desfire.appId), 'step 1 - select app');

			// something went wrong
			if (res.slice(-1)[0] !== 0x00) {
				throw new Error('error in step 1');
			}


		};

		const authenticate = async (key) => {

			// 2: [0x0a] Authenticate(keyId) [2bytes]
			// DataIn: keyId (1 byte)
			const res1 = await send(wrap(0x0a, desfire.keyId), 'step 2 - authenticate');

			// something went wrong
			if (res1.slice(-1)[0] !== 0xaf) {
				throw new Error('error in step 2 - authenticate');
			}

			// encrypted RndB from reader
			// cut out status code (last 2 bytes)
			const ecRndB = res1.slice(0, -2);

			// decrypt it
			const RndB = decrypt(key, ecRndB);

			// rotate RndB
			const RndBp = Buffer.concat([RndB.slice(1, 8), RndB.slice(0, 1)]);

			// generate a 8 byte Random Number A
			const RndA = crypto.randomBytes(8);

			// concat RndA and RndBp
			const msg = encrypt(key, Buffer.concat([RndA, RndBp]));

			// send it back to the reader
			const res2 = await send(wrap(0xaf, msg), 'step 2 - set up RndA');

			// something went wrong
			if (res2.slice(-1)[0] !== 0x00) {
				throw new Error('error in step 2 - set up RndA');
			}

			// encrypted RndAp from reader
			// cut out status code (last 2 bytes)
			const ecRndAp = res2.slice(0, -2);

			// decrypt to get rotated value of RndA2
			const RndAp = decrypt(key, ecRndAp);

			// rotate
			const RndA2 = Buffer.concat([RndAp.slice(7, 8), RndAp.slice(0, 7)]);

			// compare decrypted RndA2 response from reader with our RndA
			// if it equals authentication process was successful
			if (!RndA.equals(RndA2)) {
				throw new Error('error in step 2 - match RndA random bytes');
			}

			return {
				RndA,
				RndB,
			};

		};

		const readData = async () => {

			// 3: [0xBD] ReadData(FileNo,Offset,Length) [8bytes] - Reads data from Standard Data Files or Backup Data Files
			const res = await send(wrap(0xbd, [desfire.read.fileId, ...desfire.read.offset, ...desfire.read.length]), 'step 3 - read', 255);

			// something went wrong
			if (res.slice(-1)[0] !== 0x00) {
				throw new Error('error in step 3 - read');
			}

			console.log('data', res);

		};


		try {

			// step 1
			await selectApplication();

			// step 2
			const key = Buffer.from(desfire.key, 'hex');
			await authenticate(key);

			// step 3
			await readData();


		} catch (err) {
			pretty.error(`error occurred during processing steps`, reader, err);
		}


	});

	reader.on('error', err => {
		pretty.error(`an error occurred`, reader, err);
	});

	reader.on('end', () => {
		pretty.info(`device removed`, reader);
	});


});

nfc.on('error', err => {
	pretty.error(`an error occurred`, err);
});


================================================
FILE: examples/mifare-ultralight-c.js
================================================
'use strict';

// #############
// Example: MIFARE Ultralight C (MF0ICU2) - 3DES authentication
// - Note: This example ONLY works with the ACR122U USB NFC reader or possibly any reader
//         that uses the NXP PN533 or similar NFC frontends.
// - Docs (descriptions of the commands and data structure):
//   - MIFARE Ultralight C - see https://www.nxp.com/docs/en/data-sheet/MF0ICU2.pdf
//   - ACR122U - see https://www.acs.com.hk/download-manual/419/API-ACR122U-2.04.pdf
//   - NXP PN533 (embedded in the ACR122U) - https://www.nxp.com/docs/en/user-guide/157830_PN533_um080103.pdf
// #############

import { NFC, TAG_ISO_14443_3, TAG_ISO_14443_4, KEY_TYPE_A, KEY_TYPE_B, TransmitError } from '../src/index';
import pretty from './pretty-logger';
import crypto from 'crypto';
import assert from 'assert/strict';

export class MifareUltralight3DESAuthenticationError extends TransmitError {

	constructor(code, message, previousError) {

		super(code, message, previousError);

		this.name = 'MifareUltralight3DESAuthenticationError';

	}

}

export class MifareUltralightReadError extends TransmitError {

	constructor(code, message, previousError) {

		super(code, message, previousError);

		this.name = 'MifareUltralightReadError';

	}

}

export class MifareUltralightWriteError extends TransmitError {

	constructor(code, message, previousError) {

		super(code, message, previousError);

		this.name = 'MifareUltralightWriteError';

	}

}

/**
 * Validates that the given data is a Buffer or a HEX string of the specified byte length
 * @param name {string} data name for debugging
 * @param data {Buffer|string} a Buffer or a HEX string
 * @param length {number} number of bytes
 * @returns {Buffer} the data converted to a Buffer
 */
const parseBytes = (name, data, length) => {

	if (!(data instanceof Buffer) && typeof data !== 'string') {
		throw new Error(`${name} must an instance of Buffer or a HEX string.`);
	}

	if (Buffer.isBuffer(data)) {

		if (data.length !== length) {
			throw new Error(`${name} must be ${length} bytes long.`);
		}

		return data;

	}

	if (typeof data === 'string') {

		if (data.length !== length * 2) {
			throw new Error(`${name} must be a ${length * 2} char HEX string.`);
		}

		return Buffer.from(data, 'hex');

	}

	throw new Error(`${name} must an instance of Buffer or a HEX string.`);

};

/**
 * Constructs a ACR122U Direct Transmit command
 *
 * Docs:
 * - ACR122U - see https://www.acs.com.hk/download-manual/419/API-ACR122U-2.04.pdf
 *   - Section 6.1 Direct Transmit
 *
 * @param payload {Buffer|ArrayBuffer|Uint8Array|number[]}
 * @returns {Buffer}
 */
const ACR122U_DirectTransmit = (payload) => {

	if (Array.isArray(payload) || ArrayBuffer.isView(payload)) {
		payload = Buffer.from(payload);
	}
	else if (!Buffer.isBuffer(payload)) {
		throw new Error(`payload must be a Buffer`);
	}

	// ACR122U Direct Transmit supports up to 255 bytes
	if (payload.length > 255) {
		throw new Error(`payload cannot be longer than 255 bytes`);
	}

	// Direct Transmit command (see ACR122U docs, Section 6.1 Direct Transmit)
	return Buffer.from([
		0xFF, // Class
		0x00, // INS
		0x00, // P1
		0x00, // P2
		payload.length, // Lc: Length of the Direct Transmit Payload
		...payload, // Data In
	]);

}

/**
 * Constructs a NXP PN533 InDataExchange command
 *
 * Docs:
 * - NXP PN533 (embedded in the ACR122U) - https://www.nxp.com/docs/en/user-guide/157830_PN533_um080103.pdf
 *   - Section 8.4.8 InDataExchange
 *
 * @param tg {number}
 * @param dataOut {Buffer|ArrayBuffer|Uint8Array|number[]}
 * @returns {Buffer}
 */
const PN533_InDataExchange = (tg, dataOut) => {

	if (!Number.isInteger(tg) || tg < 0 || tg > 0xFF) {
		throw new Error(`tg must be an integer in range [0, 255]`);
	}

	if (Array.isArray(dataOut) || ArrayBuffer.isView(dataOut)) {
		dataOut = Buffer.from(dataOut);
	}
	else if (!Buffer.isBuffer(dataOut)) {
		throw new Error(`dataOut must be a Buffer`);
	}

	if (dataOut.length > 263) {
		throw new Error(`dataOut cannot be longer than 264 bytes`);
	}

	// InDataExchange command (see NXP PN533 docs, Section 8.4.8 InDataExchange)
	return Buffer.from([
		0xD4,
		0x40,
		tg,
		...dataOut,
	]);

}

/**
 * Constructs a NXP PN533 InCommunicateThru command
 *
 * Docs:
 * - NXP PN533 (embedded in the ACR122U) - https://www.nxp.com/docs/en/user-guide/157830_PN533_um080103.pdf
 *   - Section 8.4.9 InCommunicateThru
 *
 * @param dataOut {Buffer|ArrayBuffer|Uint8Array|number[]}
 * @returns {Buffer}
 */
const PN533_InCommunicateThru = (dataOut) => {

	if (Array.isArray(dataOut) || ArrayBuffer.isView(dataOut)) {
		dataOut = Buffer.from(dataOut);
	}
	else if (!Buffer.isBuffer(dataOut)) {
		throw new Error(`dataOut must be a Buffer`);
	}

	if (dataOut.length > 264) {
		throw new Error(`dataOut cannot be longer than 264 bytes`);
	}

	// InCommunicateThru command (see NXP PN533 docs, Section 8.4.9 InCommunicateThru)
	return Buffer.from([
		0xD4,
		0x42,
		...dataOut,
	]);

}

class MifareUltralightC {

	// See Section 7.5 Memory organization
	static NUM_PAGES = 48; // first 0x00, last 0x2F
	static PAGE_SIZE = 4; // 4 bytes (48 * 4 = 192 bytes EEPROM)
	// The first 4 memory pages (0x00 - 0x03) contain the 7-byte UID and its 2 Block Check Character Bytes (BCC),
	// 1 byte internal data (INT), 2 LOCK bytes, and 4 OTP bytes (7 + 2 + 1 + 2 + 4 = 16 bytes)
	// 36 user memory pages (app data, freeform), 36 * 4 = 144 bytes
	static USER_PAGE_FIRST = 0x04;
	static USER_PAGE_LAST = 0x27;
	// page 0x28 contain 2 LOCK bytes (LOCK2, LOCK3), the other 2 bytes of the page are not usable
	// page 0x29 contain one 16-bit counter, the other 2 bytes of the page are not usable
	static AUTH0_PAGE = 0x2A;
	static AUTH1_PAGE = 0x2B;
	static AUTH_KEY_PAGE_1 = 0x2C;
	static AUTH_KEY_PAGE_2 = 0x2D;
	static AUTH_KEY_PAGE_3 = 0x2E;
	static AUTH_KEY_PAGE_4 = 0x2F;
	static MEMORY_ACCESS_ONLY_WRITE_RESTRICTED = 0x01;
	static MEMORY_ACCESS_READ_WRITE_RESTRICTED = 0x00;

	constructor(reader) {
		this.reader = reader;
	}

	/**
	 * Performs the 3DES authentication using the AUTHENTICATE command
	 *
	 * Docs:
	 * - MIFARE Ultralight C - see https://www.nxp.com/docs/en/data-sheet/MF0ICU2.pdf
	 *   - Section 7.5.5 3DES Authentication
	 *   - Section 9.5 AUTHENTICATE
	 * - ACR122U - see https://www.acs.com.hk/download-manual/419/API-ACR122U-2.04.pdf
	 *   - Section 6.1 Direct Transmit
	 * - NXP PN533 (embedded in the ACR122U) - https://www.nxp.com/docs/en/user-guide/157830_PN533_um080103.pdf
	 *   - Section 8.4.9 InCommunicateThru
	 *
	 * @param key {Buffer|string} the 16-bytes 3DES (DES-EDE-CBC) authentication key,
	 *                            exactly the same byte order (**little-endian**) as when writing
	 *                            to the auth key pages 0x2C-2F,
	 *                            the first 8 bytes (0-7) correspond to the Key 1 (K1)
	 *                            and the second 8 bytes (8-15) correspond to the Key 2 (K2),
	 *                            see {@link MifareUltralightC.swapKeyEndianness}
	 *                            for more info about the keys endianness (byte order)
	 * @throws MifareUltralight3DESAuthenticationError
	 * @returns {Promise<void>}
	 */
	async authenticate3DES(key) {

		key = parseBytes('key', key, 16);
		this.reader.logger.debug('key', key);
		const keyBE = MifareUltralightC.swapKeyEndianness(key);
		this.reader.logger.debug('keyBE', keyBE);

		// See MIFARE Ultralight C docs, Section 7.5.5 3DES Authentication, Table 8
		// Note 1:
		//   The MIFARE Ultralight C docs use the || symbol which (in that context) denotes concatenation,
		//   e.g., X || Y means concatenate(X, Y).
		//   We use this symbol with the same meaning in the following code comments.
		// Note 2:
		//   In the variable names, we use `2` instead of `'`. For example, RndB2 instead of RndB'.
		// Note 3:
		//   The numbering of the steps in the code below does not match the numbering used in Table 8.

		// 1. Get the encrypted RndB (8 bytes) from the PICC (MIFARE Ultralight C).
		//    This starts the authentication process.
		const ekRndB = await this._authenticatePart1();
		this.reader.logger.debug('ekRndB', ekRndB);

		// 2. Generate an 8-byte random number RndA.
		const RndA = crypto.randomBytes(8);
		this.reader.logger.debug('RndA', RndA);

		// 3. Compute ek(RndA || RndB').
		// First, get RndB by decrypting ekRndB.
		// The 1st encryption/decryptions uses the all zero IV.
		const iv1 = MifareUltralightC.ZERO_IV;
		const RndB = MifareUltralightC.decrypt(keyBE, ekRndB, iv1);
		this.reader.logger.debug('RndB', RndB);
		// Then, compute RndB' by rotating the original RndB left by 8 bits.
		//   RndB  = [ byte 0, byte 1, byte 2, byte 3, byte 4, byte 5, byte 6, byte 7 ]
		//   RndB' = [ byte 1, byte 2, byte 3, byte 4, byte 5, byte 6, byte 7, byte 0 ]
		const RndB2 = Buffer.concat([RndB.subarray(1, 8), RndB.subarray(0, 1)]);
		this.reader.logger.debug('RndB2', RndB2);
		// Finally, compute ek(RndA || RndB').
		// For the subsequent encryptions/decryptions, the IV must be the last ciphertext block.
		const iv2 = ekRndB;
		const ekRndARndB2 = MifareUltralightC.encrypt(keyBE, Buffer.concat([RndA, RndB2]), iv2);
		this.reader.logger.debug('ekRndARndB2', ekRndARndB2);

		// 4. Send ek(RndA || RndB') to get the encrypted RndA' from the PICC (MIFARE Ultralight C).
		//    This is the second and final authentication command.
		const ekRndA2 = await this._authenticatePart2(ekRndARndB2);
		this.reader.logger.debug('ekRndA2', ekRndA2);

		// 5. Decrypt the ekRndA' and un-rotate it to get the RndA from the PICC (MIFARE Ultralight C)
		//    for comparison with our RndA.
		// First, decrypt.
		// For the subsequent encryptions/decryptions, the IV must be the last ciphertext block.
		// ekRndARndB2 is 16 bytes, i.e., 2 ciphertext blocks, and we want the last one
		const iv3 = ekRndARndB2.subarray(8, 16);
		const RndA2 = MifareUltralightC.decrypt(keyBE, ekRndA2, iv3);
		// Then, un-rotate.
		const RndAFromUltralight = Buffer.concat([RndA2.subarray(7, 8), RndA2.subarray(0, 7)]);
		this.reader.logger.debug('RndA           (local)', RndA);
		this.reader.logger.debug('RndA (from Ultralight)', RndAFromUltralight);

		// 6. Finally, compare the decrypted RndA from the PICC (MIFARE Ultralight C) (RndAFromUltralight)
		//    with the RndA value we generated in our code in the step 2.
		//    If they are equal, the authentication process was successful.
		if (!RndA.equals(RndAFromUltralight)) {
			throw new MifareUltralight3DESAuthenticationError(
				'rnd_a_differs',
				'The RndA received from the MIFARE Ultralight C is different from the RndA that was sent. This means that the authentication process was not successful.',
			);
		}

		this.reader.logger.debug('authenticate3DES: RndA from Ultralight matches, successfully authenticated');

	}

	/**
	 * Creates a copy of the given authentication key but with swapped endianness (byte ordering) of the individual keys
	 *
	 * The authentication key is 16 bytes, where the first 8 bytes (0-7) correspond to the Key 1 (K1)
	 * and the second 8 bytes (8-15) correspond to the Key 2 (K2).
	 *
	 * This function preserve the keys order (`input key = [ K1 K2 ]`, `output key = [ K1 K2 ]`),
	 * but it changes byte ordering within the individual keys.
	 * ```
	 *    input key = [ K1B0 K1B1 K1B2 K1B3 K1B4 K1B5 K1B6 K1B7 K2B0 K2B1 K2B2 K2B3 K2B4 K2B5 K2B6 K2B7 ]
	 *   output key = [ K1B7 K1B6 K1B5 K1B4 K1B3 K1B2 K1B1 K1B0 K2B7 K2B6 K2B5 K2B4 K2B3 K2B2 K2B1 K2B0 ]
	 * ```
	 *
	 * @param key {Buffer} the two keys for DES-EDE-CBC stored as 16 bytes (2 x 8 bytes = 16 bytes),
	 *                     where the first 8 bytes (0-7) correspond to the Key 1 (K1)
	 *                     and the second 8 bytes (8-15) correspond to the Key 2 (K2).
	 * @returns {Buffer} a copy of the given key but with swapped byte ordering within the individual keys,
	 *                   BIG-endian to little-endian, little-endian to BIG-endian
	 */
	static swapKeyEndianness(key) {
		const keyCopy = Buffer.from(key);
		// since each key is 8 bytes, we can use the built-in swap64() method
		// to swap byte order of the two individual 8-byte keys
		keyCopy.swap64();
		return keyCopy;
		// alternatively, we could do it manually like this:
		// return Buffer.from([
		// 	/* Key 1 */ key[7], key[6], key[5], key[4], key[3], key[2], key[1], key[0],
		// 	/* Key 2 */ key[15], key[14], key[13], key[12], key[11], key[10], key[9], key[8],
		// ]);
	}

	static ZERO_IV = Buffer.alloc(8).fill(0);

	/**
	 * Decrypts the given data using the given key and the given IV using the `DES-EDE-CBC` algorithm
	 * (Two key triple DES EDE in CBC mode). This algorithm is used during the MIFARE Ultralight C authentication.
	 *
	 * From [MIFARE Ultralight C docs](https://www.nxp.com/docs/en/data-sheet/MF0ICU2.pdf),
	 * Section 7.5.5 3DES Authentication:
	 * > The 3DES Authentication implemented in the MF0ICU2 proves that two entities
	 * > hold the same secret and each entity can be seen as a reliable partner for onwards communication.
	 * > The applied encryption algorithm ek() is the 2 key 3DES encryption
	 * > in Cipher-Block Chaining (CBC) mode as described in ISO/IEC 10116.
	 * > The Initial Value (IV) of the first encryption of the protocol is the all zero block.
	 * > IMPORTANT! For the subsequent encryptions/decryptions, the IV consists of the last ciphertext block._
	 *
	 * @param keyBE {Buffer} the two keys for DES-EDE-CBC stored as 16 bytes (2 x 8 bytes = 16 bytes),
	 *                       where the first 8 bytes (0-7) correspond to the Key 1 (K1)
	 *                       and the second 8 bytes (8-15) correspond to the Key 2 (K2),
	 *                       the individual keys (K1 and K2) must be **BIG-endian**,
	 *                       see {@link MifareUltralightC.swapKeyEndianness}
	 *                       for more info about the keys endianness (byte order)
	 * @param data {Buffer} the data to decrypt, the length must be a multiple of 8 bytes,
	 *                      which is the block size of DES-EDE-CBC
	 * @param iv {Buffer} the IV (8 bytes) (Initial Value, also called Initialization Vector)
	 *                    The 1st encryption/decryption during the MIFARE Ultralight C authentication
	 *                    uses the all zero IV. **IMPORTANT!** For the subsequent encryptions/decryptions,
	 *                    the IV must be the last ciphertext block.
	 * @returns {Buffer} the decrypted data, the returned Buffer has the same length (size) as the input data
	 */
	static decrypt(keyBE, data, iv) {
		// DES-EDE-CBC = Two key triple DES EDE in CBC mode
		//   (https://docs.openssl.org/3.4/man1/openssl-enc/#supported-ciphers)
		//   It has block size 8 bytes and the two keys are stored in the 16-bytes-long key (128 bits).
		//   However, only 112 bits are used, see https://crypto.stackexchange.com/a/63459.
		const decipher = crypto.createDecipheriv('DES-EDE-CBC', keyBE, iv);
		decipher.setAutoPadding(false);
		return Buffer.concat([decipher.update(data), decipher.final()]);
	}

	/**
	 * Encrypts the given data using the given key and the given IV using the `DES-EDE-CBC` algorithm
	 * (Two key triple DES EDE in CBC mode). This algorithm is used during the MIFARE Ultralight C authentication.
	 *
	 * From [MIFARE Ultralight C docs](https://www.nxp.com/docs/en/data-sheet/MF0ICU2.pdf),
	 * Section 7.5.5 3DES Authentication:
	 * > The 3DES Authentication implemented in the MF0ICU2 proves that two entities
	 * > hold the same secret and each entity can be seen as a reliable partner for onwards communication.
	 * > The applied encryption algorithm ek() is the 2 key 3DES encryption
	 * > in Cipher-Block Chaining (CBC) mode as described in ISO/IEC 10116.
	 * > The Initial Value (IV) of the first encryption of the protocol is the all zero block.
	 * > IMPORTANT! For the subsequent encryptions/decryptions, the IV consists of the last ciphertext block._
	 *
	 * @param keyBE {Buffer} the two keys for DES-EDE-CBC stored as 16 bytes (2 x 8 bytes = 16 bytes),
	 *                       where the first 8 bytes (0-7) correspond to the Key 1 (K1)
	 *                       and the second 8 bytes (8-15) correspond to the Key 2 (K2),
	 *                       the individual keys (K1 and K2) must be **BIG-endian**,
	 *                       see {@link MifareUltralightC.swapKeyEndianness}
	 *                       for more info about the keys endianness (byte order)
	 * @param data {Buffer} the data to encrypt, the length must be a multiple of 8 bytes,
	 *                      which is the block size of DES-EDE-CBC
	 * @param iv {Buffer} the IV (8 bytes) (Initial Value, also called Initialization Vector)
	 *                    The 1st encryption/decryption during the MIFARE Ultralight C authentication
	 *                    uses the all zero IV. **IMPORTANT!** For the subsequent encryptions/decryptions,
	 *                    the IV must be the last ciphertext block.
	 * @returns {Buffer} the encrypted data, the returned Buffer has the same length (size) as the input data
	 */
	static encrypt(keyBE, data, iv) {
		// DES-EDE-CBC = Two key triple DES EDE in CBC mode
		//   (https://docs.openssl.org/3.4/man1/openssl-enc/#supported-ciphers)
		//   It has block size 8 bytes and the two keys are stored in the 16-bytes-long key (128 bits).
		//   However, only 112 bits are used, see https://crypto.stackexchange.com/a/63459.
		const encipher = crypto.createCipheriv('DES-EDE-CBC', keyBE, iv);
		encipher.setAutoPadding(false);
		return Buffer.concat([encipher.update(data), encipher.final()]);
	}

	/**
	 * Sends the AUTHENTICATE part 1 command and parses the response
	 *
	 * @see {authenticate3DES}
	 * @throws MifareUltralight3DESAuthenticationError
	 * @returns {Promise<Buffer>} ekRndB (8 bytes) - the encrypted RndB from the PICC (MIFARE Ultralight C)
	 */
	async _authenticatePart1() {

		const cmdAuthenticatePart1 = ACR122U_DirectTransmit(
			PN533_InCommunicateThru([
				// AUTHENTICATE part 1 command
				// see MIFARE Ultralight C docs, Section 9.5 AUTHENTICATE, Table 23
				0x1A, // Cmd: authentication part 1
				0x00, // Arg: fixed value 00h as argument
			]),
		);
		this.reader.logger.debug('cmdAuthenticatePart1', cmdAuthenticatePart1);

		/** @var {Buffer} */
		const resAuthenticatePart1 = await this.reader.transmit(
			cmdAuthenticatePart1,
			// expected response max length:
			// AUTHENTICATE part 1 response should look like the following (14 bytes)
			// D5 43 00 AF xx xx xx xx xx xx xx xx 90 00
			// bytes 0-1: D5 43 InCommunicateThru output prefix (see NXP PN533 docs, Section 8.4.9 InCommunicateThru)
			// byte 2: InCommunicateThru status, 0x00 is success (see NXP PN533 docs, Table 15. Error code list)
			// byte 3: AUTHENTICATE part 1 first response byte (0xAF) that indicates
			//         the authentication process needs a second command part
			// bytes 4-11 (8 bytes): ek(RndB) - 8-byte encrypted PICC random number RndB
			// bytes 12-13 (last 2 bytes): ACR122U success code 0x90 0x00
			14,
		);
		this.reader.logger.debug('resAuthenticatePart1', resAuthenticatePart1);

		if (resAuthenticatePart1.length !== 14) {
			throw new MifareUltralight3DESAuthenticationError(
				'unexpected_response_length',
				`Unexpected response length for cmdAuthenticatePart1. Expected 14 bytes but got ${resAuthenticatePart1.length} bytes.`,
			);
		}

		if (
			resAuthenticatePart1[0] !== 0xD5 ||
			resAuthenticatePart1[1] !== 0x43 ||
			resAuthenticatePart1[2] !== 0x00 ||
			resAuthenticatePart1[3] !== 0xAF ||
			resAuthenticatePart1[12] !== 0x90 ||
			resAuthenticatePart1[13] !== 0x00
		) {
			throw new MifareUltralight3DESAuthenticationError(
				'unexpected_response',
				`Unexpected response format for cmdAuthenticatePart1.`,
			);
		}

		// ekRndB - the encrypted RndB from the PICC (MIFARE Ultralight C)
		return resAuthenticatePart1.subarray(4, 12);

	}

	/**
	 * Sends the AUTHENTICATE part 2 command and parses the response
	 *
	 * @see {authenticate3DES}
	 * @param ekRndARndB2 {Buffer} ek(RndA || RndB'): 16-byte encrypted random numbers (RndA concatenated with RndB')
	 * @throws MifareUltralight3DESAuthenticationError
	 * @returns {Promise<Buffer>} ekRndA2 (8 bytes) - the encrypted RndA' from the PICC (MIFARE Ultralight C)
	 */
	async _authenticatePart2(ekRndARndB2) {

		const cmdAuthenticatePart2 = ACR122U_DirectTransmit(
			PN533_InCommunicateThru([
				// AUTHENTICATE part 2 command
				// see MIFARE Ultralight C docs, Section 9.5 AUTHENTICATE, Table 26
				0xAF, // Cmd: fixed first byte for the AUTHENTICATE part 2 command
				...ekRndARndB2, // ek(RndA || RndB'): 16-byte encrypted random numbers: RndA concatenated with RndB'
			]),
		);
		this.reader.logger.debug('cmdAuthenticatePart2', cmdAuthenticatePart2);

		/** @var {Buffer} */
		const resAuthenticatePart2 = await this.reader.transmit(
			cmdAuthenticatePart2,
			// expected response max length:
			// AUTHENTICATE part 1 response should look like the following (14 bytes)
			// D5 43 00 00 xx xx xx xx xx xx xx xx 90 00
			// bytes 0-1: D5 43 InCommunicateThru output prefix (see NXP PN533 docs, Section 8.4.9 InCommunicateThru)
			// byte 2: InCommunicateThru status, 0x00 is success (see NXP PN533 docs, Table 15. Error code list)
			// byte 3: AUTHENTICATE part 2 first response byte (0x00) that indicates
			//         the authentication process is finished after this command
			// bytes 4-11 (8 bytes): ek(RndA') - 8-byte encrypted, shifted PCD random number RndA'
			// bytes 12-13 (last 2 bytes): ACR122U success code 0x90 0x00
			14,
		);
		this.reader.logger.debug('resAuthenticatePart2', resAuthenticatePart2);

		if (resAuthenticatePart2.length !== 14) {
			throw new MifareUltralight3DESAuthenticationError(
				'unexpected_response_length',
				`Unexpected response length for cmdAuthenticatePart2. Expected 14 bytes but got ${resAuthenticatePart2.length} bytes.`,
			);
		}

		if (
			resAuthenticatePart2[0] !== 0xD5 ||
			resAuthenticatePart2[1] !== 0x43 ||
			resAuthenticatePart2[2] !== 0x00 ||
			resAuthenticatePart2[3] !== 0x00 ||
			resAuthenticatePart2[12] !== 0x90 ||
			resAuthenticatePart2[13] !== 0x00
		) {
			throw new MifareUltralight3DESAuthenticationError(
				'unexpected_response',
				`Unexpected response format for cmdAuthenticatePart2.`,
			);
		}

		// ekRndA2 - the encrypted RndA' from the PICC (MIFARE Ultralight C)
		return resAuthenticatePart2.subarray(4, 12);

	}

	/**
	 * Sends the READ command and parses the response
	 *
	 * Docs:
	 * - MIFARE Ultralight C - see https://www.nxp.com/docs/en/data-sheet/MF0ICU2.pdf
	 *   - Section 9.2 READ
	 *
	 * @param page {number} the start page address [0x00, 0x2B]
	 * @throws MifareUltralightReadError
	 * @returns {Promise<Buffer>} the read data (16 bytes)
	 */
	async read(page) {

		const cmdRead = ACR122U_DirectTransmit(
			PN533_InCommunicateThru([
				// READ command
				// see MIFARE Ultralight C docs, Section 9.2 READ, Table 17
				0x30, // Cmd: read four pages
				page, // Addr: start page address [0x00, 0x2B]
			]),
		);
		this.reader.logger.debug('cmdRead', cmdRead);

		/** @var {Buffer} */
		const resRead = await this.reader.transmit(
			cmdRead,
			// expected response max length:
			// READ response should look like the following (21 bytes)
			// D5 41 00 [d0] ... [d15] 90 00
			// bytes 0-1: D5 43 InCommunicateThru output prefix (see NXP PN533 docs, Section 8.4.9 InCommunicateThru)
			// byte 2: InCommunicateThru status, 0x00 is success (see NXP PN533 docs, Table 15. Error code list)
			// bytes 3-18 (16 bytes): the read data
			// bytes 19-20 (last 2 bytes): ACR122U success code 0x90 0x00
			21,
		);
		this.reader.logger.debug('resRead', resRead);

		if (resRead.length !== 21) {
			throw new MifareUltralightReadError(
				'unexpected_response_length',
				`Unexpected response length for cmdRead. Expected 21 bytes but got ${resRead.length} bytes.`,
			);
		}

		if (
			resRead[0] !== 0xD5 ||
			resRead[1] !== 0x43 ||
			resRead[2] !== 0x00 ||
			resRead[19] !== 0x90 ||
			resRead[20] !== 0x00
		) {
			throw new MifareUltralightReadError(
				'unexpected_response',
				`Unexpected response format for cmdRead.`,
			);
		}

		// the read data
		return resRead.subarray(3, 19);

	}

	/**
	 * Sends the WRITE command and parses the response
	 *
	 * Docs:
	 * - MIFARE Ultralight C - see https://www.nxp.com/docs/en/data-sheet/MF0ICU2.pdf
	 *   - Section 9.3 WRITE
	 *
	 * @param page {number} the page address [0x02, 0x2F]
	 * @param data {Buffer} the page data to write (4 bytes)
	 * @throws MifareUltralightReadError
	 * @returns {Promise<void>}
	 */
	async write(page, data) {

		const cmdWrite = ACR122U_DirectTransmit(
			// Interestingly, the InCommunicateThru command works as well, but the response
			// is always D5 43 02 90 00 (0x02 = "A CRC error has been detected by the CIU"),
			// even though that the WRITE command succeeds. Maybe it is because the WRITE command
			// does not have any data in its response?
			// Nevertheless, InDataExchange seems to work without this problem,
			// so we used it here instead of InCommunicateThru.
			PN533_InDataExchange(
				// PN533 supports only one target at the time.
				// By testing empirically, we figured that the Tg value should be always set to 1
				// (at least when the InDataExchange command is used in the standard ACR122U reader flow).
				1,
				[
					// WRITE command
					// see MIFARE Ultralight C docs, Section 9.3 WRITE, Table 19
					0xA2, // Cmd: write one page
					page, // Addr: the page address [0x02, 0x2F]
					...data, // Data: the page data to write (4 bytes)
				],
			),
		);
		this.reader.logger.debug('cmdWrite', cmdWrite);

		/** @var {Buffer} */
		const resWrite = await this.reader.transmit(
			cmdWrite,
			// expected response max length:
			// WRITE response should look like the following (5 bytes)
			// D5 41 00 90 00
			// bytes 0-1: D5 41 InDataExchange output prefix (see NXP PN533 docs, Section 8.4.8 InDataExchange)
			// byte 2: InDataExchange status, 0x00 is success (see NXP PN533 docs, Table 15. Error code list)
			// bytes 3-4 (last 2 bytes): ACR122U success code 0x90 0x00
			5,
		);
		this.reader.logger.debug('resWrite', resWrite);

		if (resWrite.length !== 5) {
			throw new MifareUltralightWriteError(
				'unexpected_response_length',
				`Unexpected response length for cmdWrite. Expected 5 bytes but got ${resWrite.length} bytes.`,
			);
		}

		if (
			resWrite[0] !== 0xD5 ||
			resWrite[1] !== 0x41 ||
			resWrite[2] !== 0x00 ||
			resWrite[3] !== 0x90 ||
			resWrite[4] !== 0x00
		) {
			throw new MifareUltralightWriteError(
				'unexpected_response',
				`Unexpected response format for cmdWrite.`,
			);
		}

	}

	/**
	 * Writes the given AUTH0 byte value
	 *
	 * Docs:
	 * - MIFARE Ultralight C - see https://www.nxp.com/docs/en/data-sheet/MF0ICU2.pdf
	 *   - Section 7.5.8 Configuration for memory access via 3DES Authentication
	 *
	 * @param value {number} The AUTH0 byte value defines the page address from which the authentication is required.
	 *                       Valid address values are from `0x03` (all pages are protected)
	 *                       to `0x30` (memory protection effectively disabled).
	 * @see writeAuth1
	 * @returns {Promise<void>}
	 */
	async writeAuth0(value) {
		if (!Number.isInteger(value) || value < 0x03 || value > 0x30) {
			throw new Error('Invalid AUTH0 value!');
		}
		await this.write(MifareUltralightC.AUTH0_PAGE, Buffer.from([
			value,
			0x00,
			0x00,
			0x00,
		]));
	}

	/**
	 * Writes the given AUTH1 byte value
	 *
	 * Docs:
	 * - MIFARE Ultralight C - see https://www.nxp.com/docs/en/data-sheet/MF0ICU2.pdf
	 *   - Section 7.5.8 Configuration for memory access via 3DES Authentication
	 *
	 * @param value {number} The AUTH1 byte value determines if only write access is restricted
	 *                       (`AUTH1 = 0bxxxxxxx1`), or if both read and write access
	 *                       are restricted (`AUTH1 = 0bxxxxxxx0`). The `x` symbol denotes ignored bits.
	 *                       The ignored are persisted, so they can be used for storing additional app-specific data.
	 * @see writeAuth0
	 * @returns {Promise<void>}
	 */
	async writeAuth1(value) {
		if (!Number.isInteger(value) || value < 0x00 || value > 0xFF) {
			throw new Error('Invalid AUTH1 value!');
		}
		await this.write(MifareUltralightC.AUTH1_PAGE, Buffer.from([
			value,
			0x00,
			0x00,
			0x00,
		]));
	}

}

// This is the default factory key of MIFARE Ultralight C
// See Section 7.5.10 Initial memory configuration, Table 13. Initial memory organization,
// https://www.nxp.com/docs/en/data-sheet/MF0ICU2.pdf.
const DEFAULT_KEY = Buffer.from('BREAKMEIFYOUCAN!', 'utf-8');

// Note that some other implementations of the authenticate3DES
// might require the authentication key with a different byte order,
// see the MifareUltralightC.swapKeyEndianness() method above for more info.
assert.deepEqual(MifareUltralightC.swapKeyEndianness(DEFAULT_KEY), Buffer.from('IEMKAERB!NACUOYF', 'utf-8'));

const ZERO_KEY = Buffer.from('00000000000000000000000000000000', 'hex');
const ONES_KEY = Buffer.from('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF', 'hex');
const DEMO_KEY = Buffer.from('AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDD', 'hex');

const nfc = new NFC(pretty); // we pass an optional logger to see internal debug logs

nfc.on('reader', async reader => {

	pretty.info(`device attached`, reader);

	const ultralightC = new MifareUltralightC(reader);

	reader.on('card', async card => {

		pretty.info('card detected', reader, card);

		try {

			// Note:
			//   Depending on your MIFARE Ultralight C configuration, authentication might not be required.
			//   In the factory state, all read/write operations are allowed without authentication.
			//   Nevertheless, we can always perform authentication.
			await ultralightC.authenticate3DES(DEFAULT_KEY);
			// await ultralightC.authenticate3DES(ZERO_KEY);
			// await ultralightC.authenticate3DES(ONES_KEY);
			// await ultralightC.authenticate3DES(DEMO_KEY);
			pretty.info('successfully authenticated');

			// # Update the authentication key

			// const key = DEFAULT_KEY;
			// // (Section 7.5.7 Programming of 3DES key to memory)
			// // // write data using the universal read/write methods (works with many standard PC/SC readers)
			// await reader.write(0x2C, key.subarray(0, 4), 4);
			// await reader.write(0x2D, key.subarray(4, 8), 4);
			// await reader.write(0x2E, key.subarray(8, 12), 4);
			// await reader.write(0x2F, key.subarray(12, 16), 4);
			// pretty.info('authentication key successfully written');
			// // // alternatively, use the WRITE command directly (only works with ACR122U NFC USB reader)
			// // await ultralightC.write(0x2C, key.subarray(0, 4));
			// // await ultralightC.write(0x2D, key.subarray(4, 8));
			// // await ultralightC.write(0x2E, key.subarray(8, 12));
			// // await ultralightC.write(0x2F, key.subarray(12, 16));
			// // pretty.info('authentication key successfully written');

			// # Protect memory from write and optionally read

			// // See Section 7.5.8 Configuration for memory access via 3DES Authentication of MF0ICU2.pdf.
			// const firstAuthProtectedPage = 0x28; // an example
			// const disableProtection = 0x30; // factory default
			// await ultralightC.writeAuth0(disableProtection);
			// // read-write protection, factory default
			// await ultralightC.writeAuth1(MifareUltralightC.MEMORY_ACCESS_READ_WRITE_RESTRICTED);
			// // only write protection
			// // await ultralightC.writeAuth1(MifareUltralightC.MEMORY_ACCESS_ONLY_WRITE_RESTRICTED);

			// Note that you can also use LOCK bytes LOCK 0-4 to turn selected pages permanently into a read-only memory.
			// See Section 7.5.2 and Section 7.5.3 of MF0ICU2.pdf.

			// // # Write data
			//
			// const text = Buffer.from('ahoy', 'utf8');
			// // write data using the universal read/write methods (works with many standard PC/SC readers)
			// await reader.write(0x20, text, 4);
			// // // alternatively, use the WRITE command directly (only works with ACR122U NFC USB reader)
			// // await ultralightC.write(0x20, text);
			//
			// // # Read data
			//
			// // read data using the universal read/write methods (works with many standard PC/SC readers)
			// const data = await reader.read(0x20, 4, 4);
			// pretty.info('data', data);
			// pretty.info('data as UTF8', data.toString('utf8'));
			// // // alternatively, use the READ command directly (only works with ACR122U NFC USB reader)
			// // const data = await ultralightC.read(0x20);
			// // pretty.info('data', data.subarray(0, 4));
			// // pretty.info('data as UTF8', data.subarray(0, 4).toString('utf8'));

		} catch (err) {
			pretty.error('error:', err);
		}

	});

	reader.on('error', err => {
		pretty.error(`an error occurred`, reader, err);
	});

	reader.on('end', () => {
		pretty.info(`device removed`, reader);
	});

});

nfc.on('error', err => {
	pretty.error(`an error occurred`, err);
});

/**
 * This is the 3DES Authentication example from Section 7.5.6 (Table 9)
 * of [MIFARE Ultralight C docs](https://www.nxp.com/docs/en/data-sheet/MF0ICU2.pdf).
 *
 * The function contains asserts (the expected values taken from the docs).
 * When the function returns without throwing an error,
 * it means our encryption/decryption methods work correctly.
 */
function numerical3DESExampleFromMF0ICU2() {

	const keyBE = parseBytes('key', '49454D4B41455242214E4143554F5946', 16);

	const PICC_RndB = parseBytes('PICC_RndB', '51E764602678DF2B', 8);
	const PICC_ekRndB = parseBytes('PICC_ekRndB', '577293FD2F34CA51', 8);

	const PCD_ekRndB = PICC_ekRndB;
	const PCD_RndB = MifareUltralightC.decrypt(keyBE, PCD_ekRndB, MifareUltralightC.ZERO_IV);
	if (!PCD_RndB.equals(PICC_RndB)) {
		throw new Error('PCD_RndB');
	}

	const PCD_RndA = parseBytes('PCD_RndA', 'A8AF3B256C75ED40', 8);
	const PCD_RndB2 = Buffer.concat([PCD_RndB.subarray(1, 8), PCD_RndB.subarray(0, 1)]);
	const PCD_RndARndB2 = Buffer.concat([PCD_RndA, PCD_RndB2]);
	const PCD_ekRndARndB2 = MifareUltralightC.encrypt(keyBE, PCD_RndARndB2, PCD_ekRndB);
	const expected_PCD_ekRndARndB2 = parseBytes('expected_PCD_ekRndARndB2', '0A638559FC7737F9F15D7862EBBE967A', 16);
	if (!PCD_ekRndARndB2.equals(expected_PCD_ekRndARndB2)) {
		throw new Error('PCD_ekRndARndB2');
	}

	const PICC_ekRndARndB2 = PCD_ekRndARndB2;
	const PICC_RndARndB2 = MifareUltralightC.decrypt(keyBE, PICC_ekRndARndB2, PICC_ekRndB);
	if (!PICC_RndARndB2.equals(PCD_RndARndB2)) {
		throw new Error('PICC_RndARndB2');
	}
	const PICC_RndA = PICC_RndARndB2.subarray(0, 8);
	if (!PICC_RndA.equals(PCD_RndA)) {
		throw new Error('PICC_RndA');
	}
	const PICC_RndA2 = Buffer.concat([PICC_RndA.subarray(1, 8), PICC_RndA.subarray(0, 1)]);
	const expected_PICC_ekRndA2 = parseBytes('expected_PICC_ekRndA2', '3B884FA07C137CE1', 8);
	const PICC_ekRndA2 = MifareUltralightC.encrypt(keyBE, PICC_RndA2, PICC_ekRndARndB2.subarray(8, 16));
	if (!PICC_ekRndA2.equals(expected_PICC_ekRndA2)) {
		throw new Error('PICC_ekRndA2');
	}

	const PCD_ekRndA2 = PICC_ekRndA2;
	const PCD_RndA2 = MifareUltralightC.decrypt(keyBE, PCD_ekRndA2, PCD_ekRndARndB2.subarray(8, 16));
	if (!PCD_RndA2.equals(PICC_RndA2)) {
		throw new Error('PCD_RndA2');
	}
	const PCD_RndA_fromPICC = Buffer.concat([PCD_RndA2.subarray(7, 8), PCD_RndA2.subarray(0, 7)]);
	if (!PCD_RndA_fromPICC.equals(PCD_RndA)) {
		throw new Error('PCD_RndA_fromPICC');
	}

}

numerical3DESExampleFromMF0ICU2();


================================================
FILE: examples/mifare-ultralight-ntag.js
================================================
'use strict';

// #############
// Example: MIFARE Ultralight EV1 and NTAG 213/215/216 – implementation of card's specific commands
// - note: for instructions on reading and writing the data,
//   please see read-write.js, which is common for all ISO/IEC 14443-3 tags
// - note: this guide applies to NTAG 213/215/216 cards as-well
//   (the commands and configuration pages structure is same or very similar,
//   the only difference is the location of the pages due to different user memory size)
// - docs (descriptions of the commands and data structure):
//   - MIFARE Ultralight EV1 – see https://www.nxp.com/docs/en/data-sheet/MF0ULX1.pdf
//   - NTAG 213/215/216 – https://www.nxp.com/docs/en/data-sheet/NTAG213_215_216.pdf
// - note: works ONLY on ACR122U reader or possibly any reader which uses NXP PN533 and similar NFC frontends
// - what is covered:
//   - password authentication – PWD_AUTH command
//   - fast read – FAST_READ command
//   - setting card configuration pages – set custom password and access conditions
// #############

import { NFC, TAG_ISO_14443_3, TAG_ISO_14443_4, KEY_TYPE_A, KEY_TYPE_B, TransmitError } from '../src/index';
import pretty from './pretty-logger';


export class MifareUltralightPasswordAuthenticationError extends TransmitError {

	constructor(code, message, previousError) {

		super(code, message, previousError);

		this.name = 'MifareUltralightPasswordAuthenticationError';

	}

}

export class MifareUltralightFastReadError extends TransmitError {

	constructor(code, message, previousError) {

		super(code, message, previousError);

		this.name = 'MifareUltralightFastReadError';

	}

}

const parseBytes = (name, data, length) => {

	if (!(data instanceof Buffer) && typeof data !== 'string') {
		throw new Error(`${name} must an instance of Buffer or a HEX string.`);
	}

	if (Buffer.isBuffer(data)) {

		if (data.length !== length) {
			throw new Error(`${name} must be ${length} bytes long.`);
		}

		return data;

	}

	if (typeof data === 'string') {

		if (data.length !== length * 2) {
			throw new Error(`${name} must be a ${length * 2} char HEX string.`);
		}

		return Buffer.from(data, 'hex');

	}

	throw new Error(`${name} must an instance of Buffer or a HEX string.`);

};

class MifareUltralight {

	constructor(reader) {
		this.reader = reader;
	}

	// PWD_AUTH
	async passwordAuthenticate(password, pack) {

		// PASSWORD (4 bytes) (stored on card in page 18)
		// PACK (2 bytes) (stored in page 19 as first two bytes)
		// PACK is the response from card in case of successful PWD_AUTH cmd

		password = parseBytes('Password', password, 4);
		pack = parseBytes('Pack', pack, 2);

		// CMD: PWD_AUTH via Direct Transmit (ACR122U) and Data Exchange (PN533)
		const cmd = Buffer.from([
			0xff, // Class
			0x00, // Direct Transmit (see ACR122U docs)
			0x00, // ...
			0x00, // ...
			0x07, // Length of Direct Transmit payload
			// Payload (7 bytes)
			0xd4, // Data Exchange Command (see PN533 docs)
			0x42, // InCommunicateThru
			0x1b, // PWD_AUTH
			...password,
		]);

		this.reader.logger.debug('pwd_auth cmd', cmd);


		const response = await this.reader.transmit(cmd, 7);

		this.reader.logger.debug('pwd_auth response', response);
		// pwd_auth response should look like the following (7 bytes)
		// d5 43 00 ab cd 90 00
		// byte 0: d5 prefix for response of Data Exchange Command (see PN533 docs)
		// byte 1: 43 prefix for response of Data Exchange Command (see PN533 docs)
		// byte 2: Data Exchange Command Status 0x00 is success (see PN533 docs, Table 15. Error code list)
		// bytes 3-4: Data Exchange Command Response – our PACK (set on card in page 19, in bytes 0-1) from card
		// bytes 5-6: ACR122U success code

		if (response.length < 5) {
			throw new MifareUltralightPasswordAuthenticationError('invalid_response_length', `Invalid response length ${response.length}. Expected minimal length was 2 bytes.`)
		}

		if (response[2] !== 0x00 || response.length < 7) {
			throw new MifareUltralightPasswordAuthenticationError('invalid_password', `Authentication failed. Might be invalid password or unsupported card.`);
		}

		if (!response.slice(3, 5).equals(pack)) {
			throw new MifareUltralightPasswordAuthenticationError('pack_mismatch', `Pack mismatch.`)
		}

		return;

	}

	// FAST_READ
	async fastRead(startPage, endPage) {

		// CMD: PWD_AUTH via Direct Transmit (ACR122U) and Data Exchange (PN533)
		const cmd = Buffer.from([
			0xff, // Class
			0x00, // Direct Transmit (see ACR122U docs)
			0x00, // ...
			0x00, // ...
			0x07, // Length of Direct Transmit payload
			// Payload (7 bytes)
			0xd4, // Data Exchange Command (see PN533 docs)
			0x42, // InCommunicateThru
			0x3a, // PWD_AUTH
			startPage,
			endPage,
		]);

		const length = 3 + ((endPage - startPage + 1) * 4) + 2;

		const response = await this.reader.transmit(cmd, length);

		if (response < length) {
			throw new MifareUltralightFastReadError('invalid_response_length', `Invalid response length ${response.length}. Expected length was ${length} bytes.`)
		}

		return response.slice(3, -2);

	}

}

const nfc = new NFC(pretty); // const nfc = new NFC(pretty); // optionally you can pass logger to see internal debug logs

nfc.on('reader', async reader => {

	pretty.info(`device attached`, reader);

	const ultralight = new MifareUltralight(reader);

	reader.on('card', async card => {

		pretty.info('card detected', reader, card);

		const password = 'FFFFFFFF'; // default password
		const pack = '0000'; // default pack

		try {

			await ultralight.passwordAuthenticate(password, pack);

			pretty.info('passwordAuthenticate: successfully authenticated');

		} catch (err) {
			pretty.error('passwordAuthenticate error:', err);
		}

		try {

			const data = await ultralight.fastRead(16, 19);

			pretty.info('fastRead data:', data);

		} catch (err) {
			pretty.error('fastRead error:', err);
			return;
		}

		// Note! UPDATE locations of configuration pages according to the version of your card!
		// (see memory layout in your card's docs)

		// try {
		//
		// 	// set custom PASSWORD (4 bytes) (stored in page 18)
		// 	await reader.write(19, password);
		//
		// 	// set custom PACK (2 bytes) (stored in page 19 as first two bytes
		// 	const packPage = await reader.read(19, 4);
		// 	packPage[0] = pack[0];
		// 	packPage[1] = pack[1];
		// 	await reader.write(19, packPage);
		//
		// 	// read current configuration
		// 	const config = await reader.read(16, 8);
		//
		// 	// Configuration page 16
		// 	console.log(config[0]);
		// 	console.log(config[1]);
		// 	console.log(config[2]);
		// 	console.log(config[3]); // AUTH0 (default: 0xff)
		//
		// 	// Configuration page 17
		// 	console.log(config[4]); // ACCESS
		// 	console.log(config[5]); // VCTID (default: 0x05)
		// 	console.log(config[6]);
		// 	console.log(config[7]);
		//
		// 	// Protect everything (start with first data page)
		// 	config[3] = 0x04;
		//
		// 	// set ACCESS bits
		// 	// bit 7: PROT One bit inside the ACCESS byte defining the memory protection
		// 	//          0b ... write access is protected by the password verification
		// 	//          1b ... read and write access is protected by the password verification
		// 	// bit 6: CFGLCK Write locking bit for the user configuration
		// 	//        - 0b ... user configuration open to write access
		// 	//        - 1b ... user configuration permanently locked against write access
		// 	// bits 5-3: reserved
		// 	// bits 2-0: AUTHLIM
		// 	// bit number-76543210
		// 	//            ||||||||
		// 	config[4] = 0b10000000;
		//
		// 	// set custom access rules
		// 	await reader.write(16, config);
		//
		// } catch (err) {
		// 	pretty.error('configuration write error:', err);
		// }

	});

	reader.on('error', err => {
		pretty.error(`an error occurred`, reader, err);
	});

	reader.on('end', () => {
		pretty.info(`device removed`, reader);
	});

});

nfc.on('error', err => {
	pretty.error(`an error occurred`, err);
});


================================================
FILE: examples/mini-logger.js
================================================
"use strict";

// mini-logger for debugging

function log() {
	console.log(...arguments);
}

const logger = {
	log: log,
	debug: log,
	info: log,
	warn: log,
	error: log,
};

export default logger;


================================================
FILE: examples/ndef.js
================================================
"use strict";

// #############
// example not finished, it in progress !!!
// Read NDEF formatted data
// #############

import { NFC, TAG_ISO_14443_3, TAG_ISO_14443_4, KEY_TYPE_A, KEY_TYPE_B } from '../src/index';
import pretty from './pretty';


================================================
FILE: examples/pretty-logger.js
================================================
"use strict";

// pretty-logger for debugging
// uses great winston logger library, visit https://github.com/winstonjs/winston

import util from 'util';
import chalk from 'chalk';
import winston from 'winston';
import { SPLAT } from 'triple-beam';


const colors = {
	exception: 'red',
	error: 'red',
	warn: 'yellow',
	info: 'green',
	verbose: 'blue',
	debug: 'blue',
	silly: 'gray',
};

winston.addColors(colors);

// we could use instanceof but to avoid import we simply check obj structure
const isReader = obj => typeof obj === 'object' && obj.reader && obj.name;

const printf = winston.format.printf(({ timestamp, level, message, [SPLAT]: splat }) => {

	let splatString = '';

	let reader = '';

	if (splat) {

		let readerObj = splat.find(isReader);

		if (readerObj) {
			reader = chalk.cyan(readerObj.name) + ' ';
			splat = splat.filter(obj => !isReader(obj));
		}

		if (splat.length > 1) {
			splatString = ' ' + util.inspect(splat, { colors: true });
		}
		else if (splat.length > 0) {
			splatString = ' ' + util.inspect(splat[0], { colors: true });
		}

	}

	// see https://stackoverflow.com/questions/10729276/how-can-i-get-the-full-object-in-node-jss-console-log-rather-than-object
	return `${timestamp ? timestamp + ' – ' : ''}${reader}${level}: ${message}${splatString}`;

});

const FORMAT = winston.format.combine(
	winston.format.timestamp({
		format: () => chalk.gray(new Date().toLocaleTimeString()),
	}),
	winston.format.colorize(),
	printf,
);

const logger = winston.createLogger({
	transports: [
		new (winston.transports.Console)({
			level: 'silly',
			format: FORMAT,
		}),
	],
	exitOnError: true,

});


export default logger;


================================================
FILE: examples/read-write.js
================================================
"use strict";

// #############
// Example: Reading and writing data
// - should work well with any compatible PC/SC card reader
// - tested with MIFARE Ultralight cards but should work with many others (e.g. NTAG)
// - what is covered:
//   - example reading and writing data on from/to card
// - NOTE! for reading and writing data from/to MIFARE Classic please see examples/mifare-classic.js which explains MIFARE Classic specifics
// #############

import { NFC, TAG_ISO_14443_3, TAG_ISO_14443_4, KEY_TYPE_A, KEY_TYPE_B } from '../src/index';
import pretty from './pretty-logger';


const nfc = new NFC(); // const nfc = new NFC(pretty); // optionally you can pass logger to see internal debug logs

nfc.on('reader', async reader => {

	pretty.info(`device attached`, reader);

	// enable when you want to auto-process ISO 14443-4 tags (standard=TAG_ISO_14443_4)
	// when an ISO 14443-4 is detected, SELECT FILE command with the AID is issued
	// the response is available as card.data in the card event
	// you can set reader.aid to:
	// 1. a HEX string (which will be parsed automatically to Buffer)
	reader.aid = 'F222222222';
	// 2. an instance of Buffer containing the AID bytes
	// reader.aid = Buffer.from('F222222222', 'hex');
	// 3. a function which must return an instance of a Buffer when invoked with card object (containing standard and atr)
	//    the function may generate AIDs dynamically based on the detected card
	// reader.aid = ({ standard, atr }) => {
	//
	// 	return Buffer.from('F222222222', 'hex');
	//
	// };

	reader.on('card', async card => {

		pretty.info(`card detected`, reader, card);

		// example reading 4 bytes assuming containing 16bit integer
		// !!! note that we don't need 4 bytes - 16bit integer takes just 2 bytes !!!
		try {

			// reader.read(blockNumber, length, blockSize = 4, packetSize = 16)
			// - blockNumber - memory block number where to start reading
			// - length - how many bytes to read
			// - blockSize - 4 for MIFARE Ultralight, 16 for MIFARE Classic
			// ! Caution! length must be divisible by blockSize (we have to read the whole block(s))

			const data = await reader.read(4, 4);

			pretty.info(`data read`, reader, data);

			const payload = data.readInt16BE(0);

			pretty.info(`data converted`, reader, payload);

		} catch (err) {
			pretty.error(`error when reading data`, reader, err);
		}


		// example write 4 bytes containing 16bit integer
		// !!! note that we don't need 16 bytes - 16bit integer takes just 2 bytes !!!
		try {

			// reader.write(blockNumber, data, blockSize = 4, packetSize = 16)
			// - blockNumber - memory block number where to start writing
			// - data - what to write
			// - blockSize - 4 for MIFARE Ultralight, 16 for MIFARE Classic
			// ! Caution! data.length must be divisible by blockSize (we have to write the whole block(s))

			const data = Buffer.allocUnsafe(4).fill(0);
			const randomNumber = Math.round(Math.random() * 1000);
			data.writeInt16BE(randomNumber, 0);

			await reader.write(4, data);

			pretty.info(`data written`, reader, randomNumber, data);

		} catch (err) {
			pretty.error(`error when writing data`, reader, err);
		}


	});

	reader.on('error', err => {
		pretty.error(`an error occurred`, reader, err);
	});

	reader.on('end', () => {
		pretty.info(`device removed`, reader);
	});


});

nfc.on('error', err => {
	pretty.error(`an error occurred`, err);
});


================================================
FILE: examples/uid-logger.js
================================================
"use strict";

// #############
// Logs cards' uid
// #############

import { NFC } from '../src/index';


const nfc = new NFC();

nfc.on('reader', reader => {

	console.log(reader.name + ' reader attached, waiting for cards ...');

	reader.on('card', card => {
		console.log(card.uid);
	});

	reader.on('error', err => {
		console.error('reader error', err);
	});

	reader.on('end', () => {
		console.log(reader.name + ' reader disconnected.');
	});


});

nfc.on('error', err => {
	console.error(err);
});


================================================
FILE: examples/utils.js
================================================
"use strict";


// TODO: cover with tests

export const isDefined = value => value !== null && value !== undefined;

export const crop = (string, length, align = 1) => {

	if (string.length <= length) {
		return string;
	}

	if (align === 1 || align === 2) {
		return string.slice(length - string.length);
	}

	if (align === 3) {
		return string.slice(0, -length - string.length);
	}

};

/**
 * Prefixes/suffixes the given string with the given prefix (defaults to '0') to match the given length
 * e.g. '789', length 8 => '00000789'
 * e.g. 'hi', length 5 => '000hi'
 * @param string String
 * @param length Integer desired length
 * @param prefix String string to use as prefix/suffix/padding
 * @param align 1 left / 2 center / 3 right
 * @param finalize function to finalize the padded string
 *                 defaults to crop if desired length is exceeded
 *                 – e.g. when using 2-char or more prefix or center align
 * @return String
 */
export const paddy = (string, length, prefix = '0', align = 1, finalize = crop) => {

	if (string.length >= length) {
		return string;
	}

	while (string.length < length) {
		string = (align === 3 || align === 2 ? prefix : '') + string + (align === 1 || align === 2 ? prefix : '');
	}

	return finalize(string, length, align);

};

export const column = (value, size, align = 1, paddingLeft = 0, paddingRight = 0) => {

	const v = value.toString();
	const s = (isDefined(size) ? size : v.length);

	return paddy('', paddingLeft, ' ') + paddy(v, s, ' ', align) + paddy('', paddingRight, ' ');

};


================================================
FILE: examples/without-auto.js
================================================
"use strict";

// #############
// Alternative usage
// - see "Alternative usage" section in README for an explanation
// #############

import { NFC } from '../src/index';


const nfc = new NFC(); // optionally you can pass logger

nfc.on('reader', reader => {

	// disable auto processing
	reader.autoProcessing = false;

	console.log(`${reader.reader.name}  device attached`);

	reader.on('card', card => {

		// card is object containing following data
		// String standard: TAG_ISO_14443_3 (standard nfc tags like MIFARE) or TAG_ISO_14443_4 (Android HCE and others)
		// String type: same as standard
		// Buffer atr

		console.log(`${reader.reader.name}  card inserted`, card);

		// you can use reader.transmit to send commands and retrieve data
		// see https://github.com/pokusew/nfc-pcsc/blob/master/src/Reader.js#L288

	});

	reader.on('card.off', card => {
		console.log(`${reader.reader.name}  card removed`, card);
	});

	reader.on('error', err => {
		console.log(`${reader.reader.name}  an error occurred`, err);
	});

	reader.on('end', () => {
		console.log(`${reader.reader.name}  device removed`);
	});

});

nfc.on('error', err => {
	console.log('an error occurred', err);
});


================================================
FILE: package.json
================================================
{
  "name": "nfc-pcsc",
  "version": "0.8.1",
  "description": "Easy reading and writing NFC tags and cards",
  "keywords": [
    "arc122",
    "card",
    "desfire",
    "mifare",
    "ndef",
    "nfc",
    "pcsc",
    "pcsclite",
    "tag",
    "ultralight"
  ],
  "homepage": "https://github.com/pokusew/nfc-pcsc#readme",
  "bugs": {
    "url": "https://github.com/pokusew/nfc-pcsc/issues"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/pokusew/nfc-pcsc.git"
  },
  "license": "MIT",
  "author": {
    "name": "Martin Endler",
    "url": "https://github.com/pokusew"
  },
  "contributors": [
    {
      "name": "foxxyz",
      "url": "https://github.com/foxxyz"
    }
  ],
  "main": "dist/index.js",
  "scripts": {
    "build": "babel src --out-dir dist",
    "example": "node -r @babel/register examples/read-write.js",
    "example-basic": "node -r @babel/register examples/basic.js",
    "example-from-readme-3": "node -r @babel/register examples/from-readme-3.js",
    "example-led": "node -r @babel/register examples/led.js",
    "example-mifare-classic": "node -r @babel/register examples/mifare-classic.js",
    "example-mifare-desfire": "node -r @babel/register examples/mifare-desfire.js",
    "example-mifare-ultralight-c": "node -r @babel/register examples/mifare-ultralight-c.js",
    "example-mifare-ultralight-ntag": "node -r @babel/register examples/mifare-ultralight-ntag.js",
    "example-ndef": "node -r @babel/register examples/ndef.js",
    "example-uid-logger": "node -r @babel/register examples/uid-logger.js",
    "example-without-auto": "node -r @babel/register examples/without-auto.js",
    "prepack": "yarn build && yarn test",
    "test": "cross-env NODE_ENV=test ava test/tests.js --verbose"
  },
  "dependencies": {
    "@pokusew/pcsclite": "^0.6.0"
  },
  "devDependencies": {
    "@babel/cli": "^7.26.4",
    "@babel/core": "^7.26.0",
    "@babel/plugin-syntax-object-rest-spread": "^7.8.3",
    "@babel/plugin-transform-class-properties": "^7.25.9",
    "@babel/plugin-transform-modules-commonjs": "^7.26.3",
    "@babel/register": "^7.25.9",
    "ava": "^6.2.0",
    "chalk": "^4.0.0",
    "cross-env": "^7.0.3",
    "mock-require": "^3.0.3",
    "triple-beam": "^1.4.1",
    "winston": "^3.17.0"
  }
}


================================================
FILE: src/ACR122Reader.js
================================================
"use strict";

import Reader from './Reader';
import {
	ConnectError,
	DisconnectError,
	TransmitError,
	ControlError,
	AuthenticationError,
	LoadAuthenticationKeyError,
	ReadError,
	WriteError,
	GetUIDError,
	CARD_NOT_CONNECTED,
	OPERATION_FAILED,
	UNKNOWN_ERROR,
	FAILURE,
} from './errors';


class ACR122Reader extends Reader {

	async inAutoPoll() {

		const payload = [
			0xD4,
			0x60,
			0xFF, // PollNr (0xFF = Endless polling)
			0x01, // Period (0x01 – 0x0F) indicates the polling period in units of 150 ms
			0x00, // Type 1 0x00 = Generic passive 106 kbps (ISO/IEC14443-4A, Mifare and DEP)
		];

		// CMD: Direct Transmit (to inner PN532 chip InAutoPoll CMD)
		const packet = Buffer.from([
			0xff, // Class
			0x00, // INS
			0x00, // P1
			0x00, // P2
			payload.length, // Lc: Number of Bytes to send (Maximum 255 bytes)
			...payload,
		]);

		console.log(packet);

		let response = null;

		try {

			response = await this.control(packet, 2);

			this.logger.debug('response received', response);

			// Red OFF Green OFF  0x00
			// Red ON  Green OFF  0x01
			// Red OFF Green ON   0x02
			// Red ON  Green ON   0x03

			console.log(response.slice(1));


		} catch (err) {

			throw err;

		}

		// const statusCode = response.readUInt16BE(0);
		//
		// if (statusCode !== 0x9000) {
		// 	//throw new LoadAuthenticationKeyError(OPERATION_FAILED, `Load authentication key operation failed: Status code: ${statusCode}`);
		// }

	}

	async led(led, blinking) {

		// P2: LED State Control (1 byte = 8 bits)
		// format:
		/*
		 +-----+----------------------------------+-------------------------------------+
		 | Bit |               Item               |             Description             |
		 +-----+----------------------------------+-------------------------------------+
		 |   0 | Final Red LED State              | 1 = On; 0 = Off                     |
		 |   1 | Final Green LED State            | 1 = On; 0 = Off                     |
		 |   2 | Red LED State Mask               | 1 = Update the State; 0 = No change |
		 |   3 | Green LED State Mask             | 1 = Update the State; 0 = No change |
		 |   4 | Initial Red LED Blinking State   | 1 = On; 0 = Off                     |
		 |   5 | Initial Green LED Blinking State | 1 = On; 0 = Off                     |
		 |   6 | Red LED Blinking Mask            | 1 = Blink; 0 = Not Blink            |
		 |   7 | Green LED Blinking Mask          | 1 = Blink; 0 = Not Blink            |
		 +-----+----------------------------------+-------------------------------------+
		 */

		//const led = 0b00001111;
		//const led = 0x50;

		// Data In: Blinking Duration Control (4 bytes)
		// Byte 0: T1 Duration Initial Blinking State (Unit = 100 ms)
		// Byte 1: T2 Duration Toggle Blinking State (Unit = 100 ms)
		// Byte 2: Number of repetition
		// Byte 3: Link to Buzzer
		// - 00: The buzzer will not turn on
		// - 01: The buzzer will turn on during the T1 Duration
		// - 02: The buzzer will turn on during the T2 Duration
		// - 03: The buzzer will turn on during the T1 and T2 Duration

		// const blinking = [
		// 	0x00,
		// 	0x00,
		// 	0x00,
		// 	0x00
		// ];


		// CMD: Bi-Color LED and Buzzer Control
		const packet = Buffer.from([
			0xff, // Class
			0x00, // INS
			0x40, // P1
			led, // P2: LED State Control
			0x04, // Lc
			...blinking, // Data In: Blinking Duration Control (4 bytes)
		]);

		console.log(packet);

		let response = null;

		try {

			response = await this.control(packet, 2);

			this.logger.debug('response received', response);

			// Red OFF Green OFF  0x00
			// Red ON  Green OFF  0x01
			// Red OFF Green ON   0x02
			// Red ON  Green ON   0x03

			console.log(response.slice(1));


		} catch (err) {

			throw err;

		}

		// const statusCode = response.readUInt16BE(0);
		//
		// if (statusCode !== 0x9000) {
		// 	//throw new LoadAuthenticationKeyError(OPERATION_FAILED, `Load authentication key operation failed: Status code: ${statusCode}`);
		// }

	}

	async setBuzzerOutput(enabled = true) {


		// CMD: Set Buzzer Output Enable for Card Detection
		const packet = Buffer.from([
			0xff, // Class
			0x00, // INS
			0x52, // P1
			enabled ? 0xff : 0x00, // P2: PollBuzzStatus
			0x00, // Le
		]);

		console.log(packet);

		let response = null;

		try {

			response = await this.control(packet, 2);

			this.logger.debug('response received', response);


		} catch (err) {

			throw err;

		}

		const statusCode = response.readUInt16BE(0);

		if (statusCode !== 0x9000) {
			//throw new LoadAuthenticationKeyError(OPERATION_FAILED, `Load authentication key operation failed: Status code: ${statusCode}`);
		}

	}

	async setPICC(picc) {

		// just enable Auto ATS Generation
		// const picc = 0b01000000;

		// CMD: Set PICC Operating Parameter
		const packet = Buffer.from([
			0xff, // Class
			0x00, // INS
			0x51, // P1
			picc, // P2: New PICC Operating Parameter
			0x00, // Le
		]);

		console.log(packet);

		let response = null;

		try {

			response = await this.control(packet, 1);

			this.logger.debug('response received', response);


		} catch (err) {

			throw err;

		}

		// const statusCode = response.readUInt16BE(0);
		//
		// if (statusCode !== 0x9000) {
		// 	//throw new LoadAuthenticationKeyError(OPERATION_FAILED, `Load authentication key operation failed: Status code: ${statusCode}`);
		// }

	}

}

export default ACR122Reader;


================================================
FILE: src/NFC.js
================================================
"use strict";

import pcsclite from '@pokusew/pcsclite';
import EventEmitter from 'events';
import Reader from './Reader';
import ACR122Reader from './ACR122Reader';


class NFC extends EventEmitter {

	pcsc = null;
	logger = null;

	constructor(logger) {
		super();

		this.pcsc = pcsclite();

		if (logger) {
			this.logger = logger;
		}
		else {
			this.logger = {
				log: function () {
				},
				debug: function () {
				},
				info: function () {
				},
				warn: function () {
				},
				error: function () {
				},
			};
		}

		this.pcsc.on('reader', (reader) => {

			this.logger.debug('new reader detected', reader.name);

			// create special object for ARC122U reader with commands specific to this reader
			if (

				// 'acr122' matches ARC122U
				reader.name.toLowerCase().indexOf('acr122') !== -1

				// 'acr125' matches ACR1252U reader because ACR1252U has some common commands with ARC122U
				//   ACR1252U product page: https://www.acs.com.hk/en/products/342/acr1252u-usb-nfc-reader-iii-nfc-forum-certified-reader/
				//   TODO: in the future, this should be refactored:
				//         see discussion in PR#111 https://github.com/pokusew/nfc-pcsc/pull/111
				|| reader.name.toLowerCase().indexOf('acr125') !== -1

			) {

				const device = new ACR122Reader(reader, this.logger);

				this.emit('reader', device);

				return;

			}

			const device = new Reader(reader, this.logger);

			this.emit('reader', device);

		});

		this.pcsc.on('error', (err) => {

			this.logger.error('PCSC error', err.message);

			this.emit('error', err);

		});

	}

	get readers() {
		return this.pcsc.readers;
	}

	close() {
		this.pcsc.close();
	}

}

export default NFC;


================================================
FILE: src/Reader.js
================================================
"use strict";

import EventEmitter from 'events';
import {
	ConnectError,
	DisconnectError,
	TransmitError,
	ControlError,
	AuthenticationError,
	LoadAuthenticationKeyError,
	ReadError,
	WriteError,
	GetUIDError,
	CARD_NOT_CONNECTED,
	OPERATION_FAILED,
	UNKNOWN_ERROR,
	FAILURE,
} from './errors';


export const TAG_ISO_14443_3 = 'TAG_ISO_14443_3'; // ISO/IEC 14443-3 tags
export const TAG_ISO_14443_4 = 'TAG_ISO_14443_4'; // ISO/IEC 14443-4 tags

export const KEY_TYPE_A = 0x60;
export const KEY_TYPE_B = 0x61;

export const CONNECT_MODE_DIRECT = 'CONNECT_MODE_DIRECT';
export const CONNECT_MODE_CARD = 'CONNECT_MODE_CARD';


class Reader extends EventEmitter {

	reader = null;
	logger = null;

	connection = null;
	card = null;

	autoProcessing = true;
	_aid = null;

	keyStorage = {
		'0': null,
		'1': null,
	};

	pendingLoadAuthenticationKey = {};

	/**
	 * Reverses a copy of a given buffer
	 * Does NOT mutate the given buffer, returns a reversed COPY
	 * For mutating reverse use native .reverse() method on a buffer instance
	 * @param src {Buffer} a Buffer instance
	 * @returns {Buffer}
	 */
	static reverseBuffer(src) {

		const buffer = Buffer.allocUnsafe(src.length);

		for (let i = 0, j = src.length - 1; i <= j; ++i, --j) {
			buffer[i] = src[j];
			buffer[j] = src[i];
		}

		return buffer;

	}

	static selectStandardByAtr(atr) {

		// TODO: better detecting card types
		if (atr[5] && atr[5] === 0x4f) {
			return TAG_ISO_14443_3;
		}
		else {
			return TAG_ISO_14443_4;
		}

	}

	get aid() {
		return this._aid;
	}

	set aid(value) {

		if (typeof value === 'function' || Buffer.isBuffer(value)) {
			this._aid = value;
			return;
		}

		if (typeof value !== 'string') {
			throw new Error(`AID must be a HEX string or an instance of Buffer or a function.`);
		}

		this._aid = Buffer.from(value, 'hex');

	}

	get name() {
		return this.reader.name;
	}

	constructor(reader, logger) {

		super();

		this.reader = reader;

		if (logger) {
			this.logger = logger;
		}
		else {
			this.logger = {
				log: function () {
				},
				debug: function () {
				},
				info: function () {
				},
				warn: function () {
				},
				error: function () {
				},
			};
		}

		this.reader.on('error', (err) => {

			this.logger.error(err);

			this.emit('error', err);

		});

		this.reader.on('status', async status => {

			this.logger.debug('status', status);

			// check what has changed
			const changes = this.reader.state ^ status.state;

			this.logger.debug('changes', changes);

			if (changes) {

				if ((changes & this.reader.SCARD_STATE_EMPTY) && (status.state & this.reader.SCARD_STATE_EMPTY)) {

					this.logger.debug('card removed');

					if (this.card) {
						this.emit('card.off', { ...this.card });
					}

					try {

						this.card = null;
						if (this.connection) {
							await this.disconnect();
						}

					} catch (err) {

						this.emit(err);

					}

				}
				else if ((changes & this.reader.SCARD_STATE_PRESENT) && (status.state & this.reader.SCARD_STATE_PRESENT)) {

					const atr = status.atr;

					this.logger.debug('card inserted', atr);

					this.card = {};

					if (atr) {
						this.card.atr = atr;
						this.card.standard = Reader.selectStandardByAtr(atr);
						this.card.type = this.card.standard;
					}

					try {

						await this.connect();

						if (!this.autoProcessing) {
							this.emit('card', { ...this.card });
							return;
						}

						this.handleTag();

					} catch (err) {

						this.emit(err);

					}


				}
			}
		});

		this.reader.on('end', () => {

			this.logger.debug('reader removed');

			this.emit('end');

		});

	}

	connect(mode = CONNECT_MODE_CARD) {

		const modes = {
			[CONNECT_MODE_DIRECT]: this.reader.SCARD_SHARE_DIRECT,
			[CONNECT_MODE_CARD]: this.reader.SCARD_SHARE_SHARED,
		};

		if (!modes[mode]) {
			throw new ConnectError('invalid_mode', 'Invalid mode')
		}

		this.logger.debug('trying to connect', mode, modes[mode]);

		return new Promise((resolve, reject) => {

			// connect card
			this.reader.connect({
				share_mode: modes[mode],
				//protocol: this.reader.SCARD_PROTOCOL_UNDEFINED
			}, (err, protocol) => {

				if (err) {
					const error = new ConnectError(FAILURE, 'An error occurred while connecting.', err);
					this.logger.error(error);
					return reject(error);
				}

				this.connection = {
					type: modes[mode],
					protocol: protocol,
				};

				this.logger.debug('connected', this.connection);

				return resolve(this.connection);

			});

		});

	}

	disconnect() {

		if (!this.connection) {
			throw new DisconnectError('not_connected', 'Reader in not connected. No need for disconnecting.')
		}

		this.logger.debug('trying to disconnect', this.connection);

		return new Promise((resolve, reject) => {

			// disconnect removed
			this.reader.disconnect(this.reader.SCARD_LEAVE_CARD, (err) => {

				if (err) {
					const error = new DisconnectError(FAILURE, 'An error occurred while disconnecting.', err);
					this.logger.error(error);
					return reject(error);
				}

				this.connection = null;

				this.logger.debug('disconnected');

				return resolve(true);

			});

		});

	}

	transmit(data, responseMaxLength) {

		if (!this.card || !this.connection) {
			throw new TransmitError(CARD_NOT_CONNECTED, 'No card or connection available.');
		}

		return new Promise((resolve, reject) => {

			this.logger.debug('transmitting', data, responseMaxLength);

			this.reader.transmit(data, responseMaxLength, this.connection.protocol, (err, response) => {

				if (err) {
					const error = new TransmitError(FAILURE, 'An error occurred while transmitting.', err);
					return reject(error);
				}

				this.logger.debug('transmit response received', response, response && response.length);

				return resolve(response);

			});

		});

	}

	control(data, responseMaxLength) {

		if (!this.connection) {
			throw new ControlError('not_connected', 'No connection available.');
		}

		return new Promise((resolve, reject) => {

			this.logger.debug('transmitting control', data, responseMaxLength);

			this.reader.control(data, this.reader.IOCTL_CCID_ESCAPE, responseMaxLength, (err, response) => {

				if (err) {
					const error = new ControlError(FAILURE, 'An error occurred while transmitting control.', err);
					return reject(error);
				}

				this.logger.debug('control response received', response, response && response.length);

				return resolve(response);

			});

		});

	}

	async loadAuthenticationKey(keyNumber, key) {

		if (!(keyNumber === 0 || keyNumber === 1)) {
			throw new LoadAuthenticationKeyError('invalid_key_number');
		}

		if (!Buffer.isBuffer(key) && !Array.isArray(key)) {

			if (typeof key !== 'string') {
				throw new LoadAuthenticationKeyError(
					'invalid_key',
					'Key must an instance of Buffer or an array of bytes or a string.',
				);
			}

			key = Buffer.from(key, 'hex');

		}

		if (key.length !== 6) {
			throw new LoadAuthenticationKeyError('invalid_key', 'Key length must be 6 bytes.');
		}

		// CMD: Load Authentication Keys
		const packet = Buffer.from([
			0xff, // Class
			0x82, // INS
			0x00, // P1: Key Structure (0x00 = Key is loaded into the reader volatile memory.)
			keyNumber, // P2: Key Number (00h ~ 01h = Key Location. The keys will disappear once the reader is disconnected from the PC)
			key.length, // Lc: Length of the key (6)
			...key, // Data In: Key (6 bytes)
		]);

		let response = null;

		try {

			response = await this.transmit(packet, 2);


		} catch (err) {

			throw new LoadAuthenticationKeyError(null, null, err);

		}

		const statusCode = response.readUInt16BE(0);

		if (statusCode !== 0x9000) {
			throw new LoadAuthenticationKeyError(OPERATION_FAILED, `Load authentication key operation failed: Status code: ${statusCode}`);
		}

		this.keyStorage[keyNumber] = key;

		return keyNumber;

	}

	// for PC/SC V2.01 use obsolete = true
	// for PC/SC V2.07 use obsolete = false [default]
	async authenticate(blockNumber, keyType, key, obsolete = false) {

		let keyNumber = Object.keys(this.keyStorage).find(n => this.keyStorage[n] === key);

		// key is not in the storage
		if (!keyNumber) {

			// If there isn't already an authentication process happening for this key, start it
			if (!this.pendingLoadAuthenticationKey[key]) {

				// set key number to first
				keyNumber = Object.keys(this.keyStorage)[0];

				// if this number is not free
				if (this.keyStorage[keyNumber] !== null) {
					// try to find any free number
					const freeNumber = Object.keys(this.keyStorage).find(n => this.keyStorage[n] === null);
					// if we find, we use it, otherwise the first will be used and rewritten
					if (freeNumber) {
						keyNumber = freeNumber;
					}
				}

				// Store the authentication promise in case other blocks are in process of authentication
				this.pendingLoadAuthenticationKey[key] = this.loadAuthenticationKey(parseInt(keyNumber), key);

			}

			try {
				keyNumber = await this.pendingLoadAuthenticationKey[key];
			} catch (err) {
				throw new AuthenticationError('unable_to_load_key', 'Could not load authentication key into reader.', err);
			} finally {
				// remove the loadAuthenticationKey Promise from pendingLoadAuthenticationKey
				// as it is already resolved or rejected at this point
				delete this.pendingLoadAuthenticationKey[key];
			}

		}

		const packet = !obsolete ? (
			// CMD: Authentication
			Buffer.from([
				0xff, // Class
				0x86, // INS
				0x00, // P1
				0x00, // P2
				0x05, // Lc
				// Data In: Authenticate Data Bytes (5 bytes)
				0x01, // Byte 1: Version
				0x00, // Byte 2
				blockNumber, // Byte 3: Block Number
				keyType, // Byte 4: Key Type
				keyNumber, // Byte 5: Key Number
			])
		) : (
			// CMD: Authentication (obsolete)
			Buffer.from([
				0xff, // Class
				0x88, // INS
				0x00, // P1
				blockNumber, // P2: Block Number
				keyType, // P3: Key Type
				keyNumber, // Data In: Key Number
			])
		);

		let response = null;

		try {

			response = await this.transmit(packet, 2);

		} catch (err) {

			throw new AuthenticationError(null, null, err);

		}

		const statusCode = response.readUInt16BE(0);

		if (statusCode !== 0x9000) {
			this.logger.error('[authentication operation failed][request packet]', packet);
			throw new AuthenticationError(OPERATION_FAILED, `Authentication operation failed: Status code: 0x${statusCode.toString(16)}`);
		}

		return true;

	}

	async read(blockNumber, length, blockSize = 4, packetSize = 16, readClass = 0xff) {

		if (!this.card) {
			throw new ReadError(CARD_NOT_CONNECTED);
		}

		this.logger.debug('reading data from card', this.card);

		if (length > packetSize) {

			const p = Math.ceil(length / packetSize);

			const commands = [];

			for (let i = 0; i < p; i++) {

				const block = blockNumber + ((i * packetSize) / blockSize);

				const size = ((i + 1) * packetSize) < length ? packetSize : length - ((i) * packetSize);

				// console.log(i, block, size);

				commands.push(this.read(block, size, blockSize, packetSize, readClass));

			}

			return Promise.all(commands)
				.then(values => {
					// console.log(values);
					return Buffer.concat(values, length);
				});

		}

		// APDU CMD: Read Binary Blocks
		const packet = Buffer.from([
			readClass, // Class
			0xb0, // Ins
			(blockNumber >> 8) & 0xFF, // P1
			blockNumber & 0xFF, // P2: Block Number
			length,  // Le: Number of Bytes to Read (Maximum 16 bytes)
		]);

		let response = null;

		try {

			response = await this.transmit(packet, length + 2);

		} catch (err) {

			throw new ReadError(null, null, err);

		}

		if (response.length < 2) {
			throw new ReadError(OPERATION_FAILED, `Read operation failed: Invalid response length ${response.length}. Expected minimal length is 2 bytes.`);
		}

		const statusCode = response.slice(-2).readUInt16BE(0);

		if (statusCode !== 0x9000) {
			throw new ReadError(OPERATION_FAILED, `Read operation failed: Status code: 0x${statusCode.toString(16)}`);
		}

		const data = response.slice(0, -2);

		this.logger.debug('data', data);

		return data;

	}

	async write(blockNumber, data, blockSize = 4) {

		if (!this.card) {
			throw new WriteError(CARD_NOT_CONNECTED);
		}

		this.logger.debug('writing data to card', this.card);

		if (data.length < blockSize || data.length % blockSize !== 0) {
			throw new WriteError('invalid_data_length', 'Invalid data length. You can only update the entire data block(s).');
		}

		if (data.length > blockSize) {

			const p = data.length / blockSize;

			const commands = [];

			for (let i = 0; i < p; i++) {

				const block = blockNumber + i;

				const start = i * blockSize;
				const end = (i + 1) * blockSize;

				const part = data.slice(start, end);

				// console.log(i, block, start, end, part);

				commands.push(this.write(block, part, blockSize));

			}

			return Promise.all(commands)
				.then(values => {
					// console.log(values);
					return values;
				});

		}

		// APDU CMD: Update Binary Block
		const packetHeader = Buffer.from([
			0xff, // Class
			0xd6, // Ins
			0x00, // P1
			blockNumber, // P2: Block Number
			blockSize, // Le: Number of Bytes to Update
		]);

		const packet = Buffer.concat([packetHeader, data]);

		let response = null;

		try {

			response = await this.transmit(packet, 2);

		} catch (err) {

			throw new WriteError(null, null, err);

		}

		if (response.length < 2) {
			throw new WriteError(OPERATION_FAILED, `Write operation failed: Invalid response length ${response.length}. Expected minimal length is 2 bytes.`);
		}

		const statusCode = response.slice(-2).readUInt16BE(0);

		if (statusCode !== 0x9000) {
			throw new WriteError(OPERATION_FAILED, `Write operation failed: Status code: 0x${statusCode.toString(16)}`);
		}

		return true;

	}

	handleTag() {

		if (!this.card) {
			return false;
		}

		this.logger.debug('handling tag', this.card);

		switch (this.card.standard) {

			case TAG_ISO_14443_3:
				return this.handle_Iso_14443_3_Tag();

			case TAG_ISO_14443_4:
				return this.handle_Iso_14443_4_Tag();

			default:
				return this.handle_Iso_14443_3_Tag();

		}

	}

	// TODO: improve error handling and debugging
	async handle_Iso_14443_3_Tag() {

		if (!this.card || !this.connection) {
			return false;
		}

		this.logger.debug('processing ISO 14443-3 tag', this.card);

		// APDU CMD: Get Data
		const packet = Buffer.from([
			0xff, // Class
			0xca, // INS
			0x00, // P1: Get current card UID
			0x00, // P2
			0x00, // Le: Full Length of UID
		]);

		try {

			const response = await this.transmit(packet, 12);

			if (response.length < 2) {

				const error = new GetUIDError('invalid_response', `Invalid response length ${response.length}. Expected minimal length is 2 bytes.`);
				this.emit('error', error);

				return;

			}

			// last 2 bytes are the status code
			const statusCode = response.slice(-2).readUInt16BE(0);

			// an error occurred
			if (statusCode !== 0x9000) {

				const error = new GetUIDError(OPERATION_FAILED, 'Could not get card UID.');
				this.emit('error', error);

				return;
			}

			// strip out the status code (the rest is UID)
			const uid = response.slice(0, -2).toString('hex');
			// const uidReverse = Reader.reverseBuffer(response.slice(0, -2)).toString('hex');

			this.card.uid = uid;

			this.emit('card', { ...this.card });


		} catch (err) {

			const error = new GetUIDError(null, null, err);

			this.emit('error', error);

		}

	}

	// TODO: improve error handling and debugging
	async handle_Iso_14443_4_Tag() {

		if (!this.card || !this.connection) {
			return false;
		}

		this.logger.debug('processing ISO 14443-4 tag', this.card);

		if (!this.aid) {
			this.emit('error', new Error('Cannot process ISO 14443-4 tag because AID was not set.'));
			return;
		}

		const aid = typeof this.aid === 'function' ? this.aid(this.card) : this.aid;

		if (!Buffer.isBuffer(aid)) {
			this.emit('error', new Error('AID must be an instance of Buffer.'));
			return;
		}

		// APDU CMD: SELECT FILE
		// see http://cardwerk.com/smart-card-standard-iso7816-4-section-6-basic-interindustry-commands/#chap6_11_3
		const packet = Buffer.from([
			0x00, // Class
			0xa4, // INS
			0x04, // P1
			0x00, // P2
			aid.length, // Lc
			...aid, // AID
			0x00, // Le
		]);

		try {

			const response = await this.transmit(packet, 40);

			if (response.length === 2 && response.readUInt16BE(0) === 0x6a82) {

				const err = new Error(`Not found response. Tag not compatible with AID ${aid.toString('hex').toUpperCase()}.`);
				this.emit('error', err);

				return;
			}

			if (response.length < 2) {

				const err = new Error(`Invalid response length ${response.length}. Expected minimal length is 2 bytes.`);
				this.emit('error', err);

				return;
			}

			// another possibility const statusCode = parseInt(response.slice(-2).toString('hex'), 16)
			const statusCode = response.slice(-2).readUInt16BE(0);

			// an error occurred
			if (statusCode !== 0x9000) {

				const err = new Error(`Response status error.`);
				this.emit('error', err);

				return;
			}

			// strip out the status code
			const data = response.slice(0, -2);

			this.logger.debug('Data cropped', data);

			this.emit('card', {
				...this.card,
				data: data,
			});

		} catch (err) {

			const error = new GetUIDError(null, null, err);

			this.emit('error', error);

		}

	}

	close() {
		this.reader.close();
	}

	toString() {
		return this.name;
	}

}

export default Reader;


================================================
FILE: src/errors.js
================================================
"use strict";


export const UNKNOWN_ERROR = 'unknown_error';

export class BaseError extends Error {

	constructor(code, message, previousError) {

		super(message);

		Error.captureStackTrace(this, this.constructor);

		this.name = 'BaseError';

		if (!message && previousError) {
			this.message = previousError.message;
		}

		this.code = code;

		if (previousError) {
			this.previous = previousError;
		}

	}

}

export const FAILURE = 'failure';
export const CARD_NOT_CONNECTED = 'card_not_connected';
export const OPERATION_FAILED = 'operation_failed';

export class TransmitError extends BaseError {

	constructor(code, message, previousError) {

		super(code, message, previousError);

		this.name = 'TransmitError';

	}

}

export class ControlError extends BaseError {

	constructor(code, message, previousError) {

		super(code, message, previousError);

		this.name = 'ControlError';

	}

}

export class ReadError extends BaseError {

	constructor(code, message, previousError) {

		super(code, message, previousError);

		this.name = 'ReadError';

	}

}

export class WriteError extends BaseError {

	constructor(code, message, previousError) {

		super(code, message, previousError);

		this.name = 'WriteError';

	}

}

export class LoadAuthenticationKeyError extends BaseError {

	constructor(code, message, previousError) {

		super(code, message, previousError);

		this.name = 'LoadAuthenticationKeyError';

	}

}

export class AuthenticationError extends BaseError {

	constructor(code, message, previousError) {

		super(code, message, previousError);

		this.name = 'AuthenticationError';

	}

}

export class ConnectError extends BaseError {

	constructor(code, message, previousError) {

		super(code, message, previousError);

		this.name = 'ConnectError';

	}

}

export class DisconnectError extends BaseError {

	constructor(code, message, previousError) {

		super(code, message, previousError);

		this.name = 'DisconnectError';

	}

}

export class GetUIDError extends BaseError {

	constructor(code, message, previousError) {

		super(code, message, previousError);

		this.name = 'GetUIDError';

	}

}


================================================
FILE: src/index.js
================================================
"use strict";

import NFC from './NFC';
import Reader, {
	TAG_ISO_14443_3,
	TAG_ISO_14443_4,
	KEY_TYPE_A,
	KEY_TYPE_B,
	CONNECT_MODE_CARD,
	CONNECT_MODE_DIRECT,
} from './Reader';
import ACR122Reader from './ACR122Reader';
import {
	UNKNOWN_ERROR,
	FAILURE,
	CARD_NOT_CONNECTED,
	OPERATION_FAILED,
	BaseError,
	TransmitError,
	ControlError,
	ReadError,
	WriteError,
	LoadAuthenticationKeyError,
	AuthenticationError,
	ConnectError,
	DisconnectError,
	GetUIDError,
} from './errors';

export {
	NFC,
	Reader,
	TAG_ISO_14443_3,
	TAG_ISO_14443_4,
	KEY_TYPE_A,
	KEY_TYPE_B,
	CONNECT_MODE_CARD,
	CONNECT_MODE_DIRECT,
	ACR122Reader,
	UNKNOWN_ERROR,
	FAILURE,
	CARD_NOT_CONNECTED,
	OPERATION_FAILED,
	BaseError,
	TransmitError,
	ControlError,
	ReadError,
	WriteError,
	LoadAuthenticationKeyError,
	AuthenticationError,
	ConnectError,
	DisconnectError,
	GetUIDError,
};


================================================
FILE: test/README.md
================================================
# Tests

**There are no tests yet 😢**

To test the features of this library, we need to develop mock version of pcsclite library to simulate cards. Feel free to contribute.

---

We are going to use [AVA: Futuristic JavaScript test runner](https://github.com/avajs/ava).  

It is fully set up, so you can already run tests with:

```bash
npm run test
```

It is a shortcut for `cross-env NODE_ENV=test ava test --verbose` which runs all the tests in the test folder _(except for helpers, fixtures folders as well as files prefixed with `_`)_.


================================================
FILE: test/_node-version-test.js
================================================
"use strict";

const mock = require('mock-require');

mock('@pokusew/pcsclite', {});

const {
	NFC,
	Reader, TAG_ISO_14443_3, TAG_ISO_14443_4, KEY_TYPE_A, KEY_TYPE_B,
	ACR122Reader,
	UNKNOWN_ERROR,
	FAILURE,
	CARD_NOT_CONNECTED,
	OPERATION_FAILED,
	BaseError,
	TransmitError,
	ControlError,
	ReadError,
	WriteError,
	LoadAuthenticationKeyError,
	AuthenticationError,
	ConnectError,
	DisconnectError,
	GetUIDError
} = require('../dist/index');


================================================
FILE: test/helpers/pcsclite-mock.js
================================================
"use strict";

import EventEmitter from 'events';


class MockPCSC extends EventEmitter {

	constructor(name) {
		super();
	}

	simulateReader(reader) {
		this.emit('reader', reader);
	}

}


class MockReader extends EventEmitter {

	name = null;

	constructor(name) {
		super();
		this.name = name || 'MockReader';
	}

	simulateCard(card) {
		this.emit('card', card);
	}

}


class MockCard extends EventEmitter {

	constructor() {
		super();
	}

}


export default function () {

	return new MockPCSC();

}


================================================
FILE: test/tests.js
================================================
"use strict";

import test from 'ava';
import mock from 'mock-require';

// mock @pokusew/pcsclite to allow to simulate cards
import pcscliteMock from './helpers/pcsclite-mock';
mock('@pokusew/pcsclite', pcscliteMock);
const NFC = require('../src/NFC').default;



test('first', t => {

	const nfc = new NFC();

	t.truthy(1 === 1);

});
Download .txt
gitextract_nlchtnd6/

├── .babelrc
├── .editorconfig
├── .github/
│   └── workflows/
│       └── ci.yml
├── .gitignore
├── .idea/
│   ├── codeStyles/
│   │   ├── Project.xml
│   │   └── codeStyleConfig.xml
│   ├── encodings.xml
│   ├── jsLibraryMappings.xml
│   ├── misc.xml
│   ├── modules.xml
│   ├── nfc-pcsc.iml
│   ├── vcs.xml
│   └── watcherTasks.xml
├── .npmignore
├── LICENSE.md
├── README.md
├── ava.config.js
├── examples/
│   ├── BitSet.js
│   ├── basic.js
│   ├── from-readme-3.js
│   ├── led.js
│   ├── mifare-classic.js
│   ├── mifare-desfire.js
│   ├── mifare-ultralight-c.js
│   ├── mifare-ultralight-ntag.js
│   ├── mini-logger.js
│   ├── ndef.js
│   ├── pretty-logger.js
│   ├── read-write.js
│   ├── uid-logger.js
│   ├── utils.js
│   └── without-auto.js
├── package.json
├── src/
│   ├── ACR122Reader.js
│   ├── NFC.js
│   ├── Reader.js
│   ├── errors.js
│   └── index.js
└── test/
    ├── README.md
    ├── _node-version-test.js
    ├── helpers/
    │   └── pcsclite-mock.js
    └── tests.js
Download .txt
SYMBOL INDEX (115 symbols across 12 files)

FILE: examples/BitSet.js
  class BitSet (line 7) | class BitSet {
    method constructor (line 15) | constructor(bitsLength) {
    method clone (line 19) | clone() {
    method buffer (line 24) | get buffer() {
    method getBufferPos (line 28) | static getBufferPos(pos) {
    method getMask (line 32) | static getMask(pos) {
    method set (line 36) | set(pos) {
    method test (line 40) | test(pos) {
    method clear (line 44) | clear(pos) {
    method toggle (line 48) | toggle(pos) {
    method toArray (line 52) | toArray(useBooleans = true) {
    method print (line 66) | print(name, appendBlankLine = true) {

FILE: examples/mifare-desfire.js
  function decrypt (line 32) | function decrypt(key, data, iv = Buffer.alloc(8).fill(0)) {
  function encrypt (line 41) | function encrypt(key, data, iv = Buffer.alloc(8).fill(0)) {

FILE: examples/mifare-ultralight-c.js
  class MifareUltralight3DESAuthenticationError (line 18) | class MifareUltralight3DESAuthenticationError extends TransmitError {
    method constructor (line 20) | constructor(code, message, previousError) {
  class MifareUltralightReadError (line 30) | class MifareUltralightReadError extends TransmitError {
    method constructor (line 32) | constructor(code, message, previousError) {
  class MifareUltralightWriteError (line 42) | class MifareUltralightWriteError extends TransmitError {
    method constructor (line 44) | constructor(code, message, previousError) {
  class MifareUltralightC (line 197) | class MifareUltralightC {
    method constructor (line 218) | constructor(reader) {
    method authenticate3DES (line 244) | async authenticate3DES(key) {
    method swapKeyEndianness (line 337) | static swapKeyEndianness(key) {
    method decrypt (line 379) | static decrypt(keyBE, data, iv) {
    method encrypt (line 416) | static encrypt(keyBE, data, iv) {
    method _authenticatePart1 (line 433) | async _authenticatePart1() {
    method _authenticatePart2 (line 495) | async _authenticatePart2(ekRndARndB2) {
    method read (line 560) | async read(page) {
    method write (line 623) | async write(page, data) {
    method writeAuth0 (line 696) | async writeAuth0(value) {
    method writeAuth1 (line 722) | async writeAuth1(value) {
  constant DEFAULT_KEY (line 739) | const DEFAULT_KEY = Buffer.from('BREAKMEIFYOUCAN!', 'utf-8');
  constant ZERO_KEY (line 746) | const ZERO_KEY = Buffer.from('00000000000000000000000000000000', 'hex');
  constant ONES_KEY (line 747) | const ONES_KEY = Buffer.from('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF', 'hex');
  constant DEMO_KEY (line 748) | const DEMO_KEY = Buffer.from('AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDD', 'hex');
  function numerical3DESExampleFromMF0ICU2 (line 852) | function numerical3DESExampleFromMF0ICU2() {

FILE: examples/mifare-ultralight-ntag.js
  class MifareUltralightPasswordAuthenticationError (line 24) | class MifareUltralightPasswordAuthenticationError extends TransmitError {
    method constructor (line 26) | constructor(code, message, previousError) {
  class MifareUltralightFastReadError (line 36) | class MifareUltralightFastReadError extends TransmitError {
    method constructor (line 38) | constructor(code, message, previousError) {
  class MifareUltralight (line 78) | class MifareUltralight {
    method constructor (line 80) | constructor(reader) {
    method passwordAuthenticate (line 85) | async passwordAuthenticate(password, pack) {
    method fastRead (line 139) | async fastRead(startPage, endPage) {

FILE: examples/mini-logger.js
  function log (line 5) | function log() {

FILE: examples/pretty-logger.js
  constant FORMAT (line 56) | const FORMAT = winston.format.combine(

FILE: src/ACR122Reader.js
  class ACR122Reader (line 21) | class ACR122Reader extends Reader {
    method inAutoPoll (line 23) | async inAutoPoll() {
    method led (line 75) | async led(led, blinking) {
    method setBuzzerOutput (line 157) | async setBuzzerOutput(enabled = true) {
    method setPICC (line 194) | async setPICC(picc) {

FILE: src/NFC.js
  class NFC (line 9) | class NFC extends EventEmitter {
    method constructor (line 14) | constructor(logger) {
    method readers (line 79) | get readers() {
    method close (line 83) | close() {

FILE: src/Reader.js
  constant TAG_ISO_14443_3 (line 21) | const TAG_ISO_14443_3 = 'TAG_ISO_14443_3';
  constant TAG_ISO_14443_4 (line 22) | const TAG_ISO_14443_4 = 'TAG_ISO_14443_4';
  constant KEY_TYPE_A (line 24) | const KEY_TYPE_A = 0x60;
  constant KEY_TYPE_B (line 25) | const KEY_TYPE_B = 0x61;
  constant CONNECT_MODE_DIRECT (line 27) | const CONNECT_MODE_DIRECT = 'CONNECT_MODE_DIRECT';
  constant CONNECT_MODE_CARD (line 28) | const CONNECT_MODE_CARD = 'CONNECT_MODE_CARD';
  class Reader (line 31) | class Reader extends EventEmitter {
    method reverseBuffer (line 56) | static reverseBuffer(src) {
    method selectStandardByAtr (line 69) | static selectStandardByAtr(atr) {
    method aid (line 81) | get aid() {
    method aid (line 85) | set aid(value) {
    method name (line 100) | get name() {
    method constructor (line 104) | constructor(reader, logger) {
    method connect (line 215) | connect(mode = CONNECT_MODE_CARD) {
    method disconnect (line 257) | disconnect() {
    method transmit (line 288) | transmit(data, responseMaxLength) {
    method control (line 315) | control(data, responseMaxLength) {
    method loadAuthenticationKey (line 342) | async loadAuthenticationKey(keyNumber, key) {
    method authenticate (line 402) | async authenticate(blockNumber, keyType, key, obsolete = false) {
    method read (line 492) | async read(blockNumber, length, blockSize = 4, packetSize = 16, readCl...
    method write (line 565) | async write(blockNumber, data, blockSize = 4) {
    method handleTag (line 643) | handleTag() {
    method handle_Iso_14443_3_Tag (line 667) | async handle_Iso_14443_3_Tag() {
    method handle_Iso_14443_4_Tag (line 729) | async handle_Iso_14443_4_Tag() {
    method close (line 813) | close() {
    method toString (line 817) | toString() {

FILE: src/errors.js
  constant UNKNOWN_ERROR (line 4) | const UNKNOWN_ERROR = 'unknown_error';
  class BaseError (line 6) | class BaseError extends Error {
    method constructor (line 8) | constructor(code, message, previousError) {
  constant FAILURE (line 30) | const FAILURE = 'failure';
  constant CARD_NOT_CONNECTED (line 31) | const CARD_NOT_CONNECTED = 'card_not_connected';
  constant OPERATION_FAILED (line 32) | const OPERATION_FAILED = 'operation_failed';
  class TransmitError (line 34) | class TransmitError extends BaseError {
    method constructor (line 36) | constructor(code, message, previousError) {
  class ControlError (line 46) | class ControlError extends BaseError {
    method constructor (line 48) | constructor(code, message, previousError) {
  class ReadError (line 58) | class ReadError extends BaseError {
    method constructor (line 60) | constructor(code, message, previousError) {
  class WriteError (line 70) | class WriteError extends BaseError {
    method constructor (line 72) | constructor(code, message, previousError) {
  class LoadAuthenticationKeyError (line 82) | class LoadAuthenticationKeyError extends BaseError {
    method constructor (line 84) | constructor(code, message, previousError) {
  class AuthenticationError (line 94) | class AuthenticationError extends BaseError {
    method constructor (line 96) | constructor(code, message, previousError) {
  class ConnectError (line 106) | class ConnectError extends BaseError {
    method constructor (line 108) | constructor(code, message, previousError) {
  class DisconnectError (line 118) | class DisconnectError extends BaseError {
    method constructor (line 120) | constructor(code, message, previousError) {
  class GetUIDError (line 130) | class GetUIDError extends BaseError {
    method constructor (line 132) | constructor(code, message, previousError) {

FILE: test/helpers/pcsclite-mock.js
  class MockPCSC (line 6) | class MockPCSC extends EventEmitter {
    method constructor (line 8) | constructor(name) {
    method simulateReader (line 12) | simulateReader(reader) {
  class MockReader (line 19) | class MockReader extends EventEmitter {
    method constructor (line 23) | constructor(name) {
    method simulateCard (line 28) | simulateCard(card) {
  class MockCard (line 35) | class MockCard extends EventEmitter {
    method constructor (line 37) | constructor() {

FILE: test/tests.js
  constant NFC (line 9) | const NFC = require('../src/NFC').default;
Condensed preview — 42 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (147K chars).
[
  {
    "path": ".babelrc",
    "chars": 157,
    "preview": "{\n\t\"plugins\": [\n\t\t\"@babel/plugin-transform-modules-commonjs\",\n\t\t\"@babel/plugin-syntax-object-rest-spread\",\n\t\t\"@babel/plu"
  },
  {
    "path": ".editorconfig",
    "chars": 627,
    "preview": "# EditorConfig\n# https://www.editorconfig.org/\nroot = true\n\n[*]\nindent_style = tab\nindent_size = 4\nend_of_line = lf\nchar"
  },
  {
    "path": ".github/workflows/ci.yml",
    "chars": 2575,
    "preview": "#\n# GitHub Actions Workflow\n#   reference: https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github"
  },
  {
    "path": ".gitignore",
    "chars": 513,
    "preview": "### GENERAL\n\n# Windows\nThumbs.db\nDesktop.ini\n\n# macOS\n.DS_Store\n.supported\n\n# common code editors\n.atom/\n.vscode/\n\n# Net"
  },
  {
    "path": ".idea/codeStyles/Project.xml",
    "chars": 6149,
    "preview": "<component name=\"ProjectCodeStyleConfiguration\">\n  <code_scheme name=\"Project\" version=\"173\">\n    <option name=\"OTHER_IN"
  },
  {
    "path": ".idea/codeStyles/codeStyleConfig.xml",
    "chars": 142,
    "preview": "<component name=\"ProjectCodeStyleConfiguration\">\n  <state>\n    <option name=\"USE_PER_PROJECT_SETTINGS\" value=\"true\" />\n "
  },
  {
    "path": ".idea/encodings.xml",
    "chars": 159,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"Encoding\">\n    <file url=\"PROJECT\" chars"
  },
  {
    "path": ".idea/jsLibraryMappings.xml",
    "chars": 187,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"JavaScriptLibraryMappings\">\n    <include"
  },
  {
    "path": ".idea/misc.xml",
    "chars": 174,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"JavaScriptSettings\">\n    <option name=\"l"
  },
  {
    "path": ".idea/modules.xml",
    "chars": 268,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"ProjectModuleManager\">\n    <modules>\n   "
  },
  {
    "path": ".idea/nfc-pcsc.iml",
    "chars": 368,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<module type=\"WEB_MODULE\" version=\"4\">\n  <component name=\"NewModuleRootManager\">\n"
  },
  {
    "path": ".idea/vcs.xml",
    "chars": 180,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"VcsDirectoryMappings\">\n    <mapping dire"
  },
  {
    "path": ".idea/watcherTasks.xml",
    "chars": 139,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"ProjectTasksOptions\" suppressed-tasks=\"B"
  },
  {
    "path": ".npmignore",
    "chars": 560,
    "preview": "# Keeping files out of your package\n# docs: https://docs.npmjs.com/misc/developers#keeping-files-out-of-your-package\n# n"
  },
  {
    "path": "LICENSE.md",
    "chars": 1078,
    "preview": "MIT License\n\nCopyright (c) 2016-present Martin Endler\n\nPermission is hereby granted, free of charge, to any person obtai"
  },
  {
    "path": "README.md",
    "chars": 17699,
    "preview": "# nfc-pcsc\n\n[![npm](https://img.shields.io/npm/v/nfc-pcsc.svg)](https://www.npmjs.com/package/nfc-pcsc)\n[![build status]"
  },
  {
    "path": "ava.config.js",
    "chars": 1329,
    "preview": "\"use strict\";\n\n// run AVA\n//   npx ava --match='*foo'\n//   ./node_modules/.bin/ava --match='*foo'\n// see https://github."
  },
  {
    "path": "examples/BitSet.js",
    "chars": 2002,
    "preview": "\"use strict\";\n\nimport { column } from './utils';\n\n\n// TODO: cover with tests\nexport class BitSet {\n\n\t/**\n\t * Creates a n"
  },
  {
    "path": "examples/basic.js",
    "chars": 1867,
    "preview": "\"use strict\";\n\n// #############\n// Example: Basic usage\n// - see \"Basic usage\" section in README for an explanation\n// #"
  },
  {
    "path": "examples/from-readme-3.js",
    "chars": 1615,
    "preview": "\"use strict\";\n\n// #############\n// Example from \"Reading and writing data\" section of project's README\n// #############\n"
  },
  {
    "path": "examples/led.js",
    "chars": 1461,
    "preview": "\"use strict\";\n\n// #############\n// Example: Controlling LED and buzzer on ACR122U\n// - what is covered:\n//   - custom le"
  },
  {
    "path": "examples/mifare-classic.js",
    "chars": 6760,
    "preview": "\"use strict\";\n\n// #############\n// Example: MIFARE Classic\n// - should work well with any compatible PC/SC card reader\n/"
  },
  {
    "path": "examples/mifare-desfire.js",
    "chars": 4796,
    "preview": "\"use strict\";\n\n// #############\n// Example: MIFARE DESFire\n// - what is covered:\n//   - 3DES authentication\n//   - readi"
  },
  {
    "path": "examples/mifare-ultralight-c.js",
    "chars": 35361,
    "preview": "'use strict';\n\n// #############\n// Example: MIFARE Ultralight C (MF0ICU2) - 3DES authentication\n// - Note: This example "
  },
  {
    "path": "examples/mifare-ultralight-ntag.js",
    "chars": 7966,
    "preview": "'use strict';\n\n// #############\n// Example: MIFARE Ultralight EV1 and NTAG 213/215/216 – implementation of card's specif"
  },
  {
    "path": "examples/mini-logger.js",
    "chars": 198,
    "preview": "\"use strict\";\n\n// mini-logger for debugging\n\nfunction log() {\n\tconsole.log(...arguments);\n}\n\nconst logger = {\n\tlog: log,"
  },
  {
    "path": "examples/ndef.js",
    "chars": 247,
    "preview": "\"use strict\";\n\n// #############\n// example not finished, it in progress !!!\n// Read NDEF formatted data\n// #############"
  },
  {
    "path": "examples/pretty-logger.js",
    "chars": 1660,
    "preview": "\"use strict\";\n\n// pretty-logger for debugging\n// uses great winston logger library, visit https://github.com/winstonjs/w"
  },
  {
    "path": "examples/read-write.js",
    "chars": 3402,
    "preview": "\"use strict\";\n\n// #############\n// Example: Reading and writing data\n// - should work well with any compatible PC/SC car"
  },
  {
    "path": "examples/uid-logger.js",
    "chars": 508,
    "preview": "\"use strict\";\n\n// #############\n// Logs cards' uid\n// #############\n\nimport { NFC } from '../src/index';\n\n\nconst nfc = n"
  },
  {
    "path": "examples/utils.js",
    "chars": 1557,
    "preview": "\"use strict\";\n\n\n// TODO: cover with tests\n\nexport const isDefined = value => value !== null && value !== undefined;\n\nexp"
  },
  {
    "path": "examples/without-auto.js",
    "chars": 1196,
    "preview": "\"use strict\";\n\n// #############\n// Alternative usage\n// - see \"Alternative usage\" section in README for an explanation\n/"
  },
  {
    "path": "package.json",
    "chars": 2267,
    "preview": "{\n  \"name\": \"nfc-pcsc\",\n  \"version\": \"0.8.1\",\n  \"description\": \"Easy reading and writing NFC tags and cards\",\n  \"keyword"
  },
  {
    "path": "src/ACR122Reader.js",
    "chars": 5391,
    "preview": "\"use strict\";\n\nimport Reader from './Reader';\nimport {\n\tConnectError,\n\tDisconnectError,\n\tTransmitError,\n\tControlError,\n\t"
  },
  {
    "path": "src/NFC.js",
    "chars": 1685,
    "preview": "\"use strict\";\n\nimport pcsclite from '@pokusew/pcsclite';\nimport EventEmitter from 'events';\nimport Reader from './Reader"
  },
  {
    "path": "src/Reader.js",
    "chars": 17467,
    "preview": "\"use strict\";\n\nimport EventEmitter from 'events';\nimport {\n\tConnectError,\n\tDisconnectError,\n\tTransmitError,\n\tControlErro"
  },
  {
    "path": "src/errors.js",
    "chars": 2138,
    "preview": "\"use strict\";\n\n\nexport const UNKNOWN_ERROR = 'unknown_error';\n\nexport class BaseError extends Error {\n\n\tconstructor(code"
  },
  {
    "path": "src/index.js",
    "chars": 862,
    "preview": "\"use strict\";\n\nimport NFC from './NFC';\nimport Reader, {\n\tTAG_ISO_14443_3,\n\tTAG_ISO_14443_4,\n\tKEY_TYPE_A,\n\tKEY_TYPE_B,\n\t"
  },
  {
    "path": "test/README.md",
    "chars": 543,
    "preview": "# Tests\n\n**There are no tests yet 😢**\n\nTo test the features of this library, we need to develop mock version of pcsclite"
  },
  {
    "path": "test/_node-version-test.js",
    "chars": 443,
    "preview": "\"use strict\";\n\nconst mock = require('mock-require');\n\nmock('@pokusew/pcsclite', {});\n\nconst {\n\tNFC,\n\tReader, TAG_ISO_144"
  },
  {
    "path": "test/helpers/pcsclite-mock.js",
    "chars": 509,
    "preview": "\"use strict\";\n\nimport EventEmitter from 'events';\n\n\nclass MockPCSC extends EventEmitter {\n\n\tconstructor(name) {\n\t\tsuper("
  },
  {
    "path": "test/tests.js",
    "chars": 337,
    "preview": "\"use strict\";\n\nimport test from 'ava';\nimport mock from 'mock-require';\n\n// mock @pokusew/pcsclite to allow to simulate "
  }
]

About this extraction

This page contains the full source code of the pokusew/nfc-pcsc GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 42 files (131.4 KB), approximately 39.5k tokens, and a symbol index with 115 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!