Added comments and general code clean up

This commit is contained in:
U-BASIS\dsmyda 2019-12-13 15:46:24 -05:00
parent f4de27095c
commit d81c2bc22d
2 changed files with 244 additions and 248 deletions

View File

@ -47,13 +47,13 @@ final class XRYCallsFileParser extends AbstractSingleKeyValueParser {
//function for more details. //function for more details.
private static final DateTimeFormatter DATE_TIME_PARSER private static final DateTimeFormatter DATE_TIME_PARSER
= DateTimeFormatter.ofPattern("[(XXX) ][O ][(O) ]a h:m:s M/d/y"); = DateTimeFormatter.ofPattern("[(XXX) ][O ][(O) ]a h:m:s M/d/y");
private static final String DEVICE_LOCALE = "(device)"; private static final String DEVICE_LOCALE = "(device)";
private static final String NETWORK_LOCALE = "(network)"; private static final String NETWORK_LOCALE = "(network)";
/** /**
* All of the known XRY keys for call reports and the blackboard * All of the known XRY keys for call reports and their corresponding
* attribute types they map to. * blackboard attribute types, if any.
*/ */
private enum XryKey { private enum XryKey {
NUMBER("number", BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER), NUMBER("number", BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER),
@ -66,24 +66,24 @@ final class XRYCallsFileParser extends AbstractSingleKeyValueParser {
STORAGE("storage", null), STORAGE("storage", null),
INDEX("index", null), INDEX("index", null),
NAME("name", null); NAME("name", null);
private final String name; private final String name;
private final BlackboardAttribute.ATTRIBUTE_TYPE type; private final BlackboardAttribute.ATTRIBUTE_TYPE type;
XryKey(String name, BlackboardAttribute.ATTRIBUTE_TYPE type) { XryKey(String name, BlackboardAttribute.ATTRIBUTE_TYPE type) {
this.name = name; this.name = name;
this.type = type; this.type = type;
} }
public BlackboardAttribute.ATTRIBUTE_TYPE getType() { public BlackboardAttribute.ATTRIBUTE_TYPE getType() {
return type; return type;
} }
/** /**
* Indicates if the display name of the XRY key is a recognized type. * Indicates if the display name of the XRY key is a recognized type.
* *
* @param xryKey * @param xryKey
* @return * @return
*/ */
public static boolean contains(XRYKeyValuePair pair) { public static boolean contains(XRYKeyValuePair pair) {
try { try {
@ -93,24 +93,24 @@ final class XRYCallsFileParser extends AbstractSingleKeyValueParser {
return false; return false;
} }
} }
/** /**
* Matches the display name of the xry key to the appropriate enum type. * Matches the display name of the xry key to the appropriate enum type.
* *
* It is assumed that XRY key string is recognized. Otherwise, * It is assumed that XRY key string is recognized. Otherwise, an
* an IllegalArgumentException is thrown. Test all membership * IllegalArgumentException is thrown. Test all membership with
* with contains() before hand. * contains() before hand.
* *
* @param xryKey * @param xryKey
* @return * @return
*/ */
public static XryKey fromPair(XRYKeyValuePair pair) { public static XryKey fromPair(XRYKeyValuePair pair) {
for(XryKey keyChoice : XryKey.values()) { for (XryKey keyChoice : XryKey.values()) {
if(pair.hasKey(keyChoice.name)) { if (pair.hasKey(keyChoice.name)) {
return keyChoice; return keyChoice;
} }
} }
throw new IllegalArgumentException(String.format("Key [%s] was not found." throw new IllegalArgumentException(String.format("Key [%s] was not found."
+ " All keys should be tested with contains.", pair.getKey())); + " All keys should be tested with contains.", pair.getKey()));
} }
@ -123,47 +123,50 @@ final class XRYCallsFileParser extends AbstractSingleKeyValueParser {
TO("to"), TO("to"),
FROM("from"), FROM("from"),
NONE(null); NONE(null);
private final String name; private final String name;
XryNamespace(String name) { XryNamespace(String name) {
this.name = name; this.name = name;
} }
/** /**
* Indicates if the display name of the XRY namespace is a recognized type. * Indicates if the display name of the XRY namespace is a recognized
* * type.
*
* @param xryNamespace * @param xryNamespace
* @return * @return
*/ */
public static boolean contains(String xryNamespace) { public static boolean contains(String xryNamespace) {
String normalizedNamespace = xryNamespace.trim().toLowerCase(); String normalizedNamespace = xryNamespace.trim().toLowerCase();
for(XryNamespace keyChoice : XryNamespace.values()) { for (XryNamespace keyChoice : XryNamespace.values()) {
if(normalizedNamespace.equals(keyChoice.name)) { if (normalizedNamespace.equals(keyChoice.name)) {
return true; return true;
} }
} }
return false; return false;
} }
/** /**
* Matches the display name of the xry namespace to the appropriate enum type. * Matches the display name of the xry namespace to the appropriate enum
* * type.
* It is assumed that XRY namespace string is recognized. Otherwise, *
* an IllegalArgumentException is thrown. Test all membership * It is assumed that XRY namespace string is recognized. Otherwise, an
* with contains() before hand. * IllegalArgumentException is thrown. Test all membership with
* * contains() before hand.
*
* @param xryNamespace * @param xryNamespace
* @return * @return
*/ */
public static XryNamespace fromDisplayName(String xryNamespace) { public static XryNamespace fromDisplayName(String xryNamespace) {
String normalizedNamespace = xryNamespace.trim().toLowerCase(); String normalizedNamespace = xryNamespace.trim().toLowerCase();
for(XryNamespace keyChoice : XryNamespace.values()) { for (XryNamespace keyChoice : XryNamespace.values()) {
if(normalizedNamespace.equals(keyChoice.name)) { if (normalizedNamespace.equals(keyChoice.name)) {
return keyChoice; return keyChoice;
} }
} }
throw new IllegalArgumentException(String.format("Key [%s] was not found." throw new IllegalArgumentException(String.format("Key [%s] was not found."
+ " All keys should be tested with contains.", xryNamespace)); + " All keys should be tested with contains.", xryNamespace));
} }
@ -183,7 +186,7 @@ final class XRYCallsFileParser extends AbstractSingleKeyValueParser {
Optional<BlackboardAttribute> getBlackboardAttribute(String nameSpace, XRYKeyValuePair pair) { Optional<BlackboardAttribute> getBlackboardAttribute(String nameSpace, XRYKeyValuePair pair) {
XryKey xryKey = XryKey.fromPair(pair); XryKey xryKey = XryKey.fromPair(pair);
XryNamespace xryNamespace = XryNamespace.NONE; XryNamespace xryNamespace = XryNamespace.NONE;
if(XryNamespace.contains(nameSpace)) { if (XryNamespace.contains(nameSpace)) {
xryNamespace = XryNamespace.fromDisplayName(nameSpace); xryNamespace = XryNamespace.fromDisplayName(nameSpace);
} }
@ -208,7 +211,7 @@ final class XRYCallsFileParser extends AbstractSingleKeyValueParser {
try { try {
//Tranform value to seconds since epoch //Tranform value to seconds since epoch
long dateTimeSinceEpoch = calculateSecondsSinceEpoch(pair.getValue()); long dateTimeSinceEpoch = calculateSecondsSinceEpoch(pair.getValue());
return Optional.of(new BlackboardAttribute(xryKey.getType(), return Optional.of(new BlackboardAttribute(xryKey.getType(),
PARSER_NAME, dateTimeSinceEpoch)); PARSER_NAME, dateTimeSinceEpoch));
} catch (DateTimeParseException ex) { } catch (DateTimeParseException ex) {
logger.log(Level.WARNING, String.format("[XRY DSP] Assumption" logger.log(Level.WARNING, String.format("[XRY DSP] Assumption"
@ -219,15 +222,15 @@ final class XRYCallsFileParser extends AbstractSingleKeyValueParser {
default: default:
//Otherwise, the XryKey enum contains the correct BlackboardAttribute //Otherwise, the XryKey enum contains the correct BlackboardAttribute
//type. //type.
if(xryKey.getType() != null) { if (xryKey.getType() != null) {
return Optional.of(new BlackboardAttribute(xryKey.getType(), return Optional.of(new BlackboardAttribute(xryKey.getType(),
PARSER_NAME, pair.getValue())); PARSER_NAME, pair.getValue()));
} }
logger.log(Level.WARNING, String.format("[XRY DSP] Key value pair " logger.log(Level.WARNING, String.format("[XRY DSP] Key value pair "
+ "(in brackets) [ %s ] was recognized but " + "(in brackets) [ %s ] was recognized but "
+ "more data or time is needed to finish implementation. Discarding... ", + "more data or time is needed to finish implementation. Discarding... ",
pair)); pair));
return Optional.empty(); return Optional.empty();
} }
} }
@ -253,7 +256,7 @@ final class XRYCallsFileParser extends AbstractSingleKeyValueParser {
result = result.substring(0, deviceIndex); result = result.substring(0, deviceIndex);
} }
int networkIndex = result.toLowerCase().indexOf(NETWORK_LOCALE); int networkIndex = result.toLowerCase().indexOf(NETWORK_LOCALE);
if(networkIndex != -1) { if (networkIndex != -1) {
result = result.substring(0, networkIndex); result = result.substring(0, networkIndex);
} }
return result; return result;
@ -269,28 +272,27 @@ final class XRYCallsFileParser extends AbstractSingleKeyValueParser {
String dateTimeWithoutLocale = removeDateTimeLocale(dateTime).trim(); String dateTimeWithoutLocale = removeDateTimeLocale(dateTime).trim();
/** /**
* The format of time in XRY Messages reports is of the form: * The format of time in XRY Messages reports is of the form:
* *
* 1/3/1990 1:23:54 AM UTC+4 * 1/3/1990 1:23:54 AM UTC+4
* *
* In our current version of Java (openjdk-1.8.0.222), there is * In our current version of Java (openjdk-1.8.0.222), there is a bug
* a bug with having the timezone offset (UTC+4 or GMT-7) at the * with having the timezone offset (UTC+4 or GMT-7) at the end of the
* end of the date time input. This is fixed in later versions * date time input. This is fixed in later versions of the JDK (9 and
* of the JDK (9 and beyond). * beyond). https://bugs.openjdk.java.net/browse/JDK-8154050 Rather than
* https://bugs.openjdk.java.net/browse/JDK-8154050 * update the JDK to accommodate this, the components of the date time
* Rather than update the JDK to accommodate this, the components of * string are reversed:
* the date time string are reversed: *
* * UTC+4 AM 1:23:54 1/3/1990
* UTC+4 AM 1:23:54 1/3/1990 *
*
* The java time package will correctly parse this date time format. * The java time package will correctly parse this date time format.
*/ */
String reversedDateTime = reverseOrderOfDateTimeComponents(dateTimeWithoutLocale); String reversedDateTime = reverseOrderOfDateTimeComponents(dateTimeWithoutLocale);
/** /**
* Furthermore, the DateTimeFormatter's timezone offset letter ('O') does * Furthermore, the DateTimeFormatter's timezone offset letter ('O')
* not recognize UTC but recognizes GMT. According to * does not recognize UTC but recognizes GMT. According to
* https://en.wikipedia.org/wiki/Coordinated_Universal_Time, * https://en.wikipedia.org/wiki/Coordinated_Universal_Time, GMT only
* GMT only differs from UTC by at most 1 second and so substitution * differs from UTC by at most 1 second and so substitution will only
* will only introduce a trivial amount of error. * introduce a trivial amount of error.
*/ */
String reversedDateTimeWithGMT = reversedDateTime.replace("UTC", "GMT"); String reversedDateTimeWithGMT = reversedDateTime.replace("UTC", "GMT");
TemporalAccessor result = DATE_TIME_PARSER.parseBest(reversedDateTimeWithGMT, TemporalAccessor result = DATE_TIME_PARSER.parseBest(reversedDateTimeWithGMT,
@ -298,23 +300,20 @@ final class XRYCallsFileParser extends AbstractSingleKeyValueParser {
LocalDateTime::from, LocalDateTime::from,
OffsetDateTime::from); OffsetDateTime::from);
//Query for the ZoneID //Query for the ZoneID
if(result.query(TemporalQueries.zoneId()) == null) { if (result.query(TemporalQueries.zoneId()) == null) {
//If none, assumed GMT+0. //If none, assumed GMT+0.
return ZonedDateTime.of(LocalDateTime.from(result), return ZonedDateTime.of(LocalDateTime.from(result),
ZoneId.of("GMT")).toEpochSecond(); ZoneId.of("GMT")).toEpochSecond();
} else { } else {
return Instant.from(result).getEpochSecond(); return Instant.from(result).getEpochSecond();
} }
} }
/** /**
* Reverses the order of the date time components. * Reverses the order of the date time components.
* *
* Example: * Example: 1/3/1990 1:23:54 AM UTC+4 becomes UTC+4 AM 1:23:54 1/3/1990
* 1/3/1990 1:23:54 AM UTC+4 *
* becomes
* UTC+4 AM 1:23:54 1/3/1990
*
* @param dateTime * @param dateTime
* @return * @return
*/ */

View File

@ -49,23 +49,23 @@ final class XRYMessagesFileParser implements XRYFileParser {
private static final Logger logger = Logger.getLogger( private static final Logger logger = Logger.getLogger(
XRYMessagesFileParser.class.getName()); XRYMessagesFileParser.class.getName());
private static final String PARSER_NAME = "XRY DSP"; private static final String PARSER_NAME = "XRY DSP";
//Pattern is in reverse due to a Java 8 bug, see calculateSecondsSinceEpoch() //Pattern is in reverse due to a Java 8 bug, see calculateSecondsSinceEpoch()
//function for more details. //function for more details.
private static final DateTimeFormatter DATE_TIME_PARSER private static final DateTimeFormatter DATE_TIME_PARSER
= DateTimeFormatter.ofPattern("[(XXX) ][O ][(O) ]a h:m:s M/d/y"); = DateTimeFormatter.ofPattern("[(XXX) ][O ][(O) ]a h:m:s M/d/y");
private static final String DEVICE_LOCALE = "(device)"; private static final String DEVICE_LOCALE = "(device)";
private static final String NETWORK_LOCALE = "(network)"; private static final String NETWORK_LOCALE = "(network)";
private static final int READ = 1; private static final int READ = 1;
private static final int UNREAD = 0; private static final int UNREAD = 0;
/** /**
* All of the known XRY keys for message reports and the blackboard * All of the known XRY keys for message reports and their corresponding
* attribute types they map to. * blackboard attribute types, if any.
*/ */
private enum XryKey { private enum XryKey {
DELETED("deleted", BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ISDELETED), DELETED("deleted", BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ISDELETED),
@ -86,28 +86,28 @@ final class XRYMessagesFileParser implements XRYFileParser {
NAME("name", null), NAME("name", null),
INDEX("index", null), INDEX("index", null),
STATUS("status", null); STATUS("status", null);
private final String name; private final String name;
private final BlackboardAttribute.ATTRIBUTE_TYPE type; private final BlackboardAttribute.ATTRIBUTE_TYPE type;
XryKey(String name, BlackboardAttribute.ATTRIBUTE_TYPE type) { XryKey(String name, BlackboardAttribute.ATTRIBUTE_TYPE type) {
this.name = name; this.name = name;
this.type = type; this.type = type;
} }
public BlackboardAttribute.ATTRIBUTE_TYPE getType() { public BlackboardAttribute.ATTRIBUTE_TYPE getType() {
return type; return type;
} }
public String getDisplayName() { public String getDisplayName() {
return name; return name;
} }
/** /**
* Indicates if the display name of the XRY key is a recognized type. * Indicates if the display name of the XRY key is a recognized type.
* *
* @param xryKey * @param xryKey
* @return * @return
*/ */
public static boolean contains(String name) { public static boolean contains(String name) {
try { try {
@ -117,30 +117,30 @@ final class XRYMessagesFileParser implements XRYFileParser {
return false; return false;
} }
} }
/** /**
* Matches the display name of the xry key to the appropriate enum type. * Matches the display name of the xry key to the appropriate enum type.
* *
* It is assumed that XRY key string is recognized. Otherwise, * It is assumed that XRY key string is recognized. Otherwise, an
* an IllegalArgumentException is thrown. Test all membership * IllegalArgumentException is thrown. Test all membership with
* with contains() before hand. * contains() before hand.
* *
* @param xryKey * @param xryKey
* @return * @return
*/ */
public static XryKey fromDisplayName(String name) { public static XryKey fromDisplayName(String name) {
String normalizedName = name.trim().toLowerCase(); String normalizedName = name.trim().toLowerCase();
for(XryKey keyChoice : XryKey.values()) { for (XryKey keyChoice : XryKey.values()) {
if(normalizedName.equals(keyChoice.name)) { if (normalizedName.equals(keyChoice.name)) {
return keyChoice; return keyChoice;
} }
} }
throw new IllegalArgumentException(String.format("Key [ %s ] was not found." throw new IllegalArgumentException(String.format("Key [ %s ] was not found."
+ " All keys should be tested with contains.", name)); + " All keys should be tested with contains.", name));
} }
} }
/** /**
* All of the known XRY namespaces for message reports. * All of the known XRY namespaces for message reports.
*/ */
@ -149,18 +149,19 @@ final class XRYMessagesFileParser implements XRYFileParser {
PARTICIPANT("participant"), PARTICIPANT("participant"),
TO("to"), TO("to"),
NONE(null); NONE(null);
private final String name; private final String name;
XryNamespace(String name) { XryNamespace(String name) {
this.name = name; this.name = name;
} }
/** /**
* Indicates if the display name of the XRY namespace is a recognized type. * Indicates if the display name of the XRY namespace is a recognized
* * type.
*
* @param xryNamespace * @param xryNamespace
* @return * @return
*/ */
public static boolean contains(String xryNamespace) { public static boolean contains(String xryNamespace) {
try { try {
@ -170,30 +171,31 @@ final class XRYMessagesFileParser implements XRYFileParser {
return false; return false;
} }
} }
/** /**
* Matches the display name of the xry namespace to the appropriate enum type. * Matches the display name of the xry namespace to the appropriate enum
* * type.
* It is assumed that XRY namespace string is recognized. Otherwise, *
* an IllegalArgumentException is thrown. Test all membership * It is assumed that XRY namespace string is recognized. Otherwise, an
* with contains() before hand. * IllegalArgumentException is thrown. Test all membership with
* * contains() before hand.
*
* @param xryNamespace * @param xryNamespace
* @return * @return
*/ */
public static XryNamespace fromDisplayName(String xryNamespace) { public static XryNamespace fromDisplayName(String xryNamespace) {
String normalizedNamespace = xryNamespace.trim().toLowerCase(); String normalizedNamespace = xryNamespace.trim().toLowerCase();
for(XryNamespace keyChoice : XryNamespace.values()) { for (XryNamespace keyChoice : XryNamespace.values()) {
if(normalizedNamespace.equals(keyChoice.name)) { if (normalizedNamespace.equals(keyChoice.name)) {
return keyChoice; return keyChoice;
} }
} }
throw new IllegalArgumentException(String.format("Namespace [%s] was not found." throw new IllegalArgumentException(String.format("Namespace [%s] was not found."
+ " All namespaces should be tested with contains.", xryNamespace)); + " All namespaces should be tested with contains.", xryNamespace));
} }
} }
/** /**
* All known XRY meta keys for message reports. * All known XRY meta keys for message reports.
*/ */
@ -201,23 +203,22 @@ final class XRYMessagesFileParser implements XRYFileParser {
REFERENCE_NUMBER("reference number"), REFERENCE_NUMBER("reference number"),
SEGMENT_COUNT("segments"), SEGMENT_COUNT("segments"),
SEGMENT_NUMBER("segment number"); SEGMENT_NUMBER("segment number");
private final String name; private final String name;
XryMetaKey(String name) { XryMetaKey(String name) {
this.name = name; this.name = name;
} }
public String getDisplayName() { public String getDisplayName() {
return name; return name;
} }
/** /**
* Indicates if the display name of the XRY key is a recognized type. * Indicates if the display name of the XRY key is a recognized type.
* *
* @param xryKey * @param xryKey
* @return * @return
*/ */
public static boolean contains(String name) { public static boolean contains(String name) {
try { try {
@ -227,25 +228,25 @@ final class XRYMessagesFileParser implements XRYFileParser {
return false; return false;
} }
} }
/** /**
* Matches the display name of the xry key to the appropriate enum type. * Matches the display name of the xry key to the appropriate enum type.
* *
* It is assumed that XRY key string is recognized. Otherwise, * It is assumed that XRY key string is recognized. Otherwise, an
* an IllegalArgumentException is thrown. Test all membership * IllegalArgumentException is thrown. Test all membership with
* with contains() before hand. * contains() before hand.
* *
* @param xryKey * @param xryKey
* @return * @return
*/ */
public static XryMetaKey fromDisplayName(String name) { public static XryMetaKey fromDisplayName(String name) {
String normalizedName = name.trim().toLowerCase(); String normalizedName = name.trim().toLowerCase();
for(XryMetaKey keyChoice : XryMetaKey.values()) { for (XryMetaKey keyChoice : XryMetaKey.values()) {
if(normalizedName.equals(keyChoice.name)) { if (normalizedName.equals(keyChoice.name)) {
return keyChoice; return keyChoice;
} }
} }
throw new IllegalArgumentException(String.format("Key [ %s ] was not found." throw new IllegalArgumentException(String.format("Key [ %s ] was not found."
+ " All keys should be tested with contains.", name)); + " All keys should be tested with contains.", name));
} }
@ -253,11 +254,11 @@ final class XRYMessagesFileParser implements XRYFileParser {
/** /**
* Message-SMS report artifacts can span multiple XRY entities and their * Message-SMS report artifacts can span multiple XRY entities and their
* attributes can span multiple lines. The "Text" and "Message" keys are the only known key * attributes can span multiple lines. The "Text" and "Message" keys are the
* value pair that can span multiple lines. Messages can be segmented, * only known key value pair that can span multiple lines. Messages can be
* meaning that their "Text" and "Message" content can appear in multiple XRY entities. * segmented, meaning that their "Text" and "Message" content can appear in
* Our goal for a segmented message is to aggregate all of the text pieces and * multiple XRY entities. Our goal for a segmented message is to aggregate
* create 1 artifact. * all of the text pieces and create 1 artifact.
* *
* This parse implementation assumes that segments are contiguous and that * This parse implementation assumes that segments are contiguous and that
* they ascend incrementally. There are checks in place to verify this * they ascend incrementally. There are checks in place to verify this
@ -283,31 +284,27 @@ final class XRYMessagesFileParser implements XRYFileParser {
String xryEntity = reader.nextEntity(); String xryEntity = reader.nextEntity();
List<BlackboardAttribute> attributes = getBlackboardAttributes(xryEntity, reader, referenceNumbersSeen); List<BlackboardAttribute> attributes = getBlackboardAttributes(xryEntity, reader, referenceNumbersSeen);
//Only create artifacts with non-empty attributes. //Only create artifacts with non-empty attributes.
if(!attributes.isEmpty()) { if (!attributes.isEmpty()) {
BlackboardArtifact artifact = parent.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_MESSAGE); BlackboardArtifact artifact = parent.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_MESSAGE);
artifact.addAttributes(attributes); artifact.addAttributes(attributes);
} }
} }
} }
/** /**
* * Extracts all blackboard attributes from the XRY Entity. This function will
* @param xryEntity * unify any segmented text, if need be.
* @param reader
* @param referenceValues
* @return
* @throws IOException
*/ */
private List<BlackboardAttribute> getBlackboardAttributes(String xryEntity, private List<BlackboardAttribute> getBlackboardAttributes(String xryEntity,
XRYFileReader reader, Set<Integer> referenceValues) throws IOException { XRYFileReader reader, Set<Integer> referenceValues) throws IOException {
String[] xryLines = xryEntity.split("\n"); String[] xryLines = xryEntity.split("\n");
//First line of the entity is the title, each XRY entity is non-empty. //First line of the entity is the title, each XRY entity is non-empty.
logger.log(Level.INFO, String.format("[XRY DSP] Processing [ %s ]", xryLines[0])); logger.log(Level.INFO, String.format("[XRY DSP] Processing [ %s ]", xryLines[0]));
List<BlackboardAttribute> attributes = new ArrayList<>(); List<BlackboardAttribute> attributes = new ArrayList<>();
//Count the key value pairs in the XRY entity. //Count the key value pairs in the XRY entity.
int keyCount = countKeys(xryLines); int keyCount = getCountOfKeyValuePairs(xryLines);
for (int i = 1; i <= keyCount; i++) { for (int i = 1; i <= keyCount; i++) {
//Get the ith key value pair in the entity. Always expect to have //Get the ith key value pair in the entity. Always expect to have
//a valid value. //a valid value.
@ -333,10 +330,10 @@ final class XRYMessagesFileParser implements XRYFileParser {
//Assume text and message are the only fields that can be segmented //Assume text and message are the only fields that can be segmented
//among multiple XRY entities. //among multiple XRY entities.
if (pair.hasKey(XryKey.TEXT.getDisplayName()) || if (pair.hasKey(XryKey.TEXT.getDisplayName())
pair.hasKey(XryKey.MESSAGE.getDisplayName())) { || pair.hasKey(XryKey.MESSAGE.getDisplayName())) {
String segmentedText = getSegmentedText(xryLines, reader, referenceValues); String segmentedText = getSegmentedText(xryLines, reader, referenceValues);
pair = new XRYKeyValuePair(pair.getKey(), pair = new XRYKeyValuePair(pair.getKey(),
//Assume text is segmented by word. //Assume text is segmented by word.
pair.getValue() + " " + segmentedText, pair.getValue() + " " + segmentedText,
pair.getNamespace()); pair.getNamespace());
@ -348,18 +345,18 @@ final class XRYMessagesFileParser implements XRYFileParser {
attributes.add(attribute.get()); attributes.add(attribute.get());
} }
} }
return attributes; return attributes;
} }
/** /**
* Counts the key value pairs in an XRY entity. * Counts the key value pairs in an XRY entity. Skips counting the first
* Skips counting the first line as it is assumed to be the title. * line as it is assumed to be the title.
*/ */
private Integer countKeys(String[] xryEntity) { private Integer getCountOfKeyValuePairs(String[] xryEntity) {
int count = 0; int count = 0;
for (int i = 1; i < xryEntity.length; i++) { for (int i = 1; i < xryEntity.length; i++) {
if(XRYKeyValuePair.isPair(xryEntity[i])) { if (XRYKeyValuePair.isPair(xryEntity[i])) {
count++; count++;
} }
} }
@ -367,23 +364,23 @@ final class XRYMessagesFileParser implements XRYFileParser {
} }
/** /**
* Builds up segmented message entities so that the text is unified in the * Builds up segmented message entities so that the text is unified for a
* artifact. * single artifact.
* *
* @param referenceNumber Reference number that messages are group by * @param reader File reader that is producing XRY entities.
* @param segmentNumber Segment number of the starting segment. * @param referenceNumbersSeen All known references numbers up until this point.
* @param reader * @param xryEntity The source XRY entity.
* @return * @return
* @throws IOException * @throws IOException
*/ */
private String getSegmentedText(String[] xryEntity, XRYFileReader reader, private String getSegmentedText(String[] xryEntity, XRYFileReader reader,
Set<Integer> referenceNumbersSeen) throws IOException { Set<Integer> referenceNumbersSeen) throws IOException {
Optional<Integer> referenceNumber = getMetaKeyValue(xryEntity, XryMetaKey.REFERENCE_NUMBER); Optional<Integer> referenceNumber = getMetaKeyValue(xryEntity, XryMetaKey.REFERENCE_NUMBER);
//Check if there is any segmented text. //Check if there is any segmented text.
if(!referenceNumber.isPresent()) { if (!referenceNumber.isPresent()) {
return ""; return "";
} }
logger.log(Level.INFO, String.format("[XRY DSP] Message entity " logger.log(Level.INFO, String.format("[XRY DSP] Message entity "
+ "appears to be segmented with reference number [ %d ]", referenceNumber.get())); + "appears to be segmented with reference number [ %d ]", referenceNumber.get()));
@ -398,13 +395,13 @@ final class XRYMessagesFileParser implements XRYFileParser {
referenceNumbersSeen.add(referenceNumber.get()); referenceNumbersSeen.add(referenceNumber.get());
Optional<Integer> segmentNumber = getMetaKeyValue(xryEntity, XryMetaKey.SEGMENT_NUMBER); Optional<Integer> segmentNumber = getMetaKeyValue(xryEntity, XryMetaKey.SEGMENT_NUMBER);
if(!segmentNumber.isPresent()) { if (!segmentNumber.isPresent()) {
logger.log(Level.SEVERE, String.format("No segment " logger.log(Level.SEVERE, String.format("No segment "
+ "number was found on the message entity" + "number was found on the message entity"
+ "with reference number [%d]", referenceNumber.get())); + "with reference number [%d]", referenceNumber.get()));
return ""; return "";
} }
StringBuilder segmentedText = new StringBuilder(); StringBuilder segmentedText = new StringBuilder();
int currentSegmentNumber = segmentNumber.get(); int currentSegmentNumber = segmentNumber.get();
@ -414,8 +411,8 @@ final class XRYMessagesFileParser implements XRYFileParser {
String[] nextEntityLines = nextEntity.split("\n"); String[] nextEntityLines = nextEntity.split("\n");
Optional<Integer> nextReferenceNumber = getMetaKeyValue(nextEntityLines, XryMetaKey.REFERENCE_NUMBER); Optional<Integer> nextReferenceNumber = getMetaKeyValue(nextEntityLines, XryMetaKey.REFERENCE_NUMBER);
if (!nextReferenceNumber.isPresent() || if (!nextReferenceNumber.isPresent()
!Objects.equals(nextReferenceNumber, referenceNumber)) { || !Objects.equals(nextReferenceNumber, referenceNumber)) {
//Don't consume the next entity. It is not related //Don't consume the next entity. It is not related
//to the current message thread. //to the current message thread.
break; break;
@ -429,7 +426,7 @@ final class XRYMessagesFileParser implements XRYFileParser {
logger.log(Level.INFO, String.format("[XRY DSP] Processing [ %s ] " logger.log(Level.INFO, String.format("[XRY DSP] Processing [ %s ] "
+ "segment with reference number [ %d ]", nextEntityLines[0], referenceNumber.get())); + "segment with reference number [ %d ]", nextEntityLines[0], referenceNumber.get()));
if(!nextSegmentNumber.isPresent()) { if (!nextSegmentNumber.isPresent()) {
logger.log(Level.SEVERE, String.format("[XRY DSP] Segment with reference" logger.log(Level.SEVERE, String.format("[XRY DSP] Segment with reference"
+ " number [ %d ] did not have a segment number associated with it." + " number [ %d ] did not have a segment number associated with it."
+ " It cannot be determined if the reconstructed text will be in order.", referenceNumber.get())); + " It cannot be determined if the reconstructed text will be in order.", referenceNumber.get()));
@ -440,16 +437,16 @@ final class XRYMessagesFileParser implements XRYFileParser {
+ "text will be out of order.", nextSegmentNumber.get(), currentSegmentNumber)); + "text will be out of order.", nextSegmentNumber.get(), currentSegmentNumber));
} }
int keyCount = countKeys(nextEntityLines); int keyCount = getCountOfKeyValuePairs(nextEntityLines);
for (int i = 1; i <= keyCount; i++) { for (int i = 1; i <= keyCount; i++) {
XRYKeyValuePair pair = getKeyValuePairByIndex(nextEntityLines, i).get(); XRYKeyValuePair pair = getKeyValuePairByIndex(nextEntityLines, i).get();
if(pair.hasKey(XryKey.TEXT.getDisplayName()) || if (pair.hasKey(XryKey.TEXT.getDisplayName())
pair.hasKey(XryKey.MESSAGE.getDisplayName())) { || pair.hasKey(XryKey.MESSAGE.getDisplayName())) {
segmentedText.append(pair.getValue()).append(' '); segmentedText.append(pair.getValue()).append(' ');
} }
} }
if(nextSegmentNumber.isPresent()) { if (nextSegmentNumber.isPresent()) {
currentSegmentNumber = nextSegmentNumber.get(); currentSegmentNumber = nextSegmentNumber.get();
} }
} }
@ -458,24 +455,25 @@ final class XRYMessagesFileParser implements XRYFileParser {
if (segmentedText.length() > 0) { if (segmentedText.length() > 0) {
segmentedText.setLength(segmentedText.length() - 1); segmentedText.setLength(segmentedText.length() - 1);
} }
return segmentedText.toString(); return segmentedText.toString();
} }
/** /**
* Extracts the value of the XRY meta key, if any.
* *
* @param xryLines * @param xryLines XRY entity to extract from.
* @param metaKey * @param metaKey The key type to extract.
* @return * @return
*/ */
private Optional<Integer> getMetaKeyValue(String[] xryLines, XryMetaKey metaKey) { private Optional<Integer> getMetaKeyValue(String[] xryLines, XryMetaKey metaKey) {
for (String xryLine : xryLines) { for (String xryLine : xryLines) {
if (!XRYKeyValuePair.isPair(xryLine)) { if (!XRYKeyValuePair.isPair(xryLine)) {
continue; continue;
} }
XRYKeyValuePair pair = XRYKeyValuePair.from(xryLine); XRYKeyValuePair pair = XRYKeyValuePair.from(xryLine);
if(pair.hasKey(metaKey.getDisplayName())) { if (pair.hasKey(metaKey.getDisplayName())) {
try { try {
return Optional.of(Integer.parseInt(pair.getValue())); return Optional.of(Integer.parseInt(pair.getValue()));
} catch (NumberFormatException ex) { } catch (NumberFormatException ex) {
@ -488,42 +486,45 @@ final class XRYMessagesFileParser implements XRYFileParser {
} }
/** /**
* Extracts the ith XRY Key Value pair in the XRY Entity.
* *
* @param xryLines * The total number of pairs can be determined via getCountOfKeyValuePairs().
* @param index *
* @return * @param xryLines XRY entity.
* @param index The requested Key Value pair.
* @return
*/ */
private Optional<XRYKeyValuePair> getKeyValuePairByIndex(String[] xryLines, int index) { private Optional<XRYKeyValuePair> getKeyValuePairByIndex(String[] xryLines, int index) {
int pairsParsed = 0; int pairsParsed = 0;
String namespace = ""; String namespace = "";
for (int i = 1; i < xryLines.length; i++) { for (int i = 1; i < xryLines.length; i++) {
String xryLine = xryLines[i]; String xryLine = xryLines[i];
if(XryNamespace.contains(xryLine)) { if (XryNamespace.contains(xryLine)) {
namespace = xryLine.trim(); namespace = xryLine.trim();
continue; continue;
} }
if(!XRYKeyValuePair.isPair(xryLine)) { if (!XRYKeyValuePair.isPair(xryLine)) {
logger.log(Level.SEVERE, String.format("[XRY DSP] Expected a key value " logger.log(Level.SEVERE, String.format("[XRY DSP] Expected a key value "
+ "pair on this line (in brackets) [ %s ], but one was not detected." + "pair on this line (in brackets) [ %s ], but one was not detected."
+ " Discarding...", xryLine)); + " Discarding...", xryLine));
continue; continue;
} }
XRYKeyValuePair pair = XRYKeyValuePair.from(xryLine); XRYKeyValuePair pair = XRYKeyValuePair.from(xryLine);
String value = pair.getValue(); String value = pair.getValue();
//Build up multiple lines. //Build up multiple lines.
for (; (i+1) < xryLines.length for (; (i + 1) < xryLines.length
&& !XRYKeyValuePair.isPair(xryLines[i+1]) && !XRYKeyValuePair.isPair(xryLines[i + 1])
&& !XryNamespace.contains(xryLines[i+1]); i++) { && !XryNamespace.contains(xryLines[i + 1]); i++) {
String continuedValue = xryLines[i+1].trim(); String continuedValue = xryLines[i + 1].trim();
//Assume multi lined values are split by word. //Assume multi lined values are split by word.
value = value + " " + continuedValue; value = value + " " + continuedValue;
} }
pair = new XRYKeyValuePair(pair.getKey(), value, namespace); pair = new XRYKeyValuePair(pair.getKey(), value, namespace);
pairsParsed++; pairsParsed++;
if(pairsParsed == index) { if (pairsParsed == index) {
return Optional.of(pair); return Optional.of(pair);
} }
} }
@ -533,21 +534,21 @@ final class XRYMessagesFileParser implements XRYFileParser {
/** /**
* Creates an attribute from the extracted key value pair. * Creates an attribute from the extracted key value pair.
* *
* @param nameSpace The namespace of this key value pair. * @param nameSpace The namespace of this key value pair. It will have been
* It will have been verified beforehand, otherwise it will be NONE. * verified beforehand, otherwise it will be NONE.
* @param key The recognized XRY key. * @param key The recognized XRY key.
* @param value The value associated with that key. * @param value The value associated with that key.
* @return Corresponding blackboard attribute, if any. * @return Corresponding blackboard attribute, if any.
*/ */
private Optional<BlackboardAttribute> getBlackboardAttribute(XRYKeyValuePair pair) { private Optional<BlackboardAttribute> getBlackboardAttribute(XRYKeyValuePair pair) {
XryNamespace namespace = XryNamespace.NONE; XryNamespace namespace = XryNamespace.NONE;
if(XryNamespace.contains(pair.getNamespace())) { if (XryNamespace.contains(pair.getNamespace())) {
namespace = XryNamespace.fromDisplayName(pair.getNamespace()); namespace = XryNamespace.fromDisplayName(pair.getNamespace());
} }
XryKey key = XryKey.fromDisplayName(pair.getKey()); XryKey key = XryKey.fromDisplayName(pair.getKey());
String normalizedValue = pair.getValue().toLowerCase().trim(); String normalizedValue = pair.getValue().toLowerCase().trim();
switch (key) { switch (key) {
case TEL: case TEL:
case NUMBER: case NUMBER:
@ -565,13 +566,13 @@ final class XRYMessagesFileParser implements XRYFileParser {
return Optional.of(new BlackboardAttribute( return Optional.of(new BlackboardAttribute(
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER,
PARSER_NAME, pair.getValue())); PARSER_NAME, pair.getValue()));
} }
case TIME: case TIME:
try { try {
//Tranform value to seconds since epoch //Tranform value to seconds since epoch
long dateTimeSinceInEpoch = calculateSecondsSinceEpoch(pair.getValue()); long dateTimeSinceInEpoch = calculateSecondsSinceEpoch(pair.getValue());
return Optional.of(new BlackboardAttribute( return Optional.of(new BlackboardAttribute(
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_START, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_START,
PARSER_NAME, dateTimeSinceInEpoch)); PARSER_NAME, dateTimeSinceInEpoch));
} catch (DateTimeParseException ex) { } catch (DateTimeParseException ex) {
logger.log(Level.WARNING, String.format("[XRY DSP] Assumption" logger.log(Level.WARNING, String.format("[XRY DSP] Assumption"
@ -584,8 +585,8 @@ final class XRYMessagesFileParser implements XRYFileParser {
case "incoming": case "incoming":
case "outgoing": case "outgoing":
return Optional.of(new BlackboardAttribute( return Optional.of(new BlackboardAttribute(
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DIRECTION, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DIRECTION,
PARSER_NAME, pair.getValue())); PARSER_NAME, pair.getValue()));
case "deliver": case "deliver":
case "submit": case "submit":
case "status report": case "status report":
@ -600,11 +601,11 @@ final class XRYMessagesFileParser implements XRYFileParser {
switch (normalizedValue) { switch (normalizedValue) {
case "read": case "read":
return Optional.of(new BlackboardAttribute( return Optional.of(new BlackboardAttribute(
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_READ_STATUS, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_READ_STATUS,
PARSER_NAME, READ)); PARSER_NAME, READ));
case "unread": case "unread":
return Optional.of(new BlackboardAttribute( return Optional.of(new BlackboardAttribute(
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_READ_STATUS, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_READ_STATUS,
PARSER_NAME, UNREAD)); PARSER_NAME, UNREAD));
case "sending failed": case "sending failed":
case "deleted": case "deleted":
@ -620,15 +621,15 @@ final class XRYMessagesFileParser implements XRYFileParser {
default: default:
//Otherwise, the XryKey enum contains the correct BlackboardAttribute //Otherwise, the XryKey enum contains the correct BlackboardAttribute
//type. //type.
if(key.getType() != null) { if (key.getType() != null) {
return Optional.of(new BlackboardAttribute(key.getType(), return Optional.of(new BlackboardAttribute(key.getType(),
PARSER_NAME, pair.getValue())); PARSER_NAME, pair.getValue()));
} }
logger.log(Level.WARNING, String.format("[XRY DSP] Key value pair " logger.log(Level.WARNING, String.format("[XRY DSP] Key value pair "
+ "(in brackets) [ %s ] was recognized but " + "(in brackets) [ %s ] was recognized but "
+ "more data or time is needed to finish implementation. Discarding... ", pair)); + "more data or time is needed to finish implementation. Discarding... ", pair));
return Optional.empty(); return Optional.empty();
} }
} }
@ -648,7 +649,7 @@ final class XRYMessagesFileParser implements XRYFileParser {
result = result.substring(0, deviceIndex); result = result.substring(0, deviceIndex);
} }
int networkIndex = result.toLowerCase().indexOf(NETWORK_LOCALE); int networkIndex = result.toLowerCase().indexOf(NETWORK_LOCALE);
if(networkIndex != -1) { if (networkIndex != -1) {
result = result.substring(0, networkIndex); result = result.substring(0, networkIndex);
} }
return result; return result;
@ -664,28 +665,27 @@ final class XRYMessagesFileParser implements XRYFileParser {
String dateTimeWithoutLocale = removeDateTimeLocale(dateTime).trim(); String dateTimeWithoutLocale = removeDateTimeLocale(dateTime).trim();
/** /**
* The format of time in XRY Messages reports is of the form: * The format of time in XRY Messages reports is of the form:
* *
* 1/3/1990 1:23:54 AM UTC+4 * 1/3/1990 1:23:54 AM UTC+4
* *
* In our current version of Java (openjdk-1.8.0.222), there is * In our current version of Java (openjdk-1.8.0.222), there is a bug
* a bug with having the timezone offset (UTC+4 or GMT-7) at the * with having the timezone offset (UTC+4 or GMT-7) at the end of the
* end of the date time input. This is fixed in later versions * date time input. This is fixed in later versions of the JDK (9 and
* of the JDK (9 and beyond). * beyond). https://bugs.openjdk.java.net/browse/JDK-8154050 Rather than
* https://bugs.openjdk.java.net/browse/JDK-8154050 * update the JDK to accommodate this, the components of the date time
* Rather than update the JDK to accommodate this, the components of * string are reversed:
* the date time string are reversed: *
* * UTC+4 AM 1:23:54 1/3/1990
* UTC+4 AM 1:23:54 1/3/1990 *
*
* The java time package will correctly parse this date time format. * The java time package will correctly parse this date time format.
*/ */
String reversedDateTime = reverseOrderOfDateTimeComponents(dateTimeWithoutLocale); String reversedDateTime = reverseOrderOfDateTimeComponents(dateTimeWithoutLocale);
/** /**
* Furthermore, the DateTimeFormatter's timezone offset letter ('O') does * Furthermore, the DateTimeFormatter's timezone offset letter ('O')
* not recognize UTC but recognizes GMT. According to * does not recognize UTC but recognizes GMT. According to
* https://en.wikipedia.org/wiki/Coordinated_Universal_Time, * https://en.wikipedia.org/wiki/Coordinated_Universal_Time, GMT only
* GMT only differs from UTC by at most 1 second and so substitution * differs from UTC by at most 1 second and so substitution will only
* will only introduce a trivial amount of error. * introduce a trivial amount of error.
*/ */
String reversedDateTimeWithGMT = reversedDateTime.replace("UTC", "GMT"); String reversedDateTimeWithGMT = reversedDateTime.replace("UTC", "GMT");
TemporalAccessor result = DATE_TIME_PARSER.parseBest(reversedDateTimeWithGMT, TemporalAccessor result = DATE_TIME_PARSER.parseBest(reversedDateTimeWithGMT,
@ -693,23 +693,20 @@ final class XRYMessagesFileParser implements XRYFileParser {
LocalDateTime::from, LocalDateTime::from,
OffsetDateTime::from); OffsetDateTime::from);
//Query for the ZoneID //Query for the ZoneID
if(result.query(TemporalQueries.zoneId()) == null) { if (result.query(TemporalQueries.zoneId()) == null) {
//If none, assumed GMT+0. //If none, assumed GMT+0.
return ZonedDateTime.of(LocalDateTime.from(result), return ZonedDateTime.of(LocalDateTime.from(result),
ZoneId.of("GMT")).toEpochSecond(); ZoneId.of("GMT")).toEpochSecond();
} else { } else {
return Instant.from(result).getEpochSecond(); return Instant.from(result).getEpochSecond();
} }
} }
/** /**
* Reverses the order of the date time components. * Reverses the order of the date time components.
* *
* Example: * Example: 1/3/1990 1:23:54 AM UTC+4 becomes UTC+4 AM 1:23:54 1/3/1990
* 1/3/1990 1:23:54 AM UTC+4 *
* becomes
* UTC+4 AM 1:23:54 1/3/1990
*
* @param dateTime * @param dateTime
* @return * @return
*/ */