• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

taichunmin / chameleon-ultra.js / 16902739519

12 Aug 2025 08:00AM UTC coverage: 68.124% (-1.1%) from 69.192%
16902739519

push

github

web-flow
Add support 9 new cmds (#204)

504 of 658 branches covered (76.6%)

Branch coverage included in aggregate %.

415 of 589 new or added lines in 10 files covered. (70.46%)

9 existing lines in 3 files now uncovered.

2815 of 4214 relevant lines covered (66.8%)

2958829.46 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

84.88
/src/decoder.ts
1
import { Buffer } from '@taichunmin/buffer'
1✔
2
import * as _ from 'lodash-es'
1✔
3
import { type Class } from 'type-fest'
4

5
import {
1✔
6
  Mf1KeyType,
7
  type AnimationMode,
8
  type ButtonAction,
9
  type DarksideStatus,
10
  type HidProxFormat,
11
  type Mf1EmuWriteMode,
12
  type TagType,
13
} from './enums'
14
import { type HidProxTag, type Mf1AcquireStaticEncryptedNestedRes } from './types'
15

16
function bufUnpackToClass <T> (buf: Buffer, format: string, Type: Class<T>): T {
30✔
17
  return new Type(...buf.unpack<ConstructorParameters<typeof Type>>(format))
30✔
18
}
30✔
19

20
export function bufIsLenOrFail (buf: Buffer, len: number, name: string): void {
1✔
21
  if (Buffer.isBuffer(buf) && buf.length === len) return
63✔
22
  throw new TypeError(`${name} must be a ${len} ${['byte', 'bytes'][+(len > 1)]} Buffer.`)
15✔
23
}
15✔
24

25
export class SlotInfo {
1✔
26
  hfTagType: TagType
8✔
27
  lfTagType: TagType
8✔
28

29
  constructor (hf: TagType, lf: TagType) {
8✔
30
    this.hfTagType = hf
8✔
31
    this.lfTagType = lf
8✔
32
  }
8✔
33

34
  static fromCmd1019 (buf: Buffer): SlotInfo[] {
8✔
35
    bufIsLenOrFail(buf, 32, 'buf')
3✔
36
    return _.map(buf.chunk(4), chunk => bufUnpackToClass(chunk, '!HH', SlotInfo))
3✔
37
  }
3✔
38
}
8✔
39

40
export class SlotFreqIsEnable {
1✔
41
  hf: boolean
8✔
42
  lf: boolean
8✔
43

44
  constructor (hf: boolean, lf: boolean) {
8✔
45
    ;[this.hf, this.lf] = _.map([hf, lf], Boolean)
8✔
46
  }
8✔
47

48
  static fromCmd1023 (buf: Buffer): SlotFreqIsEnable[] {
8✔
49
    bufIsLenOrFail(buf, 16, 'buf')
3✔
50
    return _.map(buf.chunk(2), chunk => bufUnpackToClass(chunk, '!??', SlotFreqIsEnable))
3✔
51
  }
3✔
52
}
8✔
53

54
export class BatteryInfo {
1✔
55
  voltage: number
1✔
56
  level: number
1✔
57

58
  constructor (voltage: number, level: number) {
1✔
59
    ;[this.voltage, this.level] = [voltage, level]
1✔
60
  }
1✔
61

62
  static fromCmd1025 (buf: Buffer): BatteryInfo {
1✔
63
    bufIsLenOrFail(buf, 3, 'buf')
3✔
64
    return bufUnpackToClass(buf, '!HB', BatteryInfo)
3✔
65
  }
3✔
66
}
1✔
67

68
export class DeviceSettings {
1✔
69
  version: number // version of setting
1✔
70
  animation: AnimationMode
1✔
71
  buttonPressAction: ButtonAction[]
1✔
72
  buttonLongPressAction: ButtonAction[]
1✔
73
  blePairingMode: boolean
1✔
74
  blePairingKey: string
1✔
75

76
  constructor (
1✔
77
    version: number,
1✔
78
    animation: AnimationMode,
1✔
79
    btnPressA: ButtonAction,
1✔
80
    btnPressB: ButtonAction,
1✔
81
    btnLongPressA: ButtonAction,
1✔
82
    btnLongPressB: ButtonAction,
1✔
83
    blePairingMode: boolean,
1✔
84
    blePairingKey: string
1✔
85
  ) {
1✔
86
    this.version = version
1✔
87
    this.animation = animation
1✔
88
    this.buttonPressAction = [btnPressA, btnPressB]
1✔
89
    this.buttonLongPressAction = [btnLongPressA, btnLongPressB]
1✔
90
    this.blePairingMode = Boolean(blePairingMode)
1✔
91
    this.blePairingKey = blePairingKey
1✔
92
  }
1✔
93

94
  static fromCmd1034 (buf: Buffer): DeviceSettings {
1✔
95
    bufIsLenOrFail(buf, 13, 'buf')
3✔
96
    return bufUnpackToClass(buf, '!6B?6s', DeviceSettings)
3✔
97
  }
3✔
98
}
1✔
99

100
/**
101
 * Class for Hf14aAntiColl Decoding.
102
 */
103
export class Hf14aAntiColl {
1✔
104
  uid: Buffer
3✔
105
  atqa: Buffer
3✔
106
  sak: Buffer
3✔
107
  ats: Buffer
3✔
108

109
  constructor (uid: Buffer, atqa: Buffer, sak: Buffer, ats: Buffer) {
3✔
110
    ;[this.uid, this.atqa, this.sak, this.ats] = [uid, atqa, sak, ats]
3✔
111
  }
3✔
112

113
  static fromBuffer (buf: Buffer): Hf14aAntiColl {
3✔
114
    // uidlen[1]|uid[uidlen]|atqa[2]|sak[1]|atslen[1]|ats[atslen]
115
    const uidLen = buf[0]
5✔
116
    if (buf.length < uidLen + 4) throw new Error('invalid length of uid')
5✔
117
    const atsLen = buf[uidLen + 4]
4✔
118
    if (buf.length < uidLen + atsLen + 5) throw new Error('invalid length of ats')
4✔
119
    return bufUnpackToClass(buf, `!${uidLen + 1}p2ss${atsLen + 1}p`, Hf14aAntiColl)
3✔
120
  }
5✔
121

122
  static fromCmd2000 (buf: Buffer): Hf14aAntiColl[] {
3✔
123
    if (!Buffer.isBuffer(buf)) throw new TypeError('buf must be a Buffer')
3✔
124
    const tags: Hf14aAntiColl[] = []
2✔
125
    while (buf.length > 0) {
2✔
126
      const tag = Hf14aAntiColl.fromBuffer(buf)
2✔
127
      buf = buf.subarray(tag.uid.length + tag.ats.length + 5)
2✔
128
      tags.push(tag)
2✔
129
    }
2✔
130
    return tags
2✔
131
  }
3✔
132
}
3✔
133

134
export class Mf1AcquireStaticNestedRes {
1✔
135
  uid: Buffer
1✔
136
  atks: Array<{ nt1: Buffer, nt2: Buffer }>
1✔
137

138
  constructor (uid: Buffer, atks: Array<{ nt1: Buffer, nt2: Buffer }>) {
1✔
139
    ;[this.uid, this.atks] = [uid, atks]
1✔
140
  }
1✔
141

142
  static fromCmd2003 (buf: Buffer): Mf1AcquireStaticNestedRes {
1✔
143
    if (!Buffer.isBuffer(buf)) throw new TypeError('buf must be a Buffer')
2✔
144
    return new Mf1AcquireStaticNestedRes(
1✔
145
      buf.subarray(0, 4), // uid
1✔
146
      _.map(buf.subarray(4).chunk(8), chunk => ({
1✔
147
        nt1: chunk.subarray(0, 4),
2✔
148
        nt2: chunk.subarray(4, 8),
2✔
149
      })), // atks
1✔
150
    )
1✔
151
  }
2✔
152
}
1✔
153

154
export class Mf1DarksideRes {
1✔
155
  status: DarksideStatus
2✔
156
  uid?: Buffer
2✔
157
  nt?: Buffer
2✔
158
  par?: Buffer
2✔
159
  ks?: Buffer
2✔
160
  nr?: Buffer
2✔
161
  ar?: Buffer
2✔
162

163
  constructor (
2✔
164
    status: DarksideStatus,
2✔
165
    uid?: Buffer,
2✔
166
    nt?: Buffer,
2✔
167
    par?: Buffer,
2✔
168
    ks?: Buffer,
2✔
169
    nr?: Buffer,
2✔
170
    ar?: Buffer
2✔
171
  ) {
2✔
172
    this.status = status
2✔
173
    this.uid = uid
2✔
174
    this.nt = nt
2✔
175
    this.par = par
2✔
176
    this.ks = ks
2✔
177
    this.nr = nr
2✔
178
    this.ar = ar
2✔
179
  }
2✔
180

181
  static fromCmd2004 (buf: Buffer): Mf1DarksideRes {
2✔
182
    if (!Buffer.isBuffer(buf) || !_.includes([1, 33], buf.length)) throw new TypeError('buf must be a 1 or 33 bytes Buffer.')
4✔
183
    return bufUnpackToClass(buf, buf.length === 1 ? '!B' : '!B4s4s8s8s4s4s', Mf1DarksideRes)
4✔
184
  }
4✔
185
}
2✔
186

187
export class Mf1NtDistanceRes {
1✔
188
  uid: Buffer
1✔
189
  dist: Buffer
1✔
190

191
  constructor (uid: Buffer, dist: Buffer) {
1✔
192
    ;[this.uid, this.dist] = [uid, dist]
1✔
193
  }
1✔
194

195
  static fromCmd2005 (buf: Buffer): Mf1NtDistanceRes {
1✔
196
    bufIsLenOrFail(buf, 8, 'buf')
3✔
197
    return bufUnpackToClass(buf, '!4s4s', Mf1NtDistanceRes)
3✔
198
  }
3✔
199
}
1✔
200

201
/** Answer the random number parameters required for Nested attack */
202
export class Mf1NestedRes {
1✔
203
  nt1: number // Unblocked explicitly random number
2✔
204
  nt2: number // Random number of nested verification encryption
2✔
205
  par: number // The 3 parity bit of nested verification encryption
2✔
206

207
  constructor (nt1: number, nt2: number, par: number) {
2✔
208
    ;[this.nt1, this.nt2, this.par] = [nt1, nt2, par]
2✔
209
  }
2✔
210

211
  static fromCmd2006 (buf: Buffer): Mf1NestedRes[] {
2✔
212
    if (!Buffer.isBuffer(buf)) throw new TypeError('buf must be a Buffer.')
2✔
213
    return _.map(buf.chunk(9), chunk => bufUnpackToClass(chunk, '!IIB', Mf1NestedRes))
1✔
214
  }
2✔
215
}
2✔
216

217
export class Mf1CheckKeysOfSectorsRes {
1✔
218
  found: Buffer
3✔
219
  sectorKeys: Array<Buffer | null>
3✔
220

221
  constructor (found: Buffer, sectorKeys: Buffer[]) {
3✔
222
    this.found = found
3✔
223
    this.sectorKeys = _.times(80, i => found.readBitMSB(i) === 1 ? sectorKeys[i] : null)
3✔
224
  }
3✔
225

226
  static fromCmd2012 (buf: Buffer): Mf1CheckKeysOfSectorsRes {
3✔
227
    bufIsLenOrFail(buf, 490, 'buf')
5✔
228
    return new Mf1CheckKeysOfSectorsRes(
5✔
229
      buf.subarray(0, 10), // found
5✔
230
      buf.subarray(10).chunk(6), // sectorKeys
5✔
231
    )
5✔
232
  }
5✔
233
}
3✔
234

235
export class Mf1DetectionLog {
1✔
236
  block: number
3✔
237
  isKeyB: boolean
3✔
238
  isNested: boolean
3✔
239
  uid: Buffer
3✔
240
  nt: Buffer
3✔
241
  nr: Buffer
3✔
242
  ar: Buffer
3✔
243

244
  constructor (
3✔
245
    block: number,
3✔
246
    flags: Buffer,
3✔
247
    uid: Buffer,
3✔
248
    nt: Buffer,
3✔
249
    nr: Buffer,
3✔
250
    ar: Buffer
3✔
251
  ) {
3✔
252
    this.block = block
3✔
253
    this.isKeyB = flags.readBitLSB(0) === 1
3✔
254
    this.isNested = flags.readBitLSB(1) === 1
3✔
255
    this.uid = uid
3✔
256
    this.nt = nt
3✔
257
    this.nr = nr
3✔
258
    this.ar = ar
3✔
259
  }
3✔
260

261
  static fromBuffer (buf: Buffer): Mf1DetectionLog {
3✔
262
    bufIsLenOrFail(buf, 18, 'buf')
5✔
263
    return bufUnpackToClass(buf, '!Bs4s4s4s4s', Mf1DetectionLog)
5✔
264
  }
5✔
265

266
  static fromCmd4006 (buf: Buffer): Mf1DetectionLog[] {
3✔
267
    if (!Buffer.isBuffer(buf)) throw new TypeError('buf must be a Buffer.')
2✔
268
    return _.map(buf.chunk(18), Mf1DetectionLog.fromBuffer)
1✔
269
  }
2✔
270
}
3✔
271

272
export class Mf1EmuSettings {
1✔
273
  detection: boolean
1✔
274
  gen1a: boolean
1✔
275
  gen2: boolean
1✔
276
  antiColl: boolean
1✔
277
  write: Mf1EmuWriteMode
1✔
278

279
  constructor (detection: boolean, gen1a: boolean, gen2: boolean, antiColl: boolean, write: Mf1EmuWriteMode) {
1✔
280
    this.detection = detection
1✔
281
    this.gen1a = gen1a
1✔
282
    this.gen2 = gen2
1✔
283
    this.antiColl = antiColl
1✔
284
    this.write = write
1✔
285
  }
1✔
286

287
  static fromCmd4009 (buf: Buffer): Mf1EmuSettings {
1✔
288
    bufIsLenOrFail(buf, 5, 'buf')
2✔
289
    return bufUnpackToClass(buf, '!4?B', Mf1EmuSettings)
2✔
290
  }
2✔
291
}
1✔
292

293
/** Answer the random number parameters required for Hard Nested attack */
294
export class Mf1AcquireHardNestedRes {
1✔
295
  nt: number // tag nonce of nested verification encryption
×
296
  ntEnc: number // encrypted tag nonce of nested verification encryption
×
297
  par: number // The 8 parity bit of nested verification encryption
×
298

299
  constructor (nt: number, ntEnc: number, par: number) {
×
300
    ;[this.nt, this.ntEnc, this.par] = [nt, ntEnc, par]
×
301
  }
×
302

303
  static fromCmd2013 (buf: Buffer): Mf1AcquireHardNestedRes[] {
×
304
    if (!Buffer.isBuffer(buf)) throw new TypeError('buf must be a Buffer.')
×
305
    return _.map(buf.chunk(9), chunk => bufUnpackToClass(chunk, '!IIB', Mf1AcquireHardNestedRes))
×
306
  }
×
307
}
×
308

309
export class HidProxScanRes implements HidProxTag {
1✔
NEW
310
  format: HidProxFormat
×
NEW
311
  fc: number
×
NEW
312
  cn: number
×
NEW
313
  il: number
×
NEW
314
  oem: number
×
315

NEW
316
  constructor (format: HidProxFormat, fc: number, cn1: number, cn2: number, il: number, oem: number) {
×
NEW
317
    ;[this.format, this.fc, this.cn, this.il, this.oem] = [format, fc, cn1 * 0x100000000 + cn2, il, oem]
×
NEW
318
  }
×
319

NEW
320
  static fromCmd3002 (buf: Buffer): HidProxScanRes {
×
NEW
321
    bufIsLenOrFail(buf, 13, 'buf')
×
NEW
322
    return bufUnpackToClass(buf, '!BIBIBH', HidProxScanRes)
×
NEW
323
  }
×
NEW
324
}
×
325

326
export class Mf1AcquireStaticEncryptedNestedDecoder implements Mf1AcquireStaticEncryptedNestedRes {
1✔
NEW
327
  uid: number
×
NEW
328
  atks: Mf1AcquireStaticEncryptedNestedRes['atks']
×
329

NEW
330
  constructor (uid: number, atks: Mf1AcquireStaticEncryptedNestedRes['atks']) {
×
NEW
331
    ;[this.uid, this.atks] = [uid, atks]
×
NEW
332
  }
×
333

NEW
334
  static fromCmd2014 (startSector: number, buf: Buffer): Mf1AcquireStaticEncryptedNestedDecoder {
×
NEW
335
    if (!Buffer.isBuffer(buf)) throw new TypeError('buf must be a Buffer')
×
NEW
336
    return new Mf1AcquireStaticEncryptedNestedDecoder(
×
NEW
337
      buf.readUint32BE(0), // uid
×
NEW
338
      _.flatMap(buf.subarray(4).chunk(18), (chunk, i) => [
×
NEW
339
        { // key A
×
NEW
340
          sector: startSector + i,
×
NEW
341
          keyType: Mf1KeyType.KEY_A,
×
NEW
342
          nt: chunk.readUint32BE(0),
×
NEW
343
          ntEnc: chunk.readUint32BE(4),
×
NEW
344
          par: chunk[8],
×
NEW
345
        },
×
NEW
346
        { // key B
×
NEW
347
          sector: startSector + i,
×
NEW
348
          keyType: Mf1KeyType.KEY_B,
×
NEW
349
          nt: chunk.readUint32BE(9),
×
NEW
350
          ntEnc: chunk.readUint32BE(13),
×
NEW
351
          par: chunk[17],
×
NEW
352
        },
×
NEW
353
      ])
×
NEW
354
    )
×
NEW
355
  }
×
NEW
356
}
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc