Autopsy  4.19.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 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;
41 import org.sleuthkit.datamodel.BlackboardArtifact;
42 import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE;
43 import org.sleuthkit.datamodel.DataSource;
44 
49 
53  public static class CityRecordCount {
54 
55  private final CityRecord cityRecord;
56  private final int count;
57 
65  CityRecordCount(CityRecord cityRecord, int count) {
66  this.cityRecord = cityRecord;
67  this.count = count;
68  }
69 
75  return cityRecord;
76  }
77 
81  public int getCount() {
82  return count;
83  }
84  }
85 
90  public static class CityData {
91 
92  private final CityCountsList mostCommon;
93  private final CityCountsList mostRecent;
94  private final Long mostRecentSeen;
95 
103  CityData(CityCountsList mostCommon, CityCountsList mostRecent, Long mostRecentSeen) {
104  this.mostCommon = mostCommon;
105  this.mostRecent = mostRecent;
106  this.mostRecentSeen = mostRecentSeen;
107  }
108 
113  return mostCommon;
114  }
115 
120  return mostRecent;
121  }
122 
127  public Long getMostRecentSeen() {
128  return mostRecentSeen;
129  }
130  }
131 
137  public static class CityCountsList {
138 
139  private final List<CityRecordCount> counts;
140  private final int otherCount;
141 
150  CityCountsList(List<CityRecordCount> counts, int otherCount) {
151  this.counts = Collections.unmodifiableList(new ArrayList<>(counts));
152  this.otherCount = otherCount;
153  }
154 
159  public List<CityRecordCount> getCounts() {
160  return counts;
161  }
162 
167  public int getOtherCount() {
168  return otherCount;
169  }
170  }
171 
176  private static class GeoResult {
177 
178  private final Set<MapWaypoint> mapWaypoints;
179  private final List<Set<MapWaypoint>> tracks;
180  private final List<Set<MapWaypoint>> areas;
181 
191  private GeoResult(Set<MapWaypoint> mapWaypoints, List<Set<MapWaypoint>> tracks, List<Set<MapWaypoint>> areas) {
192  this.mapWaypoints = mapWaypoints;
193  this.tracks = tracks;
194  this.areas = areas;
195  }
196 
200  private Set<MapWaypoint> getMapWaypoints() {
201  return mapWaypoints;
202  }
203 
207  private List<Set<MapWaypoint>> getTracks() {
208  return tracks;
209  }
210 
214  private List<Set<MapWaypoint>> getAreas() {
215  return areas;
216  }
217  }
218 
219  // taken from GeoFilterPanel: all of the GPS artifact types.
220  @SuppressWarnings("deprecation")
221  private static final List<ARTIFACT_TYPE> GPS_ARTIFACT_TYPES = Arrays.asList(
222  BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_BOOKMARK,
223  BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_LAST_KNOWN_LOCATION,
224  BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_ROUTE,
225  BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_SEARCH,
226  BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_TRACK,
227  BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_TRACKPOINT,
228  BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF,
229  BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_AREA
230  );
231 
232  // all GPS types
233  private static final Set<Integer> GPS_ARTIFACT_TYPE_IDS = GPS_ARTIFACT_TYPES.stream()
234  .map(artifactType -> artifactType.getTypeID())
235  .collect(Collectors.toSet());
236 
237  private static final Pair<Integer, Integer> EMPTY_COUNT = Pair.of(0, 0);
238 
239  private static final long DAY_SECS = 24 * 60 * 60;
240 
242  private final SupplierWithException<ClosestCityMapper, IOException> cityMapper;
243 
247  public interface SupplierWithException<T, E extends Throwable> {
248 
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 List<ARTIFACT_TYPE> getGeoTypes() {
281  return GPS_ARTIFACT_TYPES;
282  }
283 
284  @Override
285  public Set<Integer> getArtifactTypeIdsForRefresh() {
286  return GPS_ARTIFACT_TYPE_IDS;
287  }
288 
300  private 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 
318  private Pair<Integer, Integer> getCounts(List<Long> points, Long minTime) {
319  if (points == null) {
320  return EMPTY_COUNT;
321  }
322 
323  return points.stream().reduce(
324  EMPTY_COUNT,
325  (total, time) -> Pair.of(total.getLeft() + 1, total.getRight() + (greaterThanOrEqual(minTime, time) ? 1 : 0)),
326  (pair1, pair2) -> Pair.of(pair1.getLeft() + pair2.getLeft(), pair1.getRight() + pair2.getRight()));
327  }
328 
338  private Pair<CityRecord, Long> getClosestWithTime(ClosestCityMapper cityMapper, MapWaypoint pt) {
339  if (pt == null) {
340  return null;
341  }
342 
343  CityRecord city = cityMapper.findClosest(new CityRecord(null, null, null, pt.getX(), pt.getY()));
344 
345  Long time = pt.getTimestamp();
346  return Pair.of(city, time);
347  }
348 
359  private Stream<Pair<CityRecord, Long>> reduceGrouping(Set<MapWaypoint> points, ClosestCityMapper cityMapper) {
360  if (points == null) {
361  return Stream.empty();
362  }
363 
364  Map<CityRecord, Long> timeMapping = new HashMap<>();
365  for (MapWaypoint pt : points) {
366  Pair<CityRecord, Long> pair = getClosestWithTime(cityMapper, pt);
367  if (pair == null) {
368  continue;
369  }
370 
371  CityRecord city = pair.getLeft();
372  Long prevTime = timeMapping.get(city);
373  Long curTime = pair.getRight();
374  if (prevTime == null || (curTime != null && curTime > prevTime)) {
375  timeMapping.put(city, curTime);
376  }
377  }
378 
379  return timeMapping.entrySet().stream()
380  .map(e -> Pair.of(e.getKey(), e.getValue()));
381  }
382 
394  private Stream<Pair<CityRecord, Long>> processGeoResult(GeoResult geoResult, ClosestCityMapper cityMapper) {
395  if (geoResult == null) {
396  return Stream.empty();
397  }
398 
399  List<Set<MapWaypoint>> areas = (geoResult.getAreas() == null) ? Collections.emptyList() : geoResult.getAreas();
400  List<Set<MapWaypoint>> tracks = (geoResult.getTracks() == null) ? Collections.emptyList() : geoResult.getTracks();
401 
402  Stream<Pair<CityRecord, Long>> reducedGroupings = Stream.of(areas, tracks)
403  .flatMap((groupingList) -> groupingList.stream())
404  .flatMap((grouping) -> reduceGrouping(grouping, cityMapper));
405 
406  final Set<MapWaypoint> allTracksAndAreas = Stream.of(areas, tracks)
407  .flatMap((groupingList) -> groupingList.stream())
408  .flatMap((group) -> group.stream())
409  .collect(Collectors.toSet());
410 
411  Set<MapWaypoint> pointSet = geoResult.getMapWaypoints() == null ? Collections.emptySet() : geoResult.getMapWaypoints();
412  Stream<Pair<CityRecord, Long>> citiesForPoints = pointSet.stream()
413  // it appears that AbstractWaypointFetcher.handleFilteredWaypointSet returns all points
414  // (including track and area points) in the set of MapWaypoints. This filters those points out of the remaining.
415  .filter(pt -> !allTracksAndAreas.contains(pt))
416  .map(pt -> getClosestWithTime(cityMapper, pt));
417 
418  return Stream.concat(reducedGroupings, citiesForPoints);
419  }
420 
435  public CityData getCityCounts(DataSource dataSource, int daysCount, int maxCount)
436  throws SleuthkitCaseProviderException, GeoLocationDataException, InterruptedException, IOException {
437 
438  ClosestCityMapper closestCityMapper = this.cityMapper.get();
439  GeoResult geoResult = getGeoResult(dataSource);
440  List<Pair<CityRecord, Long>> dataSourcePoints = processGeoResult(geoResult, closestCityMapper)
441  .collect(Collectors.toList());
442 
443  Map<CityRecord, List<Long>> allCityPoints = new HashMap<>();
444  List<Long> others = new ArrayList<>();
445  Long mostRecent = null;
446 
447  for (Pair<CityRecord, Long> pt : dataSourcePoints) {
448  if (pt == null) {
449  continue;
450  }
451 
452  Long curTime = pt.getRight();
453  if (curTime != null && (mostRecent == null || curTime > mostRecent)) {
454  mostRecent = curTime;
455  }
456 
457  CityRecord city = pt.getLeft();
458  if (city == null) {
459  others.add(curTime);
460  } else {
461  List<Long> cityPoints = allCityPoints.get(city);
462  if (cityPoints == null) {
463  cityPoints = new ArrayList<>();
464  allCityPoints.put(city, cityPoints);
465  }
466 
467  cityPoints.add(curTime);
468  }
469  }
470 
471  final Long mostRecentMinTime = (mostRecent == null) ? null : mostRecent - daysCount * DAY_SECS;
472 
473  // pair left is total count and right is count within range (or mostRecent is null)
474  Map<CityRecord, Pair<Integer, Integer>> allCityCounts = allCityPoints.entrySet().stream()
475  .collect(Collectors.toMap((e) -> e.getKey(), (e) -> getCounts(e.getValue(), mostRecentMinTime)));
476 
477  List<CityRecordCount> mostCommonCounts = allCityCounts.entrySet().stream()
478  .map(e -> new CityRecordCount(e.getKey(), e.getValue().getLeft()))
479  .sorted((a, b) -> -Integer.compare(a.getCount(), b.getCount()))
480  .limit(maxCount)
481  .collect(Collectors.toList());
482 
483  List<CityRecordCount> mostRecentCounts = allCityCounts.entrySet().stream()
484  .map(e -> new CityRecordCount(e.getKey(), e.getValue().getRight()))
485  .sorted((a, b) -> -Integer.compare(a.getCount(), b.getCount()))
486  .limit(maxCount)
487  .collect(Collectors.toList());
488 
489  Pair<Integer, Integer> otherCounts = getCounts(others, mostRecentMinTime);
490  int otherMostCommonCount = otherCounts.getLeft();
491  int otherMostRecentCount = otherCounts.getRight();
492 
493  return new CityData(
494  new CityCountsList(mostCommonCounts, otherMostCommonCount),
495  new CityCountsList(mostRecentCounts, otherMostRecentCount),
496  mostRecentMinTime);
497  }
498 
502  private static class PointFetcher extends AbstractWaypointFetcher {
503 
504  private final BlockingQueue<GeoResult> asyncResult;
505 
514  public PointFetcher(BlockingQueue<GeoResult> asyncResult, GeoFilter filters) {
515  super(filters);
516  this.asyncResult = asyncResult;
517  }
518 
519  @Override
520  public void handleFilteredWaypointSet(Set<MapWaypoint> mapWaypoints, List<Set<MapWaypoint>> tracks, List<Set<MapWaypoint>> areas, boolean wasEntirelySuccessful) {
521  // push to blocking queue to continue
522  try {
523  asyncResult.put(new GeoResult(mapWaypoints, tracks, areas));
524  } catch (InterruptedException ignored) {
525  // ignored cancellations
526  }
527  }
528  }
529 
539  private GeoResult getGeoResult(DataSource dataSource)
540  throws SleuthkitCaseProviderException, GeoLocationDataException, InterruptedException {
541 
542  // make asynchronous callback synchronous (the callback nature will be handled in a different level)
543  // see the following: https://stackoverflow.com/questions/20659961/java-synchronous-callback
544  final BlockingQueue<GeoResult> asyncResult = new ArrayBlockingQueue<>(1);
545 
546  GeoFilter geoFilter = new GeoFilter(true, false, 0, Arrays.asList(dataSource), GPS_ARTIFACT_TYPES);
547 
549  Arrays.asList(dataSource),
551  true,
552  -1,
553  false,
554  new PointFetcher(asyncResult, geoFilter));
555 
556  return asyncResult.take();
557  }
558 }
Pair< CityRecord, Long > getClosestWithTime(ClosestCityMapper cityMapper, MapWaypoint pt)
Stream< Pair< CityRecord, Long > > processGeoResult(GeoResult geoResult, ClosestCityMapper cityMapper)
PointFetcher(BlockingQueue< GeoResult > asyncResult, GeoFilter filters)
CityData getCityCounts(DataSource dataSource, int daysCount, int maxCount)
Stream< Pair< CityRecord, Long > > reduceGrouping(Set< MapWaypoint > points, ClosestCityMapper cityMapper)
static List< Waypoint > getAllWaypoints(SleuthkitCase skCase)
Pair< Integer, Integer > getCounts(List< Long > points, Long minTime)
GeolocationSummary(SupplierWithException< ClosestCityMapper, IOException > cityMapper, SleuthkitCaseProvider provider)
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-2021 Basis Technology. Generated on: Fri Aug 6 2021
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.