default/cvss4/static/cvss40.js (718 lines of code) (raw):

// Comes from https://www.first.org/cvss/cvss-v4.0.json, slightly modified // to match the metric groups without false-positives and to avoid double-capture // of Provider Urgency (U) due to multi-char values. const errors = { InvalidMetric: class extends Error { constructor(version, metric) { super('invalid CVSS v' + version + ' metric ' + metric); this.version = version; this.metric = metric; } }, InvalidMetricValue: class extends Error { constructor(version, metric, value) { super('invalid CVSS v' + version + ' value ' + value + ' for metric ' + metric); this.version = version; this.metric = metric; this.value = value; } } } const re = /^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))?$/g; /** * Implementation of the CVSS v4.0 specification (https://www.first.org/cvss/v4.0/specification-document). */ export class CVSS40 { /** * Construct a CVSS v4.0 object, and parse the vector if provided. * If not, the Base metrics is set to the default values (score = 0). * * @param vector The vector to parse. * @throws When the vector is invalid. */ constructor(vector = 'CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:N/SC:N/SI:N/SA:N') { this._metrics = { // Set default values of non-mandatory metrics : Not Defined (X) // => Threat 'E': 'X', // => Environmental 'CR': 'X', 'IR': 'X', 'AR': 'X', 'MAV': 'X', 'MAC': 'X', 'MAT': 'X', 'MPR': 'X', 'MUI': 'X', 'MVC': 'X', 'MVI': 'X', 'MVA': 'X', 'MSC': 'X', 'MSI': 'X', 'MSA': 'X', // => Supplemental 'S': 'X', 'AU': 'X', 'R': 'X', 'V': 'X', 'RE': 'X', 'U': 'X', }; this.parse(vector); } /** * Parse the provided vector. * Makes use of the regex for code simplicity, but we could use the * `metrics` constant to provide better accurate error messages. * * @param vector The vector to parse. * @throws When the vector is invalid. */ parse(vector) { // Ensure input is valid according to the regular expression let matches = vector.matchAll(re).next().value; if (matches == undefined) { throw new Error('invalid CVSS v4.0 vector'); } // Skip prefix matches.shift(); // Parse each metric group for (let match of matches) { if (match == undefined) { continue; } match = match.slice(1); const pts = match.split(':'); this._metrics[pts[0]] = pts[1]; } } /** * Return the vector string representation of the CVSS v4.0 object. * * @return The vector string representation. */ Vector() { let vector = 'CVSS:4.0'; for (const [om] of Object.entries(table23)) { const metric = this.Get(om); // Add the value iif was set and is not 'X' (Not Defined) if (metric == undefined || metric == 'X') { continue; } vector = vector.concat('/', om, ':', metric); } return vector; } /** * Get the metric value given its value (e.g. 'AV'). * * @param metric The metric to get the value of. * @return The corresponding metric value. * @throws Metric does not exist. */ Get(metric) { const v = this._metrics[metric]; if (v == undefined) { throw new errors.InvalidMetric('4.0', metric); } return v; } /** * Set the metric value given its key and value (e.g. 'AV' and 'L'). * * @param metric The metric to set the value of. * @param value The corresponding metric value. * @throws Metric does not exist or has an invalid value. */ Set(metric, value) { const values = table23[metric]; if (values == undefined) { throw new errors.InvalidMetric('4.0', metric); } if (!values.includes(value)) { throw new errors.InvalidMetricValue('4.0', metric, value); } this._metrics[metric] = value; } /** * Compute the CVSS v4.0 Score of the current object, given its metrics and their * corresponding values. * * The implementation internals are largely based upon https://github.com/pandatix/go-cvss * submodule 40. * * @return The score (between 0.0 and 10.0 both included). */ Score() { // If the vulnerability does not affect the system AND the subsequent // system, there is no reason to try scoring what has no risk and impact. if (['VC', 'VI', 'VA', 'SC', 'SI', 'SA'].every((met) => this.getReal(met) == "N")) { return 0.0; } const mv = this.macrovector(); const eq1 = Number(mv[0]); const eq2 = Number(mv[1]); const eq3 = Number(mv[2]); const eq4 = Number(mv[3]); const eq5 = Number(mv[4]); const eq6 = Number(mv[5]); const eqsv = mvs[mv]; // Compute EQs next lower MacroVector // -> As the lower the EQ value is the bigger, the next lower MacroVector // would be +1 to this one // -> If not possible (level+1 > level), it is set to NaN let lower = 0; let eq1nlm = NaN; if (eq1 < 2) { // 2 = maximum level for EQ1 eq1nlm = mvs[String(eq1 + 1) + String(eq2) + String(eq3) + String(eq4) + String(eq5) + String(eq6)]; lower++; } let eq2nlm = NaN; if (eq2 < 1) { // 1 = maximum level for EQ2 eq2nlm = mvs[String(eq1) + String(eq2 + 1) + String(eq3) + String(eq4) + String(eq5) + String(eq6)]; lower++; } let eq4nlm = NaN; if (eq4 < 2) { // 2 = maximum level for EQ4 eq4nlm = mvs[String(eq1) + String(eq2) + String(eq3) + String(eq4 + 1) + String(eq5) + String(eq6)]; lower++; } let eq5nlm = NaN; if (eq5 < 2) { // 2 = maximum level for EQ5 eq5nlm = mvs[String(eq1) + String(eq2) + String(eq3) + String(eq4) + String(eq5 + 1) + String(eq6)]; lower++; } // /!\ As EQ3 and EQ6 are related, we can't do the same as it could produce // eq3=2 and eq6=0 which is impossible thus will have a lookup (for EQ3) of 0. // This would fail the further computations. let eq3eq6nlm = NaN; if (eq3 == 1 && eq6 == 1) { // 11 -> 21 eq3eq6nlm = mvs[String(eq1) + String(eq2) + String(eq3 + 1) + String(eq4) + String(eq5) + String(eq6)]; lower++; } else if (eq3 == 0 && eq6 == 1) { // 01 -> 11 eq3eq6nlm = mvs[String(eq1) + String(eq2) + String(eq3 + 1) + String(eq4) + String(eq5) + String(eq6)]; lower++; } else if (eq3 == 1 && eq6 == 0) { // 10 -> 11 eq3eq6nlm = mvs[String(eq1) + String(eq2) + String(eq3) + String(eq4) + String(eq5) + String(eq6 + 1)]; lower++; } else if (eq3 == 0 && eq6 == 0) { // 00 -> 01 OR 00 -> 10, takes the bigger eq3eq6nlm = Math.max(mvs[String(eq1) + String(eq2) + String(eq3 + 1) + String(eq4) + String(eq5) + String(eq6)], mvs[String(eq1) + String(eq2) + String(eq3) + String(eq4) + String(eq5) + String(eq6 + 1)]); lower++; } // 1.a - Compute maximal scoring (absolute) differences const msd = ((nlm) => { let msd = Math.abs(nlm - eqsv); if (isNaN(msd)) { return 0; } return msd; }); let eq1msd = msd(eq1nlm); let eq2msd = msd(eq2nlm); let eq3eq6msd = msd(eq3eq6nlm); let eq4msd = msd(eq4nlm); let eq5msd = msd(eq5nlm); // 1.b - Compute the severity distances of the to-be scored vectors // to a highest AND higher severity vector in the MacroVector let eq1svdst = 0, eq2svdst = 0, eq3eq6svdst = 0, eq4svdst = 0, eq5svdst = 0; for (const eq1mx of highestSeverityVectors[1][eq1]) { for (const eq2mx of highestSeverityVectors[2][eq2]) { for (const eq3eq6mx of highestSeverityVectors[3][eq3][eq6]) { for (const eq4mx of highestSeverityVectors[4][eq4]) { // Don't need to iterate over eq5, only one dimension is involved // so the highest of a MV's EQ is always unique, such that iterating // over it would lead to nothing but cognitive complexity. const partial = [eq1mx, eq2mx, eq3eq6mx, eq4mx].join('/'); // Compute severity distances const avsvdst = CVSS40.severityDistance('AV', this.getReal('AV'), CVSS40.getValue(partial, 'AV')); const prsvdst = CVSS40.severityDistance('PR', this.getReal('PR'), CVSS40.getValue(partial, 'PR')); const uisvdst = CVSS40.severityDistance('UI', this.getReal('UI'), CVSS40.getValue(partial, 'UI')); const acsvdst = CVSS40.severityDistance('AC', this.getReal('AC'), CVSS40.getValue(partial, 'AC')); const atsvdst = CVSS40.severityDistance('AT', this.getReal('AT'), CVSS40.getValue(partial, 'AT')); const vcsvdst = CVSS40.severityDistance('VC', this.getReal('VC'), CVSS40.getValue(partial, 'VC')); const visvdst = CVSS40.severityDistance('VI', this.getReal('VI'), CVSS40.getValue(partial, 'VI')); const vasvdst = CVSS40.severityDistance('VA', this.getReal('VA'), CVSS40.getValue(partial, 'VA')); const scsvdst = CVSS40.severityDistance('SC', this.getReal('SC'), CVSS40.getValue(partial, 'SC')); const sisvdst = CVSS40.severityDistance('SI', this.getReal('SI'), CVSS40.getValue(partial, 'SI')); const sasvdst = CVSS40.severityDistance('SA', this.getReal('SA'), CVSS40.getValue(partial, 'SA')); const crsvdst = CVSS40.severityDistance('CR', this.getReal('CR'), CVSS40.getValue(partial, 'CR')); const irsvdst = CVSS40.severityDistance('IR', this.getReal('IR'), CVSS40.getValue(partial, 'IR')); const arsvdst = CVSS40.severityDistance('AR', this.getReal('AR'), CVSS40.getValue(partial, 'AR')); if ([avsvdst, prsvdst, uisvdst, acsvdst, atsvdst, vcsvdst, visvdst, vasvdst, scsvdst, sisvdst, sasvdst, crsvdst, irsvdst, arsvdst].some((met) => met < 0)) { continue; } eq1svdst = avsvdst + prsvdst + uisvdst; eq2svdst = acsvdst + atsvdst; eq3eq6svdst = vcsvdst + visvdst + vasvdst + crsvdst + irsvdst + arsvdst; eq4svdst = scsvdst + sisvdst + sasvdst; // Don't need to compute E severity distance as the maximum will // always remain the same due to only 1 dimension involved in EQ5. eq5svdst = 0; break; } } } } // 1.c - Compute proportion of the distance const eq1prop = eq1svdst / (depth[1][eq1] + 1); const eq2prop = eq2svdst / (depth[2][eq2] + 1); const eq3eq6prop = eq3eq6svdst / (depth[3][eq3][eq6] + 1); const eq4prop = eq4svdst / (depth[4][eq4] + 1); const eq5prop = eq5svdst / (depth[5][eq5] + 1); // 1.d - Multiply maximal scoring diff. by prop. of distance eq1msd *= eq1prop; eq2msd *= eq2prop; eq3eq6msd *= eq3eq6prop; eq4msd *= eq4prop; eq5msd *= eq5prop; // 2 - Compute mean let mean = 0; if (lower != 0) { mean = (eq1msd + eq2msd + eq3eq6msd + eq4msd + eq5msd) / lower; } // 3 - Compute score return CVSS40.roundup(eqsv - mean); } /** * Gives the nomenclature of the current CVSS v4.0 object i.e. its structure * according to the Base, Threat and Environmental metric groups. * * @return The nomenclature string. */ Nomenclature() { const isDefined = ((metric) => this.Get(metric) != 'X'); const t = (['E']).some(isDefined); const e = (['CR', 'IR', 'AR', 'MAV', 'MAC', 'MAT', 'MPR', 'MUI', 'MVC', 'MVI', 'MVA', 'MSC', 'MSI', 'MSA']).some(isDefined); if (t) { if (e) { return 'CVSS-BTE'; } return 'CVSS-BT'; } if (e) { return 'CVSS-BE'; } return 'CVSS-B'; } getReal(metric) { if (['AV', 'AC', 'AT', 'PR', 'UI', 'VC', 'VI', 'VA', 'SC', 'SI', 'SA'].includes(metric)) { const v = this.Get('M' + metric); if (v != 'X') { return v; } return this.Get(metric); } const v = this.Get(metric); if (v != 'X') { return v; } // If it was not a base metric then defaults switch (metric) { case 'CR': case 'IR': case 'AR': return 'H'; case 'E': return 'A'; } } macrovector() { const av = this.getReal('AV'); const ac = this.getReal('AC'); const at = this.getReal('AT'); const pr = this.getReal('PR'); const ui = this.getReal('UI'); const vc = this.getReal('VC'); const vi = this.getReal('VI'); const va = this.getReal('VA'); const sc = this.getReal('SC'); const si = this.getReal('SI'); const sa = this.getReal('SA'); const e = this.getReal('E'); const cr = this.getReal('CR'); const ir = this.getReal('IR'); const ar = this.getReal('AR'); // Compte MacroVectors // => EQ1 let eq1 = '0'; if (av == 'N' && pr == 'N' && ui == 'N') { eq1 = '0'; } else if ((av == 'N' || pr == 'N' || ui == 'N') && !(av == 'N' && pr == 'N' && ui == 'N') && !(av == 'P')) { eq1 = '1'; } else if (av == 'P' || !(av == 'N' || pr == 'N' || ui == 'N')) { eq1 = '2'; } // EQ2 let eq2 = '0'; if (!(ac == 'L' && at == 'N')) { eq2 = '1'; } // EQ3 let eq3 = '0'; if (vc == 'H' && vi == 'H') { eq3 = '0'; } else if (!(vc == 'H' && vi == 'H') && (vc == 'H' || vi == 'H' || va == 'H')) { eq3 = '1'; } else if (!(vc == 'H' || vi == 'H' || va == 'H')) { eq3 = '2'; } // EQ4 let eq4 = '0'; if (si == 'S' || sa == 'S') { eq4 = '0'; } else if (!(si == 'S' || sa == 'S') && (sc == 'H' || si == 'H' || sa == 'H')) { eq4 = '1'; } else if (!(si == 'S' || sa == 'S') && !(sc == 'H' || si == 'H' || sa == 'H')) { eq4 = '2'; } // EQ5 let eq5 = '0'; if (e == 'A' || e == 'X') { eq5 = '0'; } else if (e == 'P') { eq5 = '1'; } else if (e == 'U') { eq5 = '2'; } // EQ6 let eq6 = '0'; const crh = (cr == 'H' || cr == 'X'); const irh = (ir == 'H' || ir == 'X'); const arh = (ar == 'H' || ar == 'X'); if ((crh && vc == 'H') || (irh && vi == 'H') || (arh && va == 'H')) { eq6 = '0'; } else if (!(crh && vc == 'H') && !(irh && vi == 'H') && !(arh && va == 'H')) { eq6 = '1'; } return eq1 + eq2 + eq3 + eq4 + eq5 + eq6; } static severityDistance(metric, vecVal, mxVal) { const values = sevIdx[metric]; return values.indexOf(vecVal) - values.indexOf(mxVal); } static getValue(partial, metric) { const pts = partial.split('/'); for (const pt of pts) { let pts = pt.split(':'); if (pts[0] == metric) { return pts[1]; } } } static roundup(score) { return +(score.toFixed(1)); } /** * Give the corresponding rating of the provided score. * * @param score The score to rate. * @return The rating. * @throws When the score is out of bounds. */ static Rating(score) { if (score < 0 || score > 10) { throw new Error('score out of bounds'); } if (score >= 9.0) { return 'CRITICAL'; } if (score >= 7.0) { return 'HIGH'; } if (score >= 4.0) { return 'MEDIUM'; } if (score >= 0.1) { return 'LOW'; } return 'NONE'; } } ; // Metrics defined in Table 23. const table23 = { // Base (11 metrics) 'AV': ['N', 'A', 'L', 'P'], 'AC': ['L', 'H'], 'AT': ['N', 'P'], 'PR': ['N', 'L', 'H'], 'UI': ['N', 'P', 'A'], 'VC': ['H', 'L', 'N'], 'VI': ['H', 'L', 'N'], 'VA': ['H', 'L', 'N'], 'SC': ['H', 'L', 'N'], 'SI': ['H', 'L', 'N'], 'SA': ['H', 'L', 'N'], // Threat (1 metric) 'E': ['X', 'A', 'P', 'U'], // Environmental (14 metrics) 'CR': ['X', 'H', 'M', 'L'], 'IR': ['X', 'H', 'M', 'L'], 'AR': ['X', 'H', 'M', 'L'], 'MAV': ['X', 'N', 'A', 'L', 'P'], 'MAC': ['X', 'L', 'H'], 'MAT': ['X', 'N', 'P'], 'MPR': ['X', 'N', 'L', 'H'], 'MUI': ['X', 'N', 'P', 'A'], 'MVC': ['X', 'H', 'L', 'N'], 'MVI': ['X', 'H', 'L', 'N'], 'MVA': ['X', 'H', 'L', 'N'], 'MSC': ['X', 'H', 'L', 'N'], 'MSI': ['X', 'S', 'H', 'L', 'N'], 'MSA': ['X', 'S', 'H', 'L', 'N'], // Supplemental (6 metrics) 'S': ['X', 'N', 'P'], 'AU': ['X', 'N', 'Y'], 'R': ['X', 'A', 'U', 'I'], 'V': ['X', 'D', 'C'], 'RE': ['X', 'L', 'M', 'H'], 'U': ['X', 'Clear', 'Green', 'Amber', 'Red'], }; const highestSeverityVectors = { // EQ1 - Table 24 1: { 0: ['AV:N/PR:N/UI:N'], 1: ['AV:A/PR:N/UI:N', 'AV:N/PR:L/UI:N', 'AV:N/PR:N/UI:P'], 2: ['AV:P/PR:N/UI:N', 'AV:A/PR:L/UI:P'], }, // EQ2 - Table 25 2: { 0: ['AC:L/AT:N'], 1: ['AC:H/AT:N', 'AC:L/AT:P'], }, // EQ3-EQ6 - Table 30 3: { 0: { 0: ['VC:H/VI:H/VA:H/CR:H/IR:H/AR:H'], 1: ['VC:H/VI:H/VA:L/CR:M/IR:M/AR:H', 'VC:H/VI:H/VA:H/CR:M/IR:M/AR:M'], }, 1: { 0: ['VC:L/VI:H/VA:H/CR:H/IR:H/AR:H', 'VC:H/VI:L/VA:H/CR:H/IR:H/AR:H'], 1: ['VC:L/VI:H/VA:L/CR:H/IR:M/AR:H', 'VC:L/VI:H/VA:H/CR:H/IR:M/AR:M', 'VC:H/VI:L/VA:H/CR:M/IR:H/AR:M', 'VC:H/VI:L/VA:L/CR:M/IR:H/AR:H', 'VC:L/VI:L/VA:H/CR:H/IR:H/AR:M'], }, 2: { 1: ['VC:L/VI:L/VA:L/CR:H/IR:H/AR:H'] }, }, // EQ4 - Table 27 4: { 0: ['SC:H/SI:S/SA:S'], 1: ['SC:H/SI:H/SA:H'], 2: ['SC:L/SI:L/SA:L'], }, // EQ5 - Table 28 (useless in fact, is not involved in score computation due to only 1 dimension) 5: { 0: ['E:A'], 1: ['E:P'], 2: ['E:U'], }, }; const sevIdx = { // Base metrics 'AV': ['N', 'A', 'L', 'P'], 'AC': ['L', 'H'], 'AT': ['N', 'P'], 'PR': ['N', 'L', 'H'], 'UI': ['N', 'P', 'A'], 'VC': ['H', 'L', 'N'], 'VI': ['H', 'L', 'N'], 'VA': ['H', 'L', 'N'], 'SC': ['H', 'L', 'N'], 'SI': ['S', 'H', 'L', 'N'], 'SA': ['S', 'H', 'L', 'N'], // Threat metrics 'E': ['A', 'P', 'U'], // Environmental metrics 'CR': ['H', 'M', 'L'], 'IR': ['H', 'M', 'L'], 'AR': ['H', 'M', 'L'], }; // Depths are re-computed and has been checked in github.com/pandatix/go-cvss const depth = { // EQ1 1: { 0: 0, 1: 3, 2: 4, }, // EQ2 2: { 0: 0, 1: 1, }, // EQ3-EQ6 3: { 0: { 0: 6, 1: 5, }, 1: { 0: 7, 1: 7, }, 2: { 1: 9, }, }, // EQ4 4: { 0: 5, 1: 4, 2: 3, }, // EQ5 5: { 0: 1, 1: 1, 2: 1, }, }; // MacroVectors maximum score given each EQuivalency sets. const mvs = { '000000': 10, '000001': 9.9, '000010': 9.8, '000011': 9.5, '000020': 9.5, '000021': 9.2, '000100': 10, '000101': 9.6, '000110': 9.3, '000111': 8.7, '000120': 9.1, '000121': 8.1, '000200': 9.3, '000201': 9, '000210': 8.9, '000211': 8, '000220': 8.1, '000221': 6.8, '001000': 9.8, '001001': 9.5, '001010': 9.5, '001011': 9.2, '001020': 9, '001021': 8.4, '001100': 9.3, '001101': 9.2, '001110': 8.9, '001111': 8.1, '001120': 8.1, '001121': 6.5, '001200': 8.8, '001201': 8, '001210': 7.8, '001211': 7, '001220': 6.9, '001221': 4.8, '002001': 9.2, '002011': 8.2, '002021': 7.2, '002101': 7.9, '002111': 6.9, '002121': 5, '002201': 6.9, '002211': 5.5, '002221': 2.7, '010000': 9.9, '010001': 9.7, '010010': 9.5, '010011': 9.2, '010020': 9.2, '010021': 8.5, '010100': 9.5, '010101': 9.1, '010110': 9, '010111': 8.3, '010120': 8.4, '010121': 7.1, '010200': 9.2, '010201': 8.1, '010210': 8.2, '010211': 7.1, '010220': 7.2, '010221': 5.3, '011000': 9.5, '011001': 9.3, '011010': 9.2, '011011': 8.5, '011020': 8.5, '011021': 7.3, '011100': 9.2, '011101': 8.2, '011110': 8, '011111': 7.2, '011120': 7, '011121': 5.9, '011200': 8.4, '011201': 7, '011210': 7.1, '011211': 5.2, '011220': 5, '011221': 3, '012001': 8.6, '012011': 7.5, '012021': 5.2, '012101': 7.1, '012111': 5.2, '012121': 2.9, '012201': 6.3, '012211': 2.9, '012221': 1.7, '100000': 9.8, '100001': 9.5, '100010': 9.4, '100011': 8.7, '100020': 9.1, '100021': 8.1, '100100': 9.4, '100101': 8.9, '100110': 8.6, '100111': 7.4, '100120': 7.7, '100121': 6.4, '100200': 8.7, '100201': 7.5, '100210': 7.4, '100211': 6.3, '100220': 6.3, '100221': 4.9, '101000': 9.4, '101001': 8.9, '101010': 8.8, '101011': 7.7, '101020': 7.6, '101021': 6.7, '101100': 8.6, '101101': 7.6, '101110': 7.4, '101111': 5.8, '101120': 5.9, '101121': 5, '101200': 7.2, '101201': 5.7, '101210': 5.7, '101211': 5.2, '101220': 5.2, '101221': 2.5, '102001': 8.3, '102011': 7, '102021': 5.4, '102101': 6.5, '102111': 5.8, '102121': 2.6, '102201': 5.3, '102211': 2.1, '102221': 1.3, '110000': 9.5, '110001': 9, '110010': 8.8, '110011': 7.6, '110020': 7.6, '110021': 7, '110100': 9, '110101': 7.7, '110110': 7.5, '110111': 6.2, '110120': 6.1, '110121': 5.3, '110200': 7.7, '110201': 6.6, '110210': 6.8, '110211': 5.9, '110220': 5.2, '110221': 3, '111000': 8.9, '111001': 7.8, '111010': 7.6, '111011': 6.7, '111020': 6.2, '111021': 5.8, '111100': 7.4, '111101': 5.9, '111110': 5.7, '111111': 5.7, '111120': 4.7, '111121': 2.3, '111200': 6.1, '111201': 5.2, '111210': 5.7, '111211': 2.9, '111220': 2.4, '111221': 1.6, '112001': 7.1, '112011': 5.9, '112021': 3, '112101': 5.8, '112111': 2.6, '112121': 1.5, '112201': 2.3, '112211': 1.3, '112221': 0.6, '200000': 9.3, '200001': 8.7, '200010': 8.6, '200011': 7.2, '200020': 7.5, '200021': 5.8, '200100': 8.6, '200101': 7.4, '200110': 7.4, '200111': 6.1, '200120': 5.6, '200121': 3.4, '200200': 7, '200201': 5.4, '200210': 5.2, '200211': 4, '200220': 4, '200221': 2.2, '201000': 8.5, '201001': 7.5, '201010': 7.4, '201011': 5.5, '201020': 6.2, '201021': 5.1, '201100': 7.2, '201101': 5.7, '201110': 5.5, '201111': 4.1, '201120': 4.6, '201121': 1.9, '201200': 5.3, '201201': 3.6, '201210': 3.4, '201211': 1.9, '201220': 1.9, '201221': 0.8, '202001': 6.4, '202011': 5.1, '202021': 2, '202101': 4.7, '202111': 2.1, '202121': 1.1, '202201': 2.4, '202211': 0.9, '202221': 0.4, '210000': 8.8, '210001': 7.5, '210010': 7.3, '210011': 5.3, '210020': 6, '210021': 5, '210100': 7.3, '210101': 5.5, '210110': 5.9, '210111': 4, '210120': 4.1, '210121': 2, '210200': 5.4, '210201': 4.3, '210210': 4.5, '210211': 2.2, '210220': 2, '210221': 1.1, '211000': 7.5, '211001': 5.5, '211010': 5.8, '211011': 4.5, '211020': 4, '211021': 2.1, '211100': 6.1, '211101': 5.1, '211110': 4.8, '211111': 1.8, '211120': 2, '211121': 0.9, '211200': 4.6, '211201': 1.8, '211210': 1.7, '211211': 0.7, '211220': 0.8, '211221': 0.2, '212001': 5.3, '212011': 2.4, '212021': 1.4, '212101': 2.4, '212111': 1.2, '212121': 0.5, '212201': 1, '212211': 0.3, '212221': 0.1, };