Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
aaa0ee3
feat(CSAF2.1): #196 add optionalTest_6_2_31.js
bendo-eXX Apr 28, 2025
908ab0d
feat: rename Test to recommendedTest_6_2_30.js
bendo-eXX Jun 13, 2025
1178c2c
feat: remove unknown doc typ
bendo-eXX Jun 26, 2025
a0d00a8
refactor(CSAF2.1): refactor typedef
bendo-eXX Jul 16, 2025
8558d6b
feat(CSAF2.1): added tests to recommendedTest_6_2_31.js
bendo-eXX Jul 29, 2025
1d38f95
feat(CSAF2.1): change massage
bendo-eXX Aug 20, 2025
a749d1a
feat(CSAF2.1): change massage
bendo-eXX Aug 20, 2025
d818203
feat(CSAF2.1): change branch schema
bendo-eXX Aug 26, 2025
5133948
Merge branch 'main' into 196-csaf-2.1_optional_test_6.2.31
bendo-eXX Oct 22, 2025
d125194
Merge branch 'main' into 196-csaf-2.1_optional_test_6.2.31
bendo-eXX Oct 22, 2025
2dd8ae2
feat: add check for relationships/full_product_name
bendo-eXX Feb 27, 2026
93a9da9
Merge remote-tracking branch 'origin/main' into 196-csaf-2.1_optional…
bendo-eXX Feb 27, 2026
6f2d56d
Merge branch 'refs/heads/main' into 196-csaf-2.1_optional_test_6.2.31
bendo-eXX Jun 10, 2026
f7ba999
feat(CSAF2.1): update test 6.2.31 to the new csaf 2.1 schema
bendo-eXX Jun 10, 2026
439c6e1
feat(CSAF2.1): fix code style issue
bendo-eXX Jun 10, 2026
312a601
Merge branch 'main' into 196-csaf-2.1_optional_test_6.2.31
bendo-eXX Jul 29, 2026
06bdffc
feat(CSAF2.1): change test description
bendo-eXX Jul 29, 2026
6b630b4
Merge branch 'main' into 196-csaf-2.1_optional_test_6.2.31
bendo-eXX Jul 29, 2026
53b03d2
Merge branch 'main' into 196-csaf-2.1_optional_test_6.2.31
bendo-eXX Aug 4, 2026
6b79747
Merge branch 'main' into 196-csaf-2.1_optional_test_6.2.31
bendo-eXX Aug 4, 2026
b6a15db
Merge branch 'main' into 196-csaf-2.1_optional_test_6.2.31
bendo-eXX Aug 13, 2026
3f40bd6
Merge remote-tracking branch 'refs/remotes/origin/main' into 196-csaf…
bendo-eXX Aug 20, 2026
84fda46
feat(csaf2.1): update to vitest
bendo-eXX Aug 20, 2026
0d17039
Merge branch 'main' into 196-csaf-2.1_optional_test_6.2.31
bendo-eXX Sep 24, 2026
348ca2c
Merge branch 'refs/heads/main' into 196-csaf-2.1_optional_test_6.2.31
bendo-eXX Oct 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,6 @@ The following tests are not yet implemented and therefore missing:

- Recommended Test 6.2.20
- Recommended Test 6.2.24
- Recommended Test 6.2.31
- Recommended Test 6.2.38
- Recommended Test 6.2.39.1
- Recommended Test 6.2.46
Expand Down Expand Up @@ -508,6 +507,7 @@ export const recommendedTest_6_2_27: DocumentTest
export const recommendedTest_6_2_28: DocumentTest
export const recommendedTest_6_2_29: DocumentTest
export const recommendedTest_6_2_30: DocumentTest
export const recommendedTest_6_2_31: DocumentTest
export const recommendedTest_6_2_32: DocumentTest
export const recommendedTest_6_2_33: DocumentTest
export const recommendedTest_6_2_34: DocumentTest
Expand Down
1 change: 1 addition & 0 deletions csaf_2_1/recommendedTests.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export { recommendedTest_6_2_27 } from './recommendedTests/recommendedTest_6_2_2
export { recommendedTest_6_2_28 } from './recommendedTests/recommendedTest_6_2_28.js'
export { recommendedTest_6_2_29 } from './recommendedTests/recommendedTest_6_2_29.js'
export { recommendedTest_6_2_30 } from './recommendedTests/recommendedTest_6_2_30.js'
export { recommendedTest_6_2_31 } from './recommendedTests/recommendedTest_6_2_31.js'
export { recommendedTest_6_2_32 } from './recommendedTests/recommendedTest_6_2_32.js'
export { recommendedTest_6_2_33 } from './recommendedTests/recommendedTest_6_2_33.js'
export { recommendedTest_6_2_34 } from './recommendedTests/recommendedTest_6_2_34.js'
Expand Down
236 changes: 236 additions & 0 deletions csaf_2_1/recommendedTests/recommendedTest_6_2_31.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
import { Ajv } from 'ajv/dist/jtd.js'

const ajv = new Ajv()

const productIdentificationHelperSchema = /** @type {const} */ ({
additionalProperties: true,
optionalProperties: {
serial_numbers: {
elements: { type: 'string' },
},
model_numbers: {
elements: { type: 'string' },
},
},
})

const productSchema = /** @type {const} */ ({
additionalProperties: true,
optionalProperties: {
product_id: { type: 'string' },
product_identification_helper: productIdentificationHelperSchema,
},
})

const subpathSchema = /** @type {const} */ ({
additionalProperties: true,
optionalProperties: {
next_product_reference: { type: 'string' },
},
})

const productPathSchema = /** @type {const} */ ({
additionalProperties: true,
optionalProperties: {
beginning_product_reference: { type: 'string' },
full_product_name: productSchema,
subpaths: {
elements: subpathSchema,
},
},
})

