diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 598be3b..ffd2deb 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -36,5 +36,8 @@ module.exports = { imports: 'always-multiline', objects: 'always-multiline', }], + '@typescript-eslint/unbound-method': ['error', { + ignoreStatic: true, + }] }, } diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d025473..2ab9c6d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,10 +30,10 @@ jobs: echo 'secrets=${{ toJSON(secrets) }}' echo 'steps=${{ toJSON(steps) }}' echo 'vars=${{ toJSON(vars) }}' - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 with: - node-version: 18 + node-version: 20 cache: 'yarn' - name: install, lint, test, build run: | @@ -51,12 +51,11 @@ jobs: url: ${{ steps.deployment.outputs.page_url }} runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 with: - node-version: 18 + node-version: 20 cache: 'yarn' - - uses: actions/configure-pages@v3 - name: install and build run: | set -ex @@ -64,17 +63,18 @@ jobs: yarn test:ci yarn build - name: Coveralls - uses: coverallsapp/github-action@master + uses: coverallsapp/github-action@v2 with: github-token: ${{ secrets.GITHUB_TOKEN }} + - uses: actions/configure-pages@v4 - name: Upload artifact - uses: actions/upload-pages-artifact@v2 + uses: actions/upload-pages-artifact@v3 with: path: './dist' - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v2 + uses: actions/deploy-pages@v4 - name: 發布至 npm - uses: JS-DevTools/npm-publish@v1 + uses: JS-DevTools/npm-publish@v3 with: token: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/npm-test.yml b/.github/workflows/npm-test.yml index d11b54d..06fc2a2 100644 --- a/.github/workflows/npm-test.yml +++ b/.github/workflows/npm-test.yml @@ -6,11 +6,11 @@ on: jobs: test: runs-on: ubuntu-latest - container: node:18 + container: node:20 steps: - uses: actions/setup-node@v3 with: - node-version: 18 + node-version: 20 - name: install and npm test run: | set -ex diff --git a/.npmignore b/.npmignore index 213c999..9e77bdb 100644 --- a/.npmignore +++ b/.npmignore @@ -150,6 +150,7 @@ out /.rollup.cache /*.ts /dist/docs +/jest.config.cjs /pages /pug /src/example diff --git a/package.json b/package.json index 783dcb8..35cf69d 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "repository": "git@github.com:taichunmin/chameleon-ultra.js.git", "type": "module", "unpkg": "dist/iife/index.min.js", - "version": "0.2.18", + "version": "0.2.19", "bugs": { "url": "https://github.com/taichunmin/chameleon-ultra.js/issues" }, @@ -32,20 +32,20 @@ "@rollup/plugin-commonjs": "^25.0.7", "@rollup/plugin-node-resolve": "^15.2.3", "@rollup/plugin-terser": "^0.4.4", - "@rollup/plugin-typescript": "^11.1.5", - "@tsconfig/node-lts": "^20.1.0", + "@rollup/plugin-typescript": "^11.1.6", + "@tsconfig/node-lts": "^20.1.1", "@types/debug": "^4.1.12", "@types/finalhandler": "^1.2.3", "@types/html-minifier": "^4.0.5", "@types/jest": "^29.5.11", "@types/livereload": "^0.9.5", "@types/lodash": "^4.14.202", - "@types/node": "^20.10.5", + "@types/node": "^20.11.4", "@types/pug": "^2.0.10", "@types/serve-static": "^1.15.5", "@types/uglify-js": "^3.17.4", "@types/web-bluetooth": "^0.0.20", - "@typescript-eslint/eslint-plugin": "^6.15.0", + "@typescript-eslint/eslint-plugin": "^6.19.0", "chokidar": "^3.5.3", "concurrently": "^8.2.2", "dayjs": "^1.11.10", @@ -53,7 +53,7 @@ "eslint": "^8.56.0", "eslint-config-standard-with-typescript": "^43.0.0", "eslint-plugin-import": "^2.29.1", - "eslint-plugin-n": "^16.5.0", + "eslint-plugin-n": "^16.6.2", "eslint-plugin-promise": "^6.1.1", "eslint-plugin-pug": "^1.2.5", "finalhandler": "^1.2.0", @@ -65,7 +65,7 @@ "nodemon": "^3.0.2", "pug": "^3.0.2", "rimraf": "^5.0.5", - "rollup": "^4.9.1", + "rollup": "^4.9.5", "rollup-plugin-dts": "^6.1.0", "rollup-plugin-polyfill-node": "^0.13.0", "rollup-plugin-version-injector": "^1.3.3", @@ -74,10 +74,11 @@ "ts-jest": "^29.1.1", "tslib": "^2.6.2", "tsx": "^4.7.0", - "typedoc": "^0.25.4", - "typedoc-plugin-mdn-links": "^3.1.8", - "typedoc-plugin-missing-exports": "^2.1.0", + "typedoc": "^0.25.7", + "typedoc-plugin-mdn-links": "^3.1.12", + "typedoc-plugin-missing-exports": "^2.2.0", "typedoc-plugin-rename-defaults": "^0.7.0", + "typedoc-plugin-zod": "^1.1.2", "typescript": "^5.3.3", "uglify-js": "^3.17.4", "utility-types": "^3.10.0" diff --git a/pug/src/mifare1k.pug b/pug/src/mifare1k.pug index 7c1889c..7b1930d 100644 --- a/pug/src/mifare1k.pug +++ b/pug/src/mifare1k.pug @@ -86,7 +86,7 @@ block content .row.mx-n1.mb-2 .col.px-1: button.btn.btn-block.btn-outline-success(@click="btnGen2Read") #[i.fa.fa-fw.fa-upload] Read .col.px-1: button.btn.btn-block.btn-outline-primary(@click="btnGen2Write") #[i.fa.fa-fw.fa-download] Write - button.btn.btn-sm.btn-block.btn-outline-info.mb-2(@click="btnKeysGrab") #[i.fa.mr-1.fa-key] Grab keys from Mifare Data + button.btn.btn-sm.btn-block.btn-outline-info.mb-2(@click="btnKeysGrab") #[i.fa.mr-1.fa-key] Grab keys from Mifare Dump button.btn.btn-sm.btn-block.btn-outline-secondary.mb-2(@click="btnKeysReset") #[i.fa.mr-1.fa-fire] Reset to well-known keys .card-body.px-3.pt-3.pb-2.letter-spacing-n1px.border-top h6.card-title UID (Chinese Magic Card gen1a) @@ -94,13 +94,13 @@ block content .col.px-1: button.btn.btn-block.btn-outline-success(@click="btnGen1aRead") #[i.fa.fa-fw.fa-upload] Read .col.px-1: button.btn.btn-block.btn-outline-primary(@click="btnGen1aWrite") #[i.fa.fa-fw.fa-download] Write .card.shadow-sm.mb-2 - h6.card-header.bg-light #[i.fa.fa-id-card.mr-1] Mifare Data + h6.card-header.bg-light #[i.fa.fa-id-card.mr-1] Mifare Dump .card-body.px-3.pt-3.pb-2.letter-spacing-n1px input.d-none(type="file", ref="cardImport", @change="cardImport?.cb?.($event.target.files[0])") .row.mx-n1.mb-2 .col.px-1: button.btn.btn-block.btn-outline-success(@click="btnCardImport") #[i.fa.fa-fw.fa-file-code-o] Import .col.px-1: button.btn.btn-block.btn-outline-primary(@click="btnCardExport") #[i.fa.fa-fw.fa-floppy-o] Export - button.btn.btn-sm.btn-block.btn-outline-danger.mb-2(@click="btnCardReset") #[i.fa.mr-1.fa-repeat] Reset to empty mifare + button.btn.btn-sm.btn-block.btn-outline-danger.mb-2(@click="btnCardReset") #[i.fa.mr-1.fa-repeat] Reset to empty dump .card-body.px-3.pt-3.pb-2.letter-spacing-n1px.border-top h6.card-title Anit Collision .input-group.input-group-sm.mb-2.was-validated @@ -120,7 +120,7 @@ block content input.form-control(pattern="([\\dA-Fa-f]{2})*", placeholder="Hex format of ATS", v-model="ss.ats") .input-group-append: button.btn.btn-outline-secondary(type="button", @click="ss.ats = ''") #[i.fa.fa-fw.fa-times] .card-body.px-3.pt-3.pb-2.letter-spacing-n1px.border-top - h6.card-title Mifare 1k Card Data + h6.card-title Mifare 1k Card Dump .input-group.input-group-sm.mb-2(v-for="i in _.times(16)") .input-group-prepend: label.input-group-text.justify-content-center.flex-column(style="width: 2rem", :for="`i-toggle-${i}`") input.my-2(type="checkbox", v-model="ss.toggle[i]", :id="`i-toggle-${i}`") diff --git a/src/ChameleonUltra.test.ts b/src/ChameleonUltra.test.ts index 4f6b241..08552d4 100644 --- a/src/ChameleonUltra.test.ts +++ b/src/ChameleonUltra.test.ts @@ -1,9 +1,8 @@ +import _ from 'lodash' import { Buffer } from './buffer' +import { ChameleonUltra } from './ChameleonUltra' import BufferMockAdapter from './plugin/BufferMockAdapter' import { - ChameleonUltra, - - // enum AnimationMode, ButtonAction, ButtonType, @@ -17,7 +16,7 @@ import { Mf1VblockOperator, Slot, TagType, -} from './ChameleonUltra' +} from './enums' describe('ChameleonUltra with BufferMockAdapter', () => { let ultra: ChameleonUltra @@ -1134,7 +1133,7 @@ describe('ChameleonUltra with BufferMockAdapter', () => { test('#mf1CheckSectorKeys()', async () => { // arrange adapter.send.push(Buffer.fromHexString('11ef 07d7 0000 0000 22 00')) - adapter.send.push(Buffer.fromHexString('11ef 07d7 0000 0000 22 00')) + adapter.send.push(Buffer.fromHexString('11ef 07d8 0000 0010 11 000000000000ff078069ffffffffffff 17')) // act const keys = Buffer.from('FFFFFFFFFFFF\n000000000000\nA0A1A2A3A4A5\nD3F7D3F7D3F7', 'hex').chunk(6) @@ -1146,15 +1145,15 @@ describe('ChameleonUltra with BufferMockAdapter', () => { [Mf1KeyType.KEY_B]: Buffer.fromHexString('FFFFFFFFFFFF'), }) expect(adapter.recv).toEqual([ - Buffer.fromHexString('11ef 07d7 0000 0008 1a 6103ffffffffffff a2'), Buffer.fromHexString('11ef 07d7 0000 0008 1a 6003ffffffffffff a3'), + Buffer.fromHexString('11ef 07d8 0000 0008 19 6003ffffffffffff a3'), ]) }) test('#mf1ReadSectorByKeys()', async () => { // arrange adapter.send.push(Buffer.fromHexString('11ef 07d7 0000 0000 22 00')) - adapter.send.push(Buffer.fromHexString('11ef 07d7 0000 0000 22 00')) + adapter.send.push(Buffer.fromHexString('11ef 07d8 0000 0010 11 000000000000ff078069ffffffffffff 17')) adapter.send.push(Buffer.fromHexString('11ef 07d8 0000 0010 11 877209e11d0804000392abdef258ec90 10')) adapter.send.push(Buffer.fromHexString('11ef 07d8 0000 0010 11 00000000000000000000000000000000 00')) adapter.send.push(Buffer.fromHexString('11ef 07d8 0000 0010 11 00000000000000000000000000000000 00')) @@ -1175,8 +1174,8 @@ describe('ChameleonUltra with BufferMockAdapter', () => { success: [true, true, true, true], }) expect(adapter.recv).toEqual([ - Buffer.fromHexString('11ef 07d7 0000 0008 1a 6103ffffffffffff a2'), Buffer.fromHexString('11ef 07d7 0000 0008 1a 6003ffffffffffff a3'), + Buffer.fromHexString('11ef 07d8 0000 0008 19 6003ffffffffffff a3'), Buffer.fromHexString('11ef 07d8 0000 0008 19 6100ffffffffffff a5'), Buffer.fromHexString('11ef 07d8 0000 0008 19 6101ffffffffffff a4'), Buffer.fromHexString('11ef 07d8 0000 0008 19 6102ffffffffffff a3'), @@ -1187,7 +1186,7 @@ describe('ChameleonUltra with BufferMockAdapter', () => { test('#mf1WriteSectorByKeys()', async () => { // arrange adapter.send.push(Buffer.fromHexString('11ef 07d7 0000 0000 22 00')) - adapter.send.push(Buffer.fromHexString('11ef 07d7 0000 0000 22 00')) + adapter.send.push(Buffer.fromHexString('11ef 07d8 0000 0010 11 000000000000ff078069ffffffffffff 17')) adapter.send.push(Buffer.fromHexString('11ef 07d9 0000 0000 20 00')) adapter.send.push(Buffer.fromHexString('11ef 07d9 0000 0000 20 00')) adapter.send.push(Buffer.fromHexString('11ef 07d9 0000 0000 20 00')) @@ -1206,8 +1205,8 @@ describe('ChameleonUltra with BufferMockAdapter', () => { // assert expect(actual).toEqual({ success: [true, true, true, true] }) expect(adapter.recv).toEqual([ - Buffer.fromHexString('11ef 07d7 0000 0008 1a 6107ffffffffffff 9e'), Buffer.fromHexString('11ef 07d7 0000 0008 1a 6007ffffffffffff 9f'), + Buffer.fromHexString('11ef 07d8 0000 0008 19 6007ffffffffffff 9f'), Buffer.fromHexString('11ef 07d9 0000 0018 08 6104ffffffffffff00000000000000000000000000000000 a1'), Buffer.fromHexString('11ef 07d9 0000 0018 08 6105ffffffffffff00000000000000000000000000000000 a0'), Buffer.fromHexString('11ef 07d9 0000 0018 08 6106ffffffffffff00000000000000000000000000000000 9f'), @@ -1229,3 +1228,17 @@ describe('ChameleonUltra with BufferMockAdapter', () => { expect(actual).toBe(expected) }) }) + +test('.mf1TrailerBlockNoOfSector()', async () => { + const actual = _.times(40, ChameleonUltra.mf1TrailerBlockNoOfSector) + expect(actual).toEqual([ + // mifare classic 1k + 3, 7, 11, 15, 19, 23, 27, 31, + 35, 39, 43, 47, 51, 55, 59, 63, + // mifare classic 2k + 67, 71, 75, 79, 83, 87, 91, 95, + 99, 103, 107, 111, 115, 119, 123, 127, + // mifare classic 4k + 143, 159, 175, 191, 207, 223, 239, 255, + ]) +}) diff --git a/src/ChameleonUltra.ts b/src/ChameleonUltra.ts index f9b93de..0d1b407 100644 --- a/src/ChameleonUltra.ts +++ b/src/ChameleonUltra.ts @@ -1,16 +1,48 @@ import _ from 'lodash' import { Buffer } from './buffer' -import { createIsEnum, createIsEnumInteger, errToJson, middlewareCompose, sleep, type MiddlewareComposeFn, versionCompare } from './helper' import { debug as createDebugger, type Debugger } from 'debug' +import { errToJson, middlewareCompose, sleep, type MiddlewareComposeFn, versionCompare } from './helper' import { type ReadableStream, type UnderlyingSink, WritableStream } from 'node:stream/web' import * as Decoder from './ResponseDecoder' +import { + Cmd, + Mf1KeyType, + RespStatus, + type AnimationMode, + type ButtonAction, + type ButtonType, + type DeviceMode, + type DeviceModel, + type FreqType, + type Mf1EmuWriteMode, + type Mf1PrngType, + type Mf1VblockOperator, + type Slot, + type TagType, + + isAnimationMode, + isButtonAction, + isButtonType, + isDeviceMode, + isMf1EmuWriteMode, + isMf1KeyType, + isMf1VblockOperator, + isSlot, + isTagType, + isValidFreqType, +} from './enums' + const READ_DEFAULT_TIMEOUT = 5e3 const START_OF_FRAME = new Buffer(2).writeUInt16BE(0x11EF) const VERSION_SUPPORTED = { gte: '2.0', lt: '3.0' } as const +function isMf1BlockNo (block: any): boolean { + return _.isInteger(block) && block >= 0 && block <= 0xFF +} + function validateMf1BlockKey (block: any, keyType: any, key: any, prefix: string = ''): void { - if (!_.isSafeInteger(block)) throw new TypeError(`${prefix}block should be a integer`) + if (!isMf1BlockNo(block)) throw new TypeError(`${prefix}block should be a integer`) if (!isMf1KeyType(keyType)) throw new TypeError(`${prefix}keyType should be a Mf1KeyType`) if (!Buffer.isBuffer(key) || key.length !== 6) throw new TypeError(`${prefix}key should be a Buffer(6)`) } @@ -84,7 +116,7 @@ export class ChameleonUltra { * * *