Sleuth Kit Java Bindings (JNI)  4.10.0
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-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 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.time.Instant;
29 import java.util.ArrayList;
30 import java.util.Collection;
31 import java.util.Collections;
32 import java.util.HashMap;
33 import java.util.HashSet;
34 import java.util.List;
35 import java.util.Map;
36 import java.util.Objects;
37 import static java.util.Objects.isNull;
38 import java.util.Optional;
39 import java.util.Set;
40 import java.util.logging.Level;
41 import java.util.logging.Logger;
42 import java.util.stream.Collectors;
43 import org.joda.time.DateTimeZone;
44 import org.joda.time.Interval;
47 import static org.sleuthkit.datamodel.CollectionUtils.isNotEmpty;
50 import static org.sleuthkit.datamodel.StringUtils.buildCSVString;
51 
55 public final class TimelineManager {
56 
57  private static final Logger logger = Logger.getLogger(TimelineManager.class.getName());
58 
62  private static final ImmutableList<TimelineEventType> ROOT_CATEGORY_AND_FILESYSTEM_TYPES
63  = ImmutableList.of(
72 
79  private static final ImmutableList<TimelineEventType> PREDEFINED_EVENT_TYPES
80  = new ImmutableList.Builder<TimelineEventType>()
85  .build();
86 
87  private final SleuthkitCase caseDB;
88 
92  private static final Long MAX_TIMESTAMP_TO_ADD = Instant.now().getEpochSecond() + 394200000;
93 
97  private final Map<Long, TimelineEventType> eventTypeIDMap = new HashMap<>();
98 
109  this.caseDB = caseDB;
110 
111  //initialize root and base event types, these are added to the DB in c++ land
112  ROOT_CATEGORY_AND_FILESYSTEM_TYPES.forEach(eventType -> eventTypeIDMap.put(eventType.getTypeID(), eventType));
113 
114  //initialize the other event types that aren't added in c++
116  try (final CaseDbConnection con = caseDB.getConnection();
117  final Statement statement = con.createStatement()) {
118  for (TimelineEventType type : PREDEFINED_EVENT_TYPES) {
119  con.executeUpdate(statement,
120  insertOrIgnore(" INTO tsk_event_types(event_type_id, display_name, super_type_id) "
121  + "VALUES( " + type.getTypeID() + ", '"
122  + escapeSingleQuotes(type.getDisplayName()) + "',"
123  + type.getParent().getTypeID()
124  + ")")); //NON-NLS
125  eventTypeIDMap.put(type.getTypeID(), type);
126  }
127  } catch (SQLException ex) {
128  throw new TskCoreException("Failed to initialize timeline event types", ex); // NON-NLS
129  } finally {
131  }
132  }
133 
145  public Interval getSpanningInterval(Collection<Long> eventIDs) throws TskCoreException {
146  if (eventIDs.isEmpty()) {
147  return null;
148  }
149  final String query = "SELECT Min(time) as minTime, Max(time) as maxTime FROM tsk_events WHERE event_id IN (" + buildCSVString(eventIDs) + ")"; //NON-NLS
151  try (CaseDbConnection con = caseDB.getConnection();
152  Statement stmt = con.createStatement();
153  ResultSet results = stmt.executeQuery(query);) {
154  if (results.next()) {
155  return new Interval(results.getLong("minTime") * 1000, (results.getLong("maxTime") + 1) * 1000, DateTimeZone.UTC); // NON-NLS
156  }
157  } catch (SQLException ex) {
158  throw new TskCoreException("Error executing get spanning interval query: " + query, ex); // NON-NLS
159  } finally {
161  }
162  return null;
163  }
164 
177  public Interval getSpanningInterval(Interval timeRange, TimelineFilter.RootFilter filter, DateTimeZone timeZone) throws TskCoreException {
178  long start = timeRange.getStartMillis() / 1000;
179  long end = timeRange.getEndMillis() / 1000;
180  String sqlWhere = getSQLWhere(filter);
181  String augmentedEventsTablesSQL = getAugmentedEventsTablesSQL(filter);
182  String queryString = " SELECT (SELECT Max(time) FROM " + augmentedEventsTablesSQL
183  + " WHERE time <=" + start + " AND " + sqlWhere + ") AS start,"
184  + " (SELECT Min(time) FROM " + augmentedEventsTablesSQL
185  + " WHERE time >= " + end + " AND " + sqlWhere + ") AS end";//NON-NLS
187  try (CaseDbConnection con = caseDB.getConnection();
188  Statement stmt = con.createStatement(); //can't use prepared statement because of complex where clause
189  ResultSet results = stmt.executeQuery(queryString);) {
190 
191  if (results.next()) {
192  long start2 = results.getLong("start"); // NON-NLS
193  long end2 = results.getLong("end"); // NON-NLS
194 
195  if (end2 == 0) {
196  end2 = getMaxEventTime();
197  }
198  return new Interval(start2 * 1000, (end2 + 1) * 1000, timeZone);
199  }
200  } catch (SQLException ex) {
201  throw new TskCoreException("Failed to get MIN time.", ex); // NON-NLS
202  } finally {
204  }
205  return null;
206  }
207 
217  public TimelineEvent getEventById(long eventID) throws TskCoreException {
218  String sql = "SELECT * FROM " + getAugmentedEventsTablesSQL(false) + " WHERE event_id = " + eventID;
220  try (CaseDbConnection con = caseDB.getConnection();
221  Statement stmt = con.createStatement();) {
222  try (ResultSet results = stmt.executeQuery(sql);) {
223  if (results.next()) {
224  int typeID = results.getInt("event_type_id");
225  TimelineEventType type = getEventType(typeID).orElseThrow(() -> newEventTypeMappingException(typeID)); //NON-NLS
226  return new TimelineEvent(eventID,
227  results.getLong("data_source_obj_id"),
228  results.getLong("content_obj_id"),
229  results.getLong("artifact_id"),
230  results.getLong("time"),
231  type, results.getString("full_description"),
232  results.getString("med_description"),
233  results.getString("short_description"),
234  intToBoolean(results.getInt("hash_hit")),
235  intToBoolean(results.getInt("tagged")));
236  }
237  }
238  } catch (SQLException sqlEx) {
239  throw new TskCoreException("Error while executing query " + sql, sqlEx); // NON-NLS
240  } finally {
242  }
243  return null;
244  }
245 
257  public List<Long> getEventIDs(Interval timeRange, TimelineFilter.RootFilter filter) throws TskCoreException {
258  Long startTime = timeRange.getStartMillis() / 1000;
259  Long endTime = timeRange.getEndMillis() / 1000;
260 
261  if (Objects.equals(startTime, endTime)) {
262  endTime++; //make sure end is at least 1 millisecond after start
263  }
264 
265  ArrayList<Long> resultIDs = new ArrayList<>();
266 
267  String query = "SELECT tsk_events.event_id AS event_id FROM " + getAugmentedEventsTablesSQL(filter)
268  + " WHERE time >= " + startTime + " AND time <" + endTime + " AND " + getSQLWhere(filter) + " ORDER BY time ASC"; // NON-NLS
270  try (CaseDbConnection con = caseDB.getConnection();
271  Statement stmt = con.createStatement();
272  ResultSet results = stmt.executeQuery(query);) {
273  while (results.next()) {
274  resultIDs.add(results.getLong("event_id")); //NON-NLS
275  }
276 
277  } catch (SQLException sqlEx) {
278  throw new TskCoreException("Error while executing query " + query, sqlEx); // NON-NLS
279  } finally {
281  }
282 
283  return resultIDs;
284  }
285 
294  public Long getMaxEventTime() throws TskCoreException {
296  try (CaseDbConnection con = caseDB.getConnection();
297  Statement stms = con.createStatement();
298  ResultSet results = stms.executeQuery(STATEMENTS.GET_MAX_TIME.getSQL());) {
299  if (results.next()) {
300  return results.getLong("max"); // NON-NLS
301  }
302  } catch (SQLException ex) {
303  throw new TskCoreException("Error while executing query " + STATEMENTS.GET_MAX_TIME.getSQL(), ex); // NON-NLS
304  } finally {
306  }
307  return -1l;
308  }
309 
318  public Long getMinEventTime() throws TskCoreException {
320  try (CaseDbConnection con = caseDB.getConnection();
321  Statement stms = con.createStatement();
322  ResultSet results = stms.executeQuery(STATEMENTS.GET_MIN_TIME.getSQL());) {
323  if (results.next()) {
324  return results.getLong("min"); // NON-NLS
325  }
326  } catch (SQLException ex) {
327  throw new TskCoreException("Error while executing query " + STATEMENTS.GET_MAX_TIME.getSQL(), ex); // NON-NLS
328  } finally {
330  }
331  return -1l;
332  }
333 
342  public Optional<TimelineEventType> getEventType(long eventTypeID) {
343  return Optional.ofNullable(eventTypeIDMap.get(eventTypeID));
344  }
345 
351  public ImmutableList<TimelineEventType> getEventTypes() {
352  return ImmutableList.copyOf(eventTypeIDMap.values());
353  }
354 
355  private String insertOrIgnore(String query) {
356  switch (caseDB.getDatabaseType()) {
357  case POSTGRESQL:
358  return " INSERT " + query + " ON CONFLICT DO NOTHING "; //NON-NLS
359  case SQLITE:
360  return " INSERT OR IGNORE " + query; //NON-NLS
361  default:
362  throw new UnsupportedOperationException("Unsupported DB type: " + caseDB.getDatabaseType().name());
363  }
364  }
365 
369  private enum STATEMENTS {
370 
371  GET_MAX_TIME("SELECT Max(time) AS max FROM tsk_events"), // NON-NLS
372  GET_MIN_TIME("SELECT Min(time) AS min FROM tsk_events"); // NON-NLS
373 
374  private final String sql;
375 
376  private STATEMENTS(String sql) {
377  this.sql = sql;
378  }
379 
380  String getSQL() {
381  return sql;
382  }
383  }
384 
395  public List<Long> getEventIDsForArtifact(BlackboardArtifact artifact) throws TskCoreException {
396  ArrayList<Long> eventIDs = new ArrayList<>();
397 
398  String query
399  = "SELECT event_id FROM tsk_events "
400  + " LEFT JOIN tsk_event_descriptions on ( tsk_events.event_description_id = tsk_event_descriptions.event_description_id ) "
401  + " WHERE artifact_id = " + artifact.getArtifactID();
403  try (CaseDbConnection con = caseDB.getConnection();
404  Statement stmt = con.createStatement();
405  ResultSet results = stmt.executeQuery(query);) {
406  while (results.next()) {
407  eventIDs.add(results.getLong("event_id"));//NON-NLS
408  }
409  } catch (SQLException ex) {
410  throw new TskCoreException("Error executing getEventIDsForArtifact query.", ex); // NON-NLS
411  } finally {
413  }
414  return eventIDs;
415  }
416 
430  public Set<Long> getEventIDsForContent(Content content, boolean includeDerivedArtifacts) throws TskCoreException {
432  try (CaseDbConnection conn = caseDB.getConnection()) {
433  return getEventAndDescriptionIDs(conn, content.getId(), includeDerivedArtifacts).keySet();
434  } finally {
436  }
437  }
438 
456  private long addEventDescription(long dataSourceObjId, long fileObjId, Long artifactID,
457  String fullDescription, String medDescription, String shortDescription,
458  boolean hasHashHits, boolean tagged, CaseDbConnection connection) throws TskCoreException {
459  String insertDescriptionSql
460  = "INSERT INTO tsk_event_descriptions ( "
461  + "data_source_obj_id, content_obj_id, artifact_id, "
462  + " full_description, med_description, short_description, "
463  + " hash_hit, tagged "
464  + " ) VALUES ("
465  + dataSourceObjId + ","
466  + fileObjId + ","
467  + Objects.toString(artifactID, "NULL") + ","
468  + quotePreservingNull(fullDescription) + ","
469  + quotePreservingNull(medDescription) + ","
470  + quotePreservingNull(shortDescription) + ", "
471  + booleanToInt(hasHashHits) + ","
472  + booleanToInt(tagged)
473  + " )";
474 
476  try (Statement insertDescriptionStmt = connection.createStatement()) {
477  connection.executeUpdate(insertDescriptionStmt, insertDescriptionSql, PreparedStatement.RETURN_GENERATED_KEYS);
478  try (ResultSet generatedKeys = insertDescriptionStmt.getGeneratedKeys()) {
479  generatedKeys.next();
480  return generatedKeys.getLong(1);
481  }
482  } catch (SQLException ex) {
483  throw new TskCoreException("Failed to insert event description.", ex); // NON-NLS
484  } finally {
486  }
487  }
488 
489  Collection<TimelineEvent> addEventsForNewFile(AbstractFile file, CaseDbConnection connection) throws TskCoreException {
490  Set<TimelineEvent> events = addEventsForNewFileQuiet(file, connection);
491  events.stream()
492  .map(TimelineEventAddedEvent::new)
493  .forEach(caseDB::fireTSKEvent);
494 
495  return events;
496  }
497 
512  Set<TimelineEvent> addEventsForNewFileQuiet(AbstractFile file, CaseDbConnection connection) throws TskCoreException {
513  //gather time stamps into map
514  Map<TimelineEventType, Long> timeMap = ImmutableMap.of(TimelineEventType.FILE_CREATED, file.getCrtime(),
515  TimelineEventType.FILE_ACCESSED, file.getAtime(),
516  TimelineEventType.FILE_CHANGED, file.getCtime(),
517  TimelineEventType.FILE_MODIFIED, file.getMtime());
518 
519  /*
520  * If there are no legitimate ( greater than zero ) time stamps skip the
521  * rest of the event generation.
522  */
523  if (Collections.max(timeMap.values()) <= 0) {
524  return Collections.emptySet();
525  }
526 
527  String description = file.getParentPath() + file.getName();
528  long fileObjId = file.getId();
529  Set<TimelineEvent> events = new HashSet<>();
531  try {
532  long descriptionID = addEventDescription(file.getDataSourceObjectId(), fileObjId, null,
533  description, null, null, false, false, connection);
534 
535  for (Map.Entry<TimelineEventType, Long> timeEntry : timeMap.entrySet()) {
536  Long time = timeEntry.getValue();
537  if (time > 0 && time < MAX_TIMESTAMP_TO_ADD) {// if the time is legitimate ( greater than zero and less then 12 years from current date) insert it
538  TimelineEventType type = timeEntry.getKey();
539  long eventID = addEventWithExistingDescription(time, type, descriptionID, connection);
540 
541  /*
542  * Last two flags indicating hasTags and hasHashHits are
543  * both set to false with the assumption that this is not
544  * possible for a new file. See JIRA-5407
545  */
546  events.add(new TimelineEvent(eventID, descriptionID, fileObjId, null, time, type,
547  description, null, null, false, false));
548  } else {
549  if (time >= MAX_TIMESTAMP_TO_ADD) {
550  logger.log(Level.WARNING, String.format("Date/Time discarded from Timeline for %s for file %s with Id %d", timeEntry.getKey().getDisplayName(), file.getParentPath() + file.getName(), file.getId()));
551  }
552  }
553  }
554 
555  } finally {
557  }
558 
559  return events;
560  }
561 
575  Set<TimelineEvent> addArtifactEvents(BlackboardArtifact artifact) throws TskCoreException {
576  Set<TimelineEvent> newEvents = new HashSet<>();
577 
578  /*
579  * If the artifact is a TSK_TL_EVENT, use the TSK_TL_EVENT_TYPE
580  * attribute to determine its event type, but give it a generic
581  * description.
582  */
583  if (artifact.getArtifactTypeID() == TSK_TL_EVENT.getTypeID()) {
584  TimelineEventType eventType;//the type of the event to add.
585  BlackboardAttribute attribute = artifact.getAttribute(new BlackboardAttribute.Type(TSK_TL_EVENT_TYPE));
586  if (attribute == null) {
587  eventType = TimelineEventType.OTHER;
588  } else {
589  long eventTypeID = attribute.getValueLong();
590  eventType = eventTypeIDMap.getOrDefault(eventTypeID, TimelineEventType.OTHER);
591  }
592 
593  // @@@ This casting is risky if we change class hierarchy, but was expedient. Should move parsing to another class
594  addArtifactEvent(((TimelineEventArtifactTypeImpl) TimelineEventType.OTHER)::makeEventDescription, eventType, artifact)
595  .ifPresent(newEvents::add);
596  } else {
597  /*
598  * If there are any event types configured to make descriptions
599  * automatically, use those.
600  */
601  Set<TimelineEventArtifactTypeImpl> eventTypesForArtifact = eventTypeIDMap.values().stream()
602  .filter(TimelineEventArtifactTypeImpl.class::isInstance)
603  .map(TimelineEventArtifactTypeImpl.class::cast)
604  .filter(eventType -> eventType.getArtifactTypeID() == artifact.getArtifactTypeID())
605  .collect(Collectors.toSet());
606 
607  for (TimelineEventArtifactTypeImpl eventType : eventTypesForArtifact) {
608  addArtifactEvent(eventType::makeEventDescription, eventType, artifact)
609  .ifPresent(newEvents::add);
610  }
611  }
612  newEvents.stream()
613  .map(TimelineEventAddedEvent::new)
614  .forEach(caseDB::fireTSKEvent);
615  return newEvents;
616  }
617 
635  private Optional<TimelineEvent> addArtifactEvent(TSKCoreCheckedFunction<BlackboardArtifact, TimelineEventDescriptionWithTime> payloadExtractor,
636  TimelineEventType eventType, BlackboardArtifact artifact) throws TskCoreException {
637  TimelineEventDescriptionWithTime eventPayload = payloadExtractor.apply(artifact);
638  if (eventPayload == null) {
639  return Optional.empty();
640  }
641  long time = eventPayload.getTime();
642  // if the time is legitimate ( greater than or equal to zero or less than or equal to 12 years from present time) insert it into the db
643  if (time <= 0 || time >= MAX_TIMESTAMP_TO_ADD) {
644  if (time >= MAX_TIMESTAMP_TO_ADD) {
645  logger.log(Level.WARNING, String.format("Date/Time discarded from Timeline for %s for artifact %s with id %d", artifact.getDisplayName(), eventPayload.getDescription(TimelineLevelOfDetail.HIGH), artifact.getId()));
646  }
647  return Optional.empty();
648  }
649  String fullDescription = eventPayload.getDescription(TimelineLevelOfDetail.HIGH);
650  String medDescription = eventPayload.getDescription(TimelineLevelOfDetail.MEDIUM);
651  String shortDescription = eventPayload.getDescription(TimelineLevelOfDetail.LOW);
652  long artifactID = artifact.getArtifactID();
653  long fileObjId = artifact.getObjectID();
654  long dataSourceObjectID = artifact.getDataSourceObjectID();
655 
656  AbstractFile file = caseDB.getAbstractFileById(fileObjId);
657  boolean hasHashHits = false;
658  // file will be null if source was data source or some non-file
659  if (file != null) {
660  hasHashHits = isNotEmpty(file.getHashSetNames());
661  }
662  boolean tagged = isNotEmpty(caseDB.getBlackboardArtifactTagsByArtifact(artifact));
663 
664  TimelineEvent event;
666  try (CaseDbConnection connection = caseDB.getConnection();) {
667 
668  long descriptionID = addEventDescription(dataSourceObjectID, fileObjId, artifactID,
669  fullDescription, medDescription, shortDescription,
670  hasHashHits, tagged, connection);
671 
672  long eventID = addEventWithExistingDescription(time, eventType, descriptionID, connection);
673 
674  event = new TimelineEvent(eventID, dataSourceObjectID, fileObjId, artifactID,
675  time, eventType, fullDescription, medDescription, shortDescription,
676  hasHashHits, tagged);
677 
678  } finally {
680  }
681  return Optional.of(event);
682  }
683 
684  private long addEventWithExistingDescription(Long time, TimelineEventType type, long descriptionID, CaseDbConnection connection) throws TskCoreException {
685  String insertEventSql
686  = "INSERT INTO tsk_events ( event_type_id, event_description_id , time) "
687  + " VALUES (" + type.getTypeID() + ", " + descriptionID + ", " + time + ")";
688 
690  try (Statement insertRowStmt = connection.createStatement();) {
691  connection.executeUpdate(insertRowStmt, insertEventSql, PreparedStatement.RETURN_GENERATED_KEYS);
692 
693  try (ResultSet generatedKeys = insertRowStmt.getGeneratedKeys();) {
694  generatedKeys.next();
695  return generatedKeys.getLong(1);
696  }
697  } catch (SQLException ex) {
698  throw new TskCoreException("Failed to insert event for existing description.", ex); // NON-NLS
699  } finally {
701  }
702  }
703 
704  static private String quotePreservingNull(String value) {
705  return isNull(value) ? " NULL " : "'" + escapeSingleQuotes(value) + "'";//NON-NLS
706  }
707 
708  private Map<Long, Long> getEventAndDescriptionIDs(CaseDbConnection conn, long contentObjID, boolean includeArtifacts) throws TskCoreException {
709  return getEventAndDescriptionIDsHelper(conn, contentObjID, (includeArtifacts ? "" : " AND artifact_id IS NULL"));
710  }
711 
712  private Map<Long, Long> getEventAndDescriptionIDs(CaseDbConnection conn, long contentObjID, Long artifactID) throws TskCoreException {
713  return getEventAndDescriptionIDsHelper(conn, contentObjID, " AND artifact_id = " + artifactID);
714  }
715 
716  private Map<Long, Long> getEventAndDescriptionIDsHelper(CaseDbConnection con, long fileObjID, String artifactClause) throws TskCoreException {
717  //map from event_id to the event_description_id for that event.
718  Map<Long, Long> eventIDToDescriptionIDs = new HashMap<>();
719  String sql = "SELECT event_id, tsk_events.event_description_id"
720  + " FROM tsk_events "
721  + " LEFT JOIN tsk_event_descriptions ON ( tsk_events.event_description_id = tsk_event_descriptions.event_description_id )"
722  + " WHERE content_obj_id = " + fileObjID
723  + artifactClause;
724  try (Statement selectStmt = con.createStatement(); ResultSet executeQuery = selectStmt.executeQuery(sql);) {
725  while (executeQuery.next()) {
726  eventIDToDescriptionIDs.put(executeQuery.getLong("event_id"), executeQuery.getLong("event_description_id")); //NON-NLS
727  }
728  } catch (SQLException ex) {
729  throw new TskCoreException("Error getting event description ids for object id = " + fileObjID, ex);
730  }
731  return eventIDToDescriptionIDs;
732  }
733 
750  @Beta
751  public Set<Long> updateEventsForContentTagAdded(Content content) throws TskCoreException {
753  try (CaseDbConnection conn = caseDB.getConnection()) {
754  Map<Long, Long> eventIDs = getEventAndDescriptionIDs(conn, content.getId(), false);
755  updateEventSourceTaggedFlag(conn, eventIDs.values(), 1);
756  return eventIDs.keySet();
757  } finally {
759  }
760  }
761 
779  @Beta
780  public Set<Long> updateEventsForContentTagDeleted(Content content) throws TskCoreException {
782  try (CaseDbConnection conn = caseDB.getConnection()) {
783  if (caseDB.getContentTagsByContent(content).isEmpty()) {
784  Map<Long, Long> eventIDs = getEventAndDescriptionIDs(conn, content.getId(), false);
785  updateEventSourceTaggedFlag(conn, eventIDs.values(), 0);
786  return eventIDs.keySet();
787  } else {
788  return Collections.emptySet();
789  }
790  } finally {
792  }
793  }
794 
808  try (CaseDbConnection conn = caseDB.getConnection()) {
809  Map<Long, Long> eventIDs = getEventAndDescriptionIDs(conn, artifact.getObjectID(), artifact.getArtifactID());
810  updateEventSourceTaggedFlag(conn, eventIDs.values(), 1);
811  return eventIDs.keySet();
812  } finally {
814  }
815  }
816 
831  try (CaseDbConnection conn = caseDB.getConnection()) {
832  if (caseDB.getBlackboardArtifactTagsByArtifact(artifact).isEmpty()) {
833  Map<Long, Long> eventIDs = getEventAndDescriptionIDs(conn, artifact.getObjectID(), artifact.getArtifactID());
834  updateEventSourceTaggedFlag(conn, eventIDs.values(), 0);
835  return eventIDs.keySet();
836  } else {
837  return Collections.emptySet();
838  }
839  } finally {
841  }
842  }
843 
844  private void updateEventSourceTaggedFlag(CaseDbConnection conn, Collection<Long> eventDescriptionIDs, int flagValue) throws TskCoreException {
845  if (eventDescriptionIDs.isEmpty()) {
846  return;
847  }
848 
849  String sql = "UPDATE tsk_event_descriptions SET tagged = " + flagValue + " WHERE event_description_id IN (" + buildCSVString(eventDescriptionIDs) + ")"; //NON-NLS
850  try (Statement updateStatement = conn.createStatement()) {
851  updateStatement.executeUpdate(sql);
852  } catch (SQLException ex) {
853  throw new TskCoreException("Error marking content events tagged: " + sql, ex);//NON-NLS
854  }
855  }
856 
871  public Set<Long> updateEventsForHashSetHit(Content content) throws TskCoreException {
873  try (CaseDbConnection con = caseDB.getConnection(); Statement updateStatement = con.createStatement();) {
874  Map<Long, Long> eventIDs = getEventAndDescriptionIDs(con, content.getId(), true);
875  if (! eventIDs.isEmpty()) {
876  String sql = "UPDATE tsk_event_descriptions SET hash_hit = 1" + " WHERE event_description_id IN (" + buildCSVString(eventIDs.values()) + ")"; //NON-NLS
877  try {
878  updateStatement.executeUpdate(sql); //NON-NLS
879  return eventIDs.keySet();
880  } catch (SQLException ex) {
881  throw new TskCoreException("Error setting hash_hit of events.", ex);//NON-NLS
882  }
883  } else {
884  return eventIDs.keySet();
885  }
886  } catch (SQLException ex) {
887  throw new TskCoreException("Error setting hash_hit of events.", ex);//NON-NLS
888  } finally {
890  }
891  }
892 
893  void rollBackTransaction(SleuthkitCase.CaseDbTransaction trans) throws TskCoreException {
894  trans.rollback();
895  }
896 
916  public Map<TimelineEventType, Long> countEventsByType(Long startTime, Long endTime, TimelineFilter.RootFilter filter, TimelineEventType.HierarchyLevel typeHierachyLevel) throws TskCoreException {
917  long adjustedEndTime = Objects.equals(startTime, endTime) ? endTime + 1 : endTime;
918  //do we want the base or subtype column of the databse
919  String typeColumn = typeColumnHelper(TimelineEventType.HierarchyLevel.EVENT.equals(typeHierachyLevel));
920 
921  String queryString = "SELECT count(DISTINCT tsk_events.event_id) AS count, " + typeColumn//NON-NLS
922  + " FROM " + getAugmentedEventsTablesSQL(filter)//NON-NLS
923  + " WHERE time >= " + startTime + " AND time < " + adjustedEndTime + " AND " + getSQLWhere(filter) // NON-NLS
924  + " GROUP BY " + typeColumn; // NON-NLS
925 
927  try (CaseDbConnection con = caseDB.getConnection();
928  Statement stmt = con.createStatement();
929  ResultSet results = stmt.executeQuery(queryString);) {
930  Map<TimelineEventType, Long> typeMap = new HashMap<>();
931  while (results.next()) {
932  int eventTypeID = results.getInt(typeColumn);
933  TimelineEventType eventType = getEventType(eventTypeID)
934  .orElseThrow(() -> newEventTypeMappingException(eventTypeID));//NON-NLS
935 
936  typeMap.put(eventType, results.getLong("count")); // NON-NLS
937  }
938  return typeMap;
939  } catch (SQLException ex) {
940  throw new TskCoreException("Error getting count of events from db: " + queryString, ex); // NON-NLS
941  } finally {
943  }
944  }
945 
946  private static TskCoreException newEventTypeMappingException(int eventTypeID) {
947  return new TskCoreException("Error mapping event type id " + eventTypeID + " to EventType.");//NON-NLS
948  }
949 
963  static private String getAugmentedEventsTablesSQL(TimelineFilter.RootFilter filter) {
964  TimelineFilter.FileTypesFilter fileTypesFitler = filter.getFileTypesFilter();
965  boolean needsMimeTypes = fileTypesFitler != null && fileTypesFitler.hasSubFilters();
966 
967  return getAugmentedEventsTablesSQL(needsMimeTypes);
968  }
969 
984  static private String getAugmentedEventsTablesSQL(boolean needMimeTypes) {
985  /*
986  * Regarding the timeline event tables schema, note that several columns
987  * in the tsk_event_descriptions table seem, at first glance, to be
988  * attributes of events rather than their descriptions and would appear
989  * to belong in tsk_events table instead. The rationale for putting the
990  * data source object ID, content object ID, artifact ID and the flags
991  * indicating whether or not the event source has a hash set hit or is
992  * tagged were motivated by the fact that these attributes are identical
993  * for each event in a set of file system file MAC time events. The
994  * decision was made to avoid duplication and save space by placing this
995  * data in the tsk_event-descriptions table.
996  */
997  return "( SELECT event_id, time, tsk_event_descriptions.data_source_obj_id, content_obj_id, artifact_id, "
998  + " full_description, med_description, short_description, tsk_events.event_type_id, super_type_id,"
999  + " hash_hit, tagged "
1000  + (needMimeTypes ? ", mime_type" : "")
1001  + " FROM tsk_events "
1002  + " JOIN tsk_event_descriptions ON ( tsk_event_descriptions.event_description_id = tsk_events.event_description_id)"
1003  + " JOIN tsk_event_types ON (tsk_events.event_type_id = tsk_event_types.event_type_id ) "
1004  + (needMimeTypes ? " LEFT OUTER JOIN tsk_files "
1005  + " ON (tsk_event_descriptions.content_obj_id = tsk_files.obj_id)"
1006  : "")
1007  + ") AS tsk_events";
1008  }
1009 
1017  private static int booleanToInt(boolean value) {
1018  return value ? 1 : 0;
1019  }
1020 
1021  private static boolean intToBoolean(int value) {
1022  return value != 0;
1023  }
1024 
1037  public List<TimelineEvent> getEvents(Interval timeRange, TimelineFilter.RootFilter filter) throws TskCoreException {
1038  List<TimelineEvent> events = new ArrayList<>();
1039 
1040  Long startTime = timeRange.getStartMillis() / 1000;
1041  Long endTime = timeRange.getEndMillis() / 1000;
1042 
1043  if (Objects.equals(startTime, endTime)) {
1044  endTime++; //make sure end is at least 1 millisecond after start
1045  }
1046 
1047  if (filter == null) {
1048  return events;
1049  }
1050 
1051  if (endTime < startTime) {
1052  return events;
1053  }
1054 
1055  //build dynamic parts of query
1056  String querySql = "SELECT time, content_obj_id, data_source_obj_id, artifact_id, " // NON-NLS
1057  + " event_id, " //NON-NLS
1058  + " hash_hit, " //NON-NLS
1059  + " tagged, " //NON-NLS
1060  + " event_type_id, super_type_id, "
1061  + " full_description, med_description, short_description " // NON-NLS
1062  + " FROM " + getAugmentedEventsTablesSQL(filter) // NON-NLS
1063  + " WHERE time >= " + startTime + " AND time < " + endTime + " AND " + getSQLWhere(filter) // NON-NLS
1064  + " ORDER BY time"; // NON-NLS
1065 
1067  try (CaseDbConnection con = caseDB.getConnection();
1068  Statement stmt = con.createStatement();
1069  ResultSet resultSet = stmt.executeQuery(querySql);) {
1070 
1071  while (resultSet.next()) {
1072  int eventTypeID = resultSet.getInt("event_type_id");
1073  TimelineEventType eventType = getEventType(eventTypeID).orElseThrow(()
1074  -> new TskCoreException("Error mapping event type id " + eventTypeID + "to EventType."));//NON-NLS
1075 
1076  TimelineEvent event = new TimelineEvent(
1077  resultSet.getLong("event_id"), // NON-NLS
1078  resultSet.getLong("data_source_obj_id"), // NON-NLS
1079  resultSet.getLong("content_obj_id"), // NON-NLS
1080  resultSet.getLong("artifact_id"), // NON-NLS
1081  resultSet.getLong("time"), // NON-NLS
1082  eventType,
1083  resultSet.getString("full_description"), // NON-NLS
1084  resultSet.getString("med_description"), // NON-NLS
1085  resultSet.getString("short_description"), // NON-NLS
1086  resultSet.getInt("hash_hit") != 0, //NON-NLS
1087  resultSet.getInt("tagged") != 0);
1088 
1089  events.add(event);
1090  }
1091 
1092  } catch (SQLException ex) {
1093  throw new TskCoreException("Error getting events from db: " + querySql, ex); // NON-NLS
1094  } finally {
1096  }
1097 
1098  return events;
1099  }
1100 
1108  private static String typeColumnHelper(final boolean useSubTypes) {
1109  return useSubTypes ? "event_type_id" : "super_type_id"; //NON-NLS
1110  }
1111 
1120  String getSQLWhere(TimelineFilter.RootFilter filter) {
1121 
1122  String result;
1123  if (filter == null) {
1124  return getTrueLiteral();
1125  } else {
1126  result = filter.getSQLWhere(this);
1127  }
1128 
1129  return result;
1130  }
1131 
1132  private String getTrueLiteral() {
1133  switch (caseDB.getDatabaseType()) {
1134  case POSTGRESQL:
1135  return "TRUE";//NON-NLS
1136  case SQLITE:
1137  return "1";//NON-NLS
1138  default:
1139  throw new UnsupportedOperationException("Unsupported DB type: " + caseDB.getDatabaseType().name());//NON-NLS
1140 
1141  }
1142  }
1143 
1148  final static public class TimelineEventAddedEvent {
1149 
1150  private final TimelineEvent addedEvent;
1151 
1153  return addedEvent;
1154  }
1155 
1157  this.addedEvent = event;
1158  }
1159  }
1160 
1168  @FunctionalInterface
1169  private interface TSKCoreCheckedFunction<I, O> {
1170 
1171  O apply(I input) throws TskCoreException;
1172  }
1173 }
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-2020 Brian Carrier. (carrier -at- sleuthkit -dot- org)
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.