-
Notifications
You must be signed in to change notification settings - Fork 1
/
check-lwm2m-rules.ts
170 lines (155 loc) · 4.56 KB
/
check-lwm2m-rules.ts
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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
import chalk from 'chalk'
import { readFile, readdir, stat } from 'node:fs/promises'
import path, { parse } from 'node:path'
import assert from 'node:assert/strict'
import xml2js from 'xml2js'
import { exec } from 'node:child_process'
import { unwrapNestedArray } from './unwrapNestedArray.js'
import {
LWM2MObjectDefinition,
type LWM2MObjectDefinitionType,
} from './LWM2MObjectDefinition.js'
import type { ParsedLwM2MObjectDefinition } from './ParsedLwM2MObjectDefinition.js'
import { validate } from '../validate.js'
import { parseRangeEnumeration } from './parseRangeEnumeration.js'
const v = validate(LWM2MObjectDefinition)
const listLwm2mDefinitions = async (
modelDir: string,
): Promise<LWM2MObjectDefinitionType[]> => {
const defs: LWM2MObjectDefinitionType[] = []
const files = await readdir(modelDir)
for (const file of files) {
if (!file.endsWith('.xml')) continue
console.log(chalk.white('·'), chalk.white.bold(file))
assert.match(
file,
/^[0-9]+\.xml$/,
'LwM2M object definition files must only have numbers in their file names',
)
console.log(chalk.green('✔'), chalk.gray('File name is correct'))
const objectDefinitionFile = path.join(modelDir, file)
if ((await stat(objectDefinitionFile)).isDirectory()) continue
// validate
const schemaValidated = await new Promise<boolean>((resolve) =>
exec(
`xmllint --noout --schema ${path.join(
process.cwd(),
'lwm2m',
'LWM2M-v1_1.xsd',
)} ${objectDefinitionFile}`,
(error, _, stderr) => {
if (error) {
console.error(stderr)
return resolve(false)
}
resolve(true)
},
),
)
assert.equal(schemaValidated, true, '')
console.log(
chalk.green('✔'),
chalk.gray('Is a valid LwM2M object definition'),
)
const ObjectID = parseInt(parse(objectDefinitionFile).name, 10)
assert.equal(ObjectID > 14200, true, 'ObjectID must be greater than 14200')
assert.equal(ObjectID < 15000, true, 'ObjectID must be smaller than 15000')
const ObjectURN = `urn:oma:lwm2m:x:${ObjectID}`
const definition = (
unwrapNestedArray(
await xml2js.parseStringPromise(
await readFile(objectDefinitionFile, 'utf-8'),
),
) as any
).LWM2M.Object as ParsedLwM2MObjectDefinition
assert.equal(
ObjectID.toString(),
definition.ObjectID,
`ObjectID must match filename`,
)
assert.equal(
ObjectURN,
definition.ObjectURN,
`ObjectURN must follow schema`,
)
console.log(
chalk.green('✔'),
chalk.blue(ObjectURN),
chalk.gray('ObjectID and URN match filename and schema'),
)
const objectDef: LWM2MObjectDefinitionType = {
...definition,
LWM2MVersion: '1.1',
Resources: {},
}
const Item = definition.Resources.Item
if (Array.isArray(Item)) {
objectDef.Resources = Item.reduce<Record<string, any>>(
(resources, { $, ...item }) => {
if (resources[$.ID] !== undefined)
throw new Error(`Duplicate resource ID: ${$.ID}`)
if (item.RangeEnumeration.length > 0) {
const maybeRange = parseRangeEnumeration(item.RangeEnumeration)
if ('error' in maybeRange) throw maybeRange.error
}
return {
...resources,
[$.ID]: item,
}
},
{},
)
} else {
// Object only has one Resource
objectDef.Resources = {
[Item.$.ID]: Item,
}
}
const maybeValid = v(objectDef)
if ('errors' in maybeValid) {
console.error(maybeValid.errors)
throw new Error(`The definition should be valid!`)
}
console.log(chalk.green('✔'), chalk.gray('LwM2M limitations are honored'))
defs.push(maybeValid.value)
const { Name, Resources } = maybeValid.value
const TimeResources = Object.values(Resources).filter(
({ Type }) => Type === 'Time',
)
assert.equal(
TimeResources.length,
1,
'Objects must define one Time resource',
)
assert.equal(
TimeResources[0]?.Mandatory,
'Mandatory',
'The Time resources must be mandatory.',
)
console.log(
chalk.green('✔'),
chalk.gray(`Object has one time resource (${TimeResources[0]?.Name})`),
)
console.log(`${chalk.yellow(ObjectID)}:`, chalk.white(Name))
for (const [id, resource] of Object.entries(Resources)) {
console.log(
`${chalk.gray(ObjectID)}.${chalk.yellow(id)}:`,
chalk.white(resource.Name),
`${chalk.gray(resource.Type)} ${chalk.gray(
`(${resource.Units ?? 'no unit'})`,
)}`,
)
}
}
return defs
}
// LwM2M
console.log(chalk.gray('LwM2M rules check'))
console.log('')
const lwm2mDir = path.join(process.cwd(), 'lwm2m')
const lwm2mDefinitions = await listLwm2mDefinitions(lwm2mDir)
assert.equal(
lwm2mDefinitions.length > 0,
true,
'LwM2M objects must be defined.',
)