Skip to content

Commit cb5f6c1

Browse files
authored
Add Optional Sub Second precision Date/Instant Support (#94)
* initial for testing * remove old tests * Add support for Stix optional subsecond timestamp support with nano-second support * Add StixInstance class and related support changes to control subsecond precision * Remove println from test * fix incorrect import for Process.class * version bump * Update Date Precision support notes
1 parent f3f1eda commit cb5f6c1

39 files changed

+475
-187
lines changed

README.md

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,22 @@ https://github.com/StephenOTT/TAXII-springboot-bpmn
2525

2626
If you are looking for a gson based implementation, [CS-AWARE](https://cs-aware.eu) provides a [gson based implementation of the Stix2 library](https://github.com/cs-aware/stix2).
2727

28+
29+
## Sub-Second Precision Support
30+
31+
STIX-Java supports zero to 9 digit nanosecond precision with any date that is parsed by STIX-Java.
32+
This means that while a date that was sourced/generated by the STIX-Java library will be at sub-second precision of 3 digits,
33+
if you are parsing JSON with greater precision, or you supply a custom Instant with greater precision for a specific field,
34+
STIX-Java will support this and store the extra precision.
35+
36+
General rules to understand:
37+
38+
1. By default, timestamps generated natively by the STIX-Java library will be be with 3 digits of sub-second precision (millisecond precision).
39+
1. Sub-second precision from 0 to 9 digits (9 digits its nano second precision: `hh:mm:ss.999999999`) is supported. This means you can omit sub-seconds if you choose.
40+
1. The StixInstant.class supports a `toString()` method that will generate a STIX Spec Date with the original precision preserved
41+
1. Json parsing of JSON strings will support all of the above rules.
42+
43+
2844
-----
2945

3046
## Java Usage
@@ -217,8 +233,7 @@ class SomeClass {
217233
Optional<IdentitySdo> getCreatedByRef();
218234

219235
@NotNull
220-
@JsonSerialize(using = InstantSerializer.class)
221-
@JsonFormat(shape=JsonFormat.Shape.STRING, pattern = StixDataFormats.TIMESTAMP_PATTERN, timezone = "UTC")
236+
@JsonSerialize(using = StixInstantSerializer.class) @JsonDeserialize(using = StixInstantDeserializer.class)
222237
@JsonProperty("created")
223238
@Value.Default
224239
@Redactable(useMask = true)

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
<groupId>io.digitalstate.stix</groupId>
77
<artifactId>stix</artifactId>
8-
<version>v0.7.2</version>
8+
<version>v0.7.3</version>
99
<packaging>jar</packaging>
1010

1111
<name>STIX 2</name>

src/main/java/io/digitalstate/stix/common/StixCommonProperties.java

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@
33
import com.fasterxml.jackson.annotation.*;
44
import com.fasterxml.jackson.core.JsonProcessingException;
55
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
6+
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
67
import io.digitalstate.stix.bundle.BundleableObject;
78
import io.digitalstate.stix.datamarkings.GranularMarkingDm;
89
import io.digitalstate.stix.datamarkings.MarkingDefinitionDm;
9-
import io.digitalstate.stix.helpers.StixDataFormats;
10+
import io.digitalstate.stix.json.StixInstantDeserializer;
11+
import io.digitalstate.stix.json.StixInstantSerializer;
1012
import io.digitalstate.stix.json.StixParsers;
1113
import io.digitalstate.stix.json.converters.dehydrated.DomainObjectOptionalConverter;
1214
import io.digitalstate.stix.json.converters.dehydrated.MarkingDefinitionSetConverter;
@@ -23,7 +25,6 @@
2325
import javax.validation.constraints.Pattern;
2426
import javax.validation.constraints.Size;
2527
import javax.validation.groups.Default;
26-
import java.time.Instant;
2728
import java.util.Optional;
2829
import java.util.Set;
2930

@@ -70,13 +71,13 @@ default boolean getHydrated(){
7071
Optional<IdentitySdo> getCreatedByRef();
7172

7273
@NotNull
73-
@JsonFormat(shape=JsonFormat.Shape.STRING, pattern = StixDataFormats.TIMESTAMP_PATTERN, timezone = "UTC")
74+
@JsonSerialize(using = StixInstantSerializer.class) @JsonDeserialize(using = StixInstantDeserializer.class)
7475
@JsonProperty("created")
7576
@JsonPropertyDescription("The created property represents the time at which the first version of this object was created. The timstamp value MUST be precise to the nearest millisecond.")
7677
@Value.Default
7778
@Redactable(useMask = true)
78-
default Instant getCreated(){
79-
return Instant.now();
79+
default StixInstant getCreated(){
80+
return new StixInstant();
8081
}
8182

8283
@NotNull
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package io.digitalstate.stix.common;
2+
3+
import io.digitalstate.stix.helpers.StixDataFormats;
4+
5+
import java.time.Instant;
6+
import java.util.Objects;
7+
import java.util.regex.Matcher;
8+
import java.util.regex.Pattern;
9+
10+
/**
11+
* Stores a Java.time Instant and stores the original sub-second precision digital count.
12+
* SubSecond precision count means that the number of digits (even if all zeros / trailing zeros) are remembered.
13+
* Use toString() to get the Stix string format for the datetime.
14+
*/
15+
public class StixInstant {
16+
17+
public static final Pattern REGEX_SUBSECOND = Pattern.compile("(?<fullDate>\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(.(?<subSecond>[0-9]+))?Z)");
18+
19+
private final Instant instant;
20+
private final int originalSubSecondPrecisionDigitCount;
21+
22+
/**
23+
* Generates a Instant.now()
24+
*/
25+
public StixInstant() {
26+
Instant now = Instant.now();
27+
this.instant = now;
28+
this.originalSubSecondPrecisionDigitCount = 3;
29+
}
30+
31+
public StixInstant(Instant instant, int subSecondPrecision) {
32+
Objects.requireNonNull(instant);
33+
34+
this.instant = instant;
35+
this.originalSubSecondPrecisionDigitCount = subSecondPrecision;
36+
}
37+
38+
/**
39+
* Defaults with parsed Instant's Nano second precision digit count.
40+
* Note that trailing zeros would have been stripped.
41+
* If you need to keep exact precision, including the trailing zeros, use {@link StixInstant#StixInstant(Instant, int)}
42+
* @param instant
43+
*/
44+
public StixInstant(Instant instant) {
45+
this(instant, String.valueOf(instant.getNano()).length());
46+
}
47+
48+
/**
49+
* Get the underlying Instant value. If you need the Stix Date with the Stix Precision then use {@link StixInstant#toString()}
50+
* getInstant() should only be used in special cases where you need access to perform temporal work.
51+
* @return
52+
*/
53+
public Instant getInstant() {
54+
return instant;
55+
}
56+
57+
public int getOriginalSubSecondPrecisionDigitCount() {
58+
return originalSubSecondPrecisionDigitCount;
59+
}
60+
61+
/**
62+
* Generates a STIX Spec String of DateTime.
63+
* Uses {@link StixDataFormats#getWriterStixDateTimeFormatter(int)}
64+
* @return
65+
*/
66+
@Override
67+
public String toString() {
68+
return StixDataFormats.getWriterStixDateTimeFormatter(originalSubSecondPrecisionDigitCount)
69+
.format(this.instant);
70+
}
71+
72+
public static StixInstant parse(String dateString){
73+
Instant instant = Instant.from(StixDataFormats.getReaderStixDateTimeFormatter().parse(dateString));
74+
int subSecondCount = getSubSecondDigitCount(dateString);
75+
return new StixInstant(instant, subSecondCount);
76+
}
77+
78+
private static int getSubSecondDigitCount(String dateString){
79+
Matcher matcher = REGEX_SUBSECOND.matcher(dateString);
80+
if (matcher.find()){
81+
String subSeconds = matcher.group("subSecond");
82+
// If no sub seconds were provided then return 0 as the precision
83+
if (subSeconds == null){
84+
return 0;
85+
} else {
86+
return subSeconds.length();
87+
}
88+
89+
} else {
90+
throw new IllegalStateException("Unable to parse date");
91+
}
92+
}
93+
}

src/main/java/io/digitalstate/stix/common/StixModified.java

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
package io.digitalstate.stix.common;
22

3-
import com.fasterxml.jackson.annotation.JsonFormat;
43
import com.fasterxml.jackson.annotation.JsonProperty;
54
import com.fasterxml.jackson.annotation.JsonPropertyDescription;
6-
import io.digitalstate.stix.helpers.StixDataFormats;
5+
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
6+
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
7+
import io.digitalstate.stix.json.StixInstantDeserializer;
8+
import io.digitalstate.stix.json.StixInstantSerializer;
79
import io.digitalstate.stix.redaction.Redactable;
810
import org.immutables.value.Value;
911

@@ -19,10 +21,10 @@ public interface StixModified {
1921
@NotNull
2022
@JsonProperty("modified")
2123
@JsonPropertyDescription("The modified property represents the time that this particular version of the object was created. The timstamp value MUST be precise to the nearest millisecond.")
22-
@JsonFormat(pattern = StixDataFormats.TIMESTAMP_PATTERN, timezone = "UTC")
24+
@JsonSerialize(using = StixInstantSerializer.class) @JsonDeserialize(using = StixInstantDeserializer.class)
2325
@Value.Default
2426
@Redactable
25-
default Instant getModified(){
26-
return Instant.now();
27+
default StixInstant getModified(){
28+
return new StixInstant();
2729
}
2830
}

src/main/java/io/digitalstate/stix/coo/extension/types/NtfsFileExtenstionExt.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
import io.digitalstate.stix.coo.extension.CyberObservableExtension;
77
import io.digitalstate.stix.coo.objects.FileCoo;
88
import io.digitalstate.stix.coo.types.NtfsAlternateDataStreamObj;
9-
import io.digitalstate.stix.validation.contraints.coo.allowedparents.AllowedParents;
109
import io.digitalstate.stix.validation.contraints.businessrule.BusinessRule;
10+
import io.digitalstate.stix.validation.contraints.coo.allowedparents.AllowedParents;
1111
import io.digitalstate.stix.validation.contraints.defaulttypevalue.DefaultTypeValue;
1212
import io.digitalstate.stix.validation.groups.DefaultValuesProcessor;
1313
import org.immutables.serial.Serial;

src/main/java/io/digitalstate/stix/coo/extension/types/PdfFileExtensionExt.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
66
import io.digitalstate.stix.coo.extension.CyberObservableExtension;
77
import io.digitalstate.stix.coo.objects.FileCoo;
8-
import io.digitalstate.stix.validation.contraints.coo.allowedparents.AllowedParents;
98
import io.digitalstate.stix.validation.contraints.businessrule.BusinessRule;
9+
import io.digitalstate.stix.validation.contraints.coo.allowedparents.AllowedParents;
1010
import io.digitalstate.stix.validation.contraints.defaulttypevalue.DefaultTypeValue;
1111
import io.digitalstate.stix.validation.groups.DefaultValuesProcessor;
1212
import org.immutables.serial.Serial;

src/main/java/io/digitalstate/stix/coo/extension/types/RasterImageFileExtensionExt.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
import org.immutables.serial.Serial;
1313
import org.immutables.value.Value;
1414

15-
import javax.validation.Valid;
1615
import java.util.Map;
1716
import java.util.Optional;
1817

src/main/java/io/digitalstate/stix/coo/extension/types/TcpExtensionExt.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
66
import io.digitalstate.stix.coo.extension.CyberObservableExtension;
77
import io.digitalstate.stix.coo.objects.NetworkTrafficCoo;
8-
import io.digitalstate.stix.validation.contraints.coo.allowedparents.AllowedParents;
98
import io.digitalstate.stix.validation.contraints.businessrule.BusinessRule;
9+
import io.digitalstate.stix.validation.contraints.coo.allowedparents.AllowedParents;
1010
import io.digitalstate.stix.validation.contraints.defaulttypevalue.DefaultTypeValue;
1111
import io.digitalstate.stix.validation.groups.DefaultValuesProcessor;
1212
import org.immutables.serial.Serial;

src/main/java/io/digitalstate/stix/coo/extension/types/UnixAccountExtensionExt.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
66
import io.digitalstate.stix.coo.extension.CyberObservableExtension;
77
import io.digitalstate.stix.coo.objects.UserAccountCoo;
8-
import io.digitalstate.stix.validation.contraints.coo.allowedparents.AllowedParents;
98
import io.digitalstate.stix.validation.contraints.businessrule.BusinessRule;
9+
import io.digitalstate.stix.validation.contraints.coo.allowedparents.AllowedParents;
1010
import io.digitalstate.stix.validation.contraints.defaulttypevalue.DefaultTypeValue;
1111
import io.digitalstate.stix.validation.groups.DefaultValuesProcessor;
1212
import org.immutables.serial.Serial;

src/main/java/io/digitalstate/stix/coo/extension/types/WindowsPeBinaryFileExtensionExt.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@
33
import com.fasterxml.jackson.annotation.*;
44
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
55
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
6+
import io.digitalstate.stix.common.StixInstant;
67
import io.digitalstate.stix.coo.extension.CyberObservableExtension;
78
import io.digitalstate.stix.coo.objects.FileCoo;
89
import io.digitalstate.stix.coo.types.WindowsPeOptionalHeaderObj;
910
import io.digitalstate.stix.coo.types.WindowsPeSectionObj;
10-
import io.digitalstate.stix.helpers.StixDataFormats;
11+
import io.digitalstate.stix.json.StixOptionalInstantDeserializer;
12+
import io.digitalstate.stix.json.StixOptionalInstantSerializer;
1113
import io.digitalstate.stix.validation.contraints.coo.allowedparents.AllowedParents;
1214
import io.digitalstate.stix.validation.contraints.defaulttypevalue.DefaultTypeValue;
1315
import io.digitalstate.stix.validation.contraints.hashingvocab.HashingVocab;
@@ -66,8 +68,8 @@ public interface WindowsPeBinaryFileExtensionExt extends CyberObservableExtensio
6668

6769
@JsonProperty("time_date_stamp")
6870
@JsonPropertyDescription("Specifies the time when the PE binary was created. The timestamp value MUST BE precise to the second.")
69-
@JsonFormat(shape=JsonFormat.Shape.STRING, pattern = StixDataFormats.TIMESTAMP_PATTERN, timezone = "UTC")
70-
Optional<Instant> getTimeDateStamp();
71+
@JsonSerialize(using = StixOptionalInstantSerializer.class) @JsonDeserialize(using = StixOptionalInstantDeserializer.class)
72+
Optional<StixInstant> getTimeDateStamp();
7173

7274
@JsonProperty("pointer_to_symbol_table_hex")
7375
@JsonPropertyDescription("Specifies the file offset of the COFF symbol table.")

src/main/java/io/digitalstate/stix/coo/extension/types/WindowsProcessExtensionExt.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
66
import io.digitalstate.stix.coo.extension.CyberObservableExtension;
77
import io.digitalstate.stix.coo.objects.ProcessCoo;
8-
import io.digitalstate.stix.validation.contraints.coo.allowedparents.AllowedParents;
98
import io.digitalstate.stix.validation.contraints.businessrule.BusinessRule;
9+
import io.digitalstate.stix.validation.contraints.coo.allowedparents.AllowedParents;
1010
import io.digitalstate.stix.validation.contraints.defaulttypevalue.DefaultTypeValue;
1111
import io.digitalstate.stix.validation.groups.DefaultValuesProcessor;
1212
import org.immutables.serial.Serial;

src/main/java/io/digitalstate/stix/coo/extension/types/WindowsServiceExtensionExt.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
import org.immutables.value.Value;
1717

1818
import javax.validation.constraints.NotBlank;
19-
import javax.validation.constraints.NotNull;
2019
import java.util.Optional;
2120
import java.util.Set;
2221

src/main/java/io/digitalstate/stix/coo/objects/DirectoryCoo.java

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@
33
import com.fasterxml.jackson.annotation.*;
44
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
55
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
6+
import io.digitalstate.stix.common.StixInstant;
67
import io.digitalstate.stix.coo.CyberObservableObject;
7-
import io.digitalstate.stix.helpers.StixDataFormats;
8+
import io.digitalstate.stix.json.StixOptionalInstantDeserializer;
9+
import io.digitalstate.stix.json.StixOptionalInstantSerializer;
810
import io.digitalstate.stix.validation.contraints.defaulttypevalue.DefaultTypeValue;
911
import io.digitalstate.stix.validation.groups.DefaultValuesProcessor;
1012
import org.immutables.serial.Serial;
@@ -44,20 +46,20 @@ public interface DirectoryCoo extends CyberObservableObject {
4446
Optional<@Pattern(regexp = "^[a-zA-Z0-9/\\.+_:-]{2,250}$")
4547
String> getPathEnc();
4648

47-
@JsonFormat(pattern = StixDataFormats.TIMESTAMP_PATTERN, timezone = "UTC")
49+
@JsonSerialize(using = StixOptionalInstantSerializer.class) @JsonDeserialize(using = StixOptionalInstantDeserializer.class)
4850
@JsonProperty("created")
4951
@JsonPropertyDescription("Specifies the date/time the directory was created.")
50-
Optional<Instant> getCreated();
52+
Optional<StixInstant> getCreated();
5153

52-
@JsonFormat(pattern = StixDataFormats.TIMESTAMP_PATTERN, timezone = "UTC")
54+
@JsonSerialize(using = StixOptionalInstantSerializer.class) @JsonDeserialize(using = StixOptionalInstantDeserializer.class)
5355
@JsonProperty("modified")
5456
@JsonPropertyDescription("Specifies the date/time the directory was last written to/modified.")
55-
Optional<Instant> getModified();
57+
Optional<StixInstant> getModified();
5658

57-
@JsonFormat(pattern = StixDataFormats.TIMESTAMP_PATTERN, timezone = "UTC")
59+
@JsonSerialize(using = StixOptionalInstantSerializer.class) @JsonDeserialize(using = StixOptionalInstantDeserializer.class)
5860
@JsonProperty("accessed")
5961
@JsonPropertyDescription("Specifies the date/time the directory was last accessed.")
60-
Optional<Instant> getAccessed();
62+
Optional<StixInstant> getAccessed();
6163

6264
//@TODO add proper support for contains refs. Must be Set of File or Directory types
6365
@JsonProperty("contains_refs")

src/main/java/io/digitalstate/stix/coo/objects/EmailMessageCoo.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,17 @@
33
import com.fasterxml.jackson.annotation.*;
44
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
55
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
6+
import io.digitalstate.stix.common.StixInstant;
67
import io.digitalstate.stix.coo.CyberObservableObject;
78
import io.digitalstate.stix.coo.types.MimePartTypeObj;
8-
import io.digitalstate.stix.helpers.StixDataFormats;
9+
import io.digitalstate.stix.json.StixOptionalInstantDeserializer;
10+
import io.digitalstate.stix.json.StixOptionalInstantSerializer;
911
import io.digitalstate.stix.validation.contraints.businessrule.BusinessRule;
1012
import io.digitalstate.stix.validation.contraints.defaulttypevalue.DefaultTypeValue;
1113
import io.digitalstate.stix.validation.groups.DefaultValuesProcessor;
1214
import org.immutables.serial.Serial;
1315
import org.immutables.value.Value;
1416

15-
import javax.validation.Valid;
1617
import javax.validation.constraints.NotNull;
1718
import java.time.Instant;
1819
import java.util.Map;
@@ -42,10 +43,10 @@ public interface EmailMessageCoo extends CyberObservableObject {
4243
@NotNull
4344
boolean isMultipart();
4445

45-
@JsonFormat(pattern = StixDataFormats.TIMESTAMP_PATTERN, timezone = "UTC")
46+
@JsonSerialize(using = StixOptionalInstantSerializer.class) @JsonDeserialize(using = StixOptionalInstantDeserializer.class)
4647
@JsonProperty("date")
4748
@JsonPropertyDescription("Specifies the date/time that the email message was sent.")
48-
Optional<Instant> getDate();
49+
Optional<StixInstant> getDate();
4950

5051
@JsonProperty("content_type")
5152
@JsonPropertyDescription("Specifies the value of the 'Content-Type' header of the email message.")

0 commit comments

Comments
 (0)