Autopsy 4.23.0
Graphical digital forensics platform for The Sleuth Kit and other tools.
TskQueryService.java
Go to the documentation of this file.
1/*
2 * Autopsy Forensic Browser
3 *
4 * Copyright 2024 Basis Technology Corp.
5 * Contact: carrier <at> sleuthkit <dot> org
6 *
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
10 *
11 * http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 */
19package org.sleuthkit.autopsy.mcp;
20
21import com.fasterxml.jackson.databind.JsonNode;
22import org.joda.time.DateTimeZone;
23import org.joda.time.Interval;
24import org.sleuthkit.datamodel.*;
25
26import java.nio.ByteBuffer;
27import java.nio.CharBuffer;
28import java.nio.charset.CharsetDecoder;
29import java.nio.charset.CodingErrorAction;
30import java.nio.charset.StandardCharsets;
31import java.time.Instant;
32import java.time.ZoneOffset;
33import java.time.format.DateTimeFormatter;
34import java.util.ArrayList;
35import java.util.Collections;
36import java.util.LinkedHashMap;
37import java.util.List;
38import java.util.Map;
39
45class TskQueryService {
46
47 private final SleuthkitCase skCase;
48 private final String caseName;
49
50 TskQueryService(SleuthkitCase skCase, String caseName) {
51 this.skCase = skCase;
52 this.caseName = caseName;
53 }
54
55 String getCaseName() {
56 return caseName;
57 }
58
59 // -------------------------------------------------------------------------
60 // Tool definitions (what Claude sees)
61 // -------------------------------------------------------------------------
62
66 List<Map<String, Object>> listTools() {
67 return List.of(
68 tool("get_server_status",
69 "Returns the status of the Autopsy MCP server and whether a case is currently open. " +
70 "Call this tool first: it confirms that the Autopsy MCP server is running and tells " +
71 "you whether a case is open. If caseOpen is false, no other tools will work until the " +
72 "examiner opens a case in Autopsy. If caseOpen is true, caseName contains the name of " +
73 "the open case and all other tools are available.",
74 Map.of()),
75
76 toolWithNote("get_case_summary",
77 "Get a summary of the currently open case including name, " +
78 "data sources, file count, and artifact count. Call this first " +
79 "for any general question about the case. Returns a message " +
80 "instead of an error if no case is currently open.",
81 Map.of()),
82
83 toolWithNote("query_files",
84 "Search for files in the current case. All parameters optional. " +
85 "Returns full file metadata matching Autopsy's UI columns: name, path, " +
86 "timestamps (modified/changed/accessed/created), size, flags, known status, " +
87 "MIME type, extension, hashes (MD5/SHA-256/SHA-1), and file attributes. " +
88 "Limit defaults to 50.",
89 Map.ofEntries(
90 Map.entry("nameContains", param("string", "Filter by filename substring (case-insensitive)")),
91 Map.entry("extension", param("string", "Filter by file extension without dot, e.g. exe, jpg, pdf (case-insensitive)")),
92 Map.entry("mimeType", param("string", "Filter by MIME type e.g. image/jpeg or image/*")),
93 Map.entry("minSize", param("integer", "Minimum file size in bytes")),
94 Map.entry("maxSize", param("integer", "Maximum file size in bytes")),
95 Map.entry("modifiedAfter", param("string", "ISO 8601 date — files with mtime after this date")),
96 Map.entry("modifiedBefore", param("string", "ISO 8601 date — files with mtime before this date")),
97 Map.entry("createdAfter", param("string", "ISO 8601 date — files with crtime (birth time) after this date")),
98 Map.entry("createdBefore", param("string", "ISO 8601 date — files with crtime (birth time) before this date")),
99 Map.entry("pathContains", param("string", "Filter by parent path substring (case-insensitive)")),
100 Map.entry("isDirectory", param("boolean", "true to return only directories, false for regular files only")),
101 Map.entry("allocated", param("boolean", "true for allocated files only, false for unallocated only")),
102 Map.entry("knownState", param("string", "Filter by known status: UNKNOWN, KNOWN, or NOTABLE")),
103 Map.entry("md5", param("string", "Filter by exact MD5 hash (hex string)")),
104 Map.entry("sha256", param("string", "Filter by exact SHA-256 hash (hex string)")),
105 Map.entry("limit", param("integer", "Max results, default 50, max 500")),
106 Map.entry("orderBy", param("string", "Sort order: size_desc, size_asc, name_asc, name_desc, modified_desc, modified_asc, created_desc, created_asc"))
107 )),
108
109 toolWithNote("query_data_artifacts",
110 "Search for data artifacts in the current case. Data artifacts represent facts " +
111 "extracted from the data — browser history, messages, contacts, installed programs, " +
112 "GPS locations, etc. Use artifactType to filter by type. " +
113 "Available types: " +
114 "TSK_ACCOUNT (credit card or communications account; attr: TSK_ACCOUNT_TYPE, TSK_ID, TSK_CARD_NUMBER), " +
115 "TSK_ASSOCIATED_OBJECT (back-link to referencing artifact; attr: TSK_ASSOCIATED_ARTIFACT), " +
116 "TSK_BACKUP_EVENT (system/app/file backup; attr: TSK_DATETIME_START, TSK_DATETIME_END), " +
117 "TSK_BLUETOOTH_ADAPTER (BT adapter; attr: TSK_MAC_ADDRESS, TSK_NAME, TSK_DATETIME, TSK_DEVICE_ID), " +
118 "TSK_BLUETOOTH_PAIRING (BT pairing; attr: TSK_DEVICE_NAME, TSK_DATETIME, TSK_MAC_ADDRESS, TSK_DEVICE_ID, TSK_DATETIME_ACCESSED), " +
119 "TSK_CALENDAR_ENTRY (calendar event; attr: TSK_CALENDAR_ENTRY_TYPE, TSK_DATETIME_START, TSK_DESCRIPTION, TSK_LOCATION, TSK_DATETIME_END), " +
120 "TSK_CALLLOG (call record; attr: TSK_PHONE_NUMBER, TSK_PHONE_NUMBER_FROM, TSK_PHONE_NUMBER_TO, TSK_DATETIME_START, TSK_DATETIME_END, TSK_DIRECTION, TSK_NAME), " +
121 "TSK_CLIPBOARD_CONTENT (clipboard data; attr: TSK_TEXT), " +
122 "TSK_CONTACT (contact book entry; attr: TSK_NAME, TSK_EMAIL, TSK_PHONE_NUMBER, TSK_ORGANIZATION, TSK_URL), " +
123 "TSK_DELETED_PROG (deleted program; attr: TSK_DATETIME, TSK_PROG_NAME, TSK_PATH), " +
124 "TSK_DEVICE_ATTACHED (physically attached device e.g. USB; attr: TSK_DEVICE_ID, TSK_DATETIME, TSK_DEVICE_MAKE, TSK_DEVICE_MODEL, TSK_MAC_ADDRESS), " +
125 "TSK_DEVICE_INFO (device identifiers; attr: TSK_IMEI, TSK_ICCID, TSK_IMSI), " +
126 "TSK_EMAIL_MSG (email message; attr: TSK_EMAIL_FROM, TSK_EMAIL_TO, TSK_EMAIL_CC, TSK_EMAIL_BCC, TSK_SUBJECT, TSK_DATETIME_SENT, TSK_DATETIME_RCVD, TSK_EMAIL_CONTENT_PLAIN, TSK_HEADERS, TSK_MSG_ID, TSK_THREAD_ID), " +
127 "TSK_EXTRACTED_TEXT (text extracted from content; attr: TSK_TEXT), " +
128 "TSK_GEN_INFO (generic per-file info; attr: TSK_HASH_PHOTODNA), " +
129 "TSK_GPS_AREA (GPS area outline; attr: TSK_GEO_WAYPOINTS, TSK_LOCATION, TSK_NAME, TSK_PROG_NAME), " +
130 "TSK_GPS_BOOKMARK (saved GPS waypoint; attr: TSK_GEO_LATITUDE, TSK_GEO_LONGITUDE, TSK_GEO_ALTITUDE, TSK_DATETIME, TSK_LOCATION, TSK_NAME, TSK_PROG_NAME), " +
131 "TSK_GPS_LAST_KNOWN_LOCATION (last known GPS location; attr: TSK_GEO_LATITUDE, TSK_GEO_LONGITUDE, TSK_GEO_ALTITUDE, TSK_DATETIME, TSK_LOCATION, TSK_NAME), " +
132 "TSK_GPS_ROUTE (GPS route; attr: TSK_GEO_WAYPOINTS, TSK_DATETIME, TSK_LOCATION, TSK_NAME, TSK_PROG_NAME), " +
133 "TSK_GPS_SEARCH (GPS location that was searched; attr: TSK_GEO_LATITUDE, TSK_GEO_LONGITUDE, TSK_GEO_ALTITUDE, TSK_DATETIME, TSK_LOCATION, TSK_NAME), " +
134 "TSK_GPS_TRACK (GPS track path; attr: TSK_GEO_TRACKPOINTS, TSK_NAME, TSK_PROG_NAME), " +
135 "TSK_INSTALLED_PROG (installed program; attr: TSK_PROG_NAME, TSK_DATETIME, TSK_PATH, TSK_VERSION, TSK_PERMISSIONS), " +
136 "TSK_MESSAGE (chat/SMS message; attr: TSK_TEXT, TSK_MESSAGE_TYPE, TSK_DATETIME, TSK_DIRECTION, TSK_PHONE_NUMBER_FROM, TSK_PHONE_NUMBER_TO, TSK_READ_STATUS, TSK_SUBJECT, TSK_THREAD_ID), " +
137 "TSK_METADATA (document metadata; attr: TSK_DATETIME_CREATED, TSK_DATETIME_MODIFIED, TSK_DESCRIPTION, TSK_OWNER, TSK_ORGANIZATION, TSK_PROG_NAME, TSK_VERSION, TSK_LAST_PRINTED_DATETIME, TSK_USER_ID), " +
138 "TSK_OS_INFO (OS details; attr: TSK_PROG_NAME, TSK_VERSION, TSK_DATETIME, TSK_DOMAIN, TSK_OWNER, TSK_ORGANIZATION, TSK_PATH, TSK_PROCESSOR_ARCHITECTURE, TSK_NAME, TSK_PRODUCT_ID, TSK_TEMP_DIR), " +
139 "TSK_PROG_NOTIFICATIONS (app notifications; attr: TSK_DATETIME, TSK_PROG_NAME, TSK_TITLE, TSK_VALUE), " +
140 "TSK_PROG_RUN (program execution; attr: TSK_PROG_NAME, TSK_DATETIME, TSK_COUNT, TSK_USER_NAME, TSK_PATH), " +
141 "TSK_RECENT_OBJECT (recently accessed item; attr: TSK_PATH, TSK_DATETIME_ACCESSED, TSK_PROG_NAME, TSK_NAME, TSK_VALUE), " +
142 "TSK_REMOTE_DRIVE (remote/mapped drive; attr: TSK_REMOTE_PATH, TSK_LOCAL_PATH), " +
143 "TSK_SCREEN_SHOTS (screenshot; attr: TSK_DATETIME, TSK_PROG_NAME, TSK_PATH), " +
144 "TSK_SERVICE_ACCOUNT (app/web account; attr: TSK_PROG_NAME, TSK_USER_ID, TSK_USER_NAME, TSK_DOMAIN, TSK_URL, TSK_DATETIME_CREATED, TSK_CATEGORY, TSK_PASSWORD), " +
145 "TSK_SIM_ATTACHED (SIM card; attr: TSK_ICCID, TSK_IMSI), " +
146 "TSK_SPEED_DIAL_ENTRY (speed dial; attr: TSK_PHONE_NUMBER, TSK_NAME_PERSON, TSK_SHORTCUT), " +
147 "TSK_TL_EVENT (timeline event; attr: TSK_TL_EVENT_TYPE, TSK_DATETIME, TSK_DESCRIPTION), " +
148 "TSK_USER_DEVICE_EVENT (device activity e.g. lock/unlock; attr: TSK_DATETIME_START, TSK_ACTIVITY_TYPE, TSK_DATETIME_END, TSK_PROG_NAME), " +
149 "TSK_WEB_BOOKMARK (browser bookmark; attr: TSK_URL, TSK_DOMAIN, TSK_PROG_NAME, TSK_NAME, TSK_TITLE, TSK_DATETIME_CREATED, TSK_USER_NAME, TSK_COMMENT), " +
150 "TSK_WEB_CACHE (web cache entry; attr: TSK_URL, TSK_PATH, TSK_DOMAIN, TSK_DATETIME_CREATED, TSK_HEADERS), " +
151 "TSK_WEB_COOKIE (web cookie; attr: TSK_URL, TSK_NAME, TSK_VALUE, TSK_DOMAIN, TSK_DATETIME_CREATED, TSK_DATETIME_ACCESSED, TSK_DATETIME_END, TSK_PROG_NAME, TSK_USER_NAME), " +
152 "TSK_WEB_DOWNLOAD (web download; attr: TSK_URL, TSK_DOMAIN, TSK_PATH, TSK_DATETIME_ACCESSED, TSK_PROG_NAME), " +
153 "TSK_WEB_FORM_ADDRESS (browser autofill address; attr: TSK_LOCATION, TSK_NAME_PERSON, TSK_EMAIL, TSK_PHONE_NUMBER, TSK_DATETIME_ACCESSED), " +
154 "TSK_WEB_FORM_AUTOFILL (browser autofill field; attr: TSK_NAME, TSK_VALUE, TSK_DATETIME_CREATED, TSK_DATETIME_ACCESSED, TSK_PROG_NAME), " +
155 "TSK_WEB_HISTORY (browser history; attr: TSK_URL, TSK_DOMAIN, TSK_TITLE, TSK_DATETIME_ACCESSED, TSK_REFERRER, TSK_URL_DECODED, TSK_USER_NAME, TSK_PROG_NAME), " +
156 "TSK_WEB_SEARCH_QUERY (web search; attr: TSK_TEXT, TSK_DOMAIN, TSK_DATETIME_ACCESSED, TSK_PROG_NAME), " +
157 "TSK_WIFI_NETWORK (WiFi network; attr: TSK_SSID, TSK_DATETIME, TSK_DEVICE_ID, TSK_MAC_ADDRESS), " +
158 "TSK_WIFI_NETWORK_ADAPTER (WiFi adapter; attr: TSK_MAC_ADDRESS).",
159 Map.of(
160 "artifactType", param("string", "TSK artifact type name e.g. TSK_WEB_HISTORY"),
161 "attributeType", param("string", "Filter by attribute type name e.g. TSK_URL"),
162 "attributeValue", param("string", "Filter by attribute value substring"),
163 "dataSourceId", param("integer", "Object ID of the data source to limit results to (from query_data_sources objectId field)"),
164 "limit", param("integer", "Max results, default 50, max 500")
165 )),
166
167 toolWithNote("query_analysis_results",
168 "Search for analysis results in the current case. Analysis results are conclusions " +
169 "drawn by ingest modules — hash hits, keyword hits, encryption detection, EXIF data, " +
170 "etc. Each result includes a score (significance + priority) indicating how notable " +
171 "the finding is. Significance values: NOTABLE, LIKELY_NOTABLE, LIKELY_NONE, NONE, UNKNOWN. " +
172 "Use artifactType to filter by type. Available types: " +
173 "TSK_DATA_SOURCE_USAGE (how data source was used e.g. OS Drive; attr: TSK_DESCRIPTION), " +
174 "TSK_ENCRYPTION_DETECTED (encrypted content; attr: TSK_COMMENT), " +
175 "TSK_ENCRYPTION_SUSPECTED (likely encrypted content; attr: TSK_COMMENT), " +
176 "TSK_EXT_MISMATCH_DETECTED (file extension does not match MIME type), " +
177 "TSK_FACE_DETECTED (human face detected in media), " +
178 "TSK_HASHSET_HIT (MD5 matches a known hashset; attr: TSK_SET_NAME, TSK_COMMENT), " +
179 "TSK_INTERESTING_ITEM (matches an interesting items rule; attr: TSK_SET_NAME, TSK_COMMENT, TSK_CATEGORY), " +
180 "TSK_INTERESTING_ARTIFACT_HIT (artifact matches an interesting items rule; attr: TSK_SET_NAME, TSK_COMMENT), " +
181 "TSK_INTERESTING_FILE_HIT (file matches an interesting items rule; attr: TSK_SET_NAME, TSK_COMMENT), " +
182 "TSK_KEYWORD_HIT (keyword search match; attr: TSK_KEYWORD, TSK_KEYWORD_SEARCH_TYPE, TSK_SET_NAME, TSK_KEYWORD_PREVIEW), " +
183 "TSK_MALWARE (malware detected), " +
184 "TSK_METADATA_EXIF (EXIF metadata from image; attr: TSK_DATETIME_CREATED, TSK_DEVICE_MAKE, TSK_DEVICE_MODEL, TSK_GEO_LATITUDE, TSK_GEO_LONGITUDE, TSK_GEO_ALTITUDE), " +
185 "TSK_OBJECT_DETECTED (object detected in media by CV; attr: TSK_COMMENT), " +
186 "TSK_PREVIOUSLY_NOTABLE (previously tagged Notable in another case; attr: TSK_CORRELATION_TYPE, TSK_CORRELATION_VALUE, TSK_OTHER_CASES), " +
187 "TSK_PREVIOUSLY_SEEN (seen in another case; attr: TSK_CORRELATION_TYPE, TSK_CORRELATION_VALUE, TSK_OTHER_CASES), " +
188 "TSK_PREVIOUSLY_UNSEEN (not seen before; attr: TSK_CORRELATION_TYPE, TSK_CORRELATION_VALUE), " +
189 "TSK_USER_CONTENT_SUSPECTED (likely user-generated content; attr: TSK_COMMENT), " +
190 "TSK_VERIFICATION_FAILED (hash/integrity verification failed; attr: TSK_COMMENT), " +
191 "TSK_WEB_ACCOUNT_TYPE (web account type classification; attr: TSK_DOMAIN, TSK_TEXT, TSK_URL), " +
192 "TSK_WEB_CATEGORIZATION (web host category e.g. Web Email; attr: TSK_NAME, TSK_DOMAIN, TSK_HOST), " +
193 "TSK_YARA_HIT (YARA rule match; attr: TSK_RULE, TSK_SET_NAME).",
194 Map.of(
195 "artifactType", param("string", "TSK artifact type name e.g. TSK_KEYWORD_HIT"),
196 "attributeType", param("string", "Filter by attribute type name e.g. TSK_KEYWORD"),
197 "attributeValue", param("string", "Filter by attribute value substring"),
198 "dataSourceId", param("integer", "Object ID of the data source to limit results to (from query_data_sources objectId field)"),
199 "limit", param("integer", "Max results, default 50, max 500")
200 )),
201
202 toolWithNote("query_data_sources",
203 "List all data sources (disk images, logical file sets) in the current case. " +
204 "Returns objectId, name, type, size, timezone, and for disk images: image type, " +
205 "sector size, file paths, and acquisition hashes (MD5/SHA-1/SHA-256).",
206 Map.of()),
207
208 toolWithNote("get_hosts",
209 "List all hosts in the case. Each host groups one or more data sources " +
210 "that belong to the same device or machine. Use this as the top of the " +
211 "storage hierarchy before drilling into data sources.",
212 Map.of()),
213
214 toolWithNote("get_data_source_tree",
215 "Returns the full storage hierarchy for one or all data sources: " +
216 "Image → VolumeSystem → Volume → FileSystem. " +
217 "Use this to understand how a disk image is partitioned and what file systems it contains. " +
218 "Logical file set data sources (no partitions) appear as leaf nodes with no children. " +
219 "Each FileSystem entry includes type (NTFS, FAT32, ext4, etc.), offset, block size, " +
220 "block count, and inode range.",
221 Map.of(
222 "dataSourceId", param("integer", "Object ID of the data source to inspect. Omit to return all data sources.")
223 )),
224
225 toolWithNote("query_tags",
226 "Find files or artifacts that have been tagged by the examiner.",
227 Map.of(
228 "tagName", param("string", "Filter by tag name e.g. \"Notable Item\"")
229 )),
230
231 toolWithNote("query_timeline",
232 "Return timeline events in a time range, sorted by time. " +
233 "Covers all event types in a single query: file system timestamps (modified, accessed, " +
234 "changed, created) and artifact events (web history, downloads, searches, cookies, " +
235 "bookmarks, installed programs, USB devices, etc.). " +
236 "Much more efficient than querying individual artifact types when the question is " +
237 "time-driven. Use summarize_timeline first to understand what categories of activity " +
238 "exist before fetching detail. Omit startTime/endTime to span the entire case.",
239 Map.ofEntries(
240 Map.entry("startTime", param("string", "ISO 8601 start of time range (inclusive)")),
241 Map.entry("endTime", param("string", "ISO 8601 end of time range (inclusive)")),
242 Map.entry("dataSourceId", param("integer", "Object ID of the data source to limit results to (from query_data_sources objectId field)")),
243 Map.entry("textFilter", param("string", "Filter events whose description contains this substring")),
244 Map.entry("limit", param("integer", "Max results, default 100, max 1000"))
245 )),
246
247 toolWithNote("summarize_timeline",
248 "Return counts of timeline events grouped by category for a time range. " +
249 "Categories are: File System (file timestamps), Web Activity (history, downloads, " +
250 "cookies, bookmarks, searches), and Misc (installed programs, USB devices, etc.). " +
251 "Use this before query_timeline to understand the shape of activity in a period " +
252 "without fetching every event. Omit startTime/endTime to summarize the entire case.",
253 Map.of(
254 "startTime", param("string", "ISO 8601 start of time range (inclusive)"),
255 "endTime", param("string", "ISO 8601 end of time range (inclusive)"),
256 "dataSourceId", param("integer", "Object ID of the data source to limit results to (from query_data_sources objectId field)")
257 )),
258
259 toolWithNote("get_os_accounts",
260 "List OS user accounts discovered in the case. Returns SID/UID, login name, " +
261 "full name, account type, status, creation time, extended attributes " +
262 "(e.g., home directory, login script, last login), and which data sources " +
263 "the account appeared on with instance type (LAUNCHED, ACCESSED, or REFERENCED). " +
264 "Useful for identifying users, admins, and service accounts on examined systems.",
265 Map.of()),
266
267 toolWithNote("get_communications_accounts",
268 "List accounts found in communications data: email addresses, phone numbers, " +
269 "Skype/Facebook/WhatsApp/Twitter/Instagram usernames, etc. Optionally filter " +
270 "by account type. Available types: CREDIT_CARD, DEVICE, EMAIL, FACEBOOK, " +
271 "IMO, INSTAGRAM, LINE, MESSAGING_APP, PHONE, SHAREIT, SKYPE, TANGO, TEXTNOW, " +
272 "THREEMA, TWITTER, VIBER, WEBSITE, WHATSAPP, XENDER, ZAPYA. " +
273 "Returns account type, identifier, device ID, and relationship count.",
274 Map.of(
275 "accountType", param("string", "Filter by account type e.g. EMAIL, PHONE, SKYPE"),
276 "limit", param("integer", "Max results, default 100")
277 )),
278
279 toolWithNote("get_file_content",
280 "Read the text content of a file by its object ID. Returns UTF-8 text with " +
281 "undecodable bytes replaced by '?'. " +
282 "Files larger than 65536 bytes require an explicit maxBytes parameter up to 1048576 (1 MB); " +
283 "requests beyond that are rejected — use offset+maxBytes to page through larger files. " +
284 "Returns the content string, actual bytes read, file size, and whether the content was truncated.",
285 Map.of(
286 "objectId", param("integer", "Object ID of the file (from query_files objectId field)"),
287 "offset", param("integer", "Byte offset to start reading from, default 0"),
288 "maxBytes", param("integer", "Maximum bytes to read, default 65536, max 1048576")
289 ),
290 List.of("objectId")),
291
292 toolWithNote("get_account_relationships",
293 "Get communications relationships for a specific account — who it communicated " +
294 "with and how many messages/calls. Supply the accountType (e.g. EMAIL) and " +
295 "accountId (the identifier, e.g. user@example.com or +15551234567). " +
296 "Returns the matched account plus all related accounts with relationship counts.",
297 Map.of(
298 "accountType", param("string", "Account type e.g. EMAIL, PHONE (required)"),
299 "accountId", param("string", "Type-specific identifier e.g. user@example.com or +15551234567 (required)")
300 ),
301 List.of("accountType", "accountId")),
302
303 toolWithNote("get_object_children",
304 "Return the parent and children of any object in the case database by its object ID. " +
305 "All TSK objects (files, directories, artifacts, images, volume systems, volumes, " +
306 "file systems) share a common parent-child hierarchy. A file's children may include " +
307 "both derived files and blackboard artifacts. An image's children include volume systems " +
308 "and file systems. Use this to navigate the object tree starting from any known ID. " +
309 "Each child entry includes its objectId, objectType, and type-specific summary fields " +
310 "(name/path/size for files; artifactType/attributes for artifacts; fsType for file " +
311 "systems; etc.).",
312 Map.of(
313 "objectId", param("integer", "Object ID of the item whose children you want (required)")
314 ),
315 List.of("objectId")),
316
317 toolWithNote("list_reports",
318 "List all reports that have been generated for the current case. " +
319 "Returns objectId, reportName, sourceModule, path, createdTime, size, and contentType " +
320 "(text/plain or text/html) for each report. Use this before get_report_content " +
321 "to discover available report IDs and names.",
322 Map.of()),
323
324 toolWithNote("get_report_content",
325 "Read the content of a case report by its objectId (from list_reports). " +
326 "Reports may be plain text or HTML — check the contentType field. " +
327 "Files larger than 65536 bytes require an explicit maxBytes parameter up to 1048576 (1 MB); " +
328 "requests beyond that are rejected — use offset+maxBytes to page through larger reports. " +
329 "Returns objectId, reportName, path, fileSize, contentType, offset, bytesRead, " +
330 "truncated, eof, and content.",
331 Map.of(
332 "objectId", param("integer", "Object ID of the report (from list_reports objectId field)"),
333 "offset", param("integer", "Byte offset to start reading from, default 0"),
334 "maxBytes", param("integer", "Maximum bytes to read, default 65536, max 1048576")
335 ),
336 List.of("objectId"))
337 );
338 }
339
340 // -------------------------------------------------------------------------
341 // get_case_summary
342 // -------------------------------------------------------------------------
343
344 Map<String, Object> getCaseSummary() throws TskCoreException {
345 long totalFiles = skCase.countFilesWhere("1=1");
346
347 final long[] artifactCount = {0};
348 skCase.getCaseDbAccessManager().select(
349 "COUNT(*) AS cnt FROM blackboard_artifacts",
350 rs -> {
351 try {
352 if (rs.next()) {
353 artifactCount[0] = rs.getLong("cnt");
354 }
355 } catch (java.sql.SQLException ex) {
356 // leave count at 0
357 }
358 });
359
360 Map<String, Object> summary = new LinkedHashMap<>();
361 summary.put("caseName", caseName);
362 summary.put("dataSources", skCase.getDataSources().size());
363 summary.put("totalFiles", totalFiles);
364 summary.put("totalArtifacts", artifactCount[0]);
365 return summary;
366 }
367
368 // -------------------------------------------------------------------------
369 // query_files
370 // -------------------------------------------------------------------------
371
379 List<Map<String, Object>> queryFiles(JsonNode args) throws TskCoreException {
380 int limit = args.path("limit").asInt(50);
381 if (limit <= 0 || limit > 500) {
382 limit = 50;
383 }
384
385 List<String> conditions = new ArrayList<>();
386
387 // --- name ---
388 String nameContains = textOrNull(args, "nameContains");
389 if (nameContains != null) {
390 conditions.add("LOWER(name) LIKE '%" + escapeSql(nameContains.toLowerCase()) + "%'");
391 }
392
393 // --- extension ---
394 String extension = textOrNull(args, "extension");
395 if (extension != null) {
396 conditions.add("LOWER(extension) = '" + escapeSql(extension.toLowerCase()) + "'");
397 }
398
399 // --- MIME type ---
400 String mimeType = textOrNull(args, "mimeType");
401 if (mimeType != null) {
402 if (mimeType.endsWith("*")) {
403 String prefix = mimeType.substring(0, mimeType.length() - 1);
404 conditions.add("mime_type LIKE '" + escapeSql(prefix) + "%'");
405 } else {
406 conditions.add("mime_type = '" + escapeSql(mimeType) + "'");
407 }
408 }
409
410 // --- size ---
411 if (!args.path("minSize").isMissingNode()) {
412 conditions.add("size >= " + args.path("minSize").asLong());
413 }
414 if (!args.path("maxSize").isMissingNode()) {
415 conditions.add("size <= " + args.path("maxSize").asLong());
416 }
417
418 // --- mtime ---
419 if (!args.path("modifiedAfter").isMissingNode()) {
420 conditions.add("mtime >= " + parseTimeArg(args, "modifiedAfter", 0L));
421 }
422 if (!args.path("modifiedBefore").isMissingNode()) {
423 conditions.add("mtime <= " + parseTimeArg(args, "modifiedBefore", 0L));
424 }
425
426 // --- crtime (birth/creation time) ---
427 if (!args.path("createdAfter").isMissingNode()) {
428 conditions.add("crtime >= " + parseTimeArg(args, "createdAfter", 0L));
429 }
430 if (!args.path("createdBefore").isMissingNode()) {
431 conditions.add("crtime <= " + parseTimeArg(args, "createdBefore", 0L));
432 }
433
434 // --- parent path ---
435 String pathContains = textOrNull(args, "pathContains");
436 if (pathContains != null) {
437 conditions.add("LOWER(parent_path) LIKE '%" + escapeSql(pathContains.toLowerCase()) + "%'");
438 }
439
440 // --- directory vs. regular file ---
441 if (!args.path("isDirectory").isMissingNode()) {
442 boolean isDir = args.path("isDirectory").asBoolean();
443 conditions.add("dir_type = " + (isDir ? TskData.TSK_FS_NAME_TYPE_ENUM.DIR.getValue()
444 : TskData.TSK_FS_NAME_TYPE_ENUM.REG.getValue()));
445 }
446
447 // --- allocated / unallocated ---
448 if (!args.path("allocated").isMissingNode()) {
449 boolean alloc = args.path("allocated").asBoolean();
450 conditions.add("dir_flags = " + (alloc ? TskData.TSK_FS_NAME_FLAG_ENUM.ALLOC.getValue()
451 : TskData.TSK_FS_NAME_FLAG_ENUM.UNALLOC.getValue()));
452 }
453
454 // --- known state ---
455 String knownStateStr = textOrNull(args, "knownState");
456 if (knownStateStr != null) {
457 TskData.FileKnown knownVal = parseKnownState(knownStateStr);
458 if (knownVal != null) {
459 conditions.add("known = " + knownVal.getFileKnownValue());
460 }
461 }
462
463 // --- MD5 hash ---
464 String md5 = textOrNull(args, "md5");
465 if (md5 != null) {
466 conditions.add("LOWER(md5) = '" + escapeSql(md5.toLowerCase()) + "'");
467 }
468
469 // --- SHA-256 hash ---
470 String sha256 = textOrNull(args, "sha256");
471 if (sha256 != null) {
472 conditions.add("LOWER(sha256) = '" + escapeSql(sha256.toLowerCase()) + "'");
473 }
474
475 // --- orderBy ---
476 String orderBy = textOrNull(args, "orderBy");
477 String orderClause = switch (orderBy == null ? "" : orderBy.toLowerCase()) {
478 case "size_desc" -> " ORDER BY size DESC";
479 case "size_asc" -> " ORDER BY size ASC";
480 case "name_asc" -> " ORDER BY name ASC";
481 case "name_desc" -> " ORDER BY name DESC";
482 case "modified_desc" -> " ORDER BY mtime DESC";
483 case "modified_asc" -> " ORDER BY mtime ASC";
484 case "created_desc" -> " ORDER BY crtime DESC";
485 case "created_asc" -> " ORDER BY crtime ASC";
486 default -> "";
487 };
488
489 String whereClause = (conditions.isEmpty() ? "1=1" : String.join(" AND ", conditions))
490 + orderClause + " LIMIT " + limit;
491
492 List<AbstractFile> files = skCase.findAllFilesWhere(whereClause);
493
494 List<Map<String, Object>> result = new ArrayList<>(files.size());
495 for (AbstractFile f : files) {
496 result.add(buildFileItem(f));
497 }
498 return result;
499 }
500
505 private Map<String, Object> buildFileItem(AbstractFile f) {
506 Map<String, Object> item = new LinkedHashMap<>();
507
508 // Identity
509 item.put("objectId", f.getId());
510 item.put("name", f.getName());
511 try {
512 item.put("path", f.getUniquePath());
513 } catch (TskCoreException ex) {
514 item.put("path", f.getParentPath() + f.getName());
515 }
516 item.put("parentPath", f.getParentPath());
517 item.put("extension", f.getNameExtension());
518 item.put("dataSourceId", f.getDataSourceObjectId());
519
520 // Size
521 item.put("size", f.getSize());
522
523 // Timestamps (MACB)
524 item.put("modifiedTime", epochToIso(f.getMtime())); // mtime
525 item.put("accessedTime", epochToIso(f.getAtime())); // atime
526 item.put("changeTime", epochToIso(f.getCtime())); // ctime (metadata change)
527 item.put("createdTime", epochToIso(f.getCrtime())); // crtime (birth/creation)
528
529 // Type and flags
530 item.put("fileType", f.getType().name()); // FS, CARVED, DERIVED, etc.
531 item.put("dirType", f.getDirTypeAsString()); // REG, DIR, etc.
532 item.put("metaType", f.getMetaTypeAsString()); // regular, directory, etc.
533 item.put("dirFlag", f.getDirFlagAsString()); // Flags (Directory): ALLOC/UNALLOC
534 item.put("metaFlags", f.getMetaFlagsAsString()); // Flags (Metadata): ALLOC, UNALLOC, USED, etc.
535 item.put("modes", f.getModesAsString()); // Unix permissions string e.g. "-rw-r--r--"
536
537 // Classification
538 item.put("knownState", f.getKnown().name()); // UNKNOWN, KNOWN, BAD (NOTABLE)
539
540 // Content identifiers
541 item.put("mimeType", f.getMIMEType());
542 item.put("md5Hash", f.getMd5Hash());
543 item.put("sha256Hash", f.getSha256Hash());
544 item.put("sha1Hash", f.getSha1Hash());
545
546 // Ownership (Unix uid/gid and Windows owner SID)
547 item.put("uid", f.getUid());
548 item.put("gid", f.getGid());
549 item.put("ownerUid", f.getOwnerUid().orElse(null));
550
551 // Low-level metadata
552 item.put("metaAddr", f.getMetaAddr());
553 item.put("metaSeq", f.getMetaSeq());
554
555 // Extended file attributes (blackboard attributes attached to this file)
556 try {
557 List<Attribute> attrs = f.getAttributes();
558 if (!attrs.isEmpty()) {
559 List<Map<String, Object>> attrList = new ArrayList<>(attrs.size());
560 for (Attribute attr : attrs) {
561 Map<String, Object> attrMap = new LinkedHashMap<>();
562 attrMap.put("type", attr.getAttributeType().getTypeName());
563 attrMap.put("value", fileAttrValueAsObject(attr));
564 attrList.add(attrMap);
565 }
566 item.put("attributes", attrList);
567 } else {
568 item.put("attributes", Collections.emptyList());
569 }
570 } catch (TskCoreException ex) {
571 item.put("attributes", Collections.emptyList());
572 }
573
574 return item;
575 }
576
577 // -------------------------------------------------------------------------
578 // query_data_artifacts
579 // -------------------------------------------------------------------------
580
581 List<Map<String, Object>> queryDataArtifacts(JsonNode args) throws TskCoreException {
582 return queryArtifactsInternal(args, false);
583 }
584
585 // -------------------------------------------------------------------------
586 // query_analysis_results
587 // -------------------------------------------------------------------------
588
589 List<Map<String, Object>> queryAnalysisResults(JsonNode args) throws TskCoreException {
590 return queryArtifactsInternal(args, true);
591 }
592
601 private List<Map<String, Object>> queryArtifactsInternal(JsonNode args, boolean isAnalysis)
602 throws TskCoreException {
603 int limit = args.path("limit").asInt(50);
604 if (limit <= 0 || limit > 500) limit = 50;
605
606 String artifactTypeName = textOrNull(args, "artifactType");
607 String attrTypeFilter = textOrNull(args, "attributeType");
608 String attrValueFilter = textOrNull(args, "attributeValue");
609 long dataSourceId = args.path("dataSourceId").isMissingNode() ? -1
610 : args.path("dataSourceId").asLong();
611
612 // Build WHERE clause for the Blackboard query
613 StringBuilder where = new StringBuilder("1=1");
614 if (artifactTypeName != null) {
615 BlackboardArtifact.Type type;
616 try {
617 type = skCase.getBlackboard().getArtifactType(artifactTypeName);
618 } catch (TskCoreException ex) {
619 return Collections.emptyList(); // unknown type → no results
620 }
621 where.append(" AND artifacts.artifact_type_id = ").append(type.getTypeID());
622 }
623 if (dataSourceId >= 0) {
624 where.append(" AND artifacts.data_source_obj_id = ").append(dataSourceId);
625 }
626
627 // When no attribute post-filter is needed, push the limit into SQL so
628 // the database does not materialize the full result set.
629 if (attrTypeFilter == null && attrValueFilter == null) {
630 where.append(" LIMIT ").append(limit);
631 }
632
633 // Fetch from the correct sub-table
634 List<? extends BlackboardArtifact> artifacts = isAnalysis
635 ? skCase.getBlackboard().getAnalysisResultsWhere(where.toString())
636 : skCase.getBlackboard().getDataArtifactsWhere(where.toString());
637
638 // Post-filter by attribute and apply limit
639 List<Map<String, Object>> result = new ArrayList<>();
640 for (BlackboardArtifact artifact : artifacts) {
641 if (result.size() >= limit) break;
642
643 List<BlackboardAttribute> attrs = artifact.getAttributes();
644 if (attrTypeFilter != null || attrValueFilter != null) {
645 boolean matches = false;
646 for (BlackboardAttribute attr : attrs) {
647 boolean typeMatch = attrTypeFilter == null
648 || attr.getAttributeType().getTypeName().equalsIgnoreCase(attrTypeFilter);
649 boolean valueMatch = attrValueFilter == null
650 || attrValueAsString(attr).toLowerCase().contains(attrValueFilter.toLowerCase());
651 if (typeMatch && valueMatch) { matches = true; break; }
652 }
653 if (!matches) continue;
654 }
655
656 Map<String, Object> item = new LinkedHashMap<>();
657 item.put("objectId", artifact.getId());
658 item.put("sourceFileId", artifact.getObjectID());
659 item.put("artifactType", artifact.getArtifactTypeName());
660 item.put("dataSourceId", artifact.getDataSourceObjectID());
661
662 if (isAnalysis) {
663 Score score = ((AnalysisResult) artifact).getScore();
664 item.put("score", Map.of(
665 "significance", score.getSignificance().getName(),
666 "priority", score.getPriority().getName()
667 ));
668 }
669
670 List<Map<String, Object>> attrList = new ArrayList<>();
671 for (BlackboardAttribute attr : attrs) {
672 Map<String, Object> attrMap = new LinkedHashMap<>();
673 attrMap.put("type", attr.getAttributeType().getTypeName());
674 attrMap.put("value", attrValueAsObject(attr));
675 attrList.add(attrMap);
676 }
677 item.put("attributes", attrList);
678 result.add(item);
679 }
680 return result;
681 }
682
683 // -------------------------------------------------------------------------
684 // get_hosts
685 // -------------------------------------------------------------------------
686
691 List<Map<String, Object>> getHosts() throws TskCoreException {
692 List<Map<String, Object>> result = new ArrayList<>();
693 for (Host host : skCase.getHostManager().getAllHosts()) {
694 Map<String, Object> item = new LinkedHashMap<>();
695 item.put("id", host.getHostId());
696 item.put("name", host.getName());
697
698 List<Map<String, Object>> dataSources = new ArrayList<>();
699 for (DataSource ds : skCase.getHostManager().getDataSourcesForHost(host)) {
700 dataSources.add(buildDataSourceItem((Content) ds));
701 }
702 item.put("dataSources", dataSources);
703 result.add(item);
704 }
705 return result;
706 }
707
708 // -------------------------------------------------------------------------
709 // query_data_sources
710 // -------------------------------------------------------------------------
711
712 List<Map<String, Object>> queryDataSources() throws TskCoreException {
713 List<Map<String, Object>> result = new ArrayList<>();
714 for (Content ds : skCase.getDataSources()) {
715 result.add(buildDataSourceItem(ds));
716 }
717 return result;
718 }
719
724 private Map<String, Object> buildDataSourceItem(Content ds) throws TskCoreException {
725 Map<String, Object> item = new LinkedHashMap<>();
726 item.put("objectId", ds.getId());
727 item.put("name", ds.getName());
728 item.put("type", ds.getClass().getSimpleName());
729 item.put("size", ds.getSize());
730
731 if (ds instanceof DataSource) {
732 DataSource dataSource = (DataSource) ds;
733 item.put("timezone", dataSource.getTimeZone());
734 item.put("deviceId", dataSource.getDeviceId());
735 // added_date_time is always stored as Java milliseconds via new Date().getTime()
736 // (sole write site: SleuthkitCase.java INSERT_DATA_SOURCE_INFO) — divide by 1000 for epoch seconds
737 try {
738 Long addedMs = dataSource.getDateAdded();
739 item.put("addedDate", addedMs != null && addedMs > 0 ? epochToIso(addedMs / 1000) : null);
740 } catch (TskCoreException ex) {
741 item.put("addedDate", null);
742 }
743 }
744
745 if (ds instanceof Image) {
746 Image image = (Image) ds;
747 item.put("imageType", image.getType().name());
748 item.put("sectorSize", image.getSsize());
749 item.put("paths", List.of(image.getPaths()));
750 try { item.put("md5", image.getMd5()); }
751 catch (TskCoreException ex) { item.put("md5", null); }
752 try { item.put("sha1", image.getSha1()); }
753 catch (TskCoreException ex) { item.put("sha1", null); }
754 try { item.put("sha256", image.getSha256()); }
755 catch (TskCoreException ex) { item.put("sha256", null); }
756 try { item.put("acquisitionDetails", image.getAcquisitionDetails()); }
757 catch (TskCoreException ex) { item.put("acquisitionDetails", null); }
758 try { item.put("acquisitionToolName", image.getAcquisitionToolName()); }
759 catch (TskCoreException ex) { item.put("acquisitionToolName", null); }
760 try { item.put("acquisitionToolVersion", image.getAcquisitionToolVersion()); }
761 catch (TskCoreException ex) { item.put("acquisitionToolVersion", null); }
762 }
763
764 return item;
765 }
766
767 // -------------------------------------------------------------------------
768 // get_data_source_tree
769 // -------------------------------------------------------------------------
770
776 Object getDataSourceTree(JsonNode args) throws TskCoreException {
777 long filterDsId = args.path("dataSourceId").isMissingNode() ? -1
778 : args.path("dataSourceId").asLong();
779
780 List<Map<String, Object>> result = new ArrayList<>();
781 for (Content ds : skCase.getDataSources()) {
782 if (filterDsId >= 0 && ds.getId() != filterDsId) {
783 continue;
784 }
785 result.add(buildDataSourceTreeNode(ds));
786 }
787
788 // If a specific ID was requested, unwrap the single result
789 if (filterDsId >= 0 && result.size() == 1) {
790 return result.get(0);
791 }
792 return result;
793 }
794
795 private Map<String, Object> buildDataSourceTreeNode(Content ds) throws TskCoreException {
796 Map<String, Object> node = buildDataSourceItem(ds);
797
798 if (ds instanceof Image) {
799 Image image = (Image) ds;
800 List<Map<String, Object>> vsNodes = new ArrayList<>();
801
802 for (VolumeSystem vs : image.getVolumeSystems()) {
803 vsNodes.add(buildVolumeSystemNode(vs));
804 }
805
806 // Images may also have file systems directly (no volume system layer)
807 List<Map<String, Object>> directFsNodes = new ArrayList<>();
808 for (FileSystem fs : image.getFileSystems()) {
809 directFsNodes.add(buildFileSystemNode(fs));
810 }
811
812 node.put("volumeSystems", vsNodes);
813 // Only include fileSystems at the image level when there is no volume system.
814 // When volume systems exist, file systems are already nested inside their volumes.
815 node.put("fileSystems", vsNodes.isEmpty() ? directFsNodes : Collections.emptyList());
816 } else {
817 // LocalFilesDataSource / other — no storage sub-structure
818 node.put("volumeSystems", Collections.emptyList());
819 node.put("fileSystems", Collections.emptyList());
820 }
821
822 return node;
823 }
824
825 private Map<String, Object> buildVolumeSystemNode(VolumeSystem vs) throws TskCoreException {
826 Map<String, Object> node = new LinkedHashMap<>();
827 node.put("objectId", vs.getId());
828 node.put("type", "VolumeSystem");
829 node.put("vsType", vs.getType().getName());
830 node.put("offset", vs.getOffset());
831 node.put("blockSize", vs.getBlockSize());
832
833 List<Map<String, Object>> volNodes = new ArrayList<>();
834 for (Volume vol : vs.getVolumes()) {
835 volNodes.add(buildVolumeNode(vol));
836 }
837 node.put("volumes", volNodes);
838 return node;
839 }
840
841 private Map<String, Object> buildVolumeNode(Volume vol) throws TskCoreException {
842 Map<String, Object> node = new LinkedHashMap<>();
843 node.put("objectId", vol.getId());
844 node.put("type", "Volume");
845 node.put("addr", vol.getAddr());
846 node.put("description", vol.getDescription());
847 node.put("startSector", vol.getStart());
848 node.put("lengthSectors", vol.getLength());
849 node.put("size", vol.getSize());
850 node.put("flags", vol.getFlagsAsString());
851
852 List<Map<String, Object>> fsNodes = new ArrayList<>();
853 for (FileSystem fs : vol.getFileSystems()) {
854 fsNodes.add(buildFileSystemNode(fs));
855 }
856 node.put("fileSystems", fsNodes);
857 return node;
858 }
859
860 private Map<String, Object> buildFileSystemNode(FileSystem fs) throws TskCoreException {
861 Map<String, Object> node = new LinkedHashMap<>();
862 node.put("objectId", fs.getId());
863 node.put("type", "FileSystem");
864 node.put("fsType", fs.getFsType().getDisplayName());
865 node.put("imageOffset", fs.getImageOffset());
866 node.put("blockSize", fs.getBlock_size());
867 node.put("blockCount", fs.getBlock_count());
868 node.put("rootInum", fs.getRoot_inum());
869 node.put("firstInum", fs.getFirst_inum());
870 node.put("lastInum", fs.getLastInum());
871 return node;
872 }
873
874 // -------------------------------------------------------------------------
875 // query_tags
876 // -------------------------------------------------------------------------
877
878 List<Map<String, Object>> queryTags(JsonNode args) throws TskCoreException {
879 String tagNameFilter = textOrNull(args, "tagName");
880
881 List<TagName> tagNames = new ArrayList<>();
882 for (TagName tn : skCase.getTagNamesInUse()) {
883 if (tagNameFilter == null || tn.getDisplayName().equalsIgnoreCase(tagNameFilter)) {
884 tagNames.add(tn);
885 }
886 }
887
888 List<Map<String, Object>> result = new ArrayList<>();
889 for (TagName tagName : tagNames) {
890 for (ContentTag tag : skCase.getContentTagsByTagName(tagName)) {
891 Map<String, Object> item = new LinkedHashMap<>();
892 item.put("tagId", tag.getId());
893 item.put("tagName", tagName.getDisplayName());
894 item.put("comment", tag.getComment());
895 item.put("itemType", "file");
896 Content content = tag.getContent();
897 item.put("objectId", content.getId());
898 item.put("itemName", content.getName());
899 result.add(item);
900 }
901 for (BlackboardArtifactTag tag : skCase.getBlackboardArtifactTagsByTagName(tagName)) {
902 Map<String, Object> item = new LinkedHashMap<>();
903 item.put("tagId", tag.getId());
904 item.put("tagName", tagName.getDisplayName());
905 item.put("comment", tag.getComment());
906 item.put("itemType", "artifact");
907 BlackboardArtifact artifact = tag.getArtifact();
908 item.put("objectId", artifact.getId());
909 item.put("itemName", artifact.getArtifactTypeName());
910 result.add(item);
911 }
912 }
913 return result;
914 }
915
916 // -------------------------------------------------------------------------
917 // get_file_content
918 // -------------------------------------------------------------------------
919
920 Map<String, Object> getFileContent(JsonNode args) throws TskCoreException {
921 long fileId = args.path("objectId").asLong(-1);
922 if (fileId < 0) {
923 throw new TskCoreException("objectId is required");
924 }
925
926 long offset = args.path("offset").asLong(0);
927 int maxBytes = args.path("maxBytes").asInt(65536);
928
929 // Cap at 1 MB; default to 64 KB
930 if (maxBytes <= 0 || maxBytes > 1_048_576) {
931 maxBytes = 65536;
932 }
933
934 AbstractFile file = skCase.getAbstractFileById(fileId);
935 long fileSize = file.getSize();
936
937 if (offset < 0) {
938 offset = 0;
939 }
940 if (offset >= fileSize) {
941 return Map.of("objectId", fileId, "fileName", file.getName(), "fileSize", fileSize,
942 "offset", offset, "bytesRead", 0, "truncated", false, "eof", true, "content", "");
943 }
944
945 long available = fileSize - offset;
946 int toRead = (int) Math.min(maxBytes, available);
947 boolean truncated = available > maxBytes;
948
949 byte[] buf = new byte[toRead];
950 int bytesRead = file.read(buf, offset, toRead);
951 if (bytesRead < toRead) {
952 buf = java.util.Arrays.copyOf(buf, Math.max(bytesRead, 0));
953 }
954
955 // Decode as UTF-8, replacing undecodable bytes with '?'
956 // REPLACE mode should never throw, but decode() declares CharacterCodingException.
957 CharsetDecoder decoder = StandardCharsets.UTF_8.newDecoder()
958 .onMalformedInput(CodingErrorAction.REPLACE)
959 .onUnmappableCharacter(CodingErrorAction.REPLACE);
960 CharBuffer chars;
961 try {
962 chars = decoder.decode(ByteBuffer.wrap(buf));
963 } catch (java.nio.charset.CharacterCodingException ex) {
964 // Cannot happen with REPLACE policy; fall back to lossy Latin-1 conversion
965 chars = CharBuffer.wrap(new String(buf, StandardCharsets.ISO_8859_1));
966 }
967
968 Map<String, Object> result = new LinkedHashMap<>();
969 result.put("objectId", fileId);
970 result.put("fileName", file.getName());
971 result.put("fileSize", fileSize);
972 result.put("offset", offset);
973 result.put("bytesRead", buf.length);
974 result.put("truncated", truncated);
975 result.put("content", chars.toString());
976 return result;
977 }
978
979 // -------------------------------------------------------------------------
980 // query_timeline
981 // -------------------------------------------------------------------------
982
983 List<Map<String, Object>> queryTimeline(JsonNode args) throws TskCoreException {
984 int limit = args.path("limit").asInt(100);
985 if (limit <= 0 || limit > 1000) {
986 limit = 100;
987 }
988
989 TimelineManager tm = skCase.getTimelineManager();
990 long startEpoch = parseTimeArg(args, "startTime", tm.getMinEventTime());
991 long endEpoch = parseTimeArg(args, "endTime", tm.getMaxEventTime());
992
993 Interval interval = new Interval(startEpoch * 1000L, (endEpoch + 1) * 1000L, DateTimeZone.UTC);
994 TimelineFilter.RootFilter filter = buildTimelineFilter(args);
995
996 // TimelineManager.getEvents() has no streaming or limit overload — it
997 // materializes the full result set. The limit is applied in Java below.
998 // A SQL-level limit would require a Blackboard API change.
999 List<TimelineEvent> events = tm.getEvents(interval, filter);
1000
1001 List<Map<String, Object>> result = new ArrayList<>(Math.min(events.size(), limit));
1002 for (TimelineEvent e : events) {
1003 if (result.size() >= limit) break;
1004 Map<String, Object> item = new LinkedHashMap<>();
1005 item.put("time", epochToIso(e.getTime()));
1006 item.put("eventType", e.getEventType().getDisplayName());
1007 item.put("description", e.getDescription(TimelineLevelOfDetail.HIGH));
1008 item.put("sourceFileId", e.getContentObjID());
1009 item.put("artifactId", e.getArtifactID().orElse(null));
1010 item.put("dataSourceId", e.getDataSourceObjID());
1011 item.put("tagged", e.eventSourceIsTagged());
1012 item.put("hashHit", e.eventSourceHasHashHits());
1013 result.add(item);
1014 }
1015 return result;
1016 }
1017
1018 // -------------------------------------------------------------------------
1019 // summarize_timeline
1020 // -------------------------------------------------------------------------
1021
1022 Map<String, Object> summarizeTimeline(JsonNode args) throws TskCoreException {
1023 TimelineManager tm = skCase.getTimelineManager();
1024 long startEpoch = parseTimeArg(args, "startTime", tm.getMinEventTime());
1025 long endEpoch = parseTimeArg(args, "endTime", tm.getMaxEventTime());
1026
1027 TimelineFilter.RootFilter filter = buildTimelineFilter(args);
1028
1029 Map<TimelineEventType, Long> counts = tm.countEventsByType(
1030 startEpoch, endEpoch, filter, TimelineEventType.HierarchyLevel.CATEGORY);
1031
1032 long total = 0;
1033 Map<String, Long> byCategory = new LinkedHashMap<>();
1034 for (Map.Entry<TimelineEventType, Long> entry : counts.entrySet()) {
1035 byCategory.put(entry.getKey().getDisplayName(), entry.getValue());
1036 total += entry.getValue();
1037 }
1038
1039 Map<String, Object> result = new LinkedHashMap<>();
1040 result.put("startTime", epochToIso(startEpoch));
1041 result.put("endTime", epochToIso(endEpoch));
1042 result.put("totalEvents", total);
1043 result.put("byCategory", byCategory);
1044 return result;
1045 }
1046
1047 // -------------------------------------------------------------------------
1048 // get_os_accounts
1049 // -------------------------------------------------------------------------
1050
1051 List<Map<String, Object>> getOsAccounts() throws TskCoreException {
1052 List<Map<String, Object>> result = new ArrayList<>();
1053 OsAccountManager oam = skCase.getOsAccountManager();
1054 for (OsAccount account : oam.getOsAccounts()) {
1055 Map<String, Object> item = new LinkedHashMap<>();
1056 item.put("objectId", account.getId());
1057 item.put("addr", account.getAddr().orElse(null)); // SID or UID
1058 item.put("loginName", account.getLoginName().orElse(null));
1059 item.put("fullName", account.getFullName().orElse(null));
1060 item.put("type", account.getOsAccountType().map(t -> t.getName()).orElse(null));
1061 item.put("status", account.getOsAccountStatus().map(s -> s.getName()).orElse(null));
1062 Long createdEpoch = account.getCreationTime().orElse(null);
1063 item.put("creationTime", createdEpoch != null && createdEpoch > 0
1064 ? epochToIso(createdEpoch) : null);
1065
1066 // Extended attributes: home dir, login script, last login, etc.
1067 List<Map<String, String>> extAttrs = new ArrayList<>();
1068 for (OsAccount.OsAccountAttribute attr : account.getExtendedOsAccountAttributes()) {
1069 Map<String, String> a = new LinkedHashMap<>();
1070 a.put("type", attr.getAttributeType().getTypeName());
1071 a.put("value", attr.getDisplayString());
1072 extAttrs.add(a);
1073 }
1074 item.put("extendedAttributes", extAttrs);
1075
1076 // Which data sources this account appeared on
1077 List<Map<String, Object>> instances = new ArrayList<>();
1078 for (OsAccountInstance inst : account.getOsAccountInstances()) {
1079 Map<String, Object> instMap = new LinkedHashMap<>();
1080 instMap.put("dataSourceId", inst.getDataSource().getId());
1081 instMap.put("instanceType", inst.getInstanceType().getName());
1082 instances.add(instMap);
1083 }
1084 item.put("instances", instances);
1085 result.add(item);
1086 }
1087 return result;
1088 }
1089
1090 // -------------------------------------------------------------------------
1091 // get_communications_accounts
1092 // -------------------------------------------------------------------------
1093
1094 List<Map<String, Object>> getCommunicationsAccounts(JsonNode args) throws TskCoreException {
1095 int limit = args.path("limit").asInt(100);
1096 if (limit <= 0 || limit > 500) limit = 100;
1097
1098 String accountTypeStr = textOrNull(args, "accountType");
1099
1100 CommunicationsFilter filter = new CommunicationsFilter();
1101 if (accountTypeStr != null) {
1102 Account.Type type = lookupAccountType(accountTypeStr);
1103 if (type != null) {
1104 filter.addAndFilter(new CommunicationsFilter.AccountTypeFilter(
1105 Collections.singleton(type)));
1106 }
1107 }
1108
1109 CommunicationsManager cm = skCase.getCommunicationsManager();
1110 List<AccountDeviceInstance> adis = cm.getAccountDeviceInstancesWithRelationships(filter);
1111
1112 List<Map<String, Object>> result = new ArrayList<>();
1113 for (AccountDeviceInstance adi : adis) {
1114 if (result.size() >= limit) break;
1115 Map<String, Object> item = new LinkedHashMap<>();
1116 Account account = adi.getAccount();
1117 item.put("accountType", account.getAccountType().getDisplayName());
1118 item.put("accountId", account.getTypeSpecificID());
1119 item.put("deviceId", adi.getDeviceId());
1120 item.put("relationshipCount", cm.getRelationshipSourcesCount(adi, new CommunicationsFilter()));
1121 result.add(item);
1122 }
1123 return result;
1124 }
1125
1126 // -------------------------------------------------------------------------
1127 // get_account_relationships
1128 // -------------------------------------------------------------------------
1129
1130 Map<String, Object> getAccountRelationships(JsonNode args) throws TskCoreException, McpException {
1131 String accountTypeStr = textOrNull(args, "accountType");
1132 String accountId = textOrNull(args, "accountId");
1133
1134 if (accountId == null) {
1135 throw new McpException("accountId is required");
1136 }
1137 if (accountTypeStr == null) {
1138 throw new McpException("accountType is required");
1139 }
1140
1141 CommunicationsManager cm = skCase.getCommunicationsManager();
1142 CommunicationsFilter filter = new CommunicationsFilter();
1143
1144 // Find the target AccountDeviceInstance by exact account ID and type
1145 AccountDeviceInstance targetAdi = null;
1146 for (AccountDeviceInstance adi : cm.getAccountDeviceInstancesWithRelationships(filter)) {
1147 Account account = adi.getAccount();
1148 if (account.getTypeSpecificID().equalsIgnoreCase(accountId)
1149 && account.getAccountType().getTypeName().equalsIgnoreCase(accountTypeStr)) {
1150 targetAdi = adi;
1151 break;
1152 }
1153 }
1154
1155 Map<String, Object> result = new LinkedHashMap<>();
1156 if (targetAdi == null) {
1157 result.put("error", "Account not found: " + accountId);
1158 return result;
1159 }
1160
1161 // Get all accounts that communicated with this one
1162 List<AccountDeviceInstance> related = cm.getRelatedAccountDeviceInstances(targetAdi, filter);
1163
1164 List<Map<String, Object>> relationships = new ArrayList<>();
1165 long totalRelationships = 0;
1166 for (AccountDeviceInstance relAdi : related) {
1167 // Count only sources shared between targetAdi and relAdi (not relAdi's total)
1168 long pairCount = cm.getRelationshipSources(targetAdi, relAdi, filter).size();
1169 totalRelationships += pairCount;
1170 Map<String, Object> item = new LinkedHashMap<>();
1171 Account relAccount = relAdi.getAccount();
1172 item.put("accountType", relAccount.getAccountType().getDisplayName());
1173 item.put("accountId", relAccount.getTypeSpecificID());
1174 item.put("deviceId", relAdi.getDeviceId());
1175 item.put("relationshipCount", pairCount);
1176 relationships.add(item);
1177 }
1178
1179 result.put("account", targetAdi.getAccount().getTypeSpecificID());
1180 result.put("accountType", targetAdi.getAccount().getAccountType().getDisplayName());
1181 result.put("deviceId", targetAdi.getDeviceId());
1182 result.put("totalRelationships", totalRelationships);
1183 result.put("relatedAccounts", relationships);
1184 return result;
1185 }
1186
1187 // -------------------------------------------------------------------------
1188 // get_object_children
1189 // -------------------------------------------------------------------------
1190
1196 Map<String, Object> getObjectChildren(JsonNode args) throws TskCoreException, McpException {
1197 if (args.path("objectId").isMissingNode()) {
1198 throw new McpException("objectId is required");
1199 }
1200 long objectId = args.path("objectId").asLong();
1201
1202 Content content = skCase.getContentById(objectId);
1203 if (content == null) {
1204 throw new McpException("No object found with id " + objectId);
1205 }
1206
1207 List<Content> children = content.getChildren();
1208 List<Map<String, Object>> childList = new ArrayList<>(children.size());
1209 for (Content child : children) {
1210 childList.add(buildContentSummary(child));
1211 }
1212
1213 Content parent = content.getParent();
1214
1215 Map<String, Object> result = new LinkedHashMap<>();
1216 result.put("object", buildContentSummary(content));
1217 result.put("parent", parent != null ? buildContentSummary(parent) : null);
1218 result.put("childCount", childList.size());
1219 result.put("children", childList);
1220 return result;
1221 }
1222
1227 private Map<String, Object> buildContentSummary(Content c) throws TskCoreException {
1228 Map<String, Object> item = new LinkedHashMap<>();
1229 item.put("objectId", c.getId());
1230
1231 if (c instanceof BlackboardArtifact) {
1232 BlackboardArtifact artifact = (BlackboardArtifact) c;
1233 item.put("objectType", "artifact");
1234 item.put("artifactType", artifact.getArtifactTypeName());
1235 item.put("sourceObjectId", artifact.getObjectID());
1236 item.put("dataSourceId", artifact.getDataSourceObjectID());
1237 List<Map<String, Object>> attrList = new ArrayList<>();
1238 for (BlackboardAttribute attr : artifact.getAttributes()) {
1239 Map<String, Object> attrMap = new LinkedHashMap<>();
1240 attrMap.put("type", attr.getAttributeType().getTypeName());
1241 attrMap.put("value", attrValueAsObject(attr));
1242 attrList.add(attrMap);
1243 }
1244 item.put("attributes", attrList);
1245
1246 } else if (c instanceof AbstractFile) {
1247 AbstractFile f = (AbstractFile) c;
1248 item.put("objectType", "file");
1249 item.put("name", f.getName());
1250 try {
1251 item.put("path", f.getUniquePath());
1252 } catch (TskCoreException ex) {
1253 item.put("path", f.getParentPath() + f.getName());
1254 }
1255 item.put("size", f.getSize());
1256 item.put("mimeType", f.getMIMEType());
1257 item.put("fileType", f.getType().name());
1258 item.put("dirType", f.getDirTypeAsString());
1259 item.put("isDirectory", f.isDir());
1260 item.put("modifiedTime", epochToIso(f.getMtime()));
1261 item.put("createdTime", epochToIso(f.getCrtime()));
1262
1263 } else if (c instanceof Image) {
1264 Image img = (Image) c;
1265 item.put("objectType", "image");
1266 item.put("name", img.getName());
1267 item.put("imageType", img.getType().getName());
1268 item.put("size", img.getSize());
1269
1270 } else if (c instanceof VolumeSystem) {
1271 VolumeSystem vs = (VolumeSystem) c;
1272 item.put("objectType", "volumeSystem");
1273 item.put("vsType", vs.getType().getName());
1274 item.put("offset", vs.getOffset());
1275 item.put("blockSize", vs.getBlockSize());
1276
1277 } else if (c instanceof Volume) {
1278 Volume vol = (Volume) c;
1279 item.put("objectType", "volume");
1280 item.put("addr", vol.getAddr());
1281 item.put("description", vol.getDescription());
1282 item.put("startSector", vol.getStart());
1283 item.put("lengthSectors", vol.getLength());
1284 item.put("flags", vol.getFlagsAsString());
1285
1286 } else if (c instanceof FileSystem) {
1287 FileSystem fs = (FileSystem) c;
1288 item.put("objectType", "fileSystem");
1289 item.put("fsType", fs.getFsType().getDisplayName());
1290 item.put("imageOffset", fs.getImageOffset());
1291 item.put("blockSize", fs.getBlock_size());
1292 item.put("blockCount", fs.getBlock_count());
1293
1294 } else {
1295 item.put("objectType", c.getClass().getSimpleName());
1296 item.put("name", c.getName());
1297 }
1298
1299 return item;
1300 }
1301
1302 // -------------------------------------------------------------------------
1303 // list_reports
1304 // -------------------------------------------------------------------------
1305
1306 List<Map<String, Object>> listReports() throws TskCoreException {
1307 List<Map<String, Object>> result = new ArrayList<>();
1308 for (Report report : skCase.getAllReports()) {
1309 Map<String, Object> item = new LinkedHashMap<>();
1310 item.put("objectId", report.getId());
1311 item.put("reportName", report.getReportName());
1312 item.put("sourceModule", report.getSourceModuleName());
1313 item.put("path", report.getPath());
1314 item.put("createdTime", epochToIso(report.getCreatedTime()));
1315 item.put("size", report.getSize());
1316 item.put("contentType", guessReportTypeByExtension(report.getPath()));
1317 result.add(item);
1318 }
1319 return result;
1320 }
1321
1322 // -------------------------------------------------------------------------
1323 // get_report_content
1324 // -------------------------------------------------------------------------
1325
1326 Map<String, Object> getReportContent(JsonNode args) throws TskCoreException, McpException {
1327 if (args.path("objectId").isMissingNode()) {
1328 throw new McpException("objectId is required");
1329 }
1330 long reportId = args.path("objectId").asLong();
1331 long offset = args.path("offset").asLong(0);
1332 int maxBytes = args.path("maxBytes").asInt(65536);
1333 if (maxBytes <= 0 || maxBytes > 1_048_576) {
1334 maxBytes = 65536;
1335 }
1336
1337 Report report = skCase.getReportById(reportId);
1338 if (report == null) {
1339 throw new McpException("No report found with id " + reportId);
1340 }
1341
1342 String contentType = guessReportTypeByExtension(report.getPath());
1343 long fileSize = report.getSize();
1344
1345 if (offset < 0) {
1346 offset = 0;
1347 }
1348 if (offset >= fileSize) {
1349 Map<String, Object> empty = new LinkedHashMap<>();
1350 empty.put("objectId", reportId);
1351 empty.put("reportName", report.getReportName());
1352 empty.put("path", report.getPath());
1353 empty.put("fileSize", fileSize);
1354 empty.put("contentType", contentType);
1355 empty.put("offset", offset);
1356 empty.put("bytesRead", 0);
1357 empty.put("truncated", false);
1358 empty.put("eof", true);
1359 empty.put("content", "");
1360 return empty;
1361 }
1362
1363 long available = fileSize - offset;
1364 int toRead = (int) Math.min(maxBytes, available);
1365 boolean truncated = available > maxBytes;
1366
1367 byte[] buf = new byte[toRead];
1368 int bytesRead = report.read(buf, offset, toRead);
1369 if (bytesRead < toRead) {
1370 buf = java.util.Arrays.copyOf(buf, Math.max(bytesRead, 0));
1371 }
1372
1373 CharsetDecoder decoder = StandardCharsets.UTF_8.newDecoder()
1374 .onMalformedInput(CodingErrorAction.REPLACE)
1375 .onUnmappableCharacter(CodingErrorAction.REPLACE);
1376 CharBuffer chars;
1377 try {
1378 chars = decoder.decode(ByteBuffer.wrap(buf));
1379 } catch (java.nio.charset.CharacterCodingException ex) {
1380 chars = CharBuffer.wrap(new String(buf, StandardCharsets.ISO_8859_1));
1381 }
1382
1383 Map<String, Object> result = new LinkedHashMap<>();
1384 result.put("objectId", reportId);
1385 result.put("reportName", report.getReportName());
1386 result.put("path", report.getPath());
1387 result.put("fileSize", fileSize);
1388 result.put("contentType", contentType);
1389 result.put("offset", offset);
1390 result.put("bytesRead", buf.length);
1391 result.put("truncated", truncated);
1392 result.put("eof", !truncated);
1393 result.put("content", chars.toString());
1394 return result;
1395 }
1396
1397 // -------------------------------------------------------------------------
1398 // Helpers
1399 // -------------------------------------------------------------------------
1400
1405 private TimelineFilter.RootFilter buildTimelineFilter(JsonNode args) throws TskCoreException {
1406 // DataSource filter (optional)
1407 TimelineFilter.DataSourcesFilter dsFilter = null;
1408 if (!args.path("dataSourceId").isMissingNode()) {
1409 long dsId = args.path("dataSourceId").asLong();
1410 dsFilter = new TimelineFilter.DataSourcesFilter();
1411 dsFilter.addSubFilter(new TimelineFilter.DataSourceFilter("", dsId));
1412 }
1413
1414 // Text/description filter (optional)
1415 String text = textOrNull(args, "textFilter");
1416 TimelineFilter.TextFilter textFilter = (text != null)
1417 ? new TimelineFilter.TextFilter(text) : null;
1418
1419 // Include all event types
1420 TimelineFilter.EventTypeFilter typeFilter =
1421 new TimelineFilter.EventTypeFilter(TimelineEventType.ROOT_EVENT_TYPE);
1422
1423 return new TimelineFilter.RootFilter(
1424 null, // knownFilesFilter — null = include known files too
1425 null, // tagsFilter
1426 null, // hashSetHitsFilter
1427 textFilter,
1428 typeFilter,
1429 dsFilter,
1430 null, // fileTypesFilter
1431 Collections.emptyList()
1432 );
1433 }
1434
1438 private static long parseTimeArg(JsonNode args, String field, Long fallback) {
1439 String s = textOrNull(args, field);
1440 if (s == null) {
1441 return fallback != null ? fallback : 0L;
1442 }
1443 // Accept date-only strings (e.g. "2012-03-02").
1444 // End-boundary fields (endTime, *Before) expand to 23:59:59 so the full
1445 // day is included; start-boundary fields expand to 00:00:00 (midnight UTC).
1446 if (s.length() == 10) {
1447 boolean isEndBoundary = "endTime".equals(field) || field.endsWith("Before");
1448 s = s + (isEndBoundary ? "T23:59:59Z" : "T00:00:00Z");
1449 }
1450 return Instant.parse(s).getEpochSecond();
1451 }
1452
1453 private static final String CASE_ID_NOTE =
1454 "Note: caseId in the response reflects the currently open case — " +
1455 "if this changes between calls, alert the user.";
1456
1457 private Map<String, Object> toolWithNote(String name, String description, Map<String, Object> properties) {
1458 return tool(name, description + " " + CASE_ID_NOTE, properties);
1459 }
1460
1461 private Map<String, Object> toolWithNote(String name, String description, Map<String, Object> properties, List<String> required) {
1462 return tool(name, description + " " + CASE_ID_NOTE, properties, required);
1463 }
1464
1465 private Map<String, Object> tool(String name, String description, Map<String, Object> properties) {
1466 return Map.of(
1467 "name", name,
1468 "description", description,
1469 "inputSchema", Map.of(
1470 "type", "object",
1471 "properties", properties
1472 )
1473 );
1474 }
1475
1476 private Map<String, Object> tool(String name, String description, Map<String, Object> properties, List<String> required) {
1477 return Map.of(
1478 "name", name,
1479 "description", description,
1480 "inputSchema", Map.of(
1481 "type", "object",
1482 "properties", properties,
1483 "required", required
1484 )
1485 );
1486 }
1487
1488 private Map<String, Object> param(String type, String description) {
1489 return Map.of("type", type, "description", description);
1490 }
1491
1492 private static String textOrNull(JsonNode node, String field) {
1493 JsonNode child = node.path(field);
1494 if (child.isMissingNode() || child.isNull()) {
1495 return null;
1496 }
1497 String text = child.asText().trim();
1498 return text.isEmpty() ? null : text;
1499 }
1500
1501 private static String guessReportTypeByExtension(String path) {
1502 String p = path.toLowerCase();
1503 if (p.endsWith(".html") || p.endsWith(".htm") || p.endsWith(".xhtml")) {
1504 return "text/html";
1505 }
1506 return "text/plain";
1507 }
1508
1509 private static String escapeSql(String value) {
1510 return value.replace("'", "''");
1511 }
1512
1513 private static String epochToIso(long epochSeconds) {
1514 if (epochSeconds <= 0) {
1515 return null;
1516 }
1517 return Instant.ofEpochSecond(epochSeconds)
1518 .atOffset(ZoneOffset.UTC)
1519 .format(DateTimeFormatter.ISO_OFFSET_DATE_TIME);
1520 }
1521
1525 private static Account.Type lookupAccountType(String name) {
1526 switch (name.toUpperCase()) {
1527 case "CREDIT_CARD": return Account.Type.CREDIT_CARD;
1528 case "DEVICE": return Account.Type.DEVICE;
1529 case "EMAIL": return Account.Type.EMAIL;
1530 case "FACEBOOK": return Account.Type.FACEBOOK;
1531 case "IMO": return Account.Type.IMO;
1532 case "INSTAGRAM": return Account.Type.INSTAGRAM;
1533 case "LINE": return Account.Type.LINE;
1534 case "MESSAGING_APP": return Account.Type.MESSAGING_APP;
1535 case "PHONE": return Account.Type.PHONE;
1536 case "SHAREIT": return Account.Type.SHAREIT;
1537 case "SKYPE": return Account.Type.SKYPE;
1538 case "TANGO": return Account.Type.TANGO;
1539 case "TEXTNOW": return Account.Type.TEXTNOW;
1540 case "THREEMA": return Account.Type.THREEMA;
1541 case "TWITTER": return Account.Type.TWITTER;
1542 case "VIBER": return Account.Type.VIBER;
1543 case "WEBSITE": return Account.Type.WEBSITE;
1544 case "WHATSAPP": return Account.Type.WHATSAPP;
1545 case "XENDER": return Account.Type.XENDER;
1546 case "ZAPYA": return Account.Type.ZAPYA;
1547 default: return null;
1548 }
1549 }
1550
1554 private static TskData.FileKnown parseKnownState(String value) {
1555 switch (value.toUpperCase()) {
1556 case "KNOWN": return TskData.FileKnown.KNOWN;
1557 case "NOTABLE":
1558 case "BAD": return TskData.FileKnown.BAD;
1559 case "UNKNOWN": return TskData.FileKnown.UNKNOWN;
1560 default: return null;
1561 }
1562 }
1563
1568 private static Object attrValueAsObject(BlackboardAttribute attr) {
1569 switch (attr.getValueType()) {
1570 case STRING:
1571 case JSON:
1572 return attr.getValueString();
1573 case DATETIME:
1574 return epochToIso(attr.getValueLong());
1575 case INTEGER:
1576 return attr.getValueInt();
1577 case LONG:
1578 return attr.getValueLong();
1579 case DOUBLE:
1580 return attr.getValueDouble();
1581 default:
1582 return null;
1583 }
1584 }
1585
1590 private static Object fileAttrValueAsObject(Attribute attr) {
1591 BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE valueType =
1592 attr.getAttributeType().getValueType();
1593 switch (valueType) {
1594 case STRING:
1595 case JSON:
1596 return attr.getValueString();
1597 case DATETIME:
1598 return epochToIso(attr.getValueLong());
1599 case INTEGER:
1600 return attr.getValueInt();
1601 case LONG:
1602 return attr.getValueLong();
1603 case DOUBLE:
1604 return attr.getValueDouble();
1605 default:
1606 return null;
1607 }
1608 }
1609
1614 private static String attrValueAsString(BlackboardAttribute attr) {
1615 switch (attr.getValueType()) {
1616 case STRING:
1617 case JSON:
1618 String s = attr.getValueString();
1619 return s != null ? s : "";
1620 case DATETIME:
1621 String iso = epochToIso(attr.getValueLong());
1622 return iso != null ? iso : "";
1623 case INTEGER:
1624 return Integer.toString(attr.getValueInt());
1625 case LONG:
1626 return Long.toString(attr.getValueLong());
1627 case DOUBLE:
1628 return Double.toString(attr.getValueDouble());
1629 default:
1630 return "";
1631 }
1632 }
1633}

Copyright © 2012-2024 Sleuth Kit Labs. Generated on:
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.