Autopsy 4.22.1
Graphical digital forensics platform for The Sleuth Kit and other tools.
GeolocationSummary.java
Go to the documentation of this file.
1/*
2 * Autopsy Forensic Browser
3 *
4 * Copyright 2020-2021 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.datasourcesummary.datamodel;
20
21import java.io.IOException;
22import java.util.ArrayList;
23import java.util.Arrays;
24import java.util.Collections;
25import java.util.HashMap;
26import java.util.List;
27import java.util.Map;
28import java.util.Set;
29import java.util.concurrent.ArrayBlockingQueue;
30import java.util.concurrent.BlockingQueue;
31import java.util.stream.Collectors;
32import java.util.stream.Stream;
33import org.apache.commons.lang3.tuple.Pair;
34import org.sleuthkit.autopsy.datasourcesummary.datamodel.SleuthkitCaseProvider.SleuthkitCaseProviderException;
35import org.sleuthkit.autopsy.geolocation.AbstractWaypointFetcher;
36import org.sleuthkit.autopsy.geolocation.GeoFilter;
37import org.sleuthkit.autopsy.geolocation.MapWaypoint;
38import org.sleuthkit.autopsy.geolocation.datamodel.GeoLocationDataException;
39import org.sleuthkit.autopsy.geolocation.datamodel.WaypointBuilder;
40import org.sleuthkit.datamodel.BlackboardArtifact;
41import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE;
42import org.sleuthkit.datamodel.DataSource;
43
47public class GeolocationSummary {
48
52 public static class CityRecordCount {
53
54 private final CityRecord cityRecord;
55 private final int count;
56
64 CityRecordCount(CityRecord cityRecord, int count) {
65 this.cityRecord = cityRecord;
66 this.count = count;
67 }
68
74 return cityRecord;
75 }
76
80 public int getCount() {
81 return count;
82 }
83 }
84
89 public static class CityData {
90
93 private final Long mostRecentSeen;
94
103 this.mostCommon = mostCommon;
104 this.mostRecent = mostRecent;
105 this.mostRecentSeen = mostRecentSeen;
106 }
107
112 return mostCommon;
113 }
114
119 return mostRecent;
120 }
121
126 public Long getMostRecentSeen() {
127 return mostRecentSeen;
128 }
129 }
130
136 public static class CityCountsList {
137
138 private final List<CityRecordCount> counts;
139 private final int otherCount;
140
149 CityCountsList(List<CityRecordCount> counts, int otherCount) {
150 this.counts = Collections.unmodifiableList(new ArrayList<>(counts));
151 this.otherCount = otherCount;
152 }
153
158 public List<CityRecordCount> getCounts() {
159 return counts;
160 }
161
166 public int getOtherCount() {
167 return otherCount;
168 }
169 }
170
175 private static class GeoResult {
176
177 private final Set<MapWaypoint> mapWaypoints;
178 private final List<Set<MapWaypoint>> tracks;
179 private final List<Set<MapWaypoint>> areas;
180
190 private GeoResult(Set<MapWaypoint> mapWaypoints, List<Set<MapWaypoint>> tracks, List<Set<MapWaypoint>> areas) {
191 this.mapWaypoints = mapWaypoints;
192 this.tracks = tracks;
193 this.areas = areas;
194 }
195
199 private Set<MapWaypoint> getMapWaypoints() {
200 return mapWaypoints;
201 }
202
206 private List<Set<MapWaypoint>> getTracks() {
207 return tracks;
208 }
209
213 private List<Set<MapWaypoint>> getAreas() {
214 return areas;
215 }
216 }
217
218 // taken from GeoFilterPanel: all of the GPS artifact types.
219 @SuppressWarnings("deprecation")
220 private static final List<ARTIFACT_TYPE> GPS_ARTIFACT_TYPES = Arrays.asList(
221 BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_BOOKMARK,
222 BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_LAST_KNOWN_LOCATION,
223 BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_ROUTE,
224 BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_SEARCH,
225 BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_TRACK,
226 BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_TRACKPOINT,
227 BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF,
228 BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_AREA
229 );
230
231 // all GPS types
232 private static final Set<Integer> GPS_ARTIFACT_TYPE_IDS = GPS_ARTIFACT_TYPES.stream()
233 .map(artifactType -> artifactType.getTypeID())
234 .collect(Collectors.toSet());
235
236 private static final Pair<Integer, Integer> EMPTY_COUNT = Pair.of(0, 0);
237
238 private static final long DAY_SECS = 24 * 60 * 60;
239
241 private final SupplierWithException<ClosestCityMapper, IOException> cityMapper;
242
246 public interface SupplierWithException<T, E extends Throwable> {
247
255 T get() throws E;
256 }
257
262 this(() -> ClosestCityMapper.getInstance(), SleuthkitCaseProvider.DEFAULT);
263 }
264
276
280 public static List<ARTIFACT_TYPE> getGeoTypes() {
281 return GPS_ARTIFACT_TYPES;
282 }
283
284 public static Set<Integer> getArtifactTypeIdsForRefresh() {
285 return Collections.unmodifiableSet(GPS_ARTIFACT_TYPE_IDS);
286 }
287
300 private static boolean greaterThanOrEqual(Long minTime, Long time) {
301 if (minTime != null && time != null && time >= minTime) {
302 return true;
303 } else {
304 return false;
305 }
306 }
307
319 private static Pair<Integer, Integer> getCounts(List<Long> points, Long minTime) {
320 if (points == null) {
321 return EMPTY_COUNT;
322 }
323
324 return points.stream().reduce(
326 (total, time) -> Pair.of(total.getLeft() + 1, total.getRight() + (greaterThanOrEqual(minTime, time) ? 1 : 0)),
327 (pair1, pair2) -> Pair.of(pair1.getLeft() + pair2.getLeft(), pair1.getRight() + pair2.getRight()));
328 }
329
340 private Pair<CityRecord, Long> getClosestWithTime(ClosestCityMapper cityMapper, MapWaypoint pt) {
341 if (pt == null) {
342 return null;
343 }
344
345 CityRecord city = cityMapper.findClosest(new CityRecord(null, null, null, pt.getX(), pt.getY()));
346
347 Long time = pt.getTimestamp();
348 return Pair.of(city, time);
349 }
350
363 private Stream<Pair<CityRecord, Long>> reduceGrouping(Set<MapWaypoint> points, ClosestCityMapper cityMapper) {
364 if (points == null) {
365 return Stream.empty();
366 }
367
368 Map<CityRecord, Long> timeMapping = new HashMap<>();
369 for (MapWaypoint pt : points) {
370 Pair<CityRecord, Long> pair = getClosestWithTime(cityMapper, pt);
371 if (pair == null) {
372 continue;
373 }
374
375 CityRecord city = pair.getLeft();
376 Long prevTime = timeMapping.get(city);
377 Long curTime = pair.getRight();
378 if (prevTime == null || (curTime != null && curTime > prevTime)) {
379 timeMapping.put(city, curTime);
380 }
381 }
382
383 return timeMapping.entrySet().stream()
384 .map(e -> Pair.of(e.getKey(), e.getValue()));
385 }
386
400 private Stream<Pair<CityRecord, Long>> processGeoResult(GeoResult geoResult, ClosestCityMapper cityMapper) {
401 if (geoResult == null) {
402 return Stream.empty();
403 }
404
405 List<Set<MapWaypoint>> areas = (geoResult.getAreas() == null) ? Collections.emptyList() : geoResult.getAreas();
406 List<Set<MapWaypoint>> tracks = (geoResult.getTracks() == null) ? Collections.emptyList() : geoResult.getTracks();
407
408 Stream<Pair<CityRecord, Long>> reducedGroupings = Stream.of(areas, tracks)
409 .flatMap((groupingList) -> groupingList.stream())
410 .flatMap((grouping) -> reduceGrouping(grouping, cityMapper));
411
412 final Set<MapWaypoint> allTracksAndAreas = Stream.of(areas, tracks)
413 .flatMap((groupingList) -> groupingList.stream())
414 .flatMap((group) -> group.stream())
415 .collect(Collectors.toSet());
416
417 Set<MapWaypoint> pointSet = geoResult.getMapWaypoints() == null ? Collections.emptySet() : geoResult.getMapWaypoints();
418 Stream<Pair<CityRecord, Long>> citiesForPoints = pointSet.stream()
419 // it appears that AbstractWaypointFetcher.handleFilteredWaypointSet returns all points
420 // (including track and area points) in the set of MapWaypoints. This filters those points out of the remaining.
421 .filter(pt -> !allTracksAndAreas.contains(pt))
422 .map(pt -> getClosestWithTime(cityMapper, pt));
423
424 return Stream.concat(reducedGroupings, citiesForPoints);
425 }
426
441 public CityData getCityCounts(DataSource dataSource, int daysCount, int maxCount)
442 throws SleuthkitCaseProviderException, GeoLocationDataException, InterruptedException, IOException {
443
444 ClosestCityMapper closestCityMapper = this.cityMapper.get();
445 GeoResult geoResult = getGeoResult(dataSource);
446 List<Pair<CityRecord, Long>> dataSourcePoints = processGeoResult(geoResult, closestCityMapper)
447 .collect(Collectors.toList());
448
449 Map<CityRecord, List<Long>> allCityPoints = new HashMap<>();
450 List<Long> others = new ArrayList<>();
451 Long mostRecent = null;
452
453 for (Pair<CityRecord, Long> pt : dataSourcePoints) {
454 if (pt == null) {
455 continue;
456 }
457
458 Long curTime = pt.getRight();
459 if (curTime != null && (mostRecent == null || curTime > mostRecent)) {
460 mostRecent = curTime;
461 }
462
463 CityRecord city = pt.getLeft();
464 if (city == null) {
465 others.add(curTime);
466 } else {
467 List<Long> cityPoints = allCityPoints.get(city);
468 if (cityPoints == null) {
469 cityPoints = new ArrayList<>();
470 allCityPoints.put(city, cityPoints);
471 }
472
473 cityPoints.add(curTime);
474 }
475 }
476
477 final Long mostRecentMinTime = (mostRecent == null) ? null : mostRecent - daysCount * DAY_SECS;
478
479 // pair left is total count and right is count within range (or mostRecent is null)
480 Map<CityRecord, Pair<Integer, Integer>> allCityCounts = allCityPoints.entrySet().stream()
481 .collect(Collectors.toMap((e) -> e.getKey(), (e) -> getCounts(e.getValue(), mostRecentMinTime)));
482
483 List<CityRecordCount> mostCommonCounts = allCityCounts.entrySet().stream()
484 .map(e -> new CityRecordCount(e.getKey(), e.getValue().getLeft()))
485 .sorted((a, b) -> -Integer.compare(a.getCount(), b.getCount()))
486 .limit(maxCount)
487 .collect(Collectors.toList());
488
489 List<CityRecordCount> mostRecentCounts = allCityCounts.entrySet().stream()
490 .map(e -> new CityRecordCount(e.getKey(), e.getValue().getRight()))
491 .sorted((a, b) -> -Integer.compare(a.getCount(), b.getCount()))
492 .limit(maxCount)
493 .collect(Collectors.toList());
494
495 Pair<Integer, Integer> otherCounts = getCounts(others, mostRecentMinTime);
496 int otherMostCommonCount = otherCounts.getLeft();
497 int otherMostRecentCount = otherCounts.getRight();
498
499 return new CityData(
500 new CityCountsList(mostCommonCounts, otherMostCommonCount),
501 new CityCountsList(mostRecentCounts, otherMostRecentCount),
502 mostRecentMinTime);
503 }
504
508 private static class PointFetcher extends AbstractWaypointFetcher {
509
510 private final BlockingQueue<GeoResult> asyncResult;
511
521 PointFetcher(BlockingQueue<GeoResult> asyncResult, GeoFilter filters) {
522 super(filters);
523 this.asyncResult = asyncResult;
524 }
525
526 @Override
527 public void handleFilteredWaypointSet(Set<MapWaypoint> mapWaypoints, List<Set<MapWaypoint>> tracks, List<Set<MapWaypoint>> areas, boolean wasEntirelySuccessful) {
528 // push to blocking queue to continue
529 try {
530 asyncResult.put(new GeoResult(mapWaypoints, tracks, areas));
531 } catch (InterruptedException ignored) {
532 // ignored cancellations
533 }
534 }
535 }
536
547 private GeoResult getGeoResult(DataSource dataSource)
548 throws SleuthkitCaseProviderException, GeoLocationDataException, InterruptedException {
549
550 // make asynchronous callback synchronous (the callback nature will be handled in a different level)
551 // see the following: https://stackoverflow.com/questions/20659961/java-synchronous-callback
552 final BlockingQueue<GeoResult> asyncResult = new ArrayBlockingQueue<>(1);
553
554 GeoFilter geoFilter = new GeoFilter(true, false, 0, Arrays.asList(dataSource), GPS_ARTIFACT_TYPES);
555
557 Arrays.asList(dataSource),
559 true,
560 -1,
561 false,
562 new PointFetcher(asyncResult, geoFilter));
563
564 return asyncResult.take();
565 }
566}
GeoResult(Set< MapWaypoint > mapWaypoints, List< Set< MapWaypoint > > tracks, List< Set< MapWaypoint > > areas)
void handleFilteredWaypointSet(Set< MapWaypoint > mapWaypoints, List< Set< MapWaypoint > > tracks, List< Set< MapWaypoint > > areas, boolean wasEntirelySuccessful)
static Pair< Integer, Integer > getCounts(List< Long > points, Long minTime)
final SupplierWithException< ClosestCityMapper, IOException > cityMapper
Pair< CityRecord, Long > getClosestWithTime(ClosestCityMapper cityMapper, MapWaypoint pt)
Stream< Pair< CityRecord, Long > > processGeoResult(GeoResult geoResult, ClosestCityMapper cityMapper)
Stream< Pair< CityRecord, Long > > reduceGrouping(Set< MapWaypoint > points, ClosestCityMapper cityMapper)
CityData getCityCounts(DataSource dataSource, int daysCount, int maxCount)
GeolocationSummary(SupplierWithException< ClosestCityMapper, IOException > cityMapper, SleuthkitCaseProvider provider)
static List< Waypoint > getAllWaypoints(SleuthkitCase skCase)

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