Autopsy  4.20.0
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  */
19 package org.sleuthkit.autopsy.datasourcesummary.datamodel;
20 
21 import java.io.IOException;
22 import java.util.ArrayList;
23 import java.util.Arrays;
24 import java.util.Collections;
25 import java.util.HashMap;
26 import java.util.List;
27 import java.util.Map;
28 import java.util.Set;
29 import java.util.concurrent.ArrayBlockingQueue;
30 import java.util.concurrent.BlockingQueue;
31 import java.util.stream.Collectors;
32 import java.util.stream.Stream;
33 import org.apache.commons.lang3.tuple.Pair;
40 import org.sleuthkit.datamodel.BlackboardArtifact;
41 import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE;
42 import org.sleuthkit.datamodel.DataSource;
43 
47 public 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 
91  private final CityCountsList mostCommon;
92  private final CityCountsList mostRecent;
93  private final Long mostRecentSeen;
94 
102  CityData(CityCountsList mostCommon, CityCountsList mostRecent, Long mostRecentSeen) {
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 
273  this.cityMapper = cityMapper;
274  this.provider = provider;
275  }
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(
325  EMPTY_COUNT,
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 }
Pair< CityRecord, Long > getClosestWithTime(ClosestCityMapper cityMapper, MapWaypoint pt)
Stream< Pair< CityRecord, Long > > processGeoResult(GeoResult geoResult, ClosestCityMapper cityMapper)
CityData getCityCounts(DataSource dataSource, int daysCount, int maxCount)
Stream< Pair< CityRecord, Long > > reduceGrouping(Set< MapWaypoint > points, ClosestCityMapper cityMapper)
static List< Waypoint > getAllWaypoints(SleuthkitCase skCase)
GeolocationSummary(SupplierWithException< ClosestCityMapper, IOException > cityMapper, SleuthkitCaseProvider provider)
static Pair< Integer, Integer > getCounts(List< Long > points, Long minTime)
final SupplierWithException< ClosestCityMapper, IOException > cityMapper
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)

Copyright © 2012-2022 Basis Technology. Generated on: Tue Aug 1 2023
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.