diff --git a/bindings/java/ivy.xml b/bindings/java/ivy.xml index b28852837433a512843384a0e5c180e337bd1282..5d5379cca5171fd195ed983f05944266425c9915 100644 --- a/bindings/java/ivy.xml +++ b/bindings/java/ivy.xml @@ -1,6 +1,11 @@ <ivy-module version="2.0"> <info organisation="org.sleuthkit" module="datamodel"/> <dependencies> + <dependency org="joda-time" name="joda-time" rev="2.4" /> + <dependency org="com.google.guava" name="guava" rev="19.0"/> + <dependency org="org.apache.commons" name="commons-lang3" rev="3.0"/> + + <dependency org="junit" name="junit" rev="4.8.2"/> <dependency org="com.googlecode.java-diff-utils" name="diffutils" rev="1.2.1"/> <dependency org="org.xerial" name="sqlite-jdbc" rev="3.8.11"> diff --git a/bindings/java/nbproject/project.xml b/bindings/java/nbproject/project.xml index 0659e9e3adb3b38fbbc8aff5ba318ee51ac13d77..c314a9fa27ebc469d565ddf88442c117c84e0d32 100755 --- a/bindings/java/nbproject/project.xml +++ b/bindings/java/nbproject/project.xml @@ -30,14 +30,14 @@ </folders> <ide-actions> <action name="build"> - <target>dist</target> + <target>dist-PostgreSQL</target> </action> <action name="clean"> <target>clean</target> </action> <action name="rebuild"> <target>clean</target> - <target>dist</target> + <target>dist-PostgreSQL</target> </action> <action name="run.single"> <script>nbproject/ide-file-targets.xml</script> @@ -75,17 +75,17 @@ <export> <type>folder</type> <location>build</location> - <build-target>dist</build-target> + <build-target>dist-PostgreSQL</build-target> </export> <export> <type>folder</type> <location>build</location> - <build-target>dist</build-target> + <build-target>dist-PostgreSQL</build-target> </export> <export> <type>folder</type> <location>test</location> - <build-target>dist</build-target> + <build-target>dist-PostgreSQL</build-target> </export> <view> <items> @@ -111,12 +111,12 @@ </view> <subprojects/> </general-data> - <java-data xmlns="http://www.netbeans.org/ns/freeform-project-java/3"> + <java-data xmlns="http://www.netbeans.org/ns/freeform-project-java/4"> <compilation-unit> <package-root>src</package-root> - <classpath mode="compile">lib;lib/diffutils-1.2.1.jar;lib/junit-4.8.2.jar;lib/postgresql-9.4-1201.jdbc41.jar;lib/c3p0-0.9.5.jar;lib/mchange-commons-java-0.2.9.jar;lib/c3p0-0.9.5-sources.jar;lib/c3p0-0.9.5-javadoc.jar</classpath> + <classpath mode="compile">lib;lib/diffutils-1.2.1.jar;lib/junit-4.8.2.jar;lib/postgresql-9.4-1201.jdbc41.jar;lib/c3p0-0.9.5.jar;lib/mchange-commons-java-0.2.9.jar;lib/c3p0-0.9.5-sources.jar;lib/c3p0-0.9.5-javadoc.jar;lib/joda-time-2.4.jar;lib/commons-lang3-3.0.jar;lib/guava-19.0.jar</classpath> <built-to>build</built-to> - <source-level>1.6</source-level> + <source-level>1.8</source-level> </compilation-unit> <compilation-unit> <package-root>test</package-root> @@ -124,7 +124,7 @@ <classpath mode="compile">build;lib/diffutils-1.2.1.jar;lib/diffutils-1.2.1-javadoc.jar;lib/diffutils-1.2.1-sources.jar;lib/junit-4.8.2.jar</classpath> <built-to>build</built-to> <built-to>test</built-to> - <source-level>1.6</source-level> + <source-level>1.8</source-level> </compilation-unit> </java-data> <preferences xmlns="http://www.netbeans.org/ns/auxiliary-configuration-preferences/1"> diff --git a/bindings/java/src/org/sleuthkit/datamodel/Bundle_ja.properties b/bindings/java/src/org/sleuthkit/datamodel/Bundle_ja.properties index ce3ccde6ae1b632fef5ea3cf152c47addac60541..253b9bdf8268f9180f7f4fe6519a7f18d684e6c8 100644 --- a/bindings/java/src/org/sleuthkit/datamodel/Bundle_ja.properties +++ b/bindings/java/src/org/sleuthkit/datamodel/Bundle_ja.properties @@ -1,194 +1,194 @@ -BlackboardArtifact.tskGenInfo.text=\u4E00\u822C\u60C5\u5831 -BlackboardArtifact.tskWebBookmark.text=\u30A6\u30A7\u30D6\u30B5\u30A4\u30C8\u30D6\u30C3\u30AF\u30DE\u30FC\u30AF -BlackboardArtifact.tskWebCookie.text=\u30A6\u30A7\u30D6cookie -BlackboardArtifact.tskWebHistory.text=\u30A6\u30A7\u30D6\u5C65\u6B74 -BlackboardArtifact.tskWebDownload.text=\u30A6\u30A7\u30D6\u30C0\u30A6\u30F3\u30ED\u30FC\u30C9 -BlackboardArtifact.tsk.recentObject.text=\u6700\u8FD1\u958B\u3044\u305F\u30C9\u30AD\u30E5\u30E1\u30F3\u30C8 -BlackboardArtifact.tskGpsTrackpoint.text=GPS\u30C8\u30E9\u30C3\u30AF\u30DD\u30A4\u30F3\u30C8 -BlackboardArtifact.tskInstalledProg.text=\u30A4\u30F3\u30B9\u30C8\u30FC\u30EB\u6E08\u307F\u30D7\u30ED\u30B0\u30E9\u30E0 -BlackboardArtifact.tskKeywordHits.text=\u30AD\u30FC\u30EF\u30FC\u30C9\u30D2\u30C3\u30C8 -BlackboardArtifact.tskHashsetHit.text=\u30CF\u30C3\u30B7\u30E5\u30BB\u30C3\u30C8\u30D2\u30C3\u30C8 -BlackboardArtifact.tskInterestingFileHit.text=\u7591\u308F\u3057\u3044\u30D5\u30A1\u30A4\u30EB -BlackboardArtifact.tskEmailMsg.text=E\u30E1\u30FC\u30EB\u30E1\u30C3\u30BB\u30FC\u30B8 -BlackboardArtifact.tskExtractedText.text=\u62BD\u51FA\u3055\u308C\u305F\u30C6\u30AD\u30B9\u30C8 -BlackboardArtifact.tskWebSearchQuery.text=\u30A6\u30A7\u30D6\u691C\u7D22 -BlackboardArtifact.tskMetadataExif.text=EXIF\u30E1\u30BF\u30C7\u30FC\u30BF -BlackboardArtifact.tagFile.text=\u30BF\u30B0\u4ED8\u3051\u3055\u308C\u305F\u30D5\u30A1\u30A4\u30EB -BlackboardArtifact.tskTagArtifact.text=\u30BF\u30B0\u4ED8\u3051\u3055\u308C\u305F\u7D50\u679C -BlackboardArtifact.tskOsInfo.text=\u30AA\u30DA\u30EC\u30FC\u30C6\u30A3\u30F3\u30B0\u30B7\u30B9\u30C6\u30E0\u60C5\u5831 -BlackboardArtifact.tskOsAccount.text=\u30AA\u30DA\u30EC\u30FC\u30C6\u30A3\u30F3\u30B0\u30B7\u30B9\u30C6\u30E0\u30E6\u30FC\u30B6\u30A2\u30AB\u30A6\u30F3\u30C8 -BlackboardArtifact.tskServiceAccount.text=\u30A2\u30AB\u30A6\u30F3\u30C8 -BlackboardArtifact.tskContact.text=\u30B3\u30F3\u30BF\u30AF\u30C8 -BlackboardArtifact.tskMessage.text=\u30E1\u30C3\u30BB\u30FC\u30B8 -BlackboardArtifact.tskCalllog.text=\u30B3\u30FC\u30EB\u30ED\u30B0 -BlackboardArtifact.tskCalendarEntry.text=\u30AB\u30EC\u30F3\u30C0\u30FC\u30A8\u30F3\u30C8\u30EA\u30FC -BlackboardArtifact.tskSpeedDialEntry.text=\u30B9\u30D4\u30FC\u30C9\u30C0\u30A4\u30EB\u30A8\u30F3\u30C8\u30EA\u30FC -BlackboardArtifact.tskBluetoothPairing.text=Bluetooth\u30DA\u30A2\u30EA\u30F3\u30B0 -BlackboardArtifact.tskGpsBookmark.text=GPS\u30D6\u30C3\u30AF\u30DE\u30FC\u30AF -BlackboardArtifact.tskGpsLastKnownLocation.text=\u6700\u5F8C\u306B\u53D6\u5F97\u3057\u305FGPS\u4F4D\u7F6E\u60C5\u5831 -BlackboardArtifact.tskGpsSearch.text=GPS\u691C\u7D22 -BlackboardArtifact.tskDeviceAttached.text=\u4ED8\u5C5E\u6A5F\u5668 -BlackboardArtifact.tskToolOutput.text=\u30ED\u30FC\u30C4\u30FC\u30EB\u30A2\u30A6\u30C8\u30D7\u30C3\u30C8 -BlackboardArtifact.tskProgRun.text=\u5B9F\u884C\u30D7\u30ED\u30B0\u30E9\u30E0 -BlackboardArtifact.tskEncryptionDetected.text=\u6697\u53F7\u5316\u691C\u51FA\u6E08 -BlackboardArtifact.tskExtMismatchDetected.text=\u62E1\u5F35\u5B50\u4E0D\u4E00\u81F4\u691C\u51FA\u6E08 -BlackboardArtifact.tskInterestingArtifactHit.text=\u7591\u308F\u3057\u3044\u7D50\u679C +BlackboardArtifact.tskGenInfo.text=\u4e00\u822c\u60c5\u5831 +BlackboardArtifact.tskWebBookmark.text=\u30a6\u30a7\u30d6\u30b5\u30a4\u30c8\u30d6\u30c3\u30af\u30de\u30fc\u30af +BlackboardArtifact.tskWebCookie.text=\u30a6\u30a7\u30d6cookie +BlackboardArtifact.tskWebHistory.text=\u30a6\u30a7\u30d6\u5c65\u6b74 +BlackboardArtifact.tskWebDownload.text=\u30a6\u30a7\u30d6\u30c0\u30a6\u30f3\u30ed\u30fc\u30c9 +BlackboardArtifact.tsk.recentObject.text=\u6700\u8fd1\u958b\u3044\u305f\u30c9\u30ad\u30e5\u30e1\u30f3\u30c8 +BlackboardArtifact.tskGpsTrackpoint.text=GPS\u30c8\u30e9\u30c3\u30af\u30dd\u30a4\u30f3\u30c8 +BlackboardArtifact.tskInstalledProg.text=\u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u6e08\u307f\u30d7\u30ed\u30b0\u30e9\u30e0 +BlackboardArtifact.tskKeywordHits.text=\u30ad\u30fc\u30ef\u30fc\u30c9\u30d2\u30c3\u30c8 +BlackboardArtifact.tskHashsetHit.text=\u30cf\u30c3\u30b7\u30e5\u30bb\u30c3\u30c8\u30d2\u30c3\u30c8 +BlackboardArtifact.tskInterestingFileHit.text=\u7591\u308f\u3057\u3044\u30d5\u30a1\u30a4\u30eb +BlackboardArtifact.tskEmailMsg.text=E\u30e1\u30fc\u30eb\u30e1\u30c3\u30bb\u30fc\u30b8 +BlackboardArtifact.tskExtractedText.text=\u62bd\u51fa\u3055\u308c\u305f\u30c6\u30ad\u30b9\u30c8 +BlackboardArtifact.tskWebSearchQuery.text=\u30a6\u30a7\u30d6\u691c\u7d22 +BlackboardArtifact.tskMetadataExif.text=EXIF\u30e1\u30bf\u30c7\u30fc\u30bf +BlackboardArtifact.tagFile.text=\u30bf\u30b0\u4ed8\u3051\u3055\u308c\u305f\u30d5\u30a1\u30a4\u30eb +BlackboardArtifact.tskTagArtifact.text=\u30bf\u30b0\u4ed8\u3051\u3055\u308c\u305f\u7d50\u679c +BlackboardArtifact.tskOsInfo.text=\u30aa\u30da\u30ec\u30fc\u30c6\u30a3\u30f3\u30b0\u30b7\u30b9\u30c6\u30e0\u60c5\u5831 +BlackboardArtifact.tskOsAccount.text=\u30aa\u30da\u30ec\u30fc\u30c6\u30a3\u30f3\u30b0\u30b7\u30b9\u30c6\u30e0\u30e6\u30fc\u30b6\u30a2\u30ab\u30a6\u30f3\u30c8 +BlackboardArtifact.tskServiceAccount.text=\u30a2\u30ab\u30a6\u30f3\u30c8 +BlackboardArtifact.tskContact.text=\u30b3\u30f3\u30bf\u30af\u30c8 +BlackboardArtifact.tskMessage.text=\u30e1\u30c3\u30bb\u30fc\u30b8 +BlackboardArtifact.tskCalllog.text=\u30b3\u30fc\u30eb\u30ed\u30b0 +BlackboardArtifact.tskCalendarEntry.text=\u30ab\u30ec\u30f3\u30c0\u30fc\u30a8\u30f3\u30c8\u30ea\u30fc +BlackboardArtifact.tskSpeedDialEntry.text=\u30b9\u30d4\u30fc\u30c9\u30c0\u30a4\u30eb\u30a8\u30f3\u30c8\u30ea\u30fc +BlackboardArtifact.tskBluetoothPairing.text=Bluetooth\u30da\u30a2\u30ea\u30f3\u30b0 +BlackboardArtifact.tskGpsBookmark.text=GPS\u30d6\u30c3\u30af\u30de\u30fc\u30af +BlackboardArtifact.tskGpsLastKnownLocation.text=\u6700\u5f8c\u306b\u53d6\u5f97\u3057\u305fGPS\u4f4d\u7f6e\u60c5\u5831 +BlackboardArtifact.tskGpsSearch.text=GPS\u691c\u7d22 +BlackboardArtifact.tskDeviceAttached.text=\u4ed8\u5c5e\u6a5f\u5668 +BlackboardArtifact.tskToolOutput.text=\u30ed\u30fc\u30c4\u30fc\u30eb\u30a2\u30a6\u30c8\u30d7\u30c3\u30c8 +BlackboardArtifact.tskProgRun.text=\u5b9f\u884c\u30d7\u30ed\u30b0\u30e9\u30e0 +BlackboardArtifact.tskEncryptionDetected.text=\u6697\u53f7\u5316\u691c\u51fa\u6e08 +BlackboardArtifact.tskExtMismatchDetected.text=\u62e1\u5f35\u5b50\u4e0d\u4e00\u81f4\u691c\u51fa\u6e08 +BlackboardArtifact.tskInterestingArtifactHit.text=\u7591\u308f\u3057\u3044\u7d50\u679c BlackboardAttribute.tskUrl.text=URL -BlackboardAttribute.tskDatetime.text=\u65E5\u4ED8\uFF0F\u6642\u523B -BlackboardAttribute.tskName.text=\u540D\u524D -BlackboardAttribute.tskProgName.text=\u30D7\u30ED\u30B0\u30E9\u30E0\u540D -BlackboardAttribute.tskValue.text=\u30D0\u30EA\u30E5\u30FC -BlackboardAttribute.tskFlag.text=\u30D5\u30E9\u30B0 -BlackboardAttribute.tskPath.text=\u30D1\u30B9 -BlackboardAttribute.tskKeyword.text=\u30AD\u30FC\u30EF\u30FC\u30C9 -BlackboardAttribute.tskKeywordRegexp.text=\u6B63\u898F\u8868\u73FE\u30AD\u30FC\u30EF\u30FC\u30C9 -BlackboardAttribute.tskKeywordPreview.text=\u30AD\u30FC\u30EF\u30FC\u30C9\u30D7\u30EC\u30D3\u30E5\u30FC -BlackboardAttribute.tskKeywordSet.text=\u30AD\u30FC\u30EF\u30FC\u30C9\u30BB\u30C3\u30C8 -BlackboardAttribute.tskUserName.text=\u30E6\u30FC\u30B6\u540D -BlackboardAttribute.tskDomain.text=\u30C9\u30E1\u30A4\u30F3 -BlackboardAttribute.tskPassword.text=\u30D1\u30B9\u30EF\u30FC\u30C9 -BlackboardAttribute.tskNamePerson.text=\u4EBA\u540D -BlackboardAttribute.tskDeviceModel.text=\u6A5F\u5668\u30E2\u30C7\u30EB -BlackboardAttribute.tskDeviceId.text=\u6A5F\u5668ID -BlackboardAttribute.tskEmail.text=E\u30E1\u30FC\u30EB -BlackboardAttribute.tskHashMd5.text=MD5\u30CF\u30C3\u30B7\u30E5 -BlackboardAttribute.tskHashSha1.text=SHA1\u30CF\u30C3\u30B7\u30E5 -BlackboardAttribute.tskHashSha225.text=SHA2-256\u30CF\u30C3\u30B7\u30E5 -BlackboardAttribute.tskHashSha2512.text=SHA2-512\u30CF\u30C3\u30B7\u30E5 -BlackboardAttribute.tskText.text=\u30C6\u30AD\u30B9\u30C8 -BlackboardAttribute.tskTextFile.text=\u30C6\u30AD\u30B9\u30C8\u30D5\u30A1\u30A4\u30EB -BlackboardAttribute.tskTextLanguage.text=\u30C6\u30AD\u30B9\u30C8\u8A00\u8A9E -BlackboardAttribute.tskEntropy.text=\u30A8\u30F3\u30C8\u30ED\u30D4\u30FC -BlackboardAttribute.tskHashsetName.text=\u30CF\u30C3\u30B7\u30E5\u30BB\u30C3\u30C8\u540D -BlackboardAttribute.tskInterestingFile.text=\u7591\u308F\u3057\u3044\u30D5\u30A1\u30A4\u30EB -BlackboardAttribute.tskReferrer.text=\u30EA\u30D5\u30A1\u30E9 -BlackboardAttribute.tskDateTimeAccessed.text=\u30A2\u30AF\u30BB\u30B9\u65E5\u4ED8 -BlackboardAttribute.tskIpAddress.text=IP\u30A2\u30C9\u30EC\u30B9 -BlackboardAttribute.tskPhoneNumber.text=\u96FB\u8A71\u756A\u53F7 -BlackboardAttribute.tskPathId.text=\u30D1\u30B9ID -BlackboardAttribute.tskSetName.text=\u30BB\u30C3\u30C8\u540D -BlackboardAttribute.tskEncryptionDetected.text=\u6697\u53F7\u5316\u691C\u51FA\u6E08 -BlackboardAttribute.tskMalwareDetected.text=\u30DE\u30EB\u30A6\u30A7\u30A2\u691C\u51FA\u6E08 -BlackboardAttribute.tskDeviceMake.text=\u6A5F\u5668\u30E1\u30FC\u30AB\u30FC -BlackboardAttribute.tskStegDetected.text=\u30B9\u30C6\u30AC\u30CE\u30B0\u30E9\u30D5\u30A3\u30FC\u691C\u51FA\u6E08 -BlackboardAttribute.tskEmailTo.text=E\u30E1\u30FC\u30EB\u5B9B\u5148 +BlackboardAttribute.tskDatetime.text=\u65e5\u4ed8\uff0f\u6642\u523b +BlackboardAttribute.tskName.text=\u540d\u524d +BlackboardAttribute.tskProgName.text=\u30d7\u30ed\u30b0\u30e9\u30e0\u540d +BlackboardAttribute.tskValue.text=\u30d0\u30ea\u30e5\u30fc +BlackboardAttribute.tskFlag.text=\u30d5\u30e9\u30b0 +BlackboardAttribute.tskPath.text=\u30d1\u30b9 +BlackboardAttribute.tskKeyword.text=\u30ad\u30fc\u30ef\u30fc\u30c9 +BlackboardAttribute.tskKeywordRegexp.text=\u6b63\u898f\u8868\u73fe\u30ad\u30fc\u30ef\u30fc\u30c9 +BlackboardAttribute.tskKeywordPreview.text=\u30ad\u30fc\u30ef\u30fc\u30c9\u30d7\u30ec\u30d3\u30e5\u30fc +BlackboardAttribute.tskKeywordSet.text=\u30ad\u30fc\u30ef\u30fc\u30c9\u30bb\u30c3\u30c8 +BlackboardAttribute.tskUserName.text=\u30e6\u30fc\u30b6\u540d +BlackboardAttribute.tskDomain.text=\u30c9\u30e1\u30a4\u30f3 +BlackboardAttribute.tskPassword.text=\u30d1\u30b9\u30ef\u30fc\u30c9 +BlackboardAttribute.tskNamePerson.text=\u4eba\u540d +BlackboardAttribute.tskDeviceModel.text=\u6a5f\u5668\u30e2\u30c7\u30eb +BlackboardAttribute.tskDeviceId.text=\u6a5f\u5668ID +BlackboardAttribute.tskEmail.text=E\u30e1\u30fc\u30eb +BlackboardAttribute.tskHashMd5.text=MD5\u30cf\u30c3\u30b7\u30e5 +BlackboardAttribute.tskHashSha1.text=SHA1\u30cf\u30c3\u30b7\u30e5 +BlackboardAttribute.tskHashSha225.text=SHA2-256\u30cf\u30c3\u30b7\u30e5 +BlackboardAttribute.tskHashSha2512.text=SHA2-512\u30cf\u30c3\u30b7\u30e5 +BlackboardAttribute.tskText.text=\u30c6\u30ad\u30b9\u30c8 +BlackboardAttribute.tskTextFile.text=\u30c6\u30ad\u30b9\u30c8\u30d5\u30a1\u30a4\u30eb +BlackboardAttribute.tskTextLanguage.text=\u30c6\u30ad\u30b9\u30c8\u8a00\u8a9e +BlackboardAttribute.tskEntropy.text=\u30a8\u30f3\u30c8\u30ed\u30d4\u30fc +BlackboardAttribute.tskHashsetName.text=\u30cf\u30c3\u30b7\u30e5\u30bb\u30c3\u30c8\u540d +BlackboardAttribute.tskInterestingFile.text=\u7591\u308f\u3057\u3044\u30d5\u30a1\u30a4\u30eb +BlackboardAttribute.tskReferrer.text=\u30ea\u30d5\u30a1\u30e9 +BlackboardAttribute.tskDateTimeAccessed.text=\u30a2\u30af\u30bb\u30b9\u65e5\u4ed8 +BlackboardAttribute.tskIpAddress.text=IP\u30a2\u30c9\u30ec\u30b9 +BlackboardAttribute.tskPhoneNumber.text=\u96fb\u8a71\u756a\u53f7 +BlackboardAttribute.tskPathId.text=\u30d1\u30b9ID +BlackboardAttribute.tskSetName.text=\u30bb\u30c3\u30c8\u540d +BlackboardAttribute.tskEncryptionDetected.text=\u6697\u53f7\u5316\u691c\u51fa\u6e08 +BlackboardAttribute.tskMalwareDetected.text=\u30de\u30eb\u30a6\u30a7\u30a2\u691c\u51fa\u6e08 +BlackboardAttribute.tskDeviceMake.text=\u6a5f\u5668\u30e1\u30fc\u30ab\u30fc +BlackboardAttribute.tskStegDetected.text=\u30b9\u30c6\u30ac\u30ce\u30b0\u30e9\u30d5\u30a3\u30fc\u691c\u51fa\u6e08 +BlackboardAttribute.tskEmailTo.text=E\u30e1\u30fc\u30eb\u5b9b\u5148 BlackboardAttribute.tskEmailCc.text=E-Mail CC BlackboardAttribute.tskEmailBcc.text=E-Mail BCC -BlackboardAttribute.tskEmailFrom.text=\u9001\u4FE1\u5143E\u30E1\u30FC\u30EB -BlackboardAttribute.tskEmailContentPlain.text=\u30E1\u30C3\u30BB\u30FC\u30B8\uFF08\u30D7\u30EC\u30FC\u30F3\u30C6\u30AD\u30B9\u30C8\uFF09 -BlackboardAttribute.tskEmailContentHtml.text=\u30E1\u30C3\u30BB\u30FC\u30B8\uFF08HTML\uFF09 -BlackboardAttribute.tskEmailContentRtf.text=\u30E1\u30C3\u30BB\u30FC\u30B8\uFF08RTF\uFF09 -BlackboardAttribute.tskMsgId.text=\u30E1\u30C3\u30BB\u30FC\u30B8ID -BlackboardAttribute.tskMsgReplyId.text=\u30E1\u30C3\u30BB\u30FC\u30B8\u30EA\u30D7\u30E9\u30A4ID -BlackboardAttribute.tskDateTimeRcvd.text=\u53D7\u4FE1\u65E5 -BlackboardAttribute.tskDateTimeSent.text=\u9001\u4FE1\u65E5 -BlackboardAttribute.tskSubject.text=\u30B5\u30D6\u30B8\u30A7\u30AF\u30C8 -BlackboardAttribute.tskTitle.text=\u30BF\u30A4\u30C8\u30EB -BlackboardAttribute.tskGeoLatitude.text=\u7DEF\u5EA6 -BlackboardAttribute.tskGeoLongitude.text=\u7D4C\u5EA6 -BlackboardAttribute.tskGeoVelocity.text=\u901F\u5EA6 -BlackboardAttribute.tskGeoAltitude.text=\u6A19\u9AD8 -BlackboardAttribute.tskGeoBearing.text=\u65B9\u5411 -BlackboardAttribute.tskGeoHPrecision.text=\u6C34\u5E73\u7CBE\u5EA6 -BlackboardAttribute.tskGeoVPrecision.text=\u5782\u76F4\u7CBE\u5EA6 -BlackboardAttribute.tskGeoMapDatum.text=\u6E2C\u5730\u7CFB -BlackboardAttribute.tskFileTypeSig.text=\u30D5\u30A1\u30A4\u30EB\u30BF\u30A4\u30D7\uFF08\u30B7\u30B0\u30CD\u30C1\u30E3\uFF09 -BlackboardAttribute.tskFileTypeExt.text=\u30D5\u30A1\u30A4\u30EB\u30BF\u30A4\u30D7\uFF08\u62E1\u5F35\u5B50\uFF09 -BlackboardAttribute.tskTaggedArtifact.text=\u30BF\u30B0\u4ED8\u3051\u3055\u308C\u305F\u7D50\u679C -BlackboardAttribute.tskTagName.text=\u30BF\u30B0\u540D -BlackboardAttribute.tskComment.text=\u30B3\u30E1\u30F3\u30C8 -BlackboardAttribute.tskUrlDecoded.text=\u5FA9\u53F7\u5316\u3055\u308C\u305FURL -BlackboardAttribute.tskDateTimeCreated.text=\u4F5C\u6210\u65E5 -BlackboardAttribute.tskDateTimeModified.text=\u4FEE\u6B63\u65E5 -BlackboardAttribute.tskProcessorArchitecture.text=\u30D7\u30ED\u30BB\u30C3\u30B5\u30A2\u30FC\u30AD\u30C6\u30AF\u30C1\u30E3 -BlackboardAttribute.tskVersion.text=\u30D0\u30FC\u30B8\u30E7\u30F3 -BlackboardAttribute.tskUserId.text=\u30E6\u30FC\u30B6ID -BlackboardAttribute.tskDescription.text=\u8AAC\u660E -BlackboardAttribute.tskMessageType.text=\u30E1\u30C3\u30BB\u30FC\u30B8\u30BF\u30A4\u30D7 -BlackboardAttribute.tskPhoneNumberHome.text=\u96FB\u8A71\u756A\u53F7\uFF08\u81EA\u5B85\uFF09 -BlackboardAttribute.tskPhoneNumberOffice.text=\u96FB\u8A71\u756A\u53F7\uFF08\u4F1A\u793E\uFF09 -BlackboardAttribute.tskPhoneNumberMobile.text=\u96FB\u8A71\u756A\u53F7\uFF08\u643A\u5E2F\uFF09 -BlackboardAttribute.tskPhoneNumberFrom.text=\u767A\u4FE1\u8005\u96FB\u8A71\u756A\u53F7 -BlackboardAttribute.tskPhoneNumberTo.text=\u7740\u4FE1\u8005\u96FB\u8A71\u756A\u53F7 -BlackboardAttribute.tskDirection.text=\u65B9\u5411 -BlackboardAttribute.tskEmailHome.text=E\u30E1\u30FC\u30EB\uFF08\u81EA\u5B85\uFF09 -BlackboardAttribute.tskEmailOffice.text=E\u30E1\u30FC\u30EB\uFF08\u30AA\u30D5\u30A3\u30B9\uFF09 -BlackboardAttribute.tskDateTimeStart.text=\u958B\u59CB\u65E5\u4ED8\uFF0F\u6642\u523B -BlackboardAttribute.tskDateTimeEnd.text=\u7D42\u4E86\u65E5\u4ED8\uFF0F\u6642\u523B -BlackboardAttribute.tskCalendarEntryType.text=\u30AB\u30EC\u30F3\u30C0\u30FC\u30A8\u30F3\u30C8\u30EA\u30FC\u30BF\u30A4\u30D7 -BlackboardAttribute.tskLocation.text=\u30ED\u30B1\u30FC\u30B7\u30E7\u30F3 -BlackboardAttribute.tskShortcut.text=\u30B7\u30E7\u30FC\u30C8\u30AB\u30C3\u30C8 -BlackboardAttribute.tskDeviceName.text=\u6A5F\u5668\u540D -BlackboardAttribute.tskCategory.text=\u30AB\u30C6\u30B4\u30EA\u30FC -BlackboardAttribute.tskEmailReplyTo.text=\u8FD4\u4FE1\u30A2\u30C9\u30EC\u30B9 -BlackboardAttribute.tskServerName.text=\u30B5\u30FC\u30D0\u540D -BlackboardAttribute.tskCount.text=\u30AB\u30A6\u30F3\u30C8 -BlackboardAttribute.tskMinCount.text=\u6700\u5C0F\u30AB\u30A6\u30F3\u30C8 -BlackboardAttribute.tskPathSource.text=\u30D1\u30B9\u30BD\u30FC\u30B9 -BlackboardAttribute.tskPermissions.text=\u30D1\u30FC\u30DF\u30C3\u30B7\u30E7\u30F3 -BlackboardAttribute.tskAssociatedArtifact.text=\u95A2\u9023\u3059\u308B\u30A2\u30FC\u30C6\u30A3\u30D5\u30A1\u30AF\u30C8 -BlackboardAttribute.tskIsDeleted.text=\u306F\u524A\u9664\u3055\u308C\u307E\u3057\u305F -AbstractFile.readLocal.exception.msg4.text=\u30D5\u30A1\u30A4\u30EB{0}\u306E\u8AAD\u307F\u53D6\u308A\u4E2D\u306B\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F -AbstractFile.readLocal.exception.msg1.text=\u30ED\u30FC\u30AB\u30EB\u30D5\u30A1\u30A4\u30EB\u306E\u8AAD\u307F\u53D6\u308A\u4E2D\u306B\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F\u3002\u30ED\u30FC\u30AB\u30EB\u30D1\u30B9\u304C\u30BB\u30C3\u30C8\u3055\u308C\u3066\u3044\u307E\u305B\u3093\u3002 -AbstractFile.readLocal.exception.msg2.text=\u30ED\u30FC\u30AB\u30EB\u30D5\u30A1\u30A4\u30EB\u306E\u8AAD\u307F\u53D6\u308A\u4E2D\u306B\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F\u3002\u4E0B\u8A18\u306E\u30ED\u30FC\u30AB\u30EB\u30D1\u30B9\u306B\u306F\u5B58\u5728\u3057\u307E\u305B\u3093\uFF1A{0} -AbstractFile.readLocal.exception.msg3.text=\u30ED\u30FC\u30AB\u30EB\u30D5\u30A1\u30A4\u30EB\u306E\u8AAD\u307F\u53D6\u308A\u4E2D\u306B\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F\u3002\u4E0B\u8A18\u306E\u30ED\u30FC\u30AB\u30EB\u30D1\u30B9\u3067\u306F\u8AAD\u307F\u53D6\u308A\u3067\u304D\u307E\u305B\u3093\uFF1A{0} -AbstractFile.readLocal.exception.msg5.text=\u30ED\u30FC\u30AB\u30EB\u30D5\u30A1\u30A4\u30EB{0}\u3092\u8AAD\u307F\u53D6\u308C\u307E\u305B\u3093 -FsContent.readInt.err.context.text=\u30A4\u30E1\u30FC\u30B8\u30D5\u30A1\u30A4\u30EB\u306E\u8AAD\u307F\u53D6\u308A\u30A8\u30E9\u30FC -FsContent.readInt.err.msg.text=\u30A4\u30E1\u30FC\u30B8\u30D5\u30A1\u30A4\u30EB\u306F\u5B58\u5728\u3057\u306A\u3044\u304B\u3001\u30A2\u30AF\u30BB\u30B9\u304C\u62D2\u5426\u3055\u308C\u307E\u3057\u305F\u3002 -Image.verifyImageSize.errStr1.text=\n\u4E0D\u5B8C\u5168\u306A\u30A4\u30E1\u30FC\u30B8\u306E\u53EF\u80FD\u6027\uFF1A\u30AA\u30D5\u30BB\u30C3\u30C8{0}\u306B\u57FA\u3065\u3044\u305F\u30DC\u30EA\u30E5\u30FC\u30E0\u306E\u8AAD\u307F\u53D6\u308A\u4E2D\u306B\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F -Image.verifyImageSize.errStr2.text=\n\u4E0D\u5B8C\u5168\u306A\u30A4\u30E1\u30FC\u30B8\u306E\u53EF\u80FD\u6027\uFF1A\u30AA\u30D5\u30BB\u30C3\u30C8{0}\u306B\u57FA\u3065\u3044\u305F\u30DC\u30EA\u30E5\u30FC\u30E0\u306E\u8AAD\u307F\u53D6\u308A\u4E2D\u306B\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F -Image.verifyImageSize.errStr3.text=\n\u4E0D\u5B8C\u5168\u306A\u30A4\u30E1\u30FC\u30B8\u306E\u53EF\u80FD\u6027\uFF1A\u30AA\u30D5\u30BB\u30C3\u30C8{0}\u306B\u57FA\u3065\u3044\u305F\u30D5\u30A1\u30A4\u30EB\u30B7\u30B9\u30C6\u30E0\u306E\u8AAD\u307F\u53D6\u308A\u4E2D\u306B\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F -Image.verifyImageSize.errStr4.text=\n\u4E0D\u5B8C\u5168\u306A\u30A4\u30E1\u30FC\u30B8\u306E\u53EF\u80FD\u6027\uFF1A\u30AA\u30D5\u30BB\u30C3\u30C8{0}\u306B\u57FA\u3065\u3044\u305F\u30D5\u30A1\u30A4\u30EB\u30B7\u30B9\u30C6\u30E0\u306E\u8AAD\u307F\u53D6\u308A\u4E2D\u306B\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F -SleuthkitCase.isFileFromSource.exception.msg.text=\u30A8\u30E9\u30FC\uFF1A\u30C7\u30FC\u30BF\u30BD\u30FC\u30B9\u306F\u30DA\u30A2\u30EC\u30F3\u30C8\u304C\u7121\u3044\uFF08\u30A4\u30E1\u30FC\u30B8\u3001\u30D5\u30A1\u30A4\u30EB\u30BB\u30C3\u30C8\uFF09\u306F\u305A\u3067\u3059\u304C\u3001\u4E0B\u8A18\u304C\u5B58\u5728\u3057\u307E\u3059\uFF1A{0} -SleuthkitCase.findFiles.exception.msg3.text=\u30D5\u30A1\u30A4\u30EB\u540D\u306B\u57FA\u3065\u3044\u305F\u30C7\u30FC\u30BF\u30BD\u30FC\u30B9\u306E\u691C\u7D22\u4E2D\u306B\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F\u3002 -SleuthkitCase.findFiles3.exception.msg1.text=\u30A8\u30E9\u30FC\uFF1A\u30C7\u30FC\u30BF\u30BD\u30FC\u30B9\u306F\u30DA\u30A2\u30EC\u30F3\u30C8\u304C\u7121\u3044\uFF08\u30A4\u30E1\u30FC\u30B8\u3001\u30D5\u30A1\u30A4\u30EB\u30BB\u30C3\u30C8\uFF09\u306F\u305A\u3067\u3059\u304C\u3001\u4E0B\u8A18\u304C\u5B58\u5728\u3057\u307E\u3059\uFF1A{0} -SleuthkitCase.findFiles3.exception.msg3.text=\u30D5\u30A1\u30A4\u30EB\u540D\u306B\u57FA\u3065\u3044\u305F\u30C7\u30FC\u30BF\u30BD\u30FC\u30B9\u306E\u691C\u7D22\u4E2D\u306B\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F\u3002 -SleuthkitCase.addDerivedFile.exception.msg1.text=\u6D3E\u751F\u30D5\u30A1\u30A4\u30EB\u306E\u4F5C\u6210\u4E2D\u306B\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F\u3002\u30AA\u30D6\u30B8\u30A7\u30AF\u30C8\u306E\u65B0\u898FID\u3092\u53D6\u5F97\u3067\u304D\u307E\u305B\u3093\u3002\u30D5\u30A1\u30A4\u30EB\u540D\uFF1A{0} -SleuthkitCase.addDerivedFile.exception.msg2.text=\u6D3E\u751F\u30D5\u30A1\u30A4\u30EB\u306E\u4F5C\u6210\u4E2D\u306B\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F\u3002\u30D5\u30A1\u30A4\u30EB\u540D\uFF1A{0} -SleuthkitCase.addLocalFile.exception.msg1.text=\u30ED\u30FC\u30AB\u30EB\u30D5\u30A1\u30A4\u30EB{0}\u306E\u8FFD\u52A0\u4E2D\u306B\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F\u3002\u8FFD\u52A0\u5148\u306E\u30DA\u30A2\u30EC\u30F3\u30C8\u304C\u30CC\u30EB\u3067\u3059\u3002 -SleuthkitCase.addLocalFile.exception.msg2.text=\u30ED\u30FC\u30AB\u30EB\u30D5\u30A1\u30A4\u30EB\u306E\u4F5C\u6210\u4E2D\u306B\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F\u3002\u30AA\u30D6\u30B8\u30A7\u30AF\u30C8\u306E\u65B0\u898FID\u3092\u53D6\u5F97\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F\u3002\u30D5\u30A1\u30A4\u30EB\u540D\uFF1A{0} -SleuthkitCase.addLocalFile.exception.msg3.text=\u6D3E\u751F\u30D5\u30A1\u30A4\u30EB\u306E\u4F5C\u6210\u4E2D\u306B\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F\u3002\u30D5\u30A1\u30A4\u30EB\u540D\uFF1A{0} -SleuthkitCase.getLastObjectId.exception.msg.text=\u6700\u5F8C\u306E\u30AA\u30D6\u30B8\u30A7\u30AF\u30C8ID\u3092\u53D6\u5F97\u3057\u305F\u5F8C\u3001\u7D50\u679C\u30BB\u30C3\u30C8\u3092\u9589\u3058\u308B\u969B\u306B\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F\u3002 -TskData.tskFsNameFlagEnum.allocated=\u5272\u308A\u5F53\u3066\u6E08\u307F -TskData.tskFsNameFlagEnum.unallocated=\u672A\u5272\u308A\u5F53\u3066 -TskData.tskFsMetaFlagEnum.allocated=\u5272\u308A\u5F53\u3066\u6E08\u307F -TskData.tskFsMetaFlagEnum.unallocated=\u672A\u5272\u308A\u5F53\u3066 -TskData.tskFsMetaFlagEnum.used=\u4F7F\u7528\u6E08\u307F -TskData.tskFsMetaFlagEnum.unused=\u672A\u4F7F\u7528 -TskData.tskFsMetaFlagEnum.compressed=\u5727\u7E2E\u6E08\u307F -TskData.tskFsMetaFlagEnum.orphan=\u30AA\u30FC\u30D5\u30A1\u30F3 -TskData.tskImgTypeEnum.autoDetect=\u81EA\u52D5\u691C\u51FA -TskData.tskImgTypeEnum.rawSingle=\u30ED\u30FC\u30B7\u30F3\u30B0\u30EB -TskData.tskImgTypeEnum.rawSplit=\u30ED\u30FC\u30B9\u30D7\u30EA\u30C3\u30C8 -TskData.tskImgTypeEnum.unknown=\u4E0D\u660E -TskData.tskVSTypeEnum.autoDetect=\u81EA\u52D5\u691C\u51FA -TskData.tskVSTypeEnum.fake=\u507D\u7269 -TskData.tskVSTypeEnum.unsupported=\u975E\u30B5\u30DD\u30FC\u30C8 -TskData.fileKnown.unknown=\u4E0D\u660E -TskData.fileKnown.known=\u65E2\u77E5 -TskData.fileKnown.knownBad=\u65E2\u77E5\u60AA\u8CEA -TskData.objectTypeEnum.exception.msg1.text=\u30D0\u30EA\u30E5\u30FC\uFF1A{0}\u306F\u30AA\u30D6\u30B8\u30A7\u30AF\u30C8\u30BF\u30A4\u30D7\u306B\u8A72\u5F53\u3057\u307E\u305B\u3093 -Volume.desc.text=\u4E0D\u660E -Volume.read.exception.msg1.text=\u3053\u306E\u30DC\u30EA\u30E5\u30FC\u30E0\u306E\u30DA\u30A2\u30EC\u30F3\u30C8\u306FVolmueSystem\u3067\u3042\u308B\u3079\u304D\u3067\u3059\u304C\u3001\u9055\u3044\u307E\u3059\u3002 -Volume.vsFlagToString.allocated=\u5272\u308A\u5F53\u3066\u6E08\u307F -Volume.vsFlagToString.unallocated=\u672A\u5272\u308A\u5F53\u3066 -TskData.fileKnown.exception.msg1.text=\u30D0\u30EA\u30E5\u30FC\uFF1A{0}\u306FFileKnown\u306B\u8A72\u5F53\u3057\u307E\u305B\u3093 -TskData.tskDbFilesTypeEnum.exception.msg1.text=\u30D0\u30EA\u30E5\u30FC\uFF1A{0}\u306FTSK_FILE_TYPE_ENUM\u306B\u8A72\u5F53\u3057\u307E\u305B\u3093 -TskData.tskVSTypeEnum.exception.msg1.text=\u30D0\u30EA\u30E5\u30FC\uFF1A{0}\u306FTSK_VS_TYPE_ENUM\u306B\u8A72\u5F53\u3057\u307E\u305B\u3093 -TskData.tskImgTypeEnum.exception.msg1.text=\u30D0\u30EA\u30E5\u30FC\uFF1A{0}\u306FTSK_IMG_TYPE_ENUM\u306B\u8A72\u5F53\u3057\u307E\u305B\u3093 -TskData.tskFsTypeEnum.exception.msg1.text=\u30D0\u30EA\u30E5\u30FC\uFF1A{0}\u306FTSK_FS_TYPE_ENUM\u306B\u8A72\u5F53\u3057\u307E\u305B\u3093 -TskData.tskFsAttrTypeEnum.exception.msg1.text=\u30D0\u30EA\u30E5\u30FC\uFF1A{0}\u306FTSK_FS_TYPE_ENUM\u306B\u8A72\u5F53\u3057\u307E\u305B\u3093 -TskData.tskFsNameFlagEnum.exception.msg1.text=\u30D0\u30EA\u30E5\u30FC\uFF1A{0}\u306FTSK_FS_NAME_FLAG_ENUM\u306B\u8A72\u5F53\u3057\u307E\u305B\u3093 -TskData.tskFsMetaTypeEnum.exception.msg1.text=\u30D0\u30EA\u30E5\u30FC\uFF1A{0}\u306FTSK_FS_META_TYPE_ENUM\u306B\u8A72\u5F53\u3057\u307E\u305B\u3093 -TskData.tskFsNameTypeEnum.exception.msg1.text=\u30D0\u30EA\u30E5\u30FC\uFF1A{0}\u306FTSK_FS_NAME_TYPE_ENUM\u306B\u8A72\u5F53\u3057\u307E\u305B\u3093 -SleuthkitCase.findFiles.exception.msg1.text=\u30A8\u30E9\u30FC\uFF1A\u30C7\u30FC\u30BF\u30BD\u30FC\u30B9\u306F\u30DA\u30A2\u30EC\u30F3\u30C8\u304C\u7121\u3044\uFF08\u30A4\u30E1\u30FC\u30B8\u3001\u30D5\u30A1\u30A4\u30EB\u30BB\u30C3\u30C8\uFF09\u306F\u305A\u3067\u3059\u304C\u3001\u4E0B\u8A18\u304C\u5B58\u5728\u3057\u307E\u3059\uFF1A{0} -SleuthkitCase.isFileFromSource.exception.msg2.text=\u30A8\u30E9\u30FC\uFF1A\u30C7\u30FC\u30BF\u30BD\u30FC\u30B9\u306F\u30A4\u30E1\u30FC\u30B8\u307E\u305F\u306FVirtualDirectory\u3067\u3042\u308B\u3079\u304D\u3067\u3059\u304C\u3001\u4E0B\u8A18\u304C\u5B58\u5728\u3057\u307E\u3059\uFF1A{0} -SleuthkitCase.findFiles.exception.msg2.text=\u30A8\u30E9\u30FC\uFF1A\u30C7\u30FC\u30BF\u30BD\u30FC\u30B9\u306F\u30A4\u30E1\u30FC\u30B8\u307E\u305F\u306FVirtualDirectory\u3067\u3042\u308B\u3079\u304D\u3067\u3059\u304C\u3001\u4E0B\u8A18\u304C\u5B58\u5728\u3057\u307E\u3059\uFF1A{0} -SleuthkitCase.findFiles3.exception.msg2.text=\u30A8\u30E9\u30FC\uFF1A\u30C7\u30FC\u30BF\u30BD\u30FC\u30B9\u306F\u30A4\u30E1\u30FC\u30B8\u307E\u305F\u306FVirtualDirectory\u3067\u3042\u308B\u3079\u304D\u3067\u3059\u304C\u3001\u4E0B\u8A18\u304C\u5B58\u5728\u3057\u307E\u3059\uFF1A{0} -DerviedFile.derivedMethod.exception.msg1.text=\u30D5\u30A1\u30A4\u30EBID\uFF1A{0}\u306E\u6D3E\u751F\u65B9\u6CD5\u3092\u53D6\u5F97\u4E2D\u306B\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F -BlackboardArtifact.tskGpsRoute.text=GPS\u30EB\u30FC\u30C8 -BlackboardAttribute.tskGeoLatitudeStart.text=\u30B9\u30BF\u30FC\u30C8\u7DEF\u5EA6 -BlackboardAttribute.tskGeoLatitudeEnd.text=\u7D42\u4E86\u7DEF\u5EA6 -BlackboardAttribute.tskGeoLongitudeStart.text=\u30B9\u30BF\u30FC\u30C8\u7D4C\u5EA6 -BlackboardAttribute.tskGeoLongitudeEnd.text=\u7D42\u4E86\u7D4C\u5EA6 -BlackboardAttribute.tskReadStatus.text=\u8AAD\u3080 \ No newline at end of file +BlackboardAttribute.tskEmailFrom.text=\u9001\u4fe1\u5143E\u30e1\u30fc\u30eb +BlackboardAttribute.tskEmailContentPlain.text=\u30e1\u30c3\u30bb\u30fc\u30b8\uff08\u30d7\u30ec\u30fc\u30f3\u30c6\u30ad\u30b9\u30c8\uff09 +BlackboardAttribute.tskEmailContentHtml.text=\u30e1\u30c3\u30bb\u30fc\u30b8\uff08HTML\uff09 +BlackboardAttribute.tskEmailContentRtf.text=\u30e1\u30c3\u30bb\u30fc\u30b8\uff08RTF\uff09 +BlackboardAttribute.tskMsgId.text=\u30e1\u30c3\u30bb\u30fc\u30b8ID +BlackboardAttribute.tskMsgReplyId.text=\u30e1\u30c3\u30bb\u30fc\u30b8\u30ea\u30d7\u30e9\u30a4ID +BlackboardAttribute.tskDateTimeRcvd.text=\u53d7\u4fe1\u65e5 +BlackboardAttribute.tskDateTimeSent.text=\u9001\u4fe1\u65e5 +BlackboardAttribute.tskSubject.text=\u30b5\u30d6\u30b8\u30a7\u30af\u30c8 +BlackboardAttribute.tskTitle.text=\u30bf\u30a4\u30c8\u30eb +BlackboardAttribute.tskGeoLatitude.text=\u7def\u5ea6 +BlackboardAttribute.tskGeoLongitude.text=\u7d4c\u5ea6 +BlackboardAttribute.tskGeoVelocity.text=\u901f\u5ea6 +BlackboardAttribute.tskGeoAltitude.text=\u6a19\u9ad8 +BlackboardAttribute.tskGeoBearing.text=\u65b9\u5411 +BlackboardAttribute.tskGeoHPrecision.text=\u6c34\u5e73\u7cbe\u5ea6 +BlackboardAttribute.tskGeoVPrecision.text=\u5782\u76f4\u7cbe\u5ea6 +BlackboardAttribute.tskGeoMapDatum.text=\u6e2c\u5730\u7cfb +BlackboardAttribute.tskFileTypeSig.text=\u30d5\u30a1\u30a4\u30eb\u30bf\u30a4\u30d7\uff08\u30b7\u30b0\u30cd\u30c1\u30e3\uff09 +BlackboardAttribute.tskFileTypeExt.text=\u30d5\u30a1\u30a4\u30eb\u30bf\u30a4\u30d7\uff08\u62e1\u5f35\u5b50\uff09 +BlackboardAttribute.tskTaggedArtifact.text=\u30bf\u30b0\u4ed8\u3051\u3055\u308c\u305f\u7d50\u679c +BlackboardAttribute.tskTagName.text=\u30bf\u30b0\u540d +BlackboardAttribute.tskComment.text=\u30b3\u30e1\u30f3\u30c8 +BlackboardAttribute.tskUrlDecoded.text=\u5fa9\u53f7\u5316\u3055\u308c\u305fURL +BlackboardAttribute.tskDateTimeCreated.text=\u4f5c\u6210\u65e5 +BlackboardAttribute.tskDateTimeModified.text=\u4fee\u6b63\u65e5 +BlackboardAttribute.tskProcessorArchitecture.text=\u30d7\u30ed\u30bb\u30c3\u30b5\u30a2\u30fc\u30ad\u30c6\u30af\u30c1\u30e3 +BlackboardAttribute.tskVersion.text=\u30d0\u30fc\u30b8\u30e7\u30f3 +BlackboardAttribute.tskUserId.text=\u30e6\u30fc\u30b6ID +BlackboardAttribute.tskDescription.text=\u8aac\u660e +BlackboardAttribute.tskMessageType.text=\u30e1\u30c3\u30bb\u30fc\u30b8\u30bf\u30a4\u30d7 +BlackboardAttribute.tskPhoneNumberHome.text=\u96fb\u8a71\u756a\u53f7\uff08\u81ea\u5b85\uff09 +BlackboardAttribute.tskPhoneNumberOffice.text=\u96fb\u8a71\u756a\u53f7\uff08\u4f1a\u793e\uff09 +BlackboardAttribute.tskPhoneNumberMobile.text=\u96fb\u8a71\u756a\u53f7\uff08\u643a\u5e2f\uff09 +BlackboardAttribute.tskPhoneNumberFrom.text=\u767a\u4fe1\u8005\u96fb\u8a71\u756a\u53f7 +BlackboardAttribute.tskPhoneNumberTo.text=\u7740\u4fe1\u8005\u96fb\u8a71\u756a\u53f7 +BlackboardAttribute.tskDirection.text=\u65b9\u5411 +BlackboardAttribute.tskEmailHome.text=E\u30e1\u30fc\u30eb\uff08\u81ea\u5b85\uff09 +BlackboardAttribute.tskEmailOffice.text=E\u30e1\u30fc\u30eb\uff08\u30aa\u30d5\u30a3\u30b9\uff09 +BlackboardAttribute.tskDateTimeStart.text=\u958b\u59cb\u65e5\u4ed8\uff0f\u6642\u523b +BlackboardAttribute.tskDateTimeEnd.text=\u7d42\u4e86\u65e5\u4ed8\uff0f\u6642\u523b +BlackboardAttribute.tskCalendarEntryType.text=\u30ab\u30ec\u30f3\u30c0\u30fc\u30a8\u30f3\u30c8\u30ea\u30fc\u30bf\u30a4\u30d7 +BlackboardAttribute.tskLocation.text=\u30ed\u30b1\u30fc\u30b7\u30e7\u30f3 +BlackboardAttribute.tskShortcut.text=\u30b7\u30e7\u30fc\u30c8\u30ab\u30c3\u30c8 +BlackboardAttribute.tskDeviceName.text=\u6a5f\u5668\u540d +BlackboardAttribute.tskCategory.text=\u30ab\u30c6\u30b4\u30ea\u30fc +BlackboardAttribute.tskEmailReplyTo.text=\u8fd4\u4fe1\u30a2\u30c9\u30ec\u30b9 +BlackboardAttribute.tskServerName.text=\u30b5\u30fc\u30d0\u540d +BlackboardAttribute.tskCount.text=\u30ab\u30a6\u30f3\u30c8 +BlackboardAttribute.tskMinCount.text=\u6700\u5c0f\u30ab\u30a6\u30f3\u30c8 +BlackboardAttribute.tskPathSource.text=\u30d1\u30b9\u30bd\u30fc\u30b9 +BlackboardAttribute.tskPermissions.text=\u30d1\u30fc\u30df\u30c3\u30b7\u30e7\u30f3 +BlackboardAttribute.tskAssociatedArtifact.text=\u95a2\u9023\u3059\u308b\u30a2\u30fc\u30c6\u30a3\u30d5\u30a1\u30af\u30c8 +BlackboardAttribute.tskIsDeleted.text=\u306f\u524a\u9664\u3055\u308c\u307e\u3057\u305f +AbstractFile.readLocal.exception.msg4.text=\u30d5\u30a1\u30a4\u30eb{0}\u306e\u8aad\u307f\u53d6\u308a\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f +AbstractFile.readLocal.exception.msg1.text=\u30ed\u30fc\u30ab\u30eb\u30d5\u30a1\u30a4\u30eb\u306e\u8aad\u307f\u53d6\u308a\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f\u3002\u30ed\u30fc\u30ab\u30eb\u30d1\u30b9\u304c\u30bb\u30c3\u30c8\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3002 +AbstractFile.readLocal.exception.msg2.text=\u30ed\u30fc\u30ab\u30eb\u30d5\u30a1\u30a4\u30eb\u306e\u8aad\u307f\u53d6\u308a\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f\u3002\u4e0b\u8a18\u306e\u30ed\u30fc\u30ab\u30eb\u30d1\u30b9\u306b\u306f\u5b58\u5728\u3057\u307e\u305b\u3093\uff1a{0} +AbstractFile.readLocal.exception.msg3.text=\u30ed\u30fc\u30ab\u30eb\u30d5\u30a1\u30a4\u30eb\u306e\u8aad\u307f\u53d6\u308a\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f\u3002\u4e0b\u8a18\u306e\u30ed\u30fc\u30ab\u30eb\u30d1\u30b9\u3067\u306f\u8aad\u307f\u53d6\u308a\u3067\u304d\u307e\u305b\u3093\uff1a{0} +AbstractFile.readLocal.exception.msg5.text=\u30ed\u30fc\u30ab\u30eb\u30d5\u30a1\u30a4\u30eb{0}\u3092\u8aad\u307f\u53d6\u308c\u307e\u305b\u3093 +FsContent.readInt.err.context.text=\u30a4\u30e1\u30fc\u30b8\u30d5\u30a1\u30a4\u30eb\u306e\u8aad\u307f\u53d6\u308a\u30a8\u30e9\u30fc +FsContent.readInt.err.msg.text=\u30a4\u30e1\u30fc\u30b8\u30d5\u30a1\u30a4\u30eb\u306f\u5b58\u5728\u3057\u306a\u3044\u304b\u3001\u30a2\u30af\u30bb\u30b9\u304c\u62d2\u5426\u3055\u308c\u307e\u3057\u305f\u3002 +Image.verifyImageSize.errStr1.text=\n\u4e0d\u5b8c\u5168\u306a\u30a4\u30e1\u30fc\u30b8\u306e\u53ef\u80fd\u6027\uff1a\u30aa\u30d5\u30bb\u30c3\u30c8{0}\u306b\u57fa\u3065\u3044\u305f\u30dc\u30ea\u30e5\u30fc\u30e0\u306e\u8aad\u307f\u53d6\u308a\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f +Image.verifyImageSize.errStr2.text=\n\u4e0d\u5b8c\u5168\u306a\u30a4\u30e1\u30fc\u30b8\u306e\u53ef\u80fd\u6027\uff1a\u30aa\u30d5\u30bb\u30c3\u30c8{0}\u306b\u57fa\u3065\u3044\u305f\u30dc\u30ea\u30e5\u30fc\u30e0\u306e\u8aad\u307f\u53d6\u308a\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f +Image.verifyImageSize.errStr3.text=\n\u4e0d\u5b8c\u5168\u306a\u30a4\u30e1\u30fc\u30b8\u306e\u53ef\u80fd\u6027\uff1a\u30aa\u30d5\u30bb\u30c3\u30c8{0}\u306b\u57fa\u3065\u3044\u305f\u30d5\u30a1\u30a4\u30eb\u30b7\u30b9\u30c6\u30e0\u306e\u8aad\u307f\u53d6\u308a\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f +Image.verifyImageSize.errStr4.text=\n\u4e0d\u5b8c\u5168\u306a\u30a4\u30e1\u30fc\u30b8\u306e\u53ef\u80fd\u6027\uff1a\u30aa\u30d5\u30bb\u30c3\u30c8{0}\u306b\u57fa\u3065\u3044\u305f\u30d5\u30a1\u30a4\u30eb\u30b7\u30b9\u30c6\u30e0\u306e\u8aad\u307f\u53d6\u308a\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f +SleuthkitCase.isFileFromSource.exception.msg.text=\u30a8\u30e9\u30fc\uff1a\u30c7\u30fc\u30bf\u30bd\u30fc\u30b9\u306f\u30da\u30a2\u30ec\u30f3\u30c8\u304c\u7121\u3044\uff08\u30a4\u30e1\u30fc\u30b8\u3001\u30d5\u30a1\u30a4\u30eb\u30bb\u30c3\u30c8\uff09\u306f\u305a\u3067\u3059\u304c\u3001\u4e0b\u8a18\u304c\u5b58\u5728\u3057\u307e\u3059\uff1a{0} +SleuthkitCase.findFiles.exception.msg3.text=\u30d5\u30a1\u30a4\u30eb\u540d\u306b\u57fa\u3065\u3044\u305f\u30c7\u30fc\u30bf\u30bd\u30fc\u30b9\u306e\u691c\u7d22\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f\u3002 +SleuthkitCase.findFiles3.exception.msg1.text=\u30a8\u30e9\u30fc\uff1a\u30c7\u30fc\u30bf\u30bd\u30fc\u30b9\u306f\u30da\u30a2\u30ec\u30f3\u30c8\u304c\u7121\u3044\uff08\u30a4\u30e1\u30fc\u30b8\u3001\u30d5\u30a1\u30a4\u30eb\u30bb\u30c3\u30c8\uff09\u306f\u305a\u3067\u3059\u304c\u3001\u4e0b\u8a18\u304c\u5b58\u5728\u3057\u307e\u3059\uff1a{0} +SleuthkitCase.findFiles3.exception.msg3.text=\u30d5\u30a1\u30a4\u30eb\u540d\u306b\u57fa\u3065\u3044\u305f\u30c7\u30fc\u30bf\u30bd\u30fc\u30b9\u306e\u691c\u7d22\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f\u3002 +SleuthkitCase.addDerivedFile.exception.msg1.text=\u6d3e\u751f\u30d5\u30a1\u30a4\u30eb\u306e\u4f5c\u6210\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f\u3002\u30aa\u30d6\u30b8\u30a7\u30af\u30c8\u306e\u65b0\u898fID\u3092\u53d6\u5f97\u3067\u304d\u307e\u305b\u3093\u3002\u30d5\u30a1\u30a4\u30eb\u540d\uff1a{0} +SleuthkitCase.addDerivedFile.exception.msg2.text=\u6d3e\u751f\u30d5\u30a1\u30a4\u30eb\u306e\u4f5c\u6210\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f\u3002\u30d5\u30a1\u30a4\u30eb\u540d\uff1a{0} +SleuthkitCase.addLocalFile.exception.msg1.text=\u30ed\u30fc\u30ab\u30eb\u30d5\u30a1\u30a4\u30eb{0}\u306e\u8ffd\u52a0\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f\u3002\u8ffd\u52a0\u5148\u306e\u30da\u30a2\u30ec\u30f3\u30c8\u304c\u30cc\u30eb\u3067\u3059\u3002 +SleuthkitCase.addLocalFile.exception.msg2.text=\u30ed\u30fc\u30ab\u30eb\u30d5\u30a1\u30a4\u30eb\u306e\u4f5c\u6210\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f\u3002\u30aa\u30d6\u30b8\u30a7\u30af\u30c8\u306e\u65b0\u898fID\u3092\u53d6\u5f97\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002\u30d5\u30a1\u30a4\u30eb\u540d\uff1a{0} +SleuthkitCase.addLocalFile.exception.msg3.text=\u6d3e\u751f\u30d5\u30a1\u30a4\u30eb\u306e\u4f5c\u6210\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f\u3002\u30d5\u30a1\u30a4\u30eb\u540d\uff1a{0} +SleuthkitCase.getLastObjectId.exception.msg.text=\u6700\u5f8c\u306e\u30aa\u30d6\u30b8\u30a7\u30af\u30c8ID\u3092\u53d6\u5f97\u3057\u305f\u5f8c\u3001\u7d50\u679c\u30bb\u30c3\u30c8\u3092\u9589\u3058\u308b\u969b\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f\u3002 +TskData.tskFsNameFlagEnum.allocated=\u5272\u308a\u5f53\u3066\u6e08\u307f +TskData.tskFsNameFlagEnum.unallocated=\u672a\u5272\u308a\u5f53\u3066 +TskData.tskFsMetaFlagEnum.allocated=\u5272\u308a\u5f53\u3066\u6e08\u307f +TskData.tskFsMetaFlagEnum.unallocated=\u672a\u5272\u308a\u5f53\u3066 +TskData.tskFsMetaFlagEnum.used=\u4f7f\u7528\u6e08\u307f +TskData.tskFsMetaFlagEnum.unused=\u672a\u4f7f\u7528 +TskData.tskFsMetaFlagEnum.compressed=\u5727\u7e2e\u6e08\u307f +TskData.tskFsMetaFlagEnum.orphan=\u30aa\u30fc\u30d5\u30a1\u30f3 +TskData.tskImgTypeEnum.autoDetect=\u81ea\u52d5\u691c\u51fa +TskData.tskImgTypeEnum.rawSingle=\u30ed\u30fc\u30b7\u30f3\u30b0\u30eb +TskData.tskImgTypeEnum.rawSplit=\u30ed\u30fc\u30b9\u30d7\u30ea\u30c3\u30c8 +TskData.tskImgTypeEnum.unknown=\u4e0d\u660e +TskData.tskVSTypeEnum.autoDetect=\u81ea\u52d5\u691c\u51fa +TskData.tskVSTypeEnum.fake=\u507d\u7269 +TskData.tskVSTypeEnum.unsupported=\u975e\u30b5\u30dd\u30fc\u30c8 +TskData.fileKnown.unknown=\u4e0d\u660e +TskData.fileKnown.known=\u65e2\u77e5 +TskData.fileKnown.knownBad=\u65e2\u77e5\u60aa\u8cea +TskData.objectTypeEnum.exception.msg1.text=\u30d0\u30ea\u30e5\u30fc\uff1a{0}\u306f\u30aa\u30d6\u30b8\u30a7\u30af\u30c8\u30bf\u30a4\u30d7\u306b\u8a72\u5f53\u3057\u307e\u305b\u3093 +Volume.desc.text=\u4e0d\u660e +Volume.read.exception.msg1.text=\u3053\u306e\u30dc\u30ea\u30e5\u30fc\u30e0\u306e\u30da\u30a2\u30ec\u30f3\u30c8\u306fVolmueSystem\u3067\u3042\u308b\u3079\u304d\u3067\u3059\u304c\u3001\u9055\u3044\u307e\u3059\u3002 +Volume.vsFlagToString.allocated=\u5272\u308a\u5f53\u3066\u6e08\u307f +Volume.vsFlagToString.unallocated=\u672a\u5272\u308a\u5f53\u3066 +TskData.fileKnown.exception.msg1.text=\u30d0\u30ea\u30e5\u30fc\uff1a{0}\u306fFileKnown\u306b\u8a72\u5f53\u3057\u307e\u305b\u3093 +TskData.tskDbFilesTypeEnum.exception.msg1.text=\u30d0\u30ea\u30e5\u30fc\uff1a{0}\u306fTSK_FILE_TYPE_ENUM\u306b\u8a72\u5f53\u3057\u307e\u305b\u3093 +TskData.tskVSTypeEnum.exception.msg1.text=\u30d0\u30ea\u30e5\u30fc\uff1a{0}\u306fTSK_VS_TYPE_ENUM\u306b\u8a72\u5f53\u3057\u307e\u305b\u3093 +TskData.tskImgTypeEnum.exception.msg1.text=\u30d0\u30ea\u30e5\u30fc\uff1a{0}\u306fTSK_IMG_TYPE_ENUM\u306b\u8a72\u5f53\u3057\u307e\u305b\u3093 +TskData.tskFsTypeEnum.exception.msg1.text=\u30d0\u30ea\u30e5\u30fc\uff1a{0}\u306fTSK_FS_TYPE_ENUM\u306b\u8a72\u5f53\u3057\u307e\u305b\u3093 +TskData.tskFsAttrTypeEnum.exception.msg1.text=\u30d0\u30ea\u30e5\u30fc\uff1a{0}\u306fTSK_FS_TYPE_ENUM\u306b\u8a72\u5f53\u3057\u307e\u305b\u3093 +TskData.tskFsNameFlagEnum.exception.msg1.text=\u30d0\u30ea\u30e5\u30fc\uff1a{0}\u306fTSK_FS_NAME_FLAG_ENUM\u306b\u8a72\u5f53\u3057\u307e\u305b\u3093 +TskData.tskFsMetaTypeEnum.exception.msg1.text=\u30d0\u30ea\u30e5\u30fc\uff1a{0}\u306fTSK_FS_META_TYPE_ENUM\u306b\u8a72\u5f53\u3057\u307e\u305b\u3093 +TskData.tskFsNameTypeEnum.exception.msg1.text=\u30d0\u30ea\u30e5\u30fc\uff1a{0}\u306fTSK_FS_NAME_TYPE_ENUM\u306b\u8a72\u5f53\u3057\u307e\u305b\u3093 +SleuthkitCase.findFiles.exception.msg1.text=\u30a8\u30e9\u30fc\uff1a\u30c7\u30fc\u30bf\u30bd\u30fc\u30b9\u306f\u30da\u30a2\u30ec\u30f3\u30c8\u304c\u7121\u3044\uff08\u30a4\u30e1\u30fc\u30b8\u3001\u30d5\u30a1\u30a4\u30eb\u30bb\u30c3\u30c8\uff09\u306f\u305a\u3067\u3059\u304c\u3001\u4e0b\u8a18\u304c\u5b58\u5728\u3057\u307e\u3059\uff1a{0} +SleuthkitCase.isFileFromSource.exception.msg2.text=\u30a8\u30e9\u30fc\uff1a\u30c7\u30fc\u30bf\u30bd\u30fc\u30b9\u306f\u30a4\u30e1\u30fc\u30b8\u307e\u305f\u306fVirtualDirectory\u3067\u3042\u308b\u3079\u304d\u3067\u3059\u304c\u3001\u4e0b\u8a18\u304c\u5b58\u5728\u3057\u307e\u3059\uff1a{0} +SleuthkitCase.findFiles.exception.msg2.text=\u30a8\u30e9\u30fc\uff1a\u30c7\u30fc\u30bf\u30bd\u30fc\u30b9\u306f\u30a4\u30e1\u30fc\u30b8\u307e\u305f\u306fVirtualDirectory\u3067\u3042\u308b\u3079\u304d\u3067\u3059\u304c\u3001\u4e0b\u8a18\u304c\u5b58\u5728\u3057\u307e\u3059\uff1a{0} +SleuthkitCase.findFiles3.exception.msg2.text=\u30a8\u30e9\u30fc\uff1a\u30c7\u30fc\u30bf\u30bd\u30fc\u30b9\u306f\u30a4\u30e1\u30fc\u30b8\u307e\u305f\u306fVirtualDirectory\u3067\u3042\u308b\u3079\u304d\u3067\u3059\u304c\u3001\u4e0b\u8a18\u304c\u5b58\u5728\u3057\u307e\u3059\uff1a{0} +DerviedFile.derivedMethod.exception.msg1.text=\u30d5\u30a1\u30a4\u30ebID\uff1a{0}\u306e\u6d3e\u751f\u65b9\u6cd5\u3092\u53d6\u5f97\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f +BlackboardArtifact.tskGpsRoute.text=GPS\u30eb\u30fc\u30c8 +BlackboardAttribute.tskGeoLatitudeStart.text=\u30b9\u30bf\u30fc\u30c8\u7def\u5ea6 +BlackboardAttribute.tskGeoLatitudeEnd.text=\u7d42\u4e86\u7def\u5ea6 +BlackboardAttribute.tskGeoLongitudeStart.text=\u30b9\u30bf\u30fc\u30c8\u7d4c\u5ea6 +BlackboardAttribute.tskGeoLongitudeEnd.text=\u7d42\u4e86\u7d4c\u5ea6 +BlackboardAttribute.tskReadStatus.text=\u8aad\u3080 diff --git a/bindings/java/src/org/sleuthkit/datamodel/EventDB.java b/bindings/java/src/org/sleuthkit/datamodel/EventDB.java new file mode 100644 index 0000000000000000000000000000000000000000..121091c0e0f56ab5ffdff7497c65f2f0550702d6 --- /dev/null +++ b/bindings/java/src/org/sleuthkit/datamodel/EventDB.java @@ -0,0 +1,1720 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2013-18 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 com.google.common.collect.HashMultimap; +import com.google.common.collect.SetMultimap; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.sql.Types; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.function.Function; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.stream.Collectors; +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.joda.time.DateTimeZone; +import org.joda.time.Interval; +import org.joda.time.Period; +import static org.sleuthkit.datamodel.SleuthkitCase.closeResultSet; +import static org.sleuthkit.datamodel.SleuthkitCase.closeStatement; +import org.sleuthkit.datamodel.timeline.BaseTypes; +import org.sleuthkit.datamodel.timeline.CombinedEvent; +import org.sleuthkit.datamodel.timeline.DescriptionLoD; +import org.sleuthkit.datamodel.timeline.EventCluster; +import org.sleuthkit.datamodel.timeline.EventStripe; +import org.sleuthkit.datamodel.timeline.EventType; +import org.sleuthkit.datamodel.timeline.EventTypeZoomLevel; +import org.sleuthkit.datamodel.timeline.RangeDivisionInfo; +import org.sleuthkit.datamodel.timeline.RootEventType; +import org.sleuthkit.datamodel.timeline.SingleEvent; +import org.sleuthkit.datamodel.timeline.TimeUnits; +import org.sleuthkit.datamodel.timeline.ZoomParams; +import org.sleuthkit.datamodel.timeline.filters.AbstractFilter; +import org.sleuthkit.datamodel.timeline.filters.DataSourceFilter; +import org.sleuthkit.datamodel.timeline.filters.DataSourcesFilter; +import org.sleuthkit.datamodel.timeline.filters.DescriptionFilter; +import org.sleuthkit.datamodel.timeline.filters.Filter; +import org.sleuthkit.datamodel.timeline.filters.HashHitsFilter; +import org.sleuthkit.datamodel.timeline.filters.HashSetFilter; +import org.sleuthkit.datamodel.timeline.filters.HideKnownFilter; +import org.sleuthkit.datamodel.timeline.filters.IntersectionFilter; +import org.sleuthkit.datamodel.timeline.filters.RootFilter; +import org.sleuthkit.datamodel.timeline.filters.TagNameFilter; +import org.sleuthkit.datamodel.timeline.filters.TagsFilter; +import org.sleuthkit.datamodel.timeline.filters.TextFilter; +import org.sleuthkit.datamodel.timeline.filters.TypeFilter; +import org.sleuthkit.datamodel.timeline.filters.UnionFilter; +import org.sqlite.SQLiteJDBCLoader; + +/** + * Provides access to the Timeline SQLite database. + * + * This class borrows a lot of ideas and techniques from SleuthkitCase. Creating + * an abstract base class for SQLite databases, or using a higherlevel + * persistence api may make sense in the future. + */ +public class EventDB { + + private static final java.util.logging.Logger LOGGER = Logger.getLogger(EventDB.class.getName()); + + static { + //make sure sqlite driver is loaded, possibly redundant + try { + Class.forName("org.sqlite.JDBC"); // NON-NLS + } catch (ClassNotFoundException ex) { + LOGGER.log(Level.SEVERE, "Failed to load sqlite JDBC driver", ex); // NON-NLS + } + } + + /** + * public factory method. Creates and opens a connection to a database at + * the given path. If a database does not already exist at that path, one is + * created. + * + * @param autoCase the Autopsy Case the is events database is for. + * + * @return a new EventDB or null if there was an error. + */ + public static EventDB getEventDB(SleuthkitCase autoCase) { + try { + return new EventDB(autoCase); + } catch (Exception ex) { + LOGGER.log(Level.SEVERE, "error creating database connection", ex); // NON-NLS + return null; + } + // NON-NLS + + } + + private volatile SleuthkitCase.CaseDbConnection con; + + private PreparedStatement getEventByIDStmt; + private PreparedStatement getMaxTimeStmt; + private PreparedStatement getMinTimeStmt; + private PreparedStatement getDataSourceIDsStmt; + private PreparedStatement getHashSetNamesStmt; + private PreparedStatement insertRowStmt; + private PreparedStatement insertHashSetStmt; + private PreparedStatement insertHashHitStmt; + private PreparedStatement insertTagStmt; + private PreparedStatement deleteTagStmt; + private PreparedStatement selectHashSetStmt; + private PreparedStatement countAllEventsStmt; + private PreparedStatement dropEventsTableStmt; + private PreparedStatement dropHashSetHitsTableStmt; + private PreparedStatement dropHashSetsTableStmt; + private PreparedStatement dropTagsTableStmt; + private PreparedStatement dropDBInfoTableStmt; + private PreparedStatement selectNonArtifactEventIDsByObjectIDStmt; + private PreparedStatement selectEventIDsBYObjectAndArtifactIDStmt; + + private final Set<PreparedStatement> preparedStatements = new HashSet<PreparedStatement>(); + + private final Lock DBLock = new ReentrantReadWriteLock(true).writeLock(); //using exclusive lock for all db ops for now + private final SleuthkitCase sleuthkitCase; + + private EventDB(SleuthkitCase tskCase) throws SQLException, Exception { + //should this go into module output (or even cache, we should be able to rebuild it)? + sleuthkitCase = tskCase; + initializeDB(); + } + + @Override + public void finalize() throws Throwable { + try { + closeDBCon(); + } finally { + super.finalize(); + } + } + + void closeDBCon() { + if (con != null) { + try { + closeStatements(); + con.close(); + } catch (SQLException ex) { + LOGGER.log(Level.WARNING, "Failed to close connection to evetns.db", ex); // NON-NLS + } + } + con = null; + } + + public Interval getSpanningInterval(Collection<Long> eventIDs) { + DBLock.lock(); + Statement stmt = null; + ResultSet rs = null; // NON-NLS + try { + stmt = con.createStatement(); + rs = stmt.executeQuery("SELECT Min(time), Max(time) FROM events WHERE event_id IN (" + StringUtils.joinAsStrings(eventIDs, ", ") + ")"); + while (rs.next()) { + return new Interval(rs.getLong("Min(time)") * 1000, (rs.getLong("Max(time)") + 1) * 1000, DateTimeZone.UTC); // NON-NLS + } + } catch (SQLException ex) { + LOGGER.log(Level.SEVERE, "Error executing get spanning interval query.", ex); // NON-NLS + } finally { + SleuthkitCase.closeStatement(stmt); + SleuthkitCase.closeResultSet(rs); + DBLock.unlock(); + } + return null; + } + + public EventTransaction beginTransaction() { + return new EventTransaction(); + } + + public void commitTransaction(EventTransaction tr) { + if (tr.isClosed()) { + throw new IllegalArgumentException("can't close already closed transaction"); // NON-NLS + } + tr.commit(); + } + + /** + * @return the total number of events in the database or, -1 if there is an + * error. + */ + public int countAllEvents() { + DBLock.lock(); + ResultSet rs = null; + try { + rs = countAllEventsStmt.executeQuery(); + + while (rs.next()) { + return rs.getInt("count"); // NON-NLS + } + } catch (SQLException ex) { + LOGGER.log(Level.SEVERE, "Error counting all events", ex); //NON-NLS + } finally { + SleuthkitCase.closeResultSet(rs); + DBLock.unlock(); + } + return -1; + } + + /** + * get the count of all events that fit the given zoom params organized by + * the EvenType of the level spcified in the ZoomParams + * + * @param params the params that control what events to count and how to + * organize the returned map + * + * @return a map from event type( of the requested level) to event counts + */ + public Map<EventType, Long> countEventsByType(ZoomParams params) { + if (params.getTimeRange() != null) { + return countEventsByType(params.getTimeRange().getStartMillis() / 1000, + params.getTimeRange().getEndMillis() / 1000, + params.getFilter(), params.getTypeZoomLevel()); + } else { + return Collections.emptyMap(); + } + } + + /** + * get a count of tagnames applied to the given event ids as a map from + * tagname displayname to count of tag applications + * + * @param eventIDsWithTags the event ids to get the tag counts map for + * + * @return a map from tagname displayname to count of applications + */ + public Map<String, Long> getTagCountsByTagName(Set<Long> eventIDsWithTags) { + HashMap<String, Long> counts = new HashMap<String, Long>(); + DBLock.lock(); + ResultSet resultSet = null; + Statement statement = null; + try { + + statement = con.createStatement(); + resultSet = statement.executeQuery("SELECT tag_name_display_name, COUNT(DISTINCT tag_id) AS count FROM tags" //NON-NLS + + " WHERE event_id IN (" + StringUtils.joinAsStrings(eventIDsWithTags, ", ") + ")" //NON-NLS + + " GROUP BY tag_name_id" //NON-NLS + + " ORDER BY tag_name_display_name"); + while (resultSet.next()) { + counts.put(resultSet.getString("tag_name_display_name"), resultSet.getLong("count")); //NON-NLS + } + } catch (SQLException ex) { + LOGGER.log(Level.SEVERE, "Failed to get tag counts by tag name.", ex); //NON-NLS + } finally { + SleuthkitCase.closeResultSet(resultSet); + SleuthkitCase.closeStatement(statement); + DBLock.unlock(); + } + return counts; + } + + /** + * drop the tables from this database and recreate them in order to start + * over. + */ + public void reInitializeDB() throws TskCoreException { + DBLock.lock(); + try { + dropEventsTableStmt.executeUpdate(); + dropHashSetHitsTableStmt.executeUpdate(); + dropHashSetsTableStmt.executeUpdate(); + dropTagsTableStmt.executeUpdate(); + dropDBInfoTableStmt.executeUpdate(); + initializeDB(); + } catch (SQLException ex) { + LOGGER.log(Level.SEVERE, "could not drop old tables", ex); // NON-NLS + } finally { + DBLock.unlock(); + } + } + + /** + * drop only the tags table and rebuild it incase the tags have changed + * while TL was not listening, + */ + public void reInitializeTags() { + DBLock.lock(); + try { + dropTagsTableStmt.executeUpdate(); + initializeTagsTable(); + } catch (SQLException ex) { + LOGGER.log(Level.SEVERE, "could not drop old tags table", ex); // NON-NLS + } finally { + DBLock.unlock(); + } + } + + public Interval getBoundingEventsInterval(Interval timeRange, RootFilter filter, DateTimeZone tz) { + long start = timeRange.getStartMillis() / 1000; + long end = timeRange.getEndMillis() / 1000; + final String sqlWhere = getSQLWhere(filter); + DBLock.lock(); + Statement stmt = null; //can't use prepared statement because of complex where clause + ResultSet rs = null; + try { + stmt = con.createStatement(); + + rs = stmt.executeQuery(" SELECT (SELECT Max(time) FROM events " + useHashHitTablesHelper(filter) + useTagTablesHelper(filter) + " WHERE time <=" + start + " AND " + sqlWhere + ") AS start," //NON-NLS + + "(SELECT Min(time) FROM events" + useHashHitTablesHelper(filter) + useTagTablesHelper(filter) + " WHERE time >= " + end + " AND " + sqlWhere + ") AS end"); + while (rs.next()) { + + long start2 = rs.getLong("start"); // NON-NLS + long end2 = rs.getLong("end"); // NON-NLS + + if (end2 == 0) { + end2 = getMaxTime(); + } + return new Interval(start2 * 1000, (end2 + 1) * 1000, tz); + } + } catch (SQLException ex) { + LOGGER.log(Level.SEVERE, "Failed to get MIN time.", ex); // NON-NLS + } finally { + closeResultSet(rs); + closeStatement(stmt); + DBLock.unlock(); + } + return null; + } + + public SingleEvent getEventById(Long eventID) { + SingleEvent result = null; + DBLock.lock(); + ResultSet rs = null; + try { + getEventByIDStmt.clearParameters(); + getEventByIDStmt.setLong(1, eventID); + + rs = getEventByIDStmt.executeQuery(); + while (rs.next()) { + result = constructTimeLineEvent(rs); + break; + } + } catch (SQLException sqlEx) { + LOGGER.log(Level.SEVERE, "exception while querying for event with id = " + eventID, sqlEx); // NON-NLS + } finally { + closeResultSet(rs); + DBLock.unlock(); + } + return result; + } + + /** + * Get the IDs of all the events within the given time range that pass the + * given filter. + * + * @param timeRange The Interval that all returned events must be within. + * @param filter The Filter that all returned events must pass. + * + * @return A List of event ids, sorted by timestamp of the corresponding + * event.. + */ + public List<Long> getEventIDs(Interval timeRange, RootFilter filter) { + 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 + } + + ArrayList<Long> resultIDs = new ArrayList<Long>(); + + DBLock.lock(); + final String query = "SELECT events.event_id AS event_id FROM events" + useHashHitTablesHelper(filter) + useTagTablesHelper(filter) + + " WHERE time >= " + startTime + " AND time <" + endTime + " AND " + getSQLWhere(filter) + " ORDER BY time ASC"; // NON-NLS + Statement stmt = null; + ResultSet rs = null; + try { + stmt = con.createStatement(); + rs = stmt.executeQuery(query); + while (rs.next()) { + resultIDs.add(rs.getLong("event_id")); //NON-NLS + } + + } catch (SQLException sqlEx) { + LOGGER.log(Level.SEVERE, "failed to execute query for event ids in range", sqlEx); // NON-NLS + } finally { + closeResultSet(rs); + closeStatement(stmt); + DBLock.unlock(); + } + + return resultIDs; + } + + /** + * Get a representation of all the events, within the given time range, that + * pass the given filter, grouped by time and description such that file + * system events for the same file, with the same timestamp, are combined + * together. + * + * @param timeRange The Interval that all returned events must be within. + * @param filter The Filter that all returned events must pass. + * + * @return A List of combined events, sorted by timestamp. + */ + public List<CombinedEvent> getCombinedEvents(Interval timeRange, RootFilter filter) { + 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 + } + + ArrayList<CombinedEvent> results = new ArrayList<CombinedEvent>(); + + DBLock.lock(); + final String query = "SELECT full_description, time, file_id, GROUP_CONCAT(events.event_id), GROUP_CONCAT(sub_type)" + + " FROM events " + useHashHitTablesHelper(filter) + useTagTablesHelper(filter) + + " WHERE time >= " + startTime + " AND time <" + endTime + " AND " + getSQLWhere(filter) + + " GROUP BY time,full_description, file_id ORDER BY time ASC, full_description"; + Statement stmt = null; + ResultSet rs = null; + try { + + stmt = con.createStatement(); + rs = stmt.executeQuery(query); + + while (rs.next()) { + + //make a map from event type to event ID + List<Long> eventIDs = unGroupConcat(rs.getString("GROUP_CONCAT(events.event_id)"), Long::valueOf); + List<EventType> eventTypes = unGroupConcat(rs.getString("GROUP_CONCAT(sub_type)"), (String s) -> RootEventType.allTypes.get(Integer.valueOf(s))); + Map<EventType, Long> eventMap = new HashMap<EventType, Long>(); + for (int i = 0; i < eventIDs.size(); i++) { + eventMap.put(eventTypes.get(i), eventIDs.get(i)); + } + results.add(new CombinedEvent(rs.getLong("time") * 1000, rs.getString("full_description"), rs.getLong("file_id"), eventMap)); + } + + } catch (SQLException sqlEx) { + LOGGER.log(Level.SEVERE, "failed to execute query for combined events", sqlEx); // NON-NLS + } finally { + closeResultSet(rs); + closeStatement(stmt); + DBLock.unlock(); + } + + return results; + } + + /** + * this relies on the fact that no tskObj has ID 0 but 0 is the default + * value for the datasource_id column in the events table. + */ + public boolean hasNewColumns() { + return hasHashHitColumn() && hasDataSourceIDColumn() && hasTaggedColumn() + && (getDataSourceIDs().isEmpty() == false); + } + + public Set<Long> getDataSourceIDs() { + HashSet<Long> hashSet = new HashSet<Long>(); + DBLock.lock(); + ResultSet rs = null; + try { + rs = getDataSourceIDsStmt.executeQuery(); + while (rs.next()) { + long datasourceID = rs.getLong("datasource_id"); //NON-NLS + hashSet.add(datasourceID); + } + } catch (SQLException ex) { + LOGGER.log(Level.SEVERE, "Failed to get MAX time.", ex); // NON-NLS + } finally { + + closeResultSet(rs); + DBLock.unlock(); + } + return hashSet; + } + + public Map<Long, String> getHashSetNames() { + Map<Long, String> hashSets = new HashMap<Long, String>(); + DBLock.lock(); + ResultSet rs = null; + try { + rs = getHashSetNamesStmt.executeQuery(); + while (rs.next()) { + long hashSetID = rs.getLong("hash_set_id"); //NON-NLS + String hashSetName = rs.getString("hash_set_name"); //NON-NLS + hashSets.put(hashSetID, hashSetName); + } + } catch (SQLException ex) { + LOGGER.log(Level.SEVERE, "Failed to get hash sets.", ex); // NON-NLS + } finally { + closeResultSet(rs); + DBLock.unlock(); + } + return Collections.unmodifiableMap(hashSets); + } + + public void analyze() { + DBLock.lock(); + Statement createStatement = null; + try { + createStatement = con.createStatement(); + + boolean b = createStatement.execute("analyze; analyze sqlite_master;"); //NON-NLS + } catch (SQLException ex) { + LOGGER.log(Level.SEVERE, "Failed to analyze events db.", ex); // NON-NLS + } finally { + closeStatement(createStatement); + DBLock.unlock(); + } + } + + /** + * @return maximum time in seconds from unix epoch + */ + public Long getMaxTime() { + DBLock.lock(); + ResultSet rs = null; + try { + rs = getMaxTimeStmt.executeQuery(); + while (rs.next()) { + return rs.getLong("max"); // NON-NLS + } + } catch (SQLException ex) { + LOGGER.log(Level.SEVERE, "Failed to get MAX time.", ex); // NON-NLS + } finally { + closeResultSet(rs); + DBLock.unlock(); + } + return -1l; + } + + /** + * @return maximum time in seconds from unix epoch + */ + public Long getMinTime() { + DBLock.lock(); + ResultSet rs = null; + try { + rs = getMinTimeStmt.executeQuery(); + while (rs.next()) { + return rs.getLong("min"); // NON-NLS + } + } catch (SQLException ex) { + LOGGER.log(Level.SEVERE, "Failed to get MIN time.", ex); // NON-NLS + } finally { + closeResultSet(rs); + DBLock.unlock(); + } + return -1l; + } + + /** + * create the table and indices if they don't already exist + * + * @return the number of rows in the table , count > 0 indicating an + * existing table + */ + final synchronized void initializeDB() throws TskCoreException { + + if (con == null || con.isOpen() == false) { + con = sleuthkitCase.getConnection(); + } + try { + configureDB(); + } catch (SQLException ex) { + LOGGER.log(Level.SEVERE, "problem accessing database", ex); // NON-NLS + return; + } + + DBLock.lock(); + try { + + ///TODO: Move to SleuthkitCase + Statement stmt = null; + stmt = con.createStatement(); + try { + String sql = "CREATE TABLE if not exists db_info " // NON-NLS + + " ( key TEXT, " // NON-NLS + + " value INTEGER, " // NON-NLS + + "PRIMARY KEY (key))"; // NON-NLS + stmt.execute(sql); + } catch (SQLException ex) { + LOGGER.log(Level.SEVERE, "problem creating db_info table", ex); // NON-NLS + } + + try { + String sql = "CREATE TABLE if not exists events " // NON-NLS + + " (event_id INTEGER PRIMARY KEY, " // NON-NLS + + " datasource_id INTEGER, " // NON-NLS + + " file_id INTEGER, " // NON-NLS + + " artifact_id INTEGER, " // NON-NLS + + " time INTEGER, " // NON-NLS + + " sub_type INTEGER, " // NON-NLS + + " base_type INTEGER, " // NON-NLS + + " full_description TEXT, " // NON-NLS + + " med_description TEXT, " // NON-NLS + + " short_description TEXT, " // NON-NLS + + " known_state INTEGER," //boolean // NON-NLS + + " hash_hit INTEGER," //boolean // NON-NLS + + " tagged INTEGER)"; //boolean // NON-NLS + stmt.execute(sql); + } catch (SQLException ex) { + LOGGER.log(Level.SEVERE, "problem creating database table", ex); // NON-NLS + } + + if (hasDataSourceIDColumn() == false) { + try { + String sql = "ALTER TABLE events ADD COLUMN datasource_id INTEGER"; // NON-NLS + stmt.execute(sql); + } catch (SQLException ex) { + LOGGER.log(Level.SEVERE, "problem upgrading events table", ex); // NON-NLS + } + } + if (hasTaggedColumn() == false) { + try { + String sql = "ALTER TABLE events ADD COLUMN tagged INTEGER"; // NON-NLS + stmt.execute(sql); + } catch (SQLException ex) { + LOGGER.log(Level.SEVERE, "problem upgrading events table", ex); // NON-NLS + } + } + + if (hasHashHitColumn() == false) { + try { + String sql = "ALTER TABLE events ADD COLUMN hash_hit INTEGER"; // NON-NLS + stmt.execute(sql); + } catch (SQLException ex) { + LOGGER.log(Level.SEVERE, "problem upgrading events table", ex); // NON-NLS + } + } + + try { + String sql = "CREATE TABLE if not exists hash_sets " //NON-NLS + + "( hash_set_id INTEGER primary key," //NON-NLS + + " hash_set_name VARCHAR(255) UNIQUE NOT NULL)"; //NON-NLS + stmt.execute(sql); + } catch (SQLException ex) { + LOGGER.log(Level.SEVERE, "problem creating hash_sets table", ex); //NON-NLS + } + + try { + String sql = "CREATE TABLE if not exists hash_set_hits " //NON-NLS + + "(hash_set_id INTEGER REFERENCES hash_sets(hash_set_id) not null, " //NON-NLS + + " event_id INTEGER REFERENCES events(event_id) not null, " //NON-NLS + + " PRIMARY KEY (hash_set_id, event_id))"; //NON-NLS + stmt.execute(sql); + } catch (SQLException ex) { + LOGGER.log(Level.SEVERE, "problem creating hash_set_hits table", ex); //NON-NLS + } + + initializeTagsTable(); + + createIndex("events", Arrays.asList("datasource_id")); //NON-NLS + createIndex("events", Arrays.asList("event_id", "hash_hit")); //NON-NLS + createIndex("events", Arrays.asList("event_id", "tagged")); //NON-NLS + createIndex("events", Arrays.asList("file_id")); //NON-NLS + createIndex("events", Arrays.asList("artifact_id")); //NON-NLS + createIndex("events", Arrays.asList("sub_type", "short_description", "time")); //NON-NLS + createIndex("events", Arrays.asList("base_type", "short_description", "time")); //NON-NLS + createIndex("events", Arrays.asList("time")); //NON-NLS + createIndex("events", Arrays.asList("known_state")); //NON-NLS + + try { + insertRowStmt = prepareStatement( + "INSERT INTO events (datasource_id,file_id ,artifact_id, time, sub_type, base_type, full_description, med_description, short_description, known_state, hash_hit, tagged) " // NON-NLS + + "VALUES (?,?,?,?,?,?,?,?,?,?,?,?)"); // NON-NLS + getHashSetNamesStmt = prepareStatement("SELECT hash_set_id, hash_set_name FROM hash_sets"); // NON-NLS + getDataSourceIDsStmt = prepareStatement("SELECT DISTINCT datasource_id FROM events WHERE datasource_id != 0"); // NON-NLS + getMaxTimeStmt = prepareStatement("SELECT Max(time) AS max FROM events"); // NON-NLS + getMinTimeStmt = prepareStatement("SELECT Min(time) AS min FROM events"); // NON-NLS + getEventByIDStmt = prepareStatement("SELECT * FROM events WHERE event_id = ?"); // NON-NLS + insertHashSetStmt = prepareStatement("INSERT OR IGNORE INTO hash_sets (hash_set_name) values (?)"); //NON-NLS + selectHashSetStmt = prepareStatement("SELECT hash_set_id FROM hash_sets WHERE hash_set_name = ?"); //NON-NLS + insertHashHitStmt = prepareStatement("INSERT OR IGNORE INTO hash_set_hits (hash_set_id, event_id) values (?,?)"); //NON-NLS + insertTagStmt = prepareStatement("INSERT OR IGNORE INTO tags (tag_id, tag_name_id,tag_name_display_name, event_id) values (?,?,?,?)"); //NON-NLS + deleteTagStmt = prepareStatement("DELETE FROM tags WHERE tag_id = ?"); //NON-NLS + + /* + * This SQL query is really just a select count(*), but that has + * performance problems on very large tables unless you include + * a where clause see http://stackoverflow.com/a/9338276/4004683 + * for more. + */ + countAllEventsStmt = prepareStatement("SELECT count(event_id) AS count FROM events WHERE event_id IS NOT null"); //NON-NLS + dropEventsTableStmt = prepareStatement("DROP TABLE IF EXISTS events"); //NON-NLS + dropHashSetHitsTableStmt = prepareStatement("DROP TABLE IF EXISTS hash_set_hits"); //NON-NLS + dropHashSetsTableStmt = prepareStatement("DROP TABLE IF EXISTS hash_sets"); //NON-NLS + dropTagsTableStmt = prepareStatement("DROP TABLE IF EXISTS tags"); //NON-NLS + dropDBInfoTableStmt = prepareStatement("DROP TABLE IF EXISTS db_ino"); //NON-NLS + selectNonArtifactEventIDsByObjectIDStmt = prepareStatement("SELECT event_id FROM events WHERE file_id == ? AND artifact_id IS NULL"); //NON-NLS + selectEventIDsBYObjectAndArtifactIDStmt = prepareStatement("SELECT event_id FROM events WHERE file_id == ? AND artifact_id = ?"); //NON-NLS + } catch (SQLException sQLException) { + LOGGER.log(Level.SEVERE, "failed to prepareStatment", sQLException); // NON-NLS + } + } catch (SQLException ex) { + Logger.getLogger(EventDB.class.getName()).log(Level.SEVERE, null, ex); + } finally { + DBLock.unlock(); + } + } + + /** + * Get a List of event IDs for the events that are derived from the given + * artifact. + * + * @param artifact The BlackboardArtifact to get derived event IDs for. + * + * @return A List of event IDs for the events that are derived from the + * given artifact. + */ + public List<Long> getEventIDsForArtifact(BlackboardArtifact artifact) { + DBLock.lock(); + + String query = "SELECT event_id FROM events WHERE artifact_id == " + artifact.getArtifactID(); + + ArrayList<Long> results = new ArrayList<Long>(); + Statement stmt = null; + ResultSet rs = null; + try { + stmt = con.createStatement(); + rs = stmt.executeQuery(query); + while (rs.next()) { + results.add(rs.getLong("event_id")); + } + } catch (SQLException ex) { + LOGGER.log(Level.SEVERE, "Error executing getEventIDsForArtifact query.", ex); // NON-NLS + } finally { + closeResultSet(rs); + closeStatement(stmt); + DBLock.unlock(); + } + return results; + } + + /** + * Get a List of event IDs for the events that are derived from the given + * file. + * + * @param file The AbstractFile to get derived event IDs + * for. + * @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). + * + * @return A List of event IDs for the events that are derived from the + * given file. + */ + public List<Long> getEventIDsForFile(AbstractFile file, boolean includeDerivedArtifacts) { + DBLock.lock(); + + String query = "SELECT event_id FROM events WHERE file_id == " + file.getId() + + (includeDerivedArtifacts ? "" : " AND artifact_id IS NULL"); + + ArrayList<Long> results = new ArrayList<Long>(); + + Statement stmt = null; + ResultSet rs = null; + try { + + stmt = con.createStatement(); + rs = stmt.executeQuery(query); + while (rs.next()) { + results.add(rs.getLong("event_id")); + } + } catch (SQLException ex) { + LOGGER.log(Level.SEVERE, "Error executing getEventIDsForFile query.", ex); // NON-NLS + } finally { + closeResultSet(rs); + closeStatement(stmt); + DBLock.unlock(); + } + return results; + } + + /** + * create the tags table if it doesn't already exist. This is broken out as + * a separate method so it can be used by reInitializeTags() + */ + private void initializeTagsTable() { + String sql = "CREATE TABLE IF NOT EXISTS tags " //NON-NLS + + "(tag_id INTEGER NOT NULL," //NON-NLS + + " tag_name_id INTEGER NOT NULL, " //NON-NLS + + " tag_name_display_name TEXT NOT NULL, " //NON-NLS + + " event_id INTEGER REFERENCES events(event_id) NOT NULL, " //NON-NLS + + " PRIMARY KEY (event_id, tag_name_id))"; //NON-NLS + Statement stmt = null; + try { + stmt = con.createStatement(); + stmt.execute(sql); + } catch (SQLException ex) { + LOGGER.log(Level.SEVERE, "problem creating tags table", ex); //NON-NLS + } finally { + closeStatement(stmt); + } + } + + /** + * + * @param tableName the value of tableName + * @param columnList the value of columnList + */ + private void createIndex(final String tableName, final List<String> columnList) { + String indexColumns = columnList.stream().collect(Collectors.joining(",", "(", ")")); + String indexName = tableName + "_" + StringUtils.joinAsStrings(columnList, "_") + "_idx"; //NON-NLS + Statement stmt = null; + try { + + stmt = con.createStatement(); + String sql = "CREATE INDEX IF NOT EXISTS " + indexName + " ON " + tableName + indexColumns; // NON-NLS + stmt.execute(sql); + } catch (SQLException ex) { + LOGGER.log(Level.SEVERE, "problem creating index " + indexName, ex); // NON-NLS + } finally { + closeStatement(stmt); + } + } + + /** + * @param dbColumn the value of dbColumn + * + * @return the boolean + */ + private boolean hasDBColumn(final String dbColumn) { + Statement stmt = null; + try { + stmt = con.createStatement(); + + ResultSet executeQuery = stmt.executeQuery("PRAGMA table_info(events)"); //NON-NLS + while (executeQuery.next()) { + if (dbColumn.equals(executeQuery.getString("name"))) { + return true; + } + } + } catch (SQLException ex) { + LOGGER.log(Level.SEVERE, "problem executing pragma", ex); // NON-NLS + } + return false; + } + + private boolean hasDataSourceIDColumn() { + return hasDBColumn("datasource_id"); //NON-NLS + } + + private boolean hasTaggedColumn() { + return hasDBColumn("tagged"); //NON-NLS + } + + private boolean hasHashHitColumn() { + return hasDBColumn("hash_hit"); //NON-NLS + } + + void insertEvent(long time, EventType type, long datasourceID, long objID, + Long artifactID, String fullDescription, String medDescription, + String shortDescription, TskData.FileKnown known, Set<String> hashSets, List<? extends Tag> tags) { + + EventTransaction transaction = beginTransaction(); + insertEvent(time, type, datasourceID, objID, artifactID, fullDescription, medDescription, shortDescription, known, hashSets, tags, transaction); + commitTransaction(transaction); + } + + /** + * use transactions to update files + * + * @param f + * @param transaction + */ + public void insertEvent(long time, EventType type, long datasourceID, long objID, + Long artifactID, String fullDescription, String medDescription, + String shortDescription, TskData.FileKnown known, Set<String> hashSetNames, + List<? extends Tag> tags, EventTransaction transaction) { + + if (transaction.isClosed()) { + throw new IllegalArgumentException("can't update database with closed transaction"); // NON-NLS + } + int typeNum = RootEventType.allTypes.indexOf(type); + int superTypeNum = type.getSuperType().ordinal(); + + DBLock.lock(); + try { + + //"INSERT INTO events (datasource_id,file_id ,artifact_id, time, sub_type, base_type, full_description, med_description, short_description, known_state, hashHit, tagged) " + insertRowStmt.clearParameters(); + insertRowStmt.setLong(1, datasourceID); + insertRowStmt.setLong(2, objID); + if (artifactID != null) { + insertRowStmt.setLong(3, artifactID); + } else { + insertRowStmt.setNull(3, Types.NULL); + } + insertRowStmt.setLong(4, time); + + if (typeNum != -1) { + insertRowStmt.setInt(5, typeNum); + } else { + insertRowStmt.setNull(5, Types.INTEGER); + } + + insertRowStmt.setInt(6, superTypeNum); + insertRowStmt.setString(7, fullDescription); + insertRowStmt.setString(8, medDescription); + insertRowStmt.setString(9, shortDescription); + + insertRowStmt.setByte(10, known == null ? TskData.FileKnown.UNKNOWN.getFileKnownValue() : known.getFileKnownValue()); + + insertRowStmt.setInt(11, hashSetNames.isEmpty() ? 0 : 1); + insertRowStmt.setInt(12, tags.isEmpty() ? 0 : 1); + + insertRowStmt.executeUpdate(); + + ResultSet generatedKeys = null; + try { + generatedKeys = insertRowStmt.getGeneratedKeys(); + + while (generatedKeys.next()) { + long eventID = generatedKeys.getLong("last_insert_rowid()"); //NON-NLS + for (String name : hashSetNames) { + + // "insert or ignore into hash_sets (hash_set_name) values (?)" + insertHashSetStmt.setString(1, name); + insertHashSetStmt.executeUpdate(); + + //TODO: use nested select to get hash_set_id rather than seperate statement/query ? + //"select hash_set_id from hash_sets where hash_set_name = ?" + selectHashSetStmt.setString(1, name); + ResultSet rs = null; + try { + rs = selectHashSetStmt.executeQuery(); + while (rs.next()) { + int hashsetID = rs.getInt("hash_set_id"); //NON-NLS + //"insert or ignore into hash_set_hits (hash_set_id, obj_id) values (?,?)"; + insertHashHitStmt.setInt(1, hashsetID); + insertHashHitStmt.setLong(2, eventID); + insertHashHitStmt.executeUpdate(); + break; + } + } finally { + closeResultSet(rs); + } + } + for (Tag tag : tags) { + //could this be one insert? is there a performance win? + insertTag(tag, eventID); + } + break; + } + } finally { + closeResultSet(generatedKeys); + } + + } catch (SQLException ex) { + LOGGER.log(Level.SEVERE, "failed to insert event", ex); // NON-NLS + } finally { + DBLock.unlock(); + } + } + + /** + * mark any events with the given object and artifact ids as tagged, and + * record the tag it self. + * + * @param objectID 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 tag the tag that should be inserted + * + * @return the event ids that match the object/artifact pair + */ + public Set<Long> addTag(long objectID, Long artifactID, Tag tag, EventTransaction transaction) { + if (transaction != null && transaction.isClosed()) { + throw new IllegalArgumentException("can't update database with closed transaction"); // NON-NLS + } + DBLock.lock(); + try { + Set<Long> eventIDs = markEventsTagged(objectID, artifactID, true); + for (Long eventID : eventIDs) { + insertTag(tag, eventID); + } + return eventIDs; + } catch (SQLException ex) { + LOGGER.log(Level.SEVERE, "failed to add tag to event", ex); // NON-NLS + } finally { + DBLock.unlock(); + } + return Collections.emptySet(); + } + + /** + * insert this tag into the db + * <p> + * NOTE: does not lock the db, must be called form inside a + * DBLock.lock/unlock pair + * + * @param tag the tag to insert + * @param eventID the event id that this tag is applied to. + * + * @throws SQLException if there was a problem executing insert + */ + private void insertTag(Tag tag, long eventID) throws SQLException { + + //"INSERT OR IGNORE INTO tags (tag_id, tag_name_id,tag_name_display_name, event_id) values (?,?,?,?)" + insertTagStmt.clearParameters(); + insertTagStmt.setLong(1, tag.getId()); + insertTagStmt.setLong(2, tag.getName().getId()); + insertTagStmt.setString(3, tag.getName().getDisplayName()); + insertTagStmt.setLong(4, eventID); + insertTagStmt.executeUpdate(); + } + + /** + * mark any events with the given object and artifact ids as tagged, and + * record the tag it self. + * + * @param objectID 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 tag the tag that should be deleted + * @param stillTagged true if there are other tags still applied to this + * event in autopsy + * + * @return the event ids that match the object/artifact pair + */ + public Set<Long> deleteTag(long objectID, Long artifactID, long tagID, boolean stillTagged) { + DBLock.lock(); + try { + //"DELETE FROM tags WHERE tag_id = ? + deleteTagStmt.clearParameters(); + deleteTagStmt.setLong(1, tagID); + deleteTagStmt.executeUpdate(); + + return markEventsTagged(objectID, artifactID, stillTagged); + } catch (SQLException ex) { + LOGGER.log(Level.SEVERE, "failed to add tag to event", ex); // NON-NLS + } finally { + DBLock.unlock(); + } + return Collections.emptySet(); + } + + /** + * mark any events with the given object and artifact ids as tagged, and + * record the tag it self. + * <p> + * NOTE: does not lock the db, must be called form inside a + * DBLock.lock/unlock pair + * + * @param objectID 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 + * + * @return the event ids that match the object/artifact pair + * + * @throws SQLException if there is an error marking the events as + * (un)taggedS + */ + private Set<Long> markEventsTagged(long objectID, Long artifactID, boolean tagged) throws SQLException { + + PreparedStatement selectStmt; + if (Objects.isNull(artifactID)) { + //"SELECT event_id FROM events WHERE file_id == ? AND artifact_id IS NULL" + selectNonArtifactEventIDsByObjectIDStmt.clearParameters(); + selectNonArtifactEventIDsByObjectIDStmt.setLong(1, objectID); + selectStmt = selectNonArtifactEventIDsByObjectIDStmt; + } else { + //"SELECT event_id FROM events WHERE file_id == ? AND artifact_id = ?" + selectEventIDsBYObjectAndArtifactIDStmt.clearParameters(); + selectEventIDsBYObjectAndArtifactIDStmt.setLong(1, objectID); + selectEventIDsBYObjectAndArtifactIDStmt.setLong(2, artifactID); + selectStmt = selectEventIDsBYObjectAndArtifactIDStmt; + } + + HashSet<Long> eventIDs = new HashSet<>(); + try (ResultSet executeQuery = selectStmt.executeQuery();) { + while (executeQuery.next()) { + eventIDs.add(executeQuery.getLong("event_id")); //NON-NLS + } + } + + //update tagged state for all event with selected ids + try (Statement updateStatement = con.createStatement();) { + updateStatement.executeUpdate("UPDATE events SET tagged = " + (tagged ? 1 : 0) //NON-NLS + + " WHERE event_id IN (" + StringUtils.joinAsStrings(eventIDs, ",") + ")"); //NON-NLS + } + + return eventIDs; + } + + void rollBackTransaction(EventTransaction trans) { + trans.rollback(); + } + + private void closeStatements() throws SQLException { + for (PreparedStatement pStmt : preparedStatements) { + pStmt.close(); + } + } + + private void configureDB() throws SQLException { + DBLock.lock(); + //this should match Sleuthkit db setup + try (Statement statement = con.createStatement()) { + //reduce i/o operations, we have no OS crash recovery anyway + statement.execute("PRAGMA synchronous = OFF;"); // NON-NLS + //we don't use this feature, so turn it off for minimal speed up on queries + //this is deprecated and not recomended + statement.execute("PRAGMA count_changes = OFF;"); // NON-NLS + //this made a big difference to query speed + statement.execute("PRAGMA temp_store = MEMORY"); // NON-NLS + //this made a modest improvement in query speeds + statement.execute("PRAGMA cache_size = 50000"); // NON-NLS + //we never delete anything so... + statement.execute("PRAGMA auto_vacuum = 0"); // NON-NLS + //allow to query while in transaction - no need read locks + statement.execute("PRAGMA read_uncommitted = True;"); // NON-NLS + } finally { + DBLock.unlock(); + } + + try { + LOGGER.log(Level.INFO, String.format("sqlite-jdbc version %s loaded in %s mode", // NON-NLS + SQLiteJDBCLoader.getVersion(), SQLiteJDBCLoader.isNativeMode() ? "native" : "pure-java")); // NON-NLS + } catch (Exception exception) { + LOGGER.log(Level.SEVERE, "Failed to determine if sqlite-jdbc is loaded in native or pure-java mode.", exception); //NON-NLS + } + } + + private SingleEvent constructTimeLineEvent(ResultSet rs) throws SQLException { + return new SingleEvent(rs.getLong("event_id"), //NON-NLS + rs.getLong("datasource_id"), //NON-NLS + rs.getLong("file_id"), //NON-NLS + rs.getLong("artifact_id"), //NON-NLS + rs.getLong("time"), RootEventType.allTypes.get(rs.getInt("sub_type")), //NON-NLS + rs.getString("full_description"), //NON-NLS + rs.getString("med_description"), //NON-NLS + rs.getString("short_description"), //NON-NLS + TskData.FileKnown.valueOf(rs.getByte("known_state")), //NON-NLS + rs.getInt("hash_hit") != 0, //NON-NLS + rs.getInt("tagged") != 0); //NON-NLS + } + + /** + * 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 + */ + private Map<EventType, Long> countEventsByType(Long startTime, Long endTime, RootFilter filter, EventTypeZoomLevel zoomLevel) { + if (Objects.equals(startTime, endTime)) { + endTime++; + } + + Map<EventType, Long> typeMap = new HashMap<>(); + + //do we want the root or subtype column of the databse + final boolean useSubTypes = (zoomLevel == EventTypeZoomLevel.SUB_TYPE); + + //get some info about the range of dates requested + final String queryString = "SELECT count(DISTINCT events.event_id) AS count, " + typeColumnHelper(useSubTypes) //NON-NLS + + " FROM events" + useHashHitTablesHelper(filter) + useTagTablesHelper(filter) + " WHERE time >= " + startTime + " AND time < " + endTime + " AND " + getSQLWhere(filter) // NON-NLS + + " GROUP BY " + typeColumnHelper(useSubTypes); // NON-NLS + + DBLock.lock(); + try (Statement stmt = con.createStatement(); + ResultSet rs = stmt.executeQuery(queryString);) { + while (rs.next()) { + EventType type = useSubTypes + ? RootEventType.allTypes.get(rs.getInt("sub_type")) //NON-NLS + : BaseTypes.values()[rs.getInt("base_type")]; //NON-NLS + + typeMap.put(type, rs.getLong("count")); // NON-NLS + } + + } catch (Exception ex) { + LOGGER.log(Level.SEVERE, "Error getting count of events from db.", ex); // NON-NLS + } finally { + DBLock.unlock(); + } + return typeMap; + } + + /** + * get a list of {@link EventStripe}s, clustered according to the given zoom + * paramaters. + * + * @param params the {@link ZoomParams} that determine the zooming, + * filtering and clustering. + * + * @return a list of aggregate events within the given timerange, that pass + * the supplied filter, aggregated according to the given event type + * and description zoom levels + */ + public List<EventStripe> getEventStripes(ZoomParams params, DateTimeZone tz) { + //unpack params + Interval timeRange = params.getTimeRange(); + RootFilter filter = params.getFilter(); + DescriptionLoD descriptionLOD = params.getDescriptionLOD(); + EventTypeZoomLevel typeZoomLevel = params.getTypeZoomLevel(); + + long start = timeRange.getStartMillis() / 1000; + long end = timeRange.getEndMillis() / 1000; + + //ensure length of querried interval is not 0 + end = Math.max(end, start + 1); + + //get some info about the time range requested + RangeDivisionInfo rangeInfo = RangeDivisionInfo.getRangeDivisionInfo(timeRange, tz); + + //build dynamic parts of query + String strfTimeFormat = getStrfTimeFormat(rangeInfo.getPeriodSize()); + String descriptionColumn = getDescriptionColumn(descriptionLOD); + final boolean useSubTypes = typeZoomLevel.equals(EventTypeZoomLevel.SUB_TYPE); + String timeZone = tz.equals(DateTimeZone.getDefault()) ? ", 'localtime'" : ""; // NON-NLS + String typeColumn = typeColumnHelper(useSubTypes); + + //compose query string, the new-lines are only for nicer formatting if printing the entire query + String query = "SELECT strftime('" + strfTimeFormat + "',time , 'unixepoch'" + timeZone + ") AS interval," // NON-NLS + + "\n group_concat(events.event_id) as event_ids," //NON-NLS + + "\n group_concat(CASE WHEN hash_hit = 1 THEN events.event_id ELSE NULL END) as hash_hits," //NON-NLS + + "\n group_concat(CASE WHEN tagged = 1 THEN events.event_id ELSE NULL END) as taggeds," //NON-NLS + + "\n min(time), max(time), " + typeColumn + ", " + descriptionColumn // NON-NLS + + "\n FROM events" + useHashHitTablesHelper(filter) + useTagTablesHelper(filter) // NON-NLS + + "\n WHERE time >= " + start + " AND time < " + end + " AND " + getSQLWhere(filter) // NON-NLS + + "\n GROUP BY interval, " + typeColumn + " , " + descriptionColumn // NON-NLS + + "\n ORDER BY min(time)"; // NON-NLS + + // perform query and map results to AggregateEvent objects + List<EventCluster> events = new ArrayList<>(); + + DBLock.lock(); + try (Statement createStatement = con.createStatement(); + ResultSet rs = createStatement.executeQuery(query)) { + while (rs.next()) { + events.add(eventClusterHelper(rs, useSubTypes, descriptionLOD, filter.getTagsFilter(), tz)); + } + } catch (SQLException ex) { + LOGGER.log(Level.SEVERE, "Failed to get events with query: " + query, ex); // NON-NLS + } finally { + DBLock.unlock(); + } + + return mergeClustersToStripes(rangeInfo.getPeriodSize().getPeriod(), events); + } + + /** + * map a single row in a ResultSet to an EventCluster + * + * @param rs the result set whose current row should be mapped + * @param useSubTypes use the sub_type column if true, else use the + * base_type column + * @param descriptionLOD the description level of detail for this event + * @param filter + * + * @return an AggregateEvent corresponding to the current row in the given + * result set + * + * @throws SQLException + */ + private EventCluster eventClusterHelper(ResultSet rs, boolean useSubTypes, DescriptionLoD descriptionLOD, TagsFilter filter, DateTimeZone tz) throws SQLException { + Interval interval = new Interval(rs.getLong("min(time)") * 1000, rs.getLong("max(time)") * 1000, tz);// NON-NLS + String eventIDsString = rs.getString("event_ids");// NON-NLS + List<Long> eventIDs = unGroupConcat(eventIDsString, Long::valueOf); + String description = rs.getString(getDescriptionColumn(descriptionLOD)); + EventType type = useSubTypes ? RootEventType.allTypes.get(rs.getInt("sub_type")) : BaseTypes.values()[rs.getInt("base_type")];// NON-NLS + + List<Long> hashHits = unGroupConcat(rs.getString("hash_hits"), Long::valueOf); //NON-NLS + List<Long> tagged = unGroupConcat(rs.getString("taggeds"), Long::valueOf); //NON-NLS + + return new EventCluster(interval, type, eventIDs, hashHits, tagged, description, descriptionLOD); + } + + /** + * merge the events in the given list if they are within the same period + * General algorithm is as follows: + * + * 1) sort them into a map from (type, description)-> List<aggevent> + * 2) for each key in map, merge the events and accumulate them in a list to + * return + * + * @param timeUnitLength + * @param preMergedEvents + * + * @return + */ + static private List<EventStripe> mergeClustersToStripes(Period timeUnitLength, List<EventCluster> preMergedEvents) { + + //effectively map from type to (map from description to events) + Map<EventType, SetMultimap< String, EventCluster>> typeMap = new HashMap<>(); + + for (EventCluster aggregateEvent : preMergedEvents) { + typeMap.computeIfAbsent(aggregateEvent.getEventType(), eventType -> HashMultimap.create()) + .put(aggregateEvent.getDescription(), aggregateEvent); + } + //result list to return + ArrayList<EventCluster> aggEvents = new ArrayList<>(); + + //For each (type, description) key, merge agg events + for (SetMultimap<String, EventCluster> descrMap : typeMap.values()) { + //for each description ... + for (String descr : descrMap.keySet()) { + //run through the sorted events, merging together adjacent events + Iterator<EventCluster> iterator = descrMap.get(descr).stream() + .sorted(Comparator.comparing(event -> event.getSpan().getStartMillis())) + .iterator(); + EventCluster current = iterator.next(); + while (iterator.hasNext()) { + EventCluster next = iterator.next(); + Interval gap = current.getSpan().gap(next.getSpan()); + + //if they overlap or gap is less one quarter timeUnitLength + //TODO: 1/4 factor is arbitrary. review! -jm + if (gap == null || gap.toDuration().getMillis() <= timeUnitLength.toDurationFrom(gap.getStart()).getMillis() / 4) { + //merge them + current = EventCluster.merge(current, next); + } else { + //done merging into current, set next as new current + aggEvents.add(current); + current = next; + } + } + aggEvents.add(current); + } + } + + //merge clusters to stripes + Map<ImmutablePair<EventType, String>, EventStripe> stripeDescMap = new HashMap<>(); + + for (EventCluster eventCluster : aggEvents) { + stripeDescMap.merge(ImmutablePair.of(eventCluster.getEventType(), eventCluster.getDescription()), + new EventStripe(eventCluster), EventStripe::merge); + } + + return stripeDescMap.values().stream().sorted(Comparator.comparing(EventStripe::getStartMillis)).collect(Collectors.toList()); + } + + private static String typeColumnHelper(final boolean useSubTypes) { + return useSubTypes ? "sub_type" : "base_type"; //NON-NLS + } + + private PreparedStatement prepareStatement(String queryString) throws SQLException { + PreparedStatement prepareStatement = con.prepareStatement(queryString, 0); + preparedStatements.add(prepareStatement); + return prepareStatement; + } + + /** + * inner class that can reference access database connection + */ + public class EventTransaction { + + private boolean closed = false; + + /** + * factory creation method + * + * @return a LogicalFileTransaction for the given connection + * + * @throws SQLException + */ + private EventTransaction() { + + //get the write lock, released in close() + DBLock.lock(); + try { + con.beginTransaction(); + } catch (SQLException ex) { + LOGGER.log(Level.SEVERE, "failed to set auto-commit to to false", ex); // NON-NLS + } + + } + + private void rollback() { + if (!closed) { + try { + con.rollbackTransactionWithThrow(); + + } catch (SQLException ex1) { + LOGGER.log(Level.SEVERE, "Exception while attempting to rollback!!", ex1); // NON-NLS + } finally { + close(); + } + } + } + + private void commit() { + if (!closed) { + try { + con.commitTransaction(); + // make sure we close before we update, bc they'll need locks + close(); + + } catch (SQLException ex) { + + LOGGER.log(Level.SEVERE, "Error commiting events.db.", ex); // NON-NLS + rollback(); + } + } + } + + private void close() { + if (!closed) { + closed = true;// con.setAutoCommit(true); + // NON-NLS + DBLock.unlock(); + } + } + + public Boolean isClosed() { + return closed; + } + } + + /** + * Static helper methods for converting between java "data model" objects + * and sqlite queries. + */ + static String useHashHitTablesHelper(RootFilter filter) { + HashHitsFilter hashHitFilter = filter.getHashHitsFilter(); + return hashHitFilter.isActive() ? " LEFT JOIN hash_set_hits " : " "; //NON-NLS + } + + static String useTagTablesHelper(RootFilter filter) { + TagsFilter tagsFilter = filter.getTagsFilter(); + return tagsFilter.isActive() ? " LEFT JOIN tags " : " "; //NON-NLS + } + + /** + * take the result of a group_concat SQLite operation and split it into a + * set of X using the mapper to to convert from string to X + * + * @param <X> the type of elements to return + * @param groupConcat a string containing the group_concat result ( a comma + * separated list) + * @param mapper a function from String to X + * + * @return a Set of X, each element mapped from one element of the original + * comma delimited string + */ + static <X> List<X> unGroupConcat(String groupConcat, Function<String, X> mapper) { + List<X> result = new ArrayList<X>(); + String[] split = groupConcat.split(","); + for (String s : split) { + result.add(mapper.apply(s)); + } + return result; + } + + /** + * get the SQL where clause corresponding to an intersection filter ie + * (sub-clause1 and sub-clause2 and ... and sub-clauseN) + * + * @param filter the filter get the where clause for + * + * @return an SQL where clause (without the "where") corresponding to the + * filter + */ + private static String getSQLWhere(IntersectionFilter<?> filter) { + String join = String.join(" and ", filter.getSubFilters().stream() + .filter(Filter::isActive) + .map(EventDB::getSQLWhere) + .collect(Collectors.toList())); + return "(" + org.apache.commons.lang3.StringUtils.defaultIfBlank(join, "1") + ")"; + } + + /** + * get the SQL where clause corresponding to a union filter ie (sub-clause1 + * or sub-clause2 or ... or sub-clauseN) + * + * @param filter the filter get the where clause for + * + * @return an SQL where clause (without the "where") corresponding to the + * filter + */ + private static String getSQLWhere(UnionFilter<?> filter) { + String join = String.join(" or ", filter.getSubFilters().stream() + .filter(Filter::isActive) + .map(EventDB::getSQLWhere) + .collect(Collectors.toList())); + return "(" + org.apache.commons.lang3.StringUtils.defaultIfBlank(join, "1") + ")"; + } + + public static String getSQLWhere(RootFilter filter) { + return getSQLWhere((Filter) filter); + } + + /** + * get the SQL where clause corresponding to the given filter + * + * uses instance of to dispatch to the correct method for each filter type. + * NOTE: I don't like this if-else instance of chain, but I can't decide + * what to do instead -jm + * + * @param filter a filter to generate the SQL where clause for + * + * @return an SQL where clause (without the "where") corresponding to the + * filter + */ + private static String getSQLWhere(Filter filter) { + String result = ""; + if (filter == null) { + return "1"; + } else if (filter instanceof DescriptionFilter) { + result = getSQLWhere((DescriptionFilter) filter); + } else if (filter instanceof TagsFilter) { + result = getSQLWhere((TagsFilter) filter); + } else if (filter instanceof HashHitsFilter) { + result = getSQLWhere((HashHitsFilter) filter); + } else if (filter instanceof DataSourceFilter) { + result = getSQLWhere((DataSourceFilter) filter); + } else if (filter instanceof DataSourcesFilter) { + result = getSQLWhere((DataSourcesFilter) filter); + } else if (filter instanceof HideKnownFilter) { + result = getSQLWhere((HideKnownFilter) filter); + } else if (filter instanceof HashHitsFilter) { + result = getSQLWhere((HashHitsFilter) filter); + } else if (filter instanceof TextFilter) { + result = getSQLWhere((TextFilter) filter); + } else if (filter instanceof TypeFilter) { + result = getSQLWhere((TypeFilter) filter); + } else if (filter instanceof IntersectionFilter) { + result = getSQLWhere((IntersectionFilter) filter); + } else if (filter instanceof UnionFilter) { + result = getSQLWhere((UnionFilter) filter); + } else { + throw new IllegalArgumentException("getSQLWhere not defined for " + filter.getClass().getCanonicalName()); + } + result = org.apache.commons.lang3.StringUtils.deleteWhitespace(result).equals("(1and1and1)") ? "1" : result; //NON-NLS + result = org.apache.commons.lang3.StringUtils.deleteWhitespace(result).equals("()") ? "1" : result; + return result; + } + + private static String getSQLWhere(HideKnownFilter filter) { + if (filter.isActive()) { + return "(known_state IS NOT '" + TskData.FileKnown.KNOWN.getFileKnownValue() + "')"; // NON-NLS + } else { + return "1"; + } + } + + private static String getSQLWhere(DescriptionFilter filter) { + if (filter.isActive()) { + String likeOrNotLike = (filter.getFilterMode() == DescriptionFilter.FilterMode.INCLUDE ? "" : " NOT") + " LIKE '"; //NON-NLS + return "(" + getDescriptionColumn(filter.getDescriptionLoD()) + likeOrNotLike + filter.getDescription() + "' )"; // NON-NLS + } else { + return "1"; + } + } + + private static String getSQLWhere(TagsFilter filter) { + if (filter.isActive() + && (filter.getSubFilters().isEmpty() == false)) { + String tagNameIDs = filter.getSubFilters().stream() + .filter((TagNameFilter t) -> t.isSelected() && !t.isDisabled()) + .map((TagNameFilter t) -> String.valueOf(t.getTagName().getId())) + .collect(Collectors.joining(", ", "(", ")")); + return "(events.event_id == tags.event_id AND " //NON-NLS + + "tags.tag_name_id IN " + tagNameIDs + ") "; //NON-NLS + } else { + return "1"; + } + + } + + private static String getSQLWhere(HashHitsFilter filter) { + if (filter.isActive() + && (filter.getSubFilters().isEmpty() == false)) { + String hashSetIDs = filter.getSubFilters().stream() + .filter((HashSetFilter t) -> t.isSelected() && !t.isDisabled()) + .map((HashSetFilter t) -> String.valueOf(t.getHashSetID())) + .collect(Collectors.joining(", ", "(", ")")); + return "(hash_set_hits.hash_set_id IN " + hashSetIDs + " AND hash_set_hits.event_id == events.event_id)"; //NON-NLS + } else { + return "1"; + } + } + + private static String getSQLWhere(DataSourceFilter filter) { + if (filter.isActive()) { + return "(datasource_id = '" + filter.getDataSourceID() + "')"; //NON-NLS + } else { + return "1"; + } + } + + private static String getSQLWhere(DataSourcesFilter filter) { + return (filter.isActive()) ? "(datasource_id in (" //NON-NLS + + filter.getSubFilters().stream() + .filter(AbstractFilter::isActive) + .map((dataSourceFilter) -> String.valueOf(dataSourceFilter.getDataSourceID())) + .collect(Collectors.joining(", ")) + "))" : "1"; + } + + private static String getSQLWhere(TextFilter filter) { + if (filter.isActive()) { + if (org.apache.commons.lang3.StringUtils.isBlank(filter.getText())) { + return "1"; + } + String strippedFilterText = org.apache.commons.lang3.StringUtils.strip(filter.getText()); + return "((med_description like '%" + strippedFilterText + "%')" //NON-NLS + + " or (full_description like '%" + strippedFilterText + "%')" //NON-NLS + + " or (short_description like '%" + strippedFilterText + "%'))"; //NON-NLS + } else { + return "1"; + } + } + + /** + * generate a sql where clause for the given type filter, while trying to be + * as simple as possible to improve performance. + * + * @param typeFilter + * + * @return + */ + private static String getSQLWhere(TypeFilter typeFilter) { + if (typeFilter.isSelected() == false) { + return "0"; + } else if (typeFilter.getEventType() instanceof RootEventType) { + if (typeFilter.getSubFilters().stream() + .allMatch(subFilter -> subFilter.isActive() && subFilter.getSubFilters().stream().allMatch(Filter::isActive))) { + return "1"; //then collapse clause to true + } + } + return "(sub_type IN (" + org.apache.commons.lang3.StringUtils.join(getActiveSubTypes(typeFilter), ",") + "))"; //NON-NLS + } + + private static List<Integer> getActiveSubTypes(TypeFilter filter) { + if (filter.isActive()) { + if (filter.getSubFilters().isEmpty()) { + return Collections.singletonList(RootEventType.allTypes.indexOf(filter.getEventType())); + } else { + return filter.getSubFilters().stream().flatMap((Filter t) -> getActiveSubTypes((TypeFilter) t).stream()).collect(Collectors.toList()); + } + } else { + return Collections.emptyList(); + } + } + + /** + * get a sqlite strftime format string that will allow us to group by the + * requested period size. That is, with all info more granular than that + * requested dropped (replaced with zeros). + * + * @param timeUnit the {@link TimeUnits} instance describing what + * granularity to build a strftime string for + * + * @return a String formatted according to the sqlite strftime spec + * + * @see https://www.sqlite.org/lang_datefunc.html + */ + static String getStrfTimeFormat(TimeUnits timeUnit) { + switch (timeUnit) { + case YEARS: + return "%Y-01-01T00:00:00"; // NON-NLS + case MONTHS: + return "%Y-%m-01T00:00:00"; // NON-NLS + case DAYS: + return "%Y-%m-%dT00:00:00"; // NON-NLS + case HOURS: + return "%Y-%m-%dT%H:00:00"; // NON-NLS + case MINUTES: + return "%Y-%m-%dT%H:%M:00"; // NON-NLS + case SECONDS: + default: //seconds - should never happen + return "%Y-%m-%dT%H:%M:%S"; // NON-NLS + } + } + + static String getDescriptionColumn(DescriptionLoD 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 + } + } + +} diff --git a/bindings/java/src/org/sleuthkit/datamodel/timeline/ArtifactEventType.java b/bindings/java/src/org/sleuthkit/datamodel/timeline/ArtifactEventType.java new file mode 100644 index 0000000000000000000000000000000000000000..e4df415ca0e493d5ed3534b28826afba93a1bbcf --- /dev/null +++ b/bindings/java/src/org/sleuthkit/datamodel/timeline/ArtifactEventType.java @@ -0,0 +1,209 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2014-16 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.timeline; + +import java.text.MessageFormat; +import java.util.Optional; +import java.util.function.Function; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.apache.commons.lang3.StringUtils; +import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.BlackboardAttribute; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * + */ +public interface ArtifactEventType extends EventType { + + static final Logger LOGGER = Logger.getLogger(ArtifactEventType.class.getName()); + + /** + * Get the artifact type this event type is derived from. + * + * @return The artifact type this event type is derived from. + */ + public BlackboardArtifact.Type getArtifactType(); + + /** + * The attribute type this event type is derived from. + * + * @return The attribute type this event type is derived from. + */ + public BlackboardAttribute.Type getDateTimeAttributeType(); + + /** + * Get the ID of the the artifact type that this EventType is derived from. + * + * @return the ID of the the artifact type that this EventType is derived + * from. + */ + public default int getArtifactTypeID() { + return getArtifactType().getTypeID(); + } + + /** + * given an artifact, pull out the time stamp, and compose the descriptions. + * Each implementation of ArtifactEventType needs to implement + * parseAttributesHelper() as hook for + * buildEventDescription(org.sleuthkit.datamodel.BlackboardArtifact) to + * invoke. Most subtypes can use this default implementation. + * + * @param artf + * + * @return an AttributeEventDescription containing the timestamp and + * description information + * + * @throws TskCoreException + */ + default AttributeEventDescription parseAttributesHelper(BlackboardArtifact artf) throws TskCoreException { + final BlackboardAttribute dateTimeAttr = artf.getAttribute(getDateTimeAttributeType()); + + long time = dateTimeAttr.getValueLong(); + String shortDescription = getShortExtractor().apply(artf); + String medDescription = shortDescription + " : " + getMedExtractor().apply(artf); + String fullDescription = medDescription + " : " + getFullExtractor().apply(artf); + return new AttributeEventDescription(time, shortDescription, medDescription, fullDescription); + } + + /** + * @return a function from an artifact to a String to use as part of the + * full event description + */ + Function<BlackboardArtifact, String> getFullExtractor(); + + /** + * @return a function from an artifact to a String to use as part of the + * medium event description + */ + Function<BlackboardArtifact, String> getMedExtractor(); + + /** + * @return a function from an artifact to a String to use as part of the + * short event description + */ + Function<BlackboardArtifact, String> getShortExtractor(); + + /** + * bundles the per event information derived from a BlackBoard Artifact into + * one object. Primarily used to have a single return value for + * ArtifactEventType#buildEventDescription(ArtifactEventType, + * BlackboardArtifact). + */ + static class AttributeEventDescription { + + final private long time; + + public long getTime() { + return time; + } + + public String getShortDescription() { + return shortDescription; + } + + public String getMedDescription() { + return medDescription; + } + + public String getFullDescription() { + return fullDescription; + } + + final private String shortDescription; + + final private String medDescription; + + final private String fullDescription; + + public AttributeEventDescription(long time, String shortDescription, + String medDescription, + String fullDescription) { + this.time = time; + this.shortDescription = shortDescription; + this.medDescription = medDescription; + this.fullDescription = fullDescription; + } + } + + /** + * Build a AttributeEventDescription derived from a BlackboardArtifact. This + * is a template method that relies on each ArtifactEventType's + * implementation of ArtifactEventType#parseAttributesHelper() to know how + * to go from BlackboardAttributes to the event description. + * + * @param type + * @param artf the BlackboardArtifact to derive the event description from + * + * @return an AttributeEventDescription derived from the given artifact, if + * the given artifact has no timestamp + * + * @throws TskCoreException is there is a problem accessing the blackboard + * data + */ + static public AttributeEventDescription buildEventDescription(ArtifactEventType type, BlackboardArtifact artf) throws TskCoreException { + //if we got passed an artifact that doesn't correspond to the type of the event, + //something went very wrong. throw an exception. + if (type.getArtifactTypeID() != artf.getArtifactTypeID()) { + throw new IllegalArgumentException(); + } + if (artf.getAttribute(type.getDateTimeAttributeType()) == null) { + LOGGER.log(Level.WARNING, "Artifact {0} has no date/time attribute, skipping it.", artf.getArtifactID()); // NON-NLS + return null; + } + //use the hook provided by this subtype implementation + return type.parseAttributesHelper(artf); + } + + static class AttributeExtractor implements Function<BlackboardArtifact, String> { + + public String apply(BlackboardArtifact artf) { + return Optional.ofNullable(getAttributeSafe(artf, attributeType)) + .map(BlackboardAttribute::getDisplayString) + .map(StringUtils::defaultString) + .orElse(""); + } + + private final BlackboardAttribute.Type attributeType; + + public AttributeExtractor(BlackboardAttribute.Type attribute) { + this.attributeType = attribute; + } + + } + + static class EmptyExtractor implements Function<BlackboardArtifact, String> { + + @Override + public String apply(BlackboardArtifact t) { + return ""; + } + } + + static BlackboardAttribute getAttributeSafe(BlackboardArtifact artf, BlackboardAttribute.Type attrType) { + try { + return artf.getAttribute(attrType); + } catch (TskCoreException ex) { + LOGGER.log(Level.SEVERE, MessageFormat.format("Error getting attribute from artifact {0}.", artf.getArtifactID()), ex); // NON-NLS + return null; + } + } + +} diff --git a/bindings/java/src/org/sleuthkit/datamodel/timeline/BaseTypes.java b/bindings/java/src/org/sleuthkit/datamodel/timeline/BaseTypes.java new file mode 100644 index 0000000000000000000000000000000000000000..085316fe5d831ff18179eb898d3225006520bbd6 --- /dev/null +++ b/bindings/java/src/org/sleuthkit/datamodel/timeline/BaseTypes.java @@ -0,0 +1,108 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2014 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.timeline; + +import java.util.Arrays; +import java.util.List; +import javafx.scene.image.Image; +import static org.sleuthkit.datamodel.timeline.BundleUtils.getBundle; + +/** + * RootTypes are event types that have no super type. + */ +public enum BaseTypes implements EventType { + FILE_SYSTEM(getBundle().getString("BaseTypes.fileSystem.name"), "blue-document.png") { // NON-NLS + + @Override + public List<? extends EventType> getSubTypes() { + return Arrays.asList(FileSystemTypes.values()); + } + + @Override + public EventType getSubType(String string) { + return FileSystemTypes.valueOf(string); + } + }, + WEB_ACTIVITY(getBundle().getString("BaseTypes.webActivity.name"), "web-file.png") { // NON-NLS + + @Override + public List<? extends EventType> getSubTypes() { + return Arrays.asList(WebTypes.values()); + } + + @Override + public EventType getSubType(String string) { + return WebTypes.valueOf(string); + } + }, + MISC_TYPES(getBundle().getString("BaseTypes.miscTypes.name"), "block.png") { // NON-NLS + + @Override + public List<? extends EventType> getSubTypes() { + return Arrays.asList(MiscTypes.values()); + } + + @Override + public EventType getSubType(String string) { + return MiscTypes.valueOf(string); + } + }; + + private final String displayName; + + private final String iconBase; + + private final Image image; + + @Override + public Image getFXImage() { + return image; + } + + @Override + public String getIconBase() { + return iconBase; + } + + @Override + public EventTypeZoomLevel getZoomLevel() { + return EventTypeZoomLevel.BASE_TYPE; + } + + @Override + public String getDisplayName() { + return displayName; + } + + private BaseTypes(String displayName, String iconBase) { + this.displayName = displayName; + this.iconBase = iconBase; + this.image = new Image("org/sleuthkit/autopsy/timeline/images/" + iconBase, true); // NON-NLS + } + + @Override + public EventType getSuperType() { + return RootEventType.getInstance(); + } + + @Override + public EventType getSubType(String string) { + return BaseTypes.valueOf(string); + } +} diff --git a/bindings/java/src/org/sleuthkit/datamodel/timeline/Bundle.properties b/bindings/java/src/org/sleuthkit/datamodel/timeline/Bundle.properties new file mode 100644 index 0000000000000000000000000000000000000000..bc8eb42086511bf9ee1635ceb28827df64563b9a --- /dev/null +++ b/bindings/java/src/org/sleuthkit/datamodel/timeline/Bundle.properties @@ -0,0 +1,31 @@ + + +BaseTypes.fileSystem.name=File System +BaseTypes.webActivity.name=Web Activity +BaseTypes.miscTypes.name=Misc Types +FileSystemTypes.fileModified.name=File Modified +FileSystemTypes.fileAccessed.name=File Accessed +FileSystemTypes.fileCreated.name=File Created +FileSystemTypes.fileChanged.name=File Changed +MiscTypes.message.name=Messages +MiscTypes.GPSRoutes.name=GPS Routes +MiscTypes.GPSTrackpoint.name=Location History +MiscTypes.Calls.name=Calls +MiscTypes.Email.name=Email +MiscTypes.recentDocuments.name=Recent Documents +MiscTypes.installedPrograms.name=Installed Programs +MiscTypes.exif.name=Exif +MiscTypes.devicesAttached.name=Devices Attached +RootEventType.eventTypes.name=Event Types +WebTypes.webDownloads.name=Web Downloads +WebTypes.webCookies.name=Web Cookies +WebTypes.webBookmarks.name=Web Bookmarks +WebTypes.webHistory.name=Web History +WebTypes.webSearch.name=Web Searches +DescriptionLOD.short=Short +DescriptionLOD.medium=Medium +DescriptionLOD.full=Full + +EventTypeZoomLevel.rootType=Root Type +EventTypeZoomLevel.baseType=Base Type +EventTypeZoomLevel.subType=Sub Type \ No newline at end of file diff --git a/bindings/java/src/org/sleuthkit/datamodel/timeline/BundleUtils.java b/bindings/java/src/org/sleuthkit/datamodel/timeline/BundleUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..2b8a69b0bd13259c4221ab016966a823e4a5dd1c --- /dev/null +++ b/bindings/java/src/org/sleuthkit/datamodel/timeline/BundleUtils.java @@ -0,0 +1,13 @@ +package org.sleuthkit.datamodel.timeline; + + +import java.util.ResourceBundle; + +class BundleUtils { + + private static final ResourceBundle BUNDLE = ResourceBundle.getBundle("org.sleuthkit.datamodel.timeline.Bundle"); + + static ResourceBundle getBundle() { + return BUNDLE; + } +} diff --git a/bindings/java/src/org/sleuthkit/datamodel/timeline/Bundle_ja.properties b/bindings/java/src/org/sleuthkit/datamodel/timeline/Bundle_ja.properties new file mode 100644 index 0000000000000000000000000000000000000000..4424dd226be3c747fd624543f09e363fa825067c --- /dev/null +++ b/bindings/java/src/org/sleuthkit/datamodel/timeline/Bundle_ja.properties @@ -0,0 +1,34 @@ + + +BaseTypes.fileSystem.name=\u30d5\u30a1\u30a4\u30eb\u30b7\u30b9\u30c6\u30e0 +BaseTypes.miscTypes.name=\u305d\u306e\u4ed6\u30bf\u30a4\u30d7 +BaseTypes.webActivity.name=\u30a6\u30a7\u30d6\u30a2\u30af\u30c6\u30a3\u30d3\u30c6\u30a3 +FileSystemTypes.fileAccessed.name=\u30a2\u30af\u30bb\u30b9\u3055\u308c\u305f\u30d5\u30a1\u30a4\u30eb +FileSystemTypes.fileChanged.name=\u5909\u66f4\u3055\u308c\u305f\u30d5\u30a1\u30a4\u30eb +FileSystemTypes.fileCreated.name=\u4f5c\u6210\u3055\u308c\u305f\u30d5\u30a1\u30a4\u30eb +FileSystemTypes.fileModified.name=\u4fee\u6b63\u3055\u308c\u305f\u30d5\u30a1\u30a4\u30eb +MiscTypes.Calls.name=\u30b3\u30fc\u30eb +MiscTypes.devicesAttached.name=\u63a5\u7d9a\u3055\u308c\u3066\u3044\u308b\u6a5f\u5668 +MiscTypes.Email.name=Email +MiscTypes.exif.name=Exif +MiscTypes.GPSRoutes.name=GPS\u30eb\u30fc\u30c8 +MiscTypes.GPSTrackpoint.name=\u4f4d\u7f6e\u60c5\u5831\u5c65\u6b74 +MiscTypes.installedPrograms.name=\u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u3055\u308c\u3066\u3044\u308b\u30d7\u30ed\u30b0\u30e9\u30e0 +MiscTypes.message.name=\u30e1\u30c3\u30bb\u30fc\u30b8 +MiscTypes.recentDocuments.name=\u6700\u8fd1\u306e\u30c9\u30ad\u30e5\u30e1\u30f3\u30c8 +RootEventType.eventTypes.name=\u30a4\u30d9\u30f3\u30c8\u30bf\u30a4\u30d7 +WebTypes.webBookmarks.name=\u30a6\u30a7\u30d6\u30d6\u30c3\u30af\u30de\u30fc\u30af +WebTypes.webCookies.name=\u30a6\u30a7\u30d6\u30af\u30c3\u30ad\u30fc +WebTypes.webDownloads.name=\u30a6\u30a7\u30d6\u30c0\u30a6\u30f3\u30ed\u30fc\u30c9 +WebTypes.webHistory.name=\u30a6\u30a7\u30d6\u5c65\u6b74 +WebTypes.webSearch.name=\u30a6\u30a7\u30d6\u691c\u7d22 +EventTypeZoomLevel.baseType=\u30d9\u30fc\u30b9\u30bf\u30a4\u30d7 +EventTypeZoomLevel.rootType=\u30eb\u30fc\u30c8\u30bf\u30a4\u30d7 +EventTypeZoomLevel.subType=\u30b5\u30d6\u30bf\u30a4\u30d7 +DescriptionLOD.short=\u7c21\u6f54 +DescriptionLOD.medium=\u6982\u8981 +DescriptionLOD.full=\u8a73\u7d30 +ZoomSettingsPane.descrLODLabel.text=\u8a73\u7d30\u8aac\u660e\uff1a +ZoomSettingsPane.historyLabel.text=\u5c65\u6b74\uff1a +ZoomSettingsPane.timeUnitLabel.text=\u6642\u9593\u5358\u4f4d\uff1a +ZoomSettingsPane.typeZoomLabel.text=\u30a4\u30d9\u30f3\u30c8\u30bf\u30a4\u30d7\uff1a \ No newline at end of file diff --git a/bindings/java/src/org/sleuthkit/datamodel/timeline/CombinedEvent.java b/bindings/java/src/org/sleuthkit/datamodel/timeline/CombinedEvent.java new file mode 100644 index 0000000000000000000000000000000000000000..04042006c1b361e0676a6ed90097820ce0f34e60 --- /dev/null +++ b/bindings/java/src/org/sleuthkit/datamodel/timeline/CombinedEvent.java @@ -0,0 +1,152 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011-2016 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.timeline; + +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +/** + * A container for several events that have the same timestamp and description + * and are backed by the same file. Used in the ListView to coalesce the file + * system events for a file when they have the same timestamp. + */ +public class CombinedEvent { + + private final long fileID; + private final long epochMillis; + private final String description; + + /** + * A map from EventType to event ID. + */ + private final Map<EventType, Long> eventTypeMap = new HashMap<EventType, Long>(); + + /** + * Constructor + * + * @param epochMillis The timestamp for this event, in millis from the Unix + * epoch. + * @param description The full description shared by all the combined events + * @param fileID The ID of the file shared by all the combined events. + * @param eventMap A map from EventType to event ID. + */ + public CombinedEvent(long epochMillis, String description, long fileID, Map<EventType, Long> eventMap) { + this.epochMillis = epochMillis; + this.description = description; + eventTypeMap.putAll(eventMap); + this.fileID = fileID; + } + + /** + * Get the timestamp of this event as millis from the Unix epoch. + * + * @return The timestamp of this event as millis from the Unix epoch. + */ + public long getStartMillis() { + return epochMillis; + } + + /** + * Get the full description shared by all the combined events. + * + * @return The full description shared by all the combined events. + */ + public String getDescription() { + return description; + } + + /** + * Get the obj ID of the file shared by the combined events. + * + * @return The obj ID of the file shared by the combined events. + */ + public long getFileID() { + return fileID; + } + + /** + * Get the types of the combined events. + * + * @return The types of the combined events. + */ + public Set<EventType> getEventTypes() { + return eventTypeMap.keySet(); + } + + /** + * Get the event IDs of the combined events. + * + * @return The event IDs of the combined events. + */ + public Set<Long> getEventIDs() { + return Collections.unmodifiableSet(new HashSet<Long>(eventTypeMap.values())); + } + + /** + * Get the event ID of one event that is representative of all the combined + * events. It can be used to look up a SingleEvent with more details, for + * example. + * + * @return An arbitrary representative event ID for the combined events. + */ + public Long getRepresentativeEventID() { + return eventTypeMap.values().stream().findFirst().get(); + } + + @Override + public int hashCode() { + int hash = 3; + hash = 53 * hash + (int) (this.fileID ^ (this.fileID >>> 32)); + hash = 53 * hash + (int) (this.epochMillis ^ (this.epochMillis >>> 32)); + hash = 53 * hash + Objects.hashCode(this.description); + hash = 53 * hash + Objects.hashCode(this.eventTypeMap); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final CombinedEvent other = (CombinedEvent) obj; + if (this.fileID != other.fileID) { + return false; + } + if (this.epochMillis != other.epochMillis) { + return false; + } + if (!Objects.equals(this.description, other.description)) { + return false; + } + if (!Objects.equals(this.eventTypeMap, other.eventTypeMap)) { + return false; + } + return true; + } +} diff --git a/bindings/java/src/org/sleuthkit/datamodel/timeline/DescriptionLoD.java b/bindings/java/src/org/sleuthkit/datamodel/timeline/DescriptionLoD.java new file mode 100644 index 0000000000000000000000000000000000000000..f6171d517ac59febb9904fe3585b4f634d324a9c --- /dev/null +++ b/bindings/java/src/org/sleuthkit/datamodel/timeline/DescriptionLoD.java @@ -0,0 +1,87 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2013 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.timeline; + +/** + * Enumeration of all description levels of detail. + */ +public enum DescriptionLoD implements DisplayNameProvider { + + SHORT(BundleUtils.getBundle(). getString("DescriptionLOD.short")), + MEDIUM(BundleUtils.getBundle(). getString("DescriptionLOD.medium")), + FULL(BundleUtils.getBundle(). getString( "DescriptionLOD.full")); + + private final String displayName; + + @Override + public String getDisplayName() { + return displayName; + } + + private DescriptionLoD(String displayName) { + this.displayName = displayName; + } + + public DescriptionLoD moreDetailed() { + try { + return values()[ordinal() + 1]; + } catch (ArrayIndexOutOfBoundsException e) { + return null; + } + } + + public DescriptionLoD lessDetailed() { + try { + return values()[ordinal() - 1]; + } catch (ArrayIndexOutOfBoundsException e) { + return null; + } + } + + public DescriptionLoD withRelativeDetail(RelativeDetail relativeDetail) { + switch (relativeDetail) { + case EQUAL: + return this; + case MORE: + return moreDetailed(); + case LESS: + return lessDetailed(); + default: + throw new IllegalArgumentException("Unknown RelativeDetail value " + relativeDetail); + } + } + + public RelativeDetail getDetailLevelRelativeTo(DescriptionLoD other) { + int compareTo = this.compareTo(other); + if (compareTo < 0) { + return RelativeDetail.LESS; + } else if (compareTo == 0) { + return RelativeDetail.EQUAL; + } else { + return RelativeDetail.MORE; + } + } + + public enum RelativeDetail { + + EQUAL, + MORE, + LESS; + } +} diff --git a/bindings/java/src/org/sleuthkit/datamodel/timeline/DisplayNameProvider.java b/bindings/java/src/org/sleuthkit/datamodel/timeline/DisplayNameProvider.java new file mode 100644 index 0000000000000000000000000000000000000000..100efb03047dea71f496ec0a226159b91ed58941 --- /dev/null +++ b/bindings/java/src/org/sleuthkit/datamodel/timeline/DisplayNameProvider.java @@ -0,0 +1,32 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2016 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.timeline; + +/** + * An interface for objects with a display name + */ +public interface DisplayNameProvider { + + /** + * Get the display name of this object + * + * @return The display name. + */ + String getDisplayName(); +} diff --git a/bindings/java/src/org/sleuthkit/datamodel/timeline/EventCluster.java b/bindings/java/src/org/sleuthkit/datamodel/timeline/EventCluster.java new file mode 100644 index 0000000000000000000000000000000000000000..a2e337c14f89da8d828aababc22a4d90603e2370 --- /dev/null +++ b/bindings/java/src/org/sleuthkit/datamodel/timeline/EventCluster.java @@ -0,0 +1,252 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011-2016 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.timeline; + +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.ImmutableSortedSet; +import com.google.common.collect.Sets; +import java.util.Collection; +import java.util.Comparator; +import java.util.Objects; +import java.util.Optional; +import java.util.SortedSet; +import org.joda.time.Interval; +import org.sleuthkit.datamodel.timeline.DescriptionLoD; + +/** + * Represents a set of other events clustered together. All the sub events + * should have the same type and matching descriptions at the designated "zoom + * level", and be "close together" in time. + */ +public class EventCluster implements MultiEvent<EventStripe> { + + /** + * merge two event clusters into one new event cluster. + * + * @param cluster1 + * @param cluster2 + * + * @return a new event cluster that is the result of merging the given + * events clusters + */ + public static EventCluster merge(EventCluster cluster1, EventCluster cluster2) { + if (cluster1.getEventType() != cluster2.getEventType()) { + throw new IllegalArgumentException("event clusters are not compatible: they have different types"); + } + + if (!cluster1.getDescription().equals(cluster2.getDescription())) { + throw new IllegalArgumentException("event clusters are not compatible: they have different descriptions"); + } + Sets.SetView<Long> idsUnion = + Sets.union(cluster1.getEventIDs(), cluster2.getEventIDs()); + Sets.SetView<Long> hashHitsUnion = + Sets.union(cluster1.getEventIDsWithHashHits(), cluster2.getEventIDsWithHashHits()); + Sets.SetView<Long> taggedUnion = + Sets.union(cluster1.getEventIDsWithTags(), cluster2.getEventIDsWithTags()); + + return new EventCluster(IntervalUtils.span(cluster1.span, cluster2.span), + cluster1.getEventType(), idsUnion, hashHitsUnion, taggedUnion, + cluster1.getDescription(), cluster1.lod); + } + + final private EventStripe parent; + + /** + * the smallest time interval containing all the clustered events + */ + final private Interval span; + + /** + * the type of all the clustered events + */ + final private EventType type; + + /** + * the common description of all the clustered events + */ + final private String description; + + /** + * the description level of detail that the events were clustered at. + */ + private final DescriptionLoD lod; + + /** + * the set of ids of the clustered events + */ + final private ImmutableSet<Long> eventIDs; + + /** + * the ids of the subset of clustered events that have at least one tag + * applied to them + */ + private final ImmutableSet<Long> tagged; + + /** + * the ids of the subset of clustered events that have at least one hash set + * hit + */ + private final ImmutableSet<Long> hashHits; + + private EventCluster(Interval spanningInterval, EventType type, Collection<Long> eventIDs, + Collection<Long> hashHits, Collection<Long> tagged, String description, DescriptionLoD lod, + EventStripe parent) { + + this.span = spanningInterval; + this.type = type; + this.hashHits = ImmutableSet.copyOf(hashHits); + this.tagged = ImmutableSet.copyOf(tagged); + this.description = description; + this.eventIDs = ImmutableSet.copyOf(eventIDs); + this.lod = lod; + this.parent = parent; + } + + public EventCluster(Interval spanningInterval, EventType type, Collection<Long> eventIDs, + Collection<Long> hashHits, Collection<Long> tagged, String description, DescriptionLoD lod) { + this(spanningInterval, type, eventIDs, hashHits, tagged, description, lod, null); + } + + /** + * get the EventStripe (if any) that contains this cluster + * + * @return an Optional containg the parent stripe of this cluster, or is + * empty if the cluster has no parent set. + */ + @Override + public Optional<EventStripe> getParent() { + return Optional.ofNullable(parent); + } + + /** + * get the EventStripe (if any) that contains this cluster + * + * @return an Optional containg the parent stripe of this cluster, or is + * empty if the cluster has no parent set. + */ + @Override + public Optional<EventStripe> getParentStripe() { + //since this clusters parent must be an event stripe just delegate to getParent(); + return getParent(); + } + + public Interval getSpan() { + return span; + } + + @Override + public long getStartMillis() { + return span.getStartMillis(); + } + + @Override + public long getEndMillis() { + return span.getEndMillis(); + } + + @Override + public ImmutableSet<Long> getEventIDs() { + return eventIDs; + } + + @Override + public ImmutableSet<Long> getEventIDsWithHashHits() { + return hashHits; + } + + @Override + public ImmutableSet<Long> getEventIDsWithTags() { + return tagged; + } + + @Override + public String getDescription() { + return description; + } + + @Override + public EventType getEventType() { + return type; + } + + @Override + public DescriptionLoD getDescriptionLoD() { + return lod; + } + + /** + * return a new EventCluster identical to this one, except with the given + * EventBundle as the parent. + * + * @param parent + * + * @return a new EventCluster identical to this one, except with the given + * EventBundle as the parent. + */ + public EventCluster withParent(EventStripe parent) { + return new EventCluster(span, type, eventIDs, hashHits, tagged, description, lod, parent); + } + + @Override + public SortedSet<EventCluster> getClusters() { + return ImmutableSortedSet.orderedBy(Comparator.comparing(EventCluster::getStartMillis)).add(this).build(); + } + + @Override + public String toString() { + return "EventCluster{" + "description=" + description + ", eventIDs=" + eventIDs.size() + '}'; + } + + @Override + public int hashCode() { + int hash = 7; + hash = 23 * hash + Objects.hashCode(this.type); + hash = 23 * hash + Objects.hashCode(this.description); + hash = 23 * hash + Objects.hashCode(this.lod); + hash = 23 * hash + Objects.hashCode(this.eventIDs); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final EventCluster other = (EventCluster) obj; + if (!Objects.equals(this.description, other.description)) { + return false; + } + if (!Objects.equals(this.type, other.type)) { + return false; + } + if (this.lod != other.lod) { + return false; + } + if (!Objects.equals(this.eventIDs, other.eventIDs)) { + return false; + } + return true; + } +} diff --git a/bindings/java/src/org/sleuthkit/datamodel/timeline/EventStripe.java b/bindings/java/src/org/sleuthkit/datamodel/timeline/EventStripe.java new file mode 100644 index 0000000000000000000000000000000000000000..cb448e31e72d8b248f83e5fcb837a5c2c18a8b0b --- /dev/null +++ b/bindings/java/src/org/sleuthkit/datamodel/timeline/EventStripe.java @@ -0,0 +1,241 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2015-16 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.timeline; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.ImmutableSortedSet; +import java.util.Comparator; +import java.util.Objects; +import java.util.Optional; +import java.util.SortedSet; +import org.sleuthkit.datamodel.timeline.DescriptionLoD; + +/** + * A 'collection' of {@link EventCluster}s, all having the same type, + * description, and zoom levels, but not necessarily close together in time. + */ +public final class EventStripe implements MultiEvent<EventCluster> { + + public static EventStripe merge(EventStripe u, EventStripe v) { + Preconditions.checkNotNull(u); + Preconditions.checkNotNull(v); + Preconditions.checkArgument(Objects.equals(u.description, v.description)); + Preconditions.checkArgument(Objects.equals(u.lod, v.lod)); + Preconditions.checkArgument(Objects.equals(u.type, v.type)); + Preconditions.checkArgument(Objects.equals(u.parent, v.parent)); + return new EventStripe(u, v); + } + + private final EventCluster parent; + + private final ImmutableSortedSet<EventCluster> clusters; + + /** + * the type of all the events + */ + private final EventType type; + + /** + * the common description of all the events + */ + private final String description; + + /** + * the description level of detail that the events were clustered at. + */ + private final DescriptionLoD lod; + + /** + * the set of ids of the events + */ + private final ImmutableSet<Long> eventIDs; + + /** + * the ids of the subset of events that have at least one tag applied to + * them + */ + private final ImmutableSet<Long> tagged; + + /** + * the ids of the subset of events that have at least one hash set hit + */ + private final ImmutableSet<Long> hashHits; + + public EventStripe withParent(EventCluster parent) { + if (java.util.Objects.nonNull(this.parent)) { + throw new IllegalStateException("Event Stripe already has a parent!"); + } + return new EventStripe(parent, this.type, this.description, this.lod, clusters, eventIDs, tagged, hashHits); + } + + private EventStripe(EventCluster parent, EventType type, String description, DescriptionLoD lod, SortedSet<EventCluster> clusters, ImmutableSet<Long> eventIDs, ImmutableSet<Long> tagged, ImmutableSet<Long> hashHits) { + this.parent = parent; + this.type = type; + this.description = description; + this.lod = lod; + this.clusters = ImmutableSortedSet.copyOf(Comparator.comparing(EventCluster::getStartMillis), clusters); + + this.eventIDs = eventIDs; + this.tagged = tagged; + this.hashHits = hashHits; + } + + public EventStripe(EventCluster cluster) { + + this.clusters = ImmutableSortedSet.orderedBy(Comparator.comparing(EventCluster::getStartMillis)) + .add(cluster.withParent(this)).build(); + + type = cluster.getEventType(); + description = cluster.getDescription(); + lod = cluster.getDescriptionLoD(); + eventIDs = cluster.getEventIDs(); + tagged = cluster.getEventIDsWithTags(); + hashHits = cluster.getEventIDsWithHashHits(); + this.parent = null; + } + + private EventStripe(EventStripe u, EventStripe v) { + clusters = ImmutableSortedSet.orderedBy(Comparator.comparing(EventCluster::getStartMillis)) + .addAll(u.getClusters()) + .addAll(v.getClusters()) + .build(); + + type = u.getEventType(); + description = u.getDescription(); + lod = u.getDescriptionLoD(); + eventIDs = ImmutableSet.<Long>builder() + .addAll(u.getEventIDs()) + .addAll(v.getEventIDs()) + .build(); + tagged = ImmutableSet.<Long>builder() + .addAll(u.getEventIDsWithTags()) + .addAll(v.getEventIDsWithTags()) + .build(); + hashHits = ImmutableSet.<Long>builder() + .addAll(u.getEventIDsWithHashHits()) + .addAll(v.getEventIDsWithHashHits()) + .build(); + parent = u.getParent().orElse(v.getParent().orElse(null)); + } + + @Override + public Optional<EventCluster> getParent() { + return Optional.ofNullable(parent); + } + + public Optional<EventStripe> getParentStripe() { + if (getParent().isPresent()) { + return getParent().get().getParent(); + } else { + return Optional.empty(); + } + } + + @Override + public String getDescription() { + return description; + } + + @Override + public EventType getEventType() { + return type; + } + + @Override + public DescriptionLoD getDescriptionLoD() { + return lod; + } + + @Override + public ImmutableSet<Long> getEventIDs() { + return eventIDs; + } + + @Override + public ImmutableSet<Long> getEventIDsWithHashHits() { + return hashHits; + } + + @Override + public ImmutableSet<Long> getEventIDsWithTags() { + return tagged; + } + + @Override + public long getStartMillis() { + return clusters.first().getStartMillis(); + } + + @Override + public long getEndMillis() { + return clusters.last().getEndMillis(); + } + + @Override + public ImmutableSortedSet< EventCluster> getClusters() { + return clusters; + } + + @Override + public String toString() { + return "EventStripe{" + "description=" + description + ", eventIDs=" + (Objects.isNull(eventIDs) ? 0 : eventIDs.size()) + '}'; //NON-NLS + } + + @Override + public int hashCode() { + int hash = 3; + hash = 79 * hash + Objects.hashCode(this.clusters); + hash = 79 * hash + Objects.hashCode(this.type); + hash = 79 * hash + Objects.hashCode(this.description); + hash = 79 * hash + Objects.hashCode(this.lod); + hash = 79 * hash + Objects.hashCode(this.eventIDs); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final EventStripe other = (EventStripe) obj; + if (!Objects.equals(this.description, other.description)) { + return false; + } + if (!Objects.equals(this.clusters, other.clusters)) { + return false; + } + if (!Objects.equals(this.type, other.type)) { + return false; + } + if (this.lod != other.lod) { + return false; + } + if (!Objects.equals(this.eventIDs, other.eventIDs)) { + return false; + } + return true; + } +} diff --git a/bindings/java/src/org/sleuthkit/datamodel/timeline/EventType.java b/bindings/java/src/org/sleuthkit/datamodel/timeline/EventType.java new file mode 100644 index 0000000000000000000000000000000000000000..c5d92b4b44a1ff730f3342559fbec265172f8bff --- /dev/null +++ b/bindings/java/src/org/sleuthkit/datamodel/timeline/EventType.java @@ -0,0 +1,109 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2014 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.timeline; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import javafx.scene.image.Image; +import javafx.scene.paint.Color; + +/** + * An Event Type represents a distinct kind of event ie file system or web + * activity. An EventType may have an optional super-type and 0 or more + * subtypes, allowing events to be organized in a type hierarchy. + */ +public interface EventType { + + final static List<? extends EventType> allTypes = RootEventType.getInstance().getSubTypesRecusive(); + + static Comparator<EventType> getComparator() { + return Comparator.comparing(EventType.allTypes::indexOf); + + } + + default BaseTypes getBaseType() { + if (this instanceof BaseTypes) { + return (BaseTypes) this; + } else { + return getSuperType().getBaseType(); + } + } + + default List<? extends EventType> getSubTypesRecusive() { + ArrayList<EventType> flatList = new ArrayList<>(); + + for (EventType et : getSubTypes()) { + flatList.add(et); + flatList.addAll(et.getSubTypesRecusive()); + } + return flatList; + } + + /** + * @return the color used to represent this event type visually + */ + default Color getColor() { + + Color baseColor = this.getSuperType().getColor(); + int siblings = getSuperType().getSiblingTypes().stream().max(( + EventType t, EventType t1) + -> Integer.compare(t.getSubTypes().size(), t1.getSubTypes().size())) + .get().getSubTypes().size() + 1; + int superSiblings = this.getSuperType().getSiblingTypes().size(); + + double offset = (360.0 / superSiblings) / siblings; + final Color deriveColor = baseColor.deriveColor(ordinal() * offset, 1, 1, 1); + + return Color.hsb(deriveColor.getHue(), deriveColor.getSaturation(), deriveColor.getBrightness()); + + } + + default List<? extends EventType> getSiblingTypes() { + return this.getSuperType().getSubTypes(); + } + + /** + * @return the super type of this event + */ + public EventType getSuperType(); + + public EventTypeZoomLevel getZoomLevel(); + + /** + * @return a list of event types, one for each subtype of this eventype, or + * an empty list if this event type has no subtypes + */ + public List<? extends EventType> getSubTypes(); + + /* + * return the name of the icon file for this type, it will be resolved in + * the org/sleuthkit/autopsy/timeline/images + */ + public String getIconBase(); + + public String getDisplayName(); + + public EventType getSubType(String string); + + public Image getFXImage(); + + public int ordinal(); + +} diff --git a/bindings/java/src/org/sleuthkit/datamodel/timeline/EventTypeZoomLevel.java b/bindings/java/src/org/sleuthkit/datamodel/timeline/EventTypeZoomLevel.java new file mode 100644 index 0000000000000000000000000000000000000000..d5659fad6dcea6404e2b2f44e6c684c3bd980da2 --- /dev/null +++ b/bindings/java/src/org/sleuthkit/datamodel/timeline/EventTypeZoomLevel.java @@ -0,0 +1,41 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2014 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.timeline; + + +/** + * Enum of event type zoom levels + */ +public enum EventTypeZoomLevel implements DisplayNameProvider { + + ROOT_TYPE(BundleUtils.getBundle().getString("EventTypeZoomLevel.rootType")), + BASE_TYPE(BundleUtils.getBundle().getString("EventTypeZoomLevel.baseType")), + SUB_TYPE(BundleUtils.getBundle().getString("EventTypeZoomLevel.subType")); + + @Override + public String getDisplayName() { + return displayName; + } + + private final String displayName; + + private EventTypeZoomLevel(String displayName) { + this.displayName = displayName; + } +} diff --git a/bindings/java/src/org/sleuthkit/datamodel/timeline/FileSystemTypes.java b/bindings/java/src/org/sleuthkit/datamodel/timeline/FileSystemTypes.java new file mode 100644 index 0000000000000000000000000000000000000000..918e6aeec6471eb7a90f6a3804630e11ef276240 --- /dev/null +++ b/bindings/java/src/org/sleuthkit/datamodel/timeline/FileSystemTypes.java @@ -0,0 +1,81 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2014 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.timeline; + +import java.util.Collections; +import java.util.List; +import javafx.scene.image.Image; + +/** + * + */ +public enum FileSystemTypes implements EventType { + + FILE_MODIFIED(BundleUtils.getBundle().getString("FileSystemTypes.fileModified.name"), "blue-document-attribute-m.png"), // NON-NLS + FILE_ACCESSED(BundleUtils.getBundle().getString("FileSystemTypes.fileAccessed.name"), "blue-document-attribute-a.png"), // NON-NLS + FILE_CREATED(BundleUtils.getBundle().getString("FileSystemTypes.fileCreated.name"), "blue-document-attribute-b.png"), // NON-NLS + FILE_CHANGED(BundleUtils.getBundle().getString("FileSystemTypes.fileChanged.name"), "blue-document-attribute-c.png"); // NON-NLS + + private final String iconBase; + + private final Image image; + + @Override + public Image getFXImage() { + return image; + } + + @Override + public String getIconBase() { + return iconBase; + } + + @Override + public EventTypeZoomLevel getZoomLevel() { + return EventTypeZoomLevel.SUB_TYPE; + } + + private final String displayName; + + @Override + public EventType getSubType(String string) { + return FileSystemTypes.valueOf(string); + } + + @Override + public EventType getSuperType() { + return BaseTypes.FILE_SYSTEM; + } + + @Override + public List<? extends EventType> getSubTypes() { + return Collections.emptyList(); + } + + private FileSystemTypes(String displayName, String iconBase) { + this.displayName = displayName; + this.iconBase = iconBase; + this.image = new Image("org/sleuthkit/autopsy/timeline/images/" + iconBase, true); // NON-NLS + } + + @Override + public String getDisplayName() { + return displayName; + } +} diff --git a/bindings/java/src/org/sleuthkit/datamodel/timeline/IntervalUtils.java b/bindings/java/src/org/sleuthkit/datamodel/timeline/IntervalUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..e7a78f789736d95be87910189461b9780de3a9ea --- /dev/null +++ b/bindings/java/src/org/sleuthkit/datamodel/timeline/IntervalUtils.java @@ -0,0 +1,93 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2013 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.timeline; + +import java.time.Instant; +import java.time.temporal.TemporalAmount; +import java.util.Collection; +import org.joda.time.DateTime; +import org.joda.time.DateTimeZone; +import org.joda.time.Interval; +import org.joda.time.ReadablePeriod; + +/** + * + */ +public class IntervalUtils { + + static public Interval getSpanningInterval(Collection<DateTime> times) { + Interval trange = null; + for (DateTime t : times) { + if (trange == null) { + trange = new Interval(t.getMillis(), t.getMillis() + 1000, DateTimeZone.UTC); + } else { + trange = extendInterval(trange, t.getMillis()); + } + } + return trange; + } + + static public Interval span(Interval range, final Interval range2) { + return new Interval(Math.min(range.getStartMillis(), range2.getStartMillis()), Math.max(range.getEndMillis(), range2.getEndMillis()), DateTimeZone.UTC); + } + + static public Interval extendInterval(Interval range, final Long eventTime) { + return new Interval(Math.min(range.getStartMillis(), eventTime), Math.max(range.getEndMillis(), eventTime + 1), DateTimeZone.UTC); + } + + public static DateTime middleOf(Interval interval) { + return new DateTime((interval.getStartMillis() + interval.getEndMillis()) / 2); + } + + public static Interval getAdjustedInterval(Interval oldInterval, TimeUnits requestedUnit) { + return getIntervalAround(middleOf(oldInterval), requestedUnit.getPeriod()); + } + + static public Interval getIntervalAround(DateTime aroundInstant, ReadablePeriod period) { + DateTime start = aroundInstant.minus(period); + DateTime end = aroundInstant.plus(period); + Interval range = new Interval(start, end); + DateTime middleOf = IntervalUtils.middleOf(range); + long halfRange = range.toDurationMillis() / 4; + final Interval newInterval = new Interval(middleOf.minus(halfRange), middleOf.plus(halfRange)); + return newInterval; + } + + static public Interval getIntervalAround(Instant aroundInstant, TemporalAmount temporalAmount) { + long start = aroundInstant.minus(temporalAmount).toEpochMilli(); + long end = aroundInstant.plusMillis(1).plus(temporalAmount).toEpochMilli(); + final Interval newInterval = new Interval(start, Math.max(start + 1, end)); + return newInterval; + } + + /** + * Get an interval the length of the given period, centered around the + * center of the given interval. + * + * @param interval The interval whose center will be the center of the new + * interval. + * @param period The length of the new interval + * + * @return An interval the length of the given period, centered around the + * center of the given interval. + */ + static public Interval getIntervalAroundMiddle(Interval interval, ReadablePeriod period) { + return getIntervalAround(middleOf(interval), period); + } +} diff --git a/bindings/java/src/org/sleuthkit/datamodel/timeline/MiscTypes.java b/bindings/java/src/org/sleuthkit/datamodel/timeline/MiscTypes.java new file mode 100644 index 0000000000000000000000000000000000000000..51762116c2981f0e1cdba8b28be033be81065a3c --- /dev/null +++ b/bindings/java/src/org/sleuthkit/datamodel/timeline/MiscTypes.java @@ -0,0 +1,262 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2014-16 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.timeline; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.function.Function; +import java.util.logging.Level; +import javafx.scene.image.Image; +import org.apache.commons.lang3.StringUtils; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE; +import org.sleuthkit.datamodel.BlackboardAttribute; +import org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE; +import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.datamodel.timeline.ArtifactEventType.AttributeEventDescription; +import org.sleuthkit.datamodel.timeline.ArtifactEventType.AttributeExtractor; +import org.sleuthkit.datamodel.timeline.ArtifactEventType.EmptyExtractor; +import static org.sleuthkit.datamodel.timeline.ArtifactEventType.LOGGER; +import static org.sleuthkit.datamodel.timeline.ArtifactEventType.getAttributeSafe; + +/** + * + */ +public enum MiscTypes implements EventType, ArtifactEventType { + + MESSAGE(BundleUtils.getBundle().getString( "MiscTypes.message.name"), "message.png", // NON-NLS + new BlackboardArtifact.Type(ARTIFACT_TYPE.TSK_MESSAGE), + new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DATETIME), + new AttributeExtractor(new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_MESSAGE_TYPE)), + artf -> { + final BlackboardAttribute dir = getAttributeSafe(artf, new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DIRECTION)); + final BlackboardAttribute readStatus = getAttributeSafe(artf, new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_READ_STATUS)); + final BlackboardAttribute name = getAttributeSafe(artf, new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_NAME)); + final BlackboardAttribute phoneNumber = getAttributeSafe(artf, new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_PHONE_NUMBER)); + final BlackboardAttribute subject = getAttributeSafe(artf, new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_SUBJECT)); + List<String> asList = Arrays.asList(stringValueOf(dir), stringValueOf(readStatus), name != null || phoneNumber != null ? toFrom(dir) : "", stringValueOf(name != null ? name : phoneNumber), (subject == null ? "" : stringValueOf(subject))); + return StringUtils.join(asList, " "); + }, + new AttributeExtractor(new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_TEXT))), + GPS_ROUTE(BundleUtils.getBundle().getString( "MiscTypes.GPSRoutes.name"), "gps-search.png", // NON-NLS + new BlackboardArtifact.Type(ARTIFACT_TYPE.TSK_GPS_ROUTE), + new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DATETIME), + new AttributeExtractor(new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_PROG_NAME)), + new AttributeExtractor(new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_LOCATION)), + artf -> { + final BlackboardAttribute latStart = getAttributeSafe(artf, new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_START)); + final BlackboardAttribute longStart = getAttributeSafe(artf, new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE_START)); + final BlackboardAttribute latEnd = getAttributeSafe(artf, new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_END)); + final BlackboardAttribute longEnd = getAttributeSafe(artf, new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE_END)); + return String.format("from %1$s %2$s to %3$s %4$s", stringValueOf(latStart), stringValueOf(longStart), stringValueOf(latEnd), stringValueOf(longEnd)); // NON-NLS + }), + GPS_TRACKPOINT(BundleUtils.getBundle().getString( "MiscTypes.GPSTrackpoint.name"), "gps-trackpoint.png", // NON-NLS + new BlackboardArtifact.Type(ARTIFACT_TYPE.TSK_GPS_TRACKPOINT), + new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DATETIME), + new AttributeExtractor(new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_PROG_NAME)), + artf -> { + final BlackboardAttribute longitude = getAttributeSafe(artf, new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE)); + final BlackboardAttribute latitude = getAttributeSafe(artf, new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_GEO_LATITUDE)); + return stringValueOf(latitude) + " " + stringValueOf(longitude); // NON-NLS + }, + new EmptyExtractor()), + CALL_LOG(BundleUtils.getBundle().getString( "MiscTypes.Calls.name"), "calllog.png", // NON-NLS + new BlackboardArtifact.Type(ARTIFACT_TYPE.TSK_CALLLOG), + new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DATETIME_START), + new AttributeExtractor(new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_NAME)), + new AttributeExtractor(new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_PHONE_NUMBER)), + new AttributeExtractor(new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DIRECTION))), + EMAIL(BundleUtils.getBundle().getString( "MiscTypes.Email.name"), "mail-icon-16.png", // NON-NLS + new BlackboardArtifact.Type(ARTIFACT_TYPE.TSK_EMAIL_MSG), + new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DATETIME_SENT), + artf -> { + final BlackboardAttribute emailFrom = getAttributeSafe(artf, new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_EMAIL_FROM)); + final BlackboardAttribute emailTo = getAttributeSafe(artf, new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_EMAIL_TO)); + return stringValueOf(emailFrom) + " to " + stringValueOf(emailTo); // NON-NLS + }, + new AttributeExtractor(new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_SUBJECT)), + new AttributeExtractor(new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_EMAIL_CONTENT_PLAIN))), + RECENT_DOCUMENTS(BundleUtils.getBundle().getString( "MiscTypes.recentDocuments.name"), "recent_docs.png", // NON-NLS + new BlackboardArtifact.Type(ARTIFACT_TYPE.TSK_RECENT_OBJECT), + new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DATETIME), + new AttributeExtractor(new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_PATH)).andThen( + (String t) -> (StringUtils.substringBeforeLast(StringUtils.substringBeforeLast(t, "\\"), "\\"))), + new AttributeExtractor(new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_PATH)).andThen( + (String t) -> StringUtils.substringBeforeLast(t, "\\")), + new AttributeExtractor(new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_PATH))) { + + @Override + public AttributeEventDescription parseAttributesHelper(BlackboardArtifact artf) throws TskCoreException { + final BlackboardAttribute dateTimeAttr = artf.getAttribute(getDateTimeAttributeType()); + + long time = dateTimeAttr.getValueLong(); + + //Non-default description construction + String shortDescription = getShortExtractor().apply(artf); + String medDescription = getMedExtractor().apply(artf); + String fullDescription = getFullExtractor().apply(artf); + + return new AttributeEventDescription(time, shortDescription, medDescription, fullDescription); + } + }, + INSTALLED_PROGRAM(BundleUtils.getBundle().getString( "MiscTypes.installedPrograms.name"), "programs.png", // NON-NLS + new BlackboardArtifact.Type(ARTIFACT_TYPE.TSK_INSTALLED_PROG), + new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DATETIME), + new AttributeExtractor(new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_PROG_NAME)), + new EmptyExtractor(), + new EmptyExtractor()), + EXIF(BundleUtils.getBundle().getString( "MiscTypes.exif.name"), "camera-icon-16.png", // NON-NLS + new BlackboardArtifact.Type(ARTIFACT_TYPE.TSK_METADATA_EXIF), + new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DATETIME_CREATED), + new AttributeExtractor(new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DEVICE_MAKE)), + new AttributeExtractor(new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DEVICE_MODEL)), + artf -> { + try { + AbstractFile file = artf.getSleuthkitCase().getAbstractFileById(artf.getObjectID()); + if (file != null) { + return file.getName(); + } + } catch (TskCoreException ex) { + LOGGER.log(Level.SEVERE, "Exif event type failed to look up backing file name", ex); //NON-NLS + } + return "error loading file name"; + }), + DEVICES_ATTACHED(BundleUtils.getBundle().getString( "MiscTypes.devicesAttached.name"), "usb_devices.png", // NON-NLS + new BlackboardArtifact.Type(ARTIFACT_TYPE.TSK_DEVICE_ATTACHED), + new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DATETIME), + new AttributeExtractor(new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DEVICE_MAKE)), + new AttributeExtractor(new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DEVICE_MODEL)), + new AttributeExtractor(new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DEVICE_ID))); + + static public String stringValueOf(BlackboardAttribute attr) { + return Optional.ofNullable(attr) + .map(BlackboardAttribute::getDisplayString) + .orElse(""); + } + + public static String toFrom(BlackboardAttribute dir) { + if (dir == null) { + return ""; + } else { + switch (dir.getDisplayString()) { + case "Incoming": // NON-NLS + return "from"; // NON-NLS + case "Outgoing": // NON-NLS + return "to"; // NON-NLS + default: + return ""; // NON-NLS + } + } + } + + private final BlackboardAttribute.Type dateTimeAttributeType; + + private final String iconBase; + + private final Image image; + + @Override + public Image getFXImage() { + return image; + } + + private final Function<BlackboardArtifact, String> longExtractor; + + private final Function<BlackboardArtifact, String> medExtractor; + + private final Function<BlackboardArtifact, String> shortExtractor; + + @Override + public Function<BlackboardArtifact, String> getFullExtractor() { + return longExtractor; + } + + @Override + public Function<BlackboardArtifact, String> getMedExtractor() { + return medExtractor; + } + + @Override + public Function<BlackboardArtifact, String> getShortExtractor() { + return shortExtractor; + } + + @Override + public BlackboardAttribute.Type getDateTimeAttributeType() { + return dateTimeAttributeType; + } + + @Override + public EventTypeZoomLevel getZoomLevel() { + return EventTypeZoomLevel.SUB_TYPE; + } + + private final String displayName; + + private final BlackboardArtifact.Type artifactType; + + @Override + public String getDisplayName() { + return displayName; + } + + @Override + public String getIconBase() { + return iconBase; + } + + @Override + public EventType getSubType(String string) { + return MiscTypes.valueOf(string); + } + + private MiscTypes(String displayName, String iconBase, BlackboardArtifact.Type artifactType, + BlackboardAttribute.Type dateTimeAttributeType, + Function<BlackboardArtifact, String> shortExtractor, + Function<BlackboardArtifact, String> medExtractor, + Function<BlackboardArtifact, String> longExtractor) { + this.displayName = displayName; + this.iconBase = iconBase; + this.artifactType = artifactType; + this.dateTimeAttributeType = dateTimeAttributeType; + this.shortExtractor = shortExtractor; + this.medExtractor = medExtractor; + this.longExtractor = longExtractor; + this.image = new Image("org/sleuthkit/autopsy/timeline/images/" + iconBase, true); // NON-NLS + } + + @Override + public EventType getSuperType() { + return BaseTypes.MISC_TYPES; + } + + @Override + public List<? extends EventType> getSubTypes() { + return Collections.emptyList(); + } + + @Override + public BlackboardArtifact.Type getArtifactType() { + return artifactType; + } + +} diff --git a/bindings/java/src/org/sleuthkit/datamodel/timeline/MultiEvent.java b/bindings/java/src/org/sleuthkit/datamodel/timeline/MultiEvent.java new file mode 100644 index 0000000000000000000000000000000000000000..792723226886d670a18a8f3643a7cbe9a848f11e --- /dev/null +++ b/bindings/java/src/org/sleuthkit/datamodel/timeline/MultiEvent.java @@ -0,0 +1,37 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2015-16 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.timeline; + +import java.util.Optional; +import java.util.SortedSet; + +/** + * A interface for groups of events that share some attributes in common. + * @param <ParentType> + */ +public interface MultiEvent<ParentType extends MultiEvent<?>> extends TimeLineEvent { + + @Override + long getEndMillis(); + + Optional<ParentType> getParent(); + + @Override + SortedSet<EventCluster> getClusters(); +} diff --git a/bindings/java/src/org/sleuthkit/datamodel/timeline/RangeDivisionInfo.java b/bindings/java/src/org/sleuthkit/datamodel/timeline/RangeDivisionInfo.java new file mode 100644 index 0000000000000000000000000000000000000000..7d3928533715a2bb03fbcef66d4f05e034aaf700 --- /dev/null +++ b/bindings/java/src/org/sleuthkit/datamodel/timeline/RangeDivisionInfo.java @@ -0,0 +1,189 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2013 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.timeline; + +import com.google.common.collect.ImmutableList; +import java.util.ArrayList; +import java.util.List; +import org.joda.time.DateTime; +import org.joda.time.DateTimeFieldType; +import org.joda.time.DateTimeZone; +import org.joda.time.Days; +import org.joda.time.Hours; +import org.joda.time.Interval; +import org.joda.time.Minutes; +import org.joda.time.Months; +import org.joda.time.Seconds; +import org.joda.time.Years; +import org.joda.time.format.DateTimeFormat; +import org.joda.time.format.DateTimeFormatter; +import org.joda.time.format.ISODateTimeFormat; + +/** + * Bundles up the results of analyzing a time range for the appropriate + * TimeUnits to use to visualize it. Partly, this class exists so I + * don't have to have more member variables in other places , and partly because + * I can only return a single value from a function. This might only be a + * temporary design but is working well for now. + */ +public class RangeDivisionInfo { + + /** + * the size of the periods we should divide the interval into + */ + private final TimeUnits blockSize; + + /** + * The number of Blocks we are going to divide the interval into. + */ + private final int numberOfBlocks; + + /** + * a DateTimeFormatter corresponding to the block size for the tick + * marks on the date axis of the graph + */ + private final DateTimeFormatter tickFormatter; + + /** + * an adjusted lower bound for the range such that is lines up with a block + * boundary before or at the start of the timerange + */ + private final long lowerBound; + + /** + * an adjusted upper bound for the range such that is lines up with a block + * boundary at or after the end of the timerange + */ + private final long upperBound; + + /** + * the time range this RangeDivisionInfo describes + */ + private final Interval timeRange; + private ImmutableList<Interval> intervals; + + public Interval getTimeRange() { + return timeRange; + } + + private RangeDivisionInfo(Interval timeRange, int periodsInRange, TimeUnits periodSize, DateTimeFormatter tickformatter, long lowerBound, long upperBound) { + this.numberOfBlocks = periodsInRange; + this.blockSize = periodSize; + this.tickFormatter = tickformatter; + + this.lowerBound = lowerBound; + this.upperBound = upperBound; + this.timeRange = timeRange; + } + + /** + * Static factory method. + * + * Determine the period size, number of periods, whole period bounds, and + * formatters to use to visualize the given timerange. + * + * @param timeRange + * + * @return + */ + public static RangeDivisionInfo getRangeDivisionInfo(Interval timeRange, DateTimeZone tz) { + //Check from largest to smallest unit + + //TODO: make this more generic... reduce code duplication -jm + DateTimeFieldType timeUnit; + final DateTime startWithZone = timeRange.getStart().withZone(tz); + final DateTime endWithZone = timeRange.getEnd().withZone(tz); + + if (Years.yearsIn(timeRange).isGreaterThan(Years.THREE)) { + timeUnit = DateTimeFieldType.year(); + long lower = startWithZone.property(timeUnit).roundFloorCopy().getMillis(); + long upper = endWithZone.property(timeUnit).roundCeilingCopy().getMillis(); + return new RangeDivisionInfo(timeRange, Years.yearsIn(timeRange).get(timeUnit.getDurationType()) + 1, TimeUnits.YEARS, ISODateTimeFormat.year(), lower, upper); + } else if (Months.monthsIn(timeRange).isGreaterThan(Months.THREE)) { + timeUnit = DateTimeFieldType.monthOfYear(); + long lower = startWithZone.property(timeUnit).roundFloorCopy().getMillis(); + long upper = endWithZone.property(timeUnit).roundCeilingCopy().getMillis(); + return new RangeDivisionInfo(timeRange, Months.monthsIn(timeRange).getMonths() + 1, TimeUnits.MONTHS, DateTimeFormat.forPattern("YYYY'-'MMMM"), lower, upper); // NON-NLS + } else if (Days.daysIn(timeRange).isGreaterThan(Days.THREE)) { + timeUnit = DateTimeFieldType.dayOfMonth(); + long lower = startWithZone.property(timeUnit).roundFloorCopy().getMillis(); + long upper = endWithZone.property(timeUnit).roundCeilingCopy().getMillis(); + return new RangeDivisionInfo(timeRange, Days.daysIn(timeRange).getDays() + 1, TimeUnits.DAYS, DateTimeFormat.forPattern("YYYY'-'MMMM'-'dd"), lower, upper); // NON-NLS + } else if (Hours.hoursIn(timeRange).isGreaterThan(Hours.THREE)) { + timeUnit = DateTimeFieldType.hourOfDay(); + long lower = startWithZone.property(timeUnit).roundFloorCopy().getMillis(); + long upper = endWithZone.property(timeUnit).roundCeilingCopy().getMillis(); + return new RangeDivisionInfo(timeRange, Hours.hoursIn(timeRange).getHours() + 1, TimeUnits.HOURS, DateTimeFormat.forPattern("YYYY'-'MMMM'-'dd HH"), lower, upper); // NON-NLS + } else if (Minutes.minutesIn(timeRange).isGreaterThan(Minutes.THREE)) { + timeUnit = DateTimeFieldType.minuteOfHour(); + long lower = startWithZone.property(timeUnit).roundFloorCopy().getMillis(); + long upper = endWithZone.property(timeUnit).roundCeilingCopy().getMillis(); + return new RangeDivisionInfo(timeRange, Minutes.minutesIn(timeRange).getMinutes() + 1, TimeUnits.MINUTES, DateTimeFormat.forPattern("YYYY'-'MMMM'-'dd HH':'mm"), lower, upper); // NON-NLS + } else { + timeUnit = DateTimeFieldType.secondOfMinute(); + long lower = startWithZone.property(timeUnit).roundFloorCopy().getMillis(); + long upper = endWithZone.property(timeUnit).roundCeilingCopy().getMillis(); + return new RangeDivisionInfo(timeRange, Seconds.secondsIn(timeRange).getSeconds() + 1, TimeUnits.SECONDS, DateTimeFormat.forPattern("YYYY'-'MMMM'-'dd HH':'mm':'ss"), lower, upper); // NON-NLS + } + } + + public DateTimeFormatter getTickFormatter() { + return tickFormatter; + } + + public int getPeriodsInRange() { + return numberOfBlocks; + } + + public TimeUnits getPeriodSize() { + return blockSize; + } + + public long getUpperBound() { + return upperBound; + } + + public long getLowerBound() { + return lowerBound; + } + + @SuppressWarnings("ReturnOfCollectionOrArrayField") + synchronized public List<Interval> getIntervals(DateTimeZone tz) { + if (intervals == null) { + ArrayList<Interval> tempList = new ArrayList<>(); + //extend range to block bounderies (ie day, month, year) + final Interval range = new Interval(new DateTime(lowerBound, tz), new DateTime(upperBound, tz)); + + DateTime start = range.getStart(); + while (range.contains(start)) { + //increment for next iteration + DateTime end = start.plus(getPeriodSize().getPeriod()); + final Interval interval = new Interval(start, end); + tempList.add(interval); + start = end; + } + intervals = ImmutableList.copyOf(tempList); + } + return intervals; + } + + public String formatForTick(Interval interval) { + return interval.getStart().toString(tickFormatter); + } +} diff --git a/bindings/java/src/org/sleuthkit/datamodel/timeline/RootEventType.java b/bindings/java/src/org/sleuthkit/datamodel/timeline/RootEventType.java new file mode 100644 index 0000000000000000000000000000000000000000..9f8921a9021b72b11cfbb0d4a1a8834aada28a94 --- /dev/null +++ b/bindings/java/src/org/sleuthkit/datamodel/timeline/RootEventType.java @@ -0,0 +1,96 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2013 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.timeline; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import javafx.scene.image.Image; +import javafx.scene.paint.Color; + +/** + * A singleton EventType to represent the root type of all event types. + */ +public class RootEventType implements EventType { + + @Override + public List<RootEventType> getSiblingTypes() { + return Collections.singletonList(this); + } + + @Override + public EventTypeZoomLevel getZoomLevel() { + return EventTypeZoomLevel.ROOT_TYPE; + } + + private RootEventType() { + } + + public static RootEventType getInstance() { + return RootEventTypeHolder.INSTANCE; + } + + @Override + public EventType getSubType(String string) { + return BaseTypes.valueOf(string); + } + + @Override + public int ordinal() { + return 0; + } + + private static class RootEventTypeHolder { + + private static final RootEventType INSTANCE = new RootEventType(); + + private RootEventTypeHolder() { + } + } + + @Override + public Color getColor() { + return Color.hsb(359, .9, .9, 0); + } + + @Override + public RootEventType getSuperType() { + return this; + } + + @Override + public List<BaseTypes> getSubTypes() { + return Arrays.asList(BaseTypes.values()); + } + + @Override + public String getIconBase() { + throw new UnsupportedOperationException("Not supported yet."); // NON-NLS //To change body of generated methods, choose Tools | Templates. + } + + @Override + public String getDisplayName() { + return BundleUtils.getBundle().getString("RootEventType.eventTypes.name"); + } + + @Override + public Image getFXImage() { + return null; + } +} diff --git a/bindings/java/src/org/sleuthkit/datamodel/timeline/SingleEvent.java b/bindings/java/src/org/sleuthkit/datamodel/timeline/SingleEvent.java new file mode 100644 index 0000000000000000000000000000000000000000..2c85c0565c72b94eb6f68b640db7ef0b0c4f2bd8 --- /dev/null +++ b/bindings/java/src/org/sleuthkit/datamodel/timeline/SingleEvent.java @@ -0,0 +1,320 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2014-16 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.timeline; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSortedSet; +import java.util.Collections; +import java.util.Comparator; +import java.util.Optional; +import java.util.Set; +import java.util.SortedSet; +import org.joda.time.Interval; +import org.sleuthkit.datamodel.TskData; + +/** + * A single event. + */ +public class SingleEvent implements TimeLineEvent { + + private final long eventID; + /** + * The TSK object ID of the file this event is derived from. + */ + private final long objID; + + /** + * The TSK artifact ID of the file this event is derived from. Null, if this + * event is not derived from an artifact. + */ + private final Long artifactID; + + /** + * The TSK datasource ID of the datasource this event belongs to. + */ + private final long dataSourceID; + + /** + * The time of this event in second from the Unix epoch. + */ + private final long time; + /** + * The type of this event. + */ + private final EventType type; + + /** + * The three descriptions (full, med, short) stored in a map, keyed by + * DescriptionLOD (Level of Detail) + */ + private final ImmutableMap<DescriptionLoD, String> descriptions; + + /** + * The known value for the file this event is derived from. + */ + private final TskData.FileKnown known; + + /** + * True if the file this event is derived from hits any of the configured + * hash sets. + */ + private final boolean hashHit; + + /** + * True if the file or artifact this event is derived from is tagged. + */ + private final boolean tagged; + + /** + * Single events may or may not have their parent set, since the parent is a + * transient property of the current (details) view settings. + */ + private MultiEvent<?> parent = null; + + public SingleEvent(long eventID, long dataSourceID, long objID, Long artifactID, long time, EventType type, String fullDescription, String medDescription, String shortDescription, TskData.FileKnown known, boolean hashHit, boolean tagged) { + this.eventID = eventID; + this.dataSourceID = dataSourceID; + this.objID = objID; + this.artifactID = Long.valueOf(0).equals(artifactID) ? null : artifactID; + this.time = time; + this.type = type; + descriptions = ImmutableMap.<DescriptionLoD, String>of(DescriptionLoD.FULL, fullDescription, + DescriptionLoD.MEDIUM, medDescription, + DescriptionLoD.SHORT, shortDescription); + this.known = known; + this.hashHit = hashHit; + this.tagged = tagged; + } + + /** + * Get a new SingleEvent that is the same as this event, but with the given + * parent. + * + * @param newParent the parent of the new event object. + * + * @return a new SingleEvent that is the same as this event, but with the + * given parent. + */ + public SingleEvent withParent(MultiEvent<?> newParent) { + SingleEvent singleEvent = new SingleEvent(eventID, dataSourceID, objID, artifactID, time, type, descriptions.get(DescriptionLoD.FULL), descriptions.get(DescriptionLoD.MEDIUM), descriptions.get(DescriptionLoD.SHORT), known, hashHit, tagged); + singleEvent.parent = newParent; + return singleEvent; + } + + /** + * Is the file or artifact this event is derived from tagged? + * + * @return true if he file or artifact this event is derived from is tagged. + */ + public boolean isTagged() { + return tagged; + } + + /** + * Is the file this event is derived from in any of the configured hash + * sets. + * + * + * @return True if the file this event is derived from is in any of the + * configured hash sets. + */ + public boolean isHashHit() { + return hashHit; + } + + /** + * Get the artifact id of the artifact this event is derived from. + * + * @return An Optional containing the artifact ID. Will be empty if this + * event is not derived from an artifact + */ + public Optional<Long> getArtifactID() { + return Optional.ofNullable(artifactID); + } + + /** + * Get the event id of this event. + * + * @return The event id of this event. + */ + public long getEventID() { + return eventID; + } + + /** + * Get the obj id of the file this event is derived from. + * + * @return the object id. + */ + public long getFileID() { + return objID; + } + + /** + * Get the time of this event (in seconds from the Unix epoch). + * + * @return the time of this event in seconds from Unix epoch + */ + public long getTime() { + return time; + } + + @Override + public EventType getEventType() { + return type; + } + + /** + * Get the full description of this event. + * + * @return the full description + */ + public String getFullDescription() { + return getDescription(DescriptionLoD.FULL); + } + + /** + * Get the medium description of this event. + * + * @return the medium description + */ + public String getMedDescription() { + return getDescription(DescriptionLoD.MEDIUM); + } + + /** + * Get the short description of this event. + * + * @return the short description + */ + public String getShortDescription() { + return getDescription(DescriptionLoD.SHORT); + } + + /** + * Get the known value of the file this event is derived from. + * + * @return the known value + */ + public TskData.FileKnown getKnown() { + return known; + } + + /** + * Get the description of this event at the give level of detail(LoD). + * + * @param lod The level of detail to get. + * + * @return The description of this event at the given level of detail. + */ + public String getDescription(DescriptionLoD lod) { + return descriptions.get(lod); + } + + /** + * Get the datasource id of the datasource this event belongs to. + * + * @return the datasource id. + */ + public long getDataSourceID() { + return dataSourceID; + } + + @Override + public Set<Long> getEventIDs() { + return Collections.singleton(eventID); + } + + @Override + public Set<Long> getEventIDsWithHashHits() { + return isHashHit() ? Collections.singleton(eventID) : Collections.emptySet(); + } + + @Override + public Set<Long> getEventIDsWithTags() { + return isTagged() ? Collections.singleton(eventID) : Collections.emptySet(); + } + + @Override + public long getEndMillis() { + return time * 1000; + } + + @Override + public long getStartMillis() { + return time * 1000; + } + + @Override + public int hashCode() { + int hash = 7; + hash = 13 * hash + (int) (this.eventID ^ (this.eventID >>> 32)); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final SingleEvent other = (SingleEvent) obj; + if (this.eventID != other.eventID) { + return false; + } + return true; + } + + @Override + public SortedSet<EventCluster> getClusters() { + EventCluster eventCluster = new EventCluster(new Interval(time * 1000, time * 1000), type, getEventIDs(), getEventIDsWithHashHits(), getEventIDsWithTags(), getFullDescription(), DescriptionLoD.FULL); + return ImmutableSortedSet.orderedBy(Comparator.comparing(EventCluster::getStartMillis)).add(eventCluster).build(); + } + + @Override + public String getDescription() { + return getFullDescription(); + } + + @Override + public DescriptionLoD getDescriptionLoD() { + return DescriptionLoD.FULL; + } + + /** + * get the EventStripe (if any) that contains this event, skipping over any + * intervening event cluster + * + * @return an Optional containing the parent stripe of this cluster: empty + * if the cluster has no parent set or the parent has no parent + * stripe. + */ + @Override + public Optional<EventStripe> getParentStripe() { + if (parent == null) { + return Optional.empty(); + } else if (parent instanceof EventStripe) { + return Optional.of((EventStripe) parent); + } else { + return parent.getParentStripe(); + } + } +} diff --git a/bindings/java/src/org/sleuthkit/datamodel/timeline/TimeLineEvent.java b/bindings/java/src/org/sleuthkit/datamodel/timeline/TimeLineEvent.java new file mode 100644 index 0000000000000000000000000000000000000000..404e692233e10a0505cc8d772956c7b1e7912a23 --- /dev/null +++ b/bindings/java/src/org/sleuthkit/datamodel/timeline/TimeLineEvent.java @@ -0,0 +1,121 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2016 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.timeline; + +import java.util.Optional; +import java.util.Set; +import java.util.SortedSet; +import org.sleuthkit.datamodel.timeline.DescriptionLoD; + +/** + * An event of the timeline. Concrete implementations may represent single + * events or multiple events grouped together based on some common properties + * (for example close together in time and or having similar descriptions or + * event types). Note that for SingleEvents or events that are all simultaneous, + * the start time may be equal to the end time. + */ +public interface TimeLineEvent { + + /** + * Get a description of this event. Implementations may choose what level of + * description to provide. + * + * @return A description of this event. + */ + public String getDescription(); + + /** + * Get the Description level of detail at which all single events of this + * event have the same description, ie, what level of detail was used to + * group these events. + * + * @return the description level of detail of the given events + */ + public DescriptionLoD getDescriptionLoD(); + + /** + * get the EventStripe (if any) that contains this event. + * + * @return an Optional containing the parent stripe of this event, or is + * empty if the event has no parent stripe. + */ + public Optional<EventStripe> getParentStripe(); + + /** + * Get the id(s) of this event as a set. + * + * @return a Set containing the event id(s) of this event. + */ + Set<Long> getEventIDs(); + + /** + * Get the id(s) of this event that have hash hits associated with them. + * + * @return a Set containing the event id(s) of this event that have hash + * hits associated with them. + */ + Set<Long> getEventIDsWithHashHits(); + + /** + * Get the id(s) of this event that have tags associated with them. + * + * @return a Set containing the event id(s) of this event that have tags + * associated with them. + */ + Set<Long> getEventIDsWithTags(); + + /** + * Get the EventType of this event. + * + * @return the EventType of this event. + */ + EventType getEventType(); + + /** + * Get the start time of this event as milliseconds from the Unix Epoch. + * + * @return the start time of this event as milliseconds from the Unix Epoch. + */ + long getEndMillis(); + + /** + * Get the end time of this event as milliseconds from the Unix Epoch. + * + * @return the end time of this event as milliseconds from the Unix Epoch. + */ + long getStartMillis(); + + /** + * Get the number of SingleEvents this event contains. + * + * @return the number of SingleEvents this event contains. + */ + default int getSize() { + return getEventIDs().size(); + } + + /** + * Get the EventClusters that make up this event. May be null for + * SingleEvents, or return a refernece to this event if it is an + * EventCluster + * + * @return The EventClusters that make up this event. + */ + SortedSet<EventCluster> getClusters(); +} diff --git a/bindings/java/src/org/sleuthkit/datamodel/timeline/TimeUnits.java b/bindings/java/src/org/sleuthkit/datamodel/timeline/TimeUnits.java new file mode 100644 index 0000000000000000000000000000000000000000..da70fcaf3eecf0b292ff663a92c39c1ccc5df8e8 --- /dev/null +++ b/bindings/java/src/org/sleuthkit/datamodel/timeline/TimeUnits.java @@ -0,0 +1,96 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2014 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.timeline; + +import org.sleuthkit.datamodel.timeline.DisplayNameProvider; +import java.time.temporal.ChronoUnit; +import org.joda.time.Days; +import org.joda.time.Hours; +import org.joda.time.Minutes; +import org.joda.time.Months; +import org.joda.time.Period; +import org.joda.time.Seconds; +import org.joda.time.Years; + +/** + * predefined units of time for use in choosing axis labels and sub intervals. + */ +public enum TimeUnits implements DisplayNameProvider { + + FOREVER(null, ChronoUnit.FOREVER), + YEARS(Years.ONE.toPeriod(), ChronoUnit.YEARS), + MONTHS(Months.ONE.toPeriod(), ChronoUnit.MONTHS), + DAYS(Days.ONE.toPeriod(), ChronoUnit.DAYS), + HOURS(Hours.ONE.toPeriod(), ChronoUnit.HOURS), + MINUTES(Minutes.ONE.toPeriod(), ChronoUnit.MINUTES), + SECONDS(Seconds.ONE.toPeriod(), ChronoUnit.SECONDS); + + public static TimeUnits fromChronoUnit(ChronoUnit chronoUnit) { + switch (chronoUnit) { + + case FOREVER: + return FOREVER; + case ERAS: + case MILLENNIA: + case CENTURIES: + case DECADES: + case YEARS: + return YEARS; + case MONTHS: + return MONTHS; + case WEEKS: + case DAYS: + return DAYS; + case HOURS: + case HALF_DAYS: + return HOURS; + case MINUTES: + return MINUTES; + case SECONDS: + case MILLIS: + case MICROS: + case NANOS: + return SECONDS; + default: + return YEARS; + } + } + + private final Period p; + + private final ChronoUnit cu; + + public Period getPeriod() { + return p; + } + + public ChronoUnit getChronoUnit() { + return cu; + } + + private TimeUnits(Period p, ChronoUnit cu) { + this.p = p; + this.cu = cu; + } + + @Override + public String getDisplayName() { + return toString(); + } +} diff --git a/bindings/java/src/org/sleuthkit/datamodel/timeline/WebTypes.java b/bindings/java/src/org/sleuthkit/datamodel/timeline/WebTypes.java new file mode 100644 index 0000000000000000000000000000000000000000..963781dedd9a14f489ea0695703032d3a56fe244 --- /dev/null +++ b/bindings/java/src/org/sleuthkit/datamodel/timeline/WebTypes.java @@ -0,0 +1,210 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2014-16 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.timeline; + +import com.google.common.net.InternetDomainName; +import java.util.Collections; +import java.util.List; +import java.util.function.Function; +import javafx.scene.image.Image; +import org.apache.commons.lang3.StringUtils; +import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.BlackboardAttribute; +import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.datamodel.timeline.ArtifactEventType.AttributeEventDescription; +import org.sleuthkit.datamodel.timeline.ArtifactEventType.AttributeExtractor; + +/** + * + */ +public enum WebTypes implements EventType, ArtifactEventType { + + WEB_DOWNLOADS(BundleUtils.getBundle().getString( "WebTypes.webDownloads.name"), + "downloads.png", // NON-NLS + new BlackboardArtifact.Type(BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_DOWNLOAD), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_ACCESSED), + TopPrivateDomainExtractor.getInstance(), + new AttributeExtractor(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH)), + new AttributeExtractor(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_URL))) { + + @Override + public AttributeEventDescription parseAttributesHelper(BlackboardArtifact artf) throws TskCoreException { + long time = artf.getAttribute(getDateTimeAttributeType()).getValueLong(); + String domain = getShortExtractor().apply(artf); + String path = getMedExtractor().apply(artf); + String fileName = StringUtils.substringAfterLast(path, "/"); + String url = getFullExtractor().apply(artf); + + //TODO: review non default description construction + String shortDescription = fileName + " from " + domain; // NON-NLS + String medDescription = fileName + " from " + url; // NON-NLS + String fullDescription = path + " from " + url; // NON-NLS + return new AttributeEventDescription(time, shortDescription, medDescription, fullDescription); + } + }, + //TODO: review description separators + WEB_COOKIE(BundleUtils.getBundle().getString( "WebTypes.webCookies.name"), + "cookies.png", // NON-NLS + new BlackboardArtifact.Type(BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_COOKIE), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME), + TopPrivateDomainExtractor.getInstance(), + new AttributeExtractor(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME)), + new AttributeExtractor(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_VALUE))), + //TODO: review description separators + WEB_BOOKMARK(BundleUtils.getBundle().getString( "WebTypes.webBookmarks.name"), + "bookmarks.png", // NON-NLS + new BlackboardArtifact.Type(BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_BOOKMARK), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_CREATED), + TopPrivateDomainExtractor.getInstance(), + new AttributeExtractor(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_URL)), + new AttributeExtractor(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_TITLE))), + //TODO: review description separators + WEB_HISTORY(BundleUtils.getBundle().getString( "WebTypes.webHistory.name"), + "history.png", // NON-NLS + new BlackboardArtifact.Type(BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_HISTORY), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_ACCESSED), + TopPrivateDomainExtractor.getInstance(), + new AttributeExtractor(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_URL)), + new AttributeExtractor(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_TITLE))), + //TODO: review description separators + WEB_SEARCH(BundleUtils.getBundle().getString( "WebTypes.webSearch.name"), + "searchquery.png", // NON-NLS + new BlackboardArtifact.Type(BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_SEARCH_QUERY), + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_ACCESSED), + new AttributeExtractor(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_TEXT)), + TopPrivateDomainExtractor.getInstance(), + new AttributeExtractor(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROG_NAME))); + + private final BlackboardAttribute.Type dateTimeAttributeType; + + private final String iconBase; + + private final Image image; + + @Override + public Image getFXImage() { + return image; + } + + @Override + public BlackboardAttribute.Type getDateTimeAttributeType() { + return dateTimeAttributeType; + } + + @Override + public EventTypeZoomLevel getZoomLevel() { + return EventTypeZoomLevel.SUB_TYPE; + } + + private final Function<BlackboardArtifact, String> longExtractor; + + private final Function<BlackboardArtifact, String> medExtractor; + + private final Function<BlackboardArtifact, String> shortExtractor; + + @Override + public Function<BlackboardArtifact, String> getFullExtractor() { + return longExtractor; + } + + @Override + public Function<BlackboardArtifact, String> getMedExtractor() { + return medExtractor; + } + + @Override + public Function<BlackboardArtifact, String> getShortExtractor() { + return shortExtractor; + } + + private final String displayName; + + private final BlackboardArtifact.Type artifactType; + + @Override + public String getIconBase() { + return iconBase; + } + + @Override + public BlackboardArtifact.Type getArtifactType() { + return artifactType; + } + + private WebTypes(String displayName, String iconBase, BlackboardArtifact.Type artifactType, + BlackboardAttribute.Type dateTimeAttributeType, + Function<BlackboardArtifact, String> shortExtractor, + Function<BlackboardArtifact, String> medExtractor, + Function<BlackboardArtifact, String> longExtractor) { + this.displayName = displayName; + this.iconBase = iconBase; + this.artifactType = artifactType; + this.dateTimeAttributeType = dateTimeAttributeType; + this.shortExtractor = shortExtractor; + this.medExtractor = medExtractor; + this.longExtractor = longExtractor; + this.image = new Image("org/sleuthkit/autopsy/timeline/images/" + iconBase, true); // NON-NLS + } + + @Override + public EventType getSuperType() { + return BaseTypes.WEB_ACTIVITY; + } + + @Override + public String getDisplayName() { + return displayName; + } + + @Override + public EventType getSubType(String string) { + return WebTypes.valueOf(string); + } + + @Override + public List<? extends EventType> getSubTypes() { + return Collections.emptyList(); + } + + private static class TopPrivateDomainExtractor extends AttributeExtractor { + + final private static TopPrivateDomainExtractor instance = new TopPrivateDomainExtractor(); + + static TopPrivateDomainExtractor getInstance() { + return instance; + } + + @Override + public String apply(BlackboardArtifact artf) { + String domainString = StringUtils.substringBefore(super.apply(artf), "/"); + if (InternetDomainName.isValid(domainString)) { + InternetDomainName domain = InternetDomainName.from(domainString); + return (domain.isUnderPublicSuffix()) + ? domain.topPrivateDomain().toString() + : domain.toString(); + } else { + return domainString; + } + } + + TopPrivateDomainExtractor() { + super(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DOMAIN)); + } + } +} diff --git a/bindings/java/src/org/sleuthkit/datamodel/timeline/ZoomParams.java b/bindings/java/src/org/sleuthkit/datamodel/timeline/ZoomParams.java new file mode 100644 index 0000000000000000000000000000000000000000..1ccbfc3811d998dfe689baa67c603d3a8ef030fc --- /dev/null +++ b/bindings/java/src/org/sleuthkit/datamodel/timeline/ZoomParams.java @@ -0,0 +1,136 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2014 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.timeline; + +import java.util.Objects; +import org.joda.time.Interval; +import org.sleuthkit.datamodel.timeline.filters.RootFilter; + +/** + * This class encapsulates all the zoom(and filter) parameters into one object + * for passing around and as a memento of the zoom/filter state. + */ +public class ZoomParams { + + private final Interval timeRange; + + private final EventTypeZoomLevel typeZoomLevel; + + private final RootFilter filter; + + private final DescriptionLoD descrLOD; + + public Interval getTimeRange() { + return timeRange; + } + + public EventTypeZoomLevel getTypeZoomLevel() { + return typeZoomLevel; + } + + public RootFilter getFilter() { + return filter; + } + + public DescriptionLoD getDescriptionLOD() { + return descrLOD; + } + + public ZoomParams(Interval timeRange, EventTypeZoomLevel zoomLevel, RootFilter filter, DescriptionLoD descrLOD) { + this.timeRange = timeRange; + this.typeZoomLevel = zoomLevel; + this.filter = filter; + this.descrLOD = descrLOD; + } + + public ZoomParams withTimeAndType(Interval timeRange, EventTypeZoomLevel zoomLevel) { + return new ZoomParams(timeRange, zoomLevel, filter, descrLOD); + } + + public ZoomParams withTypeZoomLevel(EventTypeZoomLevel zoomLevel) { + return new ZoomParams(timeRange, zoomLevel, filter, descrLOD); + } + + public ZoomParams withTimeRange(Interval timeRange) { + return new ZoomParams(timeRange, typeZoomLevel, filter, descrLOD); + } + + public ZoomParams withDescrLOD(DescriptionLoD descrLOD) { + return new ZoomParams(timeRange, typeZoomLevel, filter, descrLOD); + } + + public ZoomParams withFilter(RootFilter filter) { + return new ZoomParams(timeRange, typeZoomLevel, filter, descrLOD); + } + + public boolean hasFilter(RootFilter filterSet) { + return this.filter.equals(filterSet); + } + + public boolean hasTypeZoomLevel(EventTypeZoomLevel typeZoom) { + return this.typeZoomLevel.equals(typeZoom); + } + + public boolean hasTimeRange(Interval timeRange) { + return this.timeRange == null ? false : this.timeRange.equals(timeRange); + } + + public boolean hasDescrLOD(DescriptionLoD newLOD) { + return this.descrLOD.equals(newLOD); + } + + @Override + public int hashCode() { + int hash = 3; + hash = 97 * hash + Objects.hashCode(this.timeRange.getStartMillis()); + hash = 97 * hash + Objects.hashCode(this.timeRange.getEndMillis()); + hash = 97 * hash + Objects.hashCode(this.typeZoomLevel); + hash = 97 * hash + Objects.hashCode(this.filter.isSelected()); + hash = 97 * hash + Objects.hashCode(this.descrLOD); + + return hash; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final ZoomParams other = (ZoomParams) obj; + if (!Objects.equals(this.timeRange, other.timeRange)) { + return false; + } + if (this.typeZoomLevel != other.typeZoomLevel) { + return false; + } + if (this.filter.equals(other.filter) == false) { + return false; + } + return this.descrLOD == other.descrLOD; + } + + @Override + public String toString() { + return "ZoomParams{" + "timeRange=" + timeRange + ", typeZoomLevel=" + typeZoomLevel + ", filter=" + filter + ", descrLOD=" + descrLOD + '}'; //NON-NLS + } + +} diff --git a/bindings/java/src/org/sleuthkit/datamodel/timeline/filters/AbstractFilter.java b/bindings/java/src/org/sleuthkit/datamodel/timeline/filters/AbstractFilter.java new file mode 100644 index 0000000000000000000000000000000000000000..ee5506b3a1a6c956674b1198c93596a1fbe0250e --- /dev/null +++ b/bindings/java/src/org/sleuthkit/datamodel/timeline/filters/AbstractFilter.java @@ -0,0 +1,77 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2014 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.timeline.filters; + +import javafx.beans.binding.Bindings; +import javafx.beans.binding.BooleanBinding; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.value.ObservableBooleanValue; + +/** + * Base implementation of a {@link Filter}. Implements active property. + * + */ +public abstract class AbstractFilter implements Filter { + + private final SimpleBooleanProperty selected = new SimpleBooleanProperty(true); + private final SimpleBooleanProperty disabled = new SimpleBooleanProperty(false); + private final BooleanBinding activeProperty = Bindings.and(selected, disabled.not()); + + @Override + public SimpleBooleanProperty selectedProperty() { + return selected; + } + + @Override + public ObservableBooleanValue disabledProperty() { + return disabled; + } + + @Override + public void setSelected(Boolean act) { + selected.set(act); + } + + @Override + public boolean isSelected() { + return selected.get(); + } + + @Override + public void setDisabled(Boolean act) { + disabled.set(act); + } + + @Override + public boolean isDisabled() { + return disabledProperty().get(); + } + + + + @Override + public boolean isActive() { + return activeProperty().get(); + } + + @Override + public BooleanBinding activeProperty() { + return activeProperty; + } +} diff --git a/bindings/java/src/org/sleuthkit/datamodel/timeline/filters/Bundle.properties b/bindings/java/src/org/sleuthkit/datamodel/timeline/filters/Bundle.properties new file mode 100644 index 0000000000000000000000000000000000000000..d62fb3444484e90aaac13d542a7193b197511727 --- /dev/null +++ b/bindings/java/src/org/sleuthkit/datamodel/timeline/filters/Bundle.properties @@ -0,0 +1,10 @@ +DataSourcesFilter.displayName.text=Data Source +DescriptionFilter.mode.exclude=Exclude +DescriptionFilter.mode.include=Include +hashHitsFilter.displayName.text=Hash Sets +hideKnownFilter.displayName.text=Hide Known Files +# {0} - sub filter displaynames +IntersectionFilter.displayName.text=Intersection{0} +tagsFilter.displayName.text=Tags +TextFilter.displayName.text=Text Filter +TypeFilter.displayName.text=Event Type diff --git a/bindings/java/src/org/sleuthkit/datamodel/timeline/filters/BundleUtils.java b/bindings/java/src/org/sleuthkit/datamodel/timeline/filters/BundleUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..4636e5a1747b1f40ecb7dfa1da21277c52a0b321 --- /dev/null +++ b/bindings/java/src/org/sleuthkit/datamodel/timeline/filters/BundleUtils.java @@ -0,0 +1,15 @@ +package org.sleuthkit.datamodel.timeline.filters; + + +import org.sleuthkit.datamodel.timeline.*; +import java.util.ResourceBundle; + + +class BundleUtils { + + private static final ResourceBundle BUNDLE = ResourceBundle.getBundle("org.sleuthkit.datamodel.timeline.filters.Bundle"); + + static ResourceBundle getBundle() { + return BUNDLE; + } +} diff --git a/bindings/java/src/org/sleuthkit/datamodel/timeline/filters/Bundle_ja.properties b/bindings/java/src/org/sleuthkit/datamodel/timeline/filters/Bundle_ja.properties new file mode 100644 index 0000000000000000000000000000000000000000..00dca873f4e135175a7412f80e9b7fd25a445ee0 --- /dev/null +++ b/bindings/java/src/org/sleuthkit/datamodel/timeline/filters/Bundle_ja.properties @@ -0,0 +1,9 @@ +hideKnownFilter.displayName.text=\u65E2\u77E5\u30D5\u30A1\u30A4\u30EB\u3092\u96A0\u3059 +TextFilter.displayName.text=\u30C6\u30AD\u30B9\u30C8\u30D5\u30A3\u30EB\u30BF\u30FC +TypeFilter.displayName.text=\u30A4\u30D9\u30F3\u30C8\u30BF\u30A4\u30D7\u30D5\u30A3\u30EB\u30BF\u30FC +IntersectionFilter.displayName.text=\u30A4\u30F3\u30BF\u30FC\u30BB\u30AF\u30B7\u30E7\u30F3{0} +DataSourcesFilter.displayName.text=\u30C7\u30FC\u30BF\u30BD\u30FC\u30B9 +DescriptionFilter.mode.exclude=\u9664\u5916\u3059\u308B +DescriptionFilter.mode.include=\u542B\u3080 +hashHitsFilter.displayName.text=\u30CF\u30C3\u30B7\u30E5\u30BB\u30C3\u30C8\u306E\u30D2\u30C3\u30C8\u306E\u307F +tagsFilter.displayName.text=\u30BF\u30B0\u3055\u308C\u305F\u30A4\u30D9\u30F3\u30C8\u306E\u307F \ No newline at end of file diff --git a/bindings/java/src/org/sleuthkit/datamodel/timeline/filters/CompoundFilter.java b/bindings/java/src/org/sleuthkit/datamodel/timeline/filters/CompoundFilter.java new file mode 100644 index 0000000000000000000000000000000000000000..5ce00cd6c8456dde16a838712728a6f651575e15 --- /dev/null +++ b/bindings/java/src/org/sleuthkit/datamodel/timeline/filters/CompoundFilter.java @@ -0,0 +1,92 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2014-16 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.timeline.filters; + +import java.util.List; +import java.util.Objects; +import javafx.collections.FXCollections; +import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; + +/** + * A Filter with a collection of {@link Filter} sub-filters. If this filter is + * not active than none of its sub-filters are applied either. Concrete + * implementations can decide how to combine the sub-filters. + * + * a {@link CompoundFilter} uses listeners to enforce the following + * relationships between it and its sub-filters: if all of a compound filter's + * sub-filters become un-selected, un-select the compound filter. + */ +public abstract class CompoundFilter<SubFilterType extends Filter> extends AbstractFilter { + + /** + * the list of sub-filters that make up this filter + */ + private final ObservableList<SubFilterType> subFilters = FXCollections.observableArrayList(); + + public final ObservableList<SubFilterType> getSubFilters() { + return subFilters; + } + + /** + * construct a compound filter from a list of other filters to combine. + * + * @param subFilters + */ + public CompoundFilter(List<SubFilterType> subFilters) { + super(); + + //listen to changes in list of subfilters + this.subFilters.addListener((ListChangeListener.Change<? extends SubFilterType> change) -> { + while (change.next()) { + //add a listener to the selected property of each added subfilter + change.getAddedSubList().forEach(addedSubFilter -> { + //if a subfilter's selected property changes... + addedSubFilter.selectedProperty().addListener(selectedProperty -> { + //set this compound filter selected af any of the subfilters are selected. + setSelected(getSubFilters().parallelStream().anyMatch(Filter::isSelected)); + }); + }); + } + }); + + this.subFilters.setAll(subFilters); + } + + static <SubFilterType extends Filter> boolean areSubFiltersEqual(final CompoundFilter<SubFilterType> oneFilter, final CompoundFilter<SubFilterType> otherFilter) { + if (oneFilter.getSubFilters().size() != otherFilter.getSubFilters().size()) { + return false; + } + for (int i = 0; i < oneFilter.getSubFilters().size(); i++) { + final SubFilterType subFilter = oneFilter.getSubFilters().get(i); + final SubFilterType otherSubFilter = otherFilter.getSubFilters().get(i); + if (subFilter.equals(otherSubFilter) == false) { + return false; + } + } + return true; + } + + @Override + public int hashCode() { + int hash = 3; + hash = 61 * hash + Objects.hashCode(this.subFilters); + return hash; + } +} diff --git a/bindings/java/src/org/sleuthkit/datamodel/timeline/filters/DataSourceFilter.java b/bindings/java/src/org/sleuthkit/datamodel/timeline/filters/DataSourceFilter.java new file mode 100644 index 0000000000000000000000000000000000000000..74411ec82c9169205b83164de3c2bb3cb62af858 --- /dev/null +++ b/bindings/java/src/org/sleuthkit/datamodel/timeline/filters/DataSourceFilter.java @@ -0,0 +1,82 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2015 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.timeline.filters; + +import java.util.Objects; + +/** + * Filter for an individual datasource + */ +public class DataSourceFilter extends AbstractFilter { + + private final String dataSourceName; + private final long dataSourceID; + + public long getDataSourceID() { + return dataSourceID; + } + + public String getDataSourceName() { + return dataSourceName; + } + + public DataSourceFilter(String dataSourceName, long dataSourceID) { + this.dataSourceName = dataSourceName; + this.dataSourceID = dataSourceID; + } + + @Override + synchronized public DataSourceFilter copyOf() { + DataSourceFilter filterCopy = new DataSourceFilter(getDataSourceName(), getDataSourceID()); + filterCopy.setSelected(isSelected()); + filterCopy.setDisabled(isDisabled()); + return filterCopy; + } + + @Override + public String getDisplayName() { + return getDataSourceName(); + } + + @Override + public int hashCode() { + int hash = 5; + hash = 97 * hash + Objects.hashCode(this.dataSourceName); + hash = 97 * hash + (int) (this.dataSourceID ^ (this.dataSourceID >>> 32)); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final DataSourceFilter other = (DataSourceFilter) obj; + if (!Objects.equals(this.dataSourceName, other.dataSourceName)) { + return false; + } + if (this.dataSourceID != other.dataSourceID) { + return false; + } + return isSelected() == other.isSelected(); + } +} diff --git a/bindings/java/src/org/sleuthkit/datamodel/timeline/filters/DataSourcesFilter.java b/bindings/java/src/org/sleuthkit/datamodel/timeline/filters/DataSourcesFilter.java new file mode 100644 index 0000000000000000000000000000000000000000..f87b229cf44f5c4d37768e18865a9b53edc4d967 --- /dev/null +++ b/bindings/java/src/org/sleuthkit/datamodel/timeline/filters/DataSourcesFilter.java @@ -0,0 +1,94 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2015-16 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.timeline.filters; + +import java.util.function.Predicate; +import javafx.beans.binding.Bindings; +import javafx.beans.binding.BooleanBinding; +import javafx.beans.value.ObservableBooleanValue; + +/** + * union of {@link DataSourceFilter}s + */ +public class DataSourcesFilter extends UnionFilter<DataSourceFilter> { + + //keep references to the overridden properties so they don't get GC'd + private final BooleanBinding activePropertyOverride; + private final BooleanBinding disabledPropertyOverride; + + public DataSourcesFilter() { + disabledPropertyOverride = Bindings.or(super.disabledProperty(), Bindings.size(getSubFilters()).lessThanOrEqualTo(1)); + activePropertyOverride = super.activeProperty().and(Bindings.not(disabledPropertyOverride)); + } + + @Override + public DataSourcesFilter copyOf() { + final DataSourcesFilter filterCopy = new DataSourcesFilter(); + //add a copy of each subfilter + getSubFilters().forEach(dataSourceFilter -> filterCopy.addSubFilter(dataSourceFilter.copyOf())); + //these need to happen after the listeners fired by adding the subfilters + filterCopy.setSelected(isSelected()); + filterCopy.setDisabled(isDisabled()); + + return filterCopy; + } + + @Override + public String getDisplayName() { + return BundleUtils.getBundle().getString("DataSourcesFilter.displayName.text"); + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final DataSourcesFilter other = (DataSourcesFilter) obj; + + if (isActive() != other.isActive()) { + return false; + } + + return areSubFiltersEqual(this, other); + + } + + @Override + public int hashCode() { + return 9; + } + + @Override + public ObservableBooleanValue disabledProperty() { + return disabledPropertyOverride; + } + + @Override + public BooleanBinding activeProperty() { + return activePropertyOverride; + } + + @Override + Predicate<DataSourceFilter> getDuplicatePredicate(DataSourceFilter subfilter) { + return dataSourcefilter -> dataSourcefilter.getDataSourceID() == subfilter.getDataSourceID(); + } +} diff --git a/bindings/java/src/org/sleuthkit/datamodel/timeline/filters/DescriptionFilter.java b/bindings/java/src/org/sleuthkit/datamodel/timeline/filters/DescriptionFilter.java new file mode 100644 index 0000000000000000000000000000000000000000..1d4213fdc6a3755afa36e00dee66c215fc504584 --- /dev/null +++ b/bindings/java/src/org/sleuthkit/datamodel/timeline/filters/DescriptionFilter.java @@ -0,0 +1,112 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2015 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.timeline.filters; + +import java.util.Objects; +import org.sleuthkit.datamodel.timeline.DescriptionLoD; + +public class DescriptionFilter extends AbstractFilter { + + private final DescriptionLoD descriptionLoD; + private final String description; + private final FilterMode filterMode; + + public FilterMode getFilterMode() { + return filterMode; + } + + public DescriptionFilter(DescriptionLoD descriptionLoD, String description, FilterMode filterMode) { + this.descriptionLoD = descriptionLoD; + this.description = description; + this.filterMode = filterMode; + } + + @Override + public DescriptionFilter copyOf() { + DescriptionFilter filterCopy = new DescriptionFilter(getDescriptionLoD(), getDescription(), getFilterMode()); + filterCopy.setSelected(isSelected()); + filterCopy.setDisabled(isDisabled()); + return filterCopy; + } + + @Override + public String getDisplayName() { + return getDescriptionLoD().getDisplayName() + ": " + getDescription(); + } + + /** + * @return the descriptionLoD + */ + public DescriptionLoD getDescriptionLoD() { + return descriptionLoD; + } + + /** + * @return the description + */ + public String getDescription() { + return description; + } + + public enum FilterMode { + + EXCLUDE(BundleUtils.getBundle().getString("DescriptionFilter.mode.exclude")), + INCLUDE(BundleUtils.getBundle().getString("DescriptionFilter.mode.include")); + + private final String displayName; + + private FilterMode(String displayName) { + this.displayName = displayName; + } + + private String getDisplayName() { + return displayName; + } + } + + @Override + public int hashCode() { + int hash = 7; + hash = 79 * hash + Objects.hashCode(this.descriptionLoD); + hash = 79 * hash + Objects.hashCode(this.description); + hash = 79 * hash + Objects.hashCode(this.filterMode); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final DescriptionFilter other = (DescriptionFilter) obj; + if (this.descriptionLoD != other.descriptionLoD) { + return false; + } + if (!Objects.equals(this.description, other.description)) { + return false; + } + if (this.filterMode != other.filterMode) { + return false; + } + return true; + } +} diff --git a/bindings/java/src/org/sleuthkit/datamodel/timeline/filters/Filter.java b/bindings/java/src/org/sleuthkit/datamodel/timeline/filters/Filter.java new file mode 100644 index 0000000000000000000000000000000000000000..cda02768654756a480731ecc201954047ceb3117 --- /dev/null +++ b/bindings/java/src/org/sleuthkit/datamodel/timeline/filters/Filter.java @@ -0,0 +1,129 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2014-15 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.timeline.filters; + +import javafx.beans.binding.BooleanBinding; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.value.ObservableBooleanValue; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; + +/** + * Interface for Filters. Filters are given to the EventDB who interpretes them + * a appropriately for all db queries. Since the filters are primarily + * configured in the UI, this interface provides selected, disabled and active + * (selected and not disabled) properties. + */ +public interface Filter { + + /** + * get a filter that is the intersection of the given filters + * + * @param filters a set of filters to intersect + * + * @return a filter that is the intersection of the given filters + */ + public static IntersectionFilter<Filter> intersect(ObservableList<Filter> filters) { + return new IntersectionFilter<>(filters); + } + + /** + * get a filter that is the intersection of the given filters + * + * @param filters a set of filters to intersect + * + * @return a filter that is the intersection of the given filters + */ + public static IntersectionFilter<Filter> intersect(Filter[] filters) { + return intersect(FXCollections.observableArrayList(filters)); + } + + /** + * since filters have mutable state (selected/disabled/active) and are + * observed in various places, we need a mechanism to copy the current state + * to keep in the history. + * + * Concrete sub classes should implement this in a way that preserves the + * state and any sub-filters. + * + * @return a copy of this filter. + */ + Filter copyOf(); + + /** + * get the display name of this filter + * + * @return a name for this filter to show in the UI + */ + String getDisplayName(); + + /** + * is this filter selected + * + * @return true if this filter is selected + */ + boolean isSelected(); + + /** + * set this filter selected + * + * @param selected true to selecte, false to un-select + */ + void setSelected(Boolean selected); + + /** + * observable selected property + * + * @return the observable selected property for this filter + */ + SimpleBooleanProperty selectedProperty(); + + /** + * set the filter disabled + */ + void setDisabled(Boolean act); + + /** + * observable disabled property + * + * @return the observable disabled property for this filter + */ + ObservableBooleanValue disabledProperty(); + + /** + * is this filter disabled + * + * @return true if this filter is disabled + */ + boolean isDisabled(); + + /** + * is this filter active (selected and not disabled) + * + * @return true if this filter is active + */ + boolean isActive(); + + /** + * observable active property + * + * @return the observable active property for this filter + */ + BooleanBinding activeProperty(); +} diff --git a/bindings/java/src/org/sleuthkit/datamodel/timeline/filters/HashHitsFilter.java b/bindings/java/src/org/sleuthkit/datamodel/timeline/filters/HashHitsFilter.java new file mode 100644 index 0000000000000000000000000000000000000000..80caf081ef73fbd796bc7e9c964a717fcb2249a7 --- /dev/null +++ b/bindings/java/src/org/sleuthkit/datamodel/timeline/filters/HashHitsFilter.java @@ -0,0 +1,82 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2015-16 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.timeline.filters; + +import java.util.function.Predicate; +import javafx.beans.binding.Bindings; +import javafx.beans.value.ObservableBooleanValue; + +/** + * + */ +public class HashHitsFilter extends UnionFilter<HashSetFilter> { + + @Override + public String getDisplayName() { + return BundleUtils.getBundle().getString("hashHitsFilter.displayName.text"); + } + + public HashHitsFilter() { + setSelected(false); + } + + @Override + public HashHitsFilter copyOf() { + HashHitsFilter filterCopy = new HashHitsFilter(); + //add a copy of each subfilter + this.getSubFilters().forEach(hashSetFilter -> filterCopy.addSubFilter(hashSetFilter.copyOf())); + //these need to happen after the listeners fired by adding the subfilters + filterCopy.setSelected(isSelected()); + filterCopy.setDisabled(isDisabled()); + + return filterCopy; + } + + @Override + public int hashCode() { + return 7; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final HashHitsFilter other = (HashHitsFilter) obj; + + if (isActive() != other.isActive()) { + return false; + } + + return areSubFiltersEqual(this, other); + } + + @Override + public ObservableBooleanValue disabledProperty() { + return Bindings.or(super.disabledProperty(), Bindings.isEmpty(getSubFilters())); + } + + @Override + Predicate<HashSetFilter> getDuplicatePredicate(HashSetFilter subfilter) { + return hashSetFilter -> subfilter.getHashSetID() == hashSetFilter.getHashSetID(); + } +} diff --git a/bindings/java/src/org/sleuthkit/datamodel/timeline/filters/HashSetFilter.java b/bindings/java/src/org/sleuthkit/datamodel/timeline/filters/HashSetFilter.java new file mode 100644 index 0000000000000000000000000000000000000000..799da02a70cc1b7432a41ed9d96066f42dacc55e --- /dev/null +++ b/bindings/java/src/org/sleuthkit/datamodel/timeline/filters/HashSetFilter.java @@ -0,0 +1,82 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2015 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.timeline.filters; + +import java.util.Objects; + +/** + * Filter for an individual hash set + */ +public class HashSetFilter extends AbstractFilter { + + private final String hashSetName; + private final long hashSetID; + + public long getHashSetID() { + return hashSetID; + } + + public String getHashSetName() { + return hashSetName; + } + + public HashSetFilter(String hashSetName, long hashSetID) { + this.hashSetName = hashSetName; + this.hashSetID = hashSetID; + } + + @Override + synchronized public HashSetFilter copyOf() { + HashSetFilter filterCopy = new HashSetFilter(getHashSetName(), getHashSetID()); + filterCopy.setSelected(isSelected()); + filterCopy.setDisabled(isDisabled()); + return filterCopy; + } + + @Override + public String getDisplayName() { + return hashSetName; + } + + @Override + public int hashCode() { + int hash = 7; + hash = 37 * hash + Objects.hashCode(this.hashSetName); + hash = 37 * hash + (int) (this.hashSetID ^ (this.hashSetID >>> 32)); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final HashSetFilter other = (HashSetFilter) obj; + if (!Objects.equals(this.hashSetName, other.hashSetName)) { + return false; + } + if (this.hashSetID != other.hashSetID) { + return false; + } + return isSelected() == other.isSelected(); + } +} diff --git a/bindings/java/src/org/sleuthkit/datamodel/timeline/filters/HideKnownFilter.java b/bindings/java/src/org/sleuthkit/datamodel/timeline/filters/HideKnownFilter.java new file mode 100644 index 0000000000000000000000000000000000000000..68acce984ce5f7f0cb2d63bd8da5d5594ac41c1b --- /dev/null +++ b/bindings/java/src/org/sleuthkit/datamodel/timeline/filters/HideKnownFilter.java @@ -0,0 +1,61 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2014-15 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.timeline.filters; + +/** + * Filter to hide known files + */ +public class HideKnownFilter extends AbstractFilter { + + @Override + public String getDisplayName() { + return BundleUtils.getBundle().getString("hideKnownFilter.displayName.text"); + } + + public HideKnownFilter() { + super(); + selectedProperty().set(false); + } + + @Override + public HideKnownFilter copyOf() { + HideKnownFilter hideKnownFilter = new HideKnownFilter(); + hideKnownFilter.setSelected(isSelected()); + hideKnownFilter.setDisabled(isDisabled()); + return hideKnownFilter; + } + + @Override + public int hashCode() { + return 7; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final HideKnownFilter other = (HideKnownFilter) obj; + + return isSelected() == other.isSelected(); + } +} diff --git a/bindings/java/src/org/sleuthkit/datamodel/timeline/filters/IntersectionFilter.java b/bindings/java/src/org/sleuthkit/datamodel/timeline/filters/IntersectionFilter.java new file mode 100644 index 0000000000000000000000000000000000000000..35bb79a61537258313a75cead8916b74387f7aa1 --- /dev/null +++ b/bindings/java/src/org/sleuthkit/datamodel/timeline/filters/IntersectionFilter.java @@ -0,0 +1,80 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2014-15 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.timeline.filters; + +import java.util.List; +import java.util.stream.Collectors; +import javafx.collections.FXCollections; + +/** + * Intersection (And) filter + */ +public class IntersectionFilter<S extends Filter> extends CompoundFilter<S> { + + public IntersectionFilter(List<S> subFilters) { + super(subFilters); + } + + public IntersectionFilter() { + super(FXCollections.<S>observableArrayList()); + } + + @Override + public IntersectionFilter<S> copyOf() { + @SuppressWarnings("unchecked") + IntersectionFilter<S> filter = new IntersectionFilter<>( + (List<S>) this.getSubFilters().stream() + .map(Filter::copyOf) + .collect(Collectors.toList())); + filter.setSelected(isSelected()); + filter.setDisabled(isDisabled()); + return filter; + } + + @Override + public String getDisplayName() { + String collect = getSubFilters().stream() + .map(Filter::getDisplayName) + .collect(Collectors.joining(",", "[", "]")); + return BundleUtils.getBundle().getString("IntersectionFilter.displayName.text") + collect; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + @SuppressWarnings("unchecked") + final IntersectionFilter<S> other = (IntersectionFilter<S>) obj; + + if (isSelected() != other.isSelected()) { + return false; + } + + for (int i = 0; i < getSubFilters().size(); i++) { + if (getSubFilters().get(i).equals(other.getSubFilters().get(i)) == false) { + return false; + } + } + return true; + } +} diff --git a/bindings/java/src/org/sleuthkit/datamodel/timeline/filters/RootFilter.java b/bindings/java/src/org/sleuthkit/datamodel/timeline/filters/RootFilter.java new file mode 100644 index 0000000000000000000000000000000000000000..f40101d44ea995d38657345ffd7797c907208d35 --- /dev/null +++ b/bindings/java/src/org/sleuthkit/datamodel/timeline/filters/RootFilter.java @@ -0,0 +1,140 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2015-16 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.timeline.filters; + +import java.util.Set; +import java.util.stream.Collectors; +import javafx.beans.binding.BooleanBinding; +import javafx.collections.FXCollections; + +/** + * An implementation of IntersectionFilter designed to be used as the root of a + * filter tree. provides named access to specific subfilters. + */ +public class RootFilter extends IntersectionFilter<Filter> { + + private final HideKnownFilter knownFilter; + private final TagsFilter tagsFilter; + private final HashHitsFilter hashFilter; + private final TextFilter textFilter; + private final TypeFilter typeFilter; + private final DataSourcesFilter dataSourcesFilter; + + public DataSourcesFilter getDataSourcesFilter() { + return dataSourcesFilter; + } + + public TagsFilter getTagsFilter() { + return tagsFilter; + } + + public HashHitsFilter getHashHitsFilter() { + return hashFilter; + } + + public TypeFilter getTypeFilter() { + return typeFilter; + } + + public HideKnownFilter getKnownFilter() { + return knownFilter; + } + + public TextFilter getTextFilter() { + return textFilter; + } + + public RootFilter(HideKnownFilter knownFilter, TagsFilter tagsFilter, HashHitsFilter hashFilter, TextFilter textFilter, TypeFilter typeFilter, DataSourcesFilter dataSourceFilter, Set<Filter> annonymousSubFilters) { + super(FXCollections.observableArrayList( + textFilter, + knownFilter, + dataSourceFilter, tagsFilter, + hashFilter, + typeFilter + )); + this.knownFilter = knownFilter; + this.tagsFilter = tagsFilter; + this.hashFilter = hashFilter; + this.textFilter = textFilter; + this.typeFilter = typeFilter; + this.dataSourcesFilter = dataSourceFilter; + getSubFilters().addAll(annonymousSubFilters); + setSelected(Boolean.TRUE); + setDisabled(false); + } + + @Override + public RootFilter copyOf() { + Set<Filter> annonymousSubFilters = getSubFilters().stream() + .filter(subFilter -> + !(subFilter.equals(knownFilter) + || subFilter.equals(tagsFilter) + || subFilter.equals(hashFilter) + || subFilter.equals(typeFilter) + || subFilter.equals(textFilter) + || subFilter.equals(dataSourcesFilter))) + .map(Filter::copyOf) + .collect(Collectors.toSet()); + + RootFilter filter = new RootFilter( + knownFilter.copyOf(), + tagsFilter.copyOf(), + hashFilter.copyOf(), + textFilter.copyOf(), + typeFilter.copyOf(), + dataSourcesFilter.copyOf(), + annonymousSubFilters); + filter.setSelected(isSelected()); + filter.setDisabled(isDisabled()); + return filter; + } + + @Override + public int hashCode() { + return super.hashCode(); + } + + @Override + @SuppressWarnings("unchecked") + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + return areSubFiltersEqual(this, (CompoundFilter<Filter>) obj); + } + + @Override + public boolean isActive() { + return true; + } + + @Override + public BooleanBinding activeProperty() { + + return new BooleanBinding() { + @Override + protected boolean computeValue() { + return true; + } + }; + } +} diff --git a/bindings/java/src/org/sleuthkit/datamodel/timeline/filters/TagNameFilter.java b/bindings/java/src/org/sleuthkit/datamodel/timeline/filters/TagNameFilter.java new file mode 100644 index 0000000000000000000000000000000000000000..70d4d277b6eb3207328a007f6b678a41e1aeedfe --- /dev/null +++ b/bindings/java/src/org/sleuthkit/datamodel/timeline/filters/TagNameFilter.java @@ -0,0 +1,75 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2015 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.timeline.filters; + +import java.util.Objects; +import org.sleuthkit.datamodel.TagName; + +/** + * Filter for an individual TagName + */ +public class TagNameFilter extends AbstractFilter { + + private final TagName tagName; + + public TagNameFilter(TagName tagName) { + this.tagName = tagName; + setSelected(Boolean.TRUE); + } + + public TagName getTagName() { + return tagName; + } + + @Override + synchronized public TagNameFilter copyOf() { + TagNameFilter filterCopy = new TagNameFilter(getTagName()); + filterCopy.setSelected(isSelected()); + filterCopy.setDisabled(isDisabled()); + return filterCopy; + } + + @Override + public String getDisplayName() { + return tagName.getDisplayName(); + } + + @Override + public int hashCode() { + int hash = 3; + hash = 53 * hash + Objects.hashCode(this.tagName); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final TagNameFilter other = (TagNameFilter) obj; + if (!Objects.equals(this.tagName, other.tagName)) { + return false; + } + + return isSelected() == other.isSelected(); + } +} diff --git a/bindings/java/src/org/sleuthkit/datamodel/timeline/filters/TagsFilter.java b/bindings/java/src/org/sleuthkit/datamodel/timeline/filters/TagsFilter.java new file mode 100644 index 0000000000000000000000000000000000000000..ab7d6aeb6b187a0992994ecd1c2d8f7cade7e9d9 --- /dev/null +++ b/bindings/java/src/org/sleuthkit/datamodel/timeline/filters/TagsFilter.java @@ -0,0 +1,90 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2015-16 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.timeline.filters; + +import java.util.Comparator; +import java.util.function.Predicate; +import javafx.beans.binding.Bindings; +import javafx.beans.value.ObservableBooleanValue; +import org.sleuthkit.datamodel.TagName; + +/** + * Filter to show only events tag with the tagNames of the selected subfilters. + */ +public class TagsFilter extends UnionFilter<TagNameFilter> { + + @Override + public String getDisplayName() { + return BundleUtils.getBundle().getString("tagsFilter.displayName.text"); + } + + public TagsFilter() { + setSelected(false); + } + + @Override + public TagsFilter copyOf() { + TagsFilter filterCopy = new TagsFilter(); + //add a copy of each subfilter + getSubFilters().forEach(tagNameFilter -> filterCopy.addSubFilter(tagNameFilter.copyOf())); + //these need to happen after the listeners fired by adding the subfilters + filterCopy.setSelected(isSelected()); + filterCopy.setDisabled(isDisabled()); + + return filterCopy; + } + + @Override + public int hashCode() { + return 7; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final TagsFilter other = (TagsFilter) obj; + + if (isActive() != other.isActive()) { + return false; + } + + return areSubFiltersEqual(this, other); + } + + public void removeFilterForTag(TagName tagName) { + getSubFilters().removeIf(subfilter -> subfilter.getTagName().equals(tagName)); + getSubFilters().sort(Comparator.comparing(TagNameFilter::getDisplayName)); + } + + @Override + public ObservableBooleanValue disabledProperty() { + return Bindings.or(super.disabledProperty(), Bindings.isEmpty(getSubFilters())); + } + + @Override + Predicate<TagNameFilter> getDuplicatePredicate(TagNameFilter subfilter) { + return tagNameFilter -> subfilter.getTagName().equals(tagNameFilter.getTagName()); + } + +} diff --git a/bindings/java/src/org/sleuthkit/datamodel/timeline/filters/TextFilter.java b/bindings/java/src/org/sleuthkit/datamodel/timeline/filters/TextFilter.java new file mode 100644 index 0000000000000000000000000000000000000000..b00434f2c652570250c8dde006947267e8c21651 --- /dev/null +++ b/bindings/java/src/org/sleuthkit/datamodel/timeline/filters/TextFilter.java @@ -0,0 +1,86 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2013-15 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.timeline.filters; + +import java.util.Objects; +import javafx.beans.property.Property; +import javafx.beans.property.SimpleStringProperty; + +/** + * Filter for text matching + */ +public class TextFilter extends AbstractFilter { + + public TextFilter() { + } + + public TextFilter(String text) { + this.text.set(text); + } + + private final SimpleStringProperty text = new SimpleStringProperty(); + + synchronized public void setText(String text) { + this.text.set(text); + } + + @Override + public String getDisplayName() { + return BundleUtils.getBundle().getString("TextFilter.displayName.text"); + } + + synchronized public String getText() { + return text.getValue(); + } + + public Property<String> textProperty() { + return text; + } + + @Override + synchronized public TextFilter copyOf() { + TextFilter textFilter = new TextFilter(getText()); + textFilter.setSelected(isSelected()); + textFilter.setDisabled(isDisabled()); + return textFilter; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final TextFilter other = (TextFilter) obj; + + if (isSelected() != other.isSelected()) { + return false; + } + return Objects.equals(text.get(), other.text.get()); + } + + @Override + public int hashCode() { + int hash = 5; + hash = 29 * hash + Objects.hashCode(this.text.get()); + return hash; + } +} diff --git a/bindings/java/src/org/sleuthkit/datamodel/timeline/filters/TypeFilter.java b/bindings/java/src/org/sleuthkit/datamodel/timeline/filters/TypeFilter.java new file mode 100644 index 0000000000000000000000000000000000000000..2e4c7833dbf7fd8c0dfc7790b545c8b26b892831 --- /dev/null +++ b/bindings/java/src/org/sleuthkit/datamodel/timeline/filters/TypeFilter.java @@ -0,0 +1,140 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2013-16 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.timeline.filters; + +import java.util.Comparator; +import java.util.Objects; +import java.util.function.Predicate; +import javafx.collections.FXCollections; +import javafx.scene.image.Image; +import javafx.scene.paint.Color; +import org.sleuthkit.datamodel.timeline.EventType; +import org.sleuthkit.datamodel.timeline.RootEventType; + +/** + * Event Type Filter. An instance of TypeFilter is usually a tree that parallels + * the event type hierarchy with one filter/node for each event type. + */ +public class TypeFilter extends UnionFilter<TypeFilter> { + + static private final Comparator<TypeFilter> comparator = Comparator.comparing(TypeFilter::getEventType, EventType.getComparator()); + + /** + * the event type this filter passes + */ + private final EventType eventType; + + /** + * private constructor that enables non recursive/tree construction of the + * filter hierarchy for use in {@link TypeFilter#copyOf()}. + * + * @param et the event type this filter passes + * @param recursive true if subfilters should be added for each subtype. + * False if no subfilters should be added. + */ + private TypeFilter(EventType et, boolean recursive) { + super(FXCollections.observableArrayList()); + this.eventType = et; + + if (recursive) { // add subfilters for each subtype + for (EventType subType : et.getSubTypes()) { + addSubFilter(new TypeFilter(subType), comparator); + } + } + } + + /** + * public constructor. creates a subfilter for each subtype of the given + * event type + * + * @param et the event type this filter will pass + */ + public TypeFilter(EventType et) { + this(et, true); + } + + public EventType getEventType() { + return eventType; + } + + @Override + public String getDisplayName() { + return (eventType == RootEventType.getInstance()) + ? BundleUtils.getBundle().getString("TypeFilter.displayName.text") + : eventType.getDisplayName(); + } + + /** + * @return a color to use in GUI components representing this filter + */ + public Color getColor() { + return eventType.getColor(); + } + + /** + * @return an image to use in GUI components representing this filter + */ + public Image getFXImage() { + return eventType.getFXImage(); + } + + @Override + public TypeFilter copyOf() { + //make a nonrecursive copy of this filter + final TypeFilter filterCopy = new TypeFilter(eventType, false); + //add a copy of each subfilter + getSubFilters().forEach(typeFilter -> filterCopy.addSubFilter(typeFilter.copyOf(), comparator)); + //these need to happen after the listeners fired by adding the subfilters + filterCopy.setSelected(isSelected()); + filterCopy.setDisabled(isDisabled()); + return filterCopy; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final TypeFilter other = (TypeFilter) obj; + + if (isActive() != other.isActive()) { + return false; + } + + if (this.eventType != other.eventType) { + return false; + } + return areSubFiltersEqual(this, other); + } + + @Override + public int hashCode() { + int hash = 7; + hash = 67 * hash + Objects.hashCode(this.eventType); + return hash; + } + + @Override + Predicate<TypeFilter> getDuplicatePredicate(TypeFilter subfilter) { + return t -> subfilter.getEventType().equals(t.eventType); + } +} diff --git a/bindings/java/src/org/sleuthkit/datamodel/timeline/filters/UnionFilter.java b/bindings/java/src/org/sleuthkit/datamodel/timeline/filters/UnionFilter.java new file mode 100644 index 0000000000000000000000000000000000000000..5faeb153a6a60529098e90a573b73eaadf6a8d7b --- /dev/null +++ b/bindings/java/src/org/sleuthkit/datamodel/timeline/filters/UnionFilter.java @@ -0,0 +1,52 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2014-16 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.timeline.filters; + +import java.util.Comparator; +import java.util.function.Predicate; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; + +/** + * Union(or) filter + */ +abstract public class UnionFilter<SubFilterType extends Filter> extends CompoundFilter<SubFilterType> { + + public UnionFilter(ObservableList<SubFilterType> subFilters) { + super(subFilters); + } + + public UnionFilter() { + super(FXCollections.<SubFilterType>observableArrayList()); + } + + abstract Predicate<SubFilterType> getDuplicatePredicate(SubFilterType subfilter); + + public void addSubFilter(SubFilterType subfilter) { + addSubFilter(subfilter, Comparator.comparing(SubFilterType::getDisplayName)); + } + + protected void addSubFilter(SubFilterType subfilter, Comparator<SubFilterType> comparator) { + Predicate<SubFilterType> duplicatePredicate = getDuplicatePredicate(subfilter); + if (getSubFilters().stream().anyMatch(duplicatePredicate) == false) { + getSubFilters().add(subfilter); + } + getSubFilters().sort(comparator); + } +}