Radish alpha
H
rad:z3QDZAW2FAfuLvihrhiyDC9fAD8G9
HardenedBSD Package Manager
Radicle
Git
libpkg add: OSVF parser
Tuukka Pasanen committed 7 months ago
commit 8208c23b1042929edaf1f800cd466df26958b562
parent 351bacc
7 files changed +2086 -1
modified libpkg/Makefile.autosetup
@@ -24,6 +24,7 @@ SRCS= backup_lib.c \
	pkg_config.c \
	pkg_event.c \
	pkg_object.c \
+
	pkg_osvf.c \
	pkg_repo.c \
	pkgdb_query.c \
	sha256.c dns_utils.c \
modified libpkg/pkg/audit.h
@@ -29,6 +29,9 @@
extern "C" {
#endif

+
#include <time.h>
+
#include <ucl.h>
+

#define EQ 1
#define LT 2
#define LTE 3
@@ -43,6 +46,7 @@ struct pkg_audit_version {
struct pkg_audit_versions_range {
	struct pkg_audit_version v1;
	struct pkg_audit_version v2;
+
	int type;
	struct pkg_audit_versions_range *next;
};

@@ -56,9 +60,22 @@ struct pkg_audit_pkgname {
	struct pkg_audit_pkgname *next;
};

+
struct pkg_audit_reference {
+
	char *url;
+
	int type;
+
	struct pkg_audit_reference *next;
+
};
+

+
struct pkg_audit_ecosystem {
+
	char *original;
+
	char *name;
+
	ucl_object_t *params;
+
};
+

struct pkg_audit_package {
	struct pkg_audit_pkgname *names;
	struct pkg_audit_versions_range *versions;
+
	struct pkg_audit_ecosystem *ecosystem;
	struct pkg_audit_package *next;
};

@@ -68,6 +85,10 @@ struct pkg_audit_entry {
	struct pkg_audit_pkgname *names;
	struct pkg_audit_versions_range *versions;
	struct pkg_audit_cve *cve;
+
	struct pkg_audit_reference *references;
+
	struct tm modified;
+
	struct tm published;
+
	struct tm discovery;
	char *url;
	char *desc;
	char *id;
added libpkg/pkg_osvf.c
@@ -0,0 +1,1524 @@
+
/*-
+
 * SPDX-License-Identifier: BSD-2-Clause
+
 *
+
 *​ Copyright (c) 2025 The FreeBSD Foundation
+
 *​
+
 *​ Portions of this software were developed by
+
 * Tuukka Pasanen <tuukka.pasanen@ilmi.fi> under sponsorship from
+
 * the FreeBSD Foundation
+
 */
+

+

+
#include <ctype.h>
+
#include <sys/types.h>
+
#include <sys/mman.h>
+
#include <sys/stat.h>
+
#include <sys/time.h>
+
#include <stdio.h>
+
#include <errno.h>
+
#include <unistd.h>
+
#include <fcntl.h>
+
#include <time.h>
+
#include <xmalloc.h>
+

+
#include "private/pkg_osvf.h"
+
#include "pkghash.h"
+

+
/*
+
  Open Source Vulnerability format: https://ossf.github.io/osv-schema/
+
  OSVF schema: https://github.com/ossf/osv-schema/blob/main/validation/schema.json
+
  OSVF schema version: 1.7.0
+
 */
+
static const char osvf_schema_str[] = "{"
+
                                      "  \"$schema\": \"https://json-schema.org/draft/2020-12/schema\","
+
                                      "  \"$id\": \"https://raw.githubusercontent.com/ossf/osv-schema/main/validation/schema.json\","
+
                                      "  \"title\": \"Open Source Vulnerability\","
+
                                      "  \"description\": \"A schema for describing a vulnerability in an open source package. See also https://ossf.github.io/osv-schema/\","
+
                                      "  \"type\": \"object\","
+
                                      "  \"properties\": {"
+
                                      "    \"schema_version\": {"
+
                                      "      \"type\": \"string\""
+
                                      "    },"
+
                                      "    \"id\": {"
+
                                      "      \"$ref\": \"#/$defs/prefix\""
+
                                      "    },"
+
                                      "    \"modified\": {"
+
                                      "      \"$ref\": \"#/$defs/timestamp\""
+
                                      "    },"
+
                                      "    \"published\": {"
+
                                      "      \"$ref\": \"#/$defs/timestamp\""
+
                                      "    },"
+
                                      "    \"withdrawn\": {"
+
                                      "      \"$ref\": \"#/$defs/timestamp\""
+
                                      "    },"
+
                                      "    \"aliases\": {"
+
                                      "      \"type\": ["
+
                                      "        \"array\","
+
                                      "        \"null\""
+
                                      "      ],"
+
                                      "      \"items\": {"
+
                                      "        \"type\": \"string\""
+
                                      "      }"
+
                                      "    },"
+
                                      "    \"related\": {"
+
                                      "      \"type\": \"array\","
+
                                      "      \"items\": {"
+
                                      "        \"type\": \"string\""
+
                                      "      }"
+
                                      "    },"
+
                                      "    \"upstream\": {"
+
                                      "      \"type\": \"array\","
+
                                      "      \"items\": {"
+
                                      "        \"type\": \"string\""
+
                                      "      }"
+
                                      "    },"
+
                                      "    \"summary\": {"
+
                                      "      \"type\": \"string\""
+
                                      "    },"
+
                                      "    \"details\": {"
+
                                      "      \"type\": \"string\""
+
                                      "    },"
+
                                      "    \"severity\": {"
+
                                      "      \"$ref\": \"#/$defs/severity\""
+
                                      "    },"
+
                                      "    \"affected\": {"
+
                                      "      \"type\": ["
+
                                      "        \"array\","
+
                                      "        \"null\""
+
                                      "      ],"
+
                                      "      \"items\": {"
+
                                      "        \"type\": \"object\","
+
                                      "        \"properties\": {"
+
                                      "          \"package\": {"
+
                                      "            \"type\": \"object\","
+
                                      "            \"properties\": {"
+
                                      "              \"ecosystem\": {"
+
                                      "                \"$ref\": \"#/$defs/ecosystemWithSuffix\""
+
                                      "              },"
+
                                      "              \"name\": {"
+
                                      "                \"type\": \"string\""
+
                                      "              },"
+
                                      "              \"purl\": {"
+
                                      "                \"type\": \"string\""
+
                                      "              }"
+
                                      "            },"
+
                                      "            \"required\": ["
+
                                      "              \"ecosystem\","
+
                                      "              \"name\""
+
                                      "            ]"
+
                                      "          },"
+
                                      "          \"severity\": {"
+
                                      "            \"$ref\": \"#/$defs/severity\""
+
                                      "          },"
+
                                      "          \"ranges\": {"
+
                                      "            \"type\": \"array\","
+
                                      "            \"items\": {"
+
                                      "              \"type\": \"object\","
+
                                      "              \"properties\": {"
+
                                      "                \"type\": {"
+
                                      "                  \"type\": \"string\","
+
                                      "                  \"enum\": ["
+
                                      "                    \"GIT\","
+
                                      "                    \"SEMVER\","
+
                                      "                    \"ECOSYSTEM\""
+
                                      "                  ]"
+
                                      "                },"
+
                                      "                \"repo\": {"
+
                                      "                  \"type\": \"string\""
+
                                      "                },"
+
                                      "                \"events\": {"
+
                                      "                  \"title\": \"events must contain an introduced object and may contain fixed, last_affected or limit objects\","
+
                                      "                  \"type\": \"array\","
+
                                      "                  \"contains\": {"
+
                                      "                    \"required\": ["
+
                                      "                      \"introduced\""
+
                                      "                    ]"
+
                                      "                  },"
+
                                      "                  \"items\": {"
+
                                      "                    \"type\": \"object\","
+
                                      "                    \"oneOf\": ["
+
                                      "                      {"
+
                                      "                        \"type\": \"object\","
+
                                      "                        \"properties\": {"
+
                                      "                          \"introduced\": {"
+
                                      "                            \"type\": \"string\""
+
                                      "                          }"
+
                                      "                        },"
+
                                      "                        \"required\": ["
+
                                      "                          \"introduced\""
+
                                      "                        ]"
+
                                      "                      },"
+
                                      "                      {"
+
                                      "                        \"type\": \"object\","
+
                                      "                        \"properties\": {"
+
                                      "                          \"fixed\": {"
+
                                      "                            \"type\": \"string\""
+
                                      "                          }"
+
                                      "                        },"
+
                                      "                        \"required\": ["
+
                                      "                          \"fixed\""
+
                                      "                        ]"
+
                                      "                      },"
+
                                      "                      {"
+
                                      "                        \"type\": \"object\","
+
                                      "                        \"properties\": {"
+
                                      "                          \"last_affected\": {"
+
                                      "                            \"type\": \"string\""
+
                                      "                          }"
+
                                      "                        },"
+
                                      "                        \"required\": ["
+
                                      "                          \"last_affected\""
+
                                      "                        ]"
+
                                      "                      },"
+
                                      "                      {"
+
                                      "                        \"type\": \"object\","
+
                                      "                        \"properties\": {"
+
                                      "                          \"limit\": {"
+
                                      "                            \"type\": \"string\""
+
                                      "                          }"
+
                                      "                        },"
+
                                      "                        \"required\": ["
+
                                      "                          \"limit\""
+
                                      "                        ]"
+
                                      "                      }"
+
                                      "                    ]"
+
                                      "                  },"
+
                                      "                  \"minItems\": 1"
+
                                      "                },"
+
                                      "                \"database_specific\": {"
+
                                      "                  \"type\": \"object\""
+
                                      "                }"
+
                                      "              },"
+
                                      "              \"allOf\": ["
+
                                      "                {"
+
                                      "                  \"title\": \"GIT ranges require a repo\","
+
                                      "                  \"if\": {"
+
                                      "                    \"properties\": {"
+
                                      "                      \"type\": {"
+
                                      "                        \"const\": \"GIT\""
+
                                      "                      }"
+
                                      "                    }"
+
                                      "                  },"
+
                                      "                  \"then\": {"
+
                                      "                    \"required\": ["
+
                                      "                      \"repo\""
+
                                      "                    ]"
+
                                      "                  }"
+
                                      "                },"
+
                                      "                {"
+
                                      "                  \"title\": \"last_affected and fixed events are mutually exclusive\","
+
                                      "                  \"if\": {"
+
                                      "                    \"properties\": {"
+
                                      "                      \"events\": {"
+
                                      "                        \"contains\": {"
+
                                      "                          \"required\": ["
+
                                      "                            \"last_affected\""
+
                                      "                          ]"
+
                                      "                        }"
+
                                      "                      }"
+
                                      "                    }"
+
                                      "                  },"
+
                                      "                  \"then\": {"
+
                                      "                    \"not\": {"
+
                                      "                      \"properties\": {"
+
                                      "                        \"events\": {"
+
                                      "                          \"contains\": {"
+
                                      "                            \"required\": ["
+
                                      "                              \"fixed\""
+
                                      "                            ]"
+
                                      "                          }"
+
                                      "                        }"
+
                                      "                      }"
+
                                      "                    }"
+
                                      "                  }"
+
                                      "                }"
+
                                      "              ],"
+
                                      "              \"required\": ["
+
                                      "                \"type\","
+
                                      "                \"events\""
+
                                      "              ]"
+
                                      "            }"
+
                                      "          },"
+
                                      "          \"versions\": {"
+
                                      "            \"type\": \"array\","
+
                                      "            \"items\": {"
+
                                      "              \"type\": \"string\""
+
                                      "            }"
+
                                      "          },"
+
                                      "          \"ecosystem_specific\": {"
+
                                      "            \"type\": \"object\""
+
                                      "          },"
+
                                      "          \"database_specific\": {"
+
                                      "            \"type\": \"object\""
+
                                      "          }"
+
                                      "        }"
+
                                      "      }"
+
                                      "    },"
+
                                      "    \"references\": {"
+
                                      "      \"type\": ["
+
                                      "        \"array\","
+
                                      "        \"null\""
+
                                      "      ],"
+
                                      "      \"items\": {"
+
                                      "        \"type\": \"object\","
+
                                      "        \"properties\": {"
+
                                      "          \"type\": {"
+
                                      "            \"type\": \"string\","
+
                                      "            \"enum\": ["
+
                                      "              \"ADVISORY\","
+
                                      "              \"ARTICLE\","
+
                                      "              \"DETECTION\","
+
                                      "              \"DISCUSSION\","
+
                                      "              \"REPORT\","
+
                                      "              \"FIX\","
+
                                      "              \"INTRODUCED\","
+
                                      "              \"GIT\","
+
                                      "              \"PACKAGE\","
+
                                      "              \"EVIDENCE\","
+
                                      "              \"WEB\""
+
                                      "            ]"
+
                                      "          },"
+
                                      "          \"url\": {"
+
                                      "            \"type\": \"string\","
+
                                      "            \"format\": \"uri\""
+
                                      "          }"
+
                                      "        },"
+
                                      "        \"required\": ["
+
                                      "          \"type\","
+
                                      "          \"url\""
+
                                      "        ]"
+
                                      "      }"
+
                                      "    },"
+
                                      "    \"credits\": {"
+
                                      "      \"type\": \"array\","
+
                                      "      \"items\": {"
+
                                      "        \"type\": \"object\","
+
                                      "        \"properties\": {"
+
                                      "          \"name\": {"
+
                                      "            \"type\": \"string\""
+
                                      "          },"
+
                                      "          \"contact\": {"
+
                                      "            \"type\": \"array\","
+
                                      "            \"items\": {"
+
                                      "              \"type\": \"string\""
+
                                      "            }"
+
                                      "          },"
+
                                      "          \"type\": {"
+
                                      "            \"type\": \"string\","
+
                                      "            \"enum\": ["
+
                                      "              \"FINDER\","
+
                                      "              \"REPORTER\","
+
                                      "              \"ANALYST\","
+
                                      "              \"COORDINATOR\","
+
                                      "              \"REMEDIATION_DEVELOPER\","
+
                                      "              \"REMEDIATION_REVIEWER\","
+
                                      "              \"REMEDIATION_VERIFIER\","
+
                                      "              \"TOOL\","
+
                                      "              \"SPONSOR\","
+
                                      "              \"OTHER\""
+
                                      "            ]"
+
                                      "          }"
+
                                      "        },"
+
                                      "        \"required\": ["
+
                                      "          \"name\""
+
                                      "        ]"
+
                                      "      }"
+
                                      "    },"
+
                                      "    \"database_specific\": {"
+
                                      "      \"type\": \"object\""
+
                                      "    }"
+
                                      "  },"
+
                                      "  \"required\": ["
+
                                      "    \"id\","
+
                                      "    \"modified\""
+
                                      "  ],"
+
                                      "  \"allOf\": ["
+
                                      "    {"
+
                                      "      \"if\": {"
+
                                      "        \"required\": ["
+
                                      "          \"severity\""
+
                                      "        ]"
+
                                      "      },"
+
                                      "      \"then\": {"
+
                                      "        \"properties\": {"
+
                                      "          \"affected\": {"
+
                                      "            \"items\": {"
+
                                      "              \"properties\": {"
+
                                      "                \"severity\": {"
+
                                      "                  \"type\": \"null\""
+
                                      "                }"
+
                                      "              }"
+
                                      "            }"
+
                                      "          }"
+
                                      "        }"
+
                                      "      }"
+
                                      "    }"
+
                                      "  ],"
+
                                      "  \"$defs\": {"
+
                                      "    \"ecosystemName\": {"
+
                                      "      \"type\": \"string\","
+
                                      "      \"title\": \"Currently supported ecosystems\","
+
                                      "      \"description\": \"These ecosystems are also documented at https://ossf.github.io/osv-schema/#affectedpackage-field\","
+
                                      "      \"enum\": ["
+
                                      "        \"AlmaLinux\","
+
                                      "        \"Alpine\","
+
                                      "        \"Android\","
+
                                      "        \"Bioconductor\","
+
                                      "        \"Bitnami\","
+
                                      "        \"Chainguard\","
+
                                      "        \"ConanCenter\","
+
                                      "        \"CRAN\","
+
                                      "        \"crates.io\","
+
                                      "        \"Debian\","
+
                                      "        \"FreeBSD\","
+
                                      "        \"GHC\","
+
                                      "        \"GitHub Actions\","
+
                                      "        \"Go\","
+
                                      "        \"Hackage\","
+
                                      "        \"Hex\","
+
                                      "        \"Kubernetes\","
+
                                      "        \"Linux\","
+
                                      "        \"Mageia\","
+
                                      "        \"Maven\","
+
                                      "        \"MinimOS\","
+
                                      "        \"npm\","
+
                                      "        \"NuGet\","
+
                                      "        \"openSUSE\","
+
                                      "        \"OSS-Fuzz\","
+
                                      "        \"Packagist\","
+
                                      "        \"Photon OS\","
+
                                      "        \"Pub\","
+
                                      "        \"PyPI\","
+
                                      "        \"Red Hat\","
+
                                      "        \"Rocky Linux\","
+
                                      "        \"RubyGems\","
+
                                      "        \"SUSE\","
+
                                      "        \"SwiftURL\","
+
                                      "        \"Ubuntu\","
+
                                      "        \"Wolfi\""
+
                                      "      ]"
+
                                      "    },"
+
                                      "    \"ecosystemSuffix\": {"
+
                                      "      \"type\": \"string\","
+
                                      "      \"pattern\": \":.+\""
+
                                      "    },"
+
                                      "    \"ecosystemWithSuffix\": {"
+
                                      "      \"type\": \"string\","
+
                                      "      \"title\": \"Currently supported ecosystems\","
+
                                      "      \"description\": \"These ecosystems are also documented at https://ossf.github.io/osv-schema/#affectedpackage-field\","
+
                                      "      \"pattern\": \"^(AlmaLinux|Alpine|Android|Bioconductor|Bitnami|Chainguard|ConanCenter|CRAN|crates\\.io|Debian|FreeBSD:ports|FreeBSD|GHC|GitHub Actions|Go|Hackage|Hex|Kubernetes|Linux|Mageia|Maven|MinimOS|npm|NuGet|openSUSE|OSS-Fuzz|Packagist|Photon OS|Pub|PyPI|Red Hat|Rocky Linux|RubyGems|SUSE|SwiftURL|Ubuntu|Wolfi|GIT)(:.+)?$\""
+
                                      "    },"
+
                                      "    \"prefix\": {"
+
                                      "      \"type\": \"string\","
+
                                      "      \"title\": \"Currently supported home database identifier prefixes\","
+
                                      "      \"description\": \"These home databases are also documented at https://ossf.github.io/osv-schema/#id-modified-fields\","
+
                                      "      \"pattern\": \"^(ASB-A|PUB-A|ALSA|ALBA|ALEA|BIT|CGA|CURL|CVE|DSA|DLA|ELA|FBSD|DTSA|GHSA|GO|GSD|HSEC|KUBE|LBSEC|LSN|MAL|MGASA|OSV|openSUSE-SU|PHSA|PSF|PYSEC|RHBA|RHEA|RHSA|RLSA|RXSA|RSEC|RUSTSEC|SUSE-[SRFO]U|UBUNTU|USN|V8)-\""
+
                                      "    },"
+
                                      "    \"severity\": {"
+
                                      "      \"type\": ["
+
                                      "        \"array\","
+
                                      "        \"null\""
+
                                      "      ],"
+
                                      "      \"items\": {"
+
                                      "        \"type\": \"object\","
+
                                      "        \"properties\": {"
+
                                      "          \"type\": {"
+
                                      "            \"type\": \"string\","
+
                                      "            \"enum\": ["
+
                                      "              \"CVSS_V2\","
+
                                      "              \"CVSS_V3\","
+
                                      "              \"CVSS_V4\","
+
                                      "              \"Ubuntu\""
+
                                      "            ]"
+
                                      "          },"
+
                                      "          \"score\": {"
+
                                      "            \"type\": \"string\""
+
                                      "          }"
+
                                      "        },"
+
                                      "        \"allOf\": ["
+
                                      "          {"
+
                                      "            \"if\": {"
+
                                      "              \"properties\": {"
+
                                      "                \"type\": {"
+
                                      "                  \"const\": \"CVSS_V2\""
+
                                      "                }"
+
                                      "              }"
+
                                      "            },"
+
                                      "            \"then\": {"
+
                                      "              \"properties\": {"
+
                                      "                \"score\": {"
+
                                      "                  \"pattern\": \"^((AV:[NAL]|AC:[LMH]|Au:[MSN]|[CIA]:[NPC]|E:(U|POC|F|H|ND)|RL:(OF|TF|W|U|ND)|RC:(UC|UR|C|ND)|CDP:(N|L|LM|MH|H|ND)|TD:(N|L|M|H|ND)|[CIA]R:(L|M|H|ND))/)*(AV:[NAL]|AC:[LMH]|Au:[MSN]|[CIA]:[NPC]|E:(U|POC|F|H|ND)|RL:(OF|TF|W|U|ND)|RC:(UC|UR|C|ND)|CDP:(N|L|LM|MH|H|ND)|TD:(N|L|M|H|ND)|[CIA]R:(L|M|H|ND))$\""
+
                                      "                }"
+
                                      "              }"
+
                                      "            }"
+
                                      "          },"
+
                                      "          {"
+
                                      "            \"if\": {"
+
                                      "              \"properties\": {"
+
                                      "                \"type\": {"
+
                                      "                  \"const\": \"CVSS_V3\""
+
                                      "                }"
+
                                      "              }"
+
                                      "            },"
+
                                      "            \"then\": {"
+
                                      "              \"properties\": {"
+
                                      "                \"score\": {"
+
                                      "                  \"pattern\": \"^CVSS:3[.][01]/((AV:[NALP]|AC:[LH]|PR:[NLH]|UI:[NR]|S:[UC]|[CIA]:[NLH]|E:[XUPFH]|RL:[XOTWU]|RC:[XURC]|[CIA]R:[XLMH]|MAV:[XNALP]|MAC:[XLH]|MPR:[XNLH]|MUI:[XNR]|MS:[XUC]|M[CIA]:[XNLH])/)*(AV:[NALP]|AC:[LH]|PR:[NLH]|UI:[NR]|S:[UC]|[CIA]:[NLH]|E:[XUPFH]|RL:[XOTWU]|RC:[XURC]|[CIA]R:[XLMH]|MAV:[XNALP]|MAC:[XLH]|MPR:[XNLH]|MUI:[XNR]|MS:[XUC]|M[CIA]:[XNLH])$\""
+
                                      "                }"
+
                                      "              }"
+
                                      "            }"
+
                                      "          },"
+
                                      "          {"
+
                                      "            \"if\": {"
+
                                      "              \"properties\": {"
+
                                      "                \"type\": {"
+
                                      "                  \"const\": \"CVSS_V4\""
+
                                      "                }"
+
                                      "              }"
+
                                      "            },"
+
                                      "            \"then\": {"
+
                                      "              \"properties\": {"
+
                                      "                \"score\": {"
+
                                      "                  \"pattern\": \"^CVSS:4[.]0/AV:[NALP]/AC:[LH]/AT:[NP]/PR:[NLH]/UI:[NPA]/VC:[HLN]/VI:[HLN]/VA:[HLN]/SC:[HLN]/SI:[HLN]/SA:[HLN](/E:[XAPU])?(/CR:[XHML])?(/IR:[XHML])?(/AR:[XHML])?(/MAV:[XNALP])?(/MAC:[XLH])?(/MAT:[XNP])?(/MPR:[XNLH])?(/MUI:[XNPA])?(/MVC:[XNLH])?(/MVI:[XNLH])?(/MVA:[XNLH])?(/MSC:[XNLH])?(/MSI:[XNLHS])?(/MSA:[XNLHS])?(/S:[XNP])?(/AU:[XNY])?(/R:[XAUI])?(/V:[XDC])?(/RE:[XLMH])?(/U:(X|Clear|Green|Amber|Red))?$\""
+
                                      "                }"
+
                                      "              }"
+
                                      "            }"
+
                                      "          },"
+
                                      "          {"
+
                                      "            \"if\": {"
+
                                      "              \"properties\": {"
+
                                      "                \"type\": {"
+
                                      "                  \"const\": \"Ubuntu\""
+
                                      "                }"
+
                                      "              }"
+
                                      "            },"
+
                                      "            \"then\": {"
+
                                      "              \"properties\": {"
+
                                      "                \"score\": {"
+
                                      "                  \"enum\": ["
+
                                      "                    \"negligible\","
+
                                      "                    \"low\","
+
                                      "                    \"medium\","
+
                                      "                    \"high\","
+
                                      "                    \"critical\""
+
                                      "                  ]"
+
                                      "                }"
+
                                      "              }"
+
                                      "            }"
+
                                      "          }"
+
                                      "        ],"
+
                                      "        \"required\": ["
+
                                      "          \"type\","
+
                                      "          \"score\""
+
                                      "        ]"
+
                                      "      }"
+
                                      "    },"
+
                                      "    \"timestamp\": {"
+
                                      "      \"type\": \"string\","
+
                                      "      \"format\": \"date-time\","
+
                                      "      \"pattern\": \"[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(\\.[0-9]+)?Z\""
+
                                      "    }"
+
                                      "  },"
+
                                      "  \"additionalProperties\": false"
+
                                      "}";
+

+
struct pkg_osvf_hash
+
{
+
	unsigned int value;
+
	char *name;
+
};
+

+
struct pkg_osvf_hash references_global[] =
+
{
+
	{OSVF_REFERENCE_ADVISORY, "ADVISORY"},
+
	{OSVF_REFERENCE_ARTICLE, "ARTICLE"},
+
	{OSVF_REFERENCE_DETECTION, "DETECTION"},
+
	{OSVF_REFERENCE_DISCUSSION, "DISCUSSION"},
+
	{OSVF_REFERENCE_REPORT, "REPORT"},
+
	{OSVF_REFERENCE_FIX, "FIX"},
+
	{OSVF_REFERENCE_INTRODUCED, "INTRODUCED"},
+
	{OSVF_REFERENCE_PACKAGE, "PACKAGE"},
+
	{OSVF_REFERENCE_EVIDENCE, "EVIDENCE"},
+
	{OSVF_REFERENCE_WEB, "WEB"},
+
	{OSVF_REFERENCE_UNKNOWN, NULL}
+
};
+

+
struct pkg_osvf_hash event_global[] =
+
{
+
	{OSVF_EVENT_VERSION_SEMVER, "SEMVER"},
+
	{OSVF_EVENT_VERSION_ECOSYSTEM, "ECOSYSTEM"},
+
	{OSVF_EVENT_VERSION_GIT, "GIT"},
+
	{OSVF_EVENT_VERSION_UNKNOWN, NULL}
+
};
+

+
static ucl_object_t *
+
create_schema_obj()
+
{
+
	struct ucl_parser *uclparser;
+
	ucl_object_t *obj = NULL;
+

+
	uclparser = ucl_parser_new (0);
+
	ucl_parser_add_string(uclparser, osvf_schema_str, 0);
+
	if (ucl_parser_get_error(uclparser) != NULL)
+
	{
+
		pkg_emit_error("Error occurred: %s\n", ucl_parser_get_error (uclparser));
+
		ucl_parser_free (uclparser);
+
		return (NULL);
+
	}
+

+
	obj = ucl_parser_get_object(uclparser);
+
	ucl_parser_free(uclparser);
+
	return obj;
+
}
+

+

+
ucl_object_t *
+
pkg_osvf_open(const char *location)
+
{
+
	struct ucl_parser *uclparser;
+
	ucl_object_t *obj = NULL;
+
	int fd;
+
	ucl_object_t *schema = NULL;
+
	struct ucl_schema_error err;
+

+
	fd = open(location, O_RDONLY);
+
	if (fd == -1)
+
	{
+
		pkg_emit_error("Unable to open OSVF file: %s", location);
+
		return (NULL);
+
	}
+

+
	uclparser = ucl_parser_new(0);
+
	if (!ucl_parser_add_fd(uclparser, fd))
+
	{
+
		pkg_emit_error("Error parsing UCL file '%s': %s'",
+
		               location, ucl_parser_get_error(uclparser));
+
		ucl_parser_free(uclparser);
+
		close(fd);
+
		return (NULL);
+
	}
+
	close(fd);
+

+
	obj = ucl_parser_get_object(uclparser);
+
	ucl_parser_free(uclparser);
+

+
	if (obj == NULL)
+
	{
+
		pkg_emit_error("UCL definition %s cannot be validated: %s",
+
		               location, err.msg);
+
		return (NULL);
+
	}
+

+
	schema = create_schema_obj();
+

+
	if (schema == NULL)
+
	{
+
		return (NULL);
+
	}
+

+
	if (!ucl_object_validate(schema, obj, &err))
+
	{
+
		pkg_emit_error("UCL definition %s cannot be validated: %s",
+
		               location, err.msg);
+
		ucl_object_unref(schema);
+
		ucl_object_unref(obj);
+
		return (NULL);
+
	}
+

+
	ucl_object_unref(schema);
+

+
	return (obj);
+
}
+

+
struct pkg_audit_entry *
+
pkg_osvf_new_entry()
+
{
+
	struct pkg_audit_entry *entry = xcalloc(1, sizeof(struct pkg_audit_entry));
+

+
	entry->packages = xcalloc(1, sizeof(struct pkg_audit_package));
+
	entry->names = xcalloc(1, sizeof(struct pkg_audit_pkgname));
+
	entry->versions = xcalloc(1, sizeof(struct pkg_audit_versions_range));
+
	entry->cve = xcalloc(1, sizeof(struct pkg_audit_cve));
+
	entry->references = xcalloc(1, sizeof(struct pkg_audit_reference));
+

+
	return entry;
+
}
+

+
void
+
pkg_osvf_free_pkgname(struct pkg_audit_pkgname *pkgname)
+
{
+
	if(!pkgname)
+
	{
+
		return;
+
	}
+

+
	free(pkgname->pkgname);
+
	pkgname->pkgname = NULL;
+

+
	pkg_osvf_free_pkgname(pkgname->next);
+
	pkgname->next = NULL;
+

+
	free(pkgname);
+
}
+

+
void
+
pkg_osvf_free_version(struct pkg_audit_version *ver)
+
{
+
	if(!ver)
+
	{
+
		return;
+
	}
+

+
	free(ver->version);
+
	ver->version = NULL;
+

+
	free(ver);
+
}
+

+
void
+
pkg_osvf_free_range(struct pkg_audit_versions_range *range)
+
{
+
	free(range);
+
}
+

+
void
+
pkg_osvf_free_ecosystem(struct pkg_audit_ecosystem *ecosystem)
+
{
+
	if(!ecosystem)
+
	{
+
		return;
+
	}
+

+
	free(ecosystem->original);
+
	ecosystem->original = NULL;
+

+
	free(ecosystem->name);
+
	ecosystem->name = NULL;
+

+

+
	ucl_object_unref(ecosystem->params);
+
	ecosystem->params = NULL;
+

+
	free(ecosystem);
+
}
+

+
void
+
pkg_osvf_free_package(struct pkg_audit_package *package)
+
{
+
	if(!package)
+
	{
+
		return;
+
	}
+

+
	pkg_osvf_free_pkgname(package->names);
+
	package->names = NULL;
+

+
	pkg_osvf_free_range(package->versions);
+
	package->versions = NULL;
+

+
	pkg_osvf_free_ecosystem(package->ecosystem);
+
	package->ecosystem = NULL;
+

+
	pkg_osvf_free_package(package->next);
+
	package->next = NULL;
+

+
	free(package);
+
}
+

+
void
+
pkg_osvf_free_cve(struct pkg_audit_cve *cve)
+
{
+
	if(!cve)
+
	{
+
		return;
+
	}
+

+
	free(cve->cvename);
+
	cve->cvename = NULL;
+

+
	pkg_osvf_free_cve(cve->next);
+
	cve->next = NULL;
+

+
	free(cve);
+
}
+

+
void
+
pkg_osvf_free_reference(struct pkg_audit_reference *reference)
+
{
+
	if(!reference)
+
	{
+
		return;
+
	}
+

+
	free(reference->url);
+
	reference->url = NULL;
+

+
	pkg_osvf_free_reference(reference->next);
+
	reference->next = NULL;
+

+
	free(reference);
+
}
+

+
void
+
pkg_osvf_free_entry(struct pkg_audit_entry *entry)
+
{
+
	struct pkg_audit_versions_range *versions = NULL;
+
	struct pkg_audit_versions_range *next_versions = NULL;
+

+
	struct pkg_audit_pkgname *names = NULL;
+
	struct pkg_audit_pkgname *next_names = NULL;
+

+
	if(!entry)
+
	{
+
		return;
+
	}
+

+
	versions = entry->versions;
+
	names = entry->names;
+

+
	if(entry->id)
+
	{
+
		free(entry->id);
+
		entry->id = NULL;
+
	}
+
	if(entry->desc)
+
	{
+
		free(entry->desc);
+
		entry->desc = NULL;
+
	}
+

+
	while(versions)
+
	{
+
		next_versions = versions->next;
+
		free(versions);
+
		versions = next_versions;
+
	}
+

+
	while(names)
+
	{
+
		next_names = names->next;
+
		free(names);
+
		names = next_names;
+
	}
+

+
	pkg_osvf_free_package(entry->packages);
+
	entry->packages = NULL;
+

+
	pkg_osvf_free_cve(entry->cve);
+
	entry->cve = NULL;
+

+
	pkg_osvf_free_reference(entry->references);
+
	entry->references = NULL;
+

+
	free(entry);
+
}
+

+
struct pkghash *
+
pkg_osvf_create_seek_hash(struct pkg_osvf_hash *osvf_ptr)
+
{
+
	struct pkghash *hash_table = pkghash_new();
+

+
	while(osvf_ptr->name)
+
	{
+
		pkghash_add(hash_table, osvf_ptr->name, osvf_ptr, NULL);
+
		osvf_ptr ++;
+
	}
+

+
	return hash_table;
+
}
+

+
unsigned int
+
pkg_osvf_get_hash(const char *key, struct pkg_osvf_hash *global, unsigned int unknow)
+
{
+
	struct pkghash *hash = NULL;
+
	pkghash_entry *entry = NULL;
+
	struct pkg_osvf_hash *rtn_struct = NULL;
+
	unsigned int rtn_value = unknow;
+

+
	if(!key)
+
	{
+
		return rtn_value;
+
	}
+

+
	/*
+
	 * Create seek table with struct
+
	 * Make easier to seek
+
	 */
+
	hash = pkg_osvf_create_seek_hash(global);
+

+
	entry = pkghash_get(hash, key);
+

+
	/*
+
	 * If there was key then get it and
+
	 * free hash table as we don't need it anymore
+
	 */
+
	if(entry)
+
	{
+
		rtn_struct = (struct pkg_osvf_hash *) entry->value;
+
		rtn_value = rtn_struct->value;
+
		pkghash_destroy(hash);
+
	}
+

+
	return rtn_value;
+
}
+

+

+
struct pkg_audit_ecosystem *
+
pkg_osvf_get_ecosystem(const char *ecosystem)
+
{
+
	char ecosystem_delimiter[] = ":";
+
	char *ecosystem_copy = NULL;
+
	char *ecosystem_token = NULL;
+
	struct pkg_audit_ecosystem *rtn_ecosystem = NULL;
+

+
	if(!ecosystem)
+
	{
+
		return NULL;
+
	}
+

+
	ecosystem_copy = xstrdup(ecosystem);
+
	ecosystem_token = strtok(ecosystem_copy, ecosystem_delimiter);
+

+
	if(!ecosystem_token)
+
	{
+
		free(ecosystem_copy);
+
		return NULL;
+
	}
+

+
	rtn_ecosystem = xcalloc(1, sizeof(struct pkg_audit_ecosystem));
+
	rtn_ecosystem->original = xstrdup(ecosystem);
+
	rtn_ecosystem->name = xstrdup(ecosystem_token);
+
	rtn_ecosystem->params = ucl_object_typed_new(UCL_ARRAY);
+

+
	/*
+
	 * Parse other information out of Ecosystem tags like
+
	 * Alpine:v3.16
+
	 * FreeBSD:ports
+
	 * FreeBSD:kernel:14.3
+
	 * FreeBSD:src:14.3
+
	 * Mageia:9
+
	 * Maven:https://repo1.maven.org/maven2/
+
	 * Photon OS:3.0
+
	 * Red Hat:rhel_aus:8.4::appstream
+
	 * Ubuntu:22.04:LTS
+
	 * Ubuntu:Pro:18.04:LTS
+
	 * to array for more processing further
+
	 */
+
	while(ecosystem_token)
+
	{
+
		ecosystem_token = strtok(NULL, ecosystem_delimiter);
+
		if(ecosystem_token)
+
		{
+
			ucl_array_append(rtn_ecosystem->params, ucl_object_fromstring(ecosystem_token));
+
		}
+
	}
+

+
	free(ecosystem_copy);
+
	ecosystem_copy = NULL;
+

+
	return rtn_ecosystem;
+
}
+

+
unsigned int
+
pkg_osvf_get_reference(const char *reference_type)
+
{
+
	return pkg_osvf_get_hash(reference_type, references_global, OSVF_REFERENCE_UNKNOWN);
+
}
+

+
unsigned int
+
pkg_osvf_get_event(const char *event_type)
+
{
+
	return pkg_osvf_get_hash(event_type, event_global, OSVF_EVENT_VERSION_UNKNOWN);
+
}
+

+

+
const char *
+
pkg_osvf_ucl_string(const ucl_object_t *obj, char *key)
+
{
+
	const ucl_object_t *key_obj = ucl_object_find_key(obj, key);
+

+
	if(key_obj && ucl_object_type(key_obj) == UCL_STRING)
+
	{
+
		return ucl_object_tostring(key_obj);
+
	}
+

+
	return "";
+
}
+

+
void
+
pkg_osvf_parse_package(struct pkg_audit_package *package, const ucl_object_t *package_obj)
+
{
+
	/* Parses package structure:
+
	   "package": {
+
	     "ecosystem": "FreeBSD:ports",
+
	     "name": "packagename"
+
	    },
+
	*/
+

+
	if(!package_obj || ucl_object_type(package_obj) != UCL_OBJECT)
+
	{
+
		return;
+
	}
+

+
	package->names->pkgname = xstrdup(pkg_osvf_ucl_string(package_obj, "name"));
+
	package->ecosystem = pkg_osvf_get_ecosystem(pkg_osvf_ucl_string(package_obj, "ecosystem"));
+
}
+

+
void
+
pkg_osvf_parse_events(struct pkg_audit_versions_range *range, const ucl_object_t *event_array, const char *type)
+
{
+
	ucl_object_iter_t it = NULL;
+
	const ucl_object_t *cur = NULL;
+

+
	if(!event_array || ucl_object_type(event_array) != UCL_ARRAY)
+
	{
+
		return;
+
	}
+

+
	if(!type)
+
	{
+
		return;
+
	}
+

+
	range->type = pkg_osvf_get_event(type);
+

+
	/* Parses package structure from events:
+
	   {
+
	     "fixed|introduced": "1.0.0"
+
	   }
+
	*/
+

+
	while ((cur = ucl_iterate_object(event_array, &it, true)))
+
	{
+
		if(ucl_object_find_key(cur, "fixed"))
+
		{
+
			range->v2.version = xstrdup(pkg_osvf_ucl_string(cur, "fixed"));
+
			printf("Fixed: %s\n", range->v2.version);
+
			range->v2.type = OSVF_EVENT_FIXED;
+
		}
+
		else if(ucl_object_find_key(cur, "introduced"))
+
		{
+
			range->v1.version = xstrdup(pkg_osvf_ucl_string(cur, "introduced"));
+
			printf("Intro: %s\n", range->v1.version);
+
			range->v1.type = OSVF_EVENT_INTRODUCED;
+
		}
+
	}
+
}
+

+

+
void
+
pkg_osvf_parse_ranges(struct pkg_audit_versions_range *range, const ucl_object_t *range_array)
+
{
+
	ucl_object_iter_t it = NULL;
+
	const ucl_object_t *cur = NULL;
+
	struct pkg_audit_versions_range *next_range = NULL;
+
	const ucl_object_t *sub_obj = NULL;
+
	bool is_first = true;
+

+
	if(!range_array || ucl_object_type(range_array) != UCL_ARRAY)
+
	{
+
		return;
+
	}
+

+
	/* Parses events structure
+
	   [
+
	   "type": "SEMVER",
+
	   "events": [
+
	     {
+
	       "fixed": "1.0.0"
+
	     },
+
	     {
+
	     "introduced": "0.0.1"
+
	     },
+
	   ]
+
	*/
+

+
	while ((cur = ucl_iterate_object(range_array, &it, true)))
+
	{
+
		if(is_first == false)
+
		{
+
			next_range = xcalloc(1, sizeof(struct pkg_audit_versions_range));
+
			range->next = next_range;
+
			range = next_range;
+
		}
+

+
		sub_obj = ucl_object_find_key(cur, "events");
+

+
		if(sub_obj && ucl_object_type(sub_obj) == UCL_ARRAY)
+
		{
+
			pkg_osvf_parse_events(range, ucl_object_find_key(cur, "events"), pkg_osvf_ucl_string(cur, "type"));
+
		}
+

+
		is_first = false;
+
	}
+
}
+

+
void
+
pkg_osvf_parse_reference(struct pkg_audit_reference *ref, const ucl_object_t *ref_obj)
+
{
+
	if(!ref_obj || ucl_object_type(ref_obj) != UCL_OBJECT)
+
	{
+
		return;
+
	}
+

+
	/*
+
	   Parses refrence to struct
+
	   {
+
	     "type": "ADVISORY",
+
	     "url": "https://www.freebsd.org/"
+
	   }
+
	*/
+
	ref->url = xstrdup(pkg_osvf_ucl_string(ref_obj, "url"));
+
	ref->type = pkg_osvf_get_reference(pkg_osvf_ucl_string(ref_obj, "type"));
+
}
+

+
void
+
pkg_osvf_parse_references(struct pkg_audit_entry *entry, const ucl_object_t *ref_obj)
+
{
+
	ucl_object_iter_t it = NULL;
+
	const ucl_object_t *cur = NULL;
+
	bool is_first = true;
+
	struct pkg_audit_reference *reference = entry->references;
+
	struct pkg_audit_reference *next_package = NULL;
+

+

+
	if(!ref_obj || ucl_object_type(ref_obj) != UCL_ARRAY)
+
	{
+
		return;
+
	}
+

+
	/*
+
	   Parses refereces array to linked list
+
	   "references": [
+
	     {
+
	       "type": "ADVISORY",
+
	       "url": "https://www.freebsd.org/"
+
	     }
+
	    ]
+
	*/
+

+
	while ((cur = ucl_iterate_object(ref_obj, &it, true)))
+
	{
+
		if(is_first == false)
+
		{
+
			next_package = xcalloc(1, sizeof(struct pkg_audit_reference));
+
			reference->next = next_package;
+
			reference = next_package;
+
		}
+

+
		if(ucl_object_type(cur) == UCL_OBJECT)
+
		{
+
			pkg_osvf_parse_reference(reference, cur);
+
		}
+

+
		is_first = false;
+
	}
+

+
}
+

+
void
+
pkg_osvf_parse_affected(struct pkg_audit_entry *entry, const ucl_object_t *aff_obj)
+
{
+
	ucl_object_iter_t it = NULL;
+
	const ucl_object_t *cur = NULL;
+
	const ucl_object_t *array_obj = NULL;
+
	bool is_first = true;
+
	struct pkg_audit_package *package = entry->packages;
+
	struct pkg_audit_package *next_package = NULL;
+

+
	if(!aff_obj || ucl_object_type(aff_obj) != UCL_ARRAY)
+
	{
+
		return;
+
	}
+

+
	/* Parse affected to correct structs
+
	   "affected": [
+
	     {
+
	       "package": {
+
	       "ecosystem": "FreeBSD:ports",
+
	       "name": "osvf-test-package10"
+
	     },
+
	   "ranges": [
+
	     {
+
	       "type": "SEMVER",
+
	       "events": [
+
	        {
+
	          "fixed": "1.0.0"
+
	        },
+
	        {
+
	          "introduced": "0.0.1"
+
	        },
+
	      ]
+
	     }
+
	     ]
+
	    }
+
	   ]
+
	*/
+
	while ((cur = ucl_iterate_object(aff_obj, &it, true)))
+
	{
+
		if(is_first == false)
+
		{
+
			next_package = xcalloc(1, sizeof(struct pkg_audit_package));
+
			package->next = next_package;
+
			package = next_package;
+
		}
+

+
		array_obj = ucl_object_find_key(cur, "package");
+

+
		if(array_obj && ucl_object_type(aff_obj) == UCL_ARRAY)
+
		{
+
			package->names = xcalloc(1, sizeof(struct pkg_audit_pkgname));
+
			pkg_osvf_parse_package(package, array_obj);
+
		}
+

+
		array_obj = ucl_object_find_key(cur, "ranges");
+

+
		if(array_obj && ucl_object_type(array_obj) == UCL_ARRAY)
+
		{
+
			package->versions = xcalloc(1, sizeof(struct pkg_audit_versions_range));
+
			pkg_osvf_parse_ranges(package->versions, array_obj);
+
		}
+

+
		is_first = false;
+
	}
+
}
+

+
void
+
pkg_osvf_append_version_range(struct pkg_audit_versions_range *to, struct pkg_audit_versions_range *from)
+
{
+
	struct pkg_audit_versions_range *ptr_from = from;
+
	struct pkg_audit_versions_range *ptr_to = to;
+

+
	to->v1.type = from->v1.type;
+
	to->v1.version = from->v1.version;
+
	to->v2.type = from->v2.type;
+
	to->v2.version = from->v2.version;
+
	to->type = from->type;
+

+
	while(ptr_from->next)
+
	{
+
		ptr_to->next = xcalloc(1, sizeof(struct pkg_audit_versions_range));
+

+
		ptr_to = ptr_to->next;
+
		ptr_from = ptr_from->next;
+

+
		ptr_to->v1.type = ptr_from->v1.type;
+
		ptr_to->v1.version = ptr_from->v1.version;
+
		ptr_to->v2.type = ptr_from->v2.type;
+
		ptr_to->v2.version = ptr_from->v2.version;
+
		ptr_to->type = ptr_from->type;
+
	}
+
}
+

+
void
+
pkg_osvf_print_version_type(struct pkg_audit_versions_range *versions)
+
{
+
	if(!versions)
+
	{
+
		return;
+
	}
+

+
	printf("\t\tVersion type: ");
+
	switch(versions->type)
+
	{
+
	case OSVF_EVENT_VERSION_UNKNOWN:
+
		printf("UNKNOWN\n");
+
		break;
+
	case OSVF_EVENT_VERSION_SEMVER:
+
		printf("Sematic Version 2.0\n");
+
		break;
+
	case OSVF_EVENT_VERSION_ECOSYSTEM:
+
		printf("Ecosystem\n");
+
		break;
+
	case OSVF_EVENT_VERSION_GIT:
+
		printf("Git hash\n");
+
		break;
+
	}
+
}
+

+
void
+
pkg_osvf_print_version(struct pkg_audit_version *version)
+
{
+
	if(!version)
+
	{
+
		return;
+
	}
+

+
	switch(version->type)
+
	{
+
	case OSVF_EVENT_UNKNOWN:
+
		printf("\t\tUnknown type: ");
+
		break;
+
	case OSVF_EVENT_INTRODUCED:
+
		printf("\t\tIntroduced: ");
+
		break;
+
	case OSVF_EVENT_FIXED:
+
		printf("\t\tFixed: ");
+
		break;
+
	case OSVF_EVENT_LAST_AFFECTED:
+
		printf("\t\tAffected: ");
+
		break;
+
	case OSVF_EVENT_LIMIT:
+
		printf("\t\tLimit: ");
+
		break;
+
	}
+

+
	printf("%s\n", version->version);
+
}
+

+
void
+
pkg_osvf_print_ecosystem(struct pkg_audit_ecosystem *ecosystem)
+
{
+
	ucl_object_iter_t it = NULL;
+
	const ucl_object_t *cur = NULL;
+
	int loc = 0;
+

+
	if(!ecosystem)
+
	{
+
		return;
+
	}
+

+
	printf("\t\tEcosystem: ");
+

+
	if(ecosystem->name)
+
	{
+
		printf("%s (", ecosystem->name);
+
	}
+

+
	while ((cur = ucl_iterate_object(ecosystem->params, &it, true)))
+
	{
+
		if(loc)
+
		{
+
			printf(",");
+
		}
+

+
		if(ucl_object_type(cur) == UCL_STRING)
+
		{
+
			printf("%s", ucl_object_tostring(cur));
+
		}
+

+
		loc ++;
+
	}
+

+
	printf(")\n");
+
}
+

+
void
+
pkg_osvf_print_entry(struct pkg_audit_entry *entry)
+
{
+
	char buf[255];
+
	struct pkg_audit_package *packages = NULL;
+
	struct pkg_audit_versions_range *versions = NULL;
+
	struct pkg_audit_reference *references;
+
	struct pkg_audit_pkgname *names = NULL;
+
	bool is_first = true;
+

+
	if(!entry)
+
	{
+
		return;
+
	}
+

+
	names = entry->names;
+

+
	printf("OSVF Vulnerability information:\n");
+
	printf("\tPackage name: %s\n", entry->pkgname);
+
	printf("\tPackage names: ");
+
	while(names)
+
	{
+
		if(is_first == false)
+
		{
+
			printf(", ");
+
		}
+
		printf("%s", names->pkgname);
+
		names = names->next;
+
		is_first = false;
+
	}
+

+
	printf("\n");
+

+
	printf("\tPackage id: %s\n", entry->id);
+
	printf("\tPackage description: %s\n", entry->desc);
+
	printf("\tPackage url: %s\n", entry->url);
+

+
	strftime(buf, sizeof(buf), "%d %b %Y %H:%M", &entry->discovery);
+
	printf("\tEntry discovered: %s\n", buf);
+

+
	strftime(buf, sizeof(buf), "%d %b %Y %H:%M", &entry->published);
+
	printf("\tEntry published: %s\n", buf);
+

+
	strftime(buf, sizeof(buf), "%d %b %Y %H:%M", &entry->modified);
+
	printf("\tEntry modified: %s\n", buf);
+

+
	printf("Vulnerable versions:\n");
+

+
	versions = entry->versions;
+

+
	while(versions)
+
	{
+
		pkg_osvf_print_version_type(versions);
+
		pkg_osvf_print_version(&versions->v1);
+
		pkg_osvf_print_version(&versions->v2);
+
		versions = versions->next;
+
	}
+

+
	printf("Vulnerable packages:\n");
+

+
	packages = entry->packages;
+

+
	while(packages)
+
	{
+
		printf("\tPackage name: %s\n", packages->names->pkgname);
+
		pkg_osvf_print_ecosystem(packages->ecosystem);
+
		versions = packages->versions;
+

+
		while(versions)
+
		{
+
			pkg_osvf_print_version_type(versions);
+
			pkg_osvf_print_version(&versions->v1);
+
			pkg_osvf_print_version(&versions->v2);
+
			versions = versions->next;
+
		}
+

+
		packages = packages->next;
+
	}
+

+
	printf("Vulnerability references:\n");
+

+
	references = entry->references;
+

+
	while(references)
+
	{
+
		switch(references->type)
+
		{
+
		case OSVF_REFERENCE_UNKNOWN:
+
			printf("\tUNKNOWN: %s\n", references->url);
+
			break;
+
		case OSVF_REFERENCE_ADVISORY:
+
			printf("\tADVISORY: %s\n", references->url);
+
			break;
+
		case OSVF_REFERENCE_ARTICLE:
+
			printf("\tARTICLE: %s\n", references->url);
+
			break;
+
		case OSVF_REFERENCE_DETECTION:
+
			printf("\tDETECTION: %s\n", references->url);
+
			break;
+
		case OSVF_REFERENCE_DISCUSSION:
+
			printf("\tDISCUSSIONn: %s\n", references->url);
+
			break;
+
		case OSVF_REFERENCE_REPORT:
+
			printf("\tREPORT: %s\n", references->url);
+
			break;
+
		case OSVF_REFERENCE_FIX:
+
			printf("\tFIX: %s\n", references->url);
+
			break;
+
		case OSVF_REFERENCE_INTRODUCED:
+
			printf("\tINTRODUCED: %s\n", references->url);
+
			break;
+
		case OSVF_REFERENCE_PACKAGE:
+
			printf("\tPACKAGE: %s\n", references->url);
+
			break;
+
		case OSVF_REFERENCE_EVIDENCE:
+
			printf("\tEVIDENCE: %s\n", references->url);
+
			break;
+
		case OSVF_REFERENCE_WEB:
+
			printf("\tWEB: %s\n", references->url);
+
			break;
+
		}
+
		references = references->next;
+
	}
+
}
+

+
struct pkg_audit_entry *
+
pkg_osvf_create_entry(ucl_object_t *osvf_obj)
+
{
+
	struct pkg_audit_entry *entry = NULL;
+
	struct pkg_audit_package *packages = NULL;
+
	struct pkg_audit_pkgname *names = NULL;
+
	struct pkg_audit_versions_range *versions = NULL;
+
	const ucl_object_t *sub_obj = NULL;
+
	/* Date format is in RFC3339 */
+
	const char *date_time_str = "%Y-%m-%dT%H:%M:%SZ";
+

+
	entry = pkg_osvf_new_entry();
+

+
	/* We are probably out of memory or JSON does not exist */
+
	if(osvf_obj == NULL || entry == NULL)
+
	{
+
		return NULL;
+
	}
+

+
	memset(&entry->modified, 0, sizeof(struct tm));
+
	memset(&entry->published, 0, sizeof(struct tm));
+
	memset(&entry->discovery, 0, sizeof(struct tm));
+

+
	/* Data has been validated on load so we can assume
+
	   there is all needed information */
+

+
	entry->id = xstrdup(pkg_osvf_ucl_string(osvf_obj, "id"));
+
	entry->desc = xstrdup(pkg_osvf_ucl_string(osvf_obj, "summary"));
+

+
	sub_obj = ucl_object_find_key(osvf_obj, "affected");
+

+
	if(sub_obj && ucl_object_type(sub_obj) == UCL_ARRAY)
+
	{
+
		pkg_osvf_parse_affected(entry, ucl_object_find_key(osvf_obj, "affected"));
+
	}
+
	else
+
	{
+
		return NULL;
+
	}
+

+
	sub_obj = ucl_object_find_key(osvf_obj, "references");
+

+
	if(sub_obj && ucl_object_type(sub_obj) == UCL_ARRAY)
+
	{
+
		pkg_osvf_parse_references(entry, ucl_object_find_key(osvf_obj, "references"));
+
	}
+
	else
+
	{
+
		return NULL;
+
	}
+

+
	entry->url = entry->references->url;
+

+
	packages = entry->packages;
+
	names = entry->names;
+
	versions = entry->versions;
+

+
	names->pkgname = packages->names->pkgname;
+
	pkg_osvf_append_version_range(versions, packages->versions);
+

+
	while(packages->next)
+
	{
+
		packages = packages->next;
+
		names->next = xcalloc(1, sizeof(struct pkg_audit_pkgname));
+
		names = names->next;
+
		names->pkgname = packages->names->pkgname;
+
		versions->next = xcalloc(1, sizeof(struct pkg_audit_versions_range));
+
		versions = versions->next;
+
		pkg_osvf_append_version_range(versions, packages->versions);
+
	}
+

+
	entry->pkgname = entry->names->pkgname;
+

+
	if(ucl_object_find_key(osvf_obj, "modified"))
+
	{
+
		strptime(ucl_object_tostring(ucl_object_find_key(osvf_obj, "modified")), date_time_str, &entry->modified);
+
	}
+

+
	if(ucl_object_find_key(osvf_obj, "published"))
+
	{
+
		strptime(ucl_object_tostring(ucl_object_find_key(osvf_obj, "published")), date_time_str, &entry->published);
+
	}
+

+
	if(ucl_object_find_key(osvf_obj, "database_specific"))
+
	{
+
		sub_obj = ucl_object_find_key(osvf_obj, "database_specific");
+
		if(ucl_object_find_key(sub_obj, "discovery"))
+
		{
+
			strptime(ucl_object_tostring(ucl_object_find_key(sub_obj, "discovery")), date_time_str, &entry->discovery);
+
		}
+
	}
+

+
	return entry;
+
}
added libpkg/private/pkg_osvf.h
@@ -0,0 +1,109 @@
+
/*-
+
 * SPDX-License-Identifier: BSD-2-Clause
+
 *
+
 *​ Copyright (c) 2025 The FreeBSD Foundation
+
 *​
+
 *​ Portions of this software were developed by
+
 * Tuukka Pasanen <tuukka.pasanen@ilmi.fi> under sponsorship from
+
 * the FreeBSD Foundation
+
 */
+

+
/* Private header file for internal and testing use only */
+
#ifndef _PKG_OSVF_H
+
#define _PKG_OSVF_H
+

+
#include <ucl.h>
+

+
#include "pkg.h"
+
#include "pkg/audit.h"
+

+
/*
+
 * Introduces a vulnerability: {"introduced": string}
+
 * Fixes a vulnerability: {"fixed": string}
+
 * Describes the last known affected version: {"last_affected": string}
+
 * Sets an upper limit on the range being described: {"limit": string}
+
 */
+

+
enum event_types
+
{
+
	OSVF_EVENT_UNKNOWN,
+
	OSVF_EVENT_INTRODUCED,
+
	OSVF_EVENT_FIXED,
+
	OSVF_EVENT_LAST_AFFECTED,
+
	OSVF_EVENT_LIMIT
+
};
+

+
/*
+
 * SEMVER:    The versions introduced and fixed are semantic versions as
+
 *            defined by SemVer 2.0.0,
+
 * ECOSYSTEM: The versions introduced and fixed are arbitrary,
+
 *            uninterpreted stringsspecific to the package ecosystem,
+
 *            which does not conform to SemVer 2.0’s version ordering
+
 * GIT:       The versions introduced and fixed are full-length Git
+
 *            commit hashes
+
 */
+
enum event_version_types
+
{
+
	OSVF_EVENT_VERSION_UNKNOWN,
+
	OSVF_EVENT_VERSION_SEMVER,
+
	OSVF_EVENT_VERSION_ECOSYSTEM,
+
	OSVF_EVENT_VERSION_GIT
+
};
+

+
/*
+
 * ADVISORY:   A published security advisory for the vulnerability.
+
 * ARTICLE:    An article or blog post describing the vulnerability.
+
 * DETECTION:  A tool, script, scanner, or other mechanism that allows
+
 *             for detection of the vulnerability in production
+
 *             environments.
+
 * DISCUSSION: A social media discussion regarding the vulnerability
+
 * REPORT:     A report, typically on a bug or issue tracker, of the
+
 *             vulnerability.
+
 * FIX:        A source code browser link to the fix Note that the fix
+
 *             type is meant for viewing by people using web browsers.
+
 * INTRODUCED: A source code browser link to the introduction of the
+
 *             vulnerability
+
 * PACKAGE:    A home web page for the package.
+
 * EVIDENCE:   A demonstration of the validity of a vulnerability claim.
+
 * WEB:        A web page of some unspecified kind.
+
 */
+
enum references_types
+
{
+
	OSVF_REFERENCE_UNKNOWN,
+
	OSVF_REFERENCE_ADVISORY,
+
	OSVF_REFERENCE_ARTICLE,
+
	OSVF_REFERENCE_DETECTION,
+
	OSVF_REFERENCE_DISCUSSION,
+
	OSVF_REFERENCE_REPORT,
+
	OSVF_REFERENCE_FIX,
+
	OSVF_REFERENCE_INTRODUCED,
+
	OSVF_REFERENCE_PACKAGE,
+
	OSVF_REFERENCE_EVIDENCE,
+
	OSVF_REFERENCE_WEB
+
};
+

+
ucl_object_t *
+
pkg_osvf_open(const char *location);
+

+
struct pkg_audit_entry *
+
pkg_osvf_create_entry(ucl_object_t *osvf_obj);
+

+
void
+
pkg_osvf_free_entry(struct pkg_audit_entry *entry);
+

+
void
+
pkg_osvf_free_ecosystem(struct pkg_audit_ecosystem *ecosystem);
+

+
struct pkg_audit_ecosystem *
+
pkg_osvf_get_ecosystem(const char *ecosystem);
+

+
unsigned int
+
pkg_osvf_get_reference(const char *reference_type);
+

+
unsigned int
+
pkg_osvf_get_event(const char *reference_type);
+

+
void
+
pkg_osvf_print_entry(struct pkg_audit_entry *entry);
+

+
#endif
modified tests/Makefile.autosetup
@@ -8,6 +8,7 @@ TESTS= \
	pkg_add_dir_to_del \
	pkg_printf \
	pkg_validation \
+
	pkg_osvf \
	pkg \
	plist \
	lua \
@@ -120,6 +121,7 @@ hash_OBJS= lib/hash.o
shlibs_OBJS=	lib/shlibs.o
kv_OBJS=	lib/kv.o
pkg_OBJS=	lib/pkg.o
+
pkg_osvf_OBJS=	lib/pkg_osvf.o

SRCS=	\
	$(packing_OBJS:.o=.c) \
@@ -131,6 +133,7 @@ SRCS= \
	$(deps_formula_OBJS:.o=.c) \
	$(pkg_add_dir_to_del_OBJS:.o=.c) \
	$(pkg_validation_OBJS:.o=.c) \
+
	$(pkg_osvf_OBJS:.o=.c) \
	$(ssh_OBJS:.o=.c) \
	$(utils_OBJS:.o=.c) \
	$(metalog_OBJS:.o=.c) \
@@ -155,7 +158,8 @@ LOCAL_CFLAGS= -I$(top_srcdir)/compat \
		-I$(top_builddir)/ \
		-I$(top_builddir)/libpkg \
		@PKG_ATF_C_CFLAGS@ \
-
		-DTESTING
+
		-DTESTING \
+
		-DTESTING_TOP_DIR="\"@abs_top_srcdir@/tests\""

LIBS=	\
	-L$(top_builddir)/libpkg -lpkg_flat \
added tests/lib/FBSD-2025-05-28.json
@@ -0,0 +1,130 @@
+
{
+
  "schema_version": "1.7.0",
+
  "id": "FBSD-2025-05-28",
+
  "modified": "2025-05-26T12:30:00Z",
+
  "published": "2025-09-28T16:00:00Z",
+
  "summary": "OSVF test",
+
  "details": "This is test of OSVF format parsing",
+
  "references": [
+
    {
+
      "type": "ADVISORY",
+
      "url": "https://www.freebsd.org/"
+
    },
+
    {
+
      "type": "ARTICLE",
+
      "url": "https://www.freebsd.org/"
+
    },
+
    {
+
      "type": "DETECTION",
+
      "url": "https://www.freebsd.org/"
+
    },
+
    {
+
      "type": "DISCUSSION",
+
      "url": "https://www.freebsd.org/"
+
    },
+
    {
+
      "type": "REPORT",
+
      "url": "https://www.freebsd.org/"
+
    },
+
    {
+
      "type": "FIX",
+
      "url": "https://www.freebsd.org/"
+
    },
+
    {
+
      "type": "INTRODUCED",
+
      "url": "https://www.freebsd.org/"
+
    },
+
    {
+
      "type": "PACKAGE",
+
      "url": "https://www.freebsd.org/"
+
    },
+
    {
+
      "type": "EVIDENCE",
+
      "url": "https://www.freebsd.org/"
+
    },
+
    {
+
      "type": "WEB",
+
      "url": "https://www.freebsd.org/"
+
    }
+
  ],
+
  "affected": [
+
    {
+
      "package": {
+
        "ecosystem": "FreeBSD:ports",
+
        "name": "osvf-test-package10"
+
      },
+
      "ranges": [
+
        {
+
          "type": "SEMVER",
+
          "events": [
+
            {
+
              "fixed": "1.0.0"
+
            },
+
            {
+
              "introduced": "0.0.1"
+
            },
+
            {
+
              "last_affected": "0.9.0"
+
            },
+
            {
+
              "limit": "0.9.1"
+
            }
+
          ]
+
        }
+
      ]
+
    },
+
    {
+
      "package": {
+
        "ecosystem": "FreeBSD:ports",
+
        "name": "osvf-test-package11"
+
      },
+
      "ranges": [
+
        {
+
          "type": "ECOSYSTEM",
+
          "events": [
+
            {
+
              "fixed": "1.1.0_1"
+
            },
+
            {
+
              "introduced": "1.0.9_1"
+
            },
+
            {
+
              "last_affected": "1.0.9_2"
+
            },
+
            {
+
              "limit": "1.1.0_1"
+
            }
+
          ]
+
        }
+
      ]
+
    },
+
    {
+
      "package": {
+
        "ecosystem": "FreeBSD:ports",
+
        "name": "osvf-test-package12"
+
      },
+
      "ranges": [
+
        {
+
          "type": "GIT",
+
          "events": [
+
            {
+
              "fixed": "c14e07db4"
+
            },
+
            {
+
              "introduced": "ae637a3ad"
+
            },
+
            {
+
              "last_affected": "0a5b32490"
+
            },
+
            {
+
              "limit": "c802d96a5"
+
            }
+
          ]
+
        }
+
      ]
+
    }
+
  ],
+
  "database_specific": {
+
    "discovery": "2025-05-20T09:10:00Z"
+
  }
+
}
added tests/lib/pkg_osvf.c
@@ -0,0 +1,296 @@
+
/*-
+
 * SPDX-License-Identifier: BSD-2-Clause
+
 *
+
 *​ Copyright (c) 2025 The FreeBSD Foundation
+
 *​
+
 *​ Portions of this software were developed by
+
 * Tuukka Pasanen <tuukka.pasanen@ilmi.fi> under sponsorship from
+
 * the FreeBSD Foundation
+
 */
+

+
#include <atf-c.h>
+
#include <private/pkg_osvf.h>
+
#include <stdlib.h>
+

+

+
char *osvf_json_path = TESTING_TOP_DIR "/lib/FBSD-2025-05-28.json";
+

+
ATF_TC_WITHOUT_HEAD(osvfdetect);
+
ATF_TC_WITHOUT_HEAD(osvfopen);
+
ATF_TC_WITHOUT_HEAD(osvfparse);
+

+
ATF_TC_BODY(osvfdetect, tc)
+
{
+
	struct pkg_audit_ecosystem test_rtn_ecosystem_struct[] =
+
	{
+
		{"AlmaLinux", "AlmaLinux", NULL},
+
		{"AlmaLinux:8", "AlmaLinux", NULL},
+
		{"Alpine", "Alpine", NULL},
+
		{"Alpine:v3.16", "Alpine", NULL},
+
		{"Android", "Android", NULL},
+
		{"Bioconductor", "Bioconductor", NULL},
+
		{"Bitnami", "Bitnami", NULL},
+
		{"Chainguard", "Chainguard", NULL},
+
		{"ConanCenter", "ConanCenter", NULL},
+
		{"CRAN", "CRAN", NULL},
+
		{"crates.io", "crates.io", NULL},
+
		{"Debian", "Debian", NULL},
+
		{"Debian:13", "Debian", NULL},
+
		{"FreeBSD", "FreeBSD", NULL},
+
		{"FreeBSD:ports", "FreeBSD", NULL},
+
		{"FreeBSD:src:14.3", "FreeBSD", NULL},
+
		{"FreeBSD:kernel:14.3", "FreeBSD", NULL},
+
		{"GHC", "GHC", NULL},
+
		{"GitHub Actions", "GitHub Actions", NULL},
+
		{"Go", "Go", NULL},
+
		{"Hackage", "Hackage", NULL},
+
		{"Hex", "Hex", NULL},
+
		{"Kubernetes", "Kubernetes", NULL},
+
		{"Linux", "Linux", NULL},
+
		{"Mageia", "Mageia", NULL},
+
		{"Mageia:9", "Mageia", NULL},
+
		{"Maven", "Maven", NULL},
+
		{"Maven:https://repo1.maven.org/maven2/", "Maven", NULL},
+
		{"MinimOS", "MinimOS", NULL},
+
		{"npm", "npm", NULL},
+
		{"NuGet", "NuGet", NULL},
+
		{"openSUSE", "openSUSE", NULL},
+
		{"OSS-Fuzz", "OSS-Fuzz", NULL},
+
		{"Packagist", "Packagist", NULL},
+
		{"Photon OS", "Photon OS", NULL},
+
		{"Photon OS:3.0", "Photon OS", NULL},
+
		{"Pub", "Pub", NULL},
+
		{"PyPI", "PyPI", NULL},
+
		{"Red Hat", "Red Hat", NULL},
+
		{"Red Hat:rhel_aus:8.4::appstream", "Red Hat", NULL},
+
		{"Rocky Linux", "Rocky Linux", NULL},
+
		{"RubyGems", "RubyGems", NULL},
+
		{"SUSE", "SUSE", NULL},
+
		{"SwiftURL", "SwiftURL", NULL},
+
		{"Ubuntu", "Ubuntu", NULL},
+
		{"Ubuntu:22.04:LTS", "Ubuntu", NULL},
+
		{"Ubuntu:Pro:18.04:LTS", "Ubuntu", NULL},
+
		{"Wolfi", "Wolfi", NULL},
+
	};
+

+
	unsigned int test_rtn_reference[] =
+
	{
+
		OSVF_REFERENCE_UNKNOWN,
+
		OSVF_REFERENCE_ADVISORY,
+
		OSVF_REFERENCE_ARTICLE,
+
		OSVF_REFERENCE_DETECTION,
+
		OSVF_REFERENCE_DISCUSSION,
+
		OSVF_REFERENCE_REPORT,
+
		OSVF_REFERENCE_FIX,
+
		OSVF_REFERENCE_INTRODUCED,
+
		OSVF_REFERENCE_PACKAGE,
+
		OSVF_REFERENCE_EVIDENCE,
+
		OSVF_REFERENCE_WEB
+
	};
+

+
	char *test_input_reference[] =
+
	{
+
		"NOTAVAIL",
+
		"ADVISORY",
+
		"ARTICLE",
+
		"DETECTION",
+
		"DISCUSSION",
+
		"REPORT",
+
		"FIX",
+
		"INTRODUCED",
+
		"PACKAGE",
+
		"EVIDENCE",
+
		"WEB"
+
	};
+

+
	unsigned int test_rtn_event[] =
+
	{
+
		OSVF_EVENT_VERSION_UNKNOWN,
+
		OSVF_EVENT_VERSION_SEMVER,
+
		OSVF_EVENT_VERSION_ECOSYSTEM,
+
		OSVF_EVENT_VERSION_GIT
+
	};
+

+
	char *test_input_event[] =
+
	{
+
		"SOMETHING",
+
		"SEMVER",
+
		"ECOSYSTEM",
+
		"GIT"
+
	};
+

+
	int i = 0;
+

+

+
	for(i = 0; i < 47; i ++)
+
	{
+
		struct pkg_audit_ecosystem *ecosystem = pkg_osvf_get_ecosystem(test_rtn_ecosystem_struct[i].original);
+

+
		ATF_REQUIRE_STREQ(ecosystem->name, test_rtn_ecosystem_struct[i].name);
+
		ATF_REQUIRE_STREQ(ecosystem->original, test_rtn_ecosystem_struct[i].original);
+

+
		pkg_osvf_free_ecosystem(ecosystem);
+
	}
+

+
	for(i = 0; i < 10; i ++)
+
	{
+
		ATF_REQUIRE(pkg_osvf_get_reference(test_input_reference[i]) == test_rtn_reference[i]);
+
	}
+

+
	for(i = 0; i < 4; i ++)
+
	{
+
		ATF_REQUIRE(pkg_osvf_get_event(test_input_event[i]) == test_rtn_event[i]);
+
	}
+

+
}
+

+
ATF_TC_BODY(osvfopen, tc)
+
{
+
	static ucl_object_t *obj = NULL;
+

+
	obj = pkg_osvf_open(osvf_json_path);
+

+
	ATF_REQUIRE(obj != NULL);
+
	ucl_object_unref(obj);
+

+
	ATF_REQUIRE(pkg_osvf_create_entry(NULL) == NULL);
+
}
+

+

+
ATF_TC_BODY(osvfparse, tc)
+
{
+
	static ucl_object_t *obj = NULL;
+
	char buf[1024];
+
	char *version_strs[] =
+
	{
+
		"1.0.0",
+
		"0.0.1",
+
		"1.1.0_1",
+
		"1.0.9_1",
+
		"c14e07db4",
+
		"ae637a3ad"
+
	};
+
	unsigned int version_types[] =
+
	{
+
		OSVF_EVENT_VERSION_SEMVER,
+
		OSVF_EVENT_VERSION_ECOSYSTEM,
+
		OSVF_EVENT_VERSION_GIT,
+
	};
+
	char *name_strs[] =
+
	{
+
		"osvf-test-package10",
+
		"osvf-test-package11",
+
		"osvf-test-package12"
+
	};
+
	int reference_types[] =
+
	{
+
		OSVF_REFERENCE_ADVISORY,
+
		OSVF_REFERENCE_ARTICLE,
+
		OSVF_REFERENCE_DETECTION,
+
		OSVF_REFERENCE_DISCUSSION,
+
		OSVF_REFERENCE_REPORT,
+
		OSVF_REFERENCE_FIX,
+
		OSVF_REFERENCE_INTRODUCED,
+
		OSVF_REFERENCE_PACKAGE,
+
		OSVF_REFERENCE_EVIDENCE,
+
		OSVF_REFERENCE_WEB
+
	};
+
	struct pkg_audit_versions_range *versions = NULL;
+
	struct pkg_audit_pkgname *names = NULL;
+
	struct pkg_audit_reference *references = NULL;
+
	struct pkg_audit_package *packages = NULL;
+
	int pos = 0;
+
	int subpos = 0;
+
	int otherpos = 0;
+

+
	obj = pkg_osvf_open(osvf_json_path);
+
	ATF_REQUIRE(obj != NULL);
+

+
	struct pkg_audit_entry *entry = pkg_osvf_create_entry(obj);
+
	ucl_object_unref(obj);
+

+
	ATF_REQUIRE(entry != NULL);
+

+
	ATF_CHECK_STREQ(entry->pkgname, "osvf-test-package10");
+
	ATF_CHECK_STREQ(entry->desc, "OSVF test");
+
	ATF_CHECK_STREQ(entry->url, "https://www.freebsd.org/");
+
	ATF_CHECK_STREQ(entry->id, "FBSD-2025-05-28");
+

+
	versions = entry->versions;
+
	names = entry->names;
+
	references = entry->references;
+
	packages = entry->packages;
+

+
	while(references)
+
	{
+
		ATF_CHECK_STREQ(references->url, "https://www.freebsd.org/");
+
		ATF_CHECK_INTEQ(references->type, reference_types[pos++]);
+
		references = references->next;
+
	}
+

+
	pos = 0;
+
	otherpos = 0;
+

+
	while(versions)
+
	{
+
		ATF_CHECK_INTEQ(versions->type, version_types[otherpos++]);
+
		ATF_CHECK_STREQ(versions->v2.version, version_strs[pos++]);
+
		ATF_CHECK_INTEQ(versions->v2.type, OSVF_EVENT_FIXED);
+
		ATF_CHECK_STREQ(versions->v1.version, version_strs[pos++]);
+
		ATF_CHECK_INTEQ(versions->v1.type, OSVF_EVENT_INTRODUCED);
+
		versions = versions->next;
+
	}
+

+
	pos = 0;
+

+
	while(names)
+
	{
+
		ATF_CHECK_STREQ(names->pkgname, name_strs[pos++]);
+
		names = names->next;
+
	}
+

+
	pos = 0;
+
	subpos = 0;
+
	otherpos = 0;
+

+
	while(packages)
+
	{
+
		ATF_CHECK_STREQ(packages->ecosystem->name, "FreeBSD");
+
		ATF_CHECK_STREQ(packages->names->pkgname, name_strs[pos++]);
+

+
		versions = packages->versions;
+

+
		while(versions)
+
		{
+
			ATF_CHECK_INTEQ(versions->type, version_types[otherpos++]);
+
			ATF_CHECK_STREQ(versions->v2.version, version_strs[subpos++]);
+
			ATF_CHECK_INTEQ(versions->v2.type, OSVF_EVENT_FIXED);
+
			ATF_CHECK_STREQ(versions->v1.version, version_strs[subpos++]);
+
			ATF_CHECK_INTEQ(versions->v1.type, OSVF_EVENT_INTRODUCED);
+
			versions = versions->next;
+
		}
+

+
		packages = packages->next;
+
	}
+

+
	strftime(buf, 1024, "%Y-%m-%dT%H:%M:%SZ", &entry->modified);
+
	ATF_CHECK_STREQ(buf, "2025-05-26T12:30:00Z");
+

+
	strftime(buf, 1024, "%Y-%m-%dT%H:%M:%SZ", &entry->published);
+
	ATF_CHECK_STREQ(buf, "2025-09-28T16:00:00Z");
+

+
	strftime(buf, 1024, "%Y-%m-%dT%H:%M:%SZ", &entry->discovery);
+
	ATF_CHECK_STREQ(buf, "2025-05-20T09:10:00Z");
+

+
	pkg_osvf_free_entry(entry);
+

+
}
+

+
ATF_TP_ADD_TCS(tp)
+
{
+
	ATF_TP_ADD_TC(tp, osvfdetect);
+
	ATF_TP_ADD_TC(tp, osvfopen);
+
	ATF_TP_ADD_TC(tp, osvfparse);
+

+
	return (atf_no_error());
+
}