19 package org.sleuthkit.datamodel;
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.sql.Types;
29 import java.time.Instant;
30 import java.util.ArrayList;
31 import java.util.Collection;
32 import java.util.Collections;
33 import java.util.HashMap;
34 import java.util.HashSet;
35 import java.util.List;
37 import java.util.Objects;
38 import java.util.Optional;
40 import java.util.logging.Level;
41 import java.util.logging.Logger;
42 import java.util.stream.Collectors;
43 import java.util.stream.Stream;
44 import org.joda.time.DateTimeZone;
45 import org.joda.time.Interval;
58 private static final Logger logger = Logger.getLogger(
TimelineManager.class.getName());
63 private static final ImmutableList<TimelineEventType> ROOT_CATEGORY_AND_FILESYSTEM_TYPES
80 private static final ImmutableList<TimelineEventType> PREDEFINED_EVENT_TYPES
90 .map(artType -> artType.getTypeID())
91 .collect(Collectors.toSet());
99 private static final Long MAX_TIMESTAMP_TO_ADD = Instant.now().getEpochSecond() + 394200000;
104 private final Map<Long, TimelineEventType> eventTypeIDMap =
new HashMap<>();
116 this.caseDB = caseDB;
119 ROOT_CATEGORY_AND_FILESYSTEM_TYPES.forEach(eventType -> eventTypeIDMap.put(eventType.getTypeID(), eventType));
123 try (
final CaseDbConnection con = caseDB.getConnection();
124 final Statement statement = con.createStatement()) {
126 con.executeUpdate(statement,
127 insertOrIgnore(
" INTO tsk_event_types(event_type_id, display_name, super_type_id) "
128 +
"VALUES( " + type.getTypeID() +
", '"
129 + escapeSingleQuotes(type.getDisplayName()) +
"',"
130 + type.getParent().getTypeID()
132 eventTypeIDMap.put(type.getTypeID(), type);
134 }
catch (SQLException ex) {
135 throw new TskCoreException(
"Failed to initialize timeline event types", ex);
153 if (eventIDs.isEmpty()) {
156 final String query =
"SELECT Min(time) as minTime, Max(time) as maxTime FROM tsk_events WHERE event_id IN (" + buildCSVString(eventIDs) +
")";
158 try (CaseDbConnection con = caseDB.getConnection();
159 Statement stmt = con.createStatement();
160 ResultSet results = stmt.executeQuery(query);) {
161 if (results.next()) {
162 return new Interval(results.getLong(
"minTime") * 1000, (results.getLong(
"maxTime") + 1) * 1000, DateTimeZone.UTC);
164 }
catch (SQLException ex) {
165 throw new TskCoreException(
"Error executing get spanning interval query: " + query, ex);
185 long start = timeRange.getStartMillis() / 1000;
186 long end = timeRange.getEndMillis() / 1000;
187 String sqlWhere = getSQLWhere(filter);
188 String augmentedEventsTablesSQL = getAugmentedEventsTablesSQL(filter);
189 String queryString =
" SELECT (SELECT Max(time) FROM " + augmentedEventsTablesSQL
190 +
" WHERE time <=" + start +
" AND " + sqlWhere +
") AS start,"
191 +
" (SELECT Min(time) FROM " + augmentedEventsTablesSQL
192 +
" WHERE time >= " + end +
" AND " + sqlWhere +
") AS end";
194 try (CaseDbConnection con = caseDB.getConnection();
195 Statement stmt = con.createStatement();
196 ResultSet results = stmt.executeQuery(queryString);) {
198 if (results.next()) {
199 long start2 = results.getLong(
"start");
200 long end2 = results.getLong(
"end");
205 return new Interval(start2 * 1000, (end2 + 1) * 1000, timeZone);
207 }
catch (SQLException ex) {
225 String sql =
"SELECT * FROM " + getAugmentedEventsTablesSQL(
false) +
" WHERE event_id = " + eventID;
227 try (CaseDbConnection con = caseDB.getConnection();
228 Statement stmt = con.createStatement();) {
229 try (ResultSet results = stmt.executeQuery(sql);) {
230 if (results.next()) {
231 int typeID = results.getInt(
"event_type_id");
234 results.getLong(
"data_source_obj_id"),
235 results.getLong(
"content_obj_id"),
236 results.getLong(
"artifact_id"),
237 results.getLong(
"time"),
238 type, results.getString(
"full_description"),
239 results.getString(
"med_description"),
240 results.getString(
"short_description"),
241 intToBoolean(results.getInt(
"hash_hit")),
242 intToBoolean(results.getInt(
"tagged")));
245 }
catch (SQLException sqlEx) {
265 Long startTime = timeRange.getStartMillis() / 1000;
266 Long endTime = timeRange.getEndMillis() / 1000;
268 if (Objects.equals(startTime, endTime)) {
272 ArrayList<Long> resultIDs =
new ArrayList<>();
274 String query =
"SELECT tsk_events.event_id AS event_id FROM " + getAugmentedEventsTablesSQL(filter)
275 +
" WHERE time >= " + startTime +
" AND time <" + endTime +
" AND " + getSQLWhere(filter) +
" ORDER BY time ASC";
277 try (CaseDbConnection con = caseDB.getConnection();
278 Statement stmt = con.createStatement();
279 ResultSet results = stmt.executeQuery(query);) {
280 while (results.next()) {
281 resultIDs.add(results.getLong(
"event_id"));
284 }
catch (SQLException sqlEx) {
285 throw new TskCoreException(
"Error while executing query " + query, sqlEx);
303 try (CaseDbConnection con = caseDB.getConnection();
304 Statement stms = con.createStatement();
305 ResultSet results = stms.executeQuery(STATEMENTS.GET_MAX_TIME.getSQL());) {
306 if (results.next()) {
307 return results.getLong(
"max");
309 }
catch (SQLException ex) {
310 throw new TskCoreException(
"Error while executing query " + STATEMENTS.GET_MAX_TIME.getSQL(), ex);
327 try (CaseDbConnection con = caseDB.getConnection();
328 Statement stms = con.createStatement();
329 ResultSet results = stms.executeQuery(STATEMENTS.GET_MIN_TIME.getSQL());) {
330 if (results.next()) {
331 return results.getLong(
"min");
333 }
catch (SQLException ex) {
334 throw new TskCoreException(
"Error while executing query " + STATEMENTS.GET_MAX_TIME.getSQL(), ex);
350 return Optional.ofNullable(eventTypeIDMap.get(eventTypeID));
359 return ImmutableList.copyOf(eventTypeIDMap.values());
362 private String insertOrIgnore(String query) {
365 return " INSERT " + query +
" ON CONFLICT DO NOTHING ";
367 return " INSERT OR IGNORE " + query;
369 throw new UnsupportedOperationException(
"Unsupported DB type: " + caseDB.
getDatabaseType().name());
376 private enum STATEMENTS {
378 GET_MAX_TIME(
"SELECT Max(time) AS max FROM tsk_events"),
379 GET_MIN_TIME(
"SELECT Min(time) AS min FROM tsk_events");
381 private final String sql;
383 private STATEMENTS(String sql) {
403 ArrayList<Long> eventIDs =
new ArrayList<>();
406 =
"SELECT event_id FROM tsk_events "
407 +
" LEFT JOIN tsk_event_descriptions on ( tsk_events.event_description_id = tsk_event_descriptions.event_description_id ) "
408 +
" WHERE artifact_id = " + artifact.getArtifactID();
410 try (CaseDbConnection con = caseDB.getConnection();
411 Statement stmt = con.createStatement();
412 ResultSet results = stmt.executeQuery(query);) {
413 while (results.next()) {
414 eventIDs.add(results.getLong(
"event_id"));
416 }
catch (SQLException ex) {
417 throw new TskCoreException(
"Error executing getEventIDsForArtifact query.", ex);
439 try (CaseDbConnection conn = caseDB.getConnection()) {
440 return getEventAndDescriptionIDs(conn, content.getId(), includeDerivedArtifacts).keySet();
464 private long addEventDescription(
long dataSourceObjId,
long fileObjId, Long artifactID,
465 String fullDescription, String medDescription, String shortDescription,
466 boolean hasHashHits,
boolean tagged, CaseDbConnection connection)
throws TskCoreException, DuplicateException {
467 String tableValuesClause
468 =
"tsk_event_descriptions ( "
469 +
"data_source_obj_id, content_obj_id, artifact_id, "
470 +
" full_description, med_description, short_description, "
471 +
" hash_hit, tagged "
473 +
"(?, ?, ?, ?, ?, ?, ?, ?)";
475 String insertDescriptionSql = getSqlIgnoreConflict(tableValuesClause);
478 try (PreparedStatement insertDescriptionStmt = connection.prepareStatement(insertDescriptionSql, PreparedStatement.RETURN_GENERATED_KEYS)) {
479 insertDescriptionStmt.clearParameters();
480 insertDescriptionStmt.setLong(1, dataSourceObjId);
481 insertDescriptionStmt.setLong(2, fileObjId);
483 if (artifactID == null) {
484 insertDescriptionStmt.setNull(3, Types.INTEGER);
486 insertDescriptionStmt.setLong(3, artifactID);
489 insertDescriptionStmt.setString(4, fullDescription);
490 insertDescriptionStmt.setString(5, medDescription);
491 insertDescriptionStmt.setString(6, shortDescription);
492 insertDescriptionStmt.setInt(7, booleanToInt(hasHashHits));
493 insertDescriptionStmt.setInt(8, booleanToInt(tagged));
494 int row = insertDescriptionStmt.executeUpdate();
498 throw new DuplicateException(String.format(
499 "An event description already exists for [fullDescription: %s, contentId: %d, artifactId: %s]",
500 fullDescription == null ?
"<null>" : fullDescription,
502 artifactID == null ?
"<null>" : Long.toString(artifactID)));
505 try (ResultSet generatedKeys = insertDescriptionStmt.getGeneratedKeys()) {
506 if (generatedKeys.next()) {
507 return generatedKeys.getLong(1);
509 throw new DuplicateException(String.format(
510 "An event description already exists for [fullDescription: %s, contentId: %d, artifactId: %s]",
511 fullDescription == null ?
"<null>" : fullDescription,
513 artifactID == null ?
"<null>" : Long.toString(artifactID)));
516 }
catch (SQLException ex) {
517 throw new TskCoreException(
"Failed to insert event description.", ex);
523 Collection<TimelineEvent> addEventsForNewFile(AbstractFile file, CaseDbConnection connection)
throws TskCoreException {
524 Set<TimelineEvent> events = addEventsForNewFileQuiet(file, connection);
526 .map(TimelineEventAddedEvent::new)
527 .forEach(caseDB::fireTSKEvent);
546 Set<TimelineEvent> addEventsForNewFileQuiet(AbstractFile file, CaseDbConnection connection)
throws TskCoreException {
548 Map<TimelineEventType, Long> timeMap = ImmutableMap.of(TimelineEventType.FILE_CREATED, file.getCrtime(),
549 TimelineEventType.FILE_ACCESSED, file.getAtime(),
550 TimelineEventType.FILE_CHANGED, file.getCtime(),
551 TimelineEventType.FILE_MODIFIED, file.getMtime());
557 if (Collections.max(timeMap.values()) <= 0) {
558 return Collections.emptySet();
561 String description = file.getParentPath() + file.getName();
562 long fileObjId = file.getId();
563 Set<TimelineEvent> events =
new HashSet<>();
566 long descriptionID = addEventDescription(file.getDataSourceObjectId(), fileObjId, null,
567 description, null, null,
false,
false, connection);
569 for (Map.Entry<TimelineEventType, Long> timeEntry : timeMap.entrySet()) {
570 Long time = timeEntry.getValue();
571 if (time > 0 && time < MAX_TIMESTAMP_TO_ADD) {
572 TimelineEventType type = timeEntry.getKey();
573 long eventID = addEventWithExistingDescription(time, type, descriptionID, connection);
580 events.add(
new TimelineEvent(eventID, descriptionID, fileObjId, null, time, type,
581 description, null, null,
false,
false));
583 if (time >= MAX_TIMESTAMP_TO_ADD) {
584 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()));
588 }
catch (DuplicateException dupEx) {
589 logger.log(Level.SEVERE,
"Attempt to make file event duplicate.", dupEx);
610 Set<TimelineEvent> addArtifactEvents(BlackboardArtifact artifact)
throws TskCoreException {
611 Set<TimelineEvent> newEvents =
new HashSet<>();
618 if (artifact.getArtifactTypeID() == TSK_TL_EVENT.getTypeID()) {
619 TimelineEventType eventType;
620 BlackboardAttribute attribute = artifact.getAttribute(
new BlackboardAttribute.Type(TSK_TL_EVENT_TYPE));
621 if (attribute == null) {
622 eventType = TimelineEventType.OTHER;
624 long eventTypeID = attribute.getValueLong();
625 eventType = eventTypeIDMap.getOrDefault(eventTypeID, TimelineEventType.OTHER);
630 addArtifactEvent(((TimelineEventArtifactTypeImpl) TimelineEventType.OTHER).makeEventDescription(artifact), eventType, artifact)
631 .ifPresent(newEvents::add);
632 }
catch (DuplicateException ex) {
633 logger.log(Level.SEVERE, getDuplicateExceptionMessage(artifact,
"Attempt to make a timeline event artifact duplicate"), ex);
640 Set<TimelineEventArtifactTypeImpl> eventTypesForArtifact = eventTypeIDMap.values().stream()
641 .filter(TimelineEventArtifactTypeImpl.class::isInstance)
642 .map(TimelineEventArtifactTypeImpl.class::cast)
643 .filter(eventType -> eventType.getArtifactTypeID() == artifact.getArtifactTypeID())
644 .collect(Collectors.toSet());
646 boolean duplicateExists =
false;
647 for (TimelineEventArtifactTypeImpl eventType : eventTypesForArtifact) {
649 addArtifactEvent(eventType.makeEventDescription(artifact), eventType, artifact)
650 .ifPresent(newEvents::add);
651 }
catch (DuplicateException ex) {
652 duplicateExists =
true;
653 logger.log(Level.SEVERE, getDuplicateExceptionMessage(artifact,
"Attempt to make artifact event duplicate"), ex);
658 if (!duplicateExists && newEvents.isEmpty()) {
660 addOtherEventDesc(artifact).ifPresent(newEvents::add);
661 }
catch (DuplicateException ex) {
662 logger.log(Level.SEVERE, getDuplicateExceptionMessage(artifact,
"Attempt to make 'other' artifact event duplicate"), ex);
667 .map(TimelineEventAddedEvent::new)
668 .forEach(caseDB::fireTSKEvent);
684 private String getDuplicateExceptionMessage(BlackboardArtifact artifact, String error) {
685 String artifactIDStr = null;
686 String sourceStr = null;
688 if (artifact != null) {
689 artifactIDStr = Long.toString(artifact.getId());
692 sourceStr = artifact.getAttributes().stream()
693 .filter(attr -> attr != null && attr.getSources() != null && !attr.getSources().isEmpty())
694 .map(attr -> String.join(
",", attr.getSources()))
697 }
catch (TskCoreException ex) {
698 logger.log(Level.WARNING, String.format(
"Could not fetch artifacts for artifact id: %d.", artifact.getId()), ex);
702 artifactIDStr = (artifactIDStr == null) ?
"<null>" : artifactIDStr;
703 sourceStr = (sourceStr == null) ?
"<null>" : sourceStr;
705 return String.format(
"%s (artifactID=%s, Source=%s).", error, artifactIDStr, sourceStr);
719 private Optional<TimelineEvent> addOtherEventDesc(BlackboardArtifact artifact)
throws TskCoreException, DuplicateException {
720 if (artifact == null) {
721 return Optional.empty();
724 Long timeVal = artifact.getAttributes().stream()
725 .filter((attr) -> attr.getAttributeType().getValueType() == BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.DATETIME)
726 .map(attr -> attr.getValueLong())
730 if (timeVal == null) {
731 return Optional.empty();
734 String description = String.format(
"%s: %d", artifact.getDisplayName(), artifact.getId());
736 TimelineEventDescriptionWithTime evtWDesc =
new TimelineEventDescriptionWithTime(timeVal, description, description, description);
738 TimelineEventType evtType = (ARTIFACT_TYPE_IDS.contains(artifact.getArtifactTypeID()))
739 ? TimelineEventType.OTHER
740 : TimelineEventType.USER_CREATED;
742 return addArtifactEvent(evtWDesc, evtType, artifact);
758 private Optional<TimelineEvent> addArtifactEvent(TimelineEventDescriptionWithTime eventPayload,
759 TimelineEventType eventType, BlackboardArtifact artifact)
throws TskCoreException, DuplicateException {
761 if (eventPayload == null) {
762 return Optional.empty();
764 long time = eventPayload.getTime();
766 if (time <= 0 || time >= MAX_TIMESTAMP_TO_ADD) {
767 if (time >= MAX_TIMESTAMP_TO_ADD) {
768 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()));
770 return Optional.empty();
772 String fullDescription = eventPayload.getDescription(TimelineLevelOfDetail.HIGH);
773 String medDescription = eventPayload.getDescription(TimelineLevelOfDetail.MEDIUM);
774 String shortDescription = eventPayload.getDescription(TimelineLevelOfDetail.LOW);
775 long artifactID = artifact.getArtifactID();
776 long fileObjId = artifact.getObjectID();
777 long dataSourceObjectID = artifact.getDataSourceObjectID();
780 boolean hasHashHits =
false;
783 hasHashHits = isNotEmpty(file.getHashSetNames());
789 try (CaseDbConnection connection = caseDB.getConnection();) {
791 long descriptionID = addEventDescription(dataSourceObjectID, fileObjId, artifactID,
792 fullDescription, medDescription, shortDescription,
793 hasHashHits, tagged, connection);
795 long eventID = addEventWithExistingDescription(time, eventType, descriptionID, connection);
797 event =
new TimelineEvent(eventID, dataSourceObjectID, fileObjId, artifactID,
798 time, eventType, fullDescription, medDescription, shortDescription,
799 hasHashHits, tagged);
804 return Optional.of(event);
807 private long addEventWithExistingDescription(Long time, TimelineEventType type,
long descriptionID, CaseDbConnection connection)
throws TskCoreException, DuplicateException {
808 String tableValuesClause
809 =
"tsk_events ( event_type_id, event_description_id , time) VALUES (?, ?, ?)";
811 String insertEventSql = getSqlIgnoreConflict(tableValuesClause);
814 try (PreparedStatement insertRowStmt = connection.prepareStatement(insertEventSql, Statement.RETURN_GENERATED_KEYS);) {
815 insertRowStmt.clearParameters();
816 insertRowStmt.setLong(1, type.getTypeID());
817 insertRowStmt.setLong(2, descriptionID);
818 insertRowStmt.setLong(3, time);
819 int row = insertRowStmt.executeUpdate();
822 throw new DuplicateException(String.format(
"An event already exists in the event table for this item [time: %s, type: %s, description: %d].",
823 time == null ?
"<null>" : Long.toString(time),
824 type == null ?
"<null>" : type.toString(),
828 try (ResultSet generatedKeys = insertRowStmt.getGeneratedKeys();) {
829 if (generatedKeys.next()) {
830 return generatedKeys.getLong(1);
832 throw new DuplicateException(String.format(
"An event already exists in the event table for this item [time: %s, type: %s, description: %d].",
833 time == null ?
"<null>" : Long.toString(time),
834 type == null ?
"<null>" : type.toString(),
838 }
catch (SQLException ex) {
839 throw new TskCoreException(
"Failed to insert event for existing description.", ex);
845 private Map<Long, Long> getEventAndDescriptionIDs(CaseDbConnection conn,
long contentObjID,
boolean includeArtifacts)
throws TskCoreException {
846 return getEventAndDescriptionIDsHelper(conn, contentObjID, (includeArtifacts ?
"" :
" AND artifact_id IS NULL"));
849 private Map<Long, Long> getEventAndDescriptionIDs(CaseDbConnection conn,
long contentObjID, Long artifactID)
throws TskCoreException {
850 return getEventAndDescriptionIDsHelper(conn, contentObjID,
" AND artifact_id = " + artifactID);
853 private Map<Long, Long> getEventAndDescriptionIDsHelper(CaseDbConnection con,
long fileObjID, String artifactClause)
throws TskCoreException {
855 Map<Long, Long> eventIDToDescriptionIDs =
new HashMap<>();
856 String sql =
"SELECT event_id, tsk_events.event_description_id"
857 +
" FROM tsk_events "
858 +
" LEFT JOIN tsk_event_descriptions ON ( tsk_events.event_description_id = tsk_event_descriptions.event_description_id )"
859 +
" WHERE content_obj_id = " + fileObjID
861 try (Statement selectStmt = con.createStatement(); ResultSet executeQuery = selectStmt.executeQuery(sql);) {
862 while (executeQuery.next()) {
863 eventIDToDescriptionIDs.put(executeQuery.getLong(
"event_id"), executeQuery.getLong(
"event_description_id"));
865 }
catch (SQLException ex) {
866 throw new TskCoreException(
"Error getting event description ids for object id = " + fileObjID, ex);
868 return eventIDToDescriptionIDs;
890 try (CaseDbConnection conn = caseDB.getConnection()) {
891 Map<Long, Long> eventIDs = getEventAndDescriptionIDs(conn, content.getId(),
false);
892 updateEventSourceTaggedFlag(conn, eventIDs.values(), 1);
893 return eventIDs.keySet();
919 try (CaseDbConnection conn = caseDB.getConnection()) {
921 Map<Long, Long> eventIDs = getEventAndDescriptionIDs(conn, content.getId(),
false);
922 updateEventSourceTaggedFlag(conn, eventIDs.values(), 0);
923 return eventIDs.keySet();
925 return Collections.emptySet();
945 try (CaseDbConnection conn = caseDB.getConnection()) {
946 Map<Long, Long> eventIDs = getEventAndDescriptionIDs(conn, artifact.getObjectID(), artifact.getArtifactID());
947 updateEventSourceTaggedFlag(conn, eventIDs.values(), 1);
948 return eventIDs.keySet();
968 try (CaseDbConnection conn = caseDB.getConnection()) {
970 Map<Long, Long> eventIDs = getEventAndDescriptionIDs(conn, artifact.getObjectID(), artifact.getArtifactID());
971 updateEventSourceTaggedFlag(conn, eventIDs.values(), 0);
972 return eventIDs.keySet();
974 return Collections.emptySet();
981 private void updateEventSourceTaggedFlag(CaseDbConnection conn, Collection<Long> eventDescriptionIDs,
int flagValue)
throws TskCoreException {
982 if (eventDescriptionIDs.isEmpty()) {
986 String sql =
"UPDATE tsk_event_descriptions SET tagged = " + flagValue +
" WHERE event_description_id IN (" + buildCSVString(eventDescriptionIDs) +
")";
987 try (Statement updateStatement = conn.createStatement()) {
988 updateStatement.executeUpdate(sql);
989 }
catch (SQLException ex) {
990 throw new TskCoreException(
"Error marking content events tagged: " + sql, ex);
1010 try (CaseDbConnection con = caseDB.getConnection(); Statement updateStatement = con.createStatement();) {
1011 Map<Long, Long> eventIDs = getEventAndDescriptionIDs(con, content.getId(),
true);
1012 if (!eventIDs.isEmpty()) {
1013 String sql =
"UPDATE tsk_event_descriptions SET hash_hit = 1" +
" WHERE event_description_id IN (" + buildCSVString(eventIDs.values()) +
")";
1015 updateStatement.executeUpdate(sql);
1016 return eventIDs.keySet();
1017 }
catch (SQLException ex) {
1018 throw new TskCoreException(
"Error setting hash_hit of events.", ex);
1021 return eventIDs.keySet();
1023 }
catch (SQLException ex) {
1024 throw new TskCoreException(
"Error setting hash_hit of events.", ex);
1054 long adjustedEndTime = Objects.equals(startTime, endTime) ? endTime + 1 : endTime;
1058 String queryString =
"SELECT count(DISTINCT tsk_events.event_id) AS count, " + typeColumn
1059 +
" FROM " + getAugmentedEventsTablesSQL(filter)
1060 +
" WHERE time >= " + startTime +
" AND time < " + adjustedEndTime +
" AND " + getSQLWhere(filter)
1061 +
" GROUP BY " + typeColumn;
1064 try (CaseDbConnection con = caseDB.getConnection();
1065 Statement stmt = con.createStatement();
1066 ResultSet results = stmt.executeQuery(queryString);) {
1067 Map<TimelineEventType, Long> typeMap =
new HashMap<>();
1068 while (results.next()) {
1069 int eventTypeID = results.getInt(typeColumn);
1071 .orElseThrow(() -> newEventTypeMappingException(eventTypeID));
1073 typeMap.put(eventType, results.getLong(
"count"));
1076 }
catch (SQLException ex) {
1077 throw new TskCoreException(
"Error getting count of events from db: " + queryString, ex);
1083 private static TskCoreException newEventTypeMappingException(
int eventTypeID) {
1084 return new TskCoreException(
"Error mapping event type id " + eventTypeID +
" to EventType.");
1100 static private String getAugmentedEventsTablesSQL(TimelineFilter.RootFilter filter) {
1101 TimelineFilter.FileTypesFilter fileTypesFitler = filter.getFileTypesFilter();
1102 boolean needsMimeTypes = fileTypesFitler != null && fileTypesFitler.hasSubFilters();
1104 return getAugmentedEventsTablesSQL(needsMimeTypes);
1121 static private String getAugmentedEventsTablesSQL(
boolean needMimeTypes) {
1134 return "( SELECT event_id, time, tsk_event_descriptions.data_source_obj_id, content_obj_id, artifact_id, "
1135 +
" full_description, med_description, short_description, tsk_events.event_type_id, super_type_id,"
1136 +
" hash_hit, tagged "
1137 + (needMimeTypes ?
", mime_type" :
"")
1138 +
" FROM tsk_events "
1139 +
" JOIN tsk_event_descriptions ON ( tsk_event_descriptions.event_description_id = tsk_events.event_description_id)"
1140 +
" JOIN tsk_event_types ON (tsk_events.event_type_id = tsk_event_types.event_type_id ) "
1141 + (needMimeTypes ?
" LEFT OUTER JOIN tsk_files "
1142 +
" ON (tsk_event_descriptions.content_obj_id = tsk_files.obj_id)"
1144 +
") AS tsk_events";
1154 private static int booleanToInt(
boolean value) {
1155 return value ? 1 : 0;
1158 private static boolean intToBoolean(
int value) {
1175 List<TimelineEvent> events =
new ArrayList<>();
1177 Long startTime = timeRange.getStartMillis() / 1000;
1178 Long endTime = timeRange.getEndMillis() / 1000;
1180 if (Objects.equals(startTime, endTime)) {
1184 if (filter == null) {
1188 if (endTime < startTime) {
1193 String querySql =
"SELECT time, content_obj_id, data_source_obj_id, artifact_id, "
1197 +
" event_type_id, super_type_id, "
1198 +
" full_description, med_description, short_description "
1199 +
" FROM " + getAugmentedEventsTablesSQL(filter)
1200 +
" WHERE time >= " + startTime +
" AND time < " + endTime +
" AND " + getSQLWhere(filter)
1204 try (CaseDbConnection con = caseDB.getConnection();
1205 Statement stmt = con.createStatement();
1206 ResultSet resultSet = stmt.executeQuery(querySql);) {
1208 while (resultSet.next()) {
1209 int eventTypeID = resultSet.getInt(
"event_type_id");
1211 ->
new TskCoreException(
"Error mapping event type id " + eventTypeID +
"to EventType."));
1214 resultSet.getLong(
"event_id"),
1215 resultSet.getLong(
"data_source_obj_id"),
1216 resultSet.getLong(
"content_obj_id"),
1217 resultSet.getLong(
"artifact_id"),
1218 resultSet.getLong(
"time"),
1220 resultSet.getString(
"full_description"),
1221 resultSet.getString(
"med_description"),
1222 resultSet.getString(
"short_description"),
1223 resultSet.getInt(
"hash_hit") != 0,
1224 resultSet.getInt(
"tagged") != 0);
1229 }
catch (SQLException ex) {
1230 throw new TskCoreException(
"Error getting events from db: " + querySql, ex);
1245 private static String typeColumnHelper(
final boolean useSubTypes) {
1246 return useSubTypes ?
"event_type_id" :
"super_type_id";
1257 String getSQLWhere(TimelineFilter.RootFilter filter) {
1260 if (filter == null) {
1261 return getTrueLiteral();
1263 result = filter.getSQLWhere(
this);
1280 private String getSqlIgnoreConflict(String insertTableValues)
throws TskCoreException {
1283 return "INSERT INTO " + insertTableValues +
" ON CONFLICT DO NOTHING";
1285 return "INSERT OR IGNORE INTO " + insertTableValues;
1287 throw new TskCoreException(
"Unknown DB Type: " + caseDB.
getDatabaseType().name());
1291 private String getTrueLiteral() {
1298 throw new UnsupportedOperationException(
"Unsupported DB type: " + caseDB.
getDatabaseType().name());
1316 this.addedEvent = event;
1323 private static class DuplicateException
extends Exception {
1325 private static final long serialVersionUID = 1L;
1332 DuplicateException(String message) {
List< Long > getEventIDs(Interval timeRange, TimelineFilter.RootFilter filter)
TimelineEvent getAddedEvent()
TimelineEvent getEventById(long eventID)
ImmutableList< TimelineEventType > getEventTypes()
Interval getSpanningInterval(Interval timeRange, TimelineFilter.RootFilter filter, DateTimeZone timeZone)
Set< Long > getEventIDsForContent(Content content, boolean includeDerivedArtifacts)
TimelineEventType FILE_ACCESSED
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)
TimelineEventType WEB_ACTIVITY
AbstractFile getAbstractFileById(long id)
TimelineEventType FILE_MODIFIED
void releaseSingleUserCaseReadLock()
TimelineEventType MISC_TYPES
static String escapeSingleQuotes(String text)
Set< Long > updateEventsForArtifactTagDeleted(BlackboardArtifact artifact)
void acquireSingleUserCaseWriteLock()
void releaseSingleUserCaseWriteLock()
TimelineEventType FILE_CREATED
TimelineEventType FILE_SYSTEM
Map< TimelineEventType, Long > countEventsByType(Long startTime, Long endTime, TimelineFilter.RootFilter filter, TimelineEventType.HierarchyLevel typeHierachyLevel)
List< Long > getEventIDsForArtifact(BlackboardArtifact artifact)
TimelineEventType ROOT_EVENT_TYPE
List< TimelineEvent > getEvents(Interval timeRange, TimelineFilter.RootFilter filter)
TimelineEventType CUSTOM_TYPES
void acquireSingleUserCaseReadLock()
List< ContentTag > getContentTagsByContent(Content content)
Optional< TimelineEventType > getEventType(long eventTypeID)
TimelineEventType FILE_CHANGED
Set< Long > updateEventsForArtifactTagAdded(BlackboardArtifact artifact)