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

taichunmin / chameleon-ultra.js / 15847482271

24 Jun 2025 10:04AM UTC coverage: 69.194% (+5.8%) from 63.351%
15847482271

push

github

web-flow
v0.3.30: change to vitest and lodash-es (#196)

502 of 657 branches covered (76.41%)

Branch coverage included in aggregate %.

12 of 18 new or added lines in 14 files covered. (66.67%)

218 existing lines in 5 files now uncovered.

2683 of 3946 relevant lines covered (67.99%)

3159783.57 hits per line

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

96.33
/src/ResponseDecoder.ts
1
import { Buffer } from '@taichunmin/buffer'
1✔
2
import * as _ from 'lodash-es'
1✔
3
import { type Class } from 'utility-types'
4

5
import {
6
  type AnimationMode,
7
  type ButtonAction,
8
  type DarksideStatus,
9
  type Mf1EmuWriteMode,
10
  type Mf1PrngType,
11
  type TagType,
12
} from './enums'
13

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

18
export class SlotInfo {
1✔
19
  hfTagType: TagType
8✔
20
  lfTagType: TagType
8✔
21

22
  constructor (hf: TagType, lf: TagType) {
8✔
23
    this.hfTagType = hf
8✔
24
    this.lfTagType = lf
8✔
25
  }
8✔
26

27
  static fromCmd1019 (buf: Buffer): SlotInfo[] {
8✔
28
    bufIsLenOrFail(buf, 32, 'buf')
3✔
29
    return _.map(buf.chunk(4), chunk => bufUnpackToClass(chunk, '!HH', SlotInfo))
3✔
30
  }
3✔
31
}
8✔
32

33
export class SlotFreqIsEnable {
1✔
34
  hf: boolean
8✔
35
  lf: boolean
8✔
36

37
  constructor (hf: boolean, lf: boolean) {
8✔
38
    ;[this.hf, this.lf] = _.map([hf, lf], Boolean)
8✔
39
  }
8✔
40

41
  static fromCmd1023 (buf: Buffer): SlotFreqIsEnable[] {
8✔
42
    bufIsLenOrFail(buf, 16, 'buf')
3✔
43
    return _.map(buf.chunk(2), chunk => bufUnpackToClass(chunk, '!??', SlotFreqIsEnable))
3✔
44
  }
3✔
45
}
8✔
46

47
export class BatteryInfo {
1✔
48
  voltage: number
1✔
49
  level: number
1✔
50

51
  constructor (voltage: number, level: number) {
1✔
52
    ;[this.voltage, this.level] = [voltage, level]
1✔
53
  }
1✔
54

55
  static fromCmd1025 (buf: Buffer): BatteryInfo {
1✔
56
    bufIsLenOrFail(buf, 3, 'buf')
3✔
57
    return bufUnpackToClass(buf, '!HB', BatteryInfo)
3✔
58
  }
3✔
59
}
1✔
60

61
export class DeviceSettings {
1✔
62
  version: number // version of setting
1✔
63
  animation: AnimationMode
1✔
64
  buttonPressAction: ButtonAction[]
1✔
65
  buttonLongPressAction: ButtonAction[]
1✔
66
  blePairingMode: boolean
1✔
67
  blePairingKey: string
1✔
68

69
  constructor (
1✔
70
    version: number,
1✔
71
    animation: AnimationMode,
1✔
72
    btnPressA: ButtonAction,
1✔
73
    btnPressB: ButtonAction,
1✔
74
    btnLongPressA: ButtonAction,
1✔
75
    btnLongPressB: ButtonAction,
1✔
76
    blePairingMode: boolean,
1✔
77
    blePairingKey: string
1✔
78
  ) {
1✔
79
    this.version = version
1✔
80
    this.animation = animation
1✔
81
    this.buttonPressAction = [btnPressA, btnPressB]
1✔
82
    this.buttonLongPressAction = [btnLongPressA, btnLongPressB]
1✔
83
    this.blePairingMode = Boolean(blePairingMode)
1✔
84
    this.blePairingKey = blePairingKey
1✔
85
  }
1✔
86

87
  static fromCmd1034 (buf: Buffer): DeviceSettings {
1✔
88
    bufIsLenOrFail(buf, 13, 'buf')
3✔
89
    return bufUnpackToClass(buf, '!6B?6s', DeviceSettings)
3✔
90
  }
3✔
91
}
1✔
92

93
/**
94
 * Class for Hf14aAntiColl Decoding.
95
 */
96
export class Hf14aAntiColl {
1✔
97
  uid: Buffer
3✔
98
  atqa: Buffer
3✔
99
  sak: Buffer
3✔
100
  ats: Buffer
3✔
101

102
  constructor (uid: Buffer, atqa: Buffer, sak: Buffer, ats: Buffer) {
3✔
103
    ;[this.uid, this.atqa, this.sak, this.ats] = [uid, atqa, sak, ats]
3✔
104
  }
3✔
105

106
  static fromBuffer (buf: Buffer): Hf14aAntiColl {
3✔
107
    // uidlen[1]|uid[uidlen]|atqa[2]|sak[1]|atslen[1]|ats[atslen]
108
    const uidLen = buf[0]
5✔
109
    if (buf.length < uidLen + 4) throw new Error('invalid length of uid')
5✔
110
    const atsLen = buf[uidLen + 4]
4✔
111
    if (buf.length < uidLen + atsLen + 5) throw new Error('invalid length of ats')
4✔
112
    return bufUnpackToClass(buf, `!${uidLen + 1}p2ss${atsLen + 1}p`, Hf14aAntiColl)
3✔
113
  }
5✔
114

115
  static fromCmd2000 (buf: Buffer): Hf14aAntiColl[] {
3✔
116
    if (!Buffer.isBuffer(buf)) throw new TypeError('buf must be a Buffer')
3✔
117
    const tags: Hf14aAntiColl[] = []
2✔
118
    while (buf.length > 0) {
2✔
119
      const tag = Hf14aAntiColl.fromBuffer(buf)
2✔
120
      buf = buf.subarray(tag.uid.length + tag.ats.length + 5)
2✔
121
      tags.push(tag)
2✔
122
    }
2✔
123
    return tags
2✔
124
  }
3✔
125
}
3✔
126

127
export class Mf1AcquireStaticNestedRes {
1✔
128
  uid: Buffer
1✔
129
  atks: Array<{ nt1: Buffer, nt2: Buffer }>
1✔
130

131
  constructor (uid: Buffer, atks: Array<{ nt1: Buffer, nt2: Buffer }>) {
1✔
132
    ;[this.uid, this.atks] = [uid, atks]
1✔
133
  }
1✔
134

135
  static fromCmd2003 (buf: Buffer): Mf1AcquireStaticNestedRes {
1✔
136
    if (!Buffer.isBuffer(buf)) throw new TypeError('buf must be a Buffer')
2✔
137
    return new Mf1AcquireStaticNestedRes(
1✔
138
      buf.subarray(0, 4), // uid
1✔
139
      _.map(buf.subarray(4).chunk(8), chunk => ({
1✔
140
        nt1: chunk.subarray(0, 4),
2✔
141
        nt2: chunk.subarray(4, 8),
2✔
142
      })), // atks
1✔
143
    )
1✔
144
  }
2✔
145
}
1✔
146

147
export class Mf1DarksideRes {
1✔
148
  status: DarksideStatus
2✔
149
  uid?: Buffer
2✔
150
  nt?: Buffer
2✔
151
  par?: Buffer
2✔
152
  ks?: Buffer
2✔
153
  nr?: Buffer
2✔
154
  ar?: Buffer
2✔
155

156
  constructor (
2✔
157
    status: DarksideStatus,
2✔
158
    uid?: Buffer,
2✔
159
    nt?: Buffer,
2✔
160
    par?: Buffer,
2✔
161
    ks?: Buffer,
2✔
162
    nr?: Buffer,
2✔
163
    ar?: Buffer
2✔
164
  ) {
2✔
165
    this.status = status
2✔
166
    this.uid = uid
2✔
167
    this.nt = nt
2✔
168
    this.par = par
2✔
169
    this.ks = ks
2✔
170
    this.nr = nr
2✔
171
    this.ar = ar
2✔
172
  }
2✔
173

174
  static fromCmd2004 (buf: Buffer): Mf1DarksideRes {
2✔
175
    if (!Buffer.isBuffer(buf) || !_.includes([1, 33], buf.length)) throw new TypeError('buf must be a 1 or 33 bytes Buffer.')
4✔
176
    return bufUnpackToClass(buf, buf.length === 1 ? '!B' : '!B4s4s8s8s4s4s', Mf1DarksideRes)
4✔
177
  }
4✔
178
}
2✔
179

180
export class Mf1NtDistanceRes {
1✔
181
  uid: Buffer
1✔
182
  dist: Buffer
1✔
183

184
  constructor (uid: Buffer, dist: Buffer) {
1✔
185
    ;[this.uid, this.dist] = [uid, dist]
1✔
186
  }
1✔
187

188
  static fromCmd2005 (buf: Buffer): Mf1NtDistanceRes {
1✔
189
    bufIsLenOrFail(buf, 8, 'buf')
3✔
190
    return bufUnpackToClass(buf, '!4s4s', Mf1NtDistanceRes)
3✔
191
  }
3✔
192
}
1✔
193

194
/** Answer the random number parameters required for Nested attack */
195
export class Mf1NestedRes {
1✔
196
  nt1: number // Unblocked explicitly random number
2✔
197
  nt2: number // Random number of nested verification encryption
2✔
198
  par: number // The 3 parity bit of nested verification encryption
2✔
199

200
  constructor (nt1: number, nt2: number, par: number) {
2✔
201
    ;[this.nt1, this.nt2, this.par] = [nt1, nt2, par]
2✔
202
  }
2✔
203

204
  static fromCmd2006 (buf: Buffer): Mf1NestedRes[] {
2✔
205
    if (!Buffer.isBuffer(buf)) throw new TypeError('buf must be a Buffer.')
2✔
206
    return _.map(buf.chunk(9), chunk => bufUnpackToClass(chunk, '!IIB', Mf1NestedRes))
1✔
207
  }
2✔
208
}
2✔
209

210
export class Mf1CheckKeysOfSectorsRes {
1✔
211
  found: Buffer
3✔
212
  sectorKeys: Array<Buffer | null>
3✔
213

214
  constructor (found: Buffer, sectorKeys: Buffer[]) {
3✔
215
    this.found = found
3✔
216
    this.sectorKeys = _.times(80, i => found.readBitMSB(i) === 1 ? sectorKeys[i] : null)
3✔
217
  }
3✔
218

219
  static fromCmd2012 (buf: Buffer): Mf1CheckKeysOfSectorsRes {
3✔
220
    bufIsLenOrFail(buf, 490, 'buf')
5✔
221
    return new Mf1CheckKeysOfSectorsRes(
5✔
222
      buf.subarray(0, 10), // found
5✔
223
      buf.subarray(10).chunk(6), // sectorKeys
5✔
224
    )
5✔
225
  }
5✔
226
}
3✔
227

228
export interface Hf14aTagInfo {
229
  antiColl: Hf14aAntiColl
230
  nxpTypeBySak?: string
231
  prngType?: Mf1PrngType
232
}
233

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

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

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

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

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

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

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

292
export interface Mf1EmuData {
293
  antiColl: Hf14aAntiColl
294
  settings: Mf1EmuSettings
295
  body: Buffer
296
}
297

298
export interface SlotSettings {
299
  config: { activated: number }
300
  group: Array<{
301
    hfIsEnable: boolean
302
    hfTagType: TagType
303
    lfIsEnable: boolean
304
    lfTagType: TagType
305
  }>
306
}
307

308
function bufIsLenOrFail (buf: Buffer, len: number, name: string): void {
27✔
309
  if (Buffer.isBuffer(buf) && buf.length === len) return
27✔
310
  throw new TypeError(`${name} must be a ${len} ${['byte', 'bytes'][+(len > 1)]} Buffer.`)
15✔
311
}
15✔
312

313
/** Answer the random number parameters required for Hard Nested attack */
314
export class Mf1AcquireHardNestedRes {
1✔
UNCOV
315
  nt: number // tag nonce of nested verification encryption
×
UNCOV
316
  ntEnc: number // encrypted tag nonce of nested verification encryption
×
UNCOV
317
  par: number // The 8 parity bit of nested verification encryption
×
318

UNCOV
319
  constructor (nt: number, ntEnc: number, par: number) {
×
320
    ;[this.nt, this.ntEnc, this.par] = [nt, ntEnc, par]
×
UNCOV
321
  }
×
322

UNCOV
323
  static fromCmd2013 (buf: Buffer): Mf1AcquireHardNestedRes[] {
×
324
    if (!Buffer.isBuffer(buf)) throw new TypeError('buf must be a Buffer.')
×
325
    return _.map(buf.chunk(9), chunk => bufUnpackToClass(chunk, '!IIB', Mf1AcquireHardNestedRes))
×
UNCOV
326
  }
×
UNCOV
327
}
×
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