Sleuth Kit Java Bindings (JNI)  4.12.1
Java bindings for using The Sleuth Kit
ScoringManager.java
Go to the documentation of this file.
1 /*
2  * Sleuth Kit Data Model
3  *
4  * Copyright 2020 Basis Technology Corp.
5  * Contact: carrier <at> sleuthkit <dot> org
6  *
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  * http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  */
19 package org.sleuthkit.datamodel;
20 
21 import java.sql.PreparedStatement;
22 import java.sql.ResultSet;
23 import java.sql.SQLException;
24 import java.sql.Statement;
25 import java.util.ArrayList;
26 import java.util.Collections;
27 import java.util.HashSet;
28 import java.util.List;
29 import java.util.Optional;
30 import java.util.Map;
31 import java.util.Set;
32 import java.util.logging.Logger;
33 import java.util.stream.Collectors;
39 
45 public class ScoringManager {
46 
47  private static final Logger LOGGER = Logger.getLogger(ScoringManager.class.getName());
48 
49  private final SleuthkitCase db;
50 
58  this.db = skCase;
59  }
60 
70  public Score getAggregateScore(long objId) throws TskCoreException {
72  try (CaseDbConnection connection = db.getConnection()) {
73  return getAggregateScore(objId, false, connection).orElse(Score.SCORE_UNKNOWN);
74  } finally {
76  }
77  }
78 
89  public Map<Long, Score> getAggregateScores(List<Long> objIds) throws TskCoreException {
90 
91  if (objIds.isEmpty()) {
92  return Collections.emptyMap();
93  }
94 
95  // We need to deduplicate the list of object IDs. Otherwise the map
96  // below breaks and throws an exception.
97  Set<Long> set = new HashSet<>(objIds);
98 
99  String queryString = "SELECT obj_id, significance, priority FROM tsk_aggregate_score WHERE obj_id in "
100  + set.stream().map(l -> l.toString()).collect(Collectors.joining(",", "(", ")"));
101 
102  Map<Long, Score> results = set.stream().collect(Collectors.toMap( key -> key, key -> Score.SCORE_UNKNOWN));
104  try (CaseDbConnection connection = db.getConnection()) {
105  try (Statement s = connection.createStatement(); ResultSet rs = connection.executeQuery(s, queryString)) {
106  while (rs.next()) {
107  Long objId = rs.getLong("obj_id");
108  Score score = new Score(Significance.fromID(rs.getInt("significance")), Priority.fromID(rs.getInt("priority")));
109  results.put(objId, score);
110  }
111  } catch (SQLException ex) {
112  throw new TskCoreException("SQLException thrown while running query: " + queryString, ex);
113  }
114  } finally {
116  }
117  return results;
118  }
119 
120 
133  private Optional<Score> getAggregateScore(long objId, boolean forUpdate, CaseDbTransaction transaction) throws TskCoreException {
134  CaseDbConnection connection = transaction.getConnection();
135  return getAggregateScore(objId, forUpdate, connection);
136  }
137 
149  private Optional<Score> getAggregateScore(long objId, boolean forUpdate, CaseDbConnection connection) throws TskCoreException {
150  String queryString = "SELECT significance, priority FROM tsk_aggregate_score WHERE obj_id = " + objId + (forUpdate? " FOR UPDATE " : "");
151  try (Statement s = connection.createStatement(); ResultSet rs = connection.executeQuery(s, queryString)) {
152  if (rs.next()) {
153  return Optional.of(new Score(Significance.fromID(rs.getInt("significance")), Priority.fromID(rs.getInt("priority"))));
154  } else {
155  return Optional.empty();
156  }
157  } catch (SQLException ex) {
158  throw new TskCoreException("SQLException thrown while running query: " + queryString, ex);
159  }
160  }
161 
162 
175  private void setAggregateScore(long objId, Long dataSourceObjectId, Score score, boolean updateOnly, CaseDbTransaction transaction) throws TskCoreException {
176 
177  if (updateOnly) {
178  String updateSQLString = " UPDATE tsk_aggregate_score SET significance = ?, priority = ? where obj_id = ?" ;
179 
180  CaseDbConnection connection = transaction.getConnection();
181  try {
182  PreparedStatement preparedStatement = connection.getPreparedStatement(updateSQLString, Statement.NO_GENERATED_KEYS);
183  preparedStatement.clearParameters();
184 
185  preparedStatement.setInt(1, score.getSignificance().getId());
186  preparedStatement.setInt(2, score.getPriority().getId());
187 
188  preparedStatement.setLong(3, objId);
189 
190  connection.executeUpdate(preparedStatement);
191  } catch (SQLException ex) {
192  throw new TskCoreException(String.format("Error updating aggregate score, query: %s for objId = %d", updateSQLString, objId), ex);//NON-NLS
193  }
194  } else {
195 
196  String insertSQLString = "INSERT INTO tsk_aggregate_score (obj_id, data_source_obj_id, significance , priority) VALUES (?, ?, ?, ?)"
197  + " ON CONFLICT (obj_id) DO UPDATE SET significance = ?, priority = ?";
198 
199  CaseDbConnection connection = transaction.getConnection();
200  try {
201  PreparedStatement preparedStatement = connection.getPreparedStatement(insertSQLString, Statement.NO_GENERATED_KEYS);
202  preparedStatement.clearParameters();
203 
204  preparedStatement.setLong(1, objId);
205  if (dataSourceObjectId != null) {
206  preparedStatement.setLong(2, dataSourceObjectId);
207  } else {
208  preparedStatement.setNull(2, java.sql.Types.NULL);
209  }
210  preparedStatement.setInt(3, score.getSignificance().getId());
211  preparedStatement.setInt(4, score.getPriority().getId());
212 
213  preparedStatement.setInt(5, score.getSignificance().getId());
214  preparedStatement.setInt(6, score.getPriority().getId());
215 
216  connection.executeUpdate(preparedStatement);
217  } catch (SQLException ex) {
218  throw new TskCoreException(String.format("Error updating aggregate score, query: %s for objId = %d", insertSQLString, objId), ex);//NON-NLS
219  }
220  }
221  }
222 
223 
224 
239  Score updateAggregateScoreAfterAddition(long objId, Long dataSourceObjectId, Score newResultScore, CaseDbTransaction transaction) throws TskCoreException {
240 
241  /* get an exclusive write lock on the DB before we read anything so that we know we are
242  * the only one reading existing scores and updating. The risk is that two computers
243  * could update the score and the aggregate score ends up being incorrect.
244  *
245  * NOTE: The alternative design is to add a 'version' column for opportunistic locking
246  * and calculate these outside of a transaction. We opted for table locking for performance
247  * reasons so that we can still add the analysis results in a batch. That remains an option
248  * if we get into deadlocks with the current design.
249  */
250 
251  // Get the current score
252  // Will get a "FOR UPDATE" lock in postgresql
253  Optional<Score> oCurrentAggregateScore = ScoringManager.this.getAggregateScore(objId, db.getDatabaseType().equals(DbType.POSTGRESQL), transaction);
254 
255  Score currentAggregateScore = oCurrentAggregateScore.orElse(Score.SCORE_UNKNOWN);
256 
257  // If current score is Unknown And newscore is not Unknown - allow None (good) to be recorded
258  // or if the new score is higher than the current score
259  if ( (currentAggregateScore.compareTo(Score.SCORE_UNKNOWN) == 0 && newResultScore.compareTo(Score.SCORE_UNKNOWN) != 0)
260  || (Score.getScoreComparator().compare(newResultScore, currentAggregateScore) > 0)) {
261  setAggregateScore(objId, dataSourceObjectId, newResultScore, oCurrentAggregateScore.isPresent(), transaction); // If score is present, do an update.
262  // register score change in the transaction.
263  transaction.registerScoreChange(new ScoreChange(objId, dataSourceObjectId, currentAggregateScore, newResultScore));
264  return newResultScore;
265  } else {
266  // return the current score
267  return currentAggregateScore;
268  }
269  }
270 
281  Score updateAggregateScoreAfterDeletion(long objId, Long dataSourceObjectId, CaseDbTransaction transaction) throws TskCoreException {
282 
283  CaseDbConnection connection = transaction.getConnection();
284 
285  /* get an exclusive write lock on the DB before we read anything so that we know we are
286  * the only one reading existing scores and updating. The risk is that two computers
287  * could update the score and the aggregate score ends up being incorrect.
288  *
289  * NOTE: The alternative design is to add a 'version' column for opportunistic locking
290  * and calculate these outside of a transaction. We opted for table locking for performance
291  * reasons so that we can still add the analysis results in a batch. That remains an option
292  * if we get into deadlocks with the current design.
293  */
294 
295  // Get the current score
296  Optional<Score> oCurrentAggregateScore = ScoringManager.this.getAggregateScore(objId, db.getDatabaseType().equals(DbType.POSTGRESQL), transaction);
297 
298  Score currentScore = oCurrentAggregateScore.orElse(Score.SCORE_UNKNOWN);
299 
300  // Calculate the score from scratch by getting all of them and getting the highest
301  List<AnalysisResult> analysisResults = db.getBlackboard().getAnalysisResults(objId, connection);
302  Score newScore = Score.SCORE_UNKNOWN;
303  for (AnalysisResult iter : analysisResults) {
304  Score iterScore = iter.getScore();
305  if (Score.getScoreComparator().compare(iterScore, newScore) > 0) {
306  newScore = iterScore;
307  }
308  }
309 
310  // get the maximum score of the calculated aggregate score of analysis results
311  // or the score derived from the maximum known status of a content tag on this content.
312  Optional<Score> tagScore = db.getTaggingManager().getMaxTagType(objId, transaction)
313  .map(knownStatus -> TaggingManager.getTagScore(knownStatus));
314 
315  if (tagScore.isPresent() && Score.getScoreComparator().compare(tagScore.get(), newScore) > 0) {
316  newScore = tagScore.get();
317  }
318 
319  // only change the DB if we got a new score.
320  if (newScore.compareTo(currentScore) != 0) {
321  setAggregateScore(objId, dataSourceObjectId, newScore, oCurrentAggregateScore.isPresent(), transaction);
322 
323  // register the score change with the transaction so an event can be fired for it.
324  transaction.registerScoreChange(new ScoreChange(objId, dataSourceObjectId, currentScore, newScore));
325  }
326  return newScore;
327  }
328 
339  public long getContentCount(long dataSourceObjectId, Score.Significance significance) throws TskCoreException {
341  try (CaseDbConnection connection = db.getConnection()) {
342  return getContentCount(dataSourceObjectId, significance, connection);
343  } finally {
345  }
346  }
347 
348 
361  private long getContentCount(long dataSourceObjectId, Score.Significance significance, CaseDbConnection connection) throws TskCoreException {
362  String queryString = "SELECT COUNT(obj_id) AS count FROM tsk_aggregate_score"
363  + " WHERE data_source_obj_id = " + dataSourceObjectId
364  + " AND significance = " + significance.getId();
365 
366  try (Statement statement = connection.createStatement();
367  ResultSet resultSet = connection.executeQuery(statement, queryString);) {
368 
369  long count = 0;
370  if (resultSet.next()) {
371  count = resultSet.getLong("count");
372  }
373  return count;
374  } catch (SQLException ex) {
375  throw new TskCoreException("Error getting count of items with significance = " + significance.toString(), ex);
376  }
377  }
378 
389  public List<Content> getContent(long dataSourceObjectId, Score.Significance significance) throws TskCoreException {
391  try (CaseDbConnection connection = db.getConnection()) {
392  return getContent(dataSourceObjectId, significance, connection);
393  } finally {
395  }
396  }
397 
410  private List<Content> getContent(long dataSourceObjectId, Score.Significance significance, CaseDbConnection connection) throws TskCoreException {
411  String queryString = "SELECT obj_id FROM tsk_aggregate_score"
412  + " WHERE data_source_obj_id = " + dataSourceObjectId
413  + " AND significance = " + significance.getId();
414 
415  try (Statement statement = connection.createStatement();
416  ResultSet resultSet = connection.executeQuery(statement, queryString);) {
417 
418  List<Content> items = new ArrayList<>();
419  while (resultSet.next()) {
420  long objId = resultSet.getLong("obj_id");
421  items.add(db.getContentById(objId));
422  }
423  return items;
424  } catch (SQLException ex) {
425  throw new TskCoreException("Error getting list of items with significance = " + significance.toString(), ex);
426  }
427  }
428 }
static Priority fromID(int id)
Definition: Score.java:184
static Significance fromID(int id)
Definition: Score.java:118
List< Content > getContent(long dataSourceObjectId, Score.Significance significance)
static final Score SCORE_UNKNOWN
Definition: Score.java:213
Map< Long, Score > getAggregateScores(List< Long > objIds)
synchronized TaggingManager getTaggingManager()
long getContentCount(long dataSourceObjectId, Score.Significance significance)
List< AnalysisResult > getAnalysisResults(long dataSourceObjId, Integer artifactTypeID)

Copyright © 2011-2024 Brian Carrier. (carrier -at- sleuthkit -dot- org)
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.