├── sample
├── images
│ ├── nfcjs.ico
│ ├── nfcjs.jpeg
│ ├── acr122u.png
│ └── scl3711.png
├── screenshots
│ └── 1040x811.png
├── background.js
├── manifest.json
├── app.css
├── app.html
├── app.js
├── lib
│ └── bootstrap.min.js
└── chrome-nfc.js
├── LICENSE
├── src
├── tag.js
├── b64.js
├── util.js
├── usb.js
├── sha256.js
├── devmanager.js
├── nfc.js
├── tt2.js
├── ndef.js
├── mifare-classic.js
└── scl3711.js
├── compile.py
├── CONTRIBUTING.md
└── README.md
/sample/images/nfcjs.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justinribeiro/chrome-nfc/HEAD/sample/images/nfcjs.ico
--------------------------------------------------------------------------------
/sample/images/nfcjs.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justinribeiro/chrome-nfc/HEAD/sample/images/nfcjs.jpeg
--------------------------------------------------------------------------------
/sample/images/acr122u.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justinribeiro/chrome-nfc/HEAD/sample/images/acr122u.png
--------------------------------------------------------------------------------
/sample/images/scl3711.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justinribeiro/chrome-nfc/HEAD/sample/images/scl3711.png
--------------------------------------------------------------------------------
/sample/screenshots/1040x811.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justinribeiro/chrome-nfc/HEAD/sample/screenshots/1040x811.png
--------------------------------------------------------------------------------
/sample/background.js:
--------------------------------------------------------------------------------
1 | chrome.app.runtime.onLaunched.addListener(function() {
2 | chrome.app.window.create('app.html', {
3 | 'id': 'appWindow',
4 | 'bounds': { 'width': 1024, 'height': 768 }});
5 | });
6 |
--------------------------------------------------------------------------------
/sample/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "NFC Sample",
3 | "version": "0.1",
4 | "description": "chrome.nfc API sample",
5 | "manifest_version": 2,
6 |
7 | "permissions": [
8 | "usb",
9 | {
10 | "usbDevices": [
11 | { "vendorId": 1254, "productId": 21905 }, // SCL3711
12 | { "vendorId": 1839, "productId": 8704 } // ACR122U
13 | ]
14 | }
15 | ],
16 |
17 | "app": {
18 | "background": {
19 | "scripts": [ "background.js" ]
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2014 Google Inc. All rights reserved.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 |
--------------------------------------------------------------------------------
/src/tag.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 Google Inc. All rights reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 |
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | /**
17 | * @fileoverview Tag() class
18 | */
19 |
20 |
21 | /*
22 | * Unfortunately, some tags, such as Ultralight-*, require async read
23 | * to distiguish the tag type. The 'cb' will be called if the order matters.
24 | */
25 | function Tag(tag_name, tag_id) {
26 | switch (tag_name) {
27 | case "tt2":
28 | return new TT2(tag_id);
29 |
30 | case "mifare_classic":
31 | return new MifareClassic(tag_id);
32 | }
33 |
34 | return null;
35 | }
36 |
37 |
--------------------------------------------------------------------------------
/sample/app.css:
--------------------------------------------------------------------------------
1 | html, body {
2 | height: 100%;
3 | }
4 |
5 | .wrapper {
6 | margin: 0px 20px 0 20px;
7 | }
8 |
9 | textarea {
10 | display: block;
11 | width: 660px;
12 | height: 220px;
13 | font-family: "Courier New", monospace;
14 | }
15 |
16 | .hidden {
17 | display: none
18 | }
19 |
20 | dd {
21 | margin-bottom: 10px;
22 | }
23 |
24 | #device-thumbnail {
25 | position: fixed;
26 | }
27 |
28 | #device-info {
29 | margin-left: 150px;
30 | }
31 |
32 | .tab-content {
33 | margin-bottom: 10px;
34 | }
35 |
36 | pre {
37 | -webkit-user-select: text;
38 | cursor: text;
39 | }
40 |
41 | .tab-content {
42 | overflow: initial;
43 | }
44 |
45 | #logContainer {
46 | height: 200px;
47 | width: 100%;
48 | position: fixed;
49 | bottom: 0;
50 | background: #F5F5F5;
51 | transition: height .2s ease-in-out;
52 | }
53 |
54 | .logs {
55 | overflow-y: auto;
56 | height: 190px;
57 | }
58 |
59 | .logs pre {
60 | margin: 0 0 1px;
61 | border: none;
62 | border-bottom: 1px dotted #DDD;
63 | border-radius: 0px;
64 | padding: 5px;
65 | }
66 |
67 | .drawer {
68 | height: 10px;
69 | background: #DDD;
70 | cursor: pointer;
71 | }
72 |
73 | .small {
74 | height: 10px !important;
75 | }
76 |
--------------------------------------------------------------------------------
/compile.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 |
3 | import json
4 | import os
5 | import sys
6 | import urllib.parse
7 | import urllib.request
8 |
9 | BASE_DIR = os.path.dirname(sys.argv[0])
10 | CLOSURE_URL = 'http://closure-compiler.appspot.com/compile'
11 | TARGET_JS = os.path.join(BASE_DIR, 'sample', 'chrome-nfc.js')
12 |
13 | def print_errors(errors, js_files):
14 | for error in errors:
15 | if error['file'].lower().find('externs') >= 0:
16 | filename = error['file']
17 | else:
18 | fileno = int(error['file'][6:])
19 | filename = js_files[fileno]
20 | if 'error' in error:
21 | text = error['error']
22 | else:
23 | text = error['warning']
24 | print(filename + ':' + str(error['lineno']) + ' ' + text)
25 | print(error['line'])
26 | print()
27 |
28 |
29 | JS_FILES = [
30 | 'src/b64.js',
31 | 'src/mifare-classic.js',
32 | 'src/ndef.js',
33 | 'src/nfc.js',
34 | 'src/devmanager.js',
35 | 'src/scl3711.js',
36 | 'src/sha256.js',
37 | 'src/tag.js',
38 | 'src/tt2.js',
39 | 'src/usb.js',
40 | 'src/util.js'
41 | ]
42 |
43 | def main():
44 | print('Compiling JavaScript code.')
45 |
46 | params = [
47 | ('compilation_level', 'WHITESPACE_ONLY'),
48 | ('formatting', 'pretty_print'),
49 | ('language', 'ECMASCRIPT5'),
50 | ('output_format', 'json'),
51 | ('output_info', 'statistics'),
52 | ('output_info', 'warnings'),
53 | ('output_info', 'errors'),
54 | ('output_info', 'compiled_code')
55 | ]
56 |
57 | for js_file in JS_FILES:
58 | params.append(('js_code', open(os.path.join(BASE_DIR, js_file)).read()))
59 |
60 | params = bytes(urllib.parse.urlencode(params, encoding='utf8'), 'utf8')
61 | headers = {'Content-Type': 'application/x-www-form-urlencoded'}
62 |
63 | print('Connecting', CLOSURE_URL)
64 | out = urllib.request.urlopen(CLOSURE_URL, data=params)
65 | result = json.loads(out.read().decode('utf8'))
66 |
67 | if 'errors' in result and len(result['errors']):
68 | print('Errors:')
69 | print_errors(result['errors'], JS_FILES)
70 |
71 | if 'warnings' in result and len(result['warnings']):
72 | print('Warnings:')
73 | print_errors(result['warnings'], JS_FILES)
74 |
75 | print('Writing', TARGET_JS)
76 | open(TARGET_JS, 'w').write(result['compiledCode'])
77 |
78 | if __name__ == '__main__':
79 | main()
80 |
--------------------------------------------------------------------------------
/src/b64.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 Google Inc. All rights reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 |
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | // WebSafeBase64Escape and Unescape.
18 |
19 | function B64_encode(bytes, opt_length) {
20 | if (!opt_length) opt_length = bytes.length;
21 | var b64out =
22 | "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
23 | var result = "";
24 | var shift = 0;
25 | var accu = 0;
26 | var input_index = 0;
27 | while (opt_length--) {
28 | accu <<= 8;
29 | accu |= bytes[input_index++];
30 | shift += 8;
31 | while (shift >= 6) {
32 | var i = (accu >> (shift - 6)) & 63;
33 | result += b64out.charAt(i);
34 | shift -= 6;
35 | }
36 | }
37 | if (shift) {
38 | accu <<= 8;
39 | shift += 8;
40 | var i = (accu >> (shift - 6)) & 63;
41 | result += b64out.charAt(i);
42 | }
43 | return result;
44 | };
45 |
46 | // Normal base64 encode; not websafe, including padding.
47 | function base64_encode(bytes, opt_length) {
48 | if (!opt_length) opt_length = bytes.length;
49 | var b64out =
50 | "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
51 | var result = "";
52 | var shift = 0;
53 | var accu = 0;
54 | var input_index = 0;
55 | while (opt_length--) {
56 | accu <<= 8;
57 | accu |= bytes[input_index++];
58 | shift += 8;
59 | while (shift >= 6) {
60 | var i = (accu >> (shift - 6)) & 63;
61 | result += b64out.charAt(i);
62 | shift -= 6;
63 | }
64 | }
65 | if (shift) {
66 | accu <<= 8;
67 | shift += 8;
68 | var i = (accu >> (shift - 6)) & 63;
69 | result += b64out.charAt(i);
70 | }
71 | while (result.length % 4) result += '=';
72 | return result;
73 | };
74 |
75 | var B64_inmap =
76 | [
77 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 63, 0, 0,
78 | 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 0, 0, 0, 0, 0, 0,
79 | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
80 | 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 0, 0, 0, 0, 64,
81 | 0, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41,
82 | 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 0, 0, 0, 0, 0
83 | ];
84 |
85 | function B64_decode(string) {
86 | var bytes = [];
87 | var accu = 0;
88 | var shift = 0;
89 | for (var i = 0; i < string.length; ++i) {
90 | var c = string.charCodeAt(i);
91 | if (c < 32 || c > 127 || !B64_inmap[c - 32]) return [];
92 | accu <<= 6;
93 | accu |= (B64_inmap[c - 32] - 1);
94 | shift += 6;
95 | if (shift >= 8) {
96 | bytes.push((accu >> (shift - 8)) & 255);
97 | shift -= 8;
98 | }
99 | }
100 | return bytes;
101 | };
102 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # How to contribute #
2 |
3 | We'd love to accept your patches and contributions to this project. There are
4 | a just a few small guidelines you need to follow.
5 |
6 |
7 | ## Contributor License Agreement ##
8 |
9 | Contributions to any Google project must be accompanied by a Contributor
10 | License Agreement. This is not a copyright **assignment**, it simply gives
11 | Google permission to use and redistribute your contributions as part of the
12 | project.
13 |
14 | * If you are an individual writing original source code and you're sure you
15 | own the intellectual property, then you'll need to sign an [individual
16 | CLA][].
17 |
18 | * If you work for a company that wants to allow you to contribute your work,
19 | then you'll need to sign a [corporate CLA][].
20 |
21 | You generally only need to submit a CLA once, so if you've already submitted
22 | one (even if it was for a different project), you probably don't need to do it
23 | again.
24 |
25 | [individual CLA]: https://developers.google.com/open-source/cla/individual
26 | [corporate CLA]: https://developers.google.com/open-source/cla/corporate
27 |
28 |
29 | ## Submitting a patch ##
30 |
31 | 1. It's generally best to start by opening a new issue describing the bug or
32 | feature you're intending to fix. Even if you think it's relatively minor,
33 | it's helpful to know what people are working on. Mention in the initial
34 | issue that you are planning to work on that bug or feature so that it can
35 | be assigned to you.
36 |
37 | 1. Follow the normal process of [forking][] the project, and setup a new
38 | branch to work in. It's important that each group of changes be done in
39 | separate branches in order to ensure that a pull request only includes the
40 | commits related to that bug or feature.
41 |
42 | 1. Any significant changes should almost always be accompanied by tests. The
43 | project already has good test coverage, so look at some of the existing
44 | tests if you're unsure how to go about it.
45 |
46 | 1. Do your best to have [well-formed commit messages][] for each change.
47 | This provides consistency throughout the project, and ensures that commit
48 | messages are able to be formatted properly by various git tools.
49 |
50 | 1. Finally, push the commits to your fork and submit a [pull request][].
51 |
52 | [forking]: https://help.github.com/articles/fork-a-repo
53 | [well-formed commit messages]: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html
54 | [squash]: http://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits
55 | [pull request]: https://help.github.com/articles/creating-a-pull-request
56 |
57 |
58 | ## Other notes on code organization ##
59 |
60 | Currently, everything is defined in the main `github` package, with API methods
61 | broken into separate service objects. These services map directly to how
62 | the [GitHub API documentation][] is organized, so use that as your guide for
63 | where to put new methods.
64 |
65 | Sub-service (e.g. [Repo Hooks][]) implementations are split into separate files
66 | based on the APIs they provide. These files are named service_api.go (e.g.
67 | repos_hooks.go) to describe the API to service mappings.
68 |
69 | [GitHub API documentation]: http://developer.github.com/v3/
70 | [Repo Hooks]: http://developer.github.com/v3/repos/hooks/
71 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Chrome App NFC Library
2 |
3 | With this simple library, you can build a [Chrome App](https://developer.chrome.com/apps) that communicates over USB with NFC Readers.
4 |
5 | ## Supported NFC Readers
6 |
7 | [ACR122U](http://www.acs.com.hk/en/products/3/acr122u-usb-nfc-reader) | [SCL3711](http://www.identive-group.com/products-and-solutions/identification-products/mobility-solutions/mobile-readers/scl3711-contactless-usb-smart-card-reader)
8 | --- | ---
9 |
|
10 |
11 | ## Play with the Chrome App sample
12 |
13 | * Check `Developer Mode` in `chrome://extensions`
14 | * Click "Load unpacked extension..." in `chrome://extensions` and select the [sample](/sample) folder.
15 | * Launch it.
16 |
17 |
18 |
19 | ## Caveats
20 |
21 | Learn more about USB Devices Caveats at https://developer.chrome.com/apps/app_usb#caveats
22 |
23 | ## Usage
24 |
25 | Once you've imported the [chrome-nfc.js](https:///raw.github.com/GoogleChrome/chrome-nfc/master/sample/chrome-nfc.js) javascript library into your Chrome App, you need to add the permissions below to your manifest file:
26 |
27 | ```javascript
28 | "permissions": [
29 | "usb",
30 | {
31 | "usbDevices": [
32 | { "vendorId": 1254, "productId": 21905 }, // SCL3711
33 | { "vendorId": 1839, "productId": 8704 } // ACR122U
34 | ]
35 | }
36 | ]
37 | ```
38 |
39 | ### Enumerate NFC readers
40 |
41 | ``` javascript
42 | chrome.nfc.findDevices(function(devices) {
43 | console.log("Found " + devices.length + " NFC device(s)");
44 | for (var i = 0; i < devices.length; i++) {
45 | var device = devices[i];
46 | console.log(device.vendorId, device.productId);
47 | }
48 | });
49 | ```
50 |
51 | ### Read NFC tag
52 |
53 | ``` javascript
54 | chrome.nfc.findDevices(function(devices) {
55 | var device = devices[0];
56 | chrome.nfc.read(device, {}, function(type, ndef) {
57 | console.lof(ndef);
58 | var uri = ndef.ndef[0]["uri"];
59 | console.log(uri);
60 | var text = ndef.ndef[1]["text"];
61 | console.log(text);
62 | });
63 | });
64 | ```
65 |
66 | ### Write NFC tag
67 |
68 | ``` javascript
69 | chrome.nfc.findDevices(function(devices) {
70 | var device = devices[0];
71 | var ndef = [
72 | {"text": "Chromium.org website" },
73 | {"uri": "http://chromium.org" },
74 | {"aar": "com.google.samples.apps.iosched" },
75 | ];
76 | chrome.nfc.write(device, {"ndef": ndef}, function(rc) {
77 | if (!rc) {
78 | console.log("WRITE() success!");
79 | } else {
80 | console.log("WRITE() FAILED, rc = " + rc);
81 | }
82 | });
83 | });
84 | ```
85 |
86 | ### Emulate NFC tag
87 |
88 | ``` javascript
89 | chrome.nfc.findDevices(function(devices) {
90 | var device = devices[0];
91 | var ndef = [
92 | {"type": "URI", "uri": "http://chromium.org"}
93 | ];
94 | chrome.nfc.emulate_tag(device, {"ndef": ndef}, function(rc) {
95 | if (!rc) {
96 | console.log("EMULATE() success!");
97 | } else {
98 | console.log("EMULATE() FAILED, rc = " + rc);
99 | }
100 | });
101 | });
102 | ```
103 |
104 | ### Read Mifare Classic tag (Logic Mode)
105 |
106 | ``` javascript
107 | chrome.nfc.findDevices(function(devices) {
108 | var device = devices[0];
109 | chrome.nfc.read_logic(device, 0, 2, function(rc, data) {
110 | console.log(UTIL_BytesToHex(data));
111 | });
112 | });
113 | ```
114 |
115 | ### Write Mifare Classic tag (Logic Mode)
116 |
117 | ``` javascript
118 | chrome.nfc.findDevices(function(devices) {
119 | var device = devices[0];
120 | var data = new Uint8Array([ // NDEF(http://google.com)
121 | 0xdb, 0x00, 0x03, 0xe1, 0x00, 0x00, 0x00, 0x00, // block 0 (MAD1)
122 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
123 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // block 1 (MAD1)
124 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
125 | 0x03, 0x0f, 0xd1, 0x01, 0x0b, 0x55, 0x03, 0x67, // block 2 (NDEF)
126 | 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x63, 0x6f,
127 | 0x6d, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // block 3 (NDEF)
128 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
129 | ]);
130 | chrome.nfc.write_logic(device, 0, data, function(rc) {
131 | console.log("WRITE_LOGIC() SUCCESS");
132 | });
133 | });
134 | ```
135 |
136 | ## Compiling the library
137 |
138 | Compiling script requires [Python 3.0](http://www.python.org/download/releases/3.0/) and will use online [Closure Compiler](https://developers.google.com/closure/). Just run
139 |
140 | python3 compile.py
141 |
142 | and the library will be written to `chrome-nfc.js`.
143 |
--------------------------------------------------------------------------------
/src/util.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 Google Inc. All rights reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 |
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | 'use strict';
18 |
19 | function UTIL_StringToBytes(s, bytes) {
20 | bytes = bytes || new Array(s.length);
21 | for (var i = 0; i < s.length; ++i)
22 | bytes[i] = s.charCodeAt(i);
23 | return bytes;
24 | }
25 |
26 | function UTIL_BytesToString(b) {
27 | var tmp = new String();
28 | for (var i = 0; i < b.length; ++i)
29 | tmp += String.fromCharCode(b[i]);
30 | return tmp;
31 | }
32 |
33 | function UTIL_BytesToHex(b) {
34 | if (!b) return '(null)';
35 | var hexchars = '0123456789ABCDEF';
36 | var hexrep = new Array(b.length * 2);
37 |
38 | for (var i = 0; i < b.length; ++i) {
39 | hexrep[i * 2 + 0] = hexchars.charAt((b[i] >> 4) & 15);
40 | hexrep[i * 2 + 1] = hexchars.charAt(b[i] & 15);
41 | }
42 | return hexrep.join('');
43 | }
44 |
45 | function UTIL_BytesToHexWithSeparator(b, sep) {
46 | var hexchars = '0123456789ABCDEF';
47 | var stride = 2 + (sep?1:0);
48 | var hexrep = new Array(b.length * stride);
49 |
50 | for (var i = 0; i < b.length; ++i) {
51 | if (sep) hexrep[i * stride + 0] = sep;
52 | hexrep[i * stride + stride - 2] = hexchars.charAt((b[i] >> 4) & 15);
53 | hexrep[i * stride + stride - 1] = hexchars.charAt(b[i] & 15);
54 | }
55 | return (sep?hexrep.slice(1):hexrep).join('');
56 | }
57 |
58 | function UTIL_HexToBytes(h) {
59 | var hexchars = '0123456789ABCDEFabcdef';
60 | var res = new Uint8Array(h.length / 2);
61 | for (var i = 0; i < h.length; i += 2) {
62 | if (hexchars.indexOf(h.substring(i, i + 1)) == -1) break;
63 | res[i / 2] = parseInt(h.substring(i, i + 2), 16);
64 | }
65 | return res;
66 | }
67 |
68 | function UTIL_equalArrays(a, b) {
69 | if (!a || !b) return false;
70 | if (a.length != b.length) return false;
71 | var accu = 0;
72 | for (var i = 0; i < a.length; ++i)
73 | accu |= a[i] ^ b[i];
74 | return accu === 0;
75 | }
76 |
77 | function UTIL_ltArrays(a, b) {
78 | if (a.length < b.length) return true;
79 | if (a.length > b.length) return false;
80 | for (var i = 0; i < a.length; ++i) {
81 | if (a[i] < b[i]) return true;
82 | if (a[i] > b[i]) return false;
83 | }
84 | return false;
85 | }
86 |
87 | function UTIL_geArrays(a, b) {
88 | return !UTIL_ltArrays(a, b);
89 | }
90 |
91 | function UTIL_getRandom(a) {
92 | var tmp = new Array(a);
93 | var rnd = new Uint8Array(a);
94 | window.crypto.getRandomValues(rnd); // Yay!
95 | for (var i = 0; i < a; ++i) tmp[i] = rnd[i] & 255;
96 | return tmp;
97 | }
98 |
99 | function UTIL_equalArrays(a, b) {
100 | if (!a || !b) return false;
101 | if (a.length != b.length) return false;
102 | var accu = 0;
103 | for (var i = 0; i < a.length; ++i)
104 | accu |= a[i] ^ b[i];
105 | return accu === 0;
106 | }
107 |
108 | function UTIL_setFavicon(icon) {
109 | // Construct a new favion link tag
110 | var faviconLink = document.createElement("link");
111 | faviconLink.rel = "Shortcut Icon";
112 | faviconLink.type = 'image/x-icon';
113 | faviconLink.href = icon;
114 |
115 | // Remove the old favion, if it exists
116 | var head = document.getElementsByTagName("head")[0];
117 | var links = head.getElementsByTagName("link");
118 | for (var i=0; i < links.length; i++) {
119 | var link = links[i];
120 | if (link.type == faviconLink.type && link.rel == faviconLink.rel) {
121 | head.removeChild(link);
122 | }
123 | }
124 |
125 | // Add in the new one
126 | head.appendChild(faviconLink);
127 | }
128 |
129 | // Erase all entries in array
130 | function UTIL_clear(a) {
131 | if (a instanceof Array) {
132 | for (var i = 0; i < a.length; ++i)
133 | a[i] = 0;
134 | }
135 | }
136 |
137 | // hr:min:sec.milli string
138 | function UTIL_time() {
139 | var d = new Date();
140 | var m = '000' + d.getMilliseconds();
141 | var s = d.toTimeString().substring(0, 8) + '.' + m.substring(m.length - 3);
142 | return s;
143 | }
144 |
145 | function UTIL_fmt(s) {
146 | return UTIL_time() + ' ' + s;
147 | }
148 |
149 | // a and b are Uint8Array. Returns Uint8Array.
150 | function UTIL_concat(a, b) {
151 | var c = new Uint8Array(a.length + b.length);
152 | var i, n = 0;
153 | for (i = 0; i < a.length; i++, n++) c[n] = a[i];
154 | for (i = 0; i < b.length; i++, n++) c[n] = b[i];
155 | return c;
156 | }
157 |
158 |
--------------------------------------------------------------------------------
/src/usb.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 Google Inc. All rights reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 |
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | /**
17 | * @fileoverview Low level usb cruft to SCL3711 NFC token.
18 | */
19 |
20 | 'use strict';
21 |
22 | // Low level 'driver'. One per physical USB device.
23 | function llSCL3711(dev, acr122) {
24 | this.dev = dev;
25 | this.txqueue = [];
26 | this.clients = [];
27 | this.acr122 = acr122;
28 | if (acr122) {
29 | this.endpoint = 2;
30 | } else {
31 | // scl3711
32 | this.endpoint = 4;
33 | }
34 |
35 | this.readLoop();
36 | }
37 |
38 | llSCL3711.prototype.notifyClientOfClosure = function(client) {
39 | var cb = client.onclose;
40 | if (cb) window.setTimeout(cb, 0);
41 | };
42 |
43 | llSCL3711.prototype.close = function() {
44 | // Tell clients.
45 | while (this.clients.length != 0) {
46 | this.notifyClientOfClosure(this.clients.shift());
47 | }
48 |
49 | // Tell global list to drop this device.
50 | dev_manager.dropDevice(this);
51 | };
52 |
53 | llSCL3711.prototype.publishFrame = function(f) {
54 | // Push frame to all clients.
55 | var old = this.clients;
56 |
57 | var remaining = [];
58 | var changes = false;
59 | for (var i = 0; i < old.length; ++i) {
60 | var client = old[i];
61 | if (client.receivedFrame(f)) {
62 | // Client still alive; keep on list.
63 | remaining.push(client);
64 | } else {
65 | changes = true;
66 | console.log(UTIL_fmt(
67 | '[' + client.cid.toString(16) + '] left?'));
68 | }
69 | }
70 | if (changes) this.clients = remaining;
71 | };
72 |
73 | llSCL3711.prototype.readLoop = function() {
74 | if (!this.dev) return;
75 |
76 | // console.log(UTIL_fmt('entering readLoop ' + this.dev.handle));
77 |
78 | var self = this;
79 | chrome.usb.bulkTransfer(
80 | this.dev,
81 | { direction:'in', endpoint:this.endpoint, length:2048 },
82 | function(x) {
83 | if (x.data) {
84 | if (x.data.byteLength >= 5) {
85 |
86 | var u8 = new Uint8Array(x.data);
87 | console.log(UTIL_fmt('<' + UTIL_BytesToHex(u8)));
88 |
89 | self.publishFrame(x.data);
90 |
91 | // Read more.
92 | window.setTimeout(function() { self.readLoop(); } , 0);
93 | } else {
94 | console.error(UTIL_fmt('tiny reply!'));
95 | console.error(x);
96 | // TODO(yjlou): I don't think a tiny reply requires close.
97 | // Maybe call dev_manager.close(null, clients[0])?
98 | // window.setTimeout(function() { self.close(); }, 0);
99 | }
100 |
101 | } else {
102 | console.log('no x.data!');
103 | console.log(x);
104 | throw 'no x.data!';
105 | }
106 | }
107 | );
108 | };
109 |
110 | // Register an opener.
111 | llSCL3711.prototype.registerClient = function(who) {
112 | this.clients.push(who);
113 | };
114 |
115 | // De-register an opener.
116 | // Returns number of remaining listeners for this device.
117 | llSCL3711.prototype.deregisterClient = function(who) {
118 | var current = this.clients;
119 | this.clients = [];
120 | for (var i = 0; i < current.length; ++i) {
121 | var client = current[i];
122 | if (client != who) this.clients.push(client);
123 | }
124 | return this.clients.length;
125 | };
126 |
127 | // Stuffs all queued frames from txqueue[] to device.
128 | llSCL3711.prototype.writePump = function() {
129 | if (!this.dev) return; // Ignore.
130 |
131 | if (this.txqueue.length == 0) return; // Done with current queue.
132 |
133 | var frame = this.txqueue[0];
134 |
135 | var self = this;
136 | function transferComplete(x) {
137 | self.txqueue.shift(); // drop sent frame from queue.
138 | if (self.txqueue.length != 0) {
139 | window.setTimeout(function() { self.writePump(); }, 0);
140 | }
141 | };
142 |
143 | var u8 = new Uint8Array(frame);
144 | console.log(UTIL_fmt('>' + UTIL_BytesToHex(u8)));
145 |
146 | chrome.usb.bulkTransfer(
147 | this.dev,
148 | {direction:'out', endpoint:this.endpoint, data:frame},
149 | transferComplete
150 | );
151 | };
152 |
153 | // Queue frame to be sent.
154 | // If queue was empty, start the write pump.
155 | // Returns false if device is MIA.
156 | llSCL3711.prototype.writeFrame = function(frame) {
157 | if (!this.dev) return false;
158 |
159 | var wasEmpty = (this.txqueue.length == 0);
160 | this.txqueue.push(frame);
161 | if (wasEmpty) this.writePump();
162 |
163 | return true;
164 | };
165 |
--------------------------------------------------------------------------------
/sample/app.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | chrome.nfc API sample
5 |
6 |
7 |
8 |
9 |
10 |
13 |
14 |
32 |
33 |
34 |
![]()
35 |
36 | - Name
37 |
38 | - Vendor ID
39 |
40 | - Product ID
41 |
42 |
43 |
44 |
48 |
52 |
72 |
94 |
95 |
96 |
113 |
114 |
115 |
116 |
117 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
--------------------------------------------------------------------------------
/src/sha256.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 Google Inc. All rights reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 |
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | // SHA256 {
17 | // SHA256();
18 | // void reset();
19 | // void update(byte[] data, opt_length);
20 | // byte[32] digest();
21 | // }
22 |
23 | function SHA256() {
24 | this._buf = new Array(64);
25 | this._W = new Array(64);
26 | this._pad = new Array(64);
27 | this._k = [
28 | 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
29 | 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
30 | 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
31 | 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
32 | 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
33 | 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
34 | 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
35 | 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 ];
36 |
37 | this._pad[0] = 0x80;
38 | for (var i = 1; i < 64; ++i) this._pad[i] = 0;
39 |
40 | this.reset();
41 | };
42 |
43 | SHA256.prototype.reset = function() {
44 | this._chain = [
45 | 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f,0x9b05688c, 0x1f83d9ab, 0x5be0cd19 ];
46 |
47 | this._inbuf = 0;
48 | this._total = 0;
49 | };
50 |
51 | SHA256.prototype._compress = function(buf) {
52 | var W = this._W;
53 | var k = this._k;
54 |
55 | function _rotr(w, r) { return ((w << (32 -r)) | (w >>> r)); };
56 |
57 | // get 16 big endian words
58 | for (var i = 0; i < 64; i += 4) {
59 | var w = (buf[i] << 24) | (buf[i+1] << 16) | (buf[i+2] << 8) | (buf[i+3]);
60 | W[i / 4] = w;
61 | }
62 |
63 | // expand to 64 words
64 | for ( var i = 16; i < 64; ++i ) {
65 | var s0 = _rotr(W[i - 15], 7) ^ _rotr(W[i - 15], 18) ^ (W[i - 15] >>> 3);
66 | var s1 = _rotr(W[i - 2], 17) ^ _rotr(W[i - 2], 19) ^ (W[i - 2] >>> 10);
67 | W[i] = (W[i - 16] + s0 + W[i - 7] + s1) & 0xffffffff;
68 | }
69 |
70 | var A = this._chain[0];
71 | var B = this._chain[1];
72 | var C = this._chain[2];
73 | var D = this._chain[3];
74 | var E = this._chain[4];
75 | var F = this._chain[5];
76 | var G = this._chain[6];
77 | var H = this._chain[7];
78 |
79 | for (var i = 0; i < 64; ++i) {
80 | var S0 = _rotr(A, 2) ^ _rotr(A, 13) ^ _rotr(A, 22);
81 | var maj = (A & B) ^ (A & C) ^ (B & C);
82 | var t2 = (S0 + maj) & 0xffffffff;
83 | var S1 = _rotr(E, 6) ^ _rotr(E, 11) ^ _rotr(E, 25);
84 | var ch = (E & F) ^ ((~E) & G);
85 | var t1 = (H + S1 + ch + k[i] + W[i]) & 0xffffffff;
86 |
87 | H = G;
88 | G = F;
89 | F = E;
90 | E = (D + t1) & 0xffffffff;
91 | D = C;
92 | C = B;
93 | B = A;
94 | A = (t1 + t2) & 0xffffffff;
95 | }
96 |
97 | this._chain[0] += A;
98 | this._chain[1] += B;
99 | this._chain[2] += C;
100 | this._chain[3] += D;
101 | this._chain[4] += E;
102 | this._chain[5] += F;
103 | this._chain[6] += G;
104 | this._chain[7] += H;
105 | };
106 |
107 | SHA256.prototype.update = function(bytes, opt_length) {
108 | if ( !opt_length ) opt_length = bytes.length;
109 |
110 | this._total += opt_length;
111 | for ( var n = 0; n < opt_length; ++n ) {
112 | this._buf[this._inbuf++] = bytes[n];
113 | if ( this._inbuf == 64 ) {
114 | this._compress(this._buf);
115 | this._inbuf = 0;
116 | }
117 | }
118 | };
119 |
120 | SHA256.prototype.updateRange = function(bytes, start, end) {
121 | this._total += (end - start);
122 | for ( var n = start; n < end; ++n ) {
123 | this._buf[this._inbuf++] = bytes[n];
124 | if ( this._inbuf == 64 ) {
125 | this._compress(this._buf);
126 | this._inbuf = 0;
127 | }
128 | }
129 | };
130 |
131 | SHA256.prototype.digest = function() {
132 | for (var i = 0; i < arguments.length; ++i)
133 | this.update(arguments[i]);
134 |
135 | var digest = new Array(32);
136 | var totalBits = this._total * 8;
137 |
138 | // add pad 0x80 0x00*
139 | if (this._inbuf < 56)
140 | this.update(this._pad, 56 - this._inbuf );
141 | else
142 | this.update(this._pad, 64 - ( this._inbuf - 56 ) );
143 |
144 | // add # bits, big endian
145 | for (var i = 63; i >= 56; --i) {
146 | this._buf[i] = totalBits & 255;
147 | totalBits >>>= 8;
148 | }
149 |
150 | this._compress(this._buf);
151 |
152 | var n = 0;
153 | for (var i = 0; i < 8; ++i)
154 | for (var j = 24; j >= 0; j -= 8)
155 | digest[n++] = (this._chain[i] >> j) & 255;
156 |
157 | return digest;
158 | };
159 |
--------------------------------------------------------------------------------
/sample/app.js:
--------------------------------------------------------------------------------
1 | var compatibleDevices = [
2 | {
3 | deviceName: 'ACR122U USB NFC Reader',
4 | productId: 0x2200,
5 | vendorId: 0x072f,
6 | thumbnailURL: chrome.runtime.getURL('images/acr122u.png')
7 | },
8 | {
9 | deviceName: 'SCL3711 Contactless USB Smart Card Reader',
10 | productId: 0x5591,
11 | vendorId: 0x04e6,
12 | thumbnailURL: chrome.runtime.getURL('images/scl3711.png')
13 | }
14 | ]
15 |
16 | var device = null;
17 |
18 | function log(message, object) {
19 | var logArea = document.querySelector('.logs');
20 | var pre = document.createElement('pre');
21 | pre.textContent = message;
22 | if (object)
23 | pre.textContent += ': ' + JSON.stringify(object, null, 2) + '\n';
24 | logArea.appendChild(pre);
25 | logArea.scrollTop = logArea.scrollHeight;
26 | document.querySelector('#logContainer').classList.remove('small');
27 | }
28 |
29 | function handleDeviceTimeout(func, args) {
30 | var timeoutInMs = 1000;
31 | var hasTags = false;
32 | setTimeout(function() {
33 | if (!hasTags) {
34 | log('Timeout! No tag detected');
35 | }
36 | }, timeoutInMs);
37 | var args = args || [];
38 | args = args.concat([function() { hasTags = true; }]);
39 | func.apply(this, args);
40 | }
41 |
42 | function onReadNdefTagButtonClicked() {
43 | handleDeviceTimeout(readNdefTag);
44 | }
45 |
46 | function readNdefTag(callback) {
47 | chrome.nfc.read(device, {}, function(type, ndef) {
48 | log('Found ' + ndef.ndef.length + ' NFC Tag(s)');
49 | for (var i = 0; i < ndef.ndef.length; i++)
50 | log('NFC Tag', ndef.ndef[i]);
51 | callback();
52 | });
53 | }
54 |
55 | function onReadMifareTagButtonClicked() {
56 | handleDeviceTimeout(readMifareTag);
57 | }
58 |
59 | function readMifareTag(callback) {
60 | var blockNumber = 0; // starting logic block number.
61 | var blocksCount = 2; // logic block counts.
62 | chrome.nfc.read_logic(device, blockNumber, blocksCount, function(rc, data) {
63 | log('Mifare Classic Tag', UTIL_BytesToHex(data));
64 | callback();
65 | });
66 | }
67 |
68 | function onWriteNdefTagButtonClicked() {
69 | var ndefType = document.querySelector('#write-ndef-type').value;
70 | var ndefValue = document.querySelector('#write-ndef-value').value;
71 | handleDeviceTimeout(writeNdefTag, [ndefType, ndefValue]);
72 | }
73 |
74 | function writeNdefTag(ndefType, ndefValue, callback) {
75 | var ndef = {};
76 | ndef[ndefType] = ndefValue;
77 | chrome.nfc.write(device, {"ndef": [ndef]}, function(rc) {
78 | if (!rc) {
79 | log('NFC Tag written!');
80 | } else {
81 | log('NFC Tag write operation failed', rc);
82 | }
83 | callback();
84 | });
85 | }
86 |
87 | function onWriteMifareTagButtonClicked() {
88 | try {
89 | var mifareData = JSON.parse(document.querySelector('#mifare-data').value);
90 | handleDeviceTimeout(writeMifareTag, [mifareData]);
91 | }
92 | catch(e) {
93 | log('Error', 'Mifare Data is not an Array.');
94 | }
95 | }
96 |
97 | function writeMifareTag(mifareData, callback) {
98 | var data = new Uint8Array(mifareData);
99 | var blockNumber = 0; // starting logic block number.
100 | chrome.nfc.write_logic(device, 0, data, function(rc) {
101 | if (!rc) {
102 | log('Mifare Tag written!');
103 | } else {
104 | log('Mifare Tag write operation failed', rc);
105 | }
106 | callback();
107 | });
108 | }
109 |
110 | function onEmulateTagButtonClicked() {
111 | var ndefType = document.querySelector('#emulate-ndef-type').value;
112 | var ndefValue = document.querySelector('#emulate-ndef-value').value;
113 | handleDeviceTimeout(emulateTag, [ndefType, ndefValue]);
114 | }
115 |
116 | function emulateTag(ndefType, ndefValue, callback) {
117 | var ndef = {};
118 | ndef[ndefType] = ndefValue;
119 | chrome.nfc.emulate_tag(device, {"ndef": [ndef]}, function(rc) {
120 | if (!rc) {
121 | log('NFC Tag emulated!');
122 | } else {
123 | log('NFC Tag emulate operation failed', rc);
124 | }
125 | callback();
126 | });
127 | }
128 |
129 | function showDeviceInfo() {
130 | var deviceInfo = null;
131 | for (var i = 0; i < compatibleDevices.length; i++)
132 | if (device.productId === compatibleDevices[i].productId &&
133 | device.vendorId === compatibleDevices[i].vendorId)
134 | deviceInfo = compatibleDevices[i];
135 |
136 | if (!deviceInfo)
137 | return;
138 |
139 | var thumbnail = document.querySelector('#device-thumbnail');
140 | thumbnail.src = deviceInfo.thumbnailURL;
141 | thumbnail.classList.remove('hidden');
142 |
143 | var deviceName = document.querySelector('#device-name');
144 | deviceName.textContent = deviceInfo.deviceName;
145 |
146 | var productId = document.querySelector('#device-product-id');
147 | productId.textContent = deviceInfo.productId;
148 |
149 | var vendorId = document.querySelector('#device-vendor-id');
150 | vendorId.textContent = deviceInfo.vendorId;
151 |
152 | $('a[href="#device-info"]').tab('show');
153 | }
154 |
155 | function enumerateDevices() {
156 | chrome.nfc.findDevices(function(devices) {
157 | device = devices[0];
158 | showDeviceInfo();
159 | });
160 | }
161 |
162 | enumerateDevices();
163 |
164 | document.querySelector('#read-ndef pre').textContent = readNdefTag.toString();
165 | document.querySelector('#read-ndef button').addEventListener('click', onReadNdefTagButtonClicked);
166 |
167 | document.querySelector('#read-mifare pre').textContent = readMifareTag.toString();
168 | document.querySelector('#read-mifare button').addEventListener('click', onReadMifareTagButtonClicked);
169 |
170 | document.querySelector('#write-ndef pre').textContent = writeNdefTag.toString();
171 | document.querySelector('#write-ndef button').addEventListener('click', onWriteNdefTagButtonClicked);
172 |
173 | document.querySelector('#write-mifare pre').textContent = writeMifareTag.toString();
174 | document.querySelector('#write-mifare button').addEventListener('click', onWriteMifareTagButtonClicked);
175 |
176 | document.querySelector('#emulate pre').textContent = emulateTag.toString();
177 | document.querySelector('#emulate button').addEventListener('click', onEmulateTagButtonClicked);
178 |
179 | $('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
180 | document.querySelector('#logContainer').classList.add('small');
181 | });
182 |
183 | document.querySelector('.drawer').addEventListener('click', function(e) {
184 | document.querySelector('#logContainer').classList.toggle('small');
185 | });
186 |
--------------------------------------------------------------------------------
/src/devmanager.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 Google Inc. All rights reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 |
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | /**
17 | * @fileoverview USB device manager.
18 | *
19 | * +-----------------+
20 | * | Reader-specific |
21 | * | driver, like | The "who" in the open() function.
22 | * | scl3711.js |
23 | * +-----------------+ For low level driver, this is "client".
24 | * |
25 | * v
26 | * +-----------------+
27 | * | dev_manager: |
28 | * | open and enum |
29 | * | low level devs |
30 | * +-----------------+
31 | * | |
32 | * v v
33 | * +-------+ +-------+ The "which" in the open() function.
34 | * |llSCL37| |llSCL37|
35 | * | | | | Low level USB driver.
36 | * | | | | each maps to a physical device instance.
37 | * | | | | handling Tx/Rx queues.
38 | * +-------+ +-------+
39 | *
40 | */
41 |
42 | 'use strict';
43 |
44 |
45 | // List of enumerated usb devices.
46 | function devManager() {
47 | this.devs = []; // array storing the low level device.
48 | this.enumerators = []; // array storing the pending callers of enumerate().
49 | }
50 |
51 | // Remove a device from devs[] list.
52 | devManager.prototype.dropDevice = function(dev) {
53 | var tmp = this.devs;
54 | this.devs = [];
55 |
56 | var present = false;
57 | for (var i = 0; i < tmp.length; ++i) {
58 | if (tmp[i] !== dev) {
59 | this.devs.push(tmp[i]);
60 | } else {
61 | present = true;
62 | }
63 | }
64 | if (!present) return; // Done.
65 |
66 | if (dev.dev) {
67 | chrome.usb.releaseInterface(dev.dev, 0,
68 | function() { console.log(UTIL_fmt('released')); });
69 | chrome.usb.closeDevice(dev.dev,
70 | function() { console.log(UTIL_fmt('closed')); });
71 | dev.dev = null;
72 | }
73 |
74 | console.log(this.devs.length + ' devices remaining');
75 | };
76 |
77 | // Close all enumerated devices.
78 | devManager.prototype.closeAll = function(cb) {
79 |
80 | console.debug("devManager.closeAll() is called");
81 |
82 | // First close and stop talking to any device we already
83 | // have enumerated.
84 | var d = this.devs.slice(0);
85 | for (var i = 0; i < d.length; ++i) {
86 | d[i].close();
87 | }
88 |
89 | if (cb) {
90 | cb();
91 | }
92 | };
93 |
94 | // When an app needs a device, it must claim before use (so that kernel
95 | // can handle the lock).
96 | devManager.prototype.enumerate = function(cb) {
97 | var self = this;
98 |
99 | function enumerated(d, acr122) {
100 | var nDevice = 0;
101 |
102 | if (d && d.length != 0) {
103 | console.log(UTIL_fmt('Enumerated ' + d.length + ' devices'));
104 | console.log(d);
105 | nDevice = d.length;
106 | } else {
107 | if (d) {
108 | console.log('No devices found');
109 | } else {
110 | /* TODO(yjlou): Review this case later (d==undefined).
111 | * Is this real lacking permission.
112 | */
113 | console.log('Lacking permission?');
114 | do {
115 | (function(cb) {
116 | if (cb) window.setTimeout(function() { cb(-666); }, 0);
117 | })(self.enumerators.shift());
118 | } while (self.enumerators.length);
119 | return;
120 | }
121 | }
122 |
123 | // Found multiple devices. Create a low level SCL3711 per device.
124 | for (var i = 0; i < nDevice; ++i) {
125 | (function(dev, i) {
126 | window.setTimeout(function() {
127 | chrome.usb.claimInterface(dev, 0, function(result) {
128 | console.log(UTIL_fmt('claimed'));
129 | console.log(dev);
130 |
131 | // Push the new low level device to the devs[].
132 | self.devs.push(new llSCL3711(dev, acr122));
133 |
134 | // Only callback after the last device is claimed.
135 | if (i == (nDevice - 1)) {
136 | var u8 = new Uint8Array(4);
137 | u8[0] = nDevice >> 24;
138 | u8[1] = nDevice >> 16;
139 | u8[2] = nDevice >> 8;
140 | u8[3] = nDevice;
141 |
142 | // Notify all enumerators.
143 | while (self.enumerators.length) {
144 | (function(cb) {
145 | window.setTimeout(function() { if (cb) cb(0, u8); }, 20);
146 | })(self.enumerators.shift());
147 | }
148 | }
149 | });
150 | }, 0);
151 | })(d[i], i);
152 | }
153 | };
154 | /* end of enumerated() */
155 |
156 | if (this.devs.length != 0) {
157 | // Already have devices. Report number right away.
158 | // TODO(yjlou): The new plugged-in NFC reader may not be detected after
159 | // the first time enumerate() is called.
160 | var u8 = new Uint8Array(4);
161 | u8[0] = this.devs.length >> 24;
162 | u8[1] = this.devs.length >> 16;
163 | u8[2] = this.devs.length >> 8;
164 | u8[3] = this.devs.length;
165 | if (cb) cb(0, u8);
166 | } else {
167 | var first = this.enumerators.length == 0;
168 |
169 | // Queue callback.
170 | this.enumerators.push(cb);
171 |
172 | if (first) {
173 | // Only first requester calls actual low level.
174 | window.setTimeout(function() {
175 | chrome.usb.findDevices({'vendorId': 0x04e6, 'productId': 0x5591},
176 | function (d) {
177 | if (d && d.length != 0) {
178 | enumerated(d, false);
179 | } else {
180 | chrome.usb.findDevices(
181 | {'vendorId': 0x072f, 'productId': 0x2200},
182 | function (d) {
183 | if (d && d.length != 0) {
184 | enumerated(d, true);
185 | }
186 | });
187 | }
188 | });
189 | }, 0);
190 | }
191 | }
192 | };
193 |
194 | devManager.prototype.open = function(which, who, cb) {
195 | var self = this;
196 | // Make sure we have enumerated devices.
197 | this.enumerate(function() {
198 | var dev = self.devs[which];
199 | if (dev) dev.registerClient(who);
200 | if (cb) { cb(dev || null); }
201 | });
202 | };
203 |
204 | devManager.prototype.close = function(singledev, who) {
205 | // De-register client from all known devices,
206 | // since the client might have opened them implicitly w/ enumerate().
207 | // This will thus release any device without active clients.
208 | var alldevs = this.devs;
209 | for (var i = 0; i < alldevs.length; ++i) {
210 | var dev = alldevs[i];
211 | var nremaining = dev.deregisterClient(who);
212 | // TODO: uncomment when Chrome stabilizes.
213 | /*
214 | if (nremaining == 0) {
215 | // This device has no active clients remaining.
216 | // Close it so libusb releases its claim and other processes
217 | // can try attach to the device.
218 | this.dropDevice(dev);
219 | }
220 | */
221 | }
222 | };
223 |
224 | // For console interaction.
225 | // rc - a number.
226 | // data - an ArrayBuffer.
227 | var defaultCallback = function(rc, data) {
228 | var msg = 'defaultCallback('+rc;
229 | if (data) msg += ', ' + UTIL_BytesToHex(new Uint8Array(data));
230 | msg += ')';
231 | console.log(UTIL_fmt(msg));
232 | };
233 |
234 |
235 | // Singleton tracking available devices.
236 | var dev_manager = new devManager();
237 |
238 |
--------------------------------------------------------------------------------
/src/nfc.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 Google Inc. All rights reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 |
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | /**
17 | * @fileoverview chrome.nfc
18 | */
19 |
20 | 'use strict';
21 |
22 | function NFC() {
23 | var self = this;
24 |
25 | // private functions
26 | function construct_ndef_obj(ndef_array) {
27 | var ndef_obj = new NDEF();
28 |
29 | for (var i = 0; i < ndef_array.length; i++) {
30 | ndef_obj.add(ndef_array[i]);
31 | }
32 |
33 | return ndef_obj;
34 | }
35 |
36 | function wait_for_passive_target(device, cb, timeout) {
37 | if (timeout == undefined) timeout = 9999999999;
38 |
39 | device.wait_for_passive_target(timeout, function(rc, tag_type, tag_id) {
40 | if (rc) {
41 | console.log("NFC.wait_for_passive_target() = " + rc);
42 | cb(rc);
43 | return rc;
44 | }
45 | console.log("[DEBUG] nfc.wait_for_passive_target: " + tag_type + " with ID: " + UTIL_BytesToHex(new Uint8Array(tag_id)));
46 | cb(rc, tag_type, tag_id);
47 | });
48 | }
49 |
50 | var pub = {
51 | /*
52 | * This function is to get use-able NFC device(s).
53 | *
54 | * TODO: Currently, this function returns at most 1 device.
55 | *
56 | * cb(devices) is called after enumeration. 'devices' is an array of all
57 | * found devices. It is an empty array if no NFC device is found.
58 | */
59 | "findDevices": function(cb) {
60 | var device = new usbSCL3711();
61 | window.setTimeout(function() {
62 | device.open(0, function(rc) {
63 | if (rc) {
64 | console.log("NFC.device.open() = " + rc);
65 | cb([]);
66 | return rc;
67 | }
68 | // cache device info
69 | device.vendorId = device.dev.dev.vendorId;
70 | device.productId = device.dev.dev.productId;
71 |
72 | cb([device]);
73 | }, function() {
74 | console.debug("device.onclose() is called.");
75 | });
76 | }, 1000);
77 | },
78 |
79 | /*
80 | * Read a tag.
81 | *
82 | * 'options' is a dictionary with optional parameters. If a parameter is
83 | * missed, a default value is applied. Options include:
84 | *
85 | * 'timeout': timeout for this operation. Default: infinite
86 | * TODO: 'type': type of tag to listen. Default: "any" for any type.
87 | * However, currently only tag 2 and NDEF is supported.
88 | *
89 | * 'cb' lists callback functions for particular tag contents.
90 | * When called, 2 parameters are given: 'type' and 'content'.
91 | * 'type' indicates the tag type detected in the hierarchical form, ex:
92 | * "tt2.ndef". Then 'content' is the NDEF object.
93 | */
94 | "read": function(device, options, cb) {
95 | var timeout = options["timeout"];
96 | var callback = cb;
97 |
98 | wait_for_passive_target(device, function(rc, tag_type, tag_id) {
99 | var tag = new Tag(tag_type, tag_id);
100 | if (!tag) {
101 | console.log("nfc.read: unknown tag_type: " + tag_type);
102 | return;
103 | }
104 |
105 | tag.read(device, function(rc, ndef){
106 | if (rc) {
107 | console.log("NFC.read.read() = " + rc);
108 | callback(null, null); /* no type reported */
109 | return rc;
110 | }
111 | var ndef_obj = new NDEF(ndef);
112 | callback(tag_type + ".ndef", ndef_obj);
113 | });
114 | }, timeout);
115 | },
116 |
117 | /*
118 | * Read logic blocks.
119 | */
120 | "read_logic": function(device, logic_block, cnt, cb) {
121 | var callback = cb;
122 |
123 | wait_for_passive_target(device, function(rc, tag_type, tag_id) {
124 | var tag = new Tag(tag_type, tag_id);
125 | if (!tag) {
126 | console.log("nfc.read_logic: unknown tag_type: " + tag_type);
127 | return;
128 | }
129 | if (!tag.read_logic) {
130 | console.log("nfc.read: " + tag_type +
131 | " doesn't support reading logic block");
132 | return;
133 | }
134 |
135 | tag.read_logic(device, logic_block, cnt, function(data) {
136 | callback(0, data);
137 | });
138 | });
139 | },
140 |
141 | /*
142 | * Return tag_id as soon as a tag is detected.
143 | */
144 | "wait_for_tag": function(device, timeout, cb) {
145 | var callback = cb;
146 |
147 | var loop = function(timeout) {
148 |
149 | wait_for_passive_target(device, function(rc, tag_type, tag_id) {
150 | if(rc >= 0) {
151 | callback(tag_type, tag_id);
152 | }
153 | else {
154 | if(timeout > 0) {
155 | window.setTimeout(function() {
156 | loop(timeout-250)
157 | }, 250);
158 | } else
159 | callback(null, null);
160 | }
161 | });
162 | }
163 | loop(timeout);
164 | },
165 |
166 | /*
167 | * Write content to tag.
168 | *
169 | * 'content' is a dictionary containing structures to write. Supports:
170 | * ['ndef']: an array of NDEF dictionary. Will be written as a tag
171 | * type 2.
172 | *
173 | * cb(0) is called if success.
174 | * timeout is optional.
175 | */
176 | "write": function(device, content, cb, timeout) {
177 | wait_for_passive_target(device, function(rc, tag_type, tag_id) {
178 | var tag = new Tag(tag_type, tag_id);
179 | if (!tag) {
180 | console.log("nfc.write: unknown tag_type: " + tag_type);
181 | return;
182 | }
183 |
184 | var ndef_obj = construct_ndef_obj(content["ndef"]);
185 | tag.write(device, ndef_obj.compose(), function(rc) {
186 | cb(rc);
187 | });
188 | }, timeout);
189 | },
190 |
191 | /*
192 | * Write to logic blocks.
193 | *
194 | * 'logic_block': the starting logic block number.
195 | * 'data': Uint8Array. Can large than 16-byte.
196 | */
197 | "write_logic": function(device, logic_block, data, cb) {
198 | var callback = cb;
199 |
200 | wait_for_passive_target(device, function(rc, tag_type, tag_id) {
201 | var tag = new Tag(tag_type, tag_id);
202 | if (!tag) {
203 | console.log("nfc.write_logic: unknown tag_type: " + tag_type);
204 | return;
205 | }
206 |
207 | if (!tag.write_logic) {
208 | console.log("nfc.read: " + tag_type +
209 | " doesn't support reading logic block");
210 | return;
211 | }
212 |
213 | tag.write_logic(device, logic_block, data, function(rc) {
214 | callback(rc);
215 | });
216 | });
217 | },
218 |
219 |
220 | /*
221 | * Write to physical blocks.
222 | *
223 | * 'physical_block': the starting physical block number.
224 | * 'data': Uint8Array. Can large than 16-byte.
225 | */
226 | "write_physical": function(device, physical_block, key, data, cb) {
227 | var callback = cb;
228 |
229 | wait_for_passive_target(device, function(rc, tag_type, tag_id) {
230 | var tag = new Tag(tag_type, tag_id);
231 | if (!tag) {
232 | console.log("nfc.write_physical: unknown tag_type: " + tag_type);
233 | return;
234 | }
235 |
236 | if (!tag.write_physical) {
237 | console.log("nfc.read: " + tag_type +
238 | " doesn't support reading physical block");
239 | return;
240 | }
241 |
242 | tag.write_physical(device, physical_block, key, data, function(rc) {
243 | callback(rc);
244 | });
245 | });
246 | },
247 |
248 | /*
249 | * Emulate as a tag.
250 | *
251 | * 'content' is a dictionary containing structures to write. Supports:
252 | * ['ndef']: an array of NDEF dictionary. Will be written as a tag
253 | * type 2.
254 | *
255 | * cb(0) is called if success.
256 | * timeout is optional.
257 | */
258 | "emulate_tag": function(device, content, cb, timeout) {
259 | if (timeout == undefined) timeout = 9999999999;
260 | wait_for_passive_target(device, function(rc, tag_type, tag_id) {
261 | var tt2 = new TT2();
262 | var ndef_obj = construct_ndef_obj(content["ndef"]);
263 | tt2.emulate(device, ndef_obj, timeout, function(rc) {
264 | cb(rc);
265 | });
266 | }, timeout);
267 | }
268 | };
269 |
270 | return pub;
271 | }
272 |
273 | chrome.nfc = NFC();
274 |
--------------------------------------------------------------------------------
/src/tt2.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 Google Inc. All rights reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 |
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | /**
17 | * @fileoverview
18 | */
19 |
20 | 'use strict';
21 |
22 |
23 | function TT2(tag_id) {
24 | this.tag_id = new Uint8Array(tag_id);
25 | this.type_name = null; // vendor and its card name
26 |
27 | /*
28 | * TODO: detect at beginning -- if we have a reliable way to detect.
29 | * this.detect_type_name(cb);
30 | */
31 |
32 | this.lock_contorl = [];
33 | }
34 |
35 | TT2.prototype.detect_type_name = function(cb) {
36 | var self = this;
37 | var callback = cb;
38 |
39 | if (this.tag_id[0] == 0x04) {
40 | // NxP, Try to read page 0x10. If success, it is Ultralight C.
41 | this.device.read_block(0x10, function(rc, bn) {
42 | if (rc) {
43 | self.type_name = "Mifare Ultralight";
44 | } else {
45 | self.type_name = "Mifare Ultralight C";
46 | }
47 |
48 | console.debug("[DEBUG] TT2.type_name = " + self.type_name);
49 | if (callback) callback();
50 | });
51 | }
52 | }
53 |
54 |
55 | // read NFC Type 2 tag spec 1.0 for memory structure.
56 | // The callback is called with cb(NDEF Uint8Array).
57 | TT2.prototype.read = function(device, cb) {
58 | var self = this;
59 | if (!cb) cb = defaultCallback;
60 | var callback = cb;
61 |
62 | function poll_block0(rc, b0_b3) {
63 | if (rc) return callback(rc);
64 |
65 | var card = new Uint8Array(b0_b3);
66 | var data = new Uint8Array(b0_b3);
67 | var data_size = data[14] * 8; // CC2: unit is 8 bytes.
68 | var CC0 = data[12]; // CC0: 0xE1 = NDEF
69 | var CC1 = data[13]; // CC1: version of this Type 2 tag spec.
70 | var CC3 = data[15]; // CC3: b7-b4: read permission.
71 |
72 | function check_ver(cc1) {
73 | var major = (cc1 & 0xf0 ) >> 4;
74 | var minor = cc1 & 0x0f;
75 | if (major == 0x1) return true;
76 | return false;
77 | }
78 | function readable(cc3) {
79 | return (cc3 & 0xf0) == 0x00 ? true : false;
80 | }
81 |
82 | /* TODO: support protocol other than NDEF */
83 | if (CC0 != 0xE1 || !check_ver(CC1) || !readable(CC3)) {
84 | console.log("UNsupported type 2 tag: CC0=" + CC0 +
85 | ", CC1=" + CC1 +
86 | ", CC3=" + CC3);
87 | return callback(0x0777, data.buffer);
88 | }
89 |
90 | // poll data out
91 | var poll_n = Math.floor((data_size + 15) / 16);
92 | var block = 4; // data starts from block 4
93 |
94 | function poll_block(card, block, poll_n) {
95 | console.log("[DEBUG] poll_n: " + poll_n);
96 | if (--poll_n < 0) {
97 | defaultCallback("[DEBUG] got a type 2 tag:", card.buffer);
98 |
99 | /* TODO: call tlv.js instead */
100 | /* TODO: now pass NDEF only. Support non-NDEF in the future. */
101 | for (var i = 0x10; i < card.length;) {
102 | switch (card[i]) {
103 | case 0x00: /* NULL */
104 | console.debug("NULL TLV");
105 | i++;
106 | break;
107 |
108 | case 0x01: /* Lock Control TLV */
109 | console.debug("Found Lock Control TLV");
110 |
111 | /* TODO: refactor and share code with Memory Control TLV */
112 | var PageAddr = card[i + 2] >> 4;
113 | var ByteOffset = card[i + 2] & 0xf;
114 | var Size = card[i + 3];
115 | if (Size == 0) Size = 256; /* 256 bits */
116 | var BytesPerPage = Math.pow(2, card[i + 4] & 0xf);
117 | var BytesLockedPerLockBit = card[i + 4] >> 4;
118 |
119 | console.debug("Lock control: " +
120 | "BytesLockedPerLockBit=" + BytesLockedPerLockBit +
121 | ", Size=" + Size);
122 |
123 | var ByteAddr = PageAddr * BytesPerPage + ByteOffset;
124 |
125 | console.info("Lock control: ByteAddr=" + ByteAddr);
126 | console.info(" Locked bytes:");
127 | var lock_offset = 64;
128 | for (var j = 0; j < (Size + 7) / 8; j++) {
129 | var k = ByteAddr + j;
130 |
131 | if (k >= card.length) {
132 | console.warn(" card[" + k + "] haven't read out yet.");
133 | /* TODO: read out and continue the following parse */
134 | break;
135 | }
136 |
137 | var mask = card[k];
138 | console.debug(" [" + k + "]: " + mask.toString(16));
139 |
140 | if (mask & 1) console.debug("* block-locking");
141 | for (var l = 1; l < 8; l++) {
142 | if (j * 8 + l >= Size) continue;
143 |
144 | for (var s = "", m = 0;
145 | m < BytesLockedPerLockBit;
146 | lock_offset++) {
147 | s += "0x" + lock_offset.toString(16) + ", ";
148 | }
149 | if (mask & (1 << l)) console.info(" " + s);
150 | }
151 | }
152 |
153 | i += (1/*T*/ + 1/*L*/ + card[i + 1]/*len: 3*/);
154 | break;
155 |
156 | /* TODO: 0x02 -- Memory Control TLV */
157 |
158 | case 0xFE: /* Terminator */
159 | console.debug("Terminator TLV.");
160 | return;
161 |
162 | case 0x03: /* NDEF */
163 | var len = card[i + 1];
164 | if ((i + 2 + len) > card.length) {
165 | console.warn("TLV len " + len + " > card len " + card.length);
166 | }
167 | return callback(0,
168 | new Uint8Array(card.subarray(i + 2, i + 2 + len)).buffer);
169 |
170 | default:
171 | console.error("Unknown Type [" + card[i] + "]");
172 | return;
173 | }
174 | } /* end of for */
175 | }
176 |
177 | device.read_block(block, function(rc, bn) {
178 | if (rc) return callback(rc);
179 | card = UTIL_concat(card, new Uint8Array(bn));
180 | return poll_block(card, block + 4, poll_n);
181 | });
182 | }
183 | poll_block(card, block, poll_n);
184 | }
185 |
186 | device.read_block(0, poll_block0);
187 | }
188 |
189 |
190 | /* Input:
191 | * ndef - Uint8Array
192 | */
193 | TT2.prototype.compose = function(ndef) {
194 |
195 | var blen; // CC2
196 | var need_lock_control_tlv = 0;
197 |
198 | if ((ndef.length + 16 /* tt2_header */
199 | + 2 /* ndef_tlv */
200 | + 1 /* terminator_tlv */) > 64) {
201 | /*
202 | * CC bytes of MF0ICU2 (MIFARE Ultralight-C) is OTP (One Time Program).
203 | * Set to maximum available size (144 bytes).
204 | */
205 | blen = 144 / 8;
206 | need_lock_control_tlv = 1;
207 |
208 | /* TODO: check if the ndef.length + overhead are larger than card */
209 | } else {
210 | /*
211 | * CC bytes of MF0ICU1 (MIFARE Ultralight) is OTP (One Time Program).
212 | * Set to maximum available size (48 bytes).
213 | */
214 | blen = 48 / 8;
215 | }
216 |
217 | var tt2_header = new Uint8Array([
218 | 0x00, 0x00, 0x00, 0x00, /* UID0, UID1, UID2, Internal0 */
219 | 0x00, 0x00, 0x00, 0x00, /* UID3, UID4, UID5, UID6 */
220 | 0x00, 0x00, 0x00, 0x00, /* Internal1, Internal2, Lock0, Lock1 */
221 | 0xe1, 0x10, blen, 0x00 /* CC0, CC1, CC2(len), CC3 */
222 | ]);
223 |
224 | var lock_control_tlv = (need_lock_control_tlv) ?
225 | new Uint8Array([
226 | /*T*/ 0x01,
227 | /*L*/ 0x03,
228 | /*V*/ 0xA0, 0x10, 0x44 /* BytesLockedPerLockBit=4, Size=16
229 | * ByteAddr=160
230 | */
231 | ]) :
232 | new Uint8Array([]);
233 |
234 | var ndef_tlv = new Uint8Array([
235 | 0x03, ndef.length /* NDEF Message TLV */
236 | ]);
237 | var terminator_tlv = new Uint8Array([
238 | 0xfe
239 | ]);
240 | var ret = UTIL_concat(tt2_header,
241 | UTIL_concat(lock_control_tlv,
242 | UTIL_concat(ndef_tlv,
243 | UTIL_concat(new Uint8Array(ndef),
244 | terminator_tlv))));
245 | return ret;
246 | }
247 |
248 |
249 | // Input:
250 | // ndef: ArrayBuffer. Just ndef is needed. TT2 header is handled.
251 | TT2.prototype.write = function(device, ndef, cb) {
252 | if (!cb) cb = defaultCallback;
253 |
254 | var self = this;
255 | var callback = cb;
256 | var card = self.compose(new Uint8Array(ndef));
257 | var card_blknum = Math.floor((card.length + 3) / 4);
258 |
259 | /* TODO: check memory size according to CC value */
260 | if (card_blknum > (64 / 4)) {
261 | console.warn("write_tt2() card length: " + card.length +
262 | " is larger than 64 bytes. Try to write as Ultralight-C.");
263 | if (card_blknum > (192 / 4)) {
264 | console.error("write_tt2() card length: " + card.length +
265 | " is larger than 192 bytes (more than Ultralight-C" +
266 | " can provide).");
267 | return callback(0xbbb);
268 | }
269 | }
270 |
271 | function write_block(card, block_no) {
272 | if (block_no >= card_blknum) { return callback(0); }
273 |
274 | var data = card.subarray(block_no * 4, block_no * 4 + 4);
275 | if (data.length < 4) data = UTIL_concat(data,
276 | new Uint8Array(4 - data.length));
277 |
278 | device.write_block(block_no, data, function(rc) {
279 | if (rc) return callback(rc);
280 | write_block(card, block_no + 1);
281 | });
282 | }
283 |
284 | /* Start from CC* fields */
285 | write_block(card, 3);
286 | }
287 |
288 |
289 | TT2.prototype.emulate = function(device, ndef_obj, timeout, cb) {
290 | var data = this.compose(new Uint8Array(ndef_obj.compose()));
291 | return device.emulate_tag(data, timeout, cb);
292 | }
293 |
--------------------------------------------------------------------------------
/src/ndef.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 Google Inc. All rights reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 |
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | /**
17 | * @fileoverview NDEF messgae parser.
18 | */
19 |
20 | 'use strict';
21 |
22 |
23 | /* Input:
24 | * raw is either ArrayBuffer.
25 | */
26 | function NDEF(raw, cb) {
27 | this.ndef = [];
28 | this.prepending = [ /* for RTD_URI */
29 | "",
30 | "http://www.",
31 | "https://www.",
32 | "http://",
33 | "https://",
34 | "tel:",
35 | "mailto:",
36 | "ftp://anonymous:anonymous@",
37 | "ftp://ftp.",
38 | "ftps://",
39 | "sftp://",
40 | "smb://",
41 | "nfs://",
42 | "ftp://",
43 | "dav://",
44 | "news:",
45 | "telnet://",
46 | "imap:",
47 | "rtsp://",
48 | "urn:",
49 | "pop:",
50 | "sip:",
51 | "sips:",
52 | "tftp:",
53 | "btspp://",
54 | "btl2cpa://",
55 | "btgoep://",
56 | "tcpobex://",
57 | "irdaobex://",
58 | "file://",
59 | "urn:epc:id:",
60 | "urn:epc:tag:",
61 | "urn:epc:pat:",
62 | "urn:epc:raw:",
63 | "urn:epc:",
64 | "urn:nfc:"
65 | ];
66 |
67 | if (raw) {
68 | this.ndef = this.parse(raw, cb);
69 | }
70 |
71 | }
72 |
73 | /* Input:
74 | * raw is either ArrayBuffer.
75 | *
76 | * Output:
77 | * The callback function will get a JS structure for NDEF content.
78 | *
79 | * For the message format, please refer to Chapter 3 of NDEF spec.
80 | */
81 | NDEF.prototype.parse = function(raw, cb) {
82 | var i; /* index to access raw[] */
83 | var ret = [];
84 | raw = new Uint8Array(raw);
85 |
86 | for (i = 0; i < raw.length; i++) {
87 | var MB = (raw[i] & 0x80) >> 7; /* Message Begin */
88 | var ME = (raw[i] & 0x40) >> 6; /* Message End */
89 | var CF = (raw[i] & 0x20) >> 5; /* Chunk Flag */
90 | var SR = (raw[i] & 0x10) >> 4; /* Short Record */
91 | var IL = (raw[i] & 0x08) >> 3; /* ID_LENGTH field is present */
92 | var TNF = (raw[i] & 0x07) >> 0; /* Type Name Format */
93 | var type_off;
94 | var type_len = raw[i + 1];
95 | var id;
96 | var type;
97 | var payload_off = 4 + type_len;
98 | var payload_len;
99 | var payload;
100 |
101 | if (SR) {
102 | type_off = 3;
103 | payload_off = 3 + type_len;
104 | payload_len = raw[i + 2];
105 | } else {
106 | type_off = 6;
107 | payload_off = 6 + type_len;
108 | payload_len = ((raw[i + 2] * 256 + raw[i + 3]) * 256 +
109 | raw[i + 4]) * 256 + raw[i + 5];
110 | }
111 | if (IL) {
112 | type_off += 1;
113 | var id_len = raw[i + type_off - 1];
114 | payload_off += 1 + id_len;
115 | var id_off = type_off + type_len;
116 | id = raw.subarray(i + id_off, i + id_off + id_len);
117 | } else {
118 | id = null;
119 | }
120 |
121 | type = new Uint8Array(raw.subarray(i + type_off, i + type_off + type_len));
122 | payload = new Uint8Array(
123 | raw.subarray(i + payload_off, i + payload_off + payload_len));
124 |
125 | if (1) { /* for DEBUG */
126 | console.log("raw[i]: " + raw[i]);
127 | console.log("MB: " + MB);
128 | console.log("ME: " + ME);
129 | console.log("SR: " + SR);
130 | console.log("IL: " + IL);
131 | console.log("TNF: " + TNF);
132 | console.log("type_off: " + type_off);
133 | console.log("type_len: " + type_len);
134 | console.log("payload_off: " + payload_off);
135 | console.log("payload_len: " + payload_len);
136 | console.log("type: " + UTIL_BytesToHex(type));
137 | console.log("payload: " + UTIL_BytesToHex(payload));
138 | }
139 |
140 | switch (TNF) {
141 | case 0x01: /* NFC RTD - so called Well-known type */
142 | ret.push(this.parse_RTD(type[0], payload));
143 | break;
144 | case 0x02: /* MIME - RFC 2046 */
145 | ret.push(this.parse_MIME(type, payload));
146 | break;
147 | case 0x04: /* NFC RTD - so called External type */
148 | ret.push(this.parse_ExternalType(type, payload));
149 | break;
150 | default:
151 | console.error("Unsupported TNF: " + TNF);
152 | break;
153 | }
154 |
155 | i = payload_off + payload_len - 1;
156 | if (ME) break;
157 | }
158 |
159 | if (cb)
160 | cb(ret);
161 |
162 | return ret;
163 | }
164 |
165 |
166 | /* Input:
167 | * None.
168 | *
169 | * Output:
170 | * ArrayBuffer.
171 | *
172 | */
173 | NDEF.prototype.compose = function() {
174 | var out = new Uint8Array();
175 | var arr = [];
176 |
177 | for (var i = 0; i < this.ndef.length; i++) {
178 | var entry = this.ndef[i];
179 |
180 | switch (entry["type"]) {
181 | case "TEXT":
182 | case "Text":
183 | arr.push({"TNF": 1,
184 | "TYPE": new Uint8Array([0x54 /* T */]),
185 | "PAYLOAD": this.compose_RTD_TEXT(entry["lang"],
186 | entry["text"])});
187 | break;
188 | case "URI":
189 | arr.push({"TNF": 1,
190 | "TYPE": new Uint8Array([0x55 /* U */]),
191 | "PAYLOAD": this.compose_RTD_URI(entry["uri"])});
192 | break;
193 | case "MIME":
194 | arr.push({"TNF": 2,
195 | "TYPE": new Uint8Array(UTIL_StringToBytes(entry["mime_type"])),
196 | "PAYLOAD": this.compose_MIME(entry["payload"])});
197 | break;
198 | case "AAR":
199 | arr.push({"TNF": 4,
200 | "TYPE": new Uint8Array(UTIL_StringToBytes('android.com:pkg')),
201 | "PAYLOAD": this.compose_AAR(entry["aar"])});
202 | break;
203 | default:
204 | console.error("Unsupported RTD type:" + entry["type"]);
205 | break;
206 | }
207 | }
208 |
209 | for (var i = 0; i < arr.length; i++) {
210 | var flags = 0x10 | arr[i]["TNF"]; /* SR and TNF */
211 | flags |= (i == 0) ? 0x80 : 0x00; /* MB */
212 | flags |= (i == (arr.length - 1)) ? 0x40 : 0x00; /* ME */
213 |
214 | var type = arr[i]["TYPE"];
215 | var payload = arr[i]["PAYLOAD"];
216 | out = UTIL_concat(out, [flags, type.length, payload.length]);
217 | out = UTIL_concat(out, type);
218 | out = UTIL_concat(out, payload);
219 | }
220 |
221 | return out.buffer;
222 | }
223 |
224 |
225 | /* Input:
226 | * A dictionary, with "type":
227 | * "Text": RTD Text. Require: "encoding", "lang" and "text".
228 | * "URI": RTD URI. Require: "uri".
229 | * "MIME": RFC 2046 media types. Require: "mime_type" and "payload".
230 | * "AAR": Android Application Record. Require: "aar".
231 | *
232 | * Output:
233 | * true for success.
234 | *
235 | */
236 | NDEF.prototype.add = function(d) {
237 | // short-cut
238 | if ("uri" in d) {
239 | d["type"] = "URI";
240 | } else if ("text" in d) {
241 | d["type"] = "TEXT";
242 | } else if ("aar" in d) {
243 | d["type"] = "AAR";
244 | } else if ("payload" in d) {
245 | d["type"] = "MIME";
246 | }
247 |
248 | switch (d["type"]) {
249 | case "TEXT":
250 | case "Text":
251 | /* set default values */
252 | if (!("encoding" in d)) {
253 | d["encoding"] = "utf8";
254 | }
255 | if (!("lang" in d)) {
256 | d["lang"] = "en";
257 | }
258 |
259 | if ("text" in d) {
260 | this.ndef.push(d);
261 | return true;
262 | }
263 | break;
264 |
265 | case "URI":
266 | if ("uri" in d) {
267 | this.ndef.push(d);
268 | return true;
269 | }
270 | break;
271 |
272 | case "MIME":
273 | if (("mime_type" in d) && ("payload" in d)) {
274 | this.ndef.push(d);
275 | return true;
276 | }
277 |
278 | case "AAR":
279 | if ("aar" in d) {
280 | this.ndef.push(d);
281 | return true;
282 | }
283 | break;
284 |
285 | default:
286 | console.log("Unsupported RTD type:" + d["type"]);
287 | break;
288 | }
289 | return false;
290 | }
291 |
292 |
293 | /*
294 | * Input:
295 | * type -- a byte, see RTD Type Names
296 | * rtd -- Uint8Array.
297 | *
298 | * Output:
299 | * JS structure
300 | */
301 | NDEF.prototype.parse_RTD = function(type, rtd) {
302 | switch (type) {
303 | case 0x54: /* 'T' */
304 | return this.parse_RTD_TEXT(rtd);
305 | case 0x55: /* 'U' */
306 | return this.parse_RTD_URI(rtd);
307 | default:
308 | console.log("Unsupported RTD type: " + type);
309 | }
310 | }
311 |
312 |
313 | /*
314 | * Input:
315 | * mime_type -- Uint8Array. See RFC 2046.
316 | * payload -- Uint8Array.
317 | *
318 | * Output:
319 | * JS structure
320 | */
321 | NDEF.prototype.parse_MIME = function(mime_type, payload) {
322 | return {"type": "MIME",
323 | "mime_type": UTIL_BytesToString(mime_type),
324 | "payload": UTIL_BytesToString(payload)};
325 | }
326 |
327 |
328 | /*
329 | * Input:
330 | * mime_type and payload: string.
331 | *
332 | * Output:
333 | * rtd_text -- Uint8Array.
334 | */
335 | NDEF.prototype.compose_MIME = function(payload) {
336 | return new Uint8Array(UTIL_StringToBytes(payload));
337 | }
338 |
339 |
340 | /*
341 | * Input:
342 | * payload -- Uint8Array.
343 | *
344 | * Output:
345 | * JS structure
346 | */
347 | NDEF.prototype.parse_AAR = function(payload) {
348 | return {"type": "AAR",
349 | "payload": UTIL_BytesToString(payload)};
350 | }
351 |
352 | /*
353 | * Input:
354 | * type -- Uint8Array.
355 | * payload -- Uint8Array.
356 | *
357 | * Output:
358 | * JS structure
359 | */
360 | NDEF.prototype.parse_ExternalType = function(type, payload) {
361 | if (UTIL_BytesToString(type) == "android.com:pkg")
362 | return this.parse_AAR(payload);
363 | else
364 | return {"type": type,
365 | "payload": UTIL_BytesToString(payload)};
366 | }
367 |
368 |
369 | /*
370 | * Input:
371 | * payload: string.
372 | *
373 | * Output:
374 | * Uint8Array.
375 | */
376 | NDEF.prototype.compose_AAR = function(payload) {
377 | return new Uint8Array(UTIL_StringToBytes(payload));
378 | }
379 |
380 |
381 | /*
382 | * Input:
383 | * rtd_text -- Uint8Array.
384 | *
385 | * Output:
386 | * JS structure
387 | */
388 | NDEF.prototype.parse_RTD_TEXT = function(rtd_text) {
389 | var utf16 = (rtd_text[0] & 0x80) >> 7;
390 | var lang_len = (rtd_text[0] & 0x3f);
391 | var lang = rtd_text.subarray(1, 1 + lang_len);
392 | var text = rtd_text.subarray(1 + lang_len, rtd_text.length);
393 |
394 | return {"type": "Text",
395 | "encoding": utf16 ? "utf16" : "utf8",
396 | "lang": UTIL_BytesToString(lang),
397 | "text": UTIL_BytesToString(text)};
398 | }
399 |
400 |
401 | /*
402 | * Input:
403 | * Language and text (assume UTF-8 encoded).
404 | *
405 | * Output:
406 | * rtd_text -- Uint8Array.
407 | */
408 | NDEF.prototype.compose_RTD_TEXT = function(lang, text) {
409 | var l = lang.length;
410 | l = (l > 0x3f) ? 0x3f : l;
411 | return new Uint8Array([l].concat(
412 | UTIL_StringToBytes(lang.substring(0, l))).concat(
413 | UTIL_StringToBytes(text)));
414 | }
415 |
416 |
417 | /*
418 | * Input:
419 | * rtd_uri -- Uint8Array.
420 | *
421 | * Output:
422 | * JS structure
423 | */
424 | NDEF.prototype.parse_RTD_URI = function(rtd_uri) {
425 | return {"type": "URI",
426 | "uri": this.prepending[rtd_uri[0]] +
427 | UTIL_BytesToString(rtd_uri.subarray(1, rtd_uri.length))};
428 | }
429 |
430 | /*
431 | * Input:
432 | * Thr URI to compose (assume UTF-8).
433 | *
434 | * Output:
435 | * Uint8Array.
436 | */
437 | NDEF.prototype.compose_RTD_URI = function(uri) {
438 | var longest = -1;
439 | var longest_i;
440 | for (var i = 0; i < this.prepending.length; i++) {
441 | if (uri.substring(0, this.prepending[i].length) == this.prepending[i]) {
442 | if (this.prepending[i].length > longest) {
443 | longest_i = i;
444 | longest = this.prepending[i].length;
445 | }
446 | }
447 | }
448 | // assume at least longest_i matches prepending[0], which is "".
449 |
450 | return new Uint8Array([longest_i].concat(
451 | UTIL_StringToBytes(uri.substring(longest))));
452 | }
453 |
454 |
--------------------------------------------------------------------------------
/src/mifare-classic.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 Google Inc. All rights reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 |
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | /**
17 | * @fileoverview Mifare Classic driver
18 | */
19 |
20 | 'use strict';
21 |
22 | // TODO: support Classic 4K
23 |
24 | /*
25 | * AN1305 - MIFARE Classic as NFC Type MIFARE Classic Tag
26 | *
27 | * +--------------------+
28 | * | Manufacturer Block | Physical Block 0
29 | * |--------------------|
30 | * | Logic block 0 | Physical Block 1 (MAD1)
31 | * Sector 0 |--------------------|
32 | * | Logic block 1 | Physical Block 2 (MAD1)
33 | * |--------------------|
34 | * | Sector Trailer | Physical Block 3
35 | * --+--------------------+
36 | * | Logic block 2 | Physical Block 4
37 | * |--------------------|
38 | * | Logic block 3 | Physical Block 5
39 | * Sector 1 |--------------------|
40 | * | Logic block 4 | Physical Block 6
41 | * |--------------------|
42 | * | Sector Trailer | Physical Block 7
43 | * --+--------------------+
44 | * | Logic block 5 | Physical Block 8
45 | * |--------------------|
46 | * | Logic block 6 | Physical Block 9
47 | * Sector 2 |--------------------|
48 | * | Logic block 7 | Physical Block 10
49 | * |--------------------|
50 | * | Sector Trailer | Physical Block 11
51 | * +--------------------+
52 | * | ... | ...
53 | *
54 | *
55 | *
56 | *
57 | *
58 | */
59 |
60 | function MifareClassic(tag_id) {
61 | this.tag_id = new Uint8Array(tag_id);
62 | this.type_name = "MIFARE Classic 1K";
63 |
64 | this.WRITE_COMMAND = 0xA0; // differ to type 2's 0xA2.
65 | }
66 |
67 | // private functions
68 |
69 | // Logic block number to sector number
70 | MifareClassic.prototype.log2sec = function(logic_blknum) {
71 | if (logic_blknum < 2) return 0;
72 | return Math.floor((logic_blknum - 2) / 3) + 1;
73 | }
74 |
75 | // Logic block number to physical block number
76 | MifareClassic.prototype.log2phy = function(logic_blknum) {
77 | if (logic_blknum < 2) return logic_blknum + 1;
78 |
79 | var sector = this.log2sec(logic_blknum);
80 | return sector * 4 + ((logic_blknum - 2) % 3);
81 | }
82 |
83 | // input: Uint8Array
84 | MifareClassic.prototype.mif_calc_crc8 = function(input) {
85 | var crc = 0xc7; // bit-swapped 0xe3
86 |
87 | for (var i = 0; i < input.length; i++) {
88 | crc = crc ^ input[i];
89 |
90 | for (var j = 0; j < 8; j++) {
91 | if (crc & 0x80)
92 | crc = (crc << 1) ^ 0x1d;
93 | else
94 | crc = crc << 1;
95 | }
96 | }
97 | return crc;
98 | }
99 |
100 | // input: Uint8Array
101 | MifareClassic.prototype.mif_calc_crc16 = function(input) {
102 | var crc = 0xc78c; // bit-swapped 0x31e3
103 | for (var i = 0; i < input.length; i++) {
104 | crc = crc ^ (input[i] << 8);
105 | for (var j = 0; j < 8; j++) {
106 | if (crc & 0x8000)
107 | crc = (crc << 1) ^ 0x1021;
108 | else
109 | crc = crc << 1;
110 | }
111 | }
112 | return crc;
113 | }
114 |
115 |
116 | /* Since the Key A is not readable so that we need to copy that from the
117 | * successfully authenticated key storage.
118 | * We keep key B all-0xff until one day we decide to use it.
119 | */
120 | MifareClassic.prototype.copy_auth_keys = function(data, dev) {
121 | for (var i = 0; i < 6; i++) {
122 | data[i] = dev.auth_key[i];
123 | }
124 | // Leave KEY B as default. TODO: don't overwrite if key B is readable.
125 | for (var i = 0; i < 6; i++) {
126 | data[i + 10] = 0xff;
127 | }
128 |
129 | return data;
130 | }
131 |
132 |
133 | MifareClassic.prototype.read_physical = function(device, phy_block, cnt, cb) {
134 | var self = this;
135 | var callback = cb;
136 | var dev = device;
137 | var readed = new Uint8Array(); // for closure
138 | var max_block = 1024 / 16; // TODO: assume Classic 1K
139 |
140 | if (cnt != null) max_block = phy_block + cnt;
141 |
142 | // Reading whole card is too long (~4secs). This function would return
143 | // a smaller max_block value if MAD is read and NDEF sectors are recognized.
144 | function fast_read(phy_block, data, max_block) {
145 | if (phy_block == 3 && data[0x39] != 0x69 ) { // personalized GBP
146 | // TODO: check CRC in MAD.
147 | var nfc_cnt;
148 | for (nfc_cnt = 0; // assume the NDEF is in the 1st sector.
149 | data[0x12 + nfc_cnt * 2 + 0] == 0x03 &&
150 | data[0x12 + nfc_cnt * 2 + 1] == 0xE1;
151 | nfc_cnt++) {};
152 | var new_num = (nfc_cnt + 1) * 4;
153 | if (new_num < max_block)
154 | return new_num;
155 | else
156 | return max_block;
157 | } else {
158 | return max_block;
159 | }
160 | }
161 |
162 | function read_next(phy_block) {
163 | var blk_no = phy_block;
164 | dev.publicAuthentication(blk_no, function(rc, data) {
165 | if (rc) return callback(rc);
166 | dev.read_block(blk_no, function(rc, bn) {
167 | if (rc) return callback(rc);
168 | var bn = new Uint8Array(bn);
169 |
170 | // copy KEY A with auth_key from device.
171 | if ((blk_no % 4) == 3) {
172 | bn = self.copy_auth_keys(bn, dev);
173 | }
174 |
175 | readed = UTIL_concat(readed, bn);
176 |
177 | max_block = fast_read(blk_no, readed, max_block);
178 | if ((blk_no + 1)>= max_block)
179 | return callback(readed);
180 | else
181 | return read_next(blk_no + 1, cb);
182 | });
183 | });
184 | }
185 | read_next(phy_block);
186 | }
187 |
188 |
189 | // The callback is called with cb(NDEF Uint8Array).
190 | MifareClassic.prototype.read = function(device, cb) {
191 | var self = this;
192 | if (!cb) cb = defaultCallback;
193 | var callback = cb;
194 | var card = new Uint8Array();
195 |
196 | self.read_physical(device, 0, null, function(data) {
197 | for(var i = 0; i < Math.ceil(data.length / 16); i++) {
198 | console.log(UTIL_fmt("[DEBUG] Sector[" + UTIL_BytesToHex([i]) + "] " +
199 | UTIL_BytesToHex(data.subarray(i * 16,
200 | i * 16 + 16))));
201 | }
202 |
203 | var GPB = data[0x39]; /* the first GPB */
204 | if (GPB == 0x69) {
205 | console.log("[DEBUG] Sector 0 is non-personalized (0x69).");
206 | } else {
207 | var DA = (GPB & 0x80) >> 7; // MAD available: 1 for yes.
208 | var MA = (GPB & 0x40) >> 6; // Multiapplication card: 1 for yes.
209 | var ADV = (GPB & 0x03) >> 0; // (MAD version code: 1 for v1, 2 for v2)
210 |
211 | // TODO: check CRC in MAD.
212 | var nfc_cnt;
213 | for (nfc_cnt = 0; // assume the NDEF is in the 1st sector.
214 | data[0x12 + nfc_cnt * 2 + 0] == 0x03 &&
215 | data[0x12 + nfc_cnt * 2 + 1] == 0xE1;
216 | nfc_cnt++) {};
217 | var tlv = new Uint8Array();
218 | for(var i = 1; i <= nfc_cnt; i++) {
219 | tlv = UTIL_concat(tlv, data.subarray(i * 0x40, i * 0x40 + 0x30));
220 | }
221 |
222 | // TODO: move to tlv.js
223 | for (var i = 0; i < tlv.length; i++) {
224 | switch (tlv[i]) {
225 | case 0x00: /* NULL */
226 | console.log("[DEBUG] NULL TLV.");
227 | break;
228 | case 0xFE: /* Terminator */
229 | console.log("[DEBUG] Terminator TLV.");
230 | return;
231 | case 0x03: /* NDEF */
232 | var len = tlv[i + 1];
233 | if ((len + 2) > tlv.length) {
234 | console.log("[WARN] Vlen:" + len + " > totla len:" + tlv.length);
235 | }
236 | return callback(0,
237 | new Uint8Array(tlv.subarray(i + 2, i + 2 + len)).buffer);
238 | /* TODO: now pass NDEF only. Support non-NDEF in the future. */
239 | // i += len + 1;
240 | default:
241 | console.log("[ERROR] Unsupported TLV: " + UTIL_BytesToHex(tlv[0]));
242 | return;
243 | }
244 | }
245 | }
246 | });
247 | }
248 |
249 |
250 | MifareClassic.prototype.read_logic = function(device, logic_block, cnt, cb) {
251 | var self = this;
252 | var callback = cb;
253 | var card = new Uint8Array();
254 |
255 | function next_logic(logic_block, cnt) {
256 | var blk_no = logic_block;
257 | var count = cnt;
258 | if (count <= 0) return callback(card);
259 | self.read_physical(device, self.log2phy(logic_block), 1, function(data) {
260 | card = UTIL_concat(card, data);
261 | next_logic(blk_no + 1, count - 1);
262 | });
263 | }
264 | next_logic(logic_block, cnt);
265 | }
266 |
267 |
268 | // TODO: support multiple data set
269 | /* Input:
270 | * ndef - Uint8Array
271 | *
272 | * Output:
273 | * Whole tag image.
274 | */
275 | MifareClassic.prototype.compose = function(ndef) {
276 | var self = this;
277 |
278 | /* ====== Build up TLV blocks first ====== */
279 | var ndef_tlv = new Uint8Array([
280 | 0x03, ndef.length /* NDEF Message TLV */
281 | ]);
282 | var terminator_tlv = new Uint8Array([
283 | 0xfe
284 | ]);
285 | var TLV = UTIL_concat(ndef_tlv,
286 | UTIL_concat(new Uint8Array(ndef),
287 | terminator_tlv));
288 |
289 | /* frag into sectors */
290 | var TLV_sector_num = Math.ceil(TLV.length / 0x30);
291 | var TLV_blocks = new Uint8Array();
292 | for (var i = 0; i < TLV_sector_num; i++) {
293 | TLV_blocks = UTIL_concat(TLV_blocks,
294 | TLV.subarray(i * 0x30, (i + 1) * 0x30));
295 |
296 | var padding;
297 | if ((i + 1) == TLV_sector_num) { // last sector
298 | padding = new Uint8Array(0x30 - (TLV.length % 0x30));
299 | } else {
300 | padding = new Uint8Array(0);
301 | }
302 | TLV_blocks = UTIL_concat(TLV_blocks, padding);
303 | TLV_blocks = UTIL_concat(TLV_blocks, new Uint8Array([ // Sector Trailer
304 | 0xd3, 0xf7, 0xd3, 0xf7, 0xd3, 0xf7, // NFC pub key
305 | 0x7f, 0x07, 0x88, 0x40, // access bits, GPB
306 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // KEY B
307 | ]));
308 | }
309 |
310 | /* ====== Build up MAD ====== */
311 | var classic_header = new Uint8Array([
312 | /* Manufacturer Block */
313 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
314 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
315 |
316 | /* MAD1 */
317 | 0x00, 0x00, 0x03, 0xe1, // CRC, info, AID 1
318 | 0x00, 0x00, 0x00, 0x00, // AID 2, AID 3
319 | 0x00, 0x00, 0x00, 0x00, // AID 4, AID 5
320 | 0x00, 0x00, 0x00, 0x00, // AID 6, AID 7
321 | 0x00, 0x00, 0x00, 0x00, // AID 8, AID 9
322 | 0x00, 0x00, 0x00, 0x00, // AID a, AID b
323 | 0x00, 0x00, 0x00, 0x00, // AID c, AID d
324 | 0x00, 0x00, 0x00, 0x00, // AID e, AID f
325 |
326 | /* Sector Trailer */
327 | 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, // MAD access key
328 | 0x78, 0x77, 0x88, 0xc1, // access bits, GPB
329 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // KEY B
330 | ]);
331 |
332 | for (var i = 0; i < TLV_sector_num; i++) {
333 | classic_header[0x10 + (i + 1) * 2 + 0] = 0x03;
334 | classic_header[0x10 + (i + 1) * 2 + 1] = 0xe1;
335 | }
336 | classic_header[0x10] =
337 | self.mif_calc_crc8(classic_header.subarray(0x11, 0x30));
338 |
339 | var ret = UTIL_concat(classic_header, TLV_blocks);
340 | return ret;
341 | }
342 |
343 |
344 | // Input:
345 | // block_no: starting physical block number
346 | // data: Uint8Array of data to write. Reminding data will be write to
347 | // next block continously.
348 | MifareClassic.prototype.write_physical = function(device, block_no, key,
349 | all_data, cb) {
350 | var dev = device;
351 | var blk_no = block_no; // for closure
352 | var data = all_data;
353 | var callback = cb;
354 | var self = this;
355 |
356 | if (data.length == 0) { return callback(0); }
357 | if (data.length < 16) {
358 | // Pad to 16 bytes
359 | data = UTIL_concat(data, new Uint8Array(16 - data.length));
360 | }
361 |
362 | function authenticationCallback (rc, dummy) {
363 | if (rc) return callback(rc);
364 |
365 | var block_data = data.subarray(0, 16);
366 | dev.write_block(blk_no, block_data, function(rc) {
367 | if (rc) return callback(rc);
368 | self.write_physical(dev, blk_no + 1, key, data.subarray(16), callback);
369 | }, self.WRITE_COMMAND);
370 | }
371 | if (key == null)
372 | dev.publicAuthentication(blk_no, authenticationCallback);
373 | else
374 | dev.privateAuthentication(blk_no, key, authenticationCallback);
375 | }
376 |
377 |
378 | // Input:
379 | // ndef: ArrayBuffer. Just ndef is needed. Classic header is handled.
380 | MifareClassic.prototype.write = function(device, ndef, cb) {
381 | var self = this;
382 | if (!cb) cb = defaultCallback;
383 | var callback = cb;
384 | var card = self.compose(new Uint8Array(ndef));
385 | var dev = device;
386 |
387 | var max_block = Math.ceil(card.length / 16);
388 |
389 | if (max_block > (1024 / 16)) {
390 | console.log("write Classic() card is too big (max: 1024 bytes): " +
391 | card.length);
392 | return callback(0xbbb);
393 | }
394 |
395 | /* Start from MAD */
396 | self.write_physical(dev, 1, null, card.subarray(16), callback);
397 | }
398 |
399 |
400 | // Input:
401 | // logic_block: logic block number
402 | // data: Uint8Array of data to write. Reminding data will be write to
403 | // next block continously.
404 | //
405 | // Note that the GPB will be written to no-MAD (MA=0) to fully access
406 | // all data blocks.
407 | MifareClassic.prototype.write_logic = function(device, logic_block,
408 | all_data, cb) {
409 | var self = this;
410 | var callback = cb;
411 |
412 |
413 | function write_next(device, logic_block, all_data) {
414 | var dev = device;
415 | var blk_no = logic_block;
416 | var data = all_data;
417 |
418 | if (data.length == 0) return callback(0);
419 |
420 | self.write_physical(dev, self.log2phy(blk_no), null,
421 | data.subarray(0, 16),
422 | function(rc) {
423 | if (rc) return callback(rc);
424 |
425 | // update the corresponding GPB to 0x00.
426 | var gpb_phy = self.log2sec(blk_no) * 4 + 3;
427 | dev.read_block(gpb_phy, function(rc, gpb_data) {
428 | if (rc) return callback(rc);
429 | var gpb_data = new Uint8Array(gpb_data);
430 | gpb_data = self.copy_auth_keys(gpb_data, dev);
431 |
432 | if (gpb_phy == 3)
433 | gpb_data[0x9] = 0xc1; // the first GPB: DA=MA=1, ADV=1
434 | else
435 | gpb_data[0x9] = 0x40; // non-first GPB: MA=1.
436 |
437 | dev.write_block(gpb_phy, gpb_data, function(rc) {
438 | // move to next block
439 | blk_no = blk_no + 1;
440 | data = data.subarray(16);
441 | return write_next(dev, blk_no, data);
442 | }, self.WRITE_COMMAND);
443 | });
444 | });
445 | }
446 | write_next(device, logic_block, all_data);
447 | }
448 |
449 |
450 | MifareClassic.prototype.emulate = function(device, ndef_obj, timeout, cb) {
451 | /* TODO: still presents as TT2 */
452 | var data = this.compose(new Uint8Array(ndef_obj.compose()));
453 | return device.emulate_tag(data, timeout, cb);
454 | }
455 |
--------------------------------------------------------------------------------
/sample/lib/bootstrap.min.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * Bootstrap.js by @fat & @mdo
3 | * Copyright 2013 Twitter, Inc.
4 | * http://www.apache.org/licenses/LICENSE-2.0.txt
5 | */
6 | !function(e){"use strict";e(function(){e.support.transition=function(){var e=function(){var e=document.createElement("bootstrap"),t={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"},n;for(n in t)if(e.style[n]!==undefined)return t[n]}();return e&&{end:e}}()})}(window.jQuery),!function(e){"use strict";var t='[data-dismiss="alert"]',n=function(n){e(n).on("click",t,this.close)};n.prototype.close=function(t){function s(){i.trigger("closed").remove()}var n=e(this),r=n.attr("data-target"),i;r||(r=n.attr("href"),r=r&&r.replace(/.*(?=#[^\s]*$)/,"")),i=e(r),t&&t.preventDefault(),i.length||(i=n.hasClass("alert")?n:n.parent()),i.trigger(t=e.Event("close"));if(t.isDefaultPrevented())return;i.removeClass("in"),e.support.transition&&i.hasClass("fade")?i.on(e.support.transition.end,s):s()};var r=e.fn.alert;e.fn.alert=function(t){return this.each(function(){var r=e(this),i=r.data("alert");i||r.data("alert",i=new n(this)),typeof t=="string"&&i[t].call(r)})},e.fn.alert.Constructor=n,e.fn.alert.noConflict=function(){return e.fn.alert=r,this},e(document).on("click.alert.data-api",t,n.prototype.close)}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.button.defaults,n)};t.prototype.setState=function(e){var t="disabled",n=this.$element,r=n.data(),i=n.is("input")?"val":"html";e+="Text",r.resetText||n.data("resetText",n[i]()),n[i](r[e]||this.options[e]),setTimeout(function(){e=="loadingText"?n.addClass(t).attr(t,t):n.removeClass(t).removeAttr(t)},0)},t.prototype.toggle=function(){var e=this.$element.closest('[data-toggle="buttons-radio"]');e&&e.find(".active").removeClass("active"),this.$element.toggleClass("active")};var n=e.fn.button;e.fn.button=function(n){return this.each(function(){var r=e(this),i=r.data("button"),s=typeof n=="object"&&n;i||r.data("button",i=new t(this,s)),n=="toggle"?i.toggle():n&&i.setState(n)})},e.fn.button.defaults={loadingText:"loading..."},e.fn.button.Constructor=t,e.fn.button.noConflict=function(){return e.fn.button=n,this},e(document).on("click.button.data-api","[data-toggle^=button]",function(t){var n=e(t.target);n.hasClass("btn")||(n=n.closest(".btn")),n.button("toggle")})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.$indicators=this.$element.find(".carousel-indicators"),this.options=n,this.options.pause=="hover"&&this.$element.on("mouseenter",e.proxy(this.pause,this)).on("mouseleave",e.proxy(this.cycle,this))};t.prototype={cycle:function(t){return t||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(e.proxy(this.next,this),this.options.interval)),this},getActiveIndex:function(){return this.$active=this.$element.find(".item.active"),this.$items=this.$active.parent().children(),this.$items.index(this.$active)},to:function(t){var n=this.getActiveIndex(),r=this;if(t>this.$items.length-1||t<0)return;return this.sliding?this.$element.one("slid",function(){r.to(t)}):n==t?this.pause().cycle():this.slide(t>n?"next":"prev",e(this.$items[t]))},pause:function(t){return t||(this.paused=!0),this.$element.find(".next, .prev").length&&e.support.transition.end&&(this.$element.trigger(e.support.transition.end),this.cycle(!0)),clearInterval(this.interval),this.interval=null,this},next:function(){if(this.sliding)return;return this.slide("next")},prev:function(){if(this.sliding)return;return this.slide("prev")},slide:function(t,n){var r=this.$element.find(".item.active"),i=n||r[t](),s=this.interval,o=t=="next"?"left":"right",u=t=="next"?"first":"last",a=this,f;this.sliding=!0,s&&this.pause(),i=i.length?i:this.$element.find(".item")[u](),f=e.Event("slide",{relatedTarget:i[0],direction:o});if(i.hasClass("active"))return;this.$indicators.length&&(this.$indicators.find(".active").removeClass("active"),this.$element.one("slid",function(){var t=e(a.$indicators.children()[a.getActiveIndex()]);t&&t.addClass("active")}));if(e.support.transition&&this.$element.hasClass("slide")){this.$element.trigger(f);if(f.isDefaultPrevented())return;i.addClass(t),i[0].offsetWidth,r.addClass(o),i.addClass(o),this.$element.one(e.support.transition.end,function(){i.removeClass([t,o].join(" ")).addClass("active"),r.removeClass(["active",o].join(" ")),a.sliding=!1,setTimeout(function(){a.$element.trigger("slid")},0)})}else{this.$element.trigger(f);if(f.isDefaultPrevented())return;r.removeClass("active"),i.addClass("active"),this.sliding=!1,this.$element.trigger("slid")}return s&&this.cycle(),this}};var n=e.fn.carousel;e.fn.carousel=function(n){return this.each(function(){var r=e(this),i=r.data("carousel"),s=e.extend({},e.fn.carousel.defaults,typeof n=="object"&&n),o=typeof n=="string"?n:s.slide;i||r.data("carousel",i=new t(this,s)),typeof n=="number"?i.to(n):o?i[o]():s.interval&&i.pause().cycle()})},e.fn.carousel.defaults={interval:5e3,pause:"hover"},e.fn.carousel.Constructor=t,e.fn.carousel.noConflict=function(){return e.fn.carousel=n,this},e(document).on("click.carousel.data-api","[data-slide], [data-slide-to]",function(t){var n=e(this),r,i=e(n.attr("data-target")||(r=n.attr("href"))&&r.replace(/.*(?=#[^\s]+$)/,"")),s=e.extend({},i.data(),n.data()),o;i.carousel(s),(o=n.attr("data-slide-to"))&&i.data("carousel").pause().to(o).cycle(),t.preventDefault()})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.collapse.defaults,n),this.options.parent&&(this.$parent=e(this.options.parent)),this.options.toggle&&this.toggle()};t.prototype={constructor:t,dimension:function(){var e=this.$element.hasClass("width");return e?"width":"height"},show:function(){var t,n,r,i;if(this.transitioning||this.$element.hasClass("in"))return;t=this.dimension(),n=e.camelCase(["scroll",t].join("-")),r=this.$parent&&this.$parent.find("> .accordion-group > .in");if(r&&r.length){i=r.data("collapse");if(i&&i.transitioning)return;r.collapse("hide"),i||r.data("collapse",null)}this.$element[t](0),this.transition("addClass",e.Event("show"),"shown"),e.support.transition&&this.$element[t](this.$element[0][n])},hide:function(){var t;if(this.transitioning||!this.$element.hasClass("in"))return;t=this.dimension(),this.reset(this.$element[t]()),this.transition("removeClass",e.Event("hide"),"hidden"),this.$element[t](0)},reset:function(e){var t=this.dimension();return this.$element.removeClass("collapse")[t](e||"auto")[0].offsetWidth,this.$element[e!==null?"addClass":"removeClass"]("collapse"),this},transition:function(t,n,r){var i=this,s=function(){n.type=="show"&&i.reset(),i.transitioning=0,i.$element.trigger(r)};this.$element.trigger(n);if(n.isDefaultPrevented())return;this.transitioning=1,this.$element[t]("in"),e.support.transition&&this.$element.hasClass("collapse")?this.$element.one(e.support.transition.end,s):s()},toggle:function(){this[this.$element.hasClass("in")?"hide":"show"]()}};var n=e.fn.collapse;e.fn.collapse=function(n){return this.each(function(){var r=e(this),i=r.data("collapse"),s=e.extend({},e.fn.collapse.defaults,r.data(),typeof n=="object"&&n);i||r.data("collapse",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.collapse.defaults={toggle:!0},e.fn.collapse.Constructor=t,e.fn.collapse.noConflict=function(){return e.fn.collapse=n,this},e(document).on("click.collapse.data-api","[data-toggle=collapse]",function(t){var n=e(this),r,i=n.attr("data-target")||t.preventDefault()||(r=n.attr("href"))&&r.replace(/.*(?=#[^\s]+$)/,""),s=e(i).data("collapse")?"toggle":n.data();n[e(i).hasClass("in")?"addClass":"removeClass"]("collapsed"),e(i).collapse(s)})}(window.jQuery),!function(e){"use strict";function r(){e(".dropdown-backdrop").remove(),e(t).each(function(){i(e(this)).removeClass("open")})}function i(t){var n=t.attr("data-target"),r;n||(n=t.attr("href"),n=n&&/#/.test(n)&&n.replace(/.*(?=#[^\s]*$)/,"")),r=n&&e(n);if(!r||!r.length)r=t.parent();return r}var t="[data-toggle=dropdown]",n=function(t){var n=e(t).on("click.dropdown.data-api",this.toggle);e("html").on("click.dropdown.data-api",function(){n.parent().removeClass("open")})};n.prototype={constructor:n,toggle:function(t){var n=e(this),s,o;if(n.is(".disabled, :disabled"))return;return s=i(n),o=s.hasClass("open"),r(),o||("ontouchstart"in document.documentElement&&e('').insertBefore(e(this)).on("click",r),s.toggleClass("open")),n.focus(),!1},keydown:function(n){var r,s,o,u,a,f;if(!/(38|40|27)/.test(n.keyCode))return;r=e(this),n.preventDefault(),n.stopPropagation();if(r.is(".disabled, :disabled"))return;u=i(r),a=u.hasClass("open");if(!a||a&&n.keyCode==27)return n.which==27&&u.find(t).focus(),r.click();s=e("[role=menu] li:not(.divider):visible a",u);if(!s.length)return;f=s.index(s.filter(":focus")),n.keyCode==38&&f>0&&f--,n.keyCode==40&&f').appendTo(document.body),this.$backdrop.click(this.options.backdrop=="static"?e.proxy(this.$element[0].focus,this.$element[0]):e.proxy(this.hide,this)),i&&this.$backdrop[0].offsetWidth,this.$backdrop.addClass("in");if(!t)return;i?this.$backdrop.one(e.support.transition.end,t):t()}else!this.isShown&&this.$backdrop?(this.$backdrop.removeClass("in"),e.support.transition&&this.$element.hasClass("fade")?this.$backdrop.one(e.support.transition.end,t):t()):t&&t()}};var n=e.fn.modal;e.fn.modal=function(n){return this.each(function(){var r=e(this),i=r.data("modal"),s=e.extend({},e.fn.modal.defaults,r.data(),typeof n=="object"&&n);i||r.data("modal",i=new t(this,s)),typeof n=="string"?i[n]():s.show&&i.show()})},e.fn.modal.defaults={backdrop:!0,keyboard:!0,show:!0},e.fn.modal.Constructor=t,e.fn.modal.noConflict=function(){return e.fn.modal=n,this},e(document).on("click.modal.data-api",'[data-toggle="modal"]',function(t){var n=e(this),r=n.attr("href"),i=e(n.attr("data-target")||r&&r.replace(/.*(?=#[^\s]+$)/,"")),s=i.data("modal")?"toggle":e.extend({remote:!/#/.test(r)&&r},i.data(),n.data());t.preventDefault(),i.modal(s).one("hide",function(){n.focus()})})}(window.jQuery),!function(e){"use strict";var t=function(e,t){this.init("tooltip",e,t)};t.prototype={constructor:t,init:function(t,n,r){var i,s,o,u,a;this.type=t,this.$element=e(n),this.options=this.getOptions(r),this.enabled=!0,o=this.options.trigger.split(" ");for(a=o.length;a--;)u=o[a],u=="click"?this.$element.on("click."+this.type,this.options.selector,e.proxy(this.toggle,this)):u!="manual"&&(i=u=="hover"?"mouseenter":"focus",s=u=="hover"?"mouseleave":"blur",this.$element.on(i+"."+this.type,this.options.selector,e.proxy(this.enter,this)),this.$element.on(s+"."+this.type,this.options.selector,e.proxy(this.leave,this)));this.options.selector?this._options=e.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},getOptions:function(t){return t=e.extend({},e.fn[this.type].defaults,this.$element.data(),t),t.delay&&typeof t.delay=="number"&&(t.delay={show:t.delay,hide:t.delay}),t},enter:function(t){var n=e.fn[this.type].defaults,r={},i;this._options&&e.each(this._options,function(e,t){n[e]!=t&&(r[e]=t)},this),i=e(t.currentTarget)[this.type](r).data(this.type);if(!i.options.delay||!i.options.delay.show)return i.show();clearTimeout(this.timeout),i.hoverState="in",this.timeout=setTimeout(function(){i.hoverState=="in"&&i.show()},i.options.delay.show)},leave:function(t){var n=e(t.currentTarget)[this.type](this._options).data(this.type);this.timeout&&clearTimeout(this.timeout);if(!n.options.delay||!n.options.delay.hide)return n.hide();n.hoverState="out",this.timeout=setTimeout(function(){n.hoverState=="out"&&n.hide()},n.options.delay.hide)},show:function(){var t,n,r,i,s,o,u=e.Event("show");if(this.hasContent()&&this.enabled){this.$element.trigger(u);if(u.isDefaultPrevented())return;t=this.tip(),this.setContent(),this.options.animation&&t.addClass("fade"),s=typeof this.options.placement=="function"?this.options.placement.call(this,t[0],this.$element[0]):this.options.placement,t.detach().css({top:0,left:0,display:"block"}),this.options.container?t.appendTo(this.options.container):t.insertAfter(this.$element),n=this.getPosition(),r=t[0].offsetWidth,i=t[0].offsetHeight;switch(s){case"bottom":o={top:n.top+n.height,left:n.left+n.width/2-r/2};break;case"top":o={top:n.top-i,left:n.left+n.width/2-r/2};break;case"left":o={top:n.top+n.height/2-i/2,left:n.left-r};break;case"right":o={top:n.top+n.height/2-i/2,left:n.left+n.width}}this.applyPlacement(o,s),this.$element.trigger("shown")}},applyPlacement:function(e,t){var n=this.tip(),r=n[0].offsetWidth,i=n[0].offsetHeight,s,o,u,a;n.offset(e).addClass(t).addClass("in"),s=n[0].offsetWidth,o=n[0].offsetHeight,t=="top"&&o!=i&&(e.top=e.top+i-o,a=!0),t=="bottom"||t=="top"?(u=0,e.left<0&&(u=e.left*-2,e.left=0,n.offset(e),s=n[0].offsetWidth,o=n[0].offsetHeight),this.replaceArrow(u-r+s,s,"left")):this.replaceArrow(o-i,o,"top"),a&&n.offset(e)},replaceArrow:function(e,t,n){this.arrow().css(n,e?50*(1-e/t)+"%":"")},setContent:function(){var e=this.tip(),t=this.getTitle();e.find(".tooltip-inner")[this.options.html?"html":"text"](t),e.removeClass("fade in top bottom left right")},hide:function(){function i(){var t=setTimeout(function(){n.off(e.support.transition.end).detach()},500);n.one(e.support.transition.end,function(){clearTimeout(t),n.detach()})}var t=this,n=this.tip(),r=e.Event("hide");this.$element.trigger(r);if(r.isDefaultPrevented())return;return n.removeClass("in"),e.support.transition&&this.$tip.hasClass("fade")?i():n.detach(),this.$element.trigger("hidden"),this},fixTitle:function(){var e=this.$element;(e.attr("title")||typeof e.attr("data-original-title")!="string")&&e.attr("data-original-title",e.attr("title")||"").attr("title","")},hasContent:function(){return this.getTitle()},getPosition:function(){var t=this.$element[0];return e.extend({},typeof t.getBoundingClientRect=="function"?t.getBoundingClientRect():{width:t.offsetWidth,height:t.offsetHeight},this.$element.offset())},getTitle:function(){var e,t=this.$element,n=this.options;return e=t.attr("data-original-title")||(typeof n.title=="function"?n.title.call(t[0]):n.title),e},tip:function(){return this.$tip=this.$tip||e(this.options.template)},arrow:function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},validate:function(){this.$element[0].parentNode||(this.hide(),this.$element=null,this.options=null)},enable:function(){this.enabled=!0},disable:function(){this.enabled=!1},toggleEnabled:function(){this.enabled=!this.enabled},toggle:function(t){var n=t?e(t.currentTarget)[this.type](this._options).data(this.type):this;n.tip().hasClass("in")?n.hide():n.show()},destroy:function(){this.hide().$element.off("."+this.type).removeData(this.type)}};var n=e.fn.tooltip;e.fn.tooltip=function(n){return this.each(function(){var r=e(this),i=r.data("tooltip"),s=typeof n=="object"&&n;i||r.data("tooltip",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.tooltip.Constructor=t,e.fn.tooltip.defaults={animation:!0,placement:"top",selector:!1,template:'',trigger:"hover focus",title:"",delay:0,html:!1,container:!1},e.fn.tooltip.noConflict=function(){return e.fn.tooltip=n,this}}(window.jQuery),!function(e){"use strict";var t=function(e,t){this.init("popover",e,t)};t.prototype=e.extend({},e.fn.tooltip.Constructor.prototype,{constructor:t,setContent:function(){var e=this.tip(),t=this.getTitle(),n=this.getContent();e.find(".popover-title")[this.options.html?"html":"text"](t),e.find(".popover-content")[this.options.html?"html":"text"](n),e.removeClass("fade top bottom left right in")},hasContent:function(){return this.getTitle()||this.getContent()},getContent:function(){var e,t=this.$element,n=this.options;return e=(typeof n.content=="function"?n.content.call(t[0]):n.content)||t.attr("data-content"),e},tip:function(){return this.$tip||(this.$tip=e(this.options.template)),this.$tip},destroy:function(){this.hide().$element.off("."+this.type).removeData(this.type)}});var n=e.fn.popover;e.fn.popover=function(n){return this.each(function(){var r=e(this),i=r.data("popover"),s=typeof n=="object"&&n;i||r.data("popover",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.popover.Constructor=t,e.fn.popover.defaults=e.extend({},e.fn.tooltip.defaults,{placement:"right",trigger:"click",content:"",template:''}),e.fn.popover.noConflict=function(){return e.fn.popover=n,this}}(window.jQuery),!function(e){"use strict";function t(t,n){var r=e.proxy(this.process,this),i=e(t).is("body")?e(window):e(t),s;this.options=e.extend({},e.fn.scrollspy.defaults,n),this.$scrollElement=i.on("scroll.scroll-spy.data-api",r),this.selector=(this.options.target||(s=e(t).attr("href"))&&s.replace(/.*(?=#[^\s]+$)/,"")||"")+" .nav li > a",this.$body=e("body"),this.refresh(),this.process()}t.prototype={constructor:t,refresh:function(){var t=this,n;this.offsets=e([]),this.targets=e([]),n=this.$body.find(this.selector).map(function(){var n=e(this),r=n.data("target")||n.attr("href"),i=/^#\w/.test(r)&&e(r);return i&&i.length&&[[i.position().top+(!e.isWindow(t.$scrollElement.get(0))&&t.$scrollElement.scrollTop()),r]]||null}).sort(function(e,t){return e[0]-t[0]}).each(function(){t.offsets.push(this[0]),t.targets.push(this[1])})},process:function(){var e=this.$scrollElement.scrollTop()+this.options.offset,t=this.$scrollElement[0].scrollHeight||this.$body[0].scrollHeight,n=t-this.$scrollElement.height(),r=this.offsets,i=this.targets,s=this.activeTarget,o;if(e>=n)return s!=(o=i.last()[0])&&this.activate(o);for(o=r.length;o--;)s!=i[o]&&e>=r[o]&&(!r[o+1]||e<=r[o+1])&&this.activate(i[o])},activate:function(t){var n,r;this.activeTarget=t,e(this.selector).parent(".active").removeClass("active"),r=this.selector+'[data-target="'+t+'"],'+this.selector+'[href="'+t+'"]',n=e(r).parent("li").addClass("active"),n.parent(".dropdown-menu").length&&(n=n.closest("li.dropdown").addClass("active")),n.trigger("activate")}};var n=e.fn.scrollspy;e.fn.scrollspy=function(n){return this.each(function(){var r=e(this),i=r.data("scrollspy"),s=typeof n=="object"&&n;i||r.data("scrollspy",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.scrollspy.Constructor=t,e.fn.scrollspy.defaults={offset:10},e.fn.scrollspy.noConflict=function(){return e.fn.scrollspy=n,this},e(window).on("load",function(){e('[data-spy="scroll"]').each(function(){var t=e(this);t.scrollspy(t.data())})})}(window.jQuery),!function(e){"use strict";var t=function(t){this.element=e(t)};t.prototype={constructor:t,show:function(){var t=this.element,n=t.closest("ul:not(.dropdown-menu)"),r=t.attr("data-target"),i,s,o;r||(r=t.attr("href"),r=r&&r.replace(/.*(?=#[^\s]*$)/,""));if(t.parent("li").hasClass("active"))return;i=n.find(".active:last a")[0],o=e.Event("show",{relatedTarget:i}),t.trigger(o);if(o.isDefaultPrevented())return;s=e(r),this.activate(t.parent("li"),n),this.activate(s,s.parent(),function(){t.trigger({type:"shown",relatedTarget:i})})},activate:function(t,n,r){function o(){i.removeClass("active").find("> .dropdown-menu > .active").removeClass("active"),t.addClass("active"),s?(t[0].offsetWidth,t.addClass("in")):t.removeClass("fade"),t.parent(".dropdown-menu")&&t.closest("li.dropdown").addClass("active"),r&&r()}var i=n.find("> .active"),s=r&&e.support.transition&&i.hasClass("fade");s?i.one(e.support.transition.end,o):o(),i.removeClass("in")}};var n=e.fn.tab;e.fn.tab=function(n){return this.each(function(){var r=e(this),i=r.data("tab");i||r.data("tab",i=new t(this)),typeof n=="string"&&i[n]()})},e.fn.tab.Constructor=t,e.fn.tab.noConflict=function(){return e.fn.tab=n,this},e(document).on("click.tab.data-api",'[data-toggle="tab"], [data-toggle="pill"]',function(t){t.preventDefault(),e(this).tab("show")})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.typeahead.defaults,n),this.matcher=this.options.matcher||this.matcher,this.sorter=this.options.sorter||this.sorter,this.highlighter=this.options.highlighter||this.highlighter,this.updater=this.options.updater||this.updater,this.source=this.options.source,this.$menu=e(this.options.menu),this.shown=!1,this.listen()};t.prototype={constructor:t,select:function(){var e=this.$menu.find(".active").attr("data-value");return this.$element.val(this.updater(e)).change(),this.hide()},updater:function(e){return e},show:function(){var t=e.extend({},this.$element.position(),{height:this.$element[0].offsetHeight});return this.$menu.insertAfter(this.$element).css({top:t.top+t.height,left:t.left}).show(),this.shown=!0,this},hide:function(){return this.$menu.hide(),this.shown=!1,this},lookup:function(t){var n;return this.query=this.$element.val(),!this.query||this.query.length"+t+""})},render:function(t){var n=this;return t=e(t).map(function(t,r){return t=e(n.options.item).attr("data-value",r),t.find("a").html(n.highlighter(r)),t[0]}),t.first().addClass("active"),this.$menu.html(t),this},next:function(t){var n=this.$menu.find(".active").removeClass("active"),r=n.next();r.length||(r=e(this.$menu.find("li")[0])),r.addClass("active")},prev:function(e){var t=this.$menu.find(".active").removeClass("active"),n=t.prev();n.length||(n=this.$menu.find("li").last()),n.addClass("active")},listen:function(){this.$element.on("focus",e.proxy(this.focus,this)).on("blur",e.proxy(this.blur,this)).on("keypress",e.proxy(this.keypress,this)).on("keyup",e.proxy(this.keyup,this)),this.eventSupported("keydown")&&this.$element.on("keydown",e.proxy(this.keydown,this)),this.$menu.on("click",e.proxy(this.click,this)).on("mouseenter","li",e.proxy(this.mouseenter,this)).on("mouseleave","li",e.proxy(this.mouseleave,this))},eventSupported:function(e){var t=e in this.$element;return t||(this.$element.setAttribute(e,"return;"),t=typeof this.$element[e]=="function"),t},move:function(e){if(!this.shown)return;switch(e.keyCode){case 9:case 13:case 27:e.preventDefault();break;case 38:e.preventDefault(),this.prev();break;case 40:e.preventDefault(),this.next()}e.stopPropagation()},keydown:function(t){this.suppressKeyPressRepeat=~e.inArray(t.keyCode,[40,38,9,13,27]),this.move(t)},keypress:function(e){if(this.suppressKeyPressRepeat)return;this.move(e)},keyup:function(e){switch(e.keyCode){case 40:case 38:case 16:case 17:case 18:break;case 9:case 13:if(!this.shown)return;this.select();break;case 27:if(!this.shown)return;this.hide();break;default:this.lookup()}e.stopPropagation(),e.preventDefault()},focus:function(e){this.focused=!0},blur:function(e){this.focused=!1,!this.mousedover&&this.shown&&this.hide()},click:function(e){e.stopPropagation(),e.preventDefault(),this.select(),this.$element.focus()},mouseenter:function(t){this.mousedover=!0,this.$menu.find(".active").removeClass("active"),e(t.currentTarget).addClass("active")},mouseleave:function(e){this.mousedover=!1,!this.focused&&this.shown&&this.hide()}};var n=e.fn.typeahead;e.fn.typeahead=function(n){return this.each(function(){var r=e(this),i=r.data("typeahead"),s=typeof n=="object"&&n;i||r.data("typeahead",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.typeahead.defaults={source:[],items:8,menu:'',item:'',minLength:1},e.fn.typeahead.Constructor=t,e.fn.typeahead.noConflict=function(){return e.fn.typeahead=n,this},e(document).on("focus.typeahead.data-api",'[data-provide="typeahead"]',function(t){var n=e(this);if(n.data("typeahead"))return;n.typeahead(n.data())})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.options=e.extend({},e.fn.affix.defaults,n),this.$window=e(window).on("scroll.affix.data-api",e.proxy(this.checkPosition,this)).on("click.affix.data-api",e.proxy(function(){setTimeout(e.proxy(this.checkPosition,this),1)},this)),this.$element=e(t),this.checkPosition()};t.prototype.checkPosition=function(){if(!this.$element.is(":visible"))return;var t=e(document).height(),n=this.$window.scrollTop(),r=this.$element.offset(),i=this.options.offset,s=i.bottom,o=i.top,u="affix affix-top affix-bottom",a;typeof i!="object"&&(s=o=i),typeof o=="function"&&(o=i.top()),typeof s=="function"&&(s=i.bottom()),a=this.unpin!=null&&n+this.unpin<=r.top?!1:s!=null&&r.top+this.$element.height()>=t-s?"bottom":o!=null&&n<=o?"top":!1;if(this.affixed===a)return;this.affixed=a,this.unpin=a=="bottom"?r.top-n:null,this.$element.removeClass(u).addClass("affix"+(a?"-"+a:""))};var n=e.fn.affix;e.fn.affix=function(n){return this.each(function(){var r=e(this),i=r.data("affix"),s=typeof n=="object"&&n;i||r.data("affix",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.affix.Constructor=t,e.fn.affix.defaults={offset:0},e.fn.affix.noConflict=function(){return e.fn.affix=n,this},e(window).on("load",function(){e('[data-spy="affix"]').each(function(){var t=e(this),n=t.data();n.offset=n.offset||{},n.offsetBottom&&(n.offset.bottom=n.offsetBottom),n.offsetTop&&(n.offset.top=n.offsetTop),t.affix(n)})})}(window.jQuery);
--------------------------------------------------------------------------------
/src/scl3711.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 Google Inc. All rights reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 |
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | /**
17 | * @fileoverview SCL3711 USB driver.
18 | */
19 |
20 | 'use strict';
21 |
22 | // Global SCL3711 instance counter.
23 | var scl3711_id = 0;
24 |
25 | // Worker SCL3711 instances. Tied 1-on-1 to websocket worker.
26 | function usbSCL3711() {
27 | this.dev = null;
28 | // Pick unique channel (within process..)
29 | this.cid = (++scl3711_id) & 0x00ffffff;
30 | this.rxframes = [];
31 | this.rxcb = null;
32 | this.onclose = null;
33 | this.detected_tag = null; // TODO: move this to mifare_classic.js
34 | this.auth_key = null; // TODO: move this to mifare_classic.js
35 | this.authed_sector = null; // TODO: move this to mifare_classic.js
36 | this.KEYS = [ // TODO: move this to mifare_classic.js
37 | new Uint8Array([0xff, 0xff, 0xff, 0xff, 0xff, 0xff]), // defailt
38 | new Uint8Array([0xd3, 0xf7, 0xd3, 0xf7, 0xd3, 0xf7]), // NFC Forum
39 | new Uint8Array([0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5]) // MAD
40 | ];
41 |
42 | this.strerror = function(errno) {
43 | var err = {
44 | 0x01: "time out, the target has not answered",
45 | 0x02: "checksum error during rf communication",
46 | 0x03: "parity error during rf communication",
47 | 0x04: "erroneous bit count in anticollision",
48 | 0x05: "framing error during mifare operation",
49 | 0x06: "abnormal bit collision in 106 kbps anticollision",
50 | 0x07: "insufficient communication buffer size",
51 | 0x09: "rf buffer overflow detected by ciu",
52 | 0x0a: "rf field not activated in time by active mode peer",
53 | 0x0b: "protocol error during rf communication",
54 | 0x0d: "overheated - antenna drivers deactivated",
55 | 0x0e: "internal buffer overflow",
56 | 0x10: "invalid command parameter",
57 | 0x12: "unsupported command from initiator",
58 | 0x13: "format error during rf communication",
59 | 0x14: "mifare authentication error",
60 | 0x18: "not support NFC secure",
61 | 0x19: "i2c bus line is busy",
62 | 0x23: "wrong uid check byte (14443-3)",
63 | 0x25: "command invalid in current dep state",
64 | 0x26: "operation not allowed in this configuration",
65 | 0x27: "not acceptable command due to context",
66 | 0x29: "released by initiator while operating as target",
67 | 0x2a: "card ID does not match",
68 | 0x2b: "the card previously activated has disapperaed",
69 | 0x2c: "Mismatch between NFCID3 initiator and target in DEP 212/424 kbps",
70 | 0x2d: "Over-current event has been detected",
71 | 0x2e: "NAD missing in DEP frame",
72 | 0x2f: "deselected by initiator while operating as target",
73 | 0x31: "initiator rf-off state detected in passive mode",
74 | 0x7F: "pn53x application level error"
75 | };
76 |
77 | if (errno in err) {
78 | return "[" + errno + "] " + err[errno];
79 | } else {
80 | return "Unknown error: " + errno;
81 | }
82 | };
83 |
84 | }
85 |
86 | // Notify callback for every frame received.
87 | usbSCL3711.prototype.notifyFrame = function(cb) {
88 | if (this.rxframes.length != 0) {
89 | // Already have frames; continue.
90 | if (cb) window.setTimeout(cb, 0);
91 | } else {
92 | this.rxcb = cb;
93 | }
94 | };
95 |
96 | // Called by low level driver.
97 | // Return true if still interested.
98 | usbSCL3711.prototype.receivedFrame = function(frame) {
99 | if (!this.rxframes) return false; // No longer interested.
100 |
101 | this.rxframes.push(frame);
102 |
103 | // Callback self in case we were waiting.
104 | var cb = this.rxcb;
105 | this.rxcb = null;
106 | if (cb) window.setTimeout(cb, 0);
107 |
108 | return true;
109 | };
110 |
111 | // Return oldest frame. Throw if none.
112 | usbSCL3711.prototype.readFrame = function() {
113 | if (this.rxframes.length == 0) throw 'rxframes empty!' ;
114 |
115 | var frame = this.rxframes.shift();
116 | return frame;
117 | };
118 |
119 | // Poll from rxframes[], reconstruct entire message.
120 | // timeout in seconds.
121 | usbSCL3711.prototype.read = function(timeout, cb) {
122 | if (!this.dev){ cb(1); return; }
123 |
124 | var tid = null; // timeout timer id.
125 | var callback = cb;
126 | var self = this;
127 |
128 | // Schedule call to cb if not called yet.
129 | function schedule_cb(a, b, c) {
130 | if (tid) {
131 | // Cancel timeout timer.
132 | window.clearTimeout(tid);
133 | tid = null;
134 | }
135 | var C = callback;
136 | if (C) {
137 | callback = null;
138 | window.setTimeout(function() { C(a, b, c); }, 0);
139 | }
140 | };
141 |
142 | function read_timeout() {
143 | if (!callback || !tid) return; // Already done.
144 |
145 | console.log(UTIL_fmt(
146 | '[' + self.cid.toString(16) + '] timeout!'));
147 |
148 | tid = null;
149 | };
150 |
151 | function read_frame() {
152 | if (!callback || !tid) return; // Already done.
153 |
154 | var f = new Uint8Array(self.readFrame());
155 |
156 | // http://www.nxp.com/documents/user_manual/157830_PN533_um080103.pdf
157 | // Section 7.1 ACK frame.
158 | if (f.length == 6 &&
159 | f[0] == 0x00 &&
160 | f[1] == 0x00 &&
161 | f[2] == 0xff &&
162 | f[3] == 0x00 &&
163 | f[4] == 0xff &&
164 | f[5] == 0x00) {
165 | // Expected positive ack, read more.
166 | self.notifyFrame(read_frame);
167 | return; // wait for more.
168 | }
169 |
170 | // Change the ACR122 response to SCL3711 format.
171 | if (f.length > 10) {
172 | if (f[0] == 0x80 /* RDR_to_PC_Datablock */) {
173 | f = UTIL_concat(
174 | new Uint8Array([0x00, 0x00, 0xff, 0x01, 0xff]),
175 | new Uint8Array(f.subarray(10)));
176 | } else if (f[0] == 0x83 /* RDR_to_PC_Escape */) {
177 | f = UTIL_concat(
178 | new Uint8Array([0x00, 0x00, 0xff, 0x01, 0xff]),
179 | new Uint8Array(f.subarray(10)));
180 | }
181 | }
182 |
183 | // TODO: implement NACK frame? Error frame?
184 | // TODO: preamble and postamble frames?
185 |
186 | // TODO: check data checksum?
187 | // TODO: short cut. Will leave to callback to handle.
188 | if (f.length == 7) {
189 | if (f[5] == 0x90 &&
190 | f[6] == 0x00) {
191 | /* ACR122U - operation is success. */
192 | schedule_cb(0, f.buffer);
193 | return;
194 | } else if (f[5] == 0x63 &&
195 | f[6] == 0x00) {
196 | /* ACR122U - operation is failed. */
197 | schedule_cb(0xaaa, f.buffer);
198 | return;
199 | }
200 | } else if (f.length > 6 &&
201 | f[0] == 0x00 &&
202 | f[1] == 0x00 &&
203 | f[2] == 0xff &&
204 | f[3] + f[4] == 0x100 /* header checksum */) {
205 | if (f[5] == 0xd5 &&
206 | f[6] == 0x41 /* InDataExchange reply */) {
207 | if (f[7] == 0x00 /* status */) {
208 | schedule_cb(0, new Uint8Array(f.subarray(8, f.length - 2)).buffer);
209 | } else {
210 | console.log("ERROR: InDataExchange reply status = " +
211 | self.strerror(f[7]));
212 | }
213 | return;
214 | } else if (f[5] == 0xd5 &&
215 | f[6] == 0x8d /* TgInitAsTarget reply */) {
216 | /* TODO: f[7] Mode is ignored. */
217 | schedule_cb(0, new Uint8Array(f.subarray(8, f.length - 2)).buffer);
218 | return;
219 | } else if (f[5] == 0xd5 &&
220 | f[6] == 0x89 /* TgGetInitiatorCommand reply */) {
221 | if (f[7] == 0x00 /* Status */) {
222 | schedule_cb(0, new Uint8Array(f.subarray(8, f.length - 2)).buffer);
223 | } else {
224 | console.log("ERROR: TgGetInitiatorCommand reply status = " +
225 | self.strerror(f[7]));
226 | }
227 | return;
228 | } else if (f[5] == 0xd5 &&
229 | f[6] == 0x91 /* TgResponseToInitiator reply */) {
230 | if (f[7] == 0x00 /* Status */) {
231 | schedule_cb(0, new Uint8Array(f.subarray(8, f.length - 2)).buffer);
232 | } else {
233 | console.log("ERROR: TgResponseToInitiator reply status = " +
234 | self.strerror(f[7]));
235 | }
236 | return;
237 | } else if (f[5] == 0xd5 &&
238 | f[6] == 0x33 /* RFConfiguration reply */) {
239 | schedule_cb(0, new Uint8Array(f.subarray(7, f.length - 2)).buffer);
240 | return;
241 | } else if (f[5] == 0xd5 &&
242 | f[6] == 0x4b /* InListPassiveTarget reply */) {
243 | if (f[7] == 0x01 /* tag number */ &&
244 | f[8] == 0x01 /* Tg */) {
245 |
246 | /* TODO:
247 | * Take [SENS_REQ(ATQA), SEL_RES(SAK), tag_id] to ask database.
248 | * The database would return the corresponding TAG object.
249 | */
250 |
251 | console.log("DEBUG: InListPassiveTarget SENS_REQ(ATQA)=0x" +
252 | (f[9] * 256 + f[10]).toString(16) +
253 | ", SEL_RES(SAK)=0x" + f[11].toString(16));
254 | var NFCIDLength = f[12];
255 | var tag_id = new Uint8Array(f.subarray(13, 13 + NFCIDLength)).buffer;
256 | console.log("DEBUG: tag_id: " +
257 | UTIL_BytesToHex(new Uint8Array(tag_id)));
258 |
259 | if (f[9] == 0x00 && f[10] == 0x44 /* SENS_RES */) {
260 | /* FIXME: not actually Ultralight. Only when tag_id[0]==0x04 */
261 | console.log("DEBUG: found Mifare Ultralight (106k type A)");
262 | self.detected_tag = "Mifare Ultralight";
263 | self.authed_sector = null;
264 | self.auth_key = null;
265 | schedule_cb(0, "tt2", tag_id);
266 | return;
267 | } else if (f[9] == 0x00 && f[10] == 0x04 /* SENS_RES */) {
268 | /* FIXME: not actually Classic. Only when tag_id[0]==0x04 */
269 | console.log("DEBUG: found Mifare Classic 1K (106k type A)");
270 | self.detected_tag = "Mifare Classic 1K";
271 | self.authed_sector = null;
272 | self.auth_key = null;
273 | schedule_cb(0, "mifare_classic", tag_id);
274 | return;
275 | }
276 | } else {
277 | console.log("DEBUG: found " + f[7] + " target, tg=" + f[8]);
278 | return;
279 | }
280 | }
281 | }
282 |
283 | // Not sure what kind of reply this is. Report w/ error.
284 | schedule_cb(0x888, f.buffer);
285 | };
286 |
287 | // Start timeout timer.
288 | tid = window.setTimeout(read_timeout, 1000.0 * timeout);
289 |
290 | // Schedule read of first frame.
291 | self.notifyFrame(read_frame);
292 | };
293 |
294 | // Wrap data into frame, queue for sending.
295 | usbSCL3711.prototype.write = function(data) {
296 | this.dev.writeFrame(data);
297 | };
298 |
299 | usbSCL3711.prototype.exchange = function(data, timeout, cb) {
300 | this.write(data);
301 | this.read(timeout, cb);
302 | };
303 |
304 |
305 | // TODO: move to ACR122-specific file
306 | usbSCL3711.prototype.acr122_reset_to_good_state = function(cb) {
307 | var self = this;
308 | var callback = cb;
309 |
310 | self.exchange(new Uint8Array([
311 | 0x00, 0x00, 0xff, 0x00, 0xff, 0x00]).buffer, 1, function(rc, data) {
312 | if (rc) {
313 | console.warn("[FIXME] acr122_reset_to_good_state: rc = " + rc);
314 | }
315 | // icc_power_on
316 | self.exchange(new Uint8Array([
317 | 0x62, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00]).buffer,
318 | 10, function(rc, data) {
319 | if (rc) {
320 | console.warn("[FIXME] icc_power_on: rc = " + rc);
321 | }
322 | console.log("[DEBUG] icc_power_on: turn on the device power");
323 | if (callback) window.setTimeout(function() { callback(0); }, 100);
324 | });
325 | });
326 | }
327 |
328 | // set the beep on/off
329 | usbSCL3711.prototype.acr122_set_buzzer = function(enable, cb) {
330 | var self = this;
331 | var callback = cb;
332 | var buzz = (enable) ? 0xff : 0x00;
333 |
334 | self.exchange(new Uint8Array([
335 | 0x6b, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
336 | 0xff, 0x00, 0x52, buzz, 0x00]).buffer, 1.0, function(rc, data) {
337 | if (callback) callback(rc, data);
338 | });
339 | }
340 |
341 | usbSCL3711.prototype.acr122_load_authentication_keys = function(key, loc, cb) {
342 | var self = this;
343 | var callback = cb;
344 |
345 | if (key == null) key = self.KEYS[0];
346 | else if (typeof key != "object") key = self.KEYS[key];
347 |
348 | var u8 = new Uint8Array([
349 | 0x6b, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
350 | 0xff, 0x82, /* INS: Load Authentication Keys */
351 | 0x00, /* P1: Key Structure: volatile memory */
352 | loc, /* P2: Key Number (key location): 0 or 1 */
353 | 0x06]);/* Lc: 6 bytes */
354 | u8 = UTIL_concat(u8, key);
355 |
356 | self.exchange(u8.buffer, 1.0, function(rc, data) {
357 | console.log("[DEBUG] acr122_load_authentication_keys(loc: " + loc +
358 | ", key: " + UTIL_BytesToHex(key) + ") = " + rc);
359 | if (callback) callback(rc, data);
360 | });
361 | }
362 |
363 | /* the 'block' is in 16-bytes unit. */
364 | usbSCL3711.prototype.acr122_authentication = function(block, loc, type, cb) {
365 | var self = this;
366 | var callback = cb;
367 |
368 | self.exchange(new Uint8Array([
369 | 0x6b, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
370 | 0xff, 0x86, /* INS: Authentication */
371 | 0x00, /* P1: */
372 | 0x00, /* P2: */
373 | 0x05, /* Lc: 5 bytes (Authentication Data Bytes) */
374 | 0x01, /* Version */
375 | 0x00, /* 0x00 */
376 | block, /* Block number */
377 | type, /* Key type: TYPE A (0x60) or TYPE B (0x61) */
378 | loc /* Key number (key location): 0 or 1 */
379 | ]).buffer, 1.0, function(rc, data) {
380 | console.log("[DEBUG] acr122_authentication(loc: " + loc +
381 | ", type: " + type + ", block: " + block + ") = " + rc);
382 | if (callback) callback(rc, data);
383 | });
384 | };
385 |
386 | /* For Mifare Classic only. The 'block' is in 16-bytes unit. */
387 | usbSCL3711.prototype.publicAuthentication = function(block, cb) {
388 | var self = this;
389 | var callback = cb;
390 | var sector = Math.floor(block / 4);
391 |
392 | function try_keyA(k) {
393 | var ki = k; // for closure
394 | if (ki >= 3) { // failed authentication
395 | if (callback) callback(0xfff);
396 | return;
397 | }
398 | self.acr122_load_authentication_keys(ki, 0, function(rc, data) {
399 | if (rc) return;
400 | self.acr122_authentication(block, 0, 0x60/*KEY A*/, function(rc, data) {
401 | if (rc) return try_keyA(ki + 1);
402 | self.authed_sector = sector;
403 | self.auth_key = self.KEYS[ki];
404 |
405 | // try_keyB(): always the default key
406 | self.acr122_load_authentication_keys(self.KEYS[0], 1,
407 | function(rc, data) {
408 | self.acr122_authentication(block, 1, 0x61/*KEY B*/,
409 | function(rc, data) {
410 | if (callback) callback(rc, data);
411 | });
412 | });
413 | });
414 | });
415 | }
416 |
417 | if (self.detected_tag == "Mifare Classic 1K") {
418 | if (self.dev && self.dev.acr122) {
419 | if (self.authed_sector != sector) {
420 | console.log("[DEBUG] Public Authenticate sector " + sector);
421 | try_keyA(0);
422 | } else {
423 | if (callback) callback(0, null);
424 | }
425 | } else {
426 | if (callback) callback(0, null);
427 | }
428 | } else {
429 | if (callback) callback(0, null);
430 | }
431 | };
432 |
433 | /* For Mifare Classic only. The 'block' is in 16-bytes unit. */
434 | usbSCL3711.prototype.privateAuthentication = function(block, key, cb) {
435 | var self = this;
436 | var callback = cb;
437 | var sector = Math.floor(block / 4);
438 |
439 | if (self.detected_tag == "Mifare Classic 1K") {
440 | if (self.dev && self.dev.acr122) {
441 | if (self.authed_sector != sector) {
442 | console.log("[DEBUG] Private Authenticate sector " + sector);
443 | self.acr122_load_authentication_keys(key, 1,
444 | function(rc, data) {
445 | self.acr122_authentication(block, 1, 0x61/*KEY B*/,
446 | function(rc, data) {
447 | if (rc) { console.log("KEY B AUTH ERROR"); return rc; }
448 | if (callback) callback(rc, data);
449 | });
450 | });
451 | } else {
452 | if (callback) callback(0, null);
453 | }
454 | } else {
455 | if (callback) callback(0, null);
456 | }
457 | } else {
458 | if (callback) callback(0, null);
459 | }
460 | };
461 |
462 | usbSCL3711.prototype.acr122_set_timeout = function(timeout /* secs */, cb) {
463 | var self = this;
464 | var callback = cb;
465 |
466 | var unit = Math.ceil(timeout / 5);
467 | if (unit >= 0xff) unit = 0xff;
468 | console.log("[DEBUG] acr122_set_timeout(round up to " + unit * 5 + " secs)");
469 |
470 | self.exchange(new Uint8Array([
471 | 0x6b, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
472 | 0xff, 0x00, 0x41, unit, 0x00]).buffer, 1.0, function(rc, data) {
473 | if (callback) callback(rc, data);
474 | });
475 | }
476 |
477 | // onclose callback gets called when device disappears.
478 | usbSCL3711.prototype.open = function(which, cb, onclose) {
479 | this.rxframes = [];
480 | this.onclose = onclose;
481 |
482 | this.cid &= 0x00ffffff;
483 | this.cid |= ((which + 1) << 24); // For debugging.
484 |
485 | var self = this;
486 | var callback = cb;
487 | dev_manager.open(which, this, function(device) {
488 | self.dev = device;
489 | var result = (self.dev != null) ? 0 : 1;
490 |
491 | /* extra configuration for ACR122 */
492 | if (self.dev && self.dev.acr122) {
493 | self.acr122_reset_to_good_state(function(rc) {
494 | if (rc) {
495 | console.error("[ERROR] acr122_reset_to_good_state() returns " + rc);
496 | return callback ? callback(rc) : null;
497 | }
498 | self.acr122_set_buzzer(false, function(rc) {
499 | if (rc) {
500 | console.error("[ERROR] acr122_reset_to_good_state() returns " + rc);
501 | return callback ? callback(rc) : null;
502 | }
503 | if (callback) callback(result);
504 | });
505 | });
506 | } else {
507 | if (callback) callback(result);
508 | }
509 | });
510 | };
511 |
512 | usbSCL3711.prototype.close = function() {
513 | var self = this;
514 |
515 | /* deselect and release target if any tag is associated. */
516 | function deselect_release(cb) {
517 | self.exchange(self.makeFrame(0x44/* InDeselect */,
518 | new Uint8Array([0x01/*Tg*/])), 1.0 /* timeout */,
519 | function(rc, data) {
520 | self.exchange(self.makeFrame(0x52/* InRelease */,
521 | new Uint8Array([0x01/*Tg*/])), 1.0 /* timeout */,
522 | function(rc, data) {
523 | });
524 | });
525 | }
526 |
527 | function dev_manager_close() {
528 | self.rxframes = null; // So receivedFrame() will return false.
529 | if (self.dev) {
530 | dev_manager.close(self.dev, self);
531 | self.dev = null;
532 | }
533 | }
534 |
535 | deselect_release(dev_manager_close);
536 | };
537 |
538 |
539 | /*
540 | * Help to build the USB packet:
541 | *
542 | * ACR122:
543 | *
544 | * CCID header (10bytes)
545 | *
546 | *
547 | * SCL3711:
548 | * 00 00 ff ff ff len len ~len
549 | * d4 cmd data ...
550 | * dsc ~dsc
551 | */
552 | usbSCL3711.prototype.makeFrame = function(cmd, data) {
553 | var r8 = new Uint8Array(data ? data : []);
554 | // payload: 2 bytes cmd
555 | var p8 = new Uint8Array(r8.length + 2);
556 |
557 | var dcslen = r8.length + 2; // [0xd4, cmd]
558 |
559 | // header
560 | if (this.dev.acr122) {
561 | // acr122
562 | var apdu_len = 5 /* header */ + 2 /* cmd */ + r8.length;
563 | var c8 = new Uint8Array(10); // CCID header
564 | c8[0] = 0x6b; // PC_to_RDR_Escape
565 | c8[1] = (apdu_len >> 0) & 0xff; // LEN (little-endian)
566 | c8[2] = (apdu_len >> 8) & 0xff; //
567 | c8[3] = (apdu_len >> 16) & 0xff; //
568 | c8[4] = (apdu_len >> 24) & 0xff; //
569 | c8[5] = 0x00; // bSlot
570 | c8[6] = 0x00; // bSeq
571 | c8[7] = 0x00; // abRFU
572 | c8[8] = 0x00; // abRFU
573 | c8[9] = 0x00; // abRFU
574 |
575 | var a8 = new Uint8Array(5); // Pseudo-APDU
576 | a8[0] = 0xFF; // Class
577 | a8[1] = 0x00; // INS (fixed 0)
578 | a8[2] = 0x00; // P1 (fixed 0)
579 | a8[3] = 0x00; // P2 (fixed 0)
580 | a8[4] = r8.length + 2; // Lc (Number of Bytes to send)
581 |
582 | h8 = UTIL_concat(c8, a8);
583 | } else {
584 | // scl3711
585 | var h8 = new Uint8Array(8); // header
586 | h8[0] = 0x00;
587 | h8[1] = 0x00;
588 | h8[2] = 0xff;
589 | h8[3] = 0xff;
590 | h8[4] = 0xff;
591 | h8[5] = dcslen >>> 8;
592 | h8[6] = dcslen & 255;
593 | h8[7] = 0x100 - ((h8[5] + h8[6]) & 255); // length checksum
594 | }
595 |
596 | // cmd
597 | p8[0] = 0xd4;
598 | p8[1] = cmd;
599 |
600 | // payload
601 | var dcs = p8[0] + p8[1];
602 | for (var i = 0; i < r8.length; ++i) {
603 | p8[2 + i] = r8[i];
604 | dcs += r8[i];
605 | }
606 |
607 | var chksum = null;
608 | if (this.dev.acr122) {
609 | chksum = new Uint8Array([]);
610 | } else {
611 | chksum = new Uint8Array(2); // checksum: 2 bytes checksum at the end.
612 | chksum[0] = 0x100 - (dcs & 255); // data checksum
613 | chksum[1] = 0x00;
614 | }
615 |
616 | return UTIL_concat(UTIL_concat(h8, p8), chksum).buffer;
617 | };
618 |
619 |
620 | // Wait for a passive target.
621 | usbSCL3711.prototype.wait_for_passive_target = function(timeout, cb) {
622 | var self = this;
623 |
624 | if (!cb) cb = defaultCallback;
625 |
626 | function InListPassiveTarget(timeout, cb) {
627 | self.detected_tag = null;
628 | // Command 0x4a InListPassiveTarget, 0x01/*MaxTg*/, 0x00 (106 kpbs type).
629 | self.exchange(self.makeFrame(0x4a, new Uint8Array([0x01, 0x00])),
630 | timeout, cb);
631 | }
632 |
633 | if (self.dev.acr122) {
634 | self.acr122_set_timeout(timeout, function(rc, data) {
635 | InListPassiveTarget(timeout, cb);
636 | });
637 | } else {
638 | InListPassiveTarget(timeout, cb);
639 | }
640 | };
641 |
642 |
643 | // read a block (16-byte) from tag.
644 | // cb(rc, data: ArrayBuffer)
645 | usbSCL3711.prototype.read_block = function(block, cb) {
646 | var self = this;
647 | var callback = cb;
648 | if (!cb) cb = defaultCallback;
649 |
650 | /* function-wise variable */
651 | var u8 = new Uint8Array(2); // Type 2 tag command
652 | u8[0] = 0x30; // READ command
653 | u8[1] = block; // block number
654 |
655 | self.apdu(u8, function (rc, data) {
656 | callback(rc, data);
657 | });
658 | }
659 |
660 |
661 | // Input:
662 | // data: ArrayBuffer, the type 2 tag content.
663 | usbSCL3711.prototype.emulate_tag = function(data, timeout, cb) {
664 | if (!cb) cb = defaultCallback;
665 | var callback = cb;
666 | var self = this;
667 | var TIMEOUT = timeout;
668 |
669 | /*
670 | * Input:
671 | * cmd: the TT2 command from initiator.
672 | */
673 | var HANDLE_TT2 = function(cmd) {
674 | switch (cmd[0]) {
675 | case 0x30: /* READ */
676 | var blk_no = cmd[1];
677 | console.log("recv TT2.READ(blk_no=" + blk_no + ")");
678 | var ret = data.subarray(blk_no * 4, blk_no * 4 + 16);
679 | if (ret.length < 16) {
680 | ret = UTIL_concat(ret, new Uint8Array(16 - ret.length));
681 | }
682 | /* TgResponseToInitiator */
683 | var u8 = self.makeFrame(0x90, ret);
684 | self.exchange(u8, TIMEOUT, function(rc, data) {
685 | if (rc) { console.log("exchange(): " + rc); return rc; }
686 | /* TgGetInitiatorCommand */
687 | var u8 = self.makeFrame(0x88, []);
688 | self.exchange(u8, TIMEOUT, function(rc, data) {
689 | if (rc) { console.log("exchange(): " + rc); return rc; }
690 | HANDLE_TT2(new Uint8Array(data));
691 | });
692 | });
693 | break;
694 | case 0x50: /* HALT */
695 | console.log("recv TT2.HALT received.");
696 | callback(0);
697 | break;
698 | default:
699 | console.log("Unsupported TT2 tag: " + cmd[0]);
700 | callback(0x999);
701 | }
702 | }
703 |
704 | function TgInitAsTarget() {
705 | var req = new Uint8Array([
706 | 0x01, // Mode: passive only
707 | 0x04, 0x00, 0x00, 0xb0, 0x0b, 0x00, // Mifare parameter
708 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
709 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Felica
710 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ID3
711 | 0x00, 0x00]);
712 | var u8 = self.makeFrame(0x8c, req);
713 | self.exchange(u8, TIMEOUT, function(rc, data) {
714 | if (rc != 0) { callback(rc); return; }
715 | console.log("Emulated as a tag, reply is following:");
716 |
717 | HANDLE_TT2(new Uint8Array(data));
718 | });
719 | }
720 |
721 | if (self.dev.acr122) {
722 | // Set the PICC Operating Parameter
723 | self.exchange(new Uint8Array([
724 | 0x6b, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
725 | 0xff, 0x00, 0x51, 0x00, 0x00]).buffer, 1, function(rc, data) {
726 | // RFCA:off and RF:off
727 | self.exchange(new Uint8Array([
728 | 0x6b, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
729 | 0xff, 0x00, 0x00, 0x00, 0x04, 0xd4, 0x32, 0x01, 0x00]).buffer, 1,
730 | function(rc, data) {
731 | if (rc != 0) { callback(rc); return; }
732 | self.acr122_set_timeout(timeout, function(rc, data) {
733 | if (rc != 0) { callback(rc); return; }
734 | TgInitAsTarget();
735 | });
736 | });
737 | });
738 | } else {
739 | TgInitAsTarget();
740 | }
741 | }
742 |
743 |
744 | // Input:
745 | // blk_no: block number (TT2: 4-byte; Classic: 16-byte)
746 | // data: Uint8Array.
747 | usbSCL3711.prototype.write_block = function(blk_no, data, cb, write_inst) {
748 | var callback = cb;
749 |
750 | if (write_inst == null) {
751 | write_inst = 0xA2; // TT2 WRITE command
752 | }
753 |
754 | var u8 = new Uint8Array(2 + data.length); // Type 2 tag command
755 | u8[0] = write_inst; // WRITE command
756 | u8[1] = blk_no; // block number
757 | for (var i = 0; i < data.length; i++) {
758 | u8[2 + i] = data[i];
759 | }
760 |
761 | this.apdu(u8, function(rc, dummy) {
762 | callback(rc);
763 | });
764 | }
765 |
766 | // Send apdu (0x40 -- InDataExchange), receive response.
767 | usbSCL3711.prototype.apdu = function(req, cb, write_only) {
768 | if (!cb) cb = defaultCallback;
769 |
770 | // Command 0x40 InDataExchange, our apdu as payload.
771 | var u8 = new Uint8Array(this.makeFrame(0x40,
772 | UTIL_concat([0x01/*Tg*/], req)));
773 |
774 | // Write out in 64 bytes frames.
775 | for (var i = 0; i < u8.length; i += 64) {
776 | this.dev.writeFrame(new Uint8Array(u8.subarray(i, i + 64)).buffer);
777 | }
778 |
779 | if (write_only) {
780 | cb(0, null); // tell caller the packet has been sent.
781 | } else {
782 | // Read response, interpret sw12.
783 | this.read(3.0, function(rc, data, expect_sw12) {
784 | if (rc != 0) { cb(rc); return; }
785 | var u8 = new Uint8Array(data);
786 |
787 | if (expect_sw12) {
788 | if (u8.length < 2) { cb(0x0666); return; }
789 | var sw12 = u8[u8.length - 2] * 256 + u8[u8.length - 1];
790 | // Pass all non 9000 responses.
791 | // 9000 is expected and passed as 0.
792 | cb(sw12 == 0x9000 ? 0 : sw12,
793 | new Uint8Array(u8.subarray(0, u8.length - 2)).buffer);
794 | } else {
795 | cb(0, u8.buffer);
796 | }
797 | });
798 | }
799 | };
800 |
--------------------------------------------------------------------------------
/sample/chrome-nfc.js:
--------------------------------------------------------------------------------
1 | function B64_encode(bytes, opt_length) {
2 | if (!opt_length) {
3 | opt_length = bytes.length;
4 | }
5 | var b64out = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
6 | var result = "";
7 | var shift = 0;
8 | var accu = 0;
9 | var input_index = 0;
10 | while (opt_length--) {
11 | accu <<= 8;
12 | accu |= bytes[input_index++];
13 | shift += 8;
14 | while (shift >= 6) {
15 | var i = accu >> shift - 6 & 63;
16 | result += b64out.charAt(i);
17 | shift -= 6;
18 | }
19 | }
20 | if (shift) {
21 | accu <<= 8;
22 | shift += 8;
23 | var i = accu >> shift - 6 & 63;
24 | result += b64out.charAt(i);
25 | }
26 | return result;
27 | }
28 | function base64_encode(bytes, opt_length) {
29 | if (!opt_length) {
30 | opt_length = bytes.length;
31 | }
32 | var b64out = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
33 | var result = "";
34 | var shift = 0;
35 | var accu = 0;
36 | var input_index = 0;
37 | while (opt_length--) {
38 | accu <<= 8;
39 | accu |= bytes[input_index++];
40 | shift += 8;
41 | while (shift >= 6) {
42 | var i = accu >> shift - 6 & 63;
43 | result += b64out.charAt(i);
44 | shift -= 6;
45 | }
46 | }
47 | if (shift) {
48 | accu <<= 8;
49 | shift += 8;
50 | var i = accu >> shift - 6 & 63;
51 | result += b64out.charAt(i);
52 | }
53 | while (result.length % 4) {
54 | result += "=";
55 | }
56 | return result;
57 | }
58 | var B64_inmap = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 63, 0, 0, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 0, 0, 0, 0, 64, 0, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 0, 0, 0, 0, 0];
59 | function B64_decode(string) {
60 | var bytes = [];
61 | var accu = 0;
62 | var shift = 0;
63 | for (var i = 0;i < string.length;++i) {
64 | var c = string.charCodeAt(i);
65 | if (c < 32 || c > 127 || !B64_inmap[c - 32]) {
66 | return[];
67 | }
68 | accu <<= 6;
69 | accu |= B64_inmap[c - 32] - 1;
70 | shift += 6;
71 | if (shift >= 8) {
72 | bytes.push(accu >> shift - 8 & 255);
73 | shift -= 8;
74 | }
75 | }
76 | return bytes;
77 | }
78 | ;function MifareClassic(tag_id) {
79 | this.tag_id = new Uint8Array(tag_id);
80 | this.type_name = "MIFARE Classic 1K";
81 | this.WRITE_COMMAND = 160;
82 | }
83 | MifareClassic.prototype.log2sec = function(logic_blknum) {
84 | if (logic_blknum < 2) {
85 | return 0;
86 | }
87 | return Math.floor((logic_blknum - 2) / 3) + 1;
88 | };
89 | MifareClassic.prototype.log2phy = function(logic_blknum) {
90 | if (logic_blknum < 2) {
91 | return logic_blknum + 1;
92 | }
93 | var sector = this.log2sec(logic_blknum);
94 | return sector * 4 + (logic_blknum - 2) % 3;
95 | };
96 | MifareClassic.prototype.mif_calc_crc8 = function(input) {
97 | var crc = 199;
98 | for (var i = 0;i < input.length;i++) {
99 | crc = crc ^ input[i];
100 | for (var j = 0;j < 8;j++) {
101 | if (crc & 128) {
102 | crc = crc << 1 ^ 29;
103 | } else {
104 | crc = crc << 1;
105 | }
106 | }
107 | }
108 | return crc;
109 | };
110 | MifareClassic.prototype.mif_calc_crc16 = function(input) {
111 | var crc = 51084;
112 | for (var i = 0;i < input.length;i++) {
113 | crc = crc ^ input[i] << 8;
114 | for (var j = 0;j < 8;j++) {
115 | if (crc & 32768) {
116 | crc = crc << 1 ^ 4129;
117 | } else {
118 | crc = crc << 1;
119 | }
120 | }
121 | }
122 | return crc;
123 | };
124 | MifareClassic.prototype.copy_auth_keys = function(data, dev) {
125 | for (var i = 0;i < 6;i++) {
126 | data[i] = dev.auth_key[i];
127 | }
128 | for (var i = 0;i < 6;i++) {
129 | data[i + 10] = 255;
130 | }
131 | return data;
132 | };
133 | MifareClassic.prototype.read_physical = function(device, phy_block, cnt, cb) {
134 | var self = this;
135 | var callback = cb;
136 | var dev = device;
137 | var readed = new Uint8Array;
138 | var max_block = 1024 / 16;
139 | if (cnt != null) {
140 | max_block = phy_block + cnt;
141 | }
142 | function fast_read(phy_block, data, max_block) {
143 | if (phy_block == 3 && data[57] != 105) {
144 | var nfc_cnt;
145 | for (nfc_cnt = 0;data[18 + nfc_cnt * 2 + 0] == 3 && data[18 + nfc_cnt * 2 + 1] == 225;nfc_cnt++) {
146 | }
147 | var new_num = (nfc_cnt + 1) * 4;
148 | if (new_num < max_block) {
149 | return new_num;
150 | } else {
151 | return max_block;
152 | }
153 | } else {
154 | return max_block;
155 | }
156 | }
157 | function read_next(phy_block) {
158 | var blk_no = phy_block;
159 | dev.publicAuthentication(blk_no, function(rc, data) {
160 | if (rc) {
161 | return callback(rc);
162 | }
163 | dev.read_block(blk_no, function(rc, bn) {
164 | if (rc) {
165 | return callback(rc);
166 | }
167 | var bn = new Uint8Array(bn);
168 | if (blk_no % 4 == 3) {
169 | bn = self.copy_auth_keys(bn, dev);
170 | }
171 | readed = UTIL_concat(readed, bn);
172 | max_block = fast_read(blk_no, readed, max_block);
173 | if (blk_no + 1 >= max_block) {
174 | return callback(readed);
175 | } else {
176 | return read_next(blk_no + 1, cb);
177 | }
178 | });
179 | });
180 | }
181 | read_next(phy_block);
182 | };
183 | MifareClassic.prototype.read = function(device, cb) {
184 | var self = this;
185 | if (!cb) {
186 | cb = defaultCallback;
187 | }
188 | var callback = cb;
189 | var card = new Uint8Array;
190 | self.read_physical(device, 0, null, function(data) {
191 | for (var i = 0;i < Math.ceil(data.length / 16);i++) {
192 | console.log(UTIL_fmt("[DEBUG] Sector[" + UTIL_BytesToHex([i]) + "] " + UTIL_BytesToHex(data.subarray(i * 16, i * 16 + 16))));
193 | }
194 | var GPB = data[57];
195 | if (GPB == 105) {
196 | console.log("[DEBUG] Sector 0 is non-personalized (0x69).");
197 | } else {
198 | var DA = (GPB & 128) >> 7;
199 | var MA = (GPB & 64) >> 6;
200 | var ADV = (GPB & 3) >> 0;
201 | var nfc_cnt;
202 | for (nfc_cnt = 0;data[18 + nfc_cnt * 2 + 0] == 3 && data[18 + nfc_cnt * 2 + 1] == 225;nfc_cnt++) {
203 | }
204 | var tlv = new Uint8Array;
205 | for (var i = 1;i <= nfc_cnt;i++) {
206 | tlv = UTIL_concat(tlv, data.subarray(i * 64, i * 64 + 48));
207 | }
208 | for (var i = 0;i < tlv.length;i++) {
209 | switch(tlv[i]) {
210 | case 0:
211 | console.log("[DEBUG] NULL TLV.");
212 | break;
213 | case 254:
214 | console.log("[DEBUG] Terminator TLV.");
215 | return;
216 | case 3:
217 | var len = tlv[i + 1];
218 | if (len + 2 > tlv.length) {
219 | console.log("[WARN] Vlen:" + len + " > totla len:" + tlv.length);
220 | }
221 | return callback(0, (new Uint8Array(tlv.subarray(i + 2, i + 2 + len))).buffer);
222 | default:
223 | console.log("[ERROR] Unsupported TLV: " + UTIL_BytesToHex(tlv[0]));
224 | return;
225 | }
226 | }
227 | }
228 | });
229 | };
230 | MifareClassic.prototype.read_logic = function(device, logic_block, cnt, cb) {
231 | var self = this;
232 | var callback = cb;
233 | var card = new Uint8Array;
234 | function next_logic(logic_block, cnt) {
235 | var blk_no = logic_block;
236 | var count = cnt;
237 | if (count <= 0) {
238 | return callback(card);
239 | }
240 | self.read_physical(device, self.log2phy(logic_block), 1, function(data) {
241 | card = UTIL_concat(card, data);
242 | next_logic(blk_no + 1, count - 1);
243 | });
244 | }
245 | next_logic(logic_block, cnt);
246 | };
247 | MifareClassic.prototype.compose = function(ndef) {
248 | var self = this;
249 | var ndef_tlv = new Uint8Array([3, ndef.length]);
250 | var terminator_tlv = new Uint8Array([254]);
251 | var TLV = UTIL_concat(ndef_tlv, UTIL_concat(new Uint8Array(ndef), terminator_tlv));
252 | var TLV_sector_num = Math.ceil(TLV.length / 48);
253 | var TLV_blocks = new Uint8Array;
254 | for (var i = 0;i < TLV_sector_num;i++) {
255 | TLV_blocks = UTIL_concat(TLV_blocks, TLV.subarray(i * 48, (i + 1) * 48));
256 | var padding;
257 | if (i + 1 == TLV_sector_num) {
258 | padding = new Uint8Array(48 - TLV.length % 48);
259 | } else {
260 | padding = new Uint8Array(0);
261 | }
262 | TLV_blocks = UTIL_concat(TLV_blocks, padding);
263 | TLV_blocks = UTIL_concat(TLV_blocks, new Uint8Array([211, 247, 211, 247, 211, 247, 127, 7, 136, 64, 255, 255, 255, 255, 255, 255]));
264 | }
265 | var classic_header = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 225, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 160, 161, 162, 163, 164, 165, 120, 119, 136, 193, 255, 255, 255, 255, 255, 255]);
266 | for (var i = 0;i < TLV_sector_num;i++) {
267 | classic_header[16 + (i + 1) * 2 + 0] = 3;
268 | classic_header[16 + (i + 1) * 2 + 1] = 225;
269 | }
270 | classic_header[16] = self.mif_calc_crc8(classic_header.subarray(17, 48));
271 | var ret = UTIL_concat(classic_header, TLV_blocks);
272 | return ret;
273 | };
274 | MifareClassic.prototype.write_physical = function(device, block_no, key, all_data, cb) {
275 | var dev = device;
276 | var blk_no = block_no;
277 | var data = all_data;
278 | var callback = cb;
279 | var self = this;
280 | if (data.length == 0) {
281 | return callback(0);
282 | }
283 | if (data.length < 16) {
284 | data = UTIL_concat(data, new Uint8Array(16 - data.length));
285 | }
286 | function authenticationCallback(rc, dummy) {
287 | if (rc) {
288 | return callback(rc);
289 | }
290 | var block_data = data.subarray(0, 16);
291 | dev.write_block(blk_no, block_data, function(rc) {
292 | if (rc) {
293 | return callback(rc);
294 | }
295 | self.write_physical(dev, blk_no + 1, key, data.subarray(16), callback);
296 | }, self.WRITE_COMMAND);
297 | }
298 | if (key == null) {
299 | dev.publicAuthentication(blk_no, authenticationCallback);
300 | } else {
301 | dev.privateAuthentication(blk_no, key, authenticationCallback);
302 | }
303 | };
304 | MifareClassic.prototype.write = function(device, ndef, cb) {
305 | var self = this;
306 | if (!cb) {
307 | cb = defaultCallback;
308 | }
309 | var callback = cb;
310 | var card = self.compose(new Uint8Array(ndef));
311 | var dev = device;
312 | var max_block = Math.ceil(card.length / 16);
313 | if (max_block > 1024 / 16) {
314 | console.log("write Classic() card is too big (max: 1024 bytes): " + card.length);
315 | return callback(3003);
316 | }
317 | self.write_physical(dev, 1, null, card.subarray(16), callback);
318 | };
319 | MifareClassic.prototype.write_logic = function(device, logic_block, all_data, cb) {
320 | var self = this;
321 | var callback = cb;
322 | function write_next(device, logic_block, all_data) {
323 | var dev = device;
324 | var blk_no = logic_block;
325 | var data = all_data;
326 | if (data.length == 0) {
327 | return callback(0);
328 | }
329 | self.write_physical(dev, self.log2phy(blk_no), null, data.subarray(0, 16), function(rc) {
330 | if (rc) {
331 | return callback(rc);
332 | }
333 | var gpb_phy = self.log2sec(blk_no) * 4 + 3;
334 | dev.read_block(gpb_phy, function(rc, gpb_data) {
335 | if (rc) {
336 | return callback(rc);
337 | }
338 | var gpb_data = new Uint8Array(gpb_data);
339 | gpb_data = self.copy_auth_keys(gpb_data, dev);
340 | if (gpb_phy == 3) {
341 | gpb_data[9] = 193;
342 | } else {
343 | gpb_data[9] = 64;
344 | }
345 | dev.write_block(gpb_phy, gpb_data, function(rc) {
346 | blk_no = blk_no + 1;
347 | data = data.subarray(16);
348 | return write_next(dev, blk_no, data);
349 | }, self.WRITE_COMMAND);
350 | });
351 | });
352 | }
353 | write_next(device, logic_block, all_data);
354 | };
355 | MifareClassic.prototype.emulate = function(device, ndef_obj, timeout, cb) {
356 | var data = this.compose(new Uint8Array(ndef_obj.compose()));
357 | return device.emulate_tag(data, timeout, cb);
358 | };
359 | function NDEF(raw, cb) {
360 | this.ndef = [];
361 | this.prepending = ["", "http://www.", "https://www.", "http://", "https://", "tel:", "mailto:", "ftp://anonymous:anonymous@", "ftp://ftp.", "ftps://", "sftp://", "smb://", "nfs://", "ftp://", "dav://", "news:", "telnet://", "imap:", "rtsp://", "urn:", "pop:", "sip:", "sips:", "tftp:", "btspp://", "btl2cpa://", "btgoep://", "tcpobex://", "irdaobex://", "file://", "urn:epc:id:", "urn:epc:tag:", "urn:epc:pat:", "urn:epc:raw:", "urn:epc:", "urn:nfc:"];
362 | if (raw) {
363 | this.ndef = this.parse(raw, cb);
364 | }
365 | }
366 | NDEF.prototype.parse = function(raw, cb) {
367 | var i;
368 | var ret = [];
369 | raw = new Uint8Array(raw);
370 | for (i = 0;i < raw.length;i++) {
371 | var MB = (raw[i] & 128) >> 7;
372 | var ME = (raw[i] & 64) >> 6;
373 | var CF = (raw[i] & 32) >> 5;
374 | var SR = (raw[i] & 16) >> 4;
375 | var IL = (raw[i] & 8) >> 3;
376 | var TNF = (raw[i] & 7) >> 0;
377 | var type_off;
378 | var type_len = raw[i + 1];
379 | var id;
380 | var type;
381 | var payload_off = 4 + type_len;
382 | var payload_len;
383 | var payload;
384 | if (SR) {
385 | type_off = 3;
386 | payload_off = 3 + type_len;
387 | payload_len = raw[i + 2];
388 | } else {
389 | type_off = 6;
390 | payload_off = 6 + type_len;
391 | payload_len = ((raw[i + 2] * 256 + raw[i + 3]) * 256 + raw[i + 4]) * 256 + raw[i + 5];
392 | }
393 | if (IL) {
394 | type_off += 1;
395 | var id_len = raw[i + type_off - 1];
396 | payload_off += 1 + id_len;
397 | var id_off = type_off + type_len;
398 | id = raw.subarray(i + id_off, i + id_off + id_len);
399 | } else {
400 | id = null;
401 | }
402 | type = new Uint8Array(raw.subarray(i + type_off, i + type_off + type_len));
403 | payload = new Uint8Array(raw.subarray(i + payload_off, i + payload_off + payload_len));
404 | if (1) {
405 | console.log("raw[i]: " + raw[i]);
406 | console.log("MB: " + MB);
407 | console.log("ME: " + ME);
408 | console.log("SR: " + SR);
409 | console.log("IL: " + IL);
410 | console.log("TNF: " + TNF);
411 | console.log("type_off: " + type_off);
412 | console.log("type_len: " + type_len);
413 | console.log("payload_off: " + payload_off);
414 | console.log("payload_len: " + payload_len);
415 | console.log("type: " + UTIL_BytesToHex(type));
416 | console.log("payload: " + UTIL_BytesToHex(payload));
417 | }
418 | switch(TNF) {
419 | case 1:
420 | ret.push(this.parse_RTD(type[0], payload));
421 | break;
422 | case 2:
423 | ret.push(this.parse_MIME(type, payload));
424 | break;
425 | case 4:
426 | ret.push(this.parse_ExternalType(type, payload));
427 | break;
428 | default:
429 | console.error("Unsupported TNF: " + TNF);
430 | break;
431 | }
432 | i = payload_off + payload_len - 1;
433 | if (ME) {
434 | break;
435 | }
436 | }
437 | if (cb) {
438 | cb(ret);
439 | }
440 | return ret;
441 | };
442 | NDEF.prototype.compose = function() {
443 | var out = new Uint8Array;
444 | var arr = [];
445 | for (var i = 0;i < this.ndef.length;i++) {
446 | var entry = this.ndef[i];
447 | switch(entry["type"]) {
448 | case "TEXT":
449 | ;
450 | case "Text":
451 | arr.push({"TNF":1, "TYPE":new Uint8Array([84]), "PAYLOAD":this.compose_RTD_TEXT(entry["lang"], entry["text"])});
452 | break;
453 | case "URI":
454 | arr.push({"TNF":1, "TYPE":new Uint8Array([85]), "PAYLOAD":this.compose_RTD_URI(entry["uri"])});
455 | break;
456 | case "MIME":
457 | arr.push({"TNF":2, "TYPE":new Uint8Array(UTIL_StringToBytes(entry["mime_type"])), "PAYLOAD":this.compose_MIME(entry["payload"])});
458 | break;
459 | case "AAR":
460 | arr.push({"TNF":4, "TYPE":new Uint8Array(UTIL_StringToBytes("android.com:pkg")), "PAYLOAD":this.compose_AAR(entry["aar"])});
461 | break;
462 | default:
463 | console.error("Unsupported RTD type:" + entry["type"]);
464 | break;
465 | }
466 | }
467 | for (var i = 0;i < arr.length;i++) {
468 | var flags = 16 | arr[i]["TNF"];
469 | flags |= i == 0 ? 128 : 0;
470 | flags |= i == arr.length - 1 ? 64 : 0;
471 | var type = arr[i]["TYPE"];
472 | var payload = arr[i]["PAYLOAD"];
473 | out = UTIL_concat(out, [flags, type.length, payload.length]);
474 | out = UTIL_concat(out, type);
475 | out = UTIL_concat(out, payload);
476 | }
477 | return out.buffer;
478 | };
479 | NDEF.prototype.add = function(d) {
480 | if ("uri" in d) {
481 | d["type"] = "URI";
482 | } else {
483 | if ("text" in d) {
484 | d["type"] = "TEXT";
485 | } else {
486 | if ("aar" in d) {
487 | d["type"] = "AAR";
488 | } else {
489 | if ("payload" in d) {
490 | d["type"] = "MIME";
491 | }
492 | }
493 | }
494 | }
495 | switch(d["type"]) {
496 | case "TEXT":
497 | ;
498 | case "Text":
499 | if (!("encoding" in d)) {
500 | d["encoding"] = "utf8";
501 | }
502 | if (!("lang" in d)) {
503 | d["lang"] = "en";
504 | }
505 | if ("text" in d) {
506 | this.ndef.push(d);
507 | return true;
508 | }
509 | break;
510 | case "URI":
511 | if ("uri" in d) {
512 | this.ndef.push(d);
513 | return true;
514 | }
515 | break;
516 | case "MIME":
517 | if ("mime_type" in d && "payload" in d) {
518 | this.ndef.push(d);
519 | return true;
520 | }
521 | ;
522 | case "AAR":
523 | if ("aar" in d) {
524 | this.ndef.push(d);
525 | return true;
526 | }
527 | break;
528 | default:
529 | console.log("Unsupported RTD type:" + d["type"]);
530 | break;
531 | }
532 | return false;
533 | };
534 | NDEF.prototype.parse_RTD = function(type, rtd) {
535 | switch(type) {
536 | case 84:
537 | return this.parse_RTD_TEXT(rtd);
538 | case 85:
539 | return this.parse_RTD_URI(rtd);
540 | default:
541 | console.log("Unsupported RTD type: " + type);
542 | }
543 | };
544 | NDEF.prototype.parse_MIME = function(mime_type, payload) {
545 | return{"type":"MIME", "mime_type":UTIL_BytesToString(mime_type), "payload":UTIL_BytesToString(payload)};
546 | };
547 | NDEF.prototype.compose_MIME = function(payload) {
548 | return new Uint8Array(UTIL_StringToBytes(payload));
549 | };
550 | NDEF.prototype.parse_AAR = function(payload) {
551 | return{"type":"AAR", "payload":UTIL_BytesToString(payload)};
552 | };
553 | NDEF.prototype.parse_ExternalType = function(type, payload) {
554 | if (UTIL_BytesToString(type) == "android.com:pkg") {
555 | return this.parse_AAR(payload);
556 | } else {
557 | return{"type":type, "payload":UTIL_BytesToString(payload)};
558 | }
559 | };
560 | NDEF.prototype.compose_AAR = function(payload) {
561 | return new Uint8Array(UTIL_StringToBytes(payload));
562 | };
563 | NDEF.prototype.parse_RTD_TEXT = function(rtd_text) {
564 | var utf16 = (rtd_text[0] & 128) >> 7;
565 | var lang_len = rtd_text[0] & 63;
566 | var lang = rtd_text.subarray(1, 1 + lang_len);
567 | var text = rtd_text.subarray(1 + lang_len, rtd_text.length);
568 | return{"type":"Text", "encoding":utf16 ? "utf16" : "utf8", "lang":UTIL_BytesToString(lang), "text":UTIL_BytesToString(text)};
569 | };
570 | NDEF.prototype.compose_RTD_TEXT = function(lang, text) {
571 | var l = lang.length;
572 | l = l > 63 ? 63 : l;
573 | return new Uint8Array([l].concat(UTIL_StringToBytes(lang.substring(0, l))).concat(UTIL_StringToBytes(text)));
574 | };
575 | NDEF.prototype.parse_RTD_URI = function(rtd_uri) {
576 | return{"type":"URI", "uri":this.prepending[rtd_uri[0]] + UTIL_BytesToString(rtd_uri.subarray(1, rtd_uri.length))};
577 | };
578 | NDEF.prototype.compose_RTD_URI = function(uri) {
579 | var longest = -1;
580 | var longest_i;
581 | for (var i = 0;i < this.prepending.length;i++) {
582 | if (uri.substring(0, this.prepending[i].length) == this.prepending[i]) {
583 | if (this.prepending[i].length > longest) {
584 | longest_i = i;
585 | longest = this.prepending[i].length;
586 | }
587 | }
588 | }
589 | return new Uint8Array([longest_i].concat(UTIL_StringToBytes(uri.substring(longest))));
590 | };
591 | function NFC() {
592 | var self = this;
593 | function construct_ndef_obj(ndef_array) {
594 | var ndef_obj = new NDEF;
595 | for (var i = 0;i < ndef_array.length;i++) {
596 | ndef_obj.add(ndef_array[i]);
597 | }
598 | return ndef_obj;
599 | }
600 | function wait_for_passive_target(device, cb, timeout) {
601 | if (timeout == undefined) {
602 | timeout = 9999999999;
603 | }
604 | device.wait_for_passive_target(timeout, function(rc, tag_type, tag_id) {
605 | if (rc) {
606 | console.log("NFC.wait_for_passive_target() = " + rc);
607 | cb(rc);
608 | return rc;
609 | }
610 | console.log("[DEBUG] nfc.wait_for_passive_target: " + tag_type + " with ID: " + UTIL_BytesToHex(new Uint8Array(tag_id)));
611 | cb(rc, tag_type, tag_id);
612 | });
613 | }
614 | var pub = {"findDevices":function(cb) {
615 | var device = new usbSCL3711;
616 | window.setTimeout(function() {
617 | device.open(0, function(rc) {
618 | if (rc) {
619 | console.log("NFC.device.open() = " + rc);
620 | cb([]);
621 | return rc;
622 | }
623 | device.vendorId = device.dev.dev.vendorId;
624 | device.productId = device.dev.dev.productId;
625 | cb([device]);
626 | }, function() {
627 | console.debug("device.onclose() is called.");
628 | });
629 | }, 1E3);
630 | }, "read":function(device, options, cb) {
631 | var timeout = options["timeout"];
632 | var callback = cb;
633 | wait_for_passive_target(device, function(rc, tag_type, tag_id) {
634 | var tag = new Tag(tag_type, tag_id);
635 | if (!tag) {
636 | console.log("nfc.read: unknown tag_type: " + tag_type);
637 | return;
638 | }
639 | tag.read(device, function(rc, ndef) {
640 | if (rc) {
641 | console.log("NFC.read.read() = " + rc);
642 | callback(null, null);
643 | return rc;
644 | }
645 | var ndef_obj = new NDEF(ndef);
646 | callback(tag_type + ".ndef", ndef_obj);
647 | });
648 | }, timeout);
649 | }, "read_logic":function(device, logic_block, cnt, cb) {
650 | var callback = cb;
651 | wait_for_passive_target(device, function(rc, tag_type, tag_id) {
652 | var tag = new Tag(tag_type, tag_id);
653 | if (!tag) {
654 | console.log("nfc.read_logic: unknown tag_type: " + tag_type);
655 | return;
656 | }
657 | if (!tag.read_logic) {
658 | console.log("nfc.read: " + tag_type + " doesn't support reading logic block");
659 | return;
660 | }
661 | tag.read_logic(device, logic_block, cnt, function(data) {
662 | callback(0, data);
663 | });
664 | });
665 | }, "wait_for_tag":function(device, timeout, cb) {
666 | var callback = cb;
667 | var loop = function(timeout) {
668 | wait_for_passive_target(device, function(rc, tag_type, tag_id) {
669 | if (rc >= 0) {
670 | callback(tag_type, tag_id);
671 | } else {
672 | if (timeout > 0) {
673 | window.setTimeout(function() {
674 | loop(timeout - 250);
675 | }, 250);
676 | } else {
677 | callback(null, null);
678 | }
679 | }
680 | });
681 | };
682 | loop(timeout);
683 | }, "write":function(device, content, cb, timeout) {
684 | wait_for_passive_target(device, function(rc, tag_type, tag_id) {
685 | var tag = new Tag(tag_type, tag_id);
686 | if (!tag) {
687 | console.log("nfc.write: unknown tag_type: " + tag_type);
688 | return;
689 | }
690 | var ndef_obj = construct_ndef_obj(content["ndef"]);
691 | tag.write(device, ndef_obj.compose(), function(rc) {
692 | cb(rc);
693 | });
694 | }, timeout);
695 | }, "write_logic":function(device, logic_block, data, cb) {
696 | var callback = cb;
697 | wait_for_passive_target(device, function(rc, tag_type, tag_id) {
698 | var tag = new Tag(tag_type, tag_id);
699 | if (!tag) {
700 | console.log("nfc.write_logic: unknown tag_type: " + tag_type);
701 | return;
702 | }
703 | if (!tag.write_logic) {
704 | console.log("nfc.read: " + tag_type + " doesn't support reading logic block");
705 | return;
706 | }
707 | tag.write_logic(device, logic_block, data, function(rc) {
708 | callback(rc);
709 | });
710 | });
711 | }, "write_physical":function(device, physical_block, key, data, cb) {
712 | var callback = cb;
713 | wait_for_passive_target(device, function(rc, tag_type, tag_id) {
714 | var tag = new Tag(tag_type, tag_id);
715 | if (!tag) {
716 | console.log("nfc.write_physical: unknown tag_type: " + tag_type);
717 | return;
718 | }
719 | if (!tag.write_physical) {
720 | console.log("nfc.read: " + tag_type + " doesn't support reading physical block");
721 | return;
722 | }
723 | tag.write_physical(device, physical_block, key, data, function(rc) {
724 | callback(rc);
725 | });
726 | });
727 | }, "emulate_tag":function(device, content, cb, timeout) {
728 | if (timeout == undefined) {
729 | timeout = 9999999999;
730 | }
731 | wait_for_passive_target(device, function(rc, tag_type, tag_id) {
732 | var tt2 = new TT2;
733 | var ndef_obj = construct_ndef_obj(content["ndef"]);
734 | tt2.emulate(device, ndef_obj, timeout, function(rc) {
735 | cb(rc);
736 | });
737 | }, timeout);
738 | }};
739 | return pub;
740 | }
741 | chrome.nfc = NFC();
742 | function devManager() {
743 | this.devs = [];
744 | this.enumerators = [];
745 | }
746 | devManager.prototype.dropDevice = function(dev) {
747 | var tmp = this.devs;
748 | this.devs = [];
749 | var present = false;
750 | for (var i = 0;i < tmp.length;++i) {
751 | if (tmp[i] !== dev) {
752 | this.devs.push(tmp[i]);
753 | } else {
754 | present = true;
755 | }
756 | }
757 | if (!present) {
758 | return;
759 | }
760 | if (dev.dev) {
761 | chrome.usb.releaseInterface(dev.dev, 0, function() {
762 | console.log(UTIL_fmt("released"));
763 | });
764 | chrome.usb.closeDevice(dev.dev, function() {
765 | console.log(UTIL_fmt("closed"));
766 | });
767 | dev.dev = null;
768 | }
769 | console.log(this.devs.length + " devices remaining");
770 | };
771 | devManager.prototype.closeAll = function(cb) {
772 | console.debug("devManager.closeAll() is called");
773 | var d = this.devs.slice(0);
774 | for (var i = 0;i < d.length;++i) {
775 | d[i].close();
776 | }
777 | if (cb) {
778 | cb();
779 | }
780 | };
781 | devManager.prototype.enumerate = function(cb) {
782 | var self = this;
783 | function enumerated(d, acr122) {
784 | var nDevice = 0;
785 | if (d && d.length != 0) {
786 | console.log(UTIL_fmt("Enumerated " + d.length + " devices"));
787 | console.log(d);
788 | nDevice = d.length;
789 | } else {
790 | if (d) {
791 | console.log("No devices found");
792 | } else {
793 | console.log("Lacking permission?");
794 | do {
795 | (function(cb) {
796 | if (cb) {
797 | window.setTimeout(function() {
798 | cb(-666);
799 | }, 0);
800 | }
801 | })(self.enumerators.shift());
802 | } while (self.enumerators.length);
803 | return;
804 | }
805 | }
806 | for (var i = 0;i < nDevice;++i) {
807 | (function(dev, i) {
808 | window.setTimeout(function() {
809 | chrome.usb.claimInterface(dev, 0, function(result) {
810 | console.log(UTIL_fmt("claimed"));
811 | console.log(dev);
812 | self.devs.push(new llSCL3711(dev, acr122));
813 | if (i == nDevice - 1) {
814 | var u8 = new Uint8Array(4);
815 | u8[0] = nDevice >> 24;
816 | u8[1] = nDevice >> 16;
817 | u8[2] = nDevice >> 8;
818 | u8[3] = nDevice;
819 | while (self.enumerators.length) {
820 | (function(cb) {
821 | window.setTimeout(function() {
822 | if (cb) {
823 | cb(0, u8);
824 | }
825 | }, 20);
826 | })(self.enumerators.shift());
827 | }
828 | }
829 | });
830 | }, 0);
831 | })(d[i], i);
832 | }
833 | }
834 | if (this.devs.length != 0) {
835 | var u8 = new Uint8Array(4);
836 | u8[0] = this.devs.length >> 24;
837 | u8[1] = this.devs.length >> 16;
838 | u8[2] = this.devs.length >> 8;
839 | u8[3] = this.devs.length;
840 | if (cb) {
841 | cb(0, u8);
842 | }
843 | } else {
844 | var first = this.enumerators.length == 0;
845 | this.enumerators.push(cb);
846 | if (first) {
847 | window.setTimeout(function() {
848 | chrome.usb.findDevices({"vendorId":1254, "productId":21905}, function(d) {
849 | if (d && d.length != 0) {
850 | enumerated(d, false);
851 | } else {
852 | chrome.usb.findDevices({"vendorId":1839, "productId":8704}, function(d) {
853 | if (d && d.length != 0) {
854 | enumerated(d, true);
855 | }
856 | });
857 | }
858 | });
859 | }, 0);
860 | }
861 | }
862 | };
863 | devManager.prototype.open = function(which, who, cb) {
864 | var self = this;
865 | this.enumerate(function() {
866 | var dev = self.devs[which];
867 | if (dev) {
868 | dev.registerClient(who);
869 | }
870 | if (cb) {
871 | cb(dev || null);
872 | }
873 | });
874 | };
875 | devManager.prototype.close = function(singledev, who) {
876 | var alldevs = this.devs;
877 | for (var i = 0;i < alldevs.length;++i) {
878 | var dev = alldevs[i];
879 | var nremaining = dev.deregisterClient(who);
880 | }
881 | };
882 | var defaultCallback = function(rc, data) {
883 | var msg = "defaultCallback(" + rc;
884 | if (data) {
885 | msg += ", " + UTIL_BytesToHex(new Uint8Array(data));
886 | }
887 | msg += ")";
888 | console.log(UTIL_fmt(msg));
889 | };
890 | var dev_manager = new devManager;
891 | var scl3711_id = 0;
892 | function usbSCL3711() {
893 | this.dev = null;
894 | this.cid = ++scl3711_id & 16777215;
895 | this.rxframes = [];
896 | this.rxcb = null;
897 | this.onclose = null;
898 | this.detected_tag = null;
899 | this.auth_key = null;
900 | this.authed_sector = null;
901 | this.KEYS = [new Uint8Array([255, 255, 255, 255, 255, 255]), new Uint8Array([211, 247, 211, 247, 211, 247]), new Uint8Array([160, 161, 162, 163, 164, 165])];
902 | this.strerror = function(errno) {
903 | var err = {1:"time out, the target has not answered", 2:"checksum error during rf communication", 3:"parity error during rf communication", 4:"erroneous bit count in anticollision", 5:"framing error during mifare operation", 6:"abnormal bit collision in 106 kbps anticollision", 7:"insufficient communication buffer size", 9:"rf buffer overflow detected by ciu", 10:"rf field not activated in time by active mode peer", 11:"protocol error during rf communication", 13:"overheated - antenna drivers deactivated",
904 | 14:"internal buffer overflow", 16:"invalid command parameter", 18:"unsupported command from initiator", 19:"format error during rf communication", 20:"mifare authentication error", 24:"not support NFC secure", 25:"i2c bus line is busy", 35:"wrong uid check byte (14443-3)", 37:"command invalid in current dep state", 38:"operation not allowed in this configuration", 39:"not acceptable command due to context", 41:"released by initiator while operating as target", 42:"card ID does not match", 43:"the card previously activated has disapperaed",
905 | 44:"Mismatch between NFCID3 initiator and target in DEP 212/424 kbps", 45:"Over-current event has been detected", 46:"NAD missing in DEP frame", 47:"deselected by initiator while operating as target", 49:"initiator rf-off state detected in passive mode", 127:"pn53x application level error"};
906 | if (errno in err) {
907 | return "[" + errno + "] " + err[errno];
908 | } else {
909 | return "Unknown error: " + errno;
910 | }
911 | };
912 | }
913 | usbSCL3711.prototype.notifyFrame = function(cb) {
914 | if (this.rxframes.length != 0) {
915 | if (cb) {
916 | window.setTimeout(cb, 0);
917 | }
918 | } else {
919 | this.rxcb = cb;
920 | }
921 | };
922 | usbSCL3711.prototype.receivedFrame = function(frame) {
923 | if (!this.rxframes) {
924 | return false;
925 | }
926 | this.rxframes.push(frame);
927 | var cb = this.rxcb;
928 | this.rxcb = null;
929 | if (cb) {
930 | window.setTimeout(cb, 0);
931 | }
932 | return true;
933 | };
934 | usbSCL3711.prototype.readFrame = function() {
935 | if (this.rxframes.length == 0) {
936 | throw "rxframes empty!";
937 | }
938 | var frame = this.rxframes.shift();
939 | return frame;
940 | };
941 | usbSCL3711.prototype.read = function(timeout, cb) {
942 | if (!this.dev) {
943 | cb(1);
944 | return;
945 | }
946 | var tid = null;
947 | var callback = cb;
948 | var self = this;
949 | function schedule_cb(a, b, c) {
950 | if (tid) {
951 | window.clearTimeout(tid);
952 | tid = null;
953 | }
954 | var C = callback;
955 | if (C) {
956 | callback = null;
957 | window.setTimeout(function() {
958 | C(a, b, c);
959 | }, 0);
960 | }
961 | }
962 | function read_timeout() {
963 | if (!callback || !tid) {
964 | return;
965 | }
966 | console.log(UTIL_fmt("[" + self.cid.toString(16) + "] timeout!"));
967 | tid = null;
968 | }
969 | function read_frame() {
970 | if (!callback || !tid) {
971 | return;
972 | }
973 | var f = new Uint8Array(self.readFrame());
974 | if (f.length == 6 && f[0] == 0 && f[1] == 0 && f[2] == 255 && f[3] == 0 && f[4] == 255 && f[5] == 0) {
975 | self.notifyFrame(read_frame);
976 | return;
977 | }
978 | if (f.length > 10) {
979 | if (f[0] == 128) {
980 | f = UTIL_concat(new Uint8Array([0, 0, 255, 1, 255]), new Uint8Array(f.subarray(10)));
981 | } else {
982 | if (f[0] == 131) {
983 | f = UTIL_concat(new Uint8Array([0, 0, 255, 1, 255]), new Uint8Array(f.subarray(10)));
984 | }
985 | }
986 | }
987 | if (f.length == 7) {
988 | if (f[5] == 144 && f[6] == 0) {
989 | schedule_cb(0, f.buffer);
990 | return;
991 | } else {
992 | if (f[5] == 99 && f[6] == 0) {
993 | schedule_cb(2730, f.buffer);
994 | return;
995 | }
996 | }
997 | } else {
998 | if (f.length > 6 && f[0] == 0 && f[1] == 0 && f[2] == 255 && f[3] + f[4] == 256) {
999 | if (f[5] == 213 && f[6] == 65) {
1000 | if (f[7] == 0) {
1001 | schedule_cb(0, (new Uint8Array(f.subarray(8, f.length - 2))).buffer);
1002 | } else {
1003 | console.log("ERROR: InDataExchange reply status = " + self.strerror(f[7]));
1004 | }
1005 | return;
1006 | } else {
1007 | if (f[5] == 213 && f[6] == 141) {
1008 | schedule_cb(0, (new Uint8Array(f.subarray(8, f.length - 2))).buffer);
1009 | return;
1010 | } else {
1011 | if (f[5] == 213 && f[6] == 137) {
1012 | if (f[7] == 0) {
1013 | schedule_cb(0, (new Uint8Array(f.subarray(8, f.length - 2))).buffer);
1014 | } else {
1015 | console.log("ERROR: TgGetInitiatorCommand reply status = " + self.strerror(f[7]));
1016 | }
1017 | return;
1018 | } else {
1019 | if (f[5] == 213 && f[6] == 145) {
1020 | if (f[7] == 0) {
1021 | schedule_cb(0, (new Uint8Array(f.subarray(8, f.length - 2))).buffer);
1022 | } else {
1023 | console.log("ERROR: TgResponseToInitiator reply status = " + self.strerror(f[7]));
1024 | }
1025 | return;
1026 | } else {
1027 | if (f[5] == 213 && f[6] == 51) {
1028 | schedule_cb(0, (new Uint8Array(f.subarray(7, f.length - 2))).buffer);
1029 | return;
1030 | } else {
1031 | if (f[5] == 213 && f[6] == 75) {
1032 | if (f[7] == 1 && f[8] == 1) {
1033 | console.log("DEBUG: InListPassiveTarget SENS_REQ(ATQA)=0x" + (f[9] * 256 + f[10]).toString(16) + ", SEL_RES(SAK)=0x" + f[11].toString(16));
1034 | var NFCIDLength = f[12];
1035 | var tag_id = (new Uint8Array(f.subarray(13, 13 + NFCIDLength))).buffer;
1036 | console.log("DEBUG: tag_id: " + UTIL_BytesToHex(new Uint8Array(tag_id)));
1037 | if (f[9] == 0 && f[10] == 68) {
1038 | console.log("DEBUG: found Mifare Ultralight (106k type A)");
1039 | self.detected_tag = "Mifare Ultralight";
1040 | self.authed_sector = null;
1041 | self.auth_key = null;
1042 | schedule_cb(0, "tt2", tag_id);
1043 | return;
1044 | } else {
1045 | if (f[9] == 0 && f[10] == 4) {
1046 | console.log("DEBUG: found Mifare Classic 1K (106k type A)");
1047 | self.detected_tag = "Mifare Classic 1K";
1048 | self.authed_sector = null;
1049 | self.auth_key = null;
1050 | schedule_cb(0, "mifare_classic", tag_id);
1051 | return;
1052 | }
1053 | }
1054 | } else {
1055 | console.log("DEBUG: found " + f[7] + " target, tg=" + f[8]);
1056 | return;
1057 | }
1058 | }
1059 | }
1060 | }
1061 | }
1062 | }
1063 | }
1064 | }
1065 | }
1066 | schedule_cb(2184, f.buffer);
1067 | }
1068 | tid = window.setTimeout(read_timeout, 1E3 * timeout);
1069 | self.notifyFrame(read_frame);
1070 | };
1071 | usbSCL3711.prototype.write = function(data) {
1072 | this.dev.writeFrame(data);
1073 | };
1074 | usbSCL3711.prototype.exchange = function(data, timeout, cb) {
1075 | this.write(data);
1076 | this.read(timeout, cb);
1077 | };
1078 | usbSCL3711.prototype.acr122_reset_to_good_state = function(cb) {
1079 | var self = this;
1080 | var callback = cb;
1081 | self.exchange((new Uint8Array([0, 0, 255, 0, 255, 0])).buffer, 1, function(rc, data) {
1082 | if (rc) {
1083 | console.warn("[FIXME] acr122_reset_to_good_state: rc = " + rc);
1084 | }
1085 | self.exchange((new Uint8Array([98, 0, 0, 0, 0, 0, 0, 1, 0, 0])).buffer, 10, function(rc, data) {
1086 | if (rc) {
1087 | console.warn("[FIXME] icc_power_on: rc = " + rc);
1088 | }
1089 | console.log("[DEBUG] icc_power_on: turn on the device power");
1090 | if (callback) {
1091 | window.setTimeout(function() {
1092 | callback(0);
1093 | }, 100);
1094 | }
1095 | });
1096 | });
1097 | };
1098 | usbSCL3711.prototype.acr122_set_buzzer = function(enable, cb) {
1099 | var self = this;
1100 | var callback = cb;
1101 | var buzz = enable ? 255 : 0;
1102 | self.exchange((new Uint8Array([107, 5, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 82, buzz, 0])).buffer, 1, function(rc, data) {
1103 | if (callback) {
1104 | callback(rc, data);
1105 | }
1106 | });
1107 | };
1108 | usbSCL3711.prototype.acr122_load_authentication_keys = function(key, loc, cb) {
1109 | var self = this;
1110 | var callback = cb;
1111 | if (key == null) {
1112 | key = self.KEYS[0];
1113 | } else {
1114 | if (typeof key != "object") {
1115 | key = self.KEYS[key];
1116 | }
1117 | }
1118 | var u8 = new Uint8Array([107, 11, 0, 0, 0, 0, 0, 0, 0, 0, 255, 130, 0, loc, 6]);
1119 | u8 = UTIL_concat(u8, key);
1120 | self.exchange(u8.buffer, 1, function(rc, data) {
1121 | console.log("[DEBUG] acr122_load_authentication_keys(loc: " + loc + ", key: " + UTIL_BytesToHex(key) + ") = " + rc);
1122 | if (callback) {
1123 | callback(rc, data);
1124 | }
1125 | });
1126 | };
1127 | usbSCL3711.prototype.acr122_authentication = function(block, loc, type, cb) {
1128 | var self = this;
1129 | var callback = cb;
1130 | self.exchange((new Uint8Array([107, 10, 0, 0, 0, 0, 0, 0, 0, 0, 255, 134, 0, 0, 5, 1, 0, block, type, loc])).buffer, 1, function(rc, data) {
1131 | console.log("[DEBUG] acr122_authentication(loc: " + loc + ", type: " + type + ", block: " + block + ") = " + rc);
1132 | if (callback) {
1133 | callback(rc, data);
1134 | }
1135 | });
1136 | };
1137 | usbSCL3711.prototype.publicAuthentication = function(block, cb) {
1138 | var self = this;
1139 | var callback = cb;
1140 | var sector = Math.floor(block / 4);
1141 | function try_keyA(k) {
1142 | var ki = k;
1143 | if (ki >= 3) {
1144 | if (callback) {
1145 | callback(4095);
1146 | }
1147 | return;
1148 | }
1149 | self.acr122_load_authentication_keys(ki, 0, function(rc, data) {
1150 | if (rc) {
1151 | return;
1152 | }
1153 | self.acr122_authentication(block, 0, 96, function(rc, data) {
1154 | if (rc) {
1155 | return try_keyA(ki + 1);
1156 | }
1157 | self.authed_sector = sector;
1158 | self.auth_key = self.KEYS[ki];
1159 | self.acr122_load_authentication_keys(self.KEYS[0], 1, function(rc, data) {
1160 | self.acr122_authentication(block, 1, 97, function(rc, data) {
1161 | if (callback) {
1162 | callback(rc, data);
1163 | }
1164 | });
1165 | });
1166 | });
1167 | });
1168 | }
1169 | if (self.detected_tag == "Mifare Classic 1K") {
1170 | if (self.dev && self.dev.acr122) {
1171 | if (self.authed_sector != sector) {
1172 | console.log("[DEBUG] Public Authenticate sector " + sector);
1173 | try_keyA(0);
1174 | } else {
1175 | if (callback) {
1176 | callback(0, null);
1177 | }
1178 | }
1179 | } else {
1180 | if (callback) {
1181 | callback(0, null);
1182 | }
1183 | }
1184 | } else {
1185 | if (callback) {
1186 | callback(0, null);
1187 | }
1188 | }
1189 | };
1190 | usbSCL3711.prototype.privateAuthentication = function(block, key, cb) {
1191 | var self = this;
1192 | var callback = cb;
1193 | var sector = Math.floor(block / 4);
1194 | if (self.detected_tag == "Mifare Classic 1K") {
1195 | if (self.dev && self.dev.acr122) {
1196 | if (self.authed_sector != sector) {
1197 | console.log("[DEBUG] Private Authenticate sector " + sector);
1198 | self.acr122_load_authentication_keys(key, 1, function(rc, data) {
1199 | self.acr122_authentication(block, 1, 97, function(rc, data) {
1200 | if (rc) {
1201 | console.log("KEY B AUTH ERROR");
1202 | return rc;
1203 | }
1204 | if (callback) {
1205 | callback(rc, data);
1206 | }
1207 | });
1208 | });
1209 | } else {
1210 | if (callback) {
1211 | callback(0, null);
1212 | }
1213 | }
1214 | } else {
1215 | if (callback) {
1216 | callback(0, null);
1217 | }
1218 | }
1219 | } else {
1220 | if (callback) {
1221 | callback(0, null);
1222 | }
1223 | }
1224 | };
1225 | usbSCL3711.prototype.acr122_set_timeout = function(timeout, cb) {
1226 | var self = this;
1227 | var callback = cb;
1228 | var unit = Math.ceil(timeout / 5);
1229 | if (unit >= 255) {
1230 | unit = 255;
1231 | }
1232 | console.log("[DEBUG] acr122_set_timeout(round up to " + unit * 5 + " secs)");
1233 | self.exchange((new Uint8Array([107, 5, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 65, unit, 0])).buffer, 1, function(rc, data) {
1234 | if (callback) {
1235 | callback(rc, data);
1236 | }
1237 | });
1238 | };
1239 | usbSCL3711.prototype.open = function(which, cb, onclose) {
1240 | this.rxframes = [];
1241 | this.onclose = onclose;
1242 | this.cid &= 16777215;
1243 | this.cid |= which + 1 << 24;
1244 | var self = this;
1245 | var callback = cb;
1246 | dev_manager.open(which, this, function(device) {
1247 | self.dev = device;
1248 | var result = self.dev != null ? 0 : 1;
1249 | if (self.dev && self.dev.acr122) {
1250 | self.acr122_reset_to_good_state(function(rc) {
1251 | if (rc) {
1252 | console.error("[ERROR] acr122_reset_to_good_state() returns " + rc);
1253 | return callback ? callback(rc) : null;
1254 | }
1255 | self.acr122_set_buzzer(false, function(rc) {
1256 | if (rc) {
1257 | console.error("[ERROR] acr122_reset_to_good_state() returns " + rc);
1258 | return callback ? callback(rc) : null;
1259 | }
1260 | if (callback) {
1261 | callback(result);
1262 | }
1263 | });
1264 | });
1265 | } else {
1266 | if (callback) {
1267 | callback(result);
1268 | }
1269 | }
1270 | });
1271 | };
1272 | usbSCL3711.prototype.close = function() {
1273 | var self = this;
1274 | function deselect_release(cb) {
1275 | self.exchange(self.makeFrame(68, new Uint8Array([1])), 1, function(rc, data) {
1276 | self.exchange(self.makeFrame(82, new Uint8Array([1])), 1, function(rc, data) {
1277 | });
1278 | });
1279 | }
1280 | function dev_manager_close() {
1281 | self.rxframes = null;
1282 | if (self.dev) {
1283 | dev_manager.close(self.dev, self);
1284 | self.dev = null;
1285 | }
1286 | }
1287 | deselect_release(dev_manager_close);
1288 | };
1289 | usbSCL3711.prototype.makeFrame = function(cmd, data) {
1290 | var r8 = new Uint8Array(data ? data : []);
1291 | var p8 = new Uint8Array(r8.length + 2);
1292 | var dcslen = r8.length + 2;
1293 | if (this.dev.acr122) {
1294 | var apdu_len = 5 + 2 + r8.length;
1295 | var c8 = new Uint8Array(10);
1296 | c8[0] = 107;
1297 | c8[1] = apdu_len >> 0 & 255;
1298 | c8[2] = apdu_len >> 8 & 255;
1299 | c8[3] = apdu_len >> 16 & 255;
1300 | c8[4] = apdu_len >> 24 & 255;
1301 | c8[5] = 0;
1302 | c8[6] = 0;
1303 | c8[7] = 0;
1304 | c8[8] = 0;
1305 | c8[9] = 0;
1306 | var a8 = new Uint8Array(5);
1307 | a8[0] = 255;
1308 | a8[1] = 0;
1309 | a8[2] = 0;
1310 | a8[3] = 0;
1311 | a8[4] = r8.length + 2;
1312 | h8 = UTIL_concat(c8, a8);
1313 | } else {
1314 | var h8 = new Uint8Array(8);
1315 | h8[0] = 0;
1316 | h8[1] = 0;
1317 | h8[2] = 255;
1318 | h8[3] = 255;
1319 | h8[4] = 255;
1320 | h8[5] = dcslen >>> 8;
1321 | h8[6] = dcslen & 255;
1322 | h8[7] = 256 - (h8[5] + h8[6] & 255);
1323 | }
1324 | p8[0] = 212;
1325 | p8[1] = cmd;
1326 | var dcs = p8[0] + p8[1];
1327 | for (var i = 0;i < r8.length;++i) {
1328 | p8[2 + i] = r8[i];
1329 | dcs += r8[i];
1330 | }
1331 | var chksum = null;
1332 | if (this.dev.acr122) {
1333 | chksum = new Uint8Array([]);
1334 | } else {
1335 | chksum = new Uint8Array(2);
1336 | chksum[0] = 256 - (dcs & 255);
1337 | chksum[1] = 0;
1338 | }
1339 | return UTIL_concat(UTIL_concat(h8, p8), chksum).buffer;
1340 | };
1341 | usbSCL3711.prototype.wait_for_passive_target = function(timeout, cb) {
1342 | var self = this;
1343 | if (!cb) {
1344 | cb = defaultCallback;
1345 | }
1346 | function InListPassiveTarget(timeout, cb) {
1347 | self.detected_tag = null;
1348 | self.exchange(self.makeFrame(74, new Uint8Array([1, 0])), timeout, cb);
1349 | }
1350 | if (self.dev.acr122) {
1351 | self.acr122_set_timeout(timeout, function(rc, data) {
1352 | InListPassiveTarget(timeout, cb);
1353 | });
1354 | } else {
1355 | InListPassiveTarget(timeout, cb);
1356 | }
1357 | };
1358 | usbSCL3711.prototype.read_block = function(block, cb) {
1359 | var self = this;
1360 | var callback = cb;
1361 | if (!cb) {
1362 | cb = defaultCallback;
1363 | }
1364 | var u8 = new Uint8Array(2);
1365 | u8[0] = 48;
1366 | u8[1] = block;
1367 | self.apdu(u8, function(rc, data) {
1368 | callback(rc, data);
1369 | });
1370 | };
1371 | usbSCL3711.prototype.emulate_tag = function(data, timeout, cb) {
1372 | if (!cb) {
1373 | cb = defaultCallback;
1374 | }
1375 | var callback = cb;
1376 | var self = this;
1377 | var TIMEOUT = timeout;
1378 | var HANDLE_TT2 = function(cmd) {
1379 | switch(cmd[0]) {
1380 | case 48:
1381 | var blk_no = cmd[1];
1382 | console.log("recv TT2.READ(blk_no=" + blk_no + ")");
1383 | var ret = data.subarray(blk_no * 4, blk_no * 4 + 16);
1384 | if (ret.length < 16) {
1385 | ret = UTIL_concat(ret, new Uint8Array(16 - ret.length));
1386 | }
1387 | var u8 = self.makeFrame(144, ret);
1388 | self.exchange(u8, TIMEOUT, function(rc, data) {
1389 | if (rc) {
1390 | console.log("exchange(): " + rc);
1391 | return rc;
1392 | }
1393 | var u8 = self.makeFrame(136, []);
1394 | self.exchange(u8, TIMEOUT, function(rc, data) {
1395 | if (rc) {
1396 | console.log("exchange(): " + rc);
1397 | return rc;
1398 | }
1399 | HANDLE_TT2(new Uint8Array(data));
1400 | });
1401 | });
1402 | break;
1403 | case 80:
1404 | console.log("recv TT2.HALT received.");
1405 | callback(0);
1406 | break;
1407 | default:
1408 | console.log("Unsupported TT2 tag: " + cmd[0]);
1409 | callback(2457);
1410 | }
1411 | };
1412 | function TgInitAsTarget() {
1413 | var req = new Uint8Array([1, 4, 0, 0, 176, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
1414 | var u8 = self.makeFrame(140, req);
1415 | self.exchange(u8, TIMEOUT, function(rc, data) {
1416 | if (rc != 0) {
1417 | callback(rc);
1418 | return;
1419 | }
1420 | console.log("Emulated as a tag, reply is following:");
1421 | HANDLE_TT2(new Uint8Array(data));
1422 | });
1423 | }
1424 | if (self.dev.acr122) {
1425 | self.exchange((new Uint8Array([107, 5, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 81, 0, 0])).buffer, 1, function(rc, data) {
1426 | self.exchange((new Uint8Array([107, 9, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 4, 212, 50, 1, 0])).buffer, 1, function(rc, data) {
1427 | if (rc != 0) {
1428 | callback(rc);
1429 | return;
1430 | }
1431 | self.acr122_set_timeout(timeout, function(rc, data) {
1432 | if (rc != 0) {
1433 | callback(rc);
1434 | return;
1435 | }
1436 | TgInitAsTarget();
1437 | });
1438 | });
1439 | });
1440 | } else {
1441 | TgInitAsTarget();
1442 | }
1443 | };
1444 | usbSCL3711.prototype.write_block = function(blk_no, data, cb, write_inst) {
1445 | var callback = cb;
1446 | if (write_inst == null) {
1447 | write_inst = 162;
1448 | }
1449 | var u8 = new Uint8Array(2 + data.length);
1450 | u8[0] = write_inst;
1451 | u8[1] = blk_no;
1452 | for (var i = 0;i < data.length;i++) {
1453 | u8[2 + i] = data[i];
1454 | }
1455 | this.apdu(u8, function(rc, dummy) {
1456 | callback(rc);
1457 | });
1458 | };
1459 | usbSCL3711.prototype.apdu = function(req, cb, write_only) {
1460 | if (!cb) {
1461 | cb = defaultCallback;
1462 | }
1463 | var u8 = new Uint8Array(this.makeFrame(64, UTIL_concat([1], req)));
1464 | for (var i = 0;i < u8.length;i += 64) {
1465 | this.dev.writeFrame((new Uint8Array(u8.subarray(i, i + 64))).buffer);
1466 | }
1467 | if (write_only) {
1468 | cb(0, null);
1469 | } else {
1470 | this.read(3, function(rc, data, expect_sw12) {
1471 | if (rc != 0) {
1472 | cb(rc);
1473 | return;
1474 | }
1475 | var u8 = new Uint8Array(data);
1476 | if (expect_sw12) {
1477 | if (u8.length < 2) {
1478 | cb(1638);
1479 | return;
1480 | }
1481 | var sw12 = u8[u8.length - 2] * 256 + u8[u8.length - 1];
1482 | cb(sw12 == 36864 ? 0 : sw12, (new Uint8Array(u8.subarray(0, u8.length - 2))).buffer);
1483 | } else {
1484 | cb(0, u8.buffer);
1485 | }
1486 | });
1487 | }
1488 | };
1489 | function SHA256() {
1490 | this._buf = new Array(64);
1491 | this._W = new Array(64);
1492 | this._pad = new Array(64);
1493 | this._k = [1116352408, 1899447441, 3049323471, 3921009573, 961987163, 1508970993, 2453635748, 2870763221, 3624381080, 310598401, 607225278, 1426881987, 1925078388, 2162078206, 2614888103, 3248222580, 3835390401, 4022224774, 264347078, 604807628, 770255983, 1249150122, 1555081692, 1996064986, 2554220882, 2821834349, 2952996808, 3210313671, 3336571891, 3584528711, 113926993, 338241895, 666307205, 773529912, 1294757372, 1396182291, 1695183700, 1986661051, 2177026350, 2456956037, 2730485921, 2820302411,
1494 | 3259730800, 3345764771, 3516065817, 3600352804, 4094571909, 275423344, 430227734, 506948616, 659060556, 883997877, 958139571, 1322822218, 1537002063, 1747873779, 1955562222, 2024104815, 2227730452, 2361852424, 2428436474, 2756734187, 3204031479, 3329325298];
1495 | this._pad[0] = 128;
1496 | for (var i = 1;i < 64;++i) {
1497 | this._pad[i] = 0;
1498 | }
1499 | this.reset();
1500 | }
1501 | SHA256.prototype.reset = function() {
1502 | this._chain = [1779033703, 3144134277, 1013904242, 2773480762, 1359893119, 2600822924, 528734635, 1541459225];
1503 | this._inbuf = 0;
1504 | this._total = 0;
1505 | };
1506 | SHA256.prototype._compress = function(buf) {
1507 | var W = this._W;
1508 | var k = this._k;
1509 | function _rotr(w, r) {
1510 | return w << 32 - r | w >>> r;
1511 | }
1512 | for (var i = 0;i < 64;i += 4) {
1513 | var w = buf[i] << 24 | buf[i + 1] << 16 | buf[i + 2] << 8 | buf[i + 3];
1514 | W[i / 4] = w;
1515 | }
1516 | for (var i = 16;i < 64;++i) {
1517 | var s0 = _rotr(W[i - 15], 7) ^ _rotr(W[i - 15], 18) ^ W[i - 15] >>> 3;
1518 | var s1 = _rotr(W[i - 2], 17) ^ _rotr(W[i - 2], 19) ^ W[i - 2] >>> 10;
1519 | W[i] = W[i - 16] + s0 + W[i - 7] + s1 & 4294967295;
1520 | }
1521 | var A = this._chain[0];
1522 | var B = this._chain[1];
1523 | var C = this._chain[2];
1524 | var D = this._chain[3];
1525 | var E = this._chain[4];
1526 | var F = this._chain[5];
1527 | var G = this._chain[6];
1528 | var H = this._chain[7];
1529 | for (var i = 0;i < 64;++i) {
1530 | var S0 = _rotr(A, 2) ^ _rotr(A, 13) ^ _rotr(A, 22);
1531 | var maj = A & B ^ A & C ^ B & C;
1532 | var t2 = S0 + maj & 4294967295;
1533 | var S1 = _rotr(E, 6) ^ _rotr(E, 11) ^ _rotr(E, 25);
1534 | var ch = E & F ^ ~E & G;
1535 | var t1 = H + S1 + ch + k[i] + W[i] & 4294967295;
1536 | H = G;
1537 | G = F;
1538 | F = E;
1539 | E = D + t1 & 4294967295;
1540 | D = C;
1541 | C = B;
1542 | B = A;
1543 | A = t1 + t2 & 4294967295;
1544 | }
1545 | this._chain[0] += A;
1546 | this._chain[1] += B;
1547 | this._chain[2] += C;
1548 | this._chain[3] += D;
1549 | this._chain[4] += E;
1550 | this._chain[5] += F;
1551 | this._chain[6] += G;
1552 | this._chain[7] += H;
1553 | };
1554 | SHA256.prototype.update = function(bytes, opt_length) {
1555 | if (!opt_length) {
1556 | opt_length = bytes.length;
1557 | }
1558 | this._total += opt_length;
1559 | for (var n = 0;n < opt_length;++n) {
1560 | this._buf[this._inbuf++] = bytes[n];
1561 | if (this._inbuf == 64) {
1562 | this._compress(this._buf);
1563 | this._inbuf = 0;
1564 | }
1565 | }
1566 | };
1567 | SHA256.prototype.updateRange = function(bytes, start, end) {
1568 | this._total += end - start;
1569 | for (var n = start;n < end;++n) {
1570 | this._buf[this._inbuf++] = bytes[n];
1571 | if (this._inbuf == 64) {
1572 | this._compress(this._buf);
1573 | this._inbuf = 0;
1574 | }
1575 | }
1576 | };
1577 | SHA256.prototype.digest = function() {
1578 | for (var i = 0;i < arguments.length;++i) {
1579 | this.update(arguments[i]);
1580 | }
1581 | var digest = new Array(32);
1582 | var totalBits = this._total * 8;
1583 | if (this._inbuf < 56) {
1584 | this.update(this._pad, 56 - this._inbuf);
1585 | } else {
1586 | this.update(this._pad, 64 - (this._inbuf - 56));
1587 | }
1588 | for (var i = 63;i >= 56;--i) {
1589 | this._buf[i] = totalBits & 255;
1590 | totalBits >>>= 8;
1591 | }
1592 | this._compress(this._buf);
1593 | var n = 0;
1594 | for (var i = 0;i < 8;++i) {
1595 | for (var j = 24;j >= 0;j -= 8) {
1596 | digest[n++] = this._chain[i] >> j & 255;
1597 | }
1598 | }
1599 | return digest;
1600 | };
1601 | function Tag(tag_name, tag_id) {
1602 | switch(tag_name) {
1603 | case "tt2":
1604 | return new TT2(tag_id);
1605 | case "mifare_classic":
1606 | return new MifareClassic(tag_id);
1607 | }
1608 | return null;
1609 | }
1610 | ;function TT2(tag_id) {
1611 | this.tag_id = new Uint8Array(tag_id);
1612 | this.type_name = null;
1613 | this.lock_contorl = [];
1614 | }
1615 | TT2.prototype.detect_type_name = function(cb) {
1616 | var self = this;
1617 | var callback = cb;
1618 | if (this.tag_id[0] == 4) {
1619 | this.device.read_block(16, function(rc, bn) {
1620 | if (rc) {
1621 | self.type_name = "Mifare Ultralight";
1622 | } else {
1623 | self.type_name = "Mifare Ultralight C";
1624 | }
1625 | console.debug("[DEBUG] TT2.type_name = " + self.type_name);
1626 | if (callback) {
1627 | callback();
1628 | }
1629 | });
1630 | }
1631 | };
1632 | TT2.prototype.read = function(device, cb) {
1633 | var self = this;
1634 | if (!cb) {
1635 | cb = defaultCallback;
1636 | }
1637 | var callback = cb;
1638 | function poll_block0(rc, b0_b3) {
1639 | if (rc) {
1640 | return callback(rc);
1641 | }
1642 | var card = new Uint8Array(b0_b3);
1643 | var data = new Uint8Array(b0_b3);
1644 | var data_size = data[14] * 8;
1645 | var CC0 = data[12];
1646 | var CC1 = data[13];
1647 | var CC3 = data[15];
1648 | function check_ver(cc1) {
1649 | var major = (cc1 & 240) >> 4;
1650 | var minor = cc1 & 15;
1651 | if (major == 1) {
1652 | return true;
1653 | }
1654 | return false;
1655 | }
1656 | function readable(cc3) {
1657 | return(cc3 & 240) == 0 ? true : false;
1658 | }
1659 | if (CC0 != 225 || !check_ver(CC1) || !readable(CC3)) {
1660 | console.log("UNsupported type 2 tag: CC0=" + CC0 + ", CC1=" + CC1 + ", CC3=" + CC3);
1661 | return callback(1911, data.buffer);
1662 | }
1663 | var poll_n = Math.floor((data_size + 15) / 16);
1664 | var block = 4;
1665 | function poll_block(card, block, poll_n) {
1666 | console.log("[DEBUG] poll_n: " + poll_n);
1667 | if (--poll_n < 0) {
1668 | defaultCallback("[DEBUG] got a type 2 tag:", card.buffer);
1669 | for (var i = 16;i < card.length;) {
1670 | switch(card[i]) {
1671 | case 0:
1672 | console.debug("NULL TLV");
1673 | i++;
1674 | break;
1675 | case 1:
1676 | console.debug("Found Lock Control TLV");
1677 | var PageAddr = card[i + 2] >> 4;
1678 | var ByteOffset = card[i + 2] & 15;
1679 | var Size = card[i + 3];
1680 | if (Size == 0) {
1681 | Size = 256;
1682 | }
1683 | var BytesPerPage = Math.pow(2, card[i + 4] & 15);
1684 | var BytesLockedPerLockBit = card[i + 4] >> 4;
1685 | console.debug("Lock control: " + "BytesLockedPerLockBit=" + BytesLockedPerLockBit + ", Size=" + Size);
1686 | var ByteAddr = PageAddr * BytesPerPage + ByteOffset;
1687 | console.info("Lock control: ByteAddr=" + ByteAddr);
1688 | console.info(" Locked bytes:");
1689 | var lock_offset = 64;
1690 | for (var j = 0;j < (Size + 7) / 8;j++) {
1691 | var k = ByteAddr + j;
1692 | if (k >= card.length) {
1693 | console.warn(" card[" + k + "] haven't read out yet.");
1694 | break;
1695 | }
1696 | var mask = card[k];
1697 | console.debug(" [" + k + "]: " + mask.toString(16));
1698 | if (mask & 1) {
1699 | console.debug("* block-locking");
1700 | }
1701 | for (var l = 1;l < 8;l++) {
1702 | if (j * 8 + l >= Size) {
1703 | continue;
1704 | }
1705 | for (var s = "", m = 0;m < BytesLockedPerLockBit;lock_offset++) {
1706 | s += "0x" + lock_offset.toString(16) + ", ";
1707 | }
1708 | if (mask & 1 << l) {
1709 | console.info(" " + s);
1710 | }
1711 | }
1712 | }
1713 | i += 1 + 1 + card[i + 1];
1714 | break;
1715 | case 254:
1716 | console.debug("Terminator TLV.");
1717 | return;
1718 | case 3:
1719 | var len = card[i + 1];
1720 | if (i + 2 + len > card.length) {
1721 | console.warn("TLV len " + len + " > card len " + card.length);
1722 | }
1723 | return callback(0, (new Uint8Array(card.subarray(i + 2, i + 2 + len))).buffer);
1724 | default:
1725 | console.error("Unknown Type [" + card[i] + "]");
1726 | return;
1727 | }
1728 | }
1729 | }
1730 | device.read_block(block, function(rc, bn) {
1731 | if (rc) {
1732 | return callback(rc);
1733 | }
1734 | card = UTIL_concat(card, new Uint8Array(bn));
1735 | return poll_block(card, block + 4, poll_n);
1736 | });
1737 | }
1738 | poll_block(card, block, poll_n);
1739 | }
1740 | device.read_block(0, poll_block0);
1741 | };
1742 | TT2.prototype.compose = function(ndef) {
1743 | var blen;
1744 | var need_lock_control_tlv = 0;
1745 | if (ndef.length + 16 + 2 + 1 > 64) {
1746 | blen = 144 / 8;
1747 | need_lock_control_tlv = 1;
1748 | } else {
1749 | blen = 48 / 8;
1750 | }
1751 | var tt2_header = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 225, 16, blen, 0]);
1752 | var lock_control_tlv = need_lock_control_tlv ? new Uint8Array([1, 3, 160, 16, 68]) : new Uint8Array([]);
1753 | var ndef_tlv = new Uint8Array([3, ndef.length]);
1754 | var terminator_tlv = new Uint8Array([254]);
1755 | var ret = UTIL_concat(tt2_header, UTIL_concat(lock_control_tlv, UTIL_concat(ndef_tlv, UTIL_concat(new Uint8Array(ndef), terminator_tlv))));
1756 | return ret;
1757 | };
1758 | TT2.prototype.write = function(device, ndef, cb) {
1759 | if (!cb) {
1760 | cb = defaultCallback;
1761 | }
1762 | var self = this;
1763 | var callback = cb;
1764 | var card = self.compose(new Uint8Array(ndef));
1765 | var card_blknum = Math.floor((card.length + 3) / 4);
1766 | if (card_blknum > 64 / 4) {
1767 | console.warn("write_tt2() card length: " + card.length + " is larger than 64 bytes. Try to write as Ultralight-C.");
1768 | if (card_blknum > 192 / 4) {
1769 | console.error("write_tt2() card length: " + card.length + " is larger than 192 bytes (more than Ultralight-C" + " can provide).");
1770 | return callback(3003);
1771 | }
1772 | }
1773 | function write_block(card, block_no) {
1774 | if (block_no >= card_blknum) {
1775 | return callback(0);
1776 | }
1777 | var data = card.subarray(block_no * 4, block_no * 4 + 4);
1778 | if (data.length < 4) {
1779 | data = UTIL_concat(data, new Uint8Array(4 - data.length));
1780 | }
1781 | device.write_block(block_no, data, function(rc) {
1782 | if (rc) {
1783 | return callback(rc);
1784 | }
1785 | write_block(card, block_no + 1);
1786 | });
1787 | }
1788 | write_block(card, 3);
1789 | };
1790 | TT2.prototype.emulate = function(device, ndef_obj, timeout, cb) {
1791 | var data = this.compose(new Uint8Array(ndef_obj.compose()));
1792 | return device.emulate_tag(data, timeout, cb);
1793 | };
1794 | function llSCL3711(dev, acr122) {
1795 | this.dev = dev;
1796 | this.txqueue = [];
1797 | this.clients = [];
1798 | this.acr122 = acr122;
1799 | if (acr122) {
1800 | this.endpoint = 2;
1801 | } else {
1802 | this.endpoint = 4;
1803 | }
1804 | this.readLoop();
1805 | }
1806 | llSCL3711.prototype.notifyClientOfClosure = function(client) {
1807 | var cb = client.onclose;
1808 | if (cb) {
1809 | window.setTimeout(cb, 0);
1810 | }
1811 | };
1812 | llSCL3711.prototype.close = function() {
1813 | while (this.clients.length != 0) {
1814 | this.notifyClientOfClosure(this.clients.shift());
1815 | }
1816 | dev_manager.dropDevice(this);
1817 | };
1818 | llSCL3711.prototype.publishFrame = function(f) {
1819 | var old = this.clients;
1820 | var remaining = [];
1821 | var changes = false;
1822 | for (var i = 0;i < old.length;++i) {
1823 | var client = old[i];
1824 | if (client.receivedFrame(f)) {
1825 | remaining.push(client);
1826 | } else {
1827 | changes = true;
1828 | console.log(UTIL_fmt("[" + client.cid.toString(16) + "] left?"));
1829 | }
1830 | }
1831 | if (changes) {
1832 | this.clients = remaining;
1833 | }
1834 | };
1835 | llSCL3711.prototype.readLoop = function() {
1836 | if (!this.dev) {
1837 | return;
1838 | }
1839 | var self = this;
1840 | chrome.usb.bulkTransfer(this.dev, {direction:"in", endpoint:this.endpoint, length:2048}, function(x) {
1841 | if (x.data) {
1842 | if (x.data.byteLength >= 5) {
1843 | var u8 = new Uint8Array(x.data);
1844 | console.log(UTIL_fmt("<" + UTIL_BytesToHex(u8)));
1845 | self.publishFrame(x.data);
1846 | window.setTimeout(function() {
1847 | self.readLoop();
1848 | }, 0);
1849 | } else {
1850 | console.error(UTIL_fmt("tiny reply!"));
1851 | console.error(x);
1852 | }
1853 | } else {
1854 | console.log("no x.data!");
1855 | console.log(x);
1856 | throw "no x.data!";
1857 | }
1858 | });
1859 | };
1860 | llSCL3711.prototype.registerClient = function(who) {
1861 | this.clients.push(who);
1862 | };
1863 | llSCL3711.prototype.deregisterClient = function(who) {
1864 | var current = this.clients;
1865 | this.clients = [];
1866 | for (var i = 0;i < current.length;++i) {
1867 | var client = current[i];
1868 | if (client != who) {
1869 | this.clients.push(client);
1870 | }
1871 | }
1872 | return this.clients.length;
1873 | };
1874 | llSCL3711.prototype.writePump = function() {
1875 | if (!this.dev) {
1876 | return;
1877 | }
1878 | if (this.txqueue.length == 0) {
1879 | return;
1880 | }
1881 | var frame = this.txqueue[0];
1882 | var self = this;
1883 | function transferComplete(x) {
1884 | self.txqueue.shift();
1885 | if (self.txqueue.length != 0) {
1886 | window.setTimeout(function() {
1887 | self.writePump();
1888 | }, 0);
1889 | }
1890 | }
1891 | var u8 = new Uint8Array(frame);
1892 | console.log(UTIL_fmt(">" + UTIL_BytesToHex(u8)));
1893 | chrome.usb.bulkTransfer(this.dev, {direction:"out", endpoint:this.endpoint, data:frame}, transferComplete);
1894 | };
1895 | llSCL3711.prototype.writeFrame = function(frame) {
1896 | if (!this.dev) {
1897 | return false;
1898 | }
1899 | var wasEmpty = this.txqueue.length == 0;
1900 | this.txqueue.push(frame);
1901 | if (wasEmpty) {
1902 | this.writePump();
1903 | }
1904 | return true;
1905 | };
1906 | function UTIL_StringToBytes(s, bytes) {
1907 | bytes = bytes || new Array(s.length);
1908 | for (var i = 0;i < s.length;++i) {
1909 | bytes[i] = s.charCodeAt(i);
1910 | }
1911 | return bytes;
1912 | }
1913 | function UTIL_BytesToString(b) {
1914 | var tmp = new String;
1915 | for (var i = 0;i < b.length;++i) {
1916 | tmp += String.fromCharCode(b[i]);
1917 | }
1918 | return tmp;
1919 | }
1920 | function UTIL_BytesToHex(b) {
1921 | if (!b) {
1922 | return "(null)";
1923 | }
1924 | var hexchars = "0123456789ABCDEF";
1925 | var hexrep = new Array(b.length * 2);
1926 | for (var i = 0;i < b.length;++i) {
1927 | hexrep[i * 2 + 0] = hexchars.charAt(b[i] >> 4 & 15);
1928 | hexrep[i * 2 + 1] = hexchars.charAt(b[i] & 15);
1929 | }
1930 | return hexrep.join("");
1931 | }
1932 | function UTIL_BytesToHexWithSeparator(b, sep) {
1933 | var hexchars = "0123456789ABCDEF";
1934 | var stride = 2 + (sep ? 1 : 0);
1935 | var hexrep = new Array(b.length * stride);
1936 | for (var i = 0;i < b.length;++i) {
1937 | if (sep) {
1938 | hexrep[i * stride + 0] = sep;
1939 | }
1940 | hexrep[i * stride + stride - 2] = hexchars.charAt(b[i] >> 4 & 15);
1941 | hexrep[i * stride + stride - 1] = hexchars.charAt(b[i] & 15);
1942 | }
1943 | return(sep ? hexrep.slice(1) : hexrep).join("");
1944 | }
1945 | function UTIL_HexToBytes(h) {
1946 | var hexchars = "0123456789ABCDEFabcdef";
1947 | var res = new Uint8Array(h.length / 2);
1948 | for (var i = 0;i < h.length;i += 2) {
1949 | if (hexchars.indexOf(h.substring(i, i + 1)) == -1) {
1950 | break;
1951 | }
1952 | res[i / 2] = parseInt(h.substring(i, i + 2), 16);
1953 | }
1954 | return res;
1955 | }
1956 | function UTIL_equalArrays(a, b) {
1957 | if (!a || !b) {
1958 | return false;
1959 | }
1960 | if (a.length != b.length) {
1961 | return false;
1962 | }
1963 | var accu = 0;
1964 | for (var i = 0;i < a.length;++i) {
1965 | accu |= a[i] ^ b[i];
1966 | }
1967 | return accu === 0;
1968 | }
1969 | function UTIL_ltArrays(a, b) {
1970 | if (a.length < b.length) {
1971 | return true;
1972 | }
1973 | if (a.length > b.length) {
1974 | return false;
1975 | }
1976 | for (var i = 0;i < a.length;++i) {
1977 | if (a[i] < b[i]) {
1978 | return true;
1979 | }
1980 | if (a[i] > b[i]) {
1981 | return false;
1982 | }
1983 | }
1984 | return false;
1985 | }
1986 | function UTIL_geArrays(a, b) {
1987 | return!UTIL_ltArrays(a, b);
1988 | }
1989 | function UTIL_getRandom(a) {
1990 | var tmp = new Array(a);
1991 | var rnd = new Uint8Array(a);
1992 | window.crypto.getRandomValues(rnd);
1993 | for (var i = 0;i < a;++i) {
1994 | tmp[i] = rnd[i] & 255;
1995 | }
1996 | return tmp;
1997 | }
1998 | function UTIL_equalArrays(a, b) {
1999 | if (!a || !b) {
2000 | return false;
2001 | }
2002 | if (a.length != b.length) {
2003 | return false;
2004 | }
2005 | var accu = 0;
2006 | for (var i = 0;i < a.length;++i) {
2007 | accu |= a[i] ^ b[i];
2008 | }
2009 | return accu === 0;
2010 | }
2011 | function UTIL_setFavicon(icon) {
2012 | var faviconLink = document.createElement("link");
2013 | faviconLink.rel = "Shortcut Icon";
2014 | faviconLink.type = "image/x-icon";
2015 | faviconLink.href = icon;
2016 | var head = document.getElementsByTagName("head")[0];
2017 | var links = head.getElementsByTagName("link");
2018 | for (var i = 0;i < links.length;i++) {
2019 | var link = links[i];
2020 | if (link.type == faviconLink.type && link.rel == faviconLink.rel) {
2021 | head.removeChild(link);
2022 | }
2023 | }
2024 | head.appendChild(faviconLink);
2025 | }
2026 | function UTIL_clear(a) {
2027 | if (a instanceof Array) {
2028 | for (var i = 0;i < a.length;++i) {
2029 | a[i] = 0;
2030 | }
2031 | }
2032 | }
2033 | function UTIL_time() {
2034 | var d = new Date;
2035 | var m = "000" + d.getMilliseconds();
2036 | var s = d.toTimeString().substring(0, 8) + "." + m.substring(m.length - 3);
2037 | return s;
2038 | }
2039 | function UTIL_fmt(s) {
2040 | return UTIL_time() + " " + s;
2041 | }
2042 | function UTIL_concat(a, b) {
2043 | var c = new Uint8Array(a.length + b.length);
2044 | var i, n = 0;
2045 | for (i = 0;i < a.length;i++, n++) {
2046 | c[n] = a[i];
2047 | }
2048 | for (i = 0;i < b.length;i++, n++) {
2049 | c[n] = b[i];
2050 | }
2051 | return c;
2052 | }
2053 | ;
--------------------------------------------------------------------------------