diff --git a/NEWS.txt b/NEWS.txt index e20c995e700e5bd2d3949d1074d87a587ae70998..a4a155747a6fec656a047581b3ac0d1baa0d1dcc 100644 --- a/NEWS.txt +++ b/NEWS.txt @@ -1,5 +1,31 @@ -Numbers refer to SourceForge.net tracker IDs: - http://sourceforge.net/tracker/?group_id=55685 + + +---------------- VERSION 4.7.0 -------------- +C/C++: +- DB schema was expanded to store tsk_events and related tables. +Time-based data is automatically added when files and artifacts are +created. Used by Autopsy timeline. +- Logical Imager can save files as individual files instead of in +VHD (saves space). +- Logical imager produces log of results +- Logical Imager refactor +- Removed PRIuOFF and other macros that caused problems with +signed/unsigned printing. For example, TSK_OFF_T is a signed value +and PRIuOFF would cause problems as it printed a negative number +as a big positive number. + + +Java +- Travis and Debian package use OpenJDK instead of OracleJDK +- New Blackboard Helper packages (blackboardutils) to make it easier +to make artifacts. +- Blackboard scope was expanded, including the new postArtifact() method +that adds event data to database and broadcasts an event to listeners. +- SleuthkitCase now has an EventBus for database-related events. +- New TimelineManager and associated filter classes to support new events +table + + ---------------- VERSION 4.6.7 -------------- C/C++ Code: diff --git a/bindings/java/build.xml b/bindings/java/build.xml index 52615de6c7410bce09f210ea57162433e5bcbba3..124bc42d75acfa314c56531f28fce1f244c94266 100644 --- a/bindings/java/build.xml +++ b/bindings/java/build.xml @@ -11,7 +11,7 @@ <import file="build-${os.family}.xml"/> <!-- Careful changing this because release-windows.pl updates it by pattern --> -<property name="VERSION" value="4.6.7"/> +<property name="VERSION" value="4.7.0"/> <!-- set global properties for this build --> <property name="default-jar-location" location="/usr/share/java"/> diff --git a/bindings/java/src/org/sleuthkit/datamodel/Account.java b/bindings/java/src/org/sleuthkit/datamodel/Account.java index 0d7cb68006ce112fd79f59192848c8c08b8f68dd..bec66e453181c5f31af7afebf726adfb0ddb81c9 100644 --- a/bindings/java/src/org/sleuthkit/datamodel/Account.java +++ b/bindings/java/src/org/sleuthkit/datamodel/Account.java @@ -41,42 +41,6 @@ public final class Account { */ private final String typeSpecificID; - /** - * Class to abstract an account address. - * An account address comprises of a unique id and a display name. - */ - public static final class Address { - - // account type specific unique id - private final String uniqueID; - - // Display name for account - private final String displayName; - - public Address(String uniqueID, String displayName ) { - this.uniqueID = uniqueID; - this.displayName = displayName; - } - - /** - * Account type specific unique ID - * - * @return The type name. - */ - public String getUniqueID() { - return this.uniqueID; - } - - /** - * Gets the display name - * - * @return The display name. - */ - public String getDisplayName() { - return displayName; - } - } - public static final class Type { //JIRA-900:Should the display names of predefined types be internationalized? diff --git a/bindings/java/src/org/sleuthkit/datamodel/Bundle.properties b/bindings/java/src/org/sleuthkit/datamodel/Bundle.properties index 2ff00592feba01f9e3a2819d9a5f0002e1addae0..8e865dbff2558e52031ff5d872e6d9715df76850 100644 --- a/bindings/java/src/org/sleuthkit/datamodel/Bundle.properties +++ b/bindings/java/src/org/sleuthkit/datamodel/Bundle.properties @@ -285,10 +285,9 @@ IngestModuleInfo.IngestModuleType.DataSourceLevel.displayName=Data Source Level ReviewStatus.Approved=Approved ReviewStatus.Rejected=Rejected ReviewStatus.Undecided=Undecided -DescriptionLOD.short=Short -DescriptionLOD.medium=Medium -DescriptionLOD.full=Full - +TimelineLevelOfDetail.low=Low +TimelineLevelOfDetail.medium=Medium +TimelineLevelOfDetail.high=High BaseTypes.fileSystem.name=File System BaseTypes.webActivity.name=Web Activity BaseTypes.miscTypes.name=Misc Types @@ -318,12 +317,9 @@ WebTypes.webFormAddress.name=Web Form Address CustomTypes.other.name=Other CustomTypes.userCreated.name=User Created BaseTypes.customTypes.name=Custom Types - - -EventTypeZoomLevel.rootType=Root Type -EventTypeZoomLevel.baseType=Base Type -EventTypeZoomLevel.subType=Sub Type - +EventTypeHierarchyLevel.root=Root +EventTypeHierarchyLevel.category=Category +EventTypeHierarchyLevel.event=Event DataSourcesFilter.displayName.text=Data Source DescriptionFilter.mode.exclude=Exclude DescriptionFilter.mode.include=Include diff --git a/bindings/java/src/org/sleuthkit/datamodel/TimelineEvent.java b/bindings/java/src/org/sleuthkit/datamodel/TimelineEvent.java index b1082960494b387ce79988b5902906e275b38278..23e5f376e124b270d039e157eafd60026e4a9bbf 100644 --- a/bindings/java/src/org/sleuthkit/datamodel/TimelineEvent.java +++ b/bindings/java/src/org/sleuthkit/datamodel/TimelineEvent.java @@ -19,34 +19,39 @@ package org.sleuthkit.datamodel; import java.util.Optional; -import java.util.ResourceBundle; -import static org.sleuthkit.datamodel.TimelineEventType.TypeLevel.SUB_TYPE; /** - * A single event. + * A representation of an event in the timeline of a case. */ public final class TimelineEvent { + /** + * The unique ID of this event in the case database. + */ private final long eventID; /** - * The TSK object ID of the file this event is derived from. + * The object ID of the content that is either the direct or indirect source + * of this event. For events associated with files, this will be the object + * ID of the file. For events associated with artifacts, this will be the + * object ID of the artifact source: a file, a data source, or another + * artifact. */ - private final long fileObjID; + private final long contentObjID; /** - * The TSK artifact ID of the file this event is derived from. Null, if this - * event is not derived from an artifact. + * The artifact ID (not the object ID) of the artifact, if any, that is the + * source of this event. Null for events assoicated directly with files. */ private final Long artifactID; /** - * The TSK datasource ID of the datasource this event belongs to. + * The object ID of the data source for the event source. */ private final long dataSourceObjID; /** - * The time of this event in second from the Unix epoch. + * When this event occurred, in seconds from the UNIX epoch. */ private final long time; @@ -56,179 +61,191 @@ public final class TimelineEvent { private final TimelineEventType type; /** - * The three descriptions (full, med, short) stored in a map, keyed by - * DescriptionLOD (TypeLevel of Detail) + * The description of this event, provided at three levels of detail: high + * (full description), medium (medium description), and low (short + * description). */ private final TimelineEventDescription descriptions; /** - * True if the file this event is derived from hits any of the configured - * hash sets. + * True if the file, if any, associated with this event, either directly or + * indirectly, is a file for which a hash set hit has been detected. */ - private final boolean hashHit; + private final boolean eventSourceHashHitDetected; /** - * True if the file or artifact this event is derived from is tagged. + * True if the direct source (file or artifact) of this event has been + * tagged. */ - private final boolean tagged; + private final boolean eventSourceTagged; /** + * Constructs a representation of an event in the timeline of a case. * - * @param eventID ID from tsk_events table in database - * @param dataSourceObjID Object Id for data source event is from - * @param fileObjID object id for non-artifact content that event is - * associated with - * @param artifactID ID of artifact (not object id) if event came from - * an artifact - * @param time - * @param type - * @param descriptions - * @param hashHit - * @param tagged + * @param eventID The unique ID of this event in the case + * database. + * @param dataSourceObjID The object ID of the data source for + * the event source. + * @param contentObjID The object ID of the content that is + * either the direct or indirect source of + * this event. For events associated with + * files, this will be the object ID of + * the file. For events associated with + * artifacts, this will be the object ID + * of the artifact source: a file, a data + * source, or another artifact. + * @param artifactID The artifact ID (not the object ID) of + * the artifact, if any, that is the + * source of this event. Null for events + * assoicated directly with files. + * @param time The time this event occurred, in + * seconds from the UNIX epoch. + * @param type The type of this event. + * @param fullDescription The full length description of this + * event. + * @param medDescription The medium length description of this + * event. + * @param shortDescription The short length description of this + * event. + * @param eventSourceHashHitDetected True if the file, if any, associated + * with this event, either directly or + * indirectly, is a file for which a hash + * set hit has been detected. + * @param eventSourceTagged True if the direct source (file or + * artifact) of this event has been + * tagged. */ - TimelineEvent(long eventID, long dataSourceObjID, long fileObjID, Long artifactID, - long time, TimelineEventType type, + TimelineEvent(long eventID, + long dataSourceObjID, + long contentObjID, + Long artifactID, + long time, + TimelineEventType type, String fullDescription, String medDescription, String shortDescription, - boolean hashHit, boolean tagged) { + boolean eventSourceHashHitDetected, + boolean eventSourceTagged) { this.eventID = eventID; this.dataSourceObjID = dataSourceObjID; - this.fileObjID = fileObjID; + this.contentObjID = contentObjID; this.artifactID = Long.valueOf(0).equals(artifactID) ? null : artifactID; this.time = time; this.type = type; - // This isn't the best design, but it was the most expediant way to reduce - // the public API (by keeping parseDescription()) out of the public API. + /* + * The cast that follows reflects the fact that we have not decided + * whether or not to add the parseDescription method to the + * TimelineEventType interface yet. Currently (9/18/19), this method is + * part of TimelineEventTypeImpl and all implementations of + * TimelineEventType are subclasses of TimelineEventTypeImpl. + */ if (type instanceof TimelineEventTypeImpl) { this.descriptions = ((TimelineEventTypeImpl) type).parseDescription(fullDescription, medDescription, shortDescription); } else { - throw new IllegalArgumentException(); + this.descriptions = new TimelineEventDescription(fullDescription, medDescription, shortDescription); } - this.hashHit = hashHit; - this.tagged = tagged; + this.eventSourceHashHitDetected = eventSourceHashHitDetected; + this.eventSourceTagged = eventSourceTagged; } /** - * Is the file or artifact this event is derived from tagged? + * Indicates whether or not the direct source (file or artifact) of this + * artifact has been tagged. * - * @return true if he file or artifact this event is derived from is tagged. + * @return True or false. */ - public boolean isTagged() { - return tagged; + public boolean eventSourceIsTagged() { + return eventSourceTagged; } /** - * Is the file this event is derived from in any of the configured hash - * sets. - * + * Indicates whether or not the file, if any, associated with this event, + * either directly or indirectly, is a file for which a hash set hit has + * been detected. * - * @return True if the file this event is derived from is in any of the - * configured hash sets. + * @return True or false. */ - public boolean isHashHit() { - return hashHit; + public boolean eventSourceHasHashHits() { + return eventSourceHashHitDetected; } /** - * Get the artifact id (not the object ID) of the artifact this event is - * derived from. + * Gets the artifact ID (not object ID) of the artifact, if any, that is the + * direct source of this event. * - * @return An Optional containing the artifact ID. Will be empty if this - * event is not derived from an artifact + * @return An Optional object containing the artifact ID. May be empty. */ public Optional<Long> getArtifactID() { return Optional.ofNullable(artifactID); } /** - * Get the event id of this event. + * Gets the unique ID of this event in the case database. * - * @return The event id of this event. + * @return The event ID. */ public long getEventID() { return eventID; } /** - * Get the Content obj id of the "file" (which could be a data source or - * other non AbstractFile ContentS) this event is derived from. + * Gets the object ID of the content that is the direct or indirect source + * of this event. For events associated with files, this will be the object + * ID of the file that is the direct event source. For events associated + * with artifacts, this will be the object ID of the artifact source: a + * file, a data source, or another artifact. * - * @return the object id. + * @return The object ID. */ - public long getFileObjID() { - return fileObjID; + public long getContentObjID() { + return contentObjID; } /** - * Get the time of this event (in seconds from the Unix epoch). + * Gets the time this event occurred. * - * @return the time of this event in seconds from Unix epoch + * @return The time this event occurred, in seconds from UNIX epoch. */ public long getTime() { return time; } - public TimelineEventType getEventType() { - return type; - } - - public TimelineEventType getEventType(TimelineEventType.TypeLevel zoomLevel) { - return zoomLevel.equals(SUB_TYPE) ? type : type.getBaseType(); - } - - /** - * Get the full description of this event. - * - * @return the full description - */ - public String getFullDescription() { - return getDescription(TimelineEvent.DescriptionLevel.FULL); - } - - /** - * Get the medium description of this event. - * - * @return the medium description - */ - public String getMedDescription() { - return getDescription(TimelineEvent.DescriptionLevel.MEDIUM); - } - /** - * Get the short description of this event. + * Gets the type of this event. * - * @return the short description + * @return The event type. */ - public String getShortDescription() { - return getDescription(TimelineEvent.DescriptionLevel.SHORT); + public TimelineEventType getEventType() { + return type; } /** - * Get the description of this event at the give level of detail(LoD). + * Gets the description of this event at a given level of detail. * - * @param lod The level of detail to get. + * @param levelOfDetail The desired level of detail. * * @return The description of this event at the given level of detail. */ - public String getDescription(TimelineEvent.DescriptionLevel lod) { - return descriptions.getDescription(lod); + public String getDescription(TimelineLevelOfDetail levelOfDetail) { + return descriptions.getDescription(levelOfDetail); } /** - * Get the datasource id of the datasource this event belongs to. + * Gets the object ID of the data source for the source content of this + * event. * - * @return the datasource id. + * @return The data source object ID. */ public long getDataSourceObjID() { return dataSourceObjID; } - public long getEndMillis() { - return time * 1000; - } - - public long getStartMillis() { + /** + * Gets the time this event occured, in milliseconds from the UNIX epoch. + * + * @return The event time in milliseconds from the UNIX epoch. + */ + public long getEventTimeInMs() { return time * 1000; } @@ -248,42 +265,7 @@ public boolean equals(Object obj) { return false; } final TimelineEvent other = (TimelineEvent) obj; - return this.eventID == other.eventID; + return this.eventID == other.getEventID(); } - /** - * Defines the zoom levels that are available for the event description - */ - public enum DescriptionLevel { - SHORT(ResourceBundle.getBundle("org.sleuthkit.datamodel.Bundle").getString("DescriptionLOD.short")), - MEDIUM(ResourceBundle.getBundle("org.sleuthkit.datamodel.Bundle").getString("DescriptionLOD.medium")), - FULL(ResourceBundle.getBundle("org.sleuthkit.datamodel.Bundle").getString("DescriptionLOD.full")); - - private final String displayName; - - public String getDisplayName() { - return displayName; - } - - private DescriptionLevel(String displayName) { - this.displayName = displayName; - } - - public DescriptionLevel moreDetailed() { - try { - return values()[ordinal() + 1]; - } catch (ArrayIndexOutOfBoundsException e) { - return null; - } - } - - public DescriptionLevel lessDetailed() { - try { - return values()[ordinal() - 1]; - } catch (ArrayIndexOutOfBoundsException e) { - return null; - } - } - - } } diff --git a/bindings/java/src/org/sleuthkit/datamodel/TimelineEventArtifactTypeImpl.java b/bindings/java/src/org/sleuthkit/datamodel/TimelineEventArtifactTypeImpl.java index bee1ca8845cca7ee63e1849c06ba9c1439f2ac1a..0f35b1c2cbf17e49faab8eddfd68a9b03c375d2c 100644 --- a/bindings/java/src/org/sleuthkit/datamodel/TimelineEventArtifactTypeImpl.java +++ b/bindings/java/src/org/sleuthkit/datamodel/TimelineEventArtifactTypeImpl.java @@ -29,16 +29,23 @@ /** * Version of TimelineEventType for events based on artifacts */ -class TimelineEventArtifactTypeImpl extends TimelineEventTypeImpl { +class TimelineEventArtifactTypeImpl extends TimelineEventTypeImpl { private static final Logger logger = Logger.getLogger(TimelineEventArtifactTypeImpl.class.getName()); - + + static final int EMAIL_FULL_DESCRIPTION_LENGTH_MAX = 150; + static final int EMAIL_TO_FROM_LENGTH_MAX = 75; + private final BlackboardArtifact.Type artifactType; private final BlackboardAttribute.Type dateTimeAttributeType; private final TSKCoreCheckedFunction<BlackboardArtifact, String> fullExtractor; private final TSKCoreCheckedFunction<BlackboardArtifact, String> medExtractor; private final TSKCoreCheckedFunction<BlackboardArtifact, String> shortExtractor; private final TSKCoreCheckedFunction<BlackboardArtifact, TimelineEventDescriptionWithTime> artifactParsingFunction; + + private static final int MAX_SHORT_DESCRIPTION_LENGTH = 500; + private static final int MAX_MED_DESCRIPTION_LENGTH = 500; + private static final int MAX_FULL_DESCRIPTION_LENGTH = 1024; TimelineEventArtifactTypeImpl(int typeID, String displayName, TimelineEventType superType, @@ -59,7 +66,7 @@ class TimelineEventArtifactTypeImpl extends TimelineEventTypeImpl { TSKCoreCheckedFunction<BlackboardArtifact, String> fullExtractor, TSKCoreCheckedFunction<BlackboardArtifact, TimelineEventDescriptionWithTime> eventPayloadFunction) { - super(typeID, displayName, TimelineEventType.TypeLevel.SUB_TYPE, superType); + super(typeID, displayName, TimelineEventType.HierarchyLevel.EVENT, superType); this.artifactType = artifactType; this.dateTimeAttributeType = dateTimeAttributeType; this.shortExtractor = shortExtractor; @@ -102,13 +109,14 @@ BlackboardArtifact.Type getArtifactType() { return artifactType; } - /** * Parses the artifact to create a triple description with a time. - * + * * @param artifact + * * @return - * @throws TskCoreException + * + * @throws TskCoreException */ TimelineEventDescriptionWithTime makeEventDescription(BlackboardArtifact artifact) throws TskCoreException { //if we got passed an artifact that doesn't correspond to this event type, @@ -122,7 +130,9 @@ TimelineEventDescriptionWithTime makeEventDescription(BlackboardArtifact artifac return null; } - /* Use the type-specific method */ + /* + * Use the type-specific method + */ if (this.artifactParsingFunction != null) { //use the hook provided by this subtype implementation to build the descriptions. return this.artifactParsingFunction.apply(artifact); @@ -130,8 +140,20 @@ TimelineEventDescriptionWithTime makeEventDescription(BlackboardArtifact artifac //combine descriptions in standard way String shortDescription = extractShortDescription(artifact); + if (shortDescription.length() > MAX_SHORT_DESCRIPTION_LENGTH) { + shortDescription = shortDescription.substring(0, MAX_SHORT_DESCRIPTION_LENGTH); + } + String medDescription = shortDescription + " : " + extractMedDescription(artifact); + if (medDescription.length() > MAX_MED_DESCRIPTION_LENGTH) { + medDescription = medDescription.substring(0, MAX_MED_DESCRIPTION_LENGTH); + } + String fullDescription = medDescription + " : " + extractFullDescription(artifact); + if (fullDescription.length() > MAX_FULL_DESCRIPTION_LENGTH) { + fullDescription = fullDescription.substring(0, MAX_FULL_DESCRIPTION_LENGTH); + } + return new TimelineEventDescriptionWithTime(timeAttribute.getValueLong(), shortDescription, medDescription, fullDescription); } @@ -204,6 +226,7 @@ public String apply(BlackboardArtifact artf) throws TskCoreException { */ @FunctionalInterface interface TSKCoreCheckedFunction<I, O> { + O apply(I input) throws TskCoreException; } } diff --git a/bindings/java/src/org/sleuthkit/datamodel/TimelineEventDescription.java b/bindings/java/src/org/sleuthkit/datamodel/TimelineEventDescription.java index 1a58ee2c9ffa61720004b47d3f523a0b4181553d..0adf594c74edc009f4e0a5e10b67811250eb3c2c 100755 --- a/bindings/java/src/org/sleuthkit/datamodel/TimelineEventDescription.java +++ b/bindings/java/src/org/sleuthkit/datamodel/TimelineEventDescription.java @@ -19,21 +19,40 @@ package org.sleuthkit.datamodel; /** - * Encapsulates the potential multiple levels of description for an event in to - * one object. Currently used for interim storage. + * A container for a timeline event description with potentially varying levels + * of detail. */ class TimelineEventDescription { - String shortDesc; - String mediumDesc; - String fullDesc; + private final String shortDesc; + private final String mediumDesc; + private final String fullDesc; + /** + * Constructs a container for a timeline event description that varies with + * each of three levels of detail. + * + * @param fullDescription The full length description of an event for use + * at a high level of detail. + * @param medDescription The medium length description of an event for use + * at a medium level of detail. + * @param shortDescription The short length description of an event for use + * at a low level of detail. + */ TimelineEventDescription(String fullDescription, String medDescription, String shortDescription) { this.shortDesc = shortDescription; this.mediumDesc = medDescription; this.fullDesc = fullDescription; } + /** + * Constructs a container for a timeline event description for the high + * level of detail. The descriptions for the low and medium levels of detail + * will be the empty string. + * + * @param fullDescription The full length description of an event for use at + * a high level of detail. + */ TimelineEventDescription(String fullDescription) { this.shortDesc = ""; this.mediumDesc = ""; @@ -41,48 +60,22 @@ class TimelineEventDescription { } /** - * Get the full description of this event. - * - * @return the full description - */ - String getFullDescription() { - return fullDesc; - } - - /** - * Get the medium description of this event. - * - * @return the medium description - */ - String getMediumDescription() { - return mediumDesc; - } - - /** - * Get the short description of this event. - * - * @return the short description - */ - String getShortDescription() { - return shortDesc; - } - - /** - * Get the description of this event at the give level of detail(LoD). + * Gets the description of this event at the given level of detail. * - * @param lod The level of detail to get. + * @param levelOfDetail The level of detail. * - * @return The description of this event at the given level of detail. + * @return The event description at the given level of detail. */ - String getDescription(TimelineEvent.DescriptionLevel lod) { - switch (lod) { - case FULL: + String getDescription(TimelineLevelOfDetail levelOfDetail) { + switch (levelOfDetail) { + case HIGH: default: - return getFullDescription(); + return this.fullDesc; case MEDIUM: - return getMediumDescription(); - case SHORT: - return getShortDescription(); + return this.mediumDesc; + case LOW: + return this.shortDesc; } } + } diff --git a/bindings/java/src/org/sleuthkit/datamodel/TimelineEventType.java b/bindings/java/src/org/sleuthkit/datamodel/TimelineEventType.java index 4d1563b7957a805defa02c5824541ce57353a9d9..3eff0b5afdbcf78583ca1120921a063d501a4da2 100644 --- a/bindings/java/src/org/sleuthkit/datamodel/TimelineEventType.java +++ b/bindings/java/src/org/sleuthkit/datamodel/TimelineEventType.java @@ -18,6 +18,7 @@ */ package org.sleuthkit.datamodel; +import com.google.common.annotations.Beta; import com.google.common.base.MoreObjects; import com.google.common.collect.ImmutableSortedSet; import java.util.Arrays; @@ -37,95 +38,140 @@ import static org.sleuthkit.datamodel.TimelineEventArtifactTypeImpl.getAttributeSafe; /** - * Interface for distinct kinds of events (ie file system or web - * activity) in a hierarchy. An TimelineEventType may have an optional - super-type and 0 or more subtypes. NOTE: this is not currently - extensible by modules. The structure is hard coded to a certain - number of levels and types. + * An interface implemented by timeline event types. Timeline event types are + * organized into a type hierarchy. This type hierarchy has three levels: the + * root level, the category level (e.g, file system events, web activity + * events), and the actual event level (e.g., file modified events, web download + * events). + * + * Currently (9/20/19), all supported timeline event types are defined as + * members of this interface. + * + * WARNING: THIS INTERFACE IS A "BETA" INTERFACE AND IS SUBJECT TO CHANGE AT ANY + * TIME. */ +@Beta public interface TimelineEventType extends Comparable<TimelineEventType> { - - static final int EMAIL_FULL_DESCRIPTION_LENGTH_MAX = 150; + /** + * Gets the display name of this event type. + * + * @return The event type display name. + */ String getDisplayName(); /** - * - * @return Unique type iD (from database) + * Gets the unique ID of this event type in the case database. + * + * @return The event type ID. */ long getTypeID(); /** - * - * @return The level that this event is in the type hierarchy. + * Gets the type hierarchy level of this event type. + * + * @return The type hierarchy level. */ - TimelineEventType.TypeLevel getTypeLevel(); + TimelineEventType.HierarchyLevel getTypeHierarchyLevel(); /** - * @return A list of TimelineEventTypes, one for each subtype of this EventTYpe, or - an empty set if this TimelineEventType has no subtypes. + * Gets the child event types of this event type in the type hierarchy. + * + * @return A sorted set of the child event types. */ - SortedSet<? extends TimelineEventType> getSubTypes(); - - Optional<? extends TimelineEventType> getSubType(String string); - + SortedSet<? extends TimelineEventType> getChildren(); /** - * @return the super type of this event + * Gets a specific child event type of this event type in the type + * hierarchy. + * + * @param displayName The display name of the desired child event type. + * + * @return The child event type in an Optional object, may be empty. */ - TimelineEventType getSuperType(); + Optional<? extends TimelineEventType> getChild(String displayName); - default TimelineEventType getBaseType() { - TimelineEventType superType = getSuperType(); + /** + * Gets the parent event type of this event type in the type hierarchy. + * + * @return The parent event type. + */ + TimelineEventType getParent(); - return superType.equals(ROOT_EVENT_TYPE) + /** + * Gets the category level event type for this event type in the type + * hierarchy. + * + * @return The category event type. + */ + default TimelineEventType getCategory() { + TimelineEventType parentType = getParent(); + return parentType.equals(ROOT_EVENT_TYPE) ? this - : superType.getBaseType(); - + : parentType.getCategory(); } - default SortedSet<? extends TimelineEventType> getSiblingTypes() { + /** + * Gets the sibling event types of this event type in the type hierarchy. + * + * @return The sibling event types. + */ + default SortedSet<? extends TimelineEventType> getSiblings() { return this.equals(ROOT_EVENT_TYPE) ? ImmutableSortedSet.of(ROOT_EVENT_TYPE) - : this.getSuperType().getSubTypes(); - + : this.getParent().getChildren(); } @Override default int compareTo(TimelineEventType otherType) { return Comparator.comparing(TimelineEventType::getTypeID).compare(this, otherType); } - + /** - * Enum of event type zoom levels. + * An enumeration of the levels in the event type hierarchy. */ - public enum TypeLevel { + public enum HierarchyLevel { + /** - * The root event type zoom level. All event are the same type at this - * level. + * The root level of the event types hierarchy. */ - ROOT_TYPE(getBundle().getString("EventTypeZoomLevel.rootType")), + ROOT(getBundle().getString("EventTypeHierarchyLevel.root")), /** - * The zoom level of base event types like files system, and web activity + * The category level of the event types hierarchy. Event types at this + * level represent event categories such as file system events and web + * activity events. */ - BASE_TYPE(getBundle().getString("EventTypeZoomLevel.baseType")), + CATEGORY(getBundle().getString("EventTypeHierarchyLevel.category")), /** - * The zoom level of specific type such as file modified time, or web - * download. + * The actual events level of the event types hierarchy. Event types at + * this level represent actual events such as file modified time events + * and web download events. */ - SUB_TYPE(getBundle().getString("EventTypeZoomLevel.subType")); + EVENT(getBundle().getString("EventTypeHierarchyLevel.event")); private final String displayName; + /** + * Gets the display name of this element of the enumeration of the + * levels in the event type hierarchy. + * + * @return The display name. + */ public String getDisplayName() { return displayName; } - private TypeLevel(String displayName) { + /** + * Constructs an element of the enumeration of the levels in the event + * type hierarchy. + * + * @param displayName The display name of this hierarchy level. + */ + private HierarchyLevel(String displayName) { this.displayName = displayName; } - } + } /** * The root type of all event types. No event should actually have this @@ -133,36 +179,38 @@ private TypeLevel(String displayName) { */ TimelineEventType ROOT_EVENT_TYPE = new TimelineEventTypeImpl(0, getBundle().getString("RootEventType.eventTypes.name"), // NON-NLS - TypeLevel.ROOT_TYPE, null) { + HierarchyLevel.ROOT, null) { @Override - public SortedSet< TimelineEventType> getSubTypes() { + public SortedSet< TimelineEventType> getChildren() { return ImmutableSortedSet.of(FILE_SYSTEM, WEB_ACTIVITY, MISC_TYPES, CUSTOM_TYPES); } }; TimelineEventType FILE_SYSTEM = new TimelineEventTypeImpl(1, getBundle().getString("BaseTypes.fileSystem.name"),// NON-NLS - TypeLevel.BASE_TYPE, ROOT_EVENT_TYPE) { + HierarchyLevel.CATEGORY, ROOT_EVENT_TYPE) { @Override - public SortedSet< TimelineEventType> getSubTypes() { + public SortedSet< TimelineEventType> getChildren() { return ImmutableSortedSet.of(FILE_MODIFIED, FILE_ACCESSED, FILE_CREATED, FILE_CHANGED); } }; + TimelineEventType WEB_ACTIVITY = new TimelineEventTypeImpl(2, getBundle().getString("BaseTypes.webActivity.name"), // NON-NLS - TypeLevel.BASE_TYPE, ROOT_EVENT_TYPE) { + HierarchyLevel.CATEGORY, ROOT_EVENT_TYPE) { @Override - public SortedSet< TimelineEventType> getSubTypes() { + public SortedSet< TimelineEventType> getChildren() { return ImmutableSortedSet.of(WEB_DOWNLOADS, WEB_COOKIE, WEB_BOOKMARK, WEB_HISTORY, WEB_SEARCH, WEB_FORM_AUTOFILL, WEB_FORM_ADDRESSES); } }; + TimelineEventType MISC_TYPES = new TimelineEventTypeImpl(3, getBundle().getString("BaseTypes.miscTypes.name"), // NON-NLS - TypeLevel.BASE_TYPE, ROOT_EVENT_TYPE) { + HierarchyLevel.CATEGORY, ROOT_EVENT_TYPE) { @Override - public SortedSet<TimelineEventType> getSubTypes() { + public SortedSet<TimelineEventType> getChildren() { return ImmutableSortedSet.of(CALL_LOG, DEVICES_ATTACHED, EMAIL, EXIF, GPS_ROUTE, GPS_TRACKPOINT, INSTALLED_PROGRAM, MESSAGE, RECENT_DOCUMENTS, REGISTRY, LOG_ENTRY); @@ -171,16 +219,19 @@ public SortedSet<TimelineEventType> getSubTypes() { TimelineEventType FILE_MODIFIED = new FilePathEventType(4, getBundle().getString("FileSystemTypes.fileModified.name"), // NON-NLS - TypeLevel.SUB_TYPE, FILE_SYSTEM); + HierarchyLevel.EVENT, FILE_SYSTEM); + TimelineEventType FILE_ACCESSED = new FilePathEventType(5, getBundle().getString("FileSystemTypes.fileAccessed.name"), // NON-NLS - TypeLevel.SUB_TYPE, FILE_SYSTEM); + HierarchyLevel.EVENT, FILE_SYSTEM); + TimelineEventType FILE_CREATED = new FilePathEventType(6, getBundle().getString("FileSystemTypes.fileCreated.name"), // NON-NLS - TypeLevel.SUB_TYPE, FILE_SYSTEM); + HierarchyLevel.EVENT, FILE_SYSTEM); + TimelineEventType FILE_CHANGED = new FilePathEventType(7, getBundle().getString("FileSystemTypes.fileChanged.name"), // NON-NLS - TypeLevel.SUB_TYPE, FILE_SYSTEM); + HierarchyLevel.EVENT, FILE_SYSTEM); TimelineEventType WEB_DOWNLOADS = new URLArtifactEventType(8, getBundle().getString("WebTypes.webDownloads.name"), // NON-NLS @@ -188,24 +239,28 @@ public SortedSet<TimelineEventType> getSubTypes() { new BlackboardArtifact.Type(TSK_WEB_DOWNLOAD), new Type(TSK_DATETIME_ACCESSED), new Type(TSK_URL)); + TimelineEventType WEB_COOKIE = new URLArtifactEventType(9, getBundle().getString("WebTypes.webCookies.name"),// NON-NLS WEB_ACTIVITY, new BlackboardArtifact.Type(TSK_WEB_COOKIE), new Type(TSK_DATETIME), new Type(TSK_URL)); + TimelineEventType WEB_BOOKMARK = new URLArtifactEventType(10, getBundle().getString("WebTypes.webBookmarks.name"), // NON-NLS WEB_ACTIVITY, new BlackboardArtifact.Type(TSK_WEB_BOOKMARK), new Type(TSK_DATETIME_CREATED), new Type(TSK_URL)); + TimelineEventType WEB_HISTORY = new URLArtifactEventType(11, getBundle().getString("WebTypes.webHistory.name"), // NON-NLS WEB_ACTIVITY, new BlackboardArtifact.Type(TSK_WEB_HISTORY), new Type(TSK_DATETIME_ACCESSED), new Type(TSK_URL)); + TimelineEventType WEB_SEARCH = new URLArtifactEventType(12, getBundle().getString("WebTypes.webSearch.name"), // NON-NLS WEB_ACTIVITY, @@ -226,11 +281,11 @@ public SortedSet<TimelineEventType> getSubTypes() { final BlackboardAttribute subject = getAttributeSafe(artf, new Type(TSK_SUBJECT)); BlackboardAttribute phoneNumber = getAttributeSafe(artf, new Type(TSK_PHONE_NUMBER)); // Make our best effort to find a valid phoneNumber for the description - if( phoneNumber == null) { + if (phoneNumber == null) { phoneNumber = getAttributeSafe(artf, new Type(TSK_PHONE_NUMBER_TO)); } - - if( phoneNumber == null) { + + if (phoneNumber == null) { phoneNumber = getAttributeSafe(artf, new Type(TSK_PHONE_NUMBER_FROM)); } @@ -281,13 +336,13 @@ public SortedSet<TimelineEventType> getSubTypes() { new AttributeExtractor(new Type(TSK_NAME)), artf -> { BlackboardAttribute phoneNumber = getAttributeSafe(artf, new Type(TSK_PHONE_NUMBER)); - if( phoneNumber == null) { + if (phoneNumber == null) { phoneNumber = getAttributeSafe(artf, new Type(TSK_PHONE_NUMBER_TO)); } - if( phoneNumber == null) { + if (phoneNumber == null) { phoneNumber = getAttributeSafe(artf, new Type(TSK_PHONE_NUMBER_FROM)); } - + return stringValueOf(phoneNumber); }, new AttributeExtractor(new Type(TSK_DIRECTION))); @@ -298,16 +353,22 @@ public SortedSet<TimelineEventType> getSubTypes() { new BlackboardArtifact.Type(TSK_EMAIL_MSG), new Type(TSK_DATETIME_SENT), artf -> { - final BlackboardAttribute emailFrom = getAttributeSafe(artf, new Type(TSK_EMAIL_FROM)); - final BlackboardAttribute emailTo = getAttributeSafe(artf, new Type(TSK_EMAIL_TO)); - return stringValueOf(emailFrom) + " to " + stringValueOf(emailTo); // NON-NLS + String emailFrom = stringValueOf(getAttributeSafe(artf, new Type(TSK_EMAIL_FROM))); + if (emailFrom.length() > TimelineEventArtifactTypeImpl.EMAIL_TO_FROM_LENGTH_MAX) { + emailFrom = emailFrom.substring(0, TimelineEventArtifactTypeImpl.EMAIL_TO_FROM_LENGTH_MAX); + } + String emailTo = stringValueOf(getAttributeSafe(artf, new Type(TSK_EMAIL_TO))); + if (emailTo.length() > TimelineEventArtifactTypeImpl.EMAIL_TO_FROM_LENGTH_MAX) { + emailTo = emailTo.substring(0, TimelineEventArtifactTypeImpl.EMAIL_TO_FROM_LENGTH_MAX); + } + return emailFrom + " to " + emailTo; // NON-NLS }, new AttributeExtractor(new Type(TSK_SUBJECT)), artf -> { final BlackboardAttribute msgAttribute = getAttributeSafe(artf, new Type(TSK_EMAIL_CONTENT_PLAIN)); String msg = stringValueOf(msgAttribute); - if (msg.length() > EMAIL_FULL_DESCRIPTION_LENGTH_MAX) { - msg = msg.substring(0, EMAIL_FULL_DESCRIPTION_LENGTH_MAX); + if (msg.length() > TimelineEventArtifactTypeImpl.EMAIL_FULL_DESCRIPTION_LENGTH_MAX) { + msg = msg.substring(0, TimelineEventArtifactTypeImpl.EMAIL_FULL_DESCRIPTION_LENGTH_MAX); } return msg; }); @@ -350,9 +411,9 @@ public SortedSet<TimelineEventType> getSubTypes() { //custom event type base type TimelineEventType CUSTOM_TYPES = new TimelineEventTypeImpl(22, getBundle().getString("BaseTypes.customTypes.name"), // NON-NLS - TypeLevel.BASE_TYPE, ROOT_EVENT_TYPE) { + HierarchyLevel.CATEGORY, ROOT_EVENT_TYPE) { @Override - public SortedSet< TimelineEventType> getSubTypes() { + public SortedSet< TimelineEventType> getChildren() { return ImmutableSortedSet.of(OTHER, USER_CREATED); } }; @@ -387,7 +448,7 @@ public SortedSet< TimelineEventType> getSubTypes() { new BlackboardArtifact.Type(TSK_TL_EVENT), new BlackboardAttribute.Type(TSK_DATETIME), new BlackboardAttribute.Type(TSK_DESCRIPTION)); - + TimelineEventType WEB_FORM_AUTOFILL = new TimelineEventArtifactTypeImpl(27, getBundle().getString("WebTypes.webFormAutoFill.name"),//NON-NLS WEB_ACTIVITY, @@ -399,7 +460,7 @@ public SortedSet< TimelineEventType> getSubTypes() { final BlackboardAttribute count = getAttributeSafe(artf, new Type(TSK_COUNT)); return stringValueOf(name) + ":" + stringValueOf(value) + " count: " + stringValueOf(count); // NON-NLS }, new EmptyExtractor(), new EmptyExtractor()); - + TimelineEventType WEB_FORM_ADDRESSES = new URLArtifactEventType(28, getBundle().getString("WebTypes.webFormAddress.name"),//NON-NLS WEB_ACTIVITY, @@ -407,20 +468,20 @@ public SortedSet< TimelineEventType> getSubTypes() { new Type(TSK_DATETIME_ACCESSED), new Type(TSK_EMAIL)); - static SortedSet<? extends TimelineEventType> getBaseTypes() { - return ROOT_EVENT_TYPE.getSubTypes(); + static SortedSet<? extends TimelineEventType> getCategoryTypes() { + return ROOT_EVENT_TYPE.getChildren(); } static SortedSet<? extends TimelineEventType> getFileSystemTypes() { - return FILE_SYSTEM.getSubTypes(); + return FILE_SYSTEM.getChildren(); } static SortedSet<? extends TimelineEventType> getWebActivityTypes() { - return WEB_ACTIVITY.getSubTypes(); + return WEB_ACTIVITY.getChildren(); } static SortedSet<? extends TimelineEventType> getMiscTypes() { - return MISC_TYPES.getSubTypes(); + return MISC_TYPES.getChildren(); } static String stringValueOf(BlackboardAttribute attr) { diff --git a/bindings/java/src/org/sleuthkit/datamodel/TimelineEventTypeImpl.java b/bindings/java/src/org/sleuthkit/datamodel/TimelineEventTypeImpl.java index 4f0b228117ebf3aacc0785785142a12edaaeb370..08a63323d60ee1c878d83b12b8aae1ce098f5b9a 100644 --- a/bindings/java/src/org/sleuthkit/datamodel/TimelineEventTypeImpl.java +++ b/bindings/java/src/org/sleuthkit/datamodel/TimelineEventTypeImpl.java @@ -24,44 +24,42 @@ import org.apache.commons.lang3.ObjectUtils; /** - * Implementation of TimelineEventType for the standard predefined event types AND has package - scope parsing methods. + * Implementation of TimelineEventType for the standard predefined event types. */ class TimelineEventTypeImpl implements TimelineEventType { private final long typeID; private final String displayName; private final TimelineEventType superType; - private final TimelineEventType.TypeLevel eventTypeZoomLevel; + private final TimelineEventType.HierarchyLevel eventTypeZoomLevel; /** - * - * @param typeID ID (from the Database) + * + * @param typeID ID (from the Database) * @param displayName * @param eventTypeZoomLevel Where it is in the type hierarchy - * @param superType + * @param superType */ - TimelineEventTypeImpl(long typeID, String displayName, TimelineEventType.TypeLevel eventTypeZoomLevel, TimelineEventType superType) { + TimelineEventTypeImpl(long typeID, String displayName, TimelineEventType.HierarchyLevel eventTypeZoomLevel, TimelineEventType superType) { this.superType = superType; this.typeID = typeID; this.displayName = displayName; this.eventTypeZoomLevel = eventTypeZoomLevel; } - TimelineEventDescription parseDescription(String fullDescriptionRaw, String medDescriptionRaw, String shortDescriptionRaw) { // The standard/default implementation: Just bundle the three description levels into one object. return new TimelineEventDescription(fullDescriptionRaw, medDescriptionRaw, shortDescriptionRaw); } @Override - public SortedSet<? extends TimelineEventType> getSubTypes() { + public SortedSet<? extends TimelineEventType> getChildren() { return ImmutableSortedSet.of(); } @Override - public Optional<? extends TimelineEventType> getSubType(String string) { - return getSubTypes().stream() + public Optional<? extends TimelineEventType> getChild(String string) { + return getChildren().stream() .filter(type -> type.getDisplayName().equalsIgnoreCase(displayName)) .findFirst(); } @@ -72,13 +70,13 @@ public String getDisplayName() { } @Override - public TimelineEventType getSuperType() { + public TimelineEventType getParent() { return ObjectUtils.defaultIfNull(superType, ROOT_EVENT_TYPE); } @Override - public TimelineEventType.TypeLevel getTypeLevel() { + public TimelineEventType.HierarchyLevel getTypeHierarchyLevel() { return eventTypeZoomLevel; } diff --git a/bindings/java/src/org/sleuthkit/datamodel/TimelineEventTypes.java b/bindings/java/src/org/sleuthkit/datamodel/TimelineEventTypes.java index bd74899822b22ee493a33db7e9cf618d4de999e9..e2544d22855881d9ddd4d94db542c6edfc448f67 100644 --- a/bindings/java/src/org/sleuthkit/datamodel/TimelineEventTypes.java +++ b/bindings/java/src/org/sleuthkit/datamodel/TimelineEventTypes.java @@ -91,7 +91,7 @@ TimelineEventDescription parseDescription(String fullDescriptionRaw, String medD static class FilePathEventType extends TimelineEventTypeImpl { - FilePathEventType(long typeID, String displayName, TimelineEventType.TypeLevel eventTypeZoomLevel, TimelineEventType superType) { + FilePathEventType(long typeID, String displayName, TimelineEventType.HierarchyLevel eventTypeZoomLevel, TimelineEventType superType) { super(typeID, displayName, eventTypeZoomLevel, superType); } diff --git a/bindings/java/src/org/sleuthkit/datamodel/TimelineFilter.java b/bindings/java/src/org/sleuthkit/datamodel/TimelineFilter.java index c28e7c3a1e24ea97f2a88107946b11ba32d5a733..cec25c9d4461d706a7dd061d09fda032c84bfc52 100644 --- a/bindings/java/src/org/sleuthkit/datamodel/TimelineFilter.java +++ b/bindings/java/src/org/sleuthkit/datamodel/TimelineFilter.java @@ -1,7 +1,7 @@ /* * Sleuth Kit Data Model * - * Copyright 2018 Basis Technology Corp. + * Copyright 2018-2019 Basis Technology Corp. * Contact: carrier <at> sleuthkit <dot> org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -41,15 +41,16 @@ import static org.sleuthkit.datamodel.SleuthkitCase.escapeSingleQuotes; /** - * Interface for timeline event filters. Filters are given to the - * TimelineManager who interpretes them appropriately for all db queries. + * An interface for timeline events filters used to selectively query the + * timeline tables in the case database for timeline events via the APIs of the + * timeline manager. */ public abstract class TimelineFilter { /** - * get the display name of this filter + * Gets the display name for this filter. * - * @return a name for this filter to show in the UI + * @return The display name. */ public abstract String getDisplayName(); @@ -64,6 +65,11 @@ public abstract class TimelineFilter { */ abstract String getSQLWhere(TimelineManager manager); + /** + * Makes a copy of this filter. + * + * @return A copy of this filter. + */ public abstract TimelineFilter copyOf(); @SuppressWarnings("unchecked") @@ -73,21 +79,28 @@ static <S extends TimelineFilter, T extends CompoundFilter<S>> T copySubFilters( } /** - * Intersection (And) filter + * A timeline events filter that ANDs together a collection of timeline + * event filters. * - * @param <S> The type of sub Filters in this IntersectionFilter. + * @param <SubFilterType> The type of the filters to be AND'ed together. */ - public static class IntersectionFilter<S extends TimelineFilter> extends CompoundFilter<S> { + public static class IntersectionFilter<SubFilterType extends TimelineFilter> extends CompoundFilter<SubFilterType> { + /** + * Constructs timeline events filter that ANDs together a collection of + * timeline events filters. + * + * @param subFilters The collection of filters to be AND'ed together. + */ @VisibleForTesting - public IntersectionFilter(List<S> subFilters) { + public IntersectionFilter(List<SubFilterType> subFilters) { super(subFilters); } @Override - public IntersectionFilter<S> copyOf() { + public IntersectionFilter<SubFilterType> copyOf() { @SuppressWarnings("unchecked") - List<S> subfilters = Lists.transform(getSubFilters(), f -> (S) f.copyOf()); //make copies of all the subfilters. + List<SubFilterType> subfilters = Lists.transform(getSubFilters(), f -> (SubFilterType) f.copyOf()); //make copies of all the subfilters. return new IntersectionFilter<>(subfilters); } @@ -109,68 +122,83 @@ String getSQLWhere(TimelineManager manager) { .collect(Collectors.joining(" AND ")); return join.isEmpty() ? trueLiteral : "(" + join + ")"; } + } /** - * Event Type Filter. An instance of EventTypeFilter is usually a tree that - * parallels the event type hierarchy with one filter/node for each event - * type. + * A timeline events filter used to query for a subset of the event types in + * the event types hierarchy. The filter is built via a recursive descent + * from any given type in the hierarchy, effectively creating a filter that + * accepts the events in a branch of the event types hierarchy. */ public static final class EventTypeFilter extends UnionFilter<EventTypeFilter> { - /** - * the event type this filter passes - */ - private final TimelineEventType eventType; + private final TimelineEventType rootEventType; /** - * private constructor that enables non recursive/tree construction of - * the filter hierarchy for use in EventTypeFilter.copyOf(). + * Constucts a timeline events filter used to query for a subset of the + * event types in the event types hierarchy. The filter is optionally + * built via a recursive descent from any given type in the hierarchy, + * effectively creating a filter that accepts the events in a branch of + * the event types hierarchy. Thsi constructor exists solely for the use + * of this filter's implementation of the copyOf API. * - * @param eventType the event type this filter passes - * @param recursive true if subfilters should be added for each subtype. - * False if no subfilters should be added. + * @param rootEventType The "root" of the event hierarchy for the + * purposes of this filter. + * @param recursive Whether or not to do a recursive descent of the + * event types hierarchy from the root event type. */ - private EventTypeFilter(TimelineEventType eventType, boolean recursive) { + private EventTypeFilter(TimelineEventType rootEventType, boolean recursive) { super(FXCollections.observableArrayList()); - this.eventType = eventType; + this.rootEventType = rootEventType; if (recursive) { // add subfilters for each subtype - for (TimelineEventType subType : eventType.getSubTypes()) { + for (TimelineEventType subType : rootEventType.getChildren()) { addSubFilter(new EventTypeFilter(subType)); } } } /** - * public constructor. creates a subfilter for each subtype of the given - * event type + * Constructs a timeline events filter used to query for a subset of the + * event types in the event types hierarchy. The subset of event types + * that pass the filter is determined by a recursive descent from any + * given type in the hierarchy, effectively creating a filter that + * accepts the events in a branch of the event types hierarchy. * - * @param eventType the event type this filter will pass + * @param rootEventType The "root" of the event hierarchy for the + * purposes of this filter. */ - public EventTypeFilter(TimelineEventType eventType) { - this(eventType, true); + public EventTypeFilter(TimelineEventType rootEventType) { + this(rootEventType, true); } - public TimelineEventType getEventType() { - return eventType; + /** + * Gets the "root" of the branch of the event types hierarchy accepted + * by this filter. + * + * @return The "root" event type. + */ + public TimelineEventType getRootEventType() { + return rootEventType; } @Override public String getDisplayName() { - return (TimelineEventType.ROOT_EVENT_TYPE.equals(eventType)) ? BundleProvider.getBundle().getString("TypeFilter.displayName.text") : eventType.getDisplayName(); + return (TimelineEventType.ROOT_EVENT_TYPE.equals(rootEventType)) ? BundleProvider.getBundle().getString("TypeFilter.displayName.text") : rootEventType.getDisplayName(); } @Override public EventTypeFilter copyOf() { //make a nonrecursive copy of this filter, and then copy subfilters - return copySubFilters(this, new EventTypeFilter(eventType, false)); + // RC (10/1/19): Why? + return copySubFilters(this, new EventTypeFilter(rootEventType, false)); } @Override public int hashCode() { int hash = 7; - hash = 17 * hash + Objects.hashCode(this.eventType); + hash = 17 * hash + Objects.hashCode(this.rootEventType); return hash; } @@ -186,7 +214,7 @@ public boolean equals(Object obj) { return false; } final EventTypeFilter other = (EventTypeFilter) obj; - if (notEqual(this.eventType, other.eventType)) { + if (notEqual(this.rootEventType, other.getRootEventType())) { return false; } return Objects.equals(this.getSubFilters(), other.getSubFilters()); @@ -199,7 +227,7 @@ String getSQLWhere(TimelineManager manager) { private Stream<String> getSubTypeIDs() { if (this.getSubFilters().isEmpty()) { - return Stream.of(String.valueOf(getEventType().getTypeID())); + return Stream.of(String.valueOf(getRootEventType().getTypeID())); } else { return this.getSubFilters().stream().flatMap(EventTypeFilter::getSubTypeIDs); } @@ -207,49 +235,60 @@ private Stream<String> getSubTypeIDs() { @Override public String toString() { - return "EventTypeFilter{" + "eventType=" + eventType + ", subfilters=" + getSubFilters() + '}'; + return "EventTypeFilter{" + "rootEventType=" + rootEventType + ", subfilters=" + getSubFilters() + '}'; } } /** - * Filter to show only events that are associated with objects that have - * file or result tags. + * A timeline events filter used to query for events where the direct source + * (file or artifact) of the events has either been tagged or not tagged. */ public static final class TagsFilter extends TimelineFilter { - private final BooleanProperty booleanProperty = new SimpleBooleanProperty(); + + private final BooleanProperty eventSourcesAreTagged = new SimpleBooleanProperty(); /** - * Filter constructor. + * Constructs a timeline events filter used to query for a events where + * the direct source (file or artifact) of the events has not been + * tagged. */ - public TagsFilter() {} + public TagsFilter() { + } /** - * Filter constructor and set initial state. - * - * @param isTagged Boolean initial state for the filter. + * Constructs a timeline events filter used to query for events where + * the direct source (file or artifact) of the events has either been + * tagged or not tagged. + * + * @param eventSourceIsTagged Whether the direct sources of the events + * need to be tagged or not tagged to be + * accepted by this filter. */ - public TagsFilter(boolean isTagged) { - booleanProperty.set(isTagged); + public TagsFilter(boolean eventSourceIsTagged) { + this.eventSourcesAreTagged.set(eventSourceIsTagged); } /** - * Set the state of the filter. - * - * @param isTagged True to filter events that are associated tagged items - * or results + * Sets whether the direct sources of the events have to be tagged or + * not tagged to be accepted by this filter. + * + * @param eventSourceIsTagged Whether the direct sources of the events + * have to be tagged or not tagged to be + * accepted by this filter. */ - public synchronized void setTagged(boolean isTagged) { - booleanProperty.set(isTagged); + public synchronized void setEventSourcesAreTagged(boolean eventSourceIsTagged) { + this.eventSourcesAreTagged.set(eventSourceIsTagged); } /** - * Returns the current state of this filter. - * - * @return True to filter by objects that are tagged. + * Indicates whether the direct sources of the events have to be tagged + * or not tagged. + * + * @return True or false. */ - public synchronized boolean isTagged() { - return booleanProperty.get(); + public synchronized boolean getEventSourceAreTagged() { + return eventSourcesAreTagged.get(); } @Override @@ -259,7 +298,7 @@ public String getDisplayName() { @Override public TagsFilter copyOf() { - return new TagsFilter(booleanProperty.get()); + return new TagsFilter(eventSourcesAreTagged.get()); } @Override @@ -268,33 +307,35 @@ public boolean equals(Object obj) { return false; } - return ((TagsFilter)obj).isTagged() == booleanProperty.get(); + return ((TagsFilter) obj).getEventSourceAreTagged() == eventSourcesAreTagged.get(); } @Override public int hashCode() { int hash = 7; - hash = 67 * hash + Objects.hashCode(this.booleanProperty); + hash = 67 * hash + Objects.hashCode(this.eventSourcesAreTagged); return hash; } - + @Override String getSQLWhere(TimelineManager manager) { - String whereStr = ""; - if (booleanProperty.get()) { + String whereStr; + if (eventSourcesAreTagged.get()) { whereStr = "tagged = 1"; } else { whereStr = "tagged = 0"; } - + return whereStr; } + } /** - * Union(or) filter + * A timeline events filter that ORs together a collection of timeline + * events filters. * - * @param <SubFilterType> The type of the subfilters. + * @param <SubFilterType> The type of the filters to be OR'ed together. */ public static abstract class UnionFilter<SubFilterType extends TimelineFilter> extends TimelineFilter.CompoundFilter<SubFilterType> { @@ -322,23 +363,43 @@ String getSQLWhere(TimelineManager manager) { } /** - * Filter for text matching + * A timeline events filter used to query for events that have a particular + * substring in their short, medium, or full descriptions. */ public static final class TextFilter extends TimelineFilter { - private final SimpleStringProperty textProperty = new SimpleStringProperty(); + private final SimpleStringProperty descriptionSubstring = new SimpleStringProperty(); + /** + * Constructs a timeline events filter used to query for events that + * have the empty string as a substring in their short, medium, or full + * descriptions. + */ public TextFilter() { this(""); } - public TextFilter(String text) { + /** + * Constructs a timeline events filter used to query for events that + * have a given substring in their short, medium, or full descriptions. + * + * @param descriptionSubstring The substring that must be present in one + * or more of the descriptions of each event + * that passes the filter. + */ + public TextFilter(String descriptionSubstring) { super(); - this.textProperty.set(text.trim()); + this.descriptionSubstring.set(descriptionSubstring.trim()); } - public synchronized void setText(String text) { - this.textProperty.set(text.trim()); + /** + * Sets the substring that must be present in one or more of the + * descriptions of each event that passes the filter. + * + * @param descriptionSubstring The substring. + */ + public synchronized void setDescriptionSubstring(String descriptionSubstring) { + this.descriptionSubstring.set(descriptionSubstring.trim()); } @Override @@ -346,17 +407,29 @@ public String getDisplayName() { return BundleProvider.getBundle().getString("TextFilter.displayName.text"); } - public synchronized String getText() { - return textProperty.getValue(); + /** + * Gets the substring that must be present in one or more of the + * descriptions of each event that passes the filter. + * + * @return The required substring. + */ + public synchronized String getSubstring() { + return descriptionSubstring.getValue(); } - public Property<String> textProperty() { - return textProperty; + /** + * Gets the substring that must be present in one or more of the + * descriptions of each event that passes the filter. + * + * @return The required substring as a Property. + */ + public Property<String> substringProperty() { + return descriptionSubstring; } @Override public synchronized TextFilter copyOf() { - return new TextFilter(getText()); + return new TextFilter(getSubstring()); } @Override @@ -368,22 +441,22 @@ public boolean equals(Object obj) { return false; } final TextFilter other = (TextFilter) obj; - return Objects.equals(getText(), other.getText()); + return Objects.equals(getSubstring(), other.getSubstring()); } @Override public int hashCode() { int hash = 5; - hash = 29 * hash + Objects.hashCode(this.textProperty.get()); + hash = 29 * hash + Objects.hashCode(this.descriptionSubstring.get()); return hash; } @Override String getSQLWhere(TimelineManager manager) { - if (StringUtils.isNotBlank(this.getText())) { - return "((med_description like '%" + escapeSingleQuotes(this.getText()) + "%')" //NON-NLS - + " or (full_description like '%" + escapeSingleQuotes(this.getText()) + "%')" //NON-NLS - + " or (short_description like '%" + escapeSingleQuotes(this.getText()) + "%'))"; //NON-NLS + if (StringUtils.isNotBlank(this.getSubstring())) { + return "((med_description like '%" + escapeSingleQuotes(this.getSubstring()) + "%')" //NON-NLS + + " or (full_description like '%" + escapeSingleQuotes(this.getSubstring()) + "%')" //NON-NLS + + " or (short_description like '%" + escapeSingleQuotes(this.getSubstring()) + "%'))"; //NON-NLS } else { return manager.getSQLWhere(null); } @@ -391,109 +464,174 @@ String getSQLWhere(TimelineManager manager) { @Override public String toString() { - return "TextFilter{" + "textProperty=" + textProperty.getValue() + '}'; + return "TextFilter{" + "textProperty=" + descriptionSubstring.getValue() + '}'; } } /** - * An implementation of IntersectionFilter designed to be used as the root - * of a filter tree. provides named access to specific subfilters. + * A timeline events filter that ANDs together instances of a variety of + * event filter types to create what is in effect a "tree" of filters. */ public static final class RootFilter extends IntersectionFilter<TimelineFilter> { - private final HideKnownFilter knownFilter; + private final HideKnownFilter knownFilesFilter; private final TagsFilter tagsFilter; - private final HashHitsFilter hashFilter; - private final TextFilter textFilter; - private final EventTypeFilter typeFilter; + private final HashHitsFilter hashSetHitsFilter; + private final TextFilter descriptionSubstringFilter; + private final EventTypeFilter eventTypesFilter; private final DataSourcesFilter dataSourcesFilter; private final FileTypesFilter fileTypesFilter; - private final Set<TimelineFilter> namedSubFilters = new HashSet<>(); + private final Set<TimelineFilter> additionalFilters = new HashSet<>(); + /** + * Get the data sources filter of this filter. + * + * @return The filter. + */ public DataSourcesFilter getDataSourcesFilter() { return dataSourcesFilter; } + /** + * Gets the tagged events sources filter of this filter. + * + * @return The filter. + */ public TagsFilter getTagsFilter() { return tagsFilter; } + /** + * Gets the source file hash set hits filter of this filter. + * + * @return The filter. + */ public HashHitsFilter getHashHitsFilter() { - return hashFilter; + return hashSetHitsFilter; } + /** + * Gets the event types filter of this filter. + * + * @return The filter. + */ public EventTypeFilter getEventTypeFilter() { - return typeFilter; + return eventTypesFilter; } + /** + * Gets the exclude known source files filter of this filter. + * + * @return The filter. + */ public HideKnownFilter getKnownFilter() { - return knownFilter; + return knownFilesFilter; } + /** + * Gets the description substring filter of this filter. + * + * @return The filter. + */ public TextFilter getTextFilter() { - return textFilter; + return descriptionSubstringFilter; } + /** + * Gets the source file types filter of this filter. + * + * @return The filter. + */ public FileTypesFilter getFileTypesFilter() { return fileTypesFilter; } - public RootFilter(HideKnownFilter knownFilter, TagsFilter tagsFilter, HashHitsFilter hashFilter, - TextFilter textFilter, EventTypeFilter typeFilter, DataSourcesFilter dataSourcesFilter, - FileTypesFilter fileTypesFilter, Collection<TimelineFilter> annonymousSubFilters) { - super(FXCollections.observableArrayList(textFilter, knownFilter, tagsFilter, dataSourcesFilter, hashFilter, fileTypesFilter, typeFilter)); - + /** + * Constructs a timeline events filter that ANDs together instances of a + * variety of event filter types to create what is in effect a "tree" of + * filters. + * + * @param knownFilesFilter A filter that excludes events with + * knwon file event sources. + * @param tagsFilter A filter that exludes or includes + * events with tagged event sources. + * @param hashSetHitsFilter A filter that excludes or includes + * events with event sources that have + * hash set hits. + * @param descriptionSubstringFilter A filter that requires a substring + * to be present in the event + * description. + * @param eventTypesFilter A filter that accepts events of + * specified events types. + * @param dataSourcesFilter A filter that accepts events + * associated with a specified subset + * of data sources. + * @param fileTypesFilter A filter that includes or excludes + * events with source files of + * particular media types. + * @param additionalFilters Additional filters. + */ + public RootFilter( + HideKnownFilter knownFilesFilter, + TagsFilter tagsFilter, + HashHitsFilter hashSetHitsFilter, + TextFilter descriptionSubstringFilter, + EventTypeFilter eventTypesFilter, + DataSourcesFilter dataSourcesFilter, + FileTypesFilter fileTypesFilter, + Collection<TimelineFilter> additionalFilters) { + + super(FXCollections.observableArrayList(descriptionSubstringFilter, knownFilesFilter, tagsFilter, dataSourcesFilter, hashSetHitsFilter, fileTypesFilter, eventTypesFilter)); getSubFilters().removeIf(Objects::isNull); - this.knownFilter = knownFilter; + this.knownFilesFilter = knownFilesFilter; this.tagsFilter = tagsFilter; - this.hashFilter = hashFilter; - this.textFilter = textFilter; - this.typeFilter = typeFilter; + this.hashSetHitsFilter = hashSetHitsFilter; + this.descriptionSubstringFilter = descriptionSubstringFilter; + this.eventTypesFilter = eventTypesFilter; this.dataSourcesFilter = dataSourcesFilter; this.fileTypesFilter = fileTypesFilter; - - namedSubFilters.addAll(asList(textFilter, knownFilter, tagsFilter, dataSourcesFilter, hashFilter, fileTypesFilter, typeFilter)); - namedSubFilters.removeIf(Objects::isNull); - annonymousSubFilters.stream(). + this.additionalFilters.addAll(asList(descriptionSubstringFilter, knownFilesFilter, tagsFilter, dataSourcesFilter, hashSetHitsFilter, fileTypesFilter, eventTypesFilter)); + this.additionalFilters.removeIf(Objects::isNull); + additionalFilters.stream(). filter(Objects::nonNull). - filter(this::isNotNamedSubFilter). + filter(this::hasAdditionalFilter). map(TimelineFilter::copyOf). forEach(anonymousFilter -> getSubFilters().add(anonymousFilter)); } @Override public RootFilter copyOf() { - Set<TimelineFilter> annonymousSubFilters = getSubFilters().stream() - .filter(this::isNotNamedSubFilter) + Set<TimelineFilter> subFilters = getSubFilters().stream() + .filter(this::hasAdditionalFilter) .map(TimelineFilter::copyOf) .collect(Collectors.toSet()); - return new RootFilter(knownFilter.copyOf(), tagsFilter.copyOf(), - hashFilter.copyOf(), textFilter.copyOf(), typeFilter.copyOf(), - dataSourcesFilter.copyOf(), fileTypesFilter.copyOf(), annonymousSubFilters); + return new RootFilter(knownFilesFilter.copyOf(), tagsFilter.copyOf(), + hashSetHitsFilter.copyOf(), descriptionSubstringFilter.copyOf(), eventTypesFilter.copyOf(), + dataSourcesFilter.copyOf(), fileTypesFilter.copyOf(), subFilters); } - private boolean isNotNamedSubFilter(TimelineFilter subFilter) { - return !(namedSubFilters.contains(subFilter)); + private boolean hasAdditionalFilter(TimelineFilter subFilter) { + return !(additionalFilters.contains(subFilter)); } @Override public String toString() { - return "RootFilter{" + "knownFilter=" + knownFilter + ", tagsFilter=" + tagsFilter + ", hashFilter=" + hashFilter + ", textFilter=" + textFilter + ", typeFilter=" + typeFilter + ", dataSourcesFilter=" + dataSourcesFilter + ", fileTypesFilter=" + fileTypesFilter + ", namedSubFilters=" + namedSubFilters + '}'; + return "RootFilter{" + "knownFilter=" + knownFilesFilter + ", tagsFilter=" + tagsFilter + ", hashFilter=" + hashSetHitsFilter + ", textFilter=" + descriptionSubstringFilter + ", typeFilter=" + eventTypesFilter + ", dataSourcesFilter=" + dataSourcesFilter + ", fileTypesFilter=" + fileTypesFilter + ", namedSubFilters=" + additionalFilters + '}'; } @Override public int hashCode() { int hash = 7; - hash = 17 * hash + Objects.hashCode(this.knownFilter); + hash = 17 * hash + Objects.hashCode(this.knownFilesFilter); hash = 17 * hash + Objects.hashCode(this.tagsFilter); - hash = 17 * hash + Objects.hashCode(this.hashFilter); - hash = 17 * hash + Objects.hashCode(this.textFilter); - hash = 17 * hash + Objects.hashCode(this.typeFilter); + hash = 17 * hash + Objects.hashCode(this.hashSetHitsFilter); + hash = 17 * hash + Objects.hashCode(this.descriptionSubstringFilter); + hash = 17 * hash + Objects.hashCode(this.eventTypesFilter); hash = 17 * hash + Objects.hashCode(this.dataSourcesFilter); hash = 17 * hash + Objects.hashCode(this.fileTypesFilter); - hash = 17 * hash + Objects.hashCode(this.namedSubFilters); + hash = 17 * hash + Objects.hashCode(this.additionalFilters); return hash; } @@ -509,35 +647,36 @@ public boolean equals(Object obj) { return false; } final RootFilter other = (RootFilter) obj; - if (notEqual(this.knownFilter, other.knownFilter)) { + if (notEqual(this.knownFilesFilter, other.getKnownFilter())) { return false; } - if (notEqual(this.tagsFilter, other.tagsFilter)) { + if (notEqual(this.tagsFilter, other.getTagsFilter())) { return false; } - if (notEqual(this.hashFilter, other.hashFilter)) { + if (notEqual(this.hashSetHitsFilter, other.getHashHitsFilter())) { return false; } - if (notEqual(this.textFilter, other.textFilter)) { + if (notEqual(this.descriptionSubstringFilter, other.getTextFilter())) { return false; } - if (notEqual(this.typeFilter, other.typeFilter)) { + if (notEqual(this.eventTypesFilter, other.getEventTypeFilter())) { return false; } - if (notEqual(this.dataSourcesFilter, other.dataSourcesFilter)) { + if (notEqual(this.dataSourcesFilter, other.getDataSourcesFilter())) { return false; } - if (notEqual(this.fileTypesFilter, other.fileTypesFilter)) { + if (notEqual(this.fileTypesFilter, other.getFileTypesFilter())) { return false; } - return Objects.equals(this.namedSubFilters, other.namedSubFilters); + return Objects.equals(this.additionalFilters, other.getSubFilters()); } } /** - * Filter to hide known files + * A timeline events filter used to filter out events that have a direct or + * indirect event source that is a known file. */ public static final class HideKnownFilter extends TimelineFilter { @@ -546,10 +685,6 @@ public String getDisplayName() { return BundleProvider.getBundle().getString("hideKnownFilter.displayName.text"); } - public HideKnownFilter() { - super(); - } - @Override public HideKnownFilter copyOf() { return new HideKnownFilter(); @@ -577,11 +712,13 @@ String getSQLWhere(TimelineManager manager) { public String toString() { return "HideKnownFilter{" + '}'; } + } /** - * A Filter with a collection of sub-filters. Concrete implementations can - * decide how to combine the sub-filters. + * A timeline events filter composed of a collection of event filters. + * Concrete implementations can decide how to combine the filters in the + * collection. * * @param <SubFilterType> The type of the subfilters. */ @@ -593,23 +730,31 @@ protected void addSubFilter(SubFilterType subfilter) { } } - /** - * The list of sub-filters that make up this filter - */ private final ObservableList<SubFilterType> subFilters = FXCollections.observableArrayList(); + /** + * Gets the collection of filters that make up this filter. + * + * @return The filters. + */ public final ObservableList<SubFilterType> getSubFilters() { return subFilters; } + /** + * Indicates whether or not this filter has subfilters. + * + * @return True or false. + */ public boolean hasSubFilters() { return getSubFilters().isEmpty() == false; } /** - * construct a compound filter from a list of other filters to combine. + * Constructs a timeline events filter composed of a collection of event + * filters. * - * @param subFilters + * @param subFilters The collection of filters. */ protected CompoundFilter(List<SubFilterType> subFilters) { super(); @@ -647,23 +792,41 @@ public String toString() { } } - + /** - * Filter for an individual datasource + * A timeline events filter used to query for events associated with a given + * data source. */ public static final class DataSourceFilter extends TimelineFilter { private final String dataSourceName; private final long dataSourceID; + /** + * Gets the object ID of the specified data source. + * + * @return The data source object ID. + */ public long getDataSourceID() { return dataSourceID; } + /** + * Gets the display name of the specified data source. + * + * @return The data source display name. + */ public String getDataSourceName() { return dataSourceName; } + /** + * Constructs a timeline events filter used to query for events + * associated with a given data source. + * + * @param dataSourceName The data source display name. + * @param dataSourceID The data source object ID. + */ public DataSourceFilter(String dataSourceName, long dataSourceID) { super(); this.dataSourceName = dataSourceName; @@ -714,41 +877,53 @@ String getSQLWhere(TimelineManager manager) { } /** - * TimelineFilter for events that are associated with objects have Hash Hits. + * A timeline events filter used to query for events where the files that + * are the direct or indirect sources of the events either have or do not + * have hash set hits. + * */ public static final class HashHitsFilter extends TimelineFilter { - private final BooleanProperty booleanProperty = new SimpleBooleanProperty(); + + private final BooleanProperty eventSourcesHaveHashSetHits = new SimpleBooleanProperty(); /** - * Default constructor. + * Constructs a timeline events filter used to query for events where + * the files that are the direct or indirect sources of the events + * either do not have hash set hits. */ - public HashHitsFilter() {} + public HashHitsFilter() { + } /** - * Construct the hash hit filter and set state based given argument. - * - * @param hasHashHit True to filter items that have hash hits. + * Constructs a timeline events filter used to query for events where + * the files that are the direct or indirect sources of the events + * either have or do not have hash set hits. + * + * @param hasHashHit Whether or not the files associated with the events + * have or do not have hash set hits. */ public HashHitsFilter(boolean hasHashHit) { - booleanProperty.set(hasHashHit); + eventSourcesHaveHashSetHits.set(hasHashHit); } /** - * Set the state of the filter. - * - * @param hasHashHit True to filter by items that have hash hits. + * Sets whether or not the files associated with the events have or do + * not have hash set hits + * + * @param hasHashHit True or false. */ - public synchronized void setTagged(boolean hasHashHit) { - booleanProperty.set(hasHashHit); + public synchronized void setEventSourcesHaveHashSetHits(boolean hasHashHit) { + eventSourcesHaveHashSetHits.set(hasHashHit); } /** - * Returns the current state of the filter. - * - * @return True to filter by hash hits + * Indicates whether or not the files associated with the events have or + * do not have hash set hits + * + * @return True or false. */ - public synchronized boolean hasHashHits() { - return booleanProperty.get(); + public synchronized boolean getEventSourcesHaveHashSetHits() { + return eventSourcesHaveHashSetHits.get(); } @Override @@ -758,7 +933,7 @@ public String getDisplayName() { @Override public HashHitsFilter copyOf() { - return new HashHitsFilter(booleanProperty.get()); + return new HashHitsFilter(eventSourcesHaveHashSetHits.get()); } @Override @@ -767,37 +942,36 @@ public boolean equals(Object obj) { return false; } - return ((HashHitsFilter)obj).hasHashHits() == booleanProperty.get(); + return ((HashHitsFilter) obj).getEventSourcesHaveHashSetHits() == eventSourcesHaveHashSetHits.get(); } @Override public int hashCode() { int hash = 7; - hash = 67 * hash + Objects.hashCode(this.booleanProperty); + hash = 67 * hash + Objects.hashCode(this.eventSourcesHaveHashSetHits); return hash; } - + @Override String getSQLWhere(TimelineManager manager) { String whereStr = ""; - if (booleanProperty.get()) { + if (eventSourcesHaveHashSetHits.get()) { whereStr = "hash_hit = 1"; } else { whereStr = "hash_hit = 0"; } - + return whereStr; } + } /** - * union of DataSourceFilters + * A timeline events filter used to query for events associated with a given + * subset of data sources. The filter is a union of one or more single data + * source filters. */ - static public final class DataSourcesFilter extends UnionFilter< DataSourceFilter> { - - public DataSourcesFilter() { - super(); - } + static public final class DataSourcesFilter extends UnionFilter<DataSourceFilter> { @Override public DataSourcesFilter copyOf() { @@ -808,10 +982,13 @@ public DataSourcesFilter copyOf() { public String getDisplayName() { return BundleProvider.getBundle().getString("DataSourcesFilter.displayName.text"); } + } /** - * union of FileTypeFilters + * A timeline events filter used to query for events with direct or indirect + * event sources that are files with a given set of media types. The filter + * is a union of one or more file source filters. */ static public final class FileTypesFilter extends UnionFilter<FileTypeFilter> { @@ -827,41 +1004,52 @@ public String getDisplayName() { } } - + /** - * Gets all files that are NOT the specified types - */ - static public class InverseFileTypeFilter extends FileTypeFilter { + * A timeline events filter used to query for events with direct or indirect + * event sources that are files that do not have a given set of media types. + */ + static public class InverseFileTypeFilter extends FileTypeFilter { - public InverseFileTypeFilter(String displayName, Collection<String> mediaTypes) { - super(displayName, mediaTypes); - } + public InverseFileTypeFilter(String displayName, Collection<String> mediaTypes) { + super(displayName, mediaTypes); + } - @Override - public InverseFileTypeFilter copyOf() { - return new InverseFileTypeFilter(getDisplayName(), super.mediaTypes); - } + @Override + public InverseFileTypeFilter copyOf() { + return new InverseFileTypeFilter(getDisplayName(), super.mediaTypes); + } - @Override - String getSQLWhere(TimelineManager manager) { - return " NOT " + super.getSQLWhere(manager); - } - } + @Override + String getSQLWhere(TimelineManager manager) { + return " NOT " + super.getSQLWhere(manager); + } + } /** - * Filter for events derived from files with the given media/mime-types. + * A timeline events filter used to query for events with direct or indirect + * event sources that are files with a given set of media types. */ public static class FileTypeFilter extends TimelineFilter { private final String displayName; private final String sqlWhere; - Collection <String> mediaTypes = new HashSet<>(); + Collection<String> mediaTypes = new HashSet<>(); private FileTypeFilter(String displayName, String sql) { this.displayName = displayName; this.sqlWhere = sql; } + /** + * Constructs a timeline events filter used to query for events with + * direct or indirect event sources that are files with a given set of + * media types. + * + * @param displayName The display name for the filter. + * @param mediaTypes The event source file media types that pass the + * filter. + */ public FileTypeFilter(String displayName, Collection<String> mediaTypes) { this(displayName, mediaTypes.stream() @@ -924,4 +1112,5 @@ public String toString() { } } + } diff --git a/bindings/java/src/org/sleuthkit/datamodel/TimelineLevelOfDetail.java b/bindings/java/src/org/sleuthkit/datamodel/TimelineLevelOfDetail.java new file mode 100755 index 0000000000000000000000000000000000000000..a0f7b8dc329e1709c2213fa54fa3e4f7e83e6b42 --- /dev/null +++ b/bindings/java/src/org/sleuthkit/datamodel/TimelineLevelOfDetail.java @@ -0,0 +1,80 @@ +/* + * Sleuth Kit Data Model + * + * Copyright 2019 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.datamodel; + +import java.util.ResourceBundle; + +/** + * An enumeration of the levels of detail of various aspects of timeline data. + */ +public enum TimelineLevelOfDetail { + + LOW(ResourceBundle.getBundle("org.sleuthkit.datamodel.Bundle").getString("TimelineLevelOfDetail.low")), + MEDIUM(ResourceBundle.getBundle("org.sleuthkit.datamodel.Bundle").getString("TimelineLevelOfDetail.medium")), + HIGH(ResourceBundle.getBundle("org.sleuthkit.datamodel.Bundle").getString("TimelineLevelOfDetail.high")); + + private final String displayName; + + /** + * Gets the display name of this level of detail. + * + * @return The display name. + */ + public String getDisplayName() { + return displayName; + } + + /** + * Constructs an element of the enumeration of the levels of detail of + * various aspects of timeline data such as event descriptions and the + * timeline event types hierarchy. + * + * @param displayName The display name of the level of detail. + */ + private TimelineLevelOfDetail(String displayName) { + this.displayName = displayName; + } + + /** + * Gets the next higher level of detail relative to this level of detail. + * + * @return The next higher level of detail, may be null. + */ + public TimelineLevelOfDetail moreDetailed() { + try { + return values()[ordinal() + 1]; + } catch (ArrayIndexOutOfBoundsException e) { + return null; + } + } + + /** + * Gets the next lower level of detail relative to this level of detail. + * + * @return The next lower level of detail, may be null. + */ + public TimelineLevelOfDetail lessDetailed() { + try { + return values()[ordinal() - 1]; + } catch (ArrayIndexOutOfBoundsException e) { + return null; + } + } + +} diff --git a/bindings/java/src/org/sleuthkit/datamodel/TimelineManager.java b/bindings/java/src/org/sleuthkit/datamodel/TimelineManager.java index 45f1189d204c4febdc7ba9f33da645cc5652fb2c..f633d6f71670b5afebb718c596d9c73bf653e2f6 100644 --- a/bindings/java/src/org/sleuthkit/datamodel/TimelineManager.java +++ b/bindings/java/src/org/sleuthkit/datamodel/TimelineManager.java @@ -1,7 +1,7 @@ /* * Sleuth Kit Data Model * - * Copyright 2013-2019 Basis Technology Corp. + * Copyright 2018-2019 Basis Technology Corp. * Contact: carrier <at> sleuthkit <dot> org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,6 +18,7 @@ */ package org.sleuthkit.datamodel; +import com.google.common.annotations.Beta; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import java.sql.PreparedStatement; @@ -35,7 +36,6 @@ import static java.util.Objects.isNull; import java.util.Optional; import java.util.Set; -import java.util.logging.Logger; import java.util.stream.Collectors; import org.joda.time.DateTimeZone; import org.joda.time.Interval; @@ -47,17 +47,14 @@ import static org.sleuthkit.datamodel.StringUtils.buildCSVString; /** - * Provides access to the Timeline features of SleuthkitCase + * Provides access to the timeline data in a case database. */ public final class TimelineManager { - private static final Logger logger = Logger.getLogger(TimelineManager.class.getName()); - /** - * These event types are added to the DB in c++ land, but still need to be - * put in the eventTypeIDMap + * Timeline event types added to the case database when it is created. */ - private static final ImmutableList<TimelineEventType> ROOT_BASE_AND_FILESYSTEM_TYPES + private static final ImmutableList<TimelineEventType> ROOT_CATEGORY_AND_FILESYSTEM_TYPES = ImmutableList.of( TimelineEventType.ROOT_EVENT_TYPE, TimelineEventType.WEB_ACTIVITY, @@ -69,61 +66,79 @@ public final class TimelineManager { TimelineEventType.FILE_MODIFIED); /** - * These event types are predefined but not added to the DB by the C++ code. - * They are added by the TimelineManager constructor. + * Timeline event types added to the case database by the TimelineManager + * constructor. Adding these types at runtime permits new child types of the + * category types to be defined without modifying the table creation and + * population code in the Sleuth Kit. */ private static final ImmutableList<TimelineEventType> PREDEFINED_EVENT_TYPES = new ImmutableList.Builder<TimelineEventType>() .add(TimelineEventType.CUSTOM_TYPES) - .addAll(TimelineEventType.WEB_ACTIVITY.getSubTypes()) - .addAll(TimelineEventType.MISC_TYPES.getSubTypes()) - .addAll(TimelineEventType.CUSTOM_TYPES.getSubTypes()) + .addAll(TimelineEventType.WEB_ACTIVITY.getChildren()) + .addAll(TimelineEventType.MISC_TYPES.getChildren()) + .addAll(TimelineEventType.CUSTOM_TYPES.getChildren()) .build(); - private final SleuthkitCase sleuthkitCase; + private final SleuthkitCase caseDB; /** - * map from event type id to TimelineEventType object. + * Mapping of timeline event type IDs to TimelineEventType objects. */ private final Map<Long, TimelineEventType> eventTypeIDMap = new HashMap<>(); - TimelineManager(SleuthkitCase tskCase) throws TskCoreException { - sleuthkitCase = tskCase; + /** + * Constructs a timeline manager that provides access to the timeline data + * in a case database. + * + * @param caseDB The case database. + * + * @throws TskCoreException If there is an error constructing the timeline + * manager. + */ + TimelineManager(SleuthkitCase caseDB) throws TskCoreException { + this.caseDB = caseDB; //initialize root and base event types, these are added to the DB in c++ land - ROOT_BASE_AND_FILESYSTEM_TYPES.forEach(eventType -> eventTypeIDMap.put(eventType.getTypeID(), eventType)); + ROOT_CATEGORY_AND_FILESYSTEM_TYPES.forEach(eventType -> eventTypeIDMap.put(eventType.getTypeID(), eventType)); //initialize the other event types that aren't added in c++ - sleuthkitCase.acquireSingleUserCaseWriteLock(); - try (final CaseDbConnection con = sleuthkitCase.getConnection(); + caseDB.acquireSingleUserCaseWriteLock(); + try (final CaseDbConnection con = caseDB.getConnection(); final Statement statement = con.createStatement()) { for (TimelineEventType type : PREDEFINED_EVENT_TYPES) { con.executeUpdate(statement, insertOrIgnore(" INTO tsk_event_types(event_type_id, display_name, super_type_id) " + "VALUES( " + type.getTypeID() + ", '" + escapeSingleQuotes(type.getDisplayName()) + "'," - + type.getSuperType().getTypeID() + + type.getParent().getTypeID() + ")")); //NON-NLS eventTypeIDMap.put(type.getTypeID(), type); } } catch (SQLException ex) { - throw new TskCoreException("Failed to initialize event types.", ex); // NON-NLS + throw new TskCoreException("Failed to initialize timeline event types", ex); // NON-NLS } finally { - sleuthkitCase.releaseSingleUserCaseWriteLock(); + caseDB.releaseSingleUserCaseWriteLock(); } } - SleuthkitCase getSleuthkitCase() { - return sleuthkitCase; - } - + /** + * Gets the smallest possible time interval that spans a collection of + * timeline events. + * + * @param eventIDs The event IDs of the events for which to obtain the + * spanning interval. + * + * @return The minimal spanning interval, may be null. + * + * @throws TskCoreException If there is an error querying the case database. + */ public Interval getSpanningInterval(Collection<Long> eventIDs) throws TskCoreException { if (eventIDs.isEmpty()) { return null; } - final String query = "SELECT Min(time) as minTime, Max(time) as maxTime FROM tsk_events WHERE event_id IN (" + buildCSVString(eventIDs) + ")";//NON-NLS - sleuthkitCase.acquireSingleUserCaseReadLock(); - try (CaseDbConnection con = sleuthkitCase.getConnection(); + final String query = "SELECT Min(time) as minTime, Max(time) as maxTime FROM tsk_events WHERE event_id IN (" + buildCSVString(eventIDs) + ")"; //NON-NLS + caseDB.acquireSingleUserCaseReadLock(); + try (CaseDbConnection con = caseDB.getConnection(); Statement stmt = con.createStatement(); ResultSet results = stmt.executeQuery(query);) { if (results.next()) { @@ -132,22 +147,22 @@ public Interval getSpanningInterval(Collection<Long> eventIDs) throws TskCoreExc } catch (SQLException ex) { throw new TskCoreException("Error executing get spanning interval query: " + query, ex); // NON-NLS } finally { - sleuthkitCase.releaseSingleUserCaseReadLock(); + caseDB.releaseSingleUserCaseReadLock(); } return null; } /** - * Get the minimal interval that bounds all the vents that pass the given - * filter. + * Gets the smallest possible time interval that spans a collection of + * timeline events. * - * @param timeRange The time range that the events must be within. - * @param filter The filter that the events must pass. - * @param timeZone The timeZone to return the interval in. + * @param timeRange A time range that the events must be within. + * @param filter A timeline events filter that the events must pass. + * @param timeZone The time zone for the returned time interval. * - * @return The minimal interval that bounds the events. + * @return The minimal spanning interval, may be null. * - * @throws TskCoreException + * @throws TskCoreException If there is an error querying the case database. */ public Interval getSpanningInterval(Interval timeRange, TimelineFilter.RootFilter filter, DateTimeZone timeZone) throws TskCoreException { long start = timeRange.getStartMillis() / 1000; @@ -158,8 +173,8 @@ public Interval getSpanningInterval(Interval timeRange, TimelineFilter.RootFilte + " WHERE time <=" + start + " AND " + sqlWhere + ") AS start," + " (SELECT Min(time) FROM " + augmentedEventsTablesSQL + " WHERE time >= " + end + " AND " + sqlWhere + ") AS end";//NON-NLS - sleuthkitCase.acquireSingleUserCaseReadLock(); - try (CaseDbConnection con = sleuthkitCase.getConnection(); + caseDB.acquireSingleUserCaseReadLock(); + try (CaseDbConnection con = caseDB.getConnection(); Statement stmt = con.createStatement(); //can't use prepared statement because of complex where clause ResultSet results = stmt.executeQuery(queryString);) { @@ -168,23 +183,31 @@ public Interval getSpanningInterval(Interval timeRange, TimelineFilter.RootFilte long end2 = results.getLong("end"); // NON-NLS if (end2 == 0) { - end2 = getMaxTime(); + end2 = getMaxEventTime(); } return new Interval(start2 * 1000, (end2 + 1) * 1000, timeZone); } } catch (SQLException ex) { throw new TskCoreException("Failed to get MIN time.", ex); // NON-NLS } finally { - sleuthkitCase.releaseSingleUserCaseReadLock(); + caseDB.releaseSingleUserCaseReadLock(); } return null; } + /** + * Gets the timeline event with a given event ID. + * + * @param eventID An event ID. + * + * @return The timeline event, may be null. + * + * @throws TskCoreException If there is an error querying the case database. + */ public TimelineEvent getEventById(long eventID) throws TskCoreException { String sql = "SELECT * FROM " + getAugmentedEventsTablesSQL(false) + " WHERE event_id = " + eventID; - - sleuthkitCase.acquireSingleUserCaseReadLock(); - try (CaseDbConnection con = sleuthkitCase.getConnection(); + caseDB.acquireSingleUserCaseReadLock(); + try (CaseDbConnection con = caseDB.getConnection(); Statement stmt = con.createStatement();) { try (ResultSet results = stmt.executeQuery(sql);) { if (results.next()) { @@ -192,7 +215,7 @@ public TimelineEvent getEventById(long eventID) throws TskCoreException { TimelineEventType type = getEventType(typeID).orElseThrow(() -> newEventTypeMappingException(typeID)); //NON-NLS return new TimelineEvent(eventID, results.getLong("data_source_obj_id"), - results.getLong("file_obj_id"), + results.getLong("content_obj_id"), results.getLong("artifact_id"), results.getLong("time"), type, results.getString("full_description"), @@ -203,24 +226,23 @@ public TimelineEvent getEventById(long eventID) throws TskCoreException { } } } catch (SQLException sqlEx) { - throw new TskCoreException("exception while querying for event with id = " + eventID, sqlEx); // NON-NLS + throw new TskCoreException("Error while executing query " + sql, sqlEx); // NON-NLS } finally { - sleuthkitCase.releaseSingleUserCaseReadLock(); + caseDB.releaseSingleUserCaseReadLock(); } return null; } /** - * Get the IDs of all the events within the given time range that pass the - * given filter. + * Gets the event IDs of the timeline events within a given time range that + * pass a given timeline events filter. * - * @param timeRange The Interval that all returned events must be within. - * @param filter The Filter that all returned events must pass. + * @param timeRange The time range that the events must be within. + * @param filter The timeline events filter that the events must pass. * - * @return A List of event ids, sorted by timestamp of the corresponding - * event.. + * @return A list of event IDs ordered by event time. * - * @throws org.sleuthkit.datamodel.TskCoreException + * @throws TskCoreException If there is an error querying the case database. */ public List<Long> getEventIDs(Interval timeRange, TimelineFilter.RootFilter filter) throws TskCoreException { Long startTime = timeRange.getStartMillis() / 1000; @@ -234,8 +256,8 @@ public List<Long> getEventIDs(Interval timeRange, TimelineFilter.RootFilter filt String query = "SELECT tsk_events.event_id AS event_id FROM " + getAugmentedEventsTablesSQL(filter) + " WHERE time >= " + startTime + " AND time <" + endTime + " AND " + getSQLWhere(filter) + " ORDER BY time ASC"; // NON-NLS - sleuthkitCase.acquireSingleUserCaseReadLock(); - try (CaseDbConnection con = sleuthkitCase.getConnection(); + caseDB.acquireSingleUserCaseReadLock(); + try (CaseDbConnection con = caseDB.getConnection(); Statement stmt = con.createStatement(); ResultSet results = stmt.executeQuery(query);) { while (results.next()) { @@ -243,87 +265,91 @@ public List<Long> getEventIDs(Interval timeRange, TimelineFilter.RootFilter filt } } catch (SQLException sqlEx) { - throw new TskCoreException("failed to execute query for event ids in range", sqlEx); // NON-NLS + throw new TskCoreException("Error while executing query " + query, sqlEx); // NON-NLS } finally { - sleuthkitCase.releaseSingleUserCaseReadLock(); + caseDB.releaseSingleUserCaseReadLock(); } return resultIDs; } /** - * @return maximum time in seconds from unix epoch + * Gets the maximum timeline event time in the case database. * - * @throws org.sleuthkit.datamodel.TskCoreException + * @return The maximum timeline event time in seconds since the UNIX epoch, + * or -1 if there are no timeline events in the case database. + * + * @throws TskCoreException If there is an error querying the case database. */ - public Long getMaxTime() throws TskCoreException { - sleuthkitCase.acquireSingleUserCaseReadLock(); - - try (CaseDbConnection con = sleuthkitCase.getConnection(); + public Long getMaxEventTime() throws TskCoreException { + caseDB.acquireSingleUserCaseReadLock(); + try (CaseDbConnection con = caseDB.getConnection(); Statement stms = con.createStatement(); ResultSet results = stms.executeQuery(STATEMENTS.GET_MAX_TIME.getSQL());) { if (results.next()) { return results.getLong("max"); // NON-NLS } } catch (SQLException ex) { - throw new TskCoreException("Failed to get MAX time.", ex); // NON-NLS + throw new TskCoreException("Error while executing query " + STATEMENTS.GET_MAX_TIME.getSQL(), ex); // NON-NLS } finally { - sleuthkitCase.releaseSingleUserCaseReadLock(); + caseDB.releaseSingleUserCaseReadLock(); } return -1l; } /** - * @return maximum time in seconds from unix epoch + * Gets the minimum timeline event time in the case database. + * + * @return The minimum timeline event time in seconds since the UNIX epoch, + * or -1 if there are no timeline events in the case database. * - * @throws org.sleuthkit.datamodel.TskCoreException + * @throws TskCoreException If there is an error querying the case database. */ - public Long getMinTime() throws TskCoreException { - sleuthkitCase.acquireSingleUserCaseReadLock(); - - try (CaseDbConnection con = sleuthkitCase.getConnection(); + public Long getMinEventTime() throws TskCoreException { + caseDB.acquireSingleUserCaseReadLock(); + try (CaseDbConnection con = caseDB.getConnection(); Statement stms = con.createStatement(); ResultSet results = stms.executeQuery(STATEMENTS.GET_MIN_TIME.getSQL());) { if (results.next()) { return results.getLong("min"); // NON-NLS } } catch (SQLException ex) { - throw new TskCoreException("Failed to get MIN time.", ex); // NON-NLS + throw new TskCoreException("Error while executing query " + STATEMENTS.GET_MAX_TIME.getSQL(), ex); // NON-NLS } finally { - sleuthkitCase.releaseSingleUserCaseReadLock(); + caseDB.releaseSingleUserCaseReadLock(); } return -1l; } /** - * Get an TimelineEventType object given it's ID. + * Gets the timeline event type with a given event type ID. * - * @param eventTypeID The ID of the event type to get. + * @param eventTypeID An event type ID. * - * @return An Optional containing the TimelineEventType, or an empty Optional if no - TimelineEventType with the given ID was found. + * @return The timeline event type in an Optional object, may be empty if + * the event type is not found. */ public Optional<TimelineEventType> getEventType(long eventTypeID) { return Optional.ofNullable(eventTypeIDMap.get(eventTypeID)); } /** - * Get a list of all the EventTypes. + * Gets all of the timeline event types in the case database. * - * @return A list of all the eventTypes. + * @return A list of timeline event types. */ public ImmutableList<TimelineEventType> getEventTypes() { return ImmutableList.copyOf(eventTypeIDMap.values()); } private String insertOrIgnore(String query) { - switch (sleuthkitCase.getDatabaseType()) { + switch (caseDB.getDatabaseType()) { case POSTGRESQL: return " INSERT " + query + " ON CONFLICT DO NOTHING "; //NON-NLS case SQLITE: return " INSERT OR IGNORE " + query; //NON-NLS default: - throw new UnsupportedOperationException("Unsupported DB type: " + sleuthkitCase.getDatabaseType().name()); + throw new UnsupportedOperationException("Unsupported DB type: " + caseDB.getDatabaseType().name()); } } @@ -347,15 +373,14 @@ String getSQL() { } /** - * Get a List of event IDs for the events that are derived from the given - * artifact. + * Gets a list of event IDs for the timeline events that have a given + * artifact as the event source. * - * @param artifact The BlackboardArtifact to get derived event IDs for. + * @param artifact An artifact. * - * @return A List of event IDs for the events that are derived from the - * given artifact. + * @return The list of event IDs. * - * @throws org.sleuthkit.datamodel.TskCoreException + * @throws TskCoreException If there is an error querying the case database. */ public List<Long> getEventIDsForArtifact(BlackboardArtifact artifact) throws TskCoreException { ArrayList<Long> eventIDs = new ArrayList<>(); @@ -364,8 +389,8 @@ public List<Long> getEventIDsForArtifact(BlackboardArtifact artifact) throws Tsk = "SELECT event_id FROM tsk_events " + " LEFT JOIN tsk_event_descriptions on ( tsk_events.event_description_id = tsk_event_descriptions.event_description_id ) " + " WHERE artifact_id = " + artifact.getArtifactID(); - sleuthkitCase.acquireSingleUserCaseReadLock(); - try (CaseDbConnection con = sleuthkitCase.getConnection(); + caseDB.acquireSingleUserCaseReadLock(); + try (CaseDbConnection con = caseDB.getConnection(); Statement stmt = con.createStatement(); ResultSet results = stmt.executeQuery(query);) { while (results.next()) { @@ -374,30 +399,31 @@ public List<Long> getEventIDsForArtifact(BlackboardArtifact artifact) throws Tsk } catch (SQLException ex) { throw new TskCoreException("Error executing getEventIDsForArtifact query.", ex); // NON-NLS } finally { - sleuthkitCase.releaseSingleUserCaseReadLock(); + caseDB.releaseSingleUserCaseReadLock(); } return eventIDs; } /** - * Get a Set of event IDs for the events that are derived from the given - * file. + * Gets a list of event IDs for the timeline events that have a given + * content as the event source. * - * @param file The File / data source to get derived - * event IDs for. + * @param content The content. * @param includeDerivedArtifacts If true, also get event IDs for events - * derived from artifacts derived form this - * file. If false, only gets events derived - * directly from this file (file system - * timestamps). + * where the event source is an artifact that + * has the given content as its source. * - * @return A Set of event IDs for the events that are derived from the given - * file. + * @return The list of event IDs. * - * @throws org.sleuthkit.datamodel.TskCoreException + * @throws TskCoreException If there is an error querying the case database. */ - public Set<Long> getEventIDsForFile(Content file, boolean includeDerivedArtifacts) throws TskCoreException { - return getEventAndDescriptionIDs(file.getId(), includeDerivedArtifacts).keySet(); + public Set<Long> getEventIDsForContent(Content content, boolean includeDerivedArtifacts) throws TskCoreException { + caseDB.acquireSingleUserCaseWriteLock(); + try (CaseDbConnection conn = caseDB.getConnection()) { + return getEventAndDescriptionIDs(conn, content.getId(), includeDerivedArtifacts).keySet(); + } finally { + caseDB.releaseSingleUserCaseWriteLock(); + } } /** @@ -422,7 +448,7 @@ private long addEventDescription(long dataSourceObjId, long fileObjId, Long arti boolean hasHashHits, boolean tagged, CaseDbConnection connection) throws TskCoreException { String insertDescriptionSql = "INSERT INTO tsk_event_descriptions ( " - + "data_source_obj_id, file_obj_id, artifact_id, " + + "data_source_obj_id, content_obj_id, artifact_id, " + " full_description, med_description, short_description, " + " hash_hit, tagged " + " ) VALUES (" @@ -436,7 +462,7 @@ private long addEventDescription(long dataSourceObjId, long fileObjId, Long arti + booleanToInt(tagged) + " )"; - sleuthkitCase.acquireSingleUserCaseWriteLock(); + caseDB.acquireSingleUserCaseWriteLock(); try (Statement insertDescriptionStmt = connection.createStatement()) { connection.executeUpdate(insertDescriptionStmt, insertDescriptionSql, PreparedStatement.RETURN_GENERATED_KEYS); try (ResultSet generatedKeys = insertDescriptionStmt.getGeneratedKeys()) { @@ -446,7 +472,7 @@ private long addEventDescription(long dataSourceObjId, long fileObjId, Long arti } catch (SQLException ex) { throw new TskCoreException("Failed to insert event description.", ex); // NON-NLS } finally { - sleuthkitCase.releaseSingleUserCaseWriteLock(); + caseDB.releaseSingleUserCaseWriteLock(); } } @@ -468,7 +494,7 @@ Collection<TimelineEvent> addEventsForNewFile(AbstractFile file, CaseDbConnectio String description = file.getParentPath() + file.getName(); long fileObjId = file.getId(); Set<TimelineEvent> events = new HashSet<>(); - sleuthkitCase.acquireSingleUserCaseWriteLock(); + caseDB.acquireSingleUserCaseWriteLock(); try { long descriptionID = addEventDescription(file.getDataSourceObjectId(), fileObjId, null, description, null, null, false, false, connection); @@ -490,11 +516,11 @@ Collection<TimelineEvent> addEventsForNewFile(AbstractFile file, CaseDbConnectio } } finally { - sleuthkitCase.releaseSingleUserCaseWriteLock(); + caseDB.releaseSingleUserCaseWriteLock(); } events.stream() .map(TimelineEventAddedEvent::new) - .forEach(sleuthkitCase::fireTSKEvent); + .forEach(caseDB::fireTSKEvent); return events; } @@ -530,8 +556,8 @@ Set<TimelineEvent> addArtifactEvents(BlackboardArtifact artifact) throws TskCore eventType = eventTypeIDMap.getOrDefault(eventTypeID, TimelineEventType.OTHER); } - // @@@ This casting is risky if we change class hierarchy, but was expediant. Should move parsing to another class - addArtifactEvent(((TimelineEventArtifactTypeImpl)TimelineEventType.OTHER)::makeEventDescription, eventType, artifact) + // @@@ This casting is risky if we change class hierarchy, but was expedient. Should move parsing to another class + addArtifactEvent(((TimelineEventArtifactTypeImpl) TimelineEventType.OTHER)::makeEventDescription, eventType, artifact) .ifPresent(newEvents::add); } else { /* @@ -551,7 +577,7 @@ Set<TimelineEvent> addArtifactEvents(BlackboardArtifact artifact) throws TskCore } newEvents.stream() .map(TimelineEventAddedEvent::new) - .forEach(sleuthkitCase::fireTSKEvent); + .forEach(caseDB::fireTSKEvent); return newEvents; } @@ -583,24 +609,24 @@ private Optional<TimelineEvent> addArtifactEvent(TSKCoreCheckedFunction<Blackboa if (time <= 0) { return Optional.empty(); } - String fullDescription = eventPayload.getFullDescription(); - String medDescription = eventPayload.getMediumDescription(); - String shortDescription = eventPayload.getShortDescription(); + String fullDescription = eventPayload.getDescription(TimelineLevelOfDetail.HIGH); + String medDescription = eventPayload.getDescription(TimelineLevelOfDetail.MEDIUM); + String shortDescription = eventPayload.getDescription(TimelineLevelOfDetail.LOW); long artifactID = artifact.getArtifactID(); long fileObjId = artifact.getObjectID(); long dataSourceObjectID = artifact.getDataSourceObjectID(); - AbstractFile file = sleuthkitCase.getAbstractFileById(fileObjId); + AbstractFile file = caseDB.getAbstractFileById(fileObjId); boolean hasHashHits = false; // file will be null if source was data source or some non-file if (file != null) { hasHashHits = isNotEmpty(file.getHashSetNames()); } - boolean tagged = isNotEmpty(sleuthkitCase.getBlackboardArtifactTagsByArtifact(artifact)); + boolean tagged = isNotEmpty(caseDB.getBlackboardArtifactTagsByArtifact(artifact)); TimelineEvent event; - sleuthkitCase.acquireSingleUserCaseWriteLock(); - try (CaseDbConnection connection = getSleuthkitCase().getConnection();) { + caseDB.acquireSingleUserCaseWriteLock(); + try (CaseDbConnection connection = caseDB.getConnection();) { long descriptionID = addEventDescription(dataSourceObjectID, fileObjId, artifactID, fullDescription, medDescription, shortDescription, @@ -613,7 +639,7 @@ private Optional<TimelineEvent> addArtifactEvent(TSKCoreCheckedFunction<Blackboa hasHashHits, tagged); } finally { - sleuthkitCase.releaseSingleUserCaseWriteLock(); + caseDB.releaseSingleUserCaseWriteLock(); } return Optional.of(event); } @@ -623,7 +649,7 @@ private long addEventWithExistingDescription(Long time, TimelineEventType type, = "INSERT INTO tsk_events ( event_type_id, event_description_id , time) " + " VALUES (" + type.getTypeID() + ", " + descriptionID + ", " + time + ")"; - sleuthkitCase.acquireSingleUserCaseWriteLock(); + caseDB.acquireSingleUserCaseWriteLock(); try (Statement insertRowStmt = connection.createStatement();) { connection.executeUpdate(insertRowStmt, insertEventSql, PreparedStatement.RETURN_GENERATED_KEYS); @@ -634,7 +660,7 @@ private long addEventWithExistingDescription(Long time, TimelineEventType type, } catch (SQLException ex) { throw new TskCoreException("Failed to insert event for existing description.", ex); // NON-NLS } finally { - sleuthkitCase.releaseSingleUserCaseWriteLock(); + caseDB.releaseSingleUserCaseWriteLock(); } } @@ -642,123 +668,181 @@ static private String quotePreservingNull(String value) { return isNull(value) ? " NULL " : "'" + escapeSingleQuotes(value) + "'";//NON-NLS } + private Map<Long, Long> getEventAndDescriptionIDs(CaseDbConnection conn, long contentObjID, boolean includeArtifacts) throws TskCoreException { + return getEventAndDescriptionIDsHelper(conn, contentObjID, (includeArtifacts ? "" : " AND artifact_id IS NULL")); + } + + private Map<Long, Long> getEventAndDescriptionIDs(CaseDbConnection conn, long contentObjID, Long artifactID) throws TskCoreException { + return getEventAndDescriptionIDsHelper(conn, contentObjID, " AND artifact_id = " + artifactID); + } + + private Map<Long, Long> getEventAndDescriptionIDsHelper(CaseDbConnection con, long fileObjID, String artifactClause) throws TskCoreException { + //map from event_id to the event_description_id for that event. + Map<Long, Long> eventIDToDescriptionIDs = new HashMap<>(); + String sql = "SELECT event_id, tsk_events.event_description_id" + + " FROM tsk_events " + + " LEFT JOIN tsk_event_descriptions ON ( tsk_events.event_description_id = tsk_event_descriptions.event_description_id )" + + " WHERE content_obj_id = " + fileObjID + + artifactClause; + try (Statement selectStmt = con.createStatement(); ResultSet executeQuery = selectStmt.executeQuery(sql);) { + while (executeQuery.next()) { + eventIDToDescriptionIDs.put(executeQuery.getLong("event_id"), executeQuery.getLong("event_description_id")); //NON-NLS + } + } catch (SQLException ex) { + throw new TskCoreException("Error getting event description ids for object id = " + fileObjID, ex); + } + return eventIDToDescriptionIDs; + } + /** - * Get events that are associated with the file + * Finds all of the timeline events directly associated with a given content + * and marks them as having an event source that is tagged. This does not + * include timeline events where the event source is an artifact, even if + * the artifact source is the tagged content. * - * @param fileObjID - * @param includeArtifacts true if results should also include events from - * artifacts associated with the file. + * @param content The content. * - * @return A map from event_id to event_decsription_id. + * @return The event IDs of the events that were marked as having a tagged + * event source. * - * @throws TskCoreException + * @throws TskCoreException If there is an error updating the case database. + * + * WARNING: THIS IS A BETA VERSION OF THIS METHOD, SUBJECT TO CHANGE AT ANY + * TIME. */ - private Map<Long, Long> getEventAndDescriptionIDs(long fileObjID, boolean includeArtifacts) throws TskCoreException { - return getEventAndDescriptionIDsHelper(fileObjID, (includeArtifacts ? "" : " AND artifact_id IS NULL")); + @Beta + public Set<Long> updateEventsForContentTagAdded(Content content) throws TskCoreException { + caseDB.acquireSingleUserCaseWriteLock(); + try (CaseDbConnection conn = caseDB.getConnection()) { + Map<Long, Long> eventIDs = getEventAndDescriptionIDs(conn, content.getId(), false); + updateEventSourceTaggedFlag(conn, eventIDs.values(), 1); + return eventIDs.keySet(); + } finally { + caseDB.releaseSingleUserCaseWriteLock(); + } } /** - * Get events that match both the file and artifact IDs + * Finds all of the timeline events directly associated with a given content + * and marks them as not having an event source that is tagged, if and only + * if there are no other tags on the content. The inspection of events does + * not include events where the event source is an artifact, even if the + * artifact source is the content from which trhe tag was removed. * - * @param fileObjID - * @param artifactID + * @param content The content. * - * @return A map from event_id to event_decsription_id. + * @return The event IDs of the events that were marked as not having a + * tagged event source. * - * @throws TskCoreException + * @throws TskCoreException If there is an error updating the case database. + * + * WARNING: THIS IS A BETA VERSION OF THIS METHOD, SUBJECT TO CHANGE AT ANY + * TIME. */ - private Map<Long, Long> getEventAndDescriptionIDs(long fileObjID, Long artifactID) throws TskCoreException { - return getEventAndDescriptionIDsHelper(fileObjID, " AND artifact_id = " + artifactID); + @Beta + public Set<Long> updateEventsForContentTagDeleted(Content content) throws TskCoreException { + caseDB.acquireSingleUserCaseWriteLock(); + try (CaseDbConnection conn = caseDB.getConnection()) { + if (caseDB.getContentTagsByContent(content).isEmpty()) { + Map<Long, Long> eventIDs = getEventAndDescriptionIDs(conn, content.getId(), false); + updateEventSourceTaggedFlag(conn, eventIDs.values(), 0); + return eventIDs.keySet(); + } else { + return Collections.emptySet(); + } + } finally { + caseDB.releaseSingleUserCaseWriteLock(); + } } /** - * Get a map containging event_id and their corresponding - * event_description_ids. + * Finds all of the timeline events directly associated with a given + * artifact and marks them as having an event source that is tagged. * - * @param fileObjID get event Ids for events that are derived from the - * file with this id. - * @param artifactClause SQL clause that clients can pass in to filter the - * returned ids. + * @param artifact The artifact. * - * @return A map from event_id to event_decsription_id. + * @return The event IDs of the events that were marked as having a tagged + * event source. * - * @throws TskCoreException + * @throws TskCoreException If there is an error updating the case database. */ - private Map<Long, Long> getEventAndDescriptionIDsHelper(long fileObjID, String artifactClause) throws TskCoreException { - //map from event_id to the event_description_id for that event. - Map<Long, Long> eventIDToDescriptionIDs = new HashMap<>(); - String sql = "SELECT event_id, tsk_events.event_description_id" - + " FROM tsk_events " - + " LEFT JOIN tsk_event_descriptions ON ( tsk_events.event_description_id = tsk_event_descriptions.event_description_id )" - + " WHERE file_obj_id = " + fileObjID - + artifactClause; - - sleuthkitCase.acquireSingleUserCaseReadLock(); - try (CaseDbConnection con = sleuthkitCase.getConnection(); - Statement selectStmt = con.createStatement(); - ResultSet executeQuery = selectStmt.executeQuery(sql);) { - while (executeQuery.next()) { - eventIDToDescriptionIDs.put(executeQuery.getLong("event_id"), executeQuery.getLong("event_description_id")); //NON-NLS - } - } catch (SQLException ex) { - throw new TskCoreException("Error getting event description ids for object id = " + fileObjID, ex); + public Set<Long> updateEventsForArtifactTagAdded(BlackboardArtifact artifact) throws TskCoreException { + caseDB.acquireSingleUserCaseWriteLock(); + try (CaseDbConnection conn = caseDB.getConnection()) { + Map<Long, Long> eventIDs = getEventAndDescriptionIDs(conn, artifact.getObjectID(), artifact.getArtifactID()); + updateEventSourceTaggedFlag(conn, eventIDs.values(), 1); + return eventIDs.keySet(); } finally { - sleuthkitCase.releaseSingleUserCaseReadLock(); + caseDB.releaseSingleUserCaseWriteLock(); } - return eventIDToDescriptionIDs; } /** - * Set any events with the given object and artifact ids as tagged. + * Finds all of the timeline events directly associated with a given + * artifact and marks them as not having an event source that is tagged, if + * and only if there are no other tags on the artifact. * - * @param fileObjId the obj_id that this tag applies to, the id of the - * content that the artifact is derived from for artifact - * tags - * @param artifactID the artifact_id that this tag applies to, or null if - * this is a content tag - * @param tagged true to mark the matching events tagged, false to mark - * them as untagged + * @param artifact The artifact. * - * @return the event ids that match the object/artifact pair. + * @return The event IDs of the events that were marked as not having a + * tagged event source. * - * @throws org.sleuthkit.datamodel.TskCoreException + * @throws TskCoreException If there is an error updating the case database. */ - public Set<Long> setEventsTagged(long fileObjId, Long artifactID, boolean tagged) throws TskCoreException { - sleuthkitCase.acquireSingleUserCaseWriteLock(); - Map<Long, Long> eventIDs; // map from event_ids to event_description_ids - if (Objects.isNull(artifactID)) { - eventIDs = getEventAndDescriptionIDs(fileObjId, false); - } else { - eventIDs = getEventAndDescriptionIDs(fileObjId, artifactID); + public Set<Long> updateEventsForArtifactTagDeleted(BlackboardArtifact artifact) throws TskCoreException { + caseDB.acquireSingleUserCaseWriteLock(); + try (CaseDbConnection conn = caseDB.getConnection()) { + if (caseDB.getBlackboardArtifactTagsByArtifact(artifact).isEmpty()) { + Map<Long, Long> eventIDs = getEventAndDescriptionIDs(conn, artifact.getObjectID(), artifact.getArtifactID()); + updateEventSourceTaggedFlag(conn, eventIDs.values(), 0); + return eventIDs.keySet(); + } else { + return Collections.emptySet(); + } + } finally { + caseDB.releaseSingleUserCaseWriteLock(); } + } - //update tagged state for all event with selected ids - try (CaseDbConnection con = sleuthkitCase.getConnection(); - Statement updateStatement = con.createStatement();) { - updateStatement.executeUpdate("UPDATE tsk_event_descriptions SET tagged = " + booleanToInt(tagged) - + " WHERE event_description_id IN (" + buildCSVString(eventIDs.values()) + ")"); //NON-NLS + private void updateEventSourceTaggedFlag(CaseDbConnection conn, Collection<Long> eventDescriptionIDs, int flagValue) throws TskCoreException { + String sql = "UPDATE tsk_event_descriptions SET tagged = " + flagValue + " WHERE event_description_id IN (" + buildCSVString(eventDescriptionIDs) + ")"; //NON-NLS + try (Statement updateStatement = conn.createStatement()) { + updateStatement.executeUpdate(sql); } catch (SQLException ex) { - throw new TskCoreException("Error marking events tagged", ex);//NON-NLS - } finally { - sleuthkitCase.releaseSingleUserCaseWriteLock(); + throw new TskCoreException("Error marking content events tagged: " + sql, ex);//NON-NLS } - return eventIDs.keySet(); } - public Set<Long> setEventsHashed(long fileObjdId, boolean hashHits) throws TskCoreException { - sleuthkitCase.acquireSingleUserCaseWriteLock(); - Map<Long, Long> eventIDs = getEventAndDescriptionIDs(fileObjdId, true); - - try (CaseDbConnection con = sleuthkitCase.getConnection(); - Statement updateStatement = con.createStatement();) { - updateStatement.executeUpdate("UPDATE tsk_event_descriptions SET hash_hit = " + booleanToInt(hashHits) //NON-NLS - + " WHERE event_description_id IN (" + buildCSVString(eventIDs.values()) + ")"); //NON-NLS + /** + * Finds all of the timeline events associated directly or indirectly with a + * given content and marks them as having an event source that has a hash + * set hit. This includes both the events that have the content as their + * event source and the events for which the content is the source content + * for the source artifact of the event. + * + * @param content The content. + * + * @return The event IDs of the events that were marked as having an event + * source with a hash set hit. + * + * @throws TskCoreException If there is an error updating the case database. + */ + public Set<Long> updateEventsForHashSetHit(Content content) throws TskCoreException { + caseDB.acquireSingleUserCaseWriteLock(); + try (CaseDbConnection con = caseDB.getConnection(); Statement updateStatement = con.createStatement();) { + Map<Long, Long> eventIDs = getEventAndDescriptionIDs(con, content.getId(), true); + String sql = "UPDATE tsk_event_descriptions SET hash_hit = 1" + " WHERE event_description_id IN (" + buildCSVString(eventIDs.values()) + ")"; //NON-NLS + try { + updateStatement.executeUpdate(sql); //NON-NLS + return eventIDs.keySet(); + } catch (SQLException ex) { + throw new TskCoreException("Error setting hash_hit of events.", ex);//NON-NLS + } } catch (SQLException ex) { throw new TskCoreException("Error setting hash_hit of events.", ex);//NON-NLS } finally { - sleuthkitCase.releaseSingleUserCaseWriteLock(); + caseDB.releaseSingleUserCaseWriteLock(); } - return eventIDs.keySet(); } void rollBackTransaction(SleuthkitCase.CaseDbTransaction trans) throws TskCoreException { @@ -766,35 +850,36 @@ void rollBackTransaction(SleuthkitCase.CaseDbTransaction trans) throws TskCoreEx } /** - * Count all the events with the given options and return a map organizing - * the counts in a hierarchy from date > eventtype> count - * - * @param startTime events before this time will be excluded (seconds from - * unix epoch) - * @param endTime events at or after this time will be excluded (seconds - * from unix epoch) - * @param filter only events that pass this filter will be counted - * @param zoomLevel only events of this type or a subtype will be counted - * and the counts will be organized into bins for each of - * the subtypes of the given event type - * - * @return a map organizing the counts in a hierarchy from date > eventtype> - * count - * - * @throws org.sleuthkit.datamodel.TskCoreException + * Counts the timeline events events that satisfy the given conditions. + * + * @param startTime Events that occurred before this time are not + * counted (units: seconds from UNIX epoch) + * @param endTime Events that occurred at or after this time are + * not counted (seconds from unix epoch) + * @param filter Events that fall within the specified time range + * are only ocunted if they pass this filter. + * @param typeHierachyLevel Events that fall within the specified time range + * and pass the specified filter asre only counted + * if their types are at the specified level of the + * event type hierarchy. + * + * @return The event counts for each event type at the specified level in + * the event types hierarchy. + * + * @throws TskCoreException If there is an error querying the case database. */ - public Map<TimelineEventType, Long> countEventsByType(Long startTime, final Long endTime, TimelineFilter.RootFilter filter, TimelineEventType.TypeLevel zoomLevel) throws TskCoreException { + public Map<TimelineEventType, Long> countEventsByType(Long startTime, Long endTime, TimelineFilter.RootFilter filter, TimelineEventType.HierarchyLevel typeHierachyLevel) throws TskCoreException { long adjustedEndTime = Objects.equals(startTime, endTime) ? endTime + 1 : endTime; //do we want the base or subtype column of the databse - String typeColumn = typeColumnHelper(TimelineEventType.TypeLevel.SUB_TYPE.equals(zoomLevel)); + String typeColumn = typeColumnHelper(TimelineEventType.HierarchyLevel.EVENT.equals(typeHierachyLevel)); String queryString = "SELECT count(DISTINCT tsk_events.event_id) AS count, " + typeColumn//NON-NLS + " FROM " + getAugmentedEventsTablesSQL(filter)//NON-NLS + " WHERE time >= " + startTime + " AND time < " + adjustedEndTime + " AND " + getSQLWhere(filter) // NON-NLS + " GROUP BY " + typeColumn; // NON-NLS - sleuthkitCase.acquireSingleUserCaseReadLock(); - try (CaseDbConnection con = sleuthkitCase.getConnection(); + caseDB.acquireSingleUserCaseReadLock(); + try (CaseDbConnection con = caseDB.getConnection(); Statement stmt = con.createStatement(); ResultSet results = stmt.executeQuery(queryString);) { Map<TimelineEventType, Long> typeMap = new HashMap<>(); @@ -809,7 +894,7 @@ public Map<TimelineEventType, Long> countEventsByType(Long startTime, final Long } catch (SQLException ex) { throw new TskCoreException("Error getting count of events from db: " + queryString, ex); // NON-NLS } finally { - sleuthkitCase.releaseSingleUserCaseReadLock(); + caseDB.releaseSingleUserCaseReadLock(); } } @@ -848,11 +933,23 @@ static private String getAugmentedEventsTablesSQL(TimelineFilter.RootFilter filt * @param needMimeTypes True if the filters require joining to the tsk_files * table for the mime_type. * - * @return An SQL expression that produces an events table augmented with the - * columns required by the filters. + * @return An SQL expression that produces an events table augmented with + * the columns required by the filters. */ static private String getAugmentedEventsTablesSQL(boolean needMimeTypes) { - return "( select event_id, time, tsk_event_descriptions.data_source_obj_id, file_obj_id, artifact_id, " + /* + * Regarding the timeline event tables schema, note that several columns + * in the tsk_event_descriptions table seem, at first glance, to be + * attributes of events rather than their descriptions and would appear + * to belong in tsk_events table instead. The rationale for putting the + * data source object ID, content object ID, artifact ID and the flags + * indicating whether or not the event source has a hash set hit or is + * tagged were motivated by the fact that these attributes are identical + * for each event in a set of file system file MAC time events. The + * decision was made to avoid duplication and save space by placing this + * data in the tsk_event-descriptions table. + */ + return "( SELECT event_id, time, tsk_event_descriptions.data_source_obj_id, content_obj_id, artifact_id, " + " full_description, med_description, short_description, tsk_events.event_type_id, super_type_id," + " hash_hit, tagged " + (needMimeTypes ? ", mime_type" : "") @@ -860,7 +957,7 @@ static private String getAugmentedEventsTablesSQL(boolean needMimeTypes) { + " JOIN tsk_event_descriptions ON ( tsk_event_descriptions.event_description_id = tsk_events.event_description_id)" + " JOIN tsk_event_types ON (tsk_events.event_type_id = tsk_event_types.event_type_id ) " + (needMimeTypes ? " LEFT OUTER JOIN tsk_files " - + " ON (tsk_event_descriptions.file_obj_id = tsk_files.obj_id)" + + " ON (tsk_event_descriptions.content_obj_id = tsk_files.obj_id)" : "") + ") AS tsk_events"; } @@ -879,61 +976,62 @@ private static int booleanToInt(boolean value) { private static boolean intToBoolean(int value) { return value != 0; } - + /** - * Returns a list of TimelineEvents for the given filter and time range. - * - * @param timeRange - * @param filter TimelineFilter.RootFilter for filtering data - * - * @return A list of TimelineEvents for given parameters, if filter is null - * or times are invalid an empty list will be returned. - * - * @throws TskCoreException + * Gets the timeline events that fall within a given time interval and + * satisfy a given event filter. + * + * @param timeRange The time level. + * @param filter The event filter. + * + * @return The list of events that fall within the specified interval and + * poass the specified filter. + * + * @throws TskCoreException If there is an error querying the case database. */ - public List<TimelineEvent> getEvents(Interval timeRange, TimelineFilter.RootFilter filter) throws TskCoreException{ + public List<TimelineEvent> getEvents(Interval timeRange, TimelineFilter.RootFilter filter) throws TskCoreException { List<TimelineEvent> events = new ArrayList<>(); - + Long startTime = timeRange.getStartMillis() / 1000; Long endTime = timeRange.getEndMillis() / 1000; if (Objects.equals(startTime, endTime)) { endTime++; //make sure end is at least 1 millisecond after start } - + if (filter == null) { return events; } - + if (endTime < startTime) { return events; } //build dynamic parts of query - String querySql = "SELECT time, file_obj_id, data_source_obj_id, artifact_id, " // NON-NLS - + " event_id, " //NON-NLS - + " hash_hit, " //NON-NLS - + " tagged, " //NON-NLS - + " event_type_id, super_type_id, " - + " full_description, med_description, short_description " // NON-NLS - + " FROM " + getAugmentedEventsTablesSQL(filter) // NON-NLS - + " WHERE time >= " + startTime + " AND time < " + endTime + " AND " + getSQLWhere(filter) // NON-NLS - + " ORDER BY time"; // NON-NLS - - sleuthkitCase.acquireSingleUserCaseReadLock(); - try (CaseDbConnection con = sleuthkitCase.getConnection(); + String querySql = "SELECT time, content_obj_id, data_source_obj_id, artifact_id, " // NON-NLS + + " event_id, " //NON-NLS + + " hash_hit, " //NON-NLS + + " tagged, " //NON-NLS + + " event_type_id, super_type_id, " + + " full_description, med_description, short_description " // NON-NLS + + " FROM " + getAugmentedEventsTablesSQL(filter) // NON-NLS + + " WHERE time >= " + startTime + " AND time < " + endTime + " AND " + getSQLWhere(filter) // NON-NLS + + " ORDER BY time"; // NON-NLS + + caseDB.acquireSingleUserCaseReadLock(); + try (CaseDbConnection con = caseDB.getConnection(); Statement stmt = con.createStatement(); ResultSet resultSet = stmt.executeQuery(querySql);) { - - while (resultSet.next()) { - int eventTypeID = resultSet.getInt("event_type_id"); + + while (resultSet.next()) { + int eventTypeID = resultSet.getInt("event_type_id"); TimelineEventType eventType = getEventType(eventTypeID).orElseThrow(() -> new TskCoreException("Error mapping event type id " + eventTypeID + "to EventType."));//NON-NLS - TimelineEvent event = new TimelineEvent( + TimelineEvent event = new TimelineEvent( resultSet.getLong("event_id"), // NON-NLS resultSet.getLong("data_source_obj_id"), // NON-NLS - resultSet.getLong("file_obj_id"), // NON-NLS + resultSet.getLong("content_obj_id"), // NON-NLS resultSet.getLong("artifact_id"), // NON-NLS resultSet.getLong("time"), // NON-NLS eventType, @@ -942,16 +1040,16 @@ public List<TimelineEvent> getEvents(Interval timeRange, TimelineFilter.RootFilt resultSet.getString("short_description"), // NON-NLS resultSet.getInt("hash_hit") != 0, //NON-NLS resultSet.getInt("tagged") != 0); - + events.add(event); - } - + } + } catch (SQLException ex) { throw new TskCoreException("Error getting events from db: " + querySql, ex); // NON-NLS } finally { - sleuthkitCase.releaseSingleUserCaseReadLock(); + caseDB.releaseSingleUserCaseReadLock(); } - + return events; } @@ -962,7 +1060,7 @@ public List<TimelineEvent> getEvents(Interval timeRange, TimelineFilter.RootFilt * * @return column name to use depending on if we want base types or subtypes */ - static String typeColumnHelper(final boolean useSubTypes) { + private static String typeColumnHelper(final boolean useSubTypes) { return useSubTypes ? "event_type_id" : "super_type_id"; //NON-NLS } @@ -986,26 +1084,14 @@ String getSQLWhere(TimelineFilter.RootFilter filter) { return result; } - String getDescriptionColumn(TimelineEvent.DescriptionLevel lod) { - switch (lod) { - case FULL: - return "full_description"; //NON-NLS - case MEDIUM: - return "med_description"; //NON-NLS - case SHORT: - default: - return "short_description"; //NON-NLS - } - } - - String getTrueLiteral() { - switch (sleuthkitCase.getDatabaseType()) { + private String getTrueLiteral() { + switch (caseDB.getDatabaseType()) { case POSTGRESQL: return "TRUE";//NON-NLS case SQLITE: return "1";//NON-NLS default: - throw new UnsupportedOperationException("Unsupported DB type: " + sleuthkitCase.getDatabaseType().name());//NON-NLS + throw new UnsupportedOperationException("Unsupported DB type: " + caseDB.getDatabaseType().name());//NON-NLS } } @@ -1035,7 +1121,7 @@ public TimelineEvent getAddedEvent() { * @param <O> Output type. */ @FunctionalInterface - interface TSKCoreCheckedFunction<I, O> { + private interface TSKCoreCheckedFunction<I, O> { O apply(I input) throws TskCoreException; } diff --git a/bindings/java/src/org/sleuthkit/datamodel/blackboardutils/CommunicationArtifactsHelper.java b/bindings/java/src/org/sleuthkit/datamodel/blackboardutils/CommunicationArtifactsHelper.java index 31c2b5720930a7e35f6531e9d8d1109a054a805a..373fa2459369f9a337aa960af27ca63c78d8e820 100644 --- a/bindings/java/src/org/sleuthkit/datamodel/blackboardutils/CommunicationArtifactsHelper.java +++ b/bindings/java/src/org/sleuthkit/datamodel/blackboardutils/CommunicationArtifactsHelper.java @@ -159,23 +159,22 @@ public CommunicationArtifactsHelper(SleuthkitCase caseDb, * It creates an account instance with specified type & id, and uses it as * the self account. * - * @param caseDb Sleuthkit case db. - * @param moduleName Name of module using the helper. - * @param srcFile Source file being processed by the module. - * @param accountsType Account type {@link Account.Type} created by - * this module. - * @param selfAccountType Self account type to be created for this - * module. - * @param selfAccountAddress Account unique id for the self account. + * @param caseDb Sleuthkit case db. + * @param moduleName Name of module using the helper. + * @param srcFile Source file being processed by the module. + * @param accountsType Account type {@link Account.Type} created by this + * module. + * @param selfAccountType Self account type to be created for this module. + * @param selfAccountId Account unique id for the self account. * * @throws TskCoreException If there is an error creating the self account */ - public CommunicationArtifactsHelper(SleuthkitCase caseDb, String moduleName, AbstractFile srcFile, Account.Type accountsType, Account.Type selfAccountType, Account.Address selfAccountAddress) throws TskCoreException { + public CommunicationArtifactsHelper(SleuthkitCase caseDb, String moduleName, AbstractFile srcFile, Account.Type accountsType, Account.Type selfAccountType, String selfAccountId) throws TskCoreException { super(caseDb, moduleName, srcFile); this.accountsType = accountsType; - this.selfAccountInstance = getSleuthkitCase().getCommunicationsManager().createAccountFileInstance(selfAccountType, selfAccountAddress.getUniqueID(), moduleName, getAbstractFile()); + this.selfAccountInstance = getSleuthkitCase().getCommunicationsManager().createAccountFileInstance(selfAccountType, selfAccountId, moduleName, getAbstractFile()); } /** @@ -183,14 +182,15 @@ public CommunicationArtifactsHelper(SleuthkitCase caseDb, String moduleName, Abs * attributes. Also creates an account instance of specified type for the * contact with the specified ID. * - * @param contactAccountUniqueID Unique id for contact account, required. - * @param contactName Name of contact, required. - * @param phoneNumber Primary phone number for contact, may be - * empty or null. - * @param homePhoneNumber Home phone number, may be empty or null. - * @param mobilePhoneNumber Mobile phone number, may be empty or null. - * @param emailAddr Email address for the contact, may be empty - * or null. + * @param contactName Contact name, required. + * @param phoneNumber Primary phone number for contact, may be empty + * or null. + * @param homePhoneNumber Home phone number, may be empty or null. + * @param mobilePhoneNumber Mobile phone number, may be empty or null. + * @param emailAddr Email address for the contact, may be empty or + * null. + * + * At least one phone number or email address is required. * * @return Contact artifact created. * @@ -198,10 +198,10 @@ public CommunicationArtifactsHelper(SleuthkitCase caseDb, String moduleName, Abs * @throws BlackboardException If there is a problem posting the artifact. * */ - public BlackboardArtifact addContact(String contactAccountUniqueID, String contactName, + public BlackboardArtifact addContact(String contactName, String phoneNumber, String homePhoneNumber, String mobilePhoneNumber, String emailAddr) throws TskCoreException, BlackboardException { - return addContact(contactAccountUniqueID, contactName, phoneNumber, + return addContact(contactName, phoneNumber, homePhoneNumber, mobilePhoneNumber, emailAddr, Collections.emptyList()); } @@ -211,29 +211,57 @@ public BlackboardArtifact addContact(String contactAccountUniqueID, String conta * attributes. Also creates an account instance for the contact with the * specified ID. * - * @param contactAccountUniqueID Unique id for contact account, required. - * @param contactName Name of contact, required. - * @param phoneNumber Primary phone number for contact, may be - * empty or null. - * @param homePhoneNumber Home phone number, may be empty or null. - * @param mobilePhoneNumber Mobile phone number, may be empty or null. - * @param emailAddr Email address for the contact, may be empty - * or null. + * @param contactName Contact name, required + * @param phoneNumber Primary phone number for contact, may be + * empty or null. + * @param homePhoneNumber Home phone number, may be empty or null. + * @param mobilePhoneNumber Mobile phone number, may be empty or null. + * @param emailAddr Email address for the contact, may be empty + * or null. + * + * At least one phone number or email address or an Id is required. + * An Id may be passed in as a TSK_ID attribute in additionalAttributes. * - * @param additionalAttributes Additional attributes for contact, may be - * an empty list. + * @param additionalAttributes Additional attributes for contact, may be an + * empty list. * * @return contact artifact created. * - * @throws TskCoreException If there is an error creating the artifact. + * @throws TskCoreException If there is an error creating the artifact. * @throws BlackboardException If there is a problem posting the artifact. * */ - public BlackboardArtifact addContact(String contactAccountUniqueID, String contactName, + public BlackboardArtifact addContact(String contactName, String phoneNumber, String homePhoneNumber, String mobilePhoneNumber, String emailAddr, Collection<BlackboardAttribute> additionalAttributes) throws TskCoreException, BlackboardException { + // Contact name must be provided + if (StringUtils.isEmpty(contactName)) { + throw new IllegalArgumentException("Contact name must be specified."); + } + + // check if the caller has included any phone/email/id in addtional attributes + boolean hasAnyIdAttribute = false; + if (additionalAttributes != null) { + for (BlackboardAttribute attr : additionalAttributes) { + if ((attr.getAttributeType().getTypeName().startsWith("TSK_PHONE")) || + (attr.getAttributeType().getTypeName().startsWith("TSK_EMAIL")) || + (attr.getAttributeType().getTypeName().startsWith("TSK_ID"))) { + hasAnyIdAttribute = true; + break; + } + } + } + + // At least one phone number or email address + // or an optional attribute with phone/email/id must be provided + if (StringUtils.isEmpty(phoneNumber) && StringUtils.isEmpty(homePhoneNumber) + && StringUtils.isEmpty(mobilePhoneNumber) && StringUtils.isEmpty(emailAddr) + && (!hasAnyIdAttribute)) { + throw new IllegalArgumentException("At least one phone number or email address or an id must be provided."); + } + BlackboardArtifact contactArtifact; Collection<BlackboardAttribute> attributes = new ArrayList<>(); @@ -252,17 +280,58 @@ public BlackboardArtifact addContact(String contactAccountUniqueID, String conta attributes.addAll(additionalAttributes); contactArtifact.addAttributes(attributes); - // Find/Create an account instance for the contact - // Create a relationship between selfAccount and contactAccount - AccountFileInstance contactAccountInstance = createAccountInstance(accountsType, contactAccountUniqueID); - addRelationship(selfAccountInstance, contactAccountInstance, contactArtifact, Relationship.Type.CONTACT, 0); - + // create an account for each specified contact method, and a relationship with self account + createContactMethodAccountAndRelationship(Account.Type.PHONE, phoneNumber, contactArtifact, 0); + createContactMethodAccountAndRelationship(Account.Type.PHONE, homePhoneNumber, contactArtifact, 0); + createContactMethodAccountAndRelationship(Account.Type.PHONE, mobilePhoneNumber, contactArtifact, 0); + createContactMethodAccountAndRelationship(Account.Type.EMAIL, emailAddr, contactArtifact, 0); + + // if the additional attribute list has any phone/email/id attributes, create accounts & relationships for those. + if ((additionalAttributes != null) && hasAnyIdAttribute) { + for (BlackboardAttribute bba : additionalAttributes) { + if (bba.getAttributeType().getTypeName().startsWith("TSK_PHONE")) { + createContactMethodAccountAndRelationship(Account.Type.PHONE, bba.getValueString(), contactArtifact, 0); + } else if (bba.getAttributeType().getTypeName().startsWith("TSK_EMAIL")) { + createContactMethodAccountAndRelationship(Account.Type.EMAIL, bba.getValueString(), contactArtifact, 0); + } else if (bba.getAttributeType().getTypeName().startsWith("TSK_ID")) { + createContactMethodAccountAndRelationship(this.accountsType, bba.getValueString(), contactArtifact, 0); + } + } + } + // post artifact getSleuthkitCase().getBlackboard().postArtifact(contactArtifact, getModuleName()); return contactArtifact; } + /** + * Creates a contact's account instance of specified account type, if the + * account id is not null/empty. + * + * Also creates a CONTACT relationship between the self account and the new + * contact account. + */ + private void createContactMethodAccountAndRelationship(Account.Type accountType, + String accountUniqueID, BlackboardArtifact sourceArtifact, + long dateTime) throws TskCoreException { + + // Find/Create an account instance for each of the contact method + // Create a relationship between selfAccount and contactAccount + if (!StringUtils.isEmpty(accountUniqueID)) { + AccountFileInstance contactAccountInstance = createAccountInstance(accountsType, accountUniqueID); + + // Create a relationship between self account and the contact account + try { + getSleuthkitCase().getCommunicationsManager().addRelationships(selfAccountInstance, + Collections.singletonList(contactAccountInstance), sourceArtifact, Relationship.Type.CONTACT, dateTime); + } catch (TskDataException ex) { + throw new TskCoreException(String.format("Failed to create relationship between account = %s and account = %s.", + selfAccountInstance.getAccount(), contactAccountInstance.getAccount()), ex); + } + } + } + /** * Creates an account file instance{@link AccountFileInstance} associated * with the DB file. @@ -279,31 +348,6 @@ private AccountFileInstance createAccountInstance(Account.Type accountType, Stri return getSleuthkitCase().getCommunicationsManager().createAccountFileInstance(accountType, accountUniqueID, getModuleName(), getAbstractFile()); } - /** - * Adds a relations between the two specified account instances. - * - * @param selfAccount 'Self' account. - * @param otherAccount Other account. - * @param sourceArtifact Artifact from which the relationship is derived. - * @param relationshipType Type of relationship. - * @param dateTime Date/time of relationship. - * - * @throws TskCoreException If there is an error creating relationship. - */ - private void addRelationship(AccountFileInstance selfAccountInstance, AccountFileInstance otherAccountInstance, - BlackboardArtifact sourceArtifact, Relationship.Type relationshipType, long dateTime) throws TskCoreException { - - if (selfAccountInstance.getAccount() != otherAccountInstance.getAccount()) { - try { - getSleuthkitCase().getCommunicationsManager().addRelationships(selfAccountInstance, - Collections.singletonList(otherAccountInstance), sourceArtifact, relationshipType, dateTime); - } catch (TskDataException ex) { - throw new TskCoreException(String.format("Failed to create relationship between account = %s and account = %s.", - selfAccountInstance.getAccount(), otherAccountInstance.getAccount()), ex); - } - } - } - /** * Adds a TSK_MESSAGE artifact. * @@ -312,13 +356,13 @@ private void addRelationship(AccountFileInstance selfAccountInstance, AccountFil * * @param messageType Message type, required. * @param direction Message direction, UNKNOWN if not available. - * @param fromAddress Sender address, may be null. - * @param toAddress Recipient address, may be null. + * @param senderId Sender address id, may be null. + * @param recipientId Recipient id, may be null. * @param dateTime Date/time of message, 0 if not available. * @param readStatus Message read status, UNKNOWN if not available. * @param subject Message subject, may be empty or null. * @param messageText Message body, may be empty or null. - * @param threadId, Message thread id, may be empty or null. + * @param threadId Message thread id, may be empty or null. * * @return Message artifact. * @@ -328,12 +372,12 @@ private void addRelationship(AccountFileInstance selfAccountInstance, AccountFil public BlackboardArtifact addMessage( String messageType, CommunicationDirection direction, - Account.Address fromAddress, - Account.Address toAddress, + String senderId, + String recipientId, long dateTime, MessageReadStatus readStatus, String subject, String messageText, String threadId) throws TskCoreException, BlackboardException { return addMessage(messageType, direction, - fromAddress, toAddress, dateTime, readStatus, + senderId, recipientId, dateTime, readStatus, subject, messageText, threadId, Collections.emptyList()); } @@ -346,13 +390,13 @@ public BlackboardArtifact addMessage( * * @param messageType Message type, required. * @param direction Message direction, UNKNOWN if not available. - * @param fromAddress Sender address, may be null. - * @param toAddress Recipient address, may be null. + * @param senderId Sender id, may be null. + * @param recipientId Recipient id, may be null. * @param dateTime Date/time of message, 0 if not available. * @param readStatus Message read status, UNKNOWN if not available. * @param subject Message subject, may be empty or null. * @param messageText Message body, may be empty or null. - * @param threadId, Message thread id, may be empty or null. + * @param threadId Message thread id, may be empty or null. * @param otherAttributesList Additional attributes, may be an empty list. * * @return Message artifact. @@ -362,15 +406,15 @@ public BlackboardArtifact addMessage( */ public BlackboardArtifact addMessage(String messageType, CommunicationDirection direction, - Account.Address fromAddress, - Account.Address toAddress, + String senderId, + String recipientId, long dateTime, MessageReadStatus readStatus, String subject, String messageText, String threadId, Collection<BlackboardAttribute> otherAttributesList) throws TskCoreException, BlackboardException { return addMessage(messageType, direction, - fromAddress, - Arrays.asList(toAddress), + senderId, + Arrays.asList(recipientId), dateTime, readStatus, subject, messageText, threadId, otherAttributesList); @@ -383,16 +427,15 @@ public BlackboardArtifact addMessage(String messageType, * relationship between the self account and the sender/receiver accounts. * * - * @param messageType Message type, required. - * @param direction Message direction, UNKNOWN if not available. - * @param fromAddress Sender address, may be null. - * @param recipientsList Recipient address list, may be null or empty an - * list. - * @param dateTime Date/time of message, 0 if not available. - * @param readStatus Message read status, UNKNOWN if not available. - * @param subject Message subject, may be empty or null. - * @param messageText Message body, may be empty or null. - * @param threadId, Message thread id, may be empty or null. + * @param messageType Message type, required. + * @param direction Message direction, UNKNOWN if not available. + * @param senderId Sender id, may be null. + * @param recipientIdsList Recipient ids list, may be null or empty list. + * @param dateTime Date/time of message, 0 if not available. + * @param readStatus Message read status, UNKNOWN if not available. + * @param subject Message subject, may be empty or null. + * @param messageText Message body, may be empty or null. + * @param threadId Message thread id, may be empty or null. * * @return Message artifact. * @@ -401,12 +444,12 @@ public BlackboardArtifact addMessage(String messageType, */ public BlackboardArtifact addMessage(String messageType, CommunicationDirection direction, - Account.Address fromAddress, - List<Account.Address> recipientsList, + String senderId, + List<String> recipientIdsList, long dateTime, MessageReadStatus readStatus, String subject, String messageText, String threadId) throws TskCoreException, BlackboardException { return addMessage(messageType, direction, - fromAddress, recipientsList, + senderId, recipientIdsList, dateTime, readStatus, subject, messageText, threadId, Collections.emptyList()); @@ -415,19 +458,18 @@ public BlackboardArtifact addMessage(String messageType, /** * Adds a TSK_MESSAGE artifact. * - * Also creates an account instance for the sender/receivers, and creates a - * relationship between the self account and the sender/receivers account. + * Also creates accounts for the sender/receivers, and creates relationships + * between the sender/receivers account. * * @param messageType Message type, required. * @param direction Message direction, UNKNOWN if not available. - * @param fromAddress Sender address, may be null. - * @param recipientsList Recipient address list, may be null or empty - * an list. + * @param senderId Sender id, may be null. + * @param recipientIdsList Recipient list, may be null or empty an list. * @param dateTime Date/time of message, 0 if not available. * @param readStatus Message read status, UNKNOWN if not available. * @param subject Message subject, may be empty or null. * @param messageText Message body, may be empty or null. - * @param threadId, Message thread id, may be empty or null. + * @param threadId Message thread id, may be empty or null. * @param otherAttributesList Other attributes, may be an empty list. * * @return Message artifact. @@ -437,8 +479,8 @@ public BlackboardArtifact addMessage(String messageType, */ public BlackboardArtifact addMessage(String messageType, CommunicationDirection direction, - Account.Address fromAddress, - List<Account.Address> recipientsList, + String senderId, + List<String> recipientIdsList, long dateTime, MessageReadStatus readStatus, String subject, String messageText, String threadId, @@ -458,12 +500,29 @@ public BlackboardArtifact addMessage(String messageType, addMessageReadStatusIfKnown(readStatus, attributes); addCommDirectionIfKnown(direction, attributes); - if (fromAddress != null && !StringUtils.isEmpty(fromAddress.getDisplayName())) { - attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_FROM, getModuleName(), fromAddress.getDisplayName())); + // set sender attribute and create sender account + AccountFileInstance senderAccountInstance; + if (StringUtils.isEmpty(senderId)) { + senderAccountInstance = selfAccountInstance; + addAttributeIfNotNull(ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_FROM, selfAccountInstance.getAccount().getTypeSpecificID(), attributes); + } else { + senderAccountInstance = createAccountInstance(accountsType, senderId); + addAttributeIfNotNull(ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_FROM, senderId, attributes); + } + + // set recipient attribute and create recipient accounts + List<AccountFileInstance> recipientAccountsList = new ArrayList(); + String recipientsStr = ""; + if (recipientIdsList != null) { + for (String recipient : recipientIdsList) { + if (!StringUtils.isEmpty(recipient)) { + recipientAccountsList.add(createAccountInstance(accountsType, recipient)); + } + } + // Create a comma separated string of recipients + recipientsStr = addressListToString(recipientIdsList); + addAttributeIfNotNull(ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_TO, recipientsStr, attributes); } - // Create a comma separated string of recipients - String toAddresses = addressListToString(recipientsList); - addAttributeIfNotNull(ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_TO, toAddresses, attributes); addAttributeIfNotNull(ATTRIBUTE_TYPE.TSK_SUBJECT, subject, attributes); addAttributeIfNotNull(ATTRIBUTE_TYPE.TSK_TEXT, messageText, attributes); @@ -473,11 +532,14 @@ public BlackboardArtifact addMessage(String messageType, attributes.addAll(otherAttributesList); msgArtifact.addAttributes(attributes); - // create account and relationship with sender - createSenderAccountAndRelationship(fromAddress, msgArtifact, Relationship.Type.MESSAGE, dateTime); - - // create account and relationship with each recipient - createRecipientAccountsAndRelationships(recipientsList, msgArtifact, Relationship.Type.MESSAGE, dateTime); + // create sender/recipient relationships + try { + getSleuthkitCase().getCommunicationsManager().addRelationships(senderAccountInstance, + recipientAccountsList, msgArtifact, Relationship.Type.MESSAGE, dateTime); + } catch (TskDataException ex) { + throw new TskCoreException(String.format("Failed to create Message relationships between sender account = %s and recipients = %s.", + senderAccountInstance.getAccount().getTypeSpecificID(), recipientsStr), ex); + } // post artifact getSleuthkitCase().getBlackboard().postArtifact(msgArtifact, getModuleName()); @@ -494,8 +556,11 @@ public BlackboardArtifact addMessage(String messageType, * between the self account and the callee account. * * @param direction Call direction, UNKNOWN if not available. - * @param fromAddress Caller address, may be null. - * @param toAddress Callee address, may be null. + * @param callerId Caller id, may be null. + * @param calleeId Callee id, may be null. + * + * At least one of the two must be provided - the caller Id, or a callee id. + * * @param startDateTime Start date/time, 0 if not available. * @param endDateTime End date/time, 0 if not available. * @param mediaType Media type. @@ -506,9 +571,9 @@ public BlackboardArtifact addMessage(String messageType, * @throws BlackboardException If there is a problem posting the artifact. */ public BlackboardArtifact addCalllog(CommunicationDirection direction, - Account.Address fromAddress, Account.Address toAddress, + String callerId, String calleeId, long startDateTime, long endDateTime, CallMediaType mediaType) throws TskCoreException, BlackboardException { - return addCalllog(direction, fromAddress, toAddress, + return addCalllog(direction, callerId, calleeId, startDateTime, endDateTime, mediaType, Collections.emptyList()); } @@ -521,8 +586,11 @@ public BlackboardArtifact addCalllog(CommunicationDirection direction, * between the self account and the callee account. * * @param direction Call direction, UNKNOWN if not available. - * @param fromAddress Caller address, may be null. - * @param toAddress Callee address, may be null. + * @param callerId Caller id, may be null. + * @param calleeId Callee id, may be null. + * + * At least one of the two must be provided - the caller Id, or a callee id. + * * @param startDateTime Start date/time, 0 if not available. * @param endDateTime End date/time, 0 if not available. * @param mediaType Media type. @@ -534,14 +602,14 @@ public BlackboardArtifact addCalllog(CommunicationDirection direction, * @throws BlackboardException If there is a problem posting the artifact. */ public BlackboardArtifact addCalllog(CommunicationDirection direction, - Account.Address fromAddress, - Account.Address toAddress, + String callerId, + String calleeId, long startDateTime, long endDateTime, CallMediaType mediaType, Collection<BlackboardAttribute> otherAttributesList) throws TskCoreException, BlackboardException { return addCalllog(direction, - fromAddress, - Arrays.asList(toAddress), + callerId, + Arrays.asList(calleeId), startDateTime, endDateTime, mediaType, otherAttributesList); @@ -555,8 +623,11 @@ public BlackboardArtifact addCalllog(CommunicationDirection direction, * between the self account and each callee account. * * @param direction Call direction, UNKNOWN if not available. - * @param fromAddress Caller address, may be null. - * @param toAddressList callee address list, may be an empty list. + * @param callerId Caller id, may be null. + * @param calleeIdsList Callee list, may be an empty list. + * + * At least one of the two must be provided - the caller Id, or a callee id. + * * @param startDateTime Start date/time, 0 if not available. * @param endDateTime End date/time, 0 if not available. * @param mediaType Call media type, UNKNOWN if not available. @@ -567,12 +638,12 @@ public BlackboardArtifact addCalllog(CommunicationDirection direction, * @throws BlackboardException If there is a problem posting the artifact. */ public BlackboardArtifact addCalllog(CommunicationDirection direction, - Account.Address fromAddress, - Collection<Account.Address> toAddressList, + String callerId, + Collection<String> calleeIdsList, long startDateTime, long endDateTime, CallMediaType mediaType) throws TskCoreException, BlackboardException { - return addCalllog(direction, fromAddress, toAddressList, + return addCalllog(direction, callerId, calleeIdsList, startDateTime, endDateTime, mediaType, Collections.emptyList()); @@ -581,13 +652,16 @@ public BlackboardArtifact addCalllog(CommunicationDirection direction, /** * Adds a TSK_CALLLOG artifact. * - * Also creates an account instance for the caller/callees, and creates a - * relationship between the self account and the caller account as well - * between the self account and each callee account. + * Also creates an account instance for the caller and each of the callees, + * and creates relationships between caller and callees. * * @param direction Call direction, UNKNOWN if not available. - * @param fromAddress Caller address, may be null. - * @param toAddressList callee address list, may be an empty list. + * @param callerId Caller id, required for incoming call. + * @param calleeIdsList Callee ids list, required for an outgoing + * call. + * + * At least one of the two must be provided - the caller Id, or a callee id. + * * @param startDateTime Start date/time, 0 if not available. * @param endDateTime End date/time, 0 if not available. * @param mediaType Call media type, UNKNOWN if not available. @@ -599,11 +673,17 @@ public BlackboardArtifact addCalllog(CommunicationDirection direction, * @throws BlackboardException If there is a problem posting the artifact. */ public BlackboardArtifact addCalllog(CommunicationDirection direction, - Account.Address fromAddress, - Collection<Account.Address> toAddressList, + String callerId, + Collection<String> calleeIdsList, long startDateTime, long endDateTime, CallMediaType mediaType, Collection<BlackboardAttribute> otherAttributesList) throws TskCoreException, BlackboardException { + + // Either caller id or a callee id must be provided. + if (StringUtils.isEmpty(callerId) && (isEffectivelyEmpty(calleeIdsList))) { + throw new IllegalArgumentException("Either a caller id, or at least one callee id must be provided for a call log."); + } + BlackboardArtifact callLogArtifact; Collection<BlackboardAttribute> attributes = new ArrayList<>(); @@ -615,24 +695,55 @@ public BlackboardArtifact addCalllog(CommunicationDirection direction, addAttributeIfNotZero(ATTRIBUTE_TYPE.TSK_DATETIME_END, endDateTime, attributes); addCommDirectionIfKnown(direction, attributes); - if (fromAddress != null) { - addAttributeIfNotNull(ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_FROM, fromAddress.getUniqueID(), attributes); - addAttributeIfNotNull(ATTRIBUTE_TYPE.TSK_NAME, fromAddress.getDisplayName(), attributes); + // set FROM attribute and create a caller account + AccountFileInstance callerAccountInstance; + if (StringUtils.isEmpty(callerId)) { + // for an Outgoing call, if no caller is specified, assume self account is the caller + if (direction == CommunicationDirection.OUTGOING) { + callerAccountInstance = selfAccountInstance; + addAttributeIfNotNull(ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_FROM, selfAccountInstance.getAccount().getTypeSpecificID(), attributes); + } else { // incoming call without a caller id + throw new IllegalArgumentException("Caller Id not provided for incoming call."); + } + } else { + callerAccountInstance = createAccountInstance(accountsType, callerId); + addAttributeIfNotNull(ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_FROM, callerId, attributes); } - // Create a comma separated string of recipients - String toAddresses = addressListToString(toAddressList); - addAttributeIfNotNull(ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_TO, toAddresses, attributes); + // Create a comma separated string of callee + List<AccountFileInstance> recipientAccountsList = new ArrayList(); + String calleesStr = ""; + if (! isEffectivelyEmpty(calleeIdsList)) { + calleesStr = addressListToString(calleeIdsList); + addAttributeIfNotNull(ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_TO, calleesStr, attributes); + + for (String callee : calleeIdsList) { + if (!StringUtils.isEmpty(callee)) { + recipientAccountsList.add(createAccountInstance(accountsType, callee)); + } + } + } else { + // For incoming call, if no callee specified, assume self account is callee + if (direction == CommunicationDirection.INCOMING) { + addAttributeIfNotNull(ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_TO, this.selfAccountInstance.getAccount().getTypeSpecificID(), attributes); + recipientAccountsList.add(this.selfAccountInstance); + } else { // outgoing call without any callee + throw new IllegalArgumentException("Callee not provided for an outgoing call."); + } + } // add attributes to artifact attributes.addAll(otherAttributesList); callLogArtifact.addAttributes(attributes); - // Create a relationship between selfAccount and caller - createSenderAccountAndRelationship(fromAddress, callLogArtifact, Relationship.Type.CALL_LOG, startDateTime); - - // Create a relationship between selfAccount and each callee - createRecipientAccountsAndRelationships(toAddressList, callLogArtifact, Relationship.Type.CALL_LOG, startDateTime); + // create relationships between caller/callees + try { + getSleuthkitCase().getCommunicationsManager().addRelationships(callerAccountInstance, + recipientAccountsList, callLogArtifact, Relationship.Type.CALL_LOG, startDateTime); + } catch (TskDataException ex) { + throw new TskCoreException(String.format("Failed to create Call log relationships between caller account = %s and callees = %s.", + callerAccountInstance.getAccount(), calleesStr), ex); + } // post artifact getSleuthkitCase().getBlackboard().postArtifact(callLogArtifact, getModuleName()); @@ -642,17 +753,17 @@ public BlackboardArtifact addCalllog(CommunicationDirection direction, } /** - * Converts a list of addresses into a single comma separated string of - * addresses. + * Converts a list of ids into a single comma separated string. */ - private String addressListToString(Collection<Account.Address> addressList) { + private String addressListToString(Collection<String> addressList) { String toAddresses = ""; if (addressList != null && (!addressList.isEmpty())) { StringBuilder toAddressesSb = new StringBuilder(); - for (Account.Address address : addressList) { - String displayAddress = !StringUtils.isEmpty(address.getDisplayName()) ? address.getDisplayName() : address.getUniqueID(); - toAddressesSb = toAddressesSb.length() > 0 ? toAddressesSb.append(",").append(displayAddress) : toAddressesSb.append(displayAddress); + for (String address : addressList) { + if (!StringUtils.isEmpty(address)) { + toAddressesSb = toAddressesSb.length() > 0 ? toAddressesSb.append(", ").append(address) : toAddressesSb.append(address); + } } toAddresses = toAddressesSb.toString(); } @@ -660,6 +771,29 @@ private String addressListToString(Collection<Account.Address> addressList) { return toAddresses; } + /** + * Checks if the given list of ids has at least one non-null non-blank id. + * + * @param addressList List of string ids. + * + * @return false if the list has at least one non-null non-blank id, + * otherwise true. + * + */ + private boolean isEffectivelyEmpty(Collection<String> idList) { + + if (idList == null || idList.isEmpty()) { + return true; + } + + for (String id: idList) { + if (!StringUtils.isEmpty(id)) + return false; + } + + return true; + + } /** * Adds communication direction attribute to the list, if it is not unknown. */ @@ -678,33 +812,4 @@ private void addMessageReadStatusIfKnown(MessageReadStatus readStatus, Collectio } } - /** - * Creates an account & relationship for sender, if the sender address is - * not null/empty. - */ - private void createSenderAccountAndRelationship(Account.Address fromAddress, - BlackboardArtifact artifact, Relationship.Type relationshipType, long dateTime) throws TskCoreException { - if (fromAddress != null) { - AccountFileInstance senderAccountInstance = createAccountInstance(accountsType, fromAddress.getUniqueID()); - - // Create a relationship between selfAccount and sender account - addRelationship(selfAccountInstance, senderAccountInstance, artifact, relationshipType, dateTime); - } - } - - /** - * Creates accounts & relationship with each recipient, if the recipient - * list is not null/empty. - */ - private void createRecipientAccountsAndRelationships(Collection<Account.Address> toAddressList, - BlackboardArtifact artifact, Relationship.Type relationshipType, long dateTime) throws TskCoreException { - // Create a relationship between selfAccount and each recipient - if (toAddressList != null) { - for (Account.Address recipient : toAddressList) { - AccountFileInstance calleeAccountInstance = createAccountInstance(accountsType, recipient.getUniqueID()); - addRelationship(selfAccountInstance, calleeAccountInstance, artifact, relationshipType, (dateTime > 0) ? dateTime : 0); - } - } - } - } diff --git a/bindings/java/src/org/sleuthkit/datamodel/blackboardutils/WebBrowserArtifactsHelper.java b/bindings/java/src/org/sleuthkit/datamodel/blackboardutils/WebBrowserArtifactsHelper.java index b472fd4efa9afcba5993d0d5bf36eab5fedc678b..5eb743af6a792a3d54e4879a725512ecc4ee060b 100644 --- a/bindings/java/src/org/sleuthkit/datamodel/blackboardutils/WebBrowserArtifactsHelper.java +++ b/bindings/java/src/org/sleuthkit/datamodel/blackboardutils/WebBrowserArtifactsHelper.java @@ -189,9 +189,9 @@ public BlackboardArtifact addWebCookie(String url, /** * Adds a TSK_WEB_DOWNNLOAD artifact. * - * @param path Path of downloaded file, required. - * @param startTime Date/time downloaded, 0 if not available. * @param url URL downloaded from, required. + * @param startTime Date/time downloaded, 0 if not available. + * @param path Path of downloaded file, required. * @param programName Program that initiated the download, may be empty or * null. * @@ -200,16 +200,16 @@ public BlackboardArtifact addWebCookie(String url, * @throws TskCoreException If there is an error creating the artifact. * @throws BlackboardException If there is a problem posting the artifact. */ - public BlackboardArtifact addWebDownload(String path, long startTime, String url, String programName) throws TskCoreException, BlackboardException { + public BlackboardArtifact addWebDownload(String url, long startTime, String path, String programName) throws TskCoreException, BlackboardException { return addWebDownload(path, startTime, url, programName, Collections.emptyList()); } /** * Adds a TSK_WEB_DOWNNLOAD artifact. * - * @param path Path of downloaded file, required. - * @param startTime Date/time downloaded, 0 if not available. * @param url URL downloaded from, required. + * @param startTime Date/time downloaded, 0 if not available. + * @param path Path of downloaded file, required. * @param programName Program that initiated the download, may be * empty or null. * @param otherAttributesList Other attributes, may be an empty list. @@ -219,7 +219,7 @@ public BlackboardArtifact addWebDownload(String path, long startTime, String url * @throws TskCoreException If there is an error creating the artifact. * @throws BlackboardException If there is a problem posting the artifact. */ - public BlackboardArtifact addWebDownload(String path, long startTime, String url, String programName, + public BlackboardArtifact addWebDownload(String url, long startTime, String path, String programName, Collection<BlackboardAttribute> otherAttributesList) throws TskCoreException, BlackboardException { BlackboardArtifact webDownloadArtifact; @@ -393,7 +393,7 @@ public BlackboardArtifact addWebFormAutofill(String name, String value, * @param accessTime Last access time, may be 0 if not available. * @param referrer Referrer, may be empty or null. * @param title Website title, may be empty or null. - * @param programName, Application/program recording the history, may be + * @param programName Application/program recording the history, may be * empty or null. * * @return Web history artifact created. @@ -414,7 +414,7 @@ public BlackboardArtifact addWebHistory(String url, long accessTime, * @param accessTime Last access time, may be 0 if not available. * @param referrer Referrer, may be empty or null. * @param title Website title, may be empty or null. - * @param programName, Application/program recording the history, may + * @param programName Application/program recording the history, may * be empty or null. * @param otherAttributesList Other attributes, may be an empty list. * diff --git a/bindings/java/test/org/sleuthkit/datamodel/timeline/EventTypeFilterTest.java b/bindings/java/test/org/sleuthkit/datamodel/timeline/EventTypeFilterTest.java index a581c2ee29b77ac84cdf74b37ef136eacb68b93f..c4b700ba5516a3fcb65ad8094d6d8ed99b307cce 100644 --- a/bindings/java/test/org/sleuthkit/datamodel/timeline/EventTypeFilterTest.java +++ b/bindings/java/test/org/sleuthkit/datamodel/timeline/EventTypeFilterTest.java @@ -35,11 +35,11 @@ public class EventTypeFilterTest { public void testGetEventType() { System.out.println("getEventType"); EventTypeFilter instance = new EventTypeFilter(TimelineEventType.ROOT_EVENT_TYPE); - assertEquals(TimelineEventType.ROOT_EVENT_TYPE, instance.getEventType()); + assertEquals(TimelineEventType.ROOT_EVENT_TYPE, instance.getRootEventType()); instance = new EventTypeFilter(TimelineEventType.FILE_SYSTEM); - assertEquals(TimelineEventType.FILE_SYSTEM, instance.getEventType()); + assertEquals(TimelineEventType.FILE_SYSTEM, instance.getRootEventType()); instance = new EventTypeFilter(TimelineEventType.MESSAGE); - assertEquals(TimelineEventType.MESSAGE, instance.getEventType()); + assertEquals(TimelineEventType.MESSAGE, instance.getRootEventType()); } /** diff --git a/configure.ac b/configure.ac index 2a70534131ce9bd48578fb7b6f1b47d71061c0c8..c89e0029076cf889eb8ad398c4ff398cdd6de9bd 100644 --- a/configure.ac +++ b/configure.ac @@ -4,7 +4,7 @@ dnl Process this file with autoconf to produce a configure script. AC_PREREQ(2.59) -AC_INIT(sleuthkit, 4.6.7) +AC_INIT(sleuthkit, 4.7.0) m4_include([m4/ax_pthread.m4]) dnl include the version from 1.12.1. This will work for m4_include([m4/cppunit.m4]) diff --git a/debian/changelog b/debian/changelog index 3802a67b7411953aadf3a86f8516a0dd2b82ba81..0d09c5daf67f69edc04998773f8b1a927fd042e3 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,4 +1,4 @@ -sleuthkit-java (4.6.7-1) unstable; urgency=medium +sleuthkit-java (4.7.0-1) unstable; urgency=medium * Initial release (Closes: #nnnn) <nnnn is the bug number of your ITP> diff --git a/debian/sleuthkit-java.install b/debian/sleuthkit-java.install index 6decacbed02f9d3439f8c42c05e5e2d1b999782d..fae334721d480b7b6bceee8fd712528460364408 100644 --- a/debian/sleuthkit-java.install +++ b/debian/sleuthkit-java.install @@ -1,3 +1,3 @@ bindings/java/lib/sqlite-jdbc-3.25.2.jar /usr/share/java -bindings/java/dist/sleuthkit-4.6.7.jar /usr/share/java +bindings/java/dist/sleuthkit-4.7.0.jar /usr/share/java diff --git a/packages/sleuthkit.spec b/packages/sleuthkit.spec index 27fed9749c69b73a94f2d90fefb30bd72db51216..5ad496c55079cfc061bef7ca4a61d33719d038c3 100644 --- a/packages/sleuthkit.spec +++ b/packages/sleuthkit.spec @@ -1,5 +1,5 @@ Name: sleuthkit -Version: 4.6.7 +Version: 4.7.0 Release: 1%{?dist} Summary: The Sleuth Kit (TSK) is a library and collection of command line tools that allow you to investigate volume and file system data. diff --git a/tools/logicalimager/FileExtractor.cpp b/tools/logicalimager/FileExtractor.cpp index 33e74de138976435fce85216c715a043ab045763..f6ae445d1705a1486d1da4e8fc9ff61bbbf26fca 100644 --- a/tools/logicalimager/FileExtractor.cpp +++ b/tools/logicalimager/FileExtractor.cpp @@ -75,8 +75,15 @@ TSK_RETVAL_ENUM FileExtractor::extractFile(TSK_FS_FILE *fs_file, const char *pat filename = m_rootDirectoryPath + "/" + extractedFilePath; file = _wfopen(TskHelper::toWide(filename).c_str(), L"wb"); if (file == NULL) { - ReportUtil::consoleOutput(stderr, "ERROR: extractFile failed for %s, reason: %s\n", filename.c_str(), _strerror(NULL)); - ReportUtil::handleExit(1); + // This can happen when the extension is invalid under Windows. Try again with no extension. + ReportUtil::consoleOutput(stderr, "ERROR: extractFile failed for %s, reason: %s\nTrying again with fixed file extension\n", filename.c_str(), _strerror(NULL)); + extractedFilePath = getRootImageDirPrefix() + std::to_string(m_dirCounter) + "/f-" + std::to_string(m_fileCounter - 1); + filename = m_rootDirectoryPath + "/" + extractedFilePath; + file = _wfopen(TskHelper::toWide(filename).c_str(), L"wb"); + if (file == NULL) { + ReportUtil::consoleOutput(stderr, "ERROR: extractFile failed for %s, reason: %s\n", filename.c_str(), _strerror(NULL)); + ReportUtil::handleExit(1); + } } TskHelper::replaceAll(extractedFilePath, "/", "\\"); } @@ -87,11 +94,13 @@ TSK_RETVAL_ENUM FileExtractor::extractFile(TSK_FS_FILE *fs_file, const char *pat if (fs_file->meta) { if (fs_file->meta->size == 0) { // ts_fs_file_read returns -1 with empty files, don't report it. - return TSK_OK; + result = TSK_OK; + break; } else if (fs_file->meta->flags & TSK_FS_NAME_FLAG_UNALLOC) { // don't report it - return TSK_ERR; + result = TSK_ERR; + break; } else { ReportUtil::printDebug("extractFile: tsk_fs_file_read returns -1 filename=%s\toffset=%" PRIxOFF "\n", fs_file->name->name, offset); diff --git a/tools/logicalimager/ReportUtil.cpp b/tools/logicalimager/ReportUtil.cpp index bedad64e8677331129a6be069f0e60545d6c0e12..73590f47992d531ff8067a9f59c96292c169f13f 100644 --- a/tools/logicalimager/ReportUtil.cpp +++ b/tools/logicalimager/ReportUtil.cpp @@ -127,6 +127,16 @@ void ReportUtil::reportResult(const std::string &outputLocation, TSK_RETVAL_ENUM std::string mtimeStr = (fs_file->meta ? std::to_string(fs_file->meta->mtime) : "0"); std::string atimeStr = (fs_file->meta ? std::to_string(fs_file->meta->atime) : "0"); std::string ctimeStr = (fs_file->meta ? std::to_string(fs_file->meta->ctime) : "0"); + std::string origFileName(fs_file->name ? fs_file->name->name : "name is null"); + std::string origFilePath(path); + + // Remove any newlines + origFileName.erase(std::remove(origFileName.begin(), origFileName.end(), '\n'), origFileName.end()); + origFileName.erase(std::remove(origFileName.begin(), origFileName.end(), '\r'), origFileName.end()); + origFilePath.erase(std::remove(origFilePath.begin(), origFilePath.end(), '\n'), origFilePath.end()); + origFilePath.erase(std::remove(origFilePath.begin(), origFilePath.end(), '\r'), origFilePath.end()); + + fprintf(reportFile, "%s\t%" PRIdOFF "\t%" PRIuINUM "\t%d\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n", outputLocation.c_str(), fs_file->fs_info->offset, @@ -135,8 +145,8 @@ void ReportUtil::reportResult(const std::string &outputLocation, TSK_RETVAL_ENUM ruleMatchResult->getRuleSetName().c_str(), ruleMatchResult->getName().c_str(), ruleMatchResult->getDescription().c_str(), - (fs_file->name ? fs_file->name->name : "name is null"), - path, + origFileName.c_str(), + origFilePath.c_str(), extractedFilePath.c_str(), crtimeStr.c_str(), mtimeStr.c_str(), diff --git a/tsk/Makefile.am b/tsk/Makefile.am index 6a29e34e644aedfbf87283eeefc216085202766e..9be01c595a62f0b057c9fdbaedf19a8c22ceef77 100644 --- a/tsk/Makefile.am +++ b/tsk/Makefile.am @@ -8,6 +8,6 @@ libtsk_la_LIBADD = base/libtskbase.la img/libtskimg.la \ vs/libtskvs.la fs/libtskfs.la hashdb/libtskhashdb.la \ auto/libtskauto.la # current:revision:age -libtsk_la_LDFLAGS = -version-info 18:0:5 $(LIBTSK_LDFLAGS) +libtsk_la_LDFLAGS = -version-info 19:0:0 $(LIBTSK_LDFLAGS) EXTRA_DIST = tsk_tools_i.h docs/Doxyfile docs/*.dox docs/*.html diff --git a/tsk/auto/db_postgresql.cpp b/tsk/auto/db_postgresql.cpp index 7e724559204bf4306d53993058d69d32ee73f170..83653cf7b9b7b2ac695fbc8cbcf5ae3695deed60 100755 --- a/tsk/auto/db_postgresql.cpp +++ b/tsk/auto/db_postgresql.cpp @@ -657,20 +657,32 @@ int TskDbPostgreSQL::initialize() { "insert into tsk_event_types(event_type_id, display_name, super_type_id) values(7, 'Changed', 1);" , "Error initializing tsk_event_types table rows: %s\n") || attempt_exec( + /* + * Regarding the timeline event tables schema, note that several columns + * in the tsk_event_descriptions table seem, at first glance, to be + * attributes of events rather than their descriptions and would appear + * to belong in tsk_events table instead. The rationale for putting the + * data source object ID, content object ID, artifact ID and the flags + * indicating whether or not the event source has a hash set hit or is + * tagged were motivated by the fact that these attributes are identical + * for each event in a set of file system file MAC time events. The + * decision was made to avoid duplication and save space by placing this + * data in the tsk_event-descriptions table. + */ "CREATE TABLE tsk_event_descriptions ( " " event_description_id BIGSERIAL PRIMARY KEY, " " full_description TEXT NOT NULL, " " med_description TEXT, " " short_description TEXT," " data_source_obj_id BIGINT NOT NULL, " - " file_obj_id BIGINT NOT NULL, " + " content_obj_id BIGINT NOT NULL, " " artifact_id BIGINT, " " hash_hit INTEGER NOT NULL, " //boolean " tagged INTEGER NOT NULL, " //boolean " FOREIGN KEY(data_source_obj_id) REFERENCES data_source_info(obj_id), " - " FOREIGN KEY(file_obj_id) REFERENCES tsk_objects(obj_id), " + " FOREIGN KEY(content_obj_id) REFERENCES tsk_objects(obj_id), " " FOREIGN KEY(artifact_id) REFERENCES blackboard_artifacts(artifact_id) ," - " UNIQUE (full_description, file_obj_id, artifact_id))", + " UNIQUE (full_description, content_obj_id, artifact_id))", "Error creating tsk_event_descriptions table: %s\n") || attempt_exec( @@ -751,8 +763,8 @@ int TskDbPostgreSQL::createIndexes() { //tsk_events indices attempt_exec("CREATE INDEX events_data_source_obj_id ON tsk_event_descriptions(data_source_obj_id);", "Error creating events_data_source_obj_id index on tsk_event_descriptions: %s\n") || - attempt_exec("CREATE INDEX events_file_obj_id ON tsk_event_descriptions(file_obj_id);", - "Error creating events_file_obj_id index on tsk_event_descriptions: %s\n") || + attempt_exec("CREATE INDEX events_content_obj_id ON tsk_event_descriptions(content_obj_id);", + "Error creating events_content_obj_id index on tsk_event_descriptions: %s\n") || attempt_exec("CREATE INDEX events_artifact_id ON tsk_event_descriptions(artifact_id);", "Error creating events_artifact_id index on tsk_event_descriptions: %s\n") || attempt_exec( @@ -1063,7 +1075,7 @@ int TskDbPostgreSQL::addFsFile(TSK_FS_FILE * fs_file, } -int TskDbPostgreSQL::addMACTimeEvents(char*& zSQL, const int64_t data_source_obj_id, const int64_t file_obj_id, +int TskDbPostgreSQL::addMACTimeEvents(char*& zSQL, const int64_t data_source_obj_id, const int64_t content_obj_id, std::map<int64_t, time_t> timeMap, const char* full_description) { int64_t event_description_id = -1; @@ -1082,17 +1094,17 @@ int TskDbPostgreSQL::addMACTimeEvents(char*& zSQL, const int64_t data_source_obj if (event_description_id == -1) { if (0 > snprintf(zSQL, 2048 - 1, - "INSERT INTO tsk_event_descriptions ( data_source_obj_id, file_obj_id , artifact_id, full_description, hash_hit, tagged) " + "INSERT INTO tsk_event_descriptions ( data_source_obj_id, content_obj_id , artifact_id, full_description, hash_hit, tagged) " " VALUES (" "%" PRId64 "," // data_source_obj_id - "%" PRId64 "," // file_obj_id + "%" PRId64 "," // content_obj_id "NULL," // fixed artifact_id "%s," // full_description "0," // fixed hash_hit "0" // fixed tagged ") RETURNING event_description_id", data_source_obj_id, - file_obj_id, + content_obj_id, full_description)) { return 1; diff --git a/tsk/auto/db_sqlite.cpp b/tsk/auto/db_sqlite.cpp index 25e1c3a8a2756a1b2c8be2ad806f52aa81a1f26e..3f5d1598ad241130992ec531d37e466c85136938 100755 --- a/tsk/auto/db_sqlite.cpp +++ b/tsk/auto/db_sqlite.cpp @@ -424,17 +424,29 @@ TskDbSqlite::initialize() , "Error initializing event_types table rows: %s\n") || attempt_exec( + /* + * Regarding the timeline event tables schema, note that several columns + * in the tsk_event_descriptions table seem, at first glance, to be + * attributes of events rather than their descriptions and would appear + * to belong in tsk_events table instead. The rationale for putting the + * data source object ID, content object ID, artifact ID and the flags + * indicating whether or not the event source has a hash set hit or is + * tagged were motivated by the fact that these attributes are identical + * for each event in a set of file system file MAC time events. The + * decision was made to avoid duplication and save space by placing this + * data in the tsk_event-descriptins table. + */ "CREATE TABLE tsk_event_descriptions ( " " event_description_id INTEGER PRIMARY KEY, " " full_description TEXT NOT NULL, " " med_description TEXT, " " short_description TEXT," " data_source_obj_id INTEGER NOT NULL REFERENCES data_source_info(obj_id), " - " file_obj_id INTEGER NOT NULL REFERENCES tsk_objects(obj_id), " + " content_obj_id INTEGER NOT NULL REFERENCES tsk_objects(obj_id), " " artifact_id INTEGER REFERENCES blackboard_artifacts(artifact_id), " " hash_hit INTEGER NOT NULL, " //boolean " tagged INTEGER NOT NULL, " //boolean - " UNIQUE (full_description, file_obj_id, artifact_id))", + " UNIQUE (full_description, content_obj_id, artifact_id))", "Error creating tsk_event_event_types table: %4\n") || attempt_exec( @@ -518,8 +530,8 @@ int TskDbSqlite::createIndexes() //events indices attempt_exec("CREATE INDEX events_data_source_obj_id ON tsk_event_descriptions(data_source_obj_id);", "Error creating events_data_source_obj_id index on tsk_event_descriptions: %s\n") || - attempt_exec("CREATE INDEX events_file_obj_id ON tsk_event_descriptions(file_obj_id);", - "Error creating events_file_obj_id index on tsk_event_descriptions: %s\n") || + attempt_exec("CREATE INDEX events_content_obj_id ON tsk_event_descriptions(content_obj_id);", + "Error creating events_content_obj_id index on tsk_event_descriptions: %s\n") || attempt_exec("CREATE INDEX events_artifact_id ON tsk_event_descriptions(artifact_id);", "Error creating events_artifact_id index on tsk_event_descriptions: %s\n") || attempt_exec( @@ -1000,7 +1012,7 @@ int64_t TskDbSqlite::findParObjId(const TSK_FS_FILE* fs_file, const char* parent return parObjId; } -int TskDbSqlite::addMACTimeEvents(const int64_t data_source_obj_id, const int64_t file_obj_id, +int TskDbSqlite::addMACTimeEvents(const int64_t data_source_obj_id, const int64_t content_obj_id, std::map<int64_t, time_t> timeMap, const char* full_description) { int64_t event_description_id = -1; @@ -1020,17 +1032,17 @@ int TskDbSqlite::addMACTimeEvents(const int64_t data_source_obj_id, const int64_ { //insert common description for file char* descriptionSql = sqlite3_mprintf( - "INSERT INTO tsk_event_descriptions ( data_source_obj_id, file_obj_id , artifact_id, full_description, hash_hit, tagged) " + "INSERT INTO tsk_event_descriptions ( data_source_obj_id, content_obj_id , artifact_id, full_description, hash_hit, tagged) " " VALUES (" "%" PRId64 "," // data_source_obj_id - "%" PRId64 "," // file_obj_id + "%" PRId64 "," // content_obj_id "NULL," // fixed artifact_id "%Q," // full_description "0," // fixed hash_hit "0" // fixed tagged ")", data_source_obj_id, - file_obj_id, + content_obj_id, full_description); if (attempt_exec(descriptionSql, diff --git a/tsk/base/tsk_base.h b/tsk/base/tsk_base.h old mode 100755 new mode 100644 index 4cf24261c73fb592adcda40024b5dd084013f529..929beccd3dde94d7980b83ef6d73c3ca4b5448cc --- a/tsk/base/tsk_base.h +++ b/tsk/base/tsk_base.h @@ -39,11 +39,11 @@ * 3.1.2b1 would be 0x03010201. Snapshot from Jan 2, 2003 would be * 0xFF030102. * See TSK_VERSION_STR for string form. */ -#define TSK_VERSION_NUM 0x040607ff +#define TSK_VERSION_NUM 0x040700ff /** Version of code in string form. See TSK_VERSION_NUM for * integer form. */ -#define TSK_VERSION_STR "4.6.7" +#define TSK_VERSION_STR "4.7.0" /* include the TSK-specific header file that we created in autoconf