Sleuth Kit Java Bindings (JNI)  4.6
Java bindings for using The Sleuth Kit
TimelineManager.java
Go to the documentation of this file.
1 /*
2  * Sleuth Kit Data Model
3  *
4  * Copyright 2018-2019 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 com.google.common.annotations.Beta;
22 import com.google.common.collect.ImmutableList;
23 import com.google.common.collect.ImmutableMap;
24 import java.sql.PreparedStatement;
25 import java.sql.ResultSet;
26 import java.sql.SQLException;
27 import java.sql.Statement;
28 import java.util.ArrayList;
29 import java.util.Collection;
30 import java.util.Collections;
31 import java.util.HashMap;
32 import java.util.HashSet;
33 import java.util.List;
34 import java.util.Map;
35 import java.util.Objects;
36 import static java.util.Objects.isNull;
37 import java.util.Optional;
38 import java.util.Set;
39 import java.util.stream.Collectors;
40 import org.joda.time.DateTimeZone;
41 import org.joda.time.Interval;
44 import static org.sleuthkit.datamodel.CollectionUtils.isNotEmpty;
47 import static org.sleuthkit.datamodel.StringUtils.buildCSVString;
48 
52 public final class TimelineManager {
53 
57  private static final ImmutableList<TimelineEventType> ROOT_CATEGORY_AND_FILESYSTEM_TYPES
58  = ImmutableList.of(
67 
74  private static final ImmutableList<TimelineEventType> PREDEFINED_EVENT_TYPES
75  = new ImmutableList.Builder<TimelineEventType>()
80  .build();
81 
82  private final SleuthkitCase caseDB;
83 
87  private final Map<Long, TimelineEventType> eventTypeIDMap = new HashMap<>();
88 
99  this.caseDB = caseDB;
100 
101  //initialize root and base event types, these are added to the DB in c++ land
102  ROOT_CATEGORY_AND_FILESYSTEM_TYPES.forEach(eventType -> eventTypeIDMap.put(eventType.getTypeID(), eventType));
103 
104  //initialize the other event types that aren't added in c++
106  try (final CaseDbConnection con = caseDB.getConnection();
107  final Statement statement = con.createStatement()) {
108  for (TimelineEventType type : PREDEFINED_EVENT_TYPES) {
109  con.executeUpdate(statement,
110  insertOrIgnore(" INTO tsk_event_types(event_type_id, display_name, super_type_id) "
111  + "VALUES( " + type.getTypeID() + ", '"
112  + escapeSingleQuotes(type.getDisplayName()) + "',"
113  + type.getParent().getTypeID()
114  + ")")); //NON-NLS
115  eventTypeIDMap.put(type.getTypeID(), type);
116  }
117  } catch (SQLException ex) {
118  throw new TskCoreException("Failed to initialize timeline event types", ex); // NON-NLS
119  } finally {
121  }
122  }
123 
135  public Interval getSpanningInterval(Collection<Long> eventIDs) throws TskCoreException {
136  if (eventIDs.isEmpty()) {
137  return null;
138  }
139  final String query = "SELECT Min(time) as minTime, Max(time) as maxTime FROM tsk_events WHERE event_id IN (" + buildCSVString(eventIDs) + ")"; //NON-NLS
141  try (CaseDbConnection con = caseDB.getConnection();
142  Statement stmt = con.createStatement();
143  ResultSet results = stmt.executeQuery(query);) {
144  if (results.next()) {
145  return new Interval(results.getLong("minTime") * 1000, (results.getLong("maxTime") + 1) * 1000, DateTimeZone.UTC); // NON-NLS
146  }
147  } catch (SQLException ex) {
148  throw new TskCoreException("Error executing get spanning interval query: " + query, ex); // NON-NLS
149  } finally {
151  }
152  return null;
153  }
154 
167  public Interval getSpanningInterval(Interval timeRange, TimelineFilter.RootFilter filter, DateTimeZone timeZone) throws TskCoreException {
168  long start = timeRange.getStartMillis() / 1000;
169  long end = timeRange.getEndMillis() / 1000;
170  String sqlWhere = getSQLWhere(filter);
171  String augmentedEventsTablesSQL = getAugmentedEventsTablesSQL(filter);
172  String queryString = " SELECT (SELECT Max(time) FROM " + augmentedEventsTablesSQL
173  + " WHERE time <=" + start + " AND " + sqlWhere + ") AS start,"
174  + " (SELECT Min(time) FROM " + augmentedEventsTablesSQL
175  + " WHERE time >= " + end + " AND " + sqlWhere + ") AS end";//NON-NLS
177  try (CaseDbConnection con = caseDB.getConnection();
178  Statement stmt = con.createStatement(); //can't use prepared statement because of complex where clause
179  ResultSet results = stmt.executeQuery(queryString);) {
180 
181  if (results.next()) {
182  long start2 = results.getLong("start"); // NON-NLS
183  long end2 = results.getLong("end"); // NON-NLS
184 
185  if (end2 == 0) {
186  end2 = getMaxEventTime();
187  }
188  return new Interval(start2 * 1000, (end2 + 1) * 1000, timeZone);
189  }
190  } catch (SQLException ex) {
191  throw new TskCoreException("Failed to get MIN time.", ex); // NON-NLS
192  } finally {
194  }
195  return null;
196  }
197 
207  public TimelineEvent getEventById(long eventID) throws TskCoreException {
208  String sql = "SELECT * FROM " + getAugmentedEventsTablesSQL(false) + " WHERE event_id = " + eventID;
210  try (CaseDbConnection con = caseDB.getConnection();
211  Statement stmt = con.createStatement();) {
212  try (ResultSet results = stmt.executeQuery(sql);) {
213  if (results.next()) {
214  int typeID = results.getInt("event_type_id");
215  TimelineEventType type = getEventType(typeID).orElseThrow(() -> newEventTypeMappingException(typeID)); //NON-NLS
216  return new TimelineEvent(eventID,
217  results.getLong("data_source_obj_id"),
218  results.getLong("content_obj_id"),
219  results.getLong("artifact_id"),
220  results.getLong("time"),
221  type, results.getString("full_description"),
222  results.getString("med_description"),
223  results.getString("short_description"),
224  intToBoolean(results.getInt("hash_hit")),
225  intToBoolean(results.getInt("tagged")));
226  }
227  }
228  } catch (SQLException sqlEx) {
229  throw new TskCoreException("Error while executing query " + sql, sqlEx); // NON-NLS
230  } finally {
232  }
233  return null;
234  }
235 
247  public List<Long> getEventIDs(Interval timeRange, TimelineFilter.RootFilter filter) throws TskCoreException {
248  Long startTime = timeRange.getStartMillis() / 1000;
249  Long endTime = timeRange.getEndMillis() / 1000;
250 
251  if (Objects.equals(startTime, endTime)) {
252  endTime++; //make sure end is at least 1 millisecond after start
253  }
254 
255  ArrayList<Long> resultIDs = new ArrayList<>();
256 
257  String query = "SELECT tsk_events.event_id AS event_id FROM " + getAugmentedEventsTablesSQL(filter)
258  + " WHERE time >= " + startTime + " AND time <" + endTime + " AND " + getSQLWhere(filter) + " ORDER BY time ASC"; // NON-NLS
260  try (CaseDbConnection con = caseDB.getConnection();
261  Statement stmt = con.createStatement();
262  ResultSet results = stmt.executeQuery(query);) {
263  while (results.next()) {
264  resultIDs.add(results.getLong("event_id")); //NON-NLS
265  }
266 
267  } catch (SQLException sqlEx) {
268  throw new TskCoreException("Error while executing query " + query, sqlEx); // NON-NLS
269  } finally {
271  }
272 
273  return resultIDs;
274  }
275 
284  public Long getMaxEventTime() throws TskCoreException {
286  try (CaseDbConnection con = caseDB.getConnection();
287  Statement stms = con.createStatement();
288  ResultSet results = stms.executeQuery(STATEMENTS.GET_MAX_TIME.getSQL());) {
289  if (results.next()) {
290  return results.getLong("max"); // NON-NLS
291  }
292  } catch (SQLException ex) {
293  throw new TskCoreException("Error while executing query " + STATEMENTS.GET_MAX_TIME.getSQL(), ex); // NON-NLS
294  } finally {
296  }
297  return -1l;
298  }
299 
308  public Long getMinEventTime() throws TskCoreException {
310  try (CaseDbConnection con = caseDB.getConnection();
311  Statement stms = con.createStatement();
312  ResultSet results = stms.executeQuery(STATEMENTS.GET_MIN_TIME.getSQL());) {
313  if (results.next()) {
314  return results.getLong("min"); // NON-NLS
315  }
316  } catch (SQLException ex) {
317  throw new TskCoreException("Error while executing query " + STATEMENTS.GET_MAX_TIME.getSQL(), ex); // NON-NLS
318  } finally {
320  }
321  return -1l;
322  }
323 
332  public Optional<TimelineEventType> getEventType(long eventTypeID) {
333  return Optional.ofNullable(eventTypeIDMap.get(eventTypeID));
334  }
335 
341  public ImmutableList<TimelineEventType> getEventTypes() {
342  return ImmutableList.copyOf(eventTypeIDMap.values());
343  }
344 
345  private String insertOrIgnore(String query) {
346  switch (caseDB.getDatabaseType()) {
347  case POSTGRESQL:
348  return " INSERT " + query + " ON CONFLICT DO NOTHING "; //NON-NLS
349  case SQLITE:
350  return " INSERT OR IGNORE " + query; //NON-NLS
351  default:
352  throw new UnsupportedOperationException("Unsupported DB type: " + caseDB.getDatabaseType().name());
353  }
354  }
355 
359  private enum STATEMENTS {
360 
361  GET_MAX_TIME("SELECT Max(time) AS max FROM tsk_events"), // NON-NLS
362  GET_MIN_TIME("SELECT Min(time) AS min FROM tsk_events"); // NON-NLS
363 
364  private final String sql;
365 
366  private STATEMENTS(String sql) {
367  this.sql = sql;
368  }
369 
370  String getSQL() {
371  return sql;
372  }
373  }
374 
385  public List<Long> getEventIDsForArtifact(BlackboardArtifact artifact) throws TskCoreException {
386  ArrayList<Long> eventIDs = new ArrayList<>();
387 
388  String query
389  = "SELECT event_id FROM tsk_events "
390  + " LEFT JOIN tsk_event_descriptions on ( tsk_events.event_description_id = tsk_event_descriptions.event_description_id ) "
391  + " WHERE artifact_id = " + artifact.getArtifactID();
393  try (CaseDbConnection con = caseDB.getConnection();
394  Statement stmt = con.createStatement();
395  ResultSet results = stmt.executeQuery(query);) {
396  while (results.next()) {
397  eventIDs.add(results.getLong("event_id"));//NON-NLS
398  }
399  } catch (SQLException ex) {
400  throw new TskCoreException("Error executing getEventIDsForArtifact query.", ex); // NON-NLS
401  } finally {
403  }
404  return eventIDs;
405  }
406 
420  public Set<Long> getEventIDsForContent(Content content, boolean includeDerivedArtifacts) throws TskCoreException {
422  try (CaseDbConnection conn = caseDB.getConnection()) {
423  return getEventAndDescriptionIDs(conn, content.getId(), includeDerivedArtifacts).keySet();
424  } finally {
426  }
427  }
428 
446  private long addEventDescription(long dataSourceObjId, long fileObjId, Long artifactID,
447  String fullDescription, String medDescription, String shortDescription,
448  boolean hasHashHits, boolean tagged, CaseDbConnection connection) throws TskCoreException {
449  String insertDescriptionSql
450  = "INSERT INTO tsk_event_descriptions ( "
451  + "data_source_obj_id, content_obj_id, artifact_id, "
452  + " full_description, med_description, short_description, "
453  + " hash_hit, tagged "
454  + " ) VALUES ("
455  + dataSourceObjId + ","
456  + fileObjId + ","
457  + Objects.toString(artifactID, "NULL") + ","
458  + quotePreservingNull(fullDescription) + ","
459  + quotePreservingNull(medDescription) + ","
460  + quotePreservingNull(shortDescription) + ", "
461  + booleanToInt(hasHashHits) + ","
462  + booleanToInt(tagged)
463  + " )";
464 
466  try (Statement insertDescriptionStmt = connection.createStatement()) {
467  connection.executeUpdate(insertDescriptionStmt, insertDescriptionSql, PreparedStatement.RETURN_GENERATED_KEYS);
468  try (ResultSet generatedKeys = insertDescriptionStmt.getGeneratedKeys()) {
469  generatedKeys.next();
470  return generatedKeys.getLong(1);
471  }
472  } catch (SQLException ex) {
473  throw new TskCoreException("Failed to insert event description.", ex); // NON-NLS
474  } finally {
476  }
477  }
478 
479  Collection<TimelineEvent> addEventsForNewFile(AbstractFile file, CaseDbConnection connection) throws TskCoreException {
480  //gather time stamps into map
481  Map<TimelineEventType, Long> timeMap = ImmutableMap.of(TimelineEventType.FILE_CREATED, file.getCrtime(),
482  TimelineEventType.FILE_ACCESSED, file.getAtime(),
483  TimelineEventType.FILE_CHANGED, file.getCtime(),
484  TimelineEventType.FILE_MODIFIED, file.getMtime());
485 
486  /*
487  * If there are no legitimate ( greater than zero ) time stamps skip the
488  * rest of the event generation.
489  */
490  if (Collections.max(timeMap.values()) <= 0) {
491  return Collections.emptySet();
492  }
493 
494  String description = file.getParentPath() + file.getName();
495  long fileObjId = file.getId();
496  Set<TimelineEvent> events = new HashSet<>();
498  try {
499  long descriptionID = addEventDescription(file.getDataSourceObjectId(), fileObjId, null,
500  description, null, null, false, false, connection);
501 
502  for (Map.Entry<TimelineEventType, Long> timeEntry : timeMap.entrySet()) {
503  Long time = timeEntry.getValue();
504  if (time > 0) {// if the time is legitimate ( greater than zero ) insert it
505  TimelineEventType type = timeEntry.getKey();
506  long eventID = addEventWithExistingDescription(time, type, descriptionID, connection);
507 
508  /*
509  * Last two flags indicating hasTags and hasHashHits are
510  * both set to false with the assumption that this is not
511  * possible for a new file. See JIRA-5407
512  */
513  events.add(new TimelineEvent(eventID, descriptionID, fileObjId, null, time, type,
514  description, null, null, false, false));
515  }
516  }
517 
518  } finally {
520  }
521  events.stream()
522  .map(TimelineEventAddedEvent::new)
523  .forEach(caseDB::fireTSKEvent);
524 
525  return events;
526  }
527 
541  Set<TimelineEvent> addArtifactEvents(BlackboardArtifact artifact) throws TskCoreException {
542  Set<TimelineEvent> newEvents = new HashSet<>();
543 
544  /*
545  * If the artifact is a TSK_TL_EVENT, use the TSK_TL_EVENT_TYPE
546  * attribute to determine its event type, but give it a generic
547  * description.
548  */
549  if (artifact.getArtifactTypeID() == TSK_TL_EVENT.getTypeID()) {
550  TimelineEventType eventType;//the type of the event to add.
551  BlackboardAttribute attribute = artifact.getAttribute(new BlackboardAttribute.Type(TSK_TL_EVENT_TYPE));
552  if (attribute == null) {
553  eventType = TimelineEventType.OTHER;
554  } else {
555  long eventTypeID = attribute.getValueLong();
556  eventType = eventTypeIDMap.getOrDefault(eventTypeID, TimelineEventType.OTHER);
557  }
558 
559  // @@@ This casting is risky if we change class hierarchy, but was expedient. Should move parsing to another class
560  addArtifactEvent(((TimelineEventArtifactTypeImpl) TimelineEventType.OTHER)::makeEventDescription, eventType, artifact)
561  .ifPresent(newEvents::add);
562  } else {
563  /*
564  * If there are any event types configured to make descriptions
565  * automatically, use those.
566  */
567  Set<TimelineEventArtifactTypeImpl> eventTypesForArtifact = eventTypeIDMap.values().stream()
568  .filter(TimelineEventArtifactTypeImpl.class::isInstance)
569  .map(TimelineEventArtifactTypeImpl.class::cast)
570  .filter(eventType -> eventType.getArtifactTypeID() == artifact.getArtifactTypeID())
571  .collect(Collectors.toSet());
572 
573  for (TimelineEventArtifactTypeImpl eventType : eventTypesForArtifact) {
574  addArtifactEvent(eventType::makeEventDescription, eventType, artifact)
575  .ifPresent(newEvents::add);
576  }
577  }
578  newEvents.stream()
579  .map(TimelineEventAddedEvent::new)
580  .forEach(caseDB::fireTSKEvent);
581  return newEvents;
582  }
583 
601  private Optional<TimelineEvent> addArtifactEvent(TSKCoreCheckedFunction<BlackboardArtifact, TimelineEventDescriptionWithTime> payloadExtractor,
602  TimelineEventType eventType, BlackboardArtifact artifact) throws TskCoreException {
603  TimelineEventDescriptionWithTime eventPayload = payloadExtractor.apply(artifact);
604  if (eventPayload == null) {
605  return Optional.empty();
606  }
607  long time = eventPayload.getTime();
608  // if the time is legitimate ( greater than zero ) insert it into the db
609  if (time <= 0) {
610  return Optional.empty();
611  }
612  String fullDescription = eventPayload.getDescription(TimelineLevelOfDetail.HIGH);
613  String medDescription = eventPayload.getDescription(TimelineLevelOfDetail.MEDIUM);
614  String shortDescription = eventPayload.getDescription(TimelineLevelOfDetail.LOW);
615  long artifactID = artifact.getArtifactID();
616  long fileObjId = artifact.getObjectID();
617  long dataSourceObjectID = artifact.getDataSourceObjectID();
618 
619  AbstractFile file = caseDB.getAbstractFileById(fileObjId);
620  boolean hasHashHits = false;
621  // file will be null if source was data source or some non-file
622  if (file != null) {
623  hasHashHits = isNotEmpty(file.getHashSetNames());
624  }
625  boolean tagged = isNotEmpty(caseDB.getBlackboardArtifactTagsByArtifact(artifact));
626 
627  TimelineEvent event;
629  try (CaseDbConnection connection = caseDB.getConnection();) {
630 
631  long descriptionID = addEventDescription(dataSourceObjectID, fileObjId, artifactID,
632  fullDescription, medDescription, shortDescription,
633  hasHashHits, tagged, connection);
634 
635  long eventID = addEventWithExistingDescription(time, eventType, descriptionID, connection);
636 
637  event = new TimelineEvent(eventID, dataSourceObjectID, fileObjId, artifactID,
638  time, eventType, fullDescription, medDescription, shortDescription,
639  hasHashHits, tagged);
640 
641  } finally {
643  }
644  return Optional.of(event);
645  }
646 
647  private long addEventWithExistingDescription(Long time, TimelineEventType type, long descriptionID, CaseDbConnection connection) throws TskCoreException {
648  String insertEventSql
649  = "INSERT INTO tsk_events ( event_type_id, event_description_id , time) "
650  + " VALUES (" + type.getTypeID() + ", " + descriptionID + ", " + time + ")";
651 
653  try (Statement insertRowStmt = connection.createStatement();) {
654  connection.executeUpdate(insertRowStmt, insertEventSql, PreparedStatement.RETURN_GENERATED_KEYS);
655 
656  try (ResultSet generatedKeys = insertRowStmt.getGeneratedKeys();) {
657  generatedKeys.next();
658  return generatedKeys.getLong(1);
659  }
660  } catch (SQLException ex) {
661  throw new TskCoreException("Failed to insert event for existing description.", ex); // NON-NLS
662  } finally {
664  }
665  }
666 
667  static private String quotePreservingNull(String value) {
668  return isNull(value) ? " NULL " : "'" + escapeSingleQuotes(value) + "'";//NON-NLS
669  }
670 
671  private Map<Long, Long> getEventAndDescriptionIDs(CaseDbConnection conn, long contentObjID, boolean includeArtifacts) throws TskCoreException {
672  return getEventAndDescriptionIDsHelper(conn, contentObjID, (includeArtifacts ? "" : " AND artifact_id IS NULL"));
673  }
674 
675  private Map<Long, Long> getEventAndDescriptionIDs(CaseDbConnection conn, long contentObjID, Long artifactID) throws TskCoreException {
676  return getEventAndDescriptionIDsHelper(conn, contentObjID, " AND artifact_id = " + artifactID);
677  }
678 
679  private Map<Long, Long> getEventAndDescriptionIDsHelper(CaseDbConnection con, long fileObjID, String artifactClause) throws TskCoreException {
680  //map from event_id to the event_description_id for that event.
681  Map<Long, Long> eventIDToDescriptionIDs = new HashMap<>();
682  String sql = "SELECT event_id, tsk_events.event_description_id"
683  + " FROM tsk_events "
684  + " LEFT JOIN tsk_event_descriptions ON ( tsk_events.event_description_id = tsk_event_descriptions.event_description_id )"
685  + " WHERE content_obj_id = " + fileObjID
686  + artifactClause;
687  try (Statement selectStmt = con.createStatement(); ResultSet executeQuery = selectStmt.executeQuery(sql);) {
688  while (executeQuery.next()) {
689  eventIDToDescriptionIDs.put(executeQuery.getLong("event_id"), executeQuery.getLong("event_description_id")); //NON-NLS
690  }
691  } catch (SQLException ex) {
692  throw new TskCoreException("Error getting event description ids for object id = " + fileObjID, ex);
693  }
694  return eventIDToDescriptionIDs;
695  }
696 
713  @Beta
714  public Set<Long> updateEventsForContentTagAdded(Content content) throws TskCoreException {
716  try (CaseDbConnection conn = caseDB.getConnection()) {
717  Map<Long, Long> eventIDs = getEventAndDescriptionIDs(conn, content.getId(), false);
718  updateEventSourceTaggedFlag(conn, eventIDs.values(), 1);
719  return eventIDs.keySet();
720  } finally {
722  }
723  }
724 
742  @Beta
743  public Set<Long> updateEventsForContentTagDeleted(Content content) throws TskCoreException {
745  try (CaseDbConnection conn = caseDB.getConnection()) {
746  if (caseDB.getContentTagsByContent(content).isEmpty()) {
747  Map<Long, Long> eventIDs = getEventAndDescriptionIDs(conn, content.getId(), false);
748  updateEventSourceTaggedFlag(conn, eventIDs.values(), 0);
749  return eventIDs.keySet();
750  } else {
751  return Collections.emptySet();
752  }
753  } finally {
755  }
756  }
757 
771  try (CaseDbConnection conn = caseDB.getConnection()) {
772  Map<Long, Long> eventIDs = getEventAndDescriptionIDs(conn, artifact.getObjectID(), artifact.getArtifactID());
773  updateEventSourceTaggedFlag(conn, eventIDs.values(), 1);
774  return eventIDs.keySet();
775  } finally {
777  }
778  }
779 
794  try (CaseDbConnection conn = caseDB.getConnection()) {
795  if (caseDB.getBlackboardArtifactTagsByArtifact(artifact).isEmpty()) {
796  Map<Long, Long> eventIDs = getEventAndDescriptionIDs(conn, artifact.getObjectID(), artifact.getArtifactID());
797  updateEventSourceTaggedFlag(conn, eventIDs.values(), 0);
798  return eventIDs.keySet();
799  } else {
800  return Collections.emptySet();
801  }
802  } finally {
804  }
805  }
806 
807  private void updateEventSourceTaggedFlag(CaseDbConnection conn, Collection<Long> eventDescriptionIDs, int flagValue) throws TskCoreException {
808  if (eventDescriptionIDs.isEmpty()) {
809  return;
810  }
811 
812  String sql = "UPDATE tsk_event_descriptions SET tagged = " + flagValue + " WHERE event_description_id IN (" + buildCSVString(eventDescriptionIDs) + ")"; //NON-NLS
813  try (Statement updateStatement = conn.createStatement()) {
814  updateStatement.executeUpdate(sql);
815  } catch (SQLException ex) {
816  throw new TskCoreException("Error marking content events tagged: " + sql, ex);//NON-NLS
817  }
818  }
819 
834  public Set<Long> updateEventsForHashSetHit(Content content) throws TskCoreException {
836  try (CaseDbConnection con = caseDB.getConnection(); Statement updateStatement = con.createStatement();) {
837  Map<Long, Long> eventIDs = getEventAndDescriptionIDs(con, content.getId(), true);
838  if (! eventIDs.isEmpty()) {
839  String sql = "UPDATE tsk_event_descriptions SET hash_hit = 1" + " WHERE event_description_id IN (" + buildCSVString(eventIDs.values()) + ")"; //NON-NLS
840  try {
841  updateStatement.executeUpdate(sql); //NON-NLS
842  return eventIDs.keySet();
843  } catch (SQLException ex) {
844  throw new TskCoreException("Error setting hash_hit of events.", ex);//NON-NLS
845  }
846  } else {
847  return eventIDs.keySet();
848  }
849  } catch (SQLException ex) {
850  throw new TskCoreException("Error setting hash_hit of events.", ex);//NON-NLS
851  } finally {
853  }
854  }
855 
856  void rollBackTransaction(SleuthkitCase.CaseDbTransaction trans) throws TskCoreException {
857  trans.rollback();
858  }
859 
879  public Map<TimelineEventType, Long> countEventsByType(Long startTime, Long endTime, TimelineFilter.RootFilter filter, TimelineEventType.HierarchyLevel typeHierachyLevel) throws TskCoreException {
880  long adjustedEndTime = Objects.equals(startTime, endTime) ? endTime + 1 : endTime;
881  //do we want the base or subtype column of the databse
882  String typeColumn = typeColumnHelper(TimelineEventType.HierarchyLevel.EVENT.equals(typeHierachyLevel));
883 
884  String queryString = "SELECT count(DISTINCT tsk_events.event_id) AS count, " + typeColumn//NON-NLS
885  + " FROM " + getAugmentedEventsTablesSQL(filter)//NON-NLS
886  + " WHERE time >= " + startTime + " AND time < " + adjustedEndTime + " AND " + getSQLWhere(filter) // NON-NLS
887  + " GROUP BY " + typeColumn; // NON-NLS
888 
890  try (CaseDbConnection con = caseDB.getConnection();
891  Statement stmt = con.createStatement();
892  ResultSet results = stmt.executeQuery(queryString);) {
893  Map<TimelineEventType, Long> typeMap = new HashMap<>();
894  while (results.next()) {
895  int eventTypeID = results.getInt(typeColumn);
896  TimelineEventType eventType = getEventType(eventTypeID)
897  .orElseThrow(() -> newEventTypeMappingException(eventTypeID));//NON-NLS
898 
899  typeMap.put(eventType, results.getLong("count")); // NON-NLS
900  }
901  return typeMap;
902  } catch (SQLException ex) {
903  throw new TskCoreException("Error getting count of events from db: " + queryString, ex); // NON-NLS
904  } finally {
906  }
907  }
908 
909  private static TskCoreException newEventTypeMappingException(int eventTypeID) {
910  return new TskCoreException("Error mapping event type id " + eventTypeID + " to EventType.");//NON-NLS
911  }
912 
926  static private String getAugmentedEventsTablesSQL(TimelineFilter.RootFilter filter) {
927  TimelineFilter.FileTypesFilter fileTypesFitler = filter.getFileTypesFilter();
928  boolean needsMimeTypes = fileTypesFitler != null && fileTypesFitler.hasSubFilters();
929 
930  return getAugmentedEventsTablesSQL(needsMimeTypes);
931  }
932 
947  static private String getAugmentedEventsTablesSQL(boolean needMimeTypes) {
948  /*
949  * Regarding the timeline event tables schema, note that several columns
950  * in the tsk_event_descriptions table seem, at first glance, to be
951  * attributes of events rather than their descriptions and would appear
952  * to belong in tsk_events table instead. The rationale for putting the
953  * data source object ID, content object ID, artifact ID and the flags
954  * indicating whether or not the event source has a hash set hit or is
955  * tagged were motivated by the fact that these attributes are identical
956  * for each event in a set of file system file MAC time events. The
957  * decision was made to avoid duplication and save space by placing this
958  * data in the tsk_event-descriptions table.
959  */
960  return "( SELECT event_id, time, tsk_event_descriptions.data_source_obj_id, content_obj_id, artifact_id, "
961  + " full_description, med_description, short_description, tsk_events.event_type_id, super_type_id,"
962  + " hash_hit, tagged "
963  + (needMimeTypes ? ", mime_type" : "")
964  + " FROM tsk_events "
965  + " JOIN tsk_event_descriptions ON ( tsk_event_descriptions.event_description_id = tsk_events.event_description_id)"
966  + " JOIN tsk_event_types ON (tsk_events.event_type_id = tsk_event_types.event_type_id ) "
967  + (needMimeTypes ? " LEFT OUTER JOIN tsk_files "
968  + " ON (tsk_event_descriptions.content_obj_id = tsk_files.obj_id)"
969  : "")
970  + ") AS tsk_events";
971  }
972 
980  private static int booleanToInt(boolean value) {
981  return value ? 1 : 0;
982  }
983 
984  private static boolean intToBoolean(int value) {
985  return value != 0;
986  }
987 
1000  public List<TimelineEvent> getEvents(Interval timeRange, TimelineFilter.RootFilter filter) throws TskCoreException {
1001  List<TimelineEvent> events = new ArrayList<>();
1002 
1003  Long startTime = timeRange.getStartMillis() / 1000;
1004  Long endTime = timeRange.getEndMillis() / 1000;
1005 
1006  if (Objects.equals(startTime, endTime)) {
1007  endTime++; //make sure end is at least 1 millisecond after start
1008  }
1009 
1010  if (filter == null) {
1011  return events;
1012  }
1013 
1014  if (endTime < startTime) {
1015  return events;
1016  }
1017 
1018  //build dynamic parts of query
1019  String querySql = "SELECT time, content_obj_id, data_source_obj_id, artifact_id, " // NON-NLS
1020  + " event_id, " //NON-NLS
1021  + " hash_hit, " //NON-NLS
1022  + " tagged, " //NON-NLS
1023  + " event_type_id, super_type_id, "
1024  + " full_description, med_description, short_description " // NON-NLS
1025  + " FROM " + getAugmentedEventsTablesSQL(filter) // NON-NLS
1026  + " WHERE time >= " + startTime + " AND time < " + endTime + " AND " + getSQLWhere(filter) // NON-NLS
1027  + " ORDER BY time"; // NON-NLS
1028 
1030  try (CaseDbConnection con = caseDB.getConnection();
1031  Statement stmt = con.createStatement();
1032  ResultSet resultSet = stmt.executeQuery(querySql);) {
1033 
1034  while (resultSet.next()) {
1035  int eventTypeID = resultSet.getInt("event_type_id");
1036  TimelineEventType eventType = getEventType(eventTypeID).orElseThrow(()
1037  -> new TskCoreException("Error mapping event type id " + eventTypeID + "to EventType."));//NON-NLS
1038 
1039  TimelineEvent event = new TimelineEvent(
1040  resultSet.getLong("event_id"), // NON-NLS
1041  resultSet.getLong("data_source_obj_id"), // NON-NLS
1042  resultSet.getLong("content_obj_id"), // NON-NLS
1043  resultSet.getLong("artifact_id"), // NON-NLS
1044  resultSet.getLong("time"), // NON-NLS
1045  eventType,
1046  resultSet.getString("full_description"), // NON-NLS
1047  resultSet.getString("med_description"), // NON-NLS
1048  resultSet.getString("short_description"), // NON-NLS
1049  resultSet.getInt("hash_hit") != 0, //NON-NLS
1050  resultSet.getInt("tagged") != 0);
1051 
1052  events.add(event);
1053  }
1054 
1055  } catch (SQLException ex) {
1056  throw new TskCoreException("Error getting events from db: " + querySql, ex); // NON-NLS
1057  } finally {
1059  }
1060 
1061  return events;
1062  }
1063 
1071  private static String typeColumnHelper(final boolean useSubTypes) {
1072  return useSubTypes ? "event_type_id" : "super_type_id"; //NON-NLS
1073  }
1074 
1083  String getSQLWhere(TimelineFilter.RootFilter filter) {
1084 
1085  String result;
1086  if (filter == null) {
1087  return getTrueLiteral();
1088  } else {
1089  result = filter.getSQLWhere(this);
1090  }
1091 
1092  return result;
1093  }
1094 
1095  private String getTrueLiteral() {
1096  switch (caseDB.getDatabaseType()) {
1097  case POSTGRESQL:
1098  return "TRUE";//NON-NLS
1099  case SQLITE:
1100  return "1";//NON-NLS
1101  default:
1102  throw new UnsupportedOperationException("Unsupported DB type: " + caseDB.getDatabaseType().name());//NON-NLS
1103 
1104  }
1105  }
1106 
1111  final static public class TimelineEventAddedEvent {
1112 
1113  private final TimelineEvent addedEvent;
1114 
1116  return addedEvent;
1117  }
1118 
1120  this.addedEvent = event;
1121  }
1122  }
1123 
1131  @FunctionalInterface
1132  private interface TSKCoreCheckedFunction<I, O> {
1133 
1134  O apply(I input) throws TskCoreException;
1135  }
1136 }
List< Long > getEventIDs(Interval timeRange, TimelineFilter.RootFilter filter)
TimelineEvent getEventById(long eventID)
ImmutableList< TimelineEventType > getEventTypes()
Interval getSpanningInterval(Interval timeRange, TimelineFilter.RootFilter filter, DateTimeZone timeZone)
Set< Long > getEventIDsForContent(Content content, boolean includeDerivedArtifacts)
Interval getSpanningInterval(Collection< Long > eventIDs)
Set< Long > updateEventsForContentTagAdded(Content content)
List< BlackboardArtifactTag > getBlackboardArtifactTagsByArtifact(BlackboardArtifact artifact)
SortedSet<?extends TimelineEventType > getChildren()
Set< Long > updateEventsForContentTagDeleted(Content content)
Set< Long > updateEventsForHashSetHit(Content content)
static String escapeSingleQuotes(String text)
Set< Long > updateEventsForArtifactTagDeleted(BlackboardArtifact artifact)
Map< TimelineEventType, Long > countEventsByType(Long startTime, Long endTime, TimelineFilter.RootFilter filter, TimelineEventType.HierarchyLevel typeHierachyLevel)
List< Long > getEventIDsForArtifact(BlackboardArtifact artifact)
List< TimelineEvent > getEvents(Interval timeRange, TimelineFilter.RootFilter filter)
List< ContentTag > getContentTagsByContent(Content content)
Optional< TimelineEventType > getEventType(long eventTypeID)
Set< Long > updateEventsForArtifactTagAdded(BlackboardArtifact artifact)

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