provenant/license_detection/
hash_match.rs1use sha1::{Digest, Sha1};
13
14use crate::license_detection::index::LicenseIndex;
15use crate::license_detection::index::dictionary::{TokenId, TokenKind};
16use crate::license_detection::models::position_span::PositionSpan;
17use crate::license_detection::models::{LicenseMatch, MatchCoordinates, MatcherKind};
18use crate::license_detection::query::QueryRun;
19use crate::models::LineNumber;
20use crate::models::MatchScore;
21
22pub const MATCH_HASH: MatcherKind = MatcherKind::Hash;
23
24pub fn compute_hash(tokens: &[TokenId]) -> [u8; 20] {
37 let mut hasher = Sha1::new();
38
39 for token in tokens {
40 let signed = token.raw() as i16;
41 hasher.update(signed.to_le_bytes());
42 }
43
44 hasher.finalize().into()
45}
46
47pub fn hash_match(index: &LicenseIndex, query_run: &QueryRun) -> Vec<LicenseMatch> {
61 let mut matches = Vec::new();
62 let query_hash = compute_hash(query_run.tokens());
63
64 if let Some(rid) = index.rid_by_hash.get(&query_hash) {
65 let Some(rule) = index.rule(*rid) else {
66 return matches;
67 };
68 let Some(itokens) = index.rule_tokens(*rid) else {
69 return matches;
70 };
71
72 let rule_length = rule.tokens.len();
73
74 let matched_length = query_run.tokens().len();
75 let match_coverage = 100.0;
76
77 let start_line = query_run
78 .line_for_pos(query_run.start)
79 .and_then(LineNumber::new)
80 .unwrap_or(LineNumber::ONE);
81 let end_line = if let Some(end) = query_run.end {
82 query_run
83 .line_for_pos(end)
84 .and_then(LineNumber::new)
85 .unwrap_or(start_line)
86 } else {
87 start_line
88 };
89
90 let end = query_run.end.unwrap_or(query_run.start);
91 let qspan = PositionSpan::range(query_run.start, end + 1);
92 let ispan = PositionSpan::range(0, rule_length);
93 let hispan = PositionSpan::from_positions(
94 (0..rule_length)
95 .filter(|&p| index.dictionary.token_kind(itokens[p]) == TokenKind::Legalese),
96 );
97
98 let license_match = LicenseMatch {
99 license_expression: rule.license_expression.clone(),
100 license_expression_spdx: index
101 .rule_metadata_by_identifier
102 .get(&rule.identifier)
103 .and_then(|metadata| metadata.license_expression_spdx.clone()),
104 from_file: None,
105 start_line,
106 end_line,
107 start_token: query_run.start,
108 end_token: query_run.end.map_or(query_run.start, |e| e + 1),
109 matcher: MATCH_HASH,
110 score: MatchScore::MAX,
111 matched_length,
112 rule_length,
113 match_coverage,
114 rule_relevance: rule.relevance,
115 rid: *rid,
116 rule_identifier: rule.identifier.clone(),
117 rule_url: rule.rule_url().unwrap_or_default(),
118 matched_text: None,
119 referenced_filenames: rule.referenced_filenames.clone(),
120 rule_kind: rule.kind(),
121 is_from_license: rule.is_from_license,
122 rule_start_token: 0,
123 coordinates: MatchCoordinates::rule_aligned(qspan, ispan, hispan),
124 };
125
126 matches.push(license_match);
127 }
128
129 matches
130}
131
132#[cfg(test)]
133#[path = "hash_match_test.rs"]
134mod tests;