const branchSchema = /** @type {const} */ ({
additionalProperties: true,
optionalProperties: {
product: productSchema,
branches: {
elements: {
additionalProperties: true,
// AJV's JTD does not support recursive schemas.
// Nested branches are validated at runtime in checkBranches() by calling
// validateBranch() on each child branch individually during the recursive traversal.
properties: {},
Comment thread
bendo-eXX marked this conversation as resolved.
},
},
},
})

const inputSchema = /** @type {const} */ ({
Comment thread
rainer-exxcellent marked this conversation as resolved.
additionalProperties: true,
optionalProperties: {
product_tree: {
additionalProperties: true,
optionalProperties: {
branches: {
elements: branchSchema,
},
full_product_names: {
elements: productSchema,
},
product_paths: {
elements: productPathSchema,
},
},
},
},
})

const validateInput = ajv.compile(inputSchema)
const validateBranch = ajv.compile(branchSchema)

/**
* @typedef {import('ajv/dist/core.js').JTDDataType<typeof branchSchema>} Branch
* @typedef {import('ajv/dist/core.js').JTDDataType<typeof productSchema>} FullProductName
* @typedef {import('ajv/dist/core.js').JTDDataType<typeof productPathSchema>} ProductPath
*/

/**
* This implements the recommended test 6.2.31 of the CSAF 2.1 standard.
* @param {unknown} doc
*/
export function recommendedTest_6_2_31(doc) {
const ctx = {
warnings:
/** @type {Array<{ instancePath: string; message: string }>} */ ([]),
}

if (!validateInput(doc)) {
return ctx
}

/** @type {ProductPath[]} */
const productPaths = Array.isArray(doc.product_tree?.product_paths)
? doc.product_tree.product_paths
: []

// Start the recursive check from the root branches
checkBranches(doc.product_tree?.branches ?? [], productPaths, ctx)

checkFullProductNames(
doc.product_tree?.full_product_names ?? [],
productPaths,
ctx
)

Comment thread
bendo-eXX marked this conversation as resolved.
productPaths.forEach((pp, index) => {
const fpn = pp?.full_product_name
if (!fpn?.product_identification_helper || !fpn.product_id) return
const { serial_numbers, model_numbers } = fpn.product_identification_helper
if (
(serial_numbers?.length || model_numbers?.length) &&
!checkProductPath(productPaths, fpn.product_id)
) {
ctx.warnings.push({
instancePath: `/product_tree/product_paths/${index}/full_product_name`,
message:
'missing product path: product with serial number or model number should be referenced in a product path.',
})
}
})

return ctx
}

/**
* Check full_product_names for serial_numbers or model_numbers
* @param {FullProductName[]} full_product_names
* @param {ProductPath[]} productPaths
* @param {{ warnings: Array<{ instancePath: string; message: string }> }} ctx
*/
function checkFullProductNames(full_product_names, productPaths, ctx) {
full_product_names.forEach((fullProductName, index) => {
if (
!fullProductName?.product_identification_helper ||
!fullProductName.product_id
)
return
const { serial_numbers, model_numbers } =
fullProductName.product_identification_helper

if (
(serial_numbers?.length || model_numbers?.length) &&
!checkProductPath(productPaths, fullProductName.product_id)
) {
ctx.warnings.push({
instancePath: `/product_tree/full_product_names/${index}`,
message:
'missing product path: product with serial number or model number should be referenced in a product path.',
})
}
})
}

/**
* Recursive function to check branches for products with serial_numbers or model_numbers
* but no corresponding product path.
* @param {Branch[]} branches - The current level of branches to process.
* @param {ProductPath[]} productPaths - The product paths array to check against.
* @param {{ warnings: Array<{ instancePath: string; message: string }> }} ctx - The context to store warnings.
* @param {string} [path='/product_tree/branches'] - The current JSON path.
*/
function checkBranches(
branches,
productPaths,
ctx,
path = '/product_tree/branches'
) {
branches?.forEach((branch, branchIndex) => {
// Skip invalid branches
if (!validateBranch(branch)) return

const currentPath = `${path}/${branchIndex}`
const product = branch.product

if (product) {
if (product.product_id && product.product_identification_helper) {
const { serial_numbers, model_numbers } =
product.product_identification_helper

if (
(serial_numbers?.length || model_numbers?.length) &&
!checkProductPath(productPaths, product.product_id)
) {
ctx.warnings.push({
instancePath: `${currentPath}/product`,
message:
'missing product path: product with serial number or model number should be referenced in a product path.',
})
}
}
}

// Recursively check nested branches
if (Array.isArray(branch.branches)) {
checkBranches(
branch.branches,
productPaths,
ctx,
`${currentPath}/branches`
)
}
})
}

/**
* Check if there is a valid product path referencing the given productId.
* A product path is valid if it has at least one subpath and the productId
* matches either the beginning_product_reference or any subpaths[].next_product_reference.
* @param {ProductPath[]} productPaths
* @param {string} productId
* @returns {boolean}
*/
function checkProductPath(productPaths, productId) {
return productPaths.some((pp) => {
const subpaths = pp.subpaths
if (!Array.isArray(subpaths) || subpaths.length === 0) {
return false
}

if (pp.beginning_product_reference === productId) {
return true
}

return subpaths.some((sp) => sp.next_product_reference === productId)
})
}
1 change: 0 additions & 1 deletion tests/csaf_2_1/oasis.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ const excluded = [
'6.1.60.3',
'6.2.20',
'6.2.24',
'6.2.31',
'6.2.39.1',
'6.2.46',
'6.2.50.1',
Expand Down
Loading
Loading