Skip to content

Commit bfab64f

Browse files
Merge pull request #410 from secvisogram/feat/372-csaf-2.1-mandatory-test-6.1.43
feat(CSAF2.1): #372 add mandatory test 6.1.43
2 parents d4628a9 + dec5d57 commit bfab64f

File tree

5 files changed

+251
-2
lines changed

5 files changed

+251
-2
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -323,7 +323,6 @@ The following tests are not yet implemented and therefore missing:
323323
- Mandatory Test 6.1.27.18
324324
- Mandatory Test 6.1.27.19
325325
- Mandatory Test 6.1.42
326-
- Mandatory Test 6.1.43
327326
- Mandatory Test 6.1.44
328327
- Mandatory Test 6.1.45
329328
- Mandatory Test 6.1.46
@@ -437,6 +436,7 @@ export const mandatoryTest_6_1_38: DocumentTest
437436
export const mandatoryTest_6_1_39: DocumentTest
438437
export const mandatoryTest_6_1_40: DocumentTest
439438
export const mandatoryTest_6_1_41: DocumentTest
439+
export const mandatoryTest_6_1_43: DocumentTest
440440
```
441441
442442
[(back to top)](#bsi-csaf-validator-lib)

csaf_2_1/mandatoryTests.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,3 +50,4 @@ export { mandatoryTest_6_1_38 } from './mandatoryTests/mandatoryTests_6_1_38.js'
5050
export { mandatoryTest_6_1_39 } from './mandatoryTests/mandatoryTest_6_1_39.js'
5151
export { mandatoryTest_6_1_40 } from './mandatoryTests/mandatoryTest_6_1_40.js'
5252
export { mandatoryTest_6_1_41 } from './mandatoryTests/mandatoryTest_6_1_41.js'
53+
export { mandatoryTest_6_1_43 } from './mandatoryTests/mandatoryTest_6_1_43.js'
Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
import Ajv from 'ajv/dist/jtd.js'
2+
3+
const ajv = new Ajv()
4+
5+
const branchSchema = /** @type {const} */ ({
6+
additionalProperties: true,
7+
optionalProperties: {
8+
branches: {
9+
elements: {
10+
additionalProperties: true,
11+
properties: {},
12+
},
13+
},
14+
product: {
15+
additionalProperties: true,
16+
optionalProperties: {
17+
product_identification_helper: {
18+
additionalProperties: true,
19+
optionalProperties: {
20+
model_numbers: { elements: { type: 'string' } },
21+
},
22+
},
23+
},
24+
},
25+
},
26+
})
27+
28+
const validateBranch = ajv.compile(branchSchema)
29+
30+
const fullProductNameSchema = /** @type {const} */ ({
31+
additionalProperties: true,
32+
optionalProperties: {
33+
product_identification_helper: {
34+
additionalProperties: true,
35+
optionalProperties: {
36+
model_numbers: { elements: { type: 'string' } },
37+
},
38+
},
39+
},
40+
})
41+
42+
/*
43+
This is the jtd schema that needs to match the input document so that the
44+
test is activated. If this schema doesn't match, it normally means that the input
45+
document does not validate against the csaf JSON schema or optional fields that
46+
the test checks are not present.
47+
*/
48+
const inputSchema = /** @type {const} */ ({
49+
additionalProperties: true,
50+
optionalProperties: {
51+
product_tree: {
52+
additionalProperties: true,
53+
optionalProperties: {
54+
branches: {
55+
elements: branchSchema,
56+
},
57+
full_product_names: {
58+
elements: fullProductNameSchema,
59+
},
60+
relationships: {
61+
elements: {
62+
additionalProperties: true,
63+
optionalProperties: {
64+
full_product_name: fullProductNameSchema,
65+
},
66+
},
67+
},
68+
},
69+
},
70+
},
71+
})
72+
73+
const validate = ajv.compile(inputSchema)
74+
75+
/**
76+
* @typedef {import('ajv/dist/core').JTDDataType<typeof branchSchema>} Branch
77+
* @typedef {import('ajv/dist/core').JTDDataType<typeof fullProductNameSchema>} FullProductName
78+
*/
79+
80+
/**
81+
*
82+
* @param {string} stringToCheck
83+
* @return {boolean}
84+
*/
85+
export function containMultipleUnescapedStars(stringToCheck) {
86+
const regex = /\*/g
87+
return (stringToCheck.replace(/\\\*/g, '').match(regex)?.length ?? 0) > 1
88+
}
89+
90+
/**
91+
* Validates all given model numbers and
92+
* check whether they contain multiple unescaped stars
93+
*
94+
* @param {Array<string> | undefined} modelNumbers model_numbers to check
95+
* @return {Array<number>} indexes of the model_numbers that invalid
96+
*/
97+
export function checkModelNumbers(modelNumbers) {
98+
/** @type {Array<number>}*/
99+
const invalidNumbers = []
100+
if (modelNumbers) {
101+
for (let i = 0; i < modelNumbers.length; i++) {
102+
const modelNumber = modelNumbers[i]
103+
if (containMultipleUnescapedStars(modelNumber)) {
104+
invalidNumbers.push(i)
105+
}
106+
}
107+
}
108+
return invalidNumbers
109+
}
110+
111+
/**
112+
* For each model number, it MUST be tested
113+
* that it does not contain multiple unescaped stars.
114+
* @param {unknown} doc
115+
*/
116+
export function mandatoryTest_6_1_43(doc) {
117+
/*
118+
The `ctx` variable holds the state that is accumulated during the test run and is
119+
finally returned by the function.
120+
*/
121+
const ctx = {
122+
errors:
123+
/** @type {Array<{ instancePath: string; message: string }>} */ ([]),
124+
isValid: true,
125+
}
126+
127+
if (!validate(doc)) {
128+
return ctx
129+
}
130+
131+
doc.product_tree?.branches?.forEach((branch, index) => {
132+
checkBranch(`/product_tree/branches/${index}`, branch)
133+
})
134+
135+
doc.product_tree?.full_product_names?.forEach((fullProduceName, index) => {
136+
checkFullProductName(
137+
`/product_tree/full_product_names/${index}`,
138+
fullProduceName
139+
)
140+
})
141+
142+
doc.product_tree?.relationships?.forEach((relationship, index) => {
143+
const fullProductName = relationship.full_product_name
144+
if (fullProductName) {
145+
checkFullProductName(
146+
`/product_tree/relationships/${index}/full_product_name`,
147+
fullProductName
148+
)
149+
}
150+
})
151+
152+
return ctx
153+
154+
/**
155+
* Check whether the model numbers contain multiple unescaped stars for a full product name object
156+
*
157+
* @param {string} prefix The instance path prefix of the "full product name". It is
158+
* used to generate error messages.
159+
* @param {FullProductName} fullProductName The "full product name" object.
160+
*/
161+
function checkFullProductName(prefix, fullProductName) {
162+
const invalidNumberIndexes = checkModelNumbers(
163+
fullProductName.product_identification_helper?.model_numbers
164+
)
165+
invalidNumberIndexes.forEach((invalidNumberIndex) => {
166+
ctx.isValid = false
167+
ctx.errors.push({
168+
instancePath: `${prefix}/product_identification_helper/model_numbers/${invalidNumberIndex}`,
169+
message: `model number contains multiple unescaped stars`,
170+
})
171+
})
172+
}
173+
174+
/**
175+
* Check whether the model numbers contain multiple unescaped stars for the given branch object
176+
* and its branch children.
177+
*
178+
* @param {string} prefix The instance path prefix of the "branch". It is
179+
* used to generate error messages.
180+
* @param {Branch} branch The "branch" object.
181+
*/
182+
function checkBranch(prefix, branch) {
183+
const invalidNumberIndexes = checkModelNumbers(
184+
branch.product?.product_identification_helper?.model_numbers
185+
)
186+
invalidNumberIndexes.forEach((invalidNumberIndex) => {
187+
ctx.isValid = false
188+
ctx.errors.push({
189+
instancePath: `${prefix}/product/product_identification_helper/model_numbers/${invalidNumberIndex}`,
190+
message: `model number contains multiple unescaped stars`,
191+
})
192+
})
193+
branch.branches?.forEach((branch, index) => {
194+
if (validateBranch(branch)) {
195+
checkBranch(`${prefix}/branches/${index}`, branch)
196+
}
197+
})
198+
}
199+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import assert from 'node:assert/strict'
2+
3+
import {
4+
mandatoryTest_6_1_43,
5+
containMultipleUnescapedStars,
6+
} from '../../csaf_2_1/mandatoryTests/mandatoryTest_6_1_43.js'
7+
8+
describe('mandatoryTest_6_1_43', function () {
9+
it('only runs on relevant documents', function () {
10+
assert.equal(mandatoryTest_6_1_43({ product_tree: 'mydoc' }).isValid, true)
11+
})
12+
13+
describe('containMultipleUnescapedStars', function () {
14+
const testCases = /** @type {Array<[string, boolean]>} */ ([
15+
// Valid cases - single or no unescaped stars
16+
['PA*', false],
17+
['P?A*', false],
18+
['P??A*', false],
19+
['P???A*', false],
20+
['P????A*', false],
21+
['*PA', false],
22+
['PA', false],
23+
['*P\\*\\*?\\*', false],
24+
['\\*PA*', false],
25+
['PA\\*', false],
26+
['PA\\**', false],
27+
['*\\*', false],
28+
['\\**', false],
29+
['\\*\\*', false],
30+
['\\**\\*', false],
31+
// Invalid cases - multiple unescaped stars
32+
['P*A*', true],
33+
['*P*A', true],
34+
['*P*\\*?*', true],
35+
['**', true],
36+
['***', true],
37+
['*\\**', true],
38+
['*P*', true],
39+
['P*A*B', true],
40+
['P*A*B*', true],
41+
['*P*\\*?*', true],
42+
])
43+
44+
testCases.forEach((testCase) => {
45+
it(`${testCase[0]} -> ${testCase[1]}`, () => {
46+
assert.equal(containMultipleUnescapedStars(testCase[0]), testCase[1])
47+
})
48+
})
49+
})
50+
})

tests/csaf_2_1/oasis.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ const excluded = [
2727
'6.1.27.19',
2828
'6.1.37',
2929
'6.1.42',
30-
'6.1.43',
3130
'6.1.44',
3231
'6.1.45',
3332
'6.1.46',

0 commit comments

Comments
 (0)