-
Notifications
You must be signed in to change notification settings - Fork 4
/
TimedLookup.js
116 lines (100 loc) · 4.84 KB
/
TimedLookup.js
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
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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
import { getTimestamp } from 'source/common/time'
import { getRandomArrayBuffer } from 'source/common/math/random'
import { swapObfuscateString } from 'source/common/data/function'
import { packArrayBufferPacket, parseArrayBufferPacket } from 'source/common/data/ArrayBufferPacket'
const CHECK_CODE_SEP = '-'
const CHAR_CODE_1 = '1'.charCodeAt(0)
const calcCode = (size, tokenSize, dataView, seed = 0) => {
seed = Math.floor(seed)
const seedBinaryString = seed.toString(2)
const valueMax = Math.pow(16, tokenSize)
let index = seed % size
let value = dataView.getUint8(index) // 0 to 255, 8bit
__DEV__ && console.log('calcCode', { seed, seedBinaryString, index, value })
for (let seedIndex = 0, seedIndexMax = seedBinaryString.length; seedIndex < seedIndexMax; seedIndex++) {
if (seedBinaryString.charCodeAt(seedIndex) === CHAR_CODE_1) index = (index + dataView.getUint8((index + 1) % size)) % size
else value = value * 16 // shift 4bit
value = (value + dataView.getUint8(index)) % valueMax
__DEV__ && console.log('calcCode step', { dataViewData: dataView.getUint8(index), index, seedIndex, value })
}
__DEV__ && console.log('calcCode', { value })
return (value % Math.pow(2, 4 * tokenSize)).toString(16).padStart(tokenSize, '0')
}
const verifyOption = ({
tag = getTimestamp().toString(36), // /^\w*$/ only, public visible, set a long tag will cause long checkCode
size = 64 * 1024, // in byte, 32 based, min 1024byte
tokenSize = 8, // in byte, min 2byte, max 13byte (limited by calc step `Math.pow(16, tokenSize)`)
timeGap = 30, // in sec, min 1sec, set amount of client-server time diff is accepted
info = null // extra data, any JSON value, can be used for marking what this is for
}) => {
if (!/^\w*$/.test(tag)) throw new Error(`invalid tag: ${tag}`)
if (!Number.isInteger(size) || size <= 1024 || size % 32) throw new Error(`invalid size: ${size}`)
if (!Number.isInteger(tokenSize) || tokenSize > 13 || tokenSize < 2) throw new Error(`invalid tokenSize: ${tokenSize}`)
if (!Number.isInteger(timeGap) || timeGap < 1) throw new Error(`invalid timeGap: ${timeGap}`)
return { tag, size, tokenSize, timeGap, info }
}
const verifyCheckCode = (
timedLookupData,
checkCode,
timestamp = getTimestamp()
) => {
__DEV__ && console.log('verifyCheckCode', timedLookupData.tokenSize, timedLookupData.timeGap, checkCode, timestamp)
if (typeof (checkCode) !== 'string' || checkCode.length < timedLookupData.tokenSize) throw new Error(`invalid checkCode: ${checkCode}`)
verifyParsedCheckCode(timedLookupData, parseCheckCode(checkCode), timestamp)
}
const verifyParsedCheckCode = (
{ tag, size, tokenSize, timeGap, dataView },
[ verifyTag, verifyTimestamp, verifyCode ],
timestamp = getTimestamp()
) => {
__DEV__ && console.log('verifyParsedCheckCode', tokenSize, timeGap, [ verifyTag, verifyTimestamp, verifyCode ], timestamp)
if (verifyTag !== tag) throw new Error(`tag mismatch: ${verifyTag}, expect: ${tag}`)
if (Math.abs(timestamp - verifyTimestamp) > timeGap) throw new Error(`timestamp mismatch: ${verifyTimestamp}, expect: ${timestamp}±${timeGap}`)
const code = calcCode(size, tokenSize, dataView, verifyTimestamp / timeGap)
if (code !== verifyCode) throw new Error(`code mismatch: ${verifyCode}, expect: ${code}`)
}
const generateLookupData = (option) => {
option = verifyOption(option)
return {
...option,
dataView: new DataView(getRandomArrayBuffer(option.size)) // TODO: add timestamp & checksum of dataView?
}
}
const generateCheckCode = (
{ tag, size, tokenSize, timeGap, dataView },
timestamp = getTimestamp()
) => {
const code = calcCode(size, tokenSize, dataView, timestamp / timeGap)
__DEV__ && console.log('generateCheckCode', tokenSize, timeGap, timestamp, code)
return packCheckCode(tag, timestamp, code)
}
const packCheckCode = (tagString, timestamp, codeString) => swapObfuscateString([
tagString,
timestamp.toString(36),
codeString
].join(CHECK_CODE_SEP))
const parseCheckCode = (checkCodeString) => {
const resultList = swapObfuscateString(checkCodeString).split(CHECK_CODE_SEP)
resultList[ 1 ] = Number.parseInt(resultList[ 1 ], 36) // string to number
return resultList // [ tagString, timestamp, codeString ]
}
const packDataArrayBuffer = ({ tag, size, tokenSize, timeGap, info, dataView }) => packArrayBufferPacket(
JSON.stringify([ tag, size, tokenSize, timeGap, info ]),
dataView.buffer
)
const parseDataArrayBuffer = (dataArrayBuffer) => {
const [ headerString, payloadArrayBuffer ] = parseArrayBufferPacket(dataArrayBuffer)
const [ tag, size, tokenSize, timeGap, info = null ] = JSON.parse(headerString)
return { tag, size, tokenSize, timeGap, info, dataView: new DataView(payloadArrayBuffer) }
}
export {
verifyOption,
verifyCheckCode,
verifyParsedCheckCode,
generateLookupData,
generateCheckCode,
packCheckCode,
parseCheckCode,
packDataArrayBuffer,
parseDataArrayBuffer
}