diff --git a/ASN.1 schema/v1_1/ApplicationData.asn1 b/ASN.1 schema/v1_1/ApplicationData.asn1
index b7b4deae..c9449d06 100644
--- a/ASN.1 schema/v1_1/ApplicationData.asn1
+++ b/ASN.1 schema/v1_1/ApplicationData.asn1
@@ -39,8 +39,7 @@ MP_UserLoggingInResp ::= SEQUENCE
tokenExpiration Timestamp OPTIONAL,
vinList SEQUENCE SIZE(1..256) OF VinInfo OPTIONAL,
userPhoto IA5String(SIZE(1..128)) OPTIONAL,
- userName IA5String(SIZE(8..12)),
- languageType LanguageType OPTIONAL
+ userName IA5String(SIZE(8..12))
}
APPUpgradeInfoReq ::= SEQUENCE
{
diff --git a/ASN.1 schema/v2_0/ApplicationData.asn1 b/ASN.1 schema/v2_0/ApplicationData.asn1
new file mode 100644
index 00000000..b2ff365b
--- /dev/null
+++ b/ASN.1 schema/v2_0/ApplicationData.asn1
@@ -0,0 +1,134 @@
+ApplicationDataModule
+
+DEFINITIONS
+AUTOMATIC TAGS ::=
+BEGIN
+MP_SecurityAlarmResp ::= SEQUENCE
+{
+ securityAlarms SEQUENCE SIZE(1..256) OF SecurityAlarm OPTIONAL
+}
+OTA_RVMVehicleStatusReq ::= SEQUENCE
+{
+ vehStatusReqType INTEGER(0..255)
+}
+OTA_RVMVehicleStatusResp ::= SEQUENCE
+{
+ statusTime INTEGER(0..2147483647),
+ gpsPosition RvsPosition(1),
+ basicVehicleStatus RvsBasicStatus(1),
+ extendedVehicleStatus RvsExtStatus(1) OPTIONAL
+}
+OTA_RVCReq ::= SEQUENCE
+{
+ rvcReqType OCTET STRING(SIZE(1)),
+ rvcParams SEQUENCE SIZE(1..10) OF RvcReqParam OPTIONAL
+}
+OTA_RVCStatus ::= SEQUENCE
+{
+ rvcReqType OCTET STRING(SIZE(1)),
+ rvcReqSts OCTET STRING(SIZE(1)),
+ failureType INTEGER(0..255) OPTIONAL,
+ gpsPosition RvsPosition(1),
+ basicVehicleStatus RvsBasicStatus(1)
+}
+SecurityAlarm ::= SEQUENCE
+{
+ title OCTET STRING(SIZE(1..128)) OPTIONAL,
+ content OCTET STRING(SIZE(1..2048)) OPTIONAL,
+ messageType IA5String(SIZE(3)),
+ vin IA5String(SIZE(17)),
+ alertId INTEGER(0..65535),
+ alertTime INTEGER(0..2147483647) OPTIONAL,
+ latitude INTEGER(-90000000..90000000),
+ longitude INTEGER(-180000000..180000000)
+}
+RvsPosition ::= SEQUENCE
+{
+ wayPoint RvsWayPoint,
+ timestamp4Short Timestamp4Short,
+ gpsStatus GPSStatus
+}
+RvsBasicStatus ::= SEQUENCE
+{
+ driverDoor BOOLEAN,
+ passengerDoor BOOLEAN,
+ rearLeftDoor BOOLEAN,
+ rearRightDoor BOOLEAN,
+ bootStatus BOOLEAN,
+ bonnetStatus BOOLEAN,
+ lockStatus BOOLEAN,
+ driverWindow BOOLEAN OPTIONAL,
+ passengerWindow BOOLEAN OPTIONAL,
+ rearLeftWindow BOOLEAN OPTIONAL,
+ rearRightWindow BOOLEAN OPTIONAL,
+ sunroofStatus BOOLEAN OPTIONAL,
+ frontRrightTyrePressure INTEGER(0..255) OPTIONAL,
+ frontLeftTyrePressure INTEGER(0..255) OPTIONAL,
+ rearRightTyrePressure INTEGER(0..255) OPTIONAL,
+ rearLeftTyrePressure INTEGER(0..255) OPTIONAL,
+ wheelTyreMonitorStatus INTEGER(0..255) OPTIONAL,
+ sideLightStatus BOOLEAN,
+ dippedBeamStatus BOOLEAN,
+ mainBeamStatus BOOLEAN,
+ vehicleAlarmStatus INTEGER(0..255) OPTIONAL,
+ engineStatus INTEGER(0..255),
+ powerMode INTEGER(0..255),
+ lastKeySeen INTEGER(0..65535),
+ currentjourneyDistance INTEGER(0..65535),
+ currentJourneyID INTEGER(0..2147483647),
+ interiorTemperature INTEGER(-128..127),
+ exteriorTemperature INTEGER(-128..127),
+ fuelLevelPrc INTEGER(0..255),
+ fuelRange INTEGER(0..65535),
+ remoteClimateStatus INTEGER(0..255),
+ frontLeftSeatHeatLevel INTEGER(0..255) OPTIONAL,
+ frontRightSeatHeatLevel INTEGER(0..255) OPTIONAL,
+ canBusActive BOOLEAN,
+ timeOfLastCANBUSActivity INTEGER(0..2147483647),
+ clstrDspdFuelLvlSgmt INTEGER(0..255),
+ mileage INTEGER(0..2147483647),
+ batteryVoltage INTEGER(0..65535),
+ extendedData1 INTEGER(0..2147483647) OPTIONAL,
+ extendedData2 INTEGER(0..2147483647) OPTIONAL
+}
+RvsExtStatus ::= SEQUENCE
+{
+ vehicleAlerts SEQUENCE SIZE(0..64) OF VehicleAlertInfo
+}
+RvcReqParam ::= SEQUENCE
+{
+ paramId INTEGER(0..65535),
+ paramValue OCTET STRING(SIZE(1..255))
+}
+RvsWayPoint ::= SEQUENCE
+{
+ position RvsWGS84Point,
+ heading INTEGER(0..359),
+ speed INTEGER(-1000..4500),
+ hdop INTEGER(0..1000),
+ satellites INTEGER(0..16)
+}
+Timestamp4Short ::= SEQUENCE
+{
+ seconds INTEGER(0..2147483647)
+}
+GPSStatus ::= ENUMERATED
+{
+ noGpsSignal(0),
+ timeFix(1),
+ fix2D(2),
+ fix3D(3)
+}
+VehicleAlertInfo ::= SEQUENCE
+{
+ id INTEGER(0..255),
+ value INTEGER(0..255)
+}
+RvsWGS84Point ::= SEQUENCE
+{
+ latitude INTEGER(-90000000..90000000),
+ longitude INTEGER(-180000000..180000000),
+ altitude INTEGER(-100..8900)
+}
+
+END
diff --git a/ASN.1 schema/v2_0/MP_DispatcherBody.asn1 b/ASN.1 schema/v2_0/MP_DispatcherBody.asn1
new file mode 100644
index 00000000..30177a4e
--- /dev/null
+++ b/ASN.1 schema/v2_0/MP_DispatcherBody.asn1
@@ -0,0 +1,33 @@
+MP_DispatcherBodyModule
+
+DEFINITIONS
+AUTOMATIC TAGS ::=
+BEGIN
+MP_DispatcherBody ::= SEQUENCE
+{
+ uid IA5String(SIZE(50)) OPTIONAL,
+ token IA5String(SIZE(40)) OPTIONAL,
+ applicationID IA5String(SIZE(3)),
+ vin IA5String(SIZE(17)) OPTIONAL,
+ messageID INTEGER(0..255),
+ eventCreationTime INTEGER(0..2147483647),
+ eventID INTEGER(0..2147483647) OPTIONAL,
+ ulMessageCounter INTEGER(0..65535) OPTIONAL,
+ dlMessageCounter INTEGER(0..65535) OPTIONAL,
+ ackMessageCounter INTEGER(0..65535) OPTIONAL,
+ ackRequired BOOLEAN OPTIONAL,
+ applicationDataLength INTEGER(0..65535) OPTIONAL,
+ applicationDataEncoding DataEncodingType OPTIONAL,
+ applicationDataProtocolVersion INTEGER(0..65535) OPTIONAL,
+ testFlag INTEGER(1..3) OPTIONAL,
+ result INTEGER(0..65535) OPTIONAL,
+ errorMessage OCTET STRING(SIZE(1..1024)) OPTIONAL
+}
+DataEncodingType ::= ENUMERATED
+{
+ perUnaligned(0),
+ der(1),
+ ber(2)
+}
+
+END
diff --git a/ASN.1 schema/v2_0/MP_DispatcherHeader.asn1 b/ASN.1 schema/v2_0/MP_DispatcherHeader.asn1
new file mode 100644
index 00000000..103e8183
--- /dev/null
+++ b/ASN.1 schema/v2_0/MP_DispatcherHeader.asn1
@@ -0,0 +1,13 @@
+MP_DispatcherHeaderModule
+
+DEFINITIONS
+AUTOMATIC TAGS ::=
+BEGIN
+MP_DispatcherHeader ::= SEQUENCE
+{
+ dispatcherBodyEncoding INTEGER(0..2),
+ dispatcherMessageLength INTEGER(0..255),
+ protocolVersion INTEGER(0..255)
+}
+
+END
diff --git a/docs/examples/v1_1/501_513_response.per b/docs/examples/v1_1/501_513_response.per
new file mode 100644
index 00000000..bc3d1535
--- /dev/null
+++ b/docs/examples/v1_1/501_513_response.per
@@ -0,0 +1 @@
+0963111008100D82E60C183060C183060C183060C183060C183060C183060C183060C183060C183062C183060C183062C1B3968D58346F93163C3898E36AD5A3772C70B7698F23864DD862C393235CAE3330CB918B46D85AB062C9376940000000000000040202468ACF1343530ECA864468ACF1342468ACF13420000085C0100A00000FC88F2A54DCCC3133C741B7082E3B79D3A5C6FEEC6CB6E7DE3D4A529B73E5DB6CFEF2A8B065919B165972B772D71E5C591B31C795AB06589AE370C5AE6CCDB2E36AD59B5CAD5B3261A0D1803FCCA72ABB768C1CB8976993372D5B335B54D8B15B1429A8E1D351C81694C81155842A54A952A5509CC60B260C5C9C86DEEOCADCDOC2CECADC4084D8EACBEAEEC3B72BAA9A796541C3965E7CFAF2CA836EFDDA7A6FE5A776741CFCF3E9976ACC7BF26575298B759DBOECEB95D3177BBOEDCAEA965CFD7661E4830E9E58B0E7E6B31EFC995D5160C1676C3B3AE574C5DEEC3B72BA8DCB7EEE8B79E5C3D1061D3CB161CFCD663DF932BAA2C18ACED87675CAE98BBDD876E575074F2C5873A0E7DF4F4C7A1663DF932BAA2C1CACED87675CAE983BDD876E5754FAEE414B7EFCCB31EFC995D5366D5676C3B3AE574C1DEEC3B72BAA5976EFE995063DFBBA72DFB1663DF932BAA6D98ACED87675CAE98BBDD876E575074F2418F7EEC9A7A69DFBB4EECEB31EFC995D5462C5676C3B3AE574C5DEEC3B72BA8BB32E3E9CB4E341437F7CBC9053E9972F2D3BB3ACC7BF265751685359DBOECEB95D3177BBOEDCAEA9E5C7D7969E9E5061D997974598F7E4CAEA9C16CD1676C3B3AE574C183060C183060C183060C183060C183160C183060C18B060C583162C183060C183060C183060C183060C183060C183060C183060C183160EF761DB95D42DFBB775E8829F4C3D3AF3598F7E4CAEA14F9D3AAD459DBOECEB95D3177BBOEDCAEA26FDFC9053E987A75E6B31EFC995D449F3E92CED87675CAE98B162C5DEEC3B72BA85BF7F4414FA61E9D79ACC7BF265750A7CFA8B3B61D9D72BA62EF761DB95D57D3BB26FEE829F4C3D3AF3598F7E4CAEABC99D127D759DBOECEB95D3060C183BDD876E575532EDE197961E9D7965598F7E4CAEAA459B422D28352AD28AB3B61D9D72BA6OEF761DB95D45DD9F4EECA829F4C3D3AF3598F7E4CAEA2CE8F2674559DBOECEB95D3177BBOEDCAEA965DBBFA6541074F2410F7EEE9CB7EC598F7E4CAEAA316361676C3B3AE574C5DEEC3B72BA8DCB7EEE8B70F8D995054D3CB2ACC7BF26575298B78CB3B61D9D72BA62E582346C9AB077BBOEDCAEA965C3C96E1F1B32A0A9A7965598F7E4CAEA5316F49676C3B3AE574C5CB0468D93560EF761DB95D4BCBE5050DFCF4F4D3BF72CC7BF265752E2D9A13E9ACED87675CAE98BBDD876E57517665C7D3969C682B65D1A71ECCAB31EFC995D45ACB3B61D9D72BA62EF761DB95D45AC821E8C3CB3E9DD9D04DDF932EC598F7E4CAEA2D69AB3B61D9D72BA62C5DEEC3B72BA9B1D057C3B3665E8B31EFC995D4D8F5D676C3B3AE574C5DEEC3B72BA8587A74CBCBCAOADBF674C39F2ACC7BF265750A0D4A91695959DBOECEB95D3176BF149950C18366CC183060C183966E1BB864C1C365CC983260BD8395EC9B2F6F9B1B27391B335AC99B86EB5A39C9996B8CACDAAD64D70B9CAD9930CCC98B54C1C3768C593060D1A0
\ No newline at end of file
diff --git a/saic-java-api/pom.xml b/saic-java-api/pom.xml
index a4d5b1bb..7330827f 100644
--- a/saic-java-api/pom.xml
+++ b/saic-java-api/pom.xml
@@ -128,6 +128,66 @@
+
+ generate-dispatcher-header-v2_0
+
+ java
+
+ process-sources
+
+ org.bn.compiler.Main
+
+ -m
+ java
+ -o
+ ${project.build.directory}/generated-sources/asn1-java/net/heberling/ismart/asn1/v2_0
+ -f
+ ${project.basedir}/../ASN.1 schema/v2_0/MP_DispatcherHeader.asn1
+ -ns
+ net.heberling.ismart.asn1.v2_0
+
+
+
+
+ generate-dispatcher-body-mp-v2_0
+
+ java
+
+ process-sources
+
+ org.bn.compiler.Main
+
+ -m
+ java
+ -o
+ ${project.build.directory}/generated-sources/asn1-java/net/heberling/ismart/asn1/v2_0
+ -f
+ ${project.basedir}/../ASN.1 schema/v2_0/MP_DispatcherBody.asn1
+ -ns
+ net.heberling.ismart.asn1.v2_0
+
+
+
+
+ generate-application-data-mp-v2_0
+
+ java
+
+ process-sources
+
+ org.bn.compiler.Main
+
+ -m
+ java
+ -o
+ ${project.build.directory}/generated-sources/asn1-java/net/heberling/ismart/asn1/v2_0/entity
+ -f
+ ${project.basedir}/../ASN.1 schema/v2_1+0/ApplicationData.asn1
+ -ns
+ net.heberling.ismart.asn1.v2_0.entity
+
+
+
generate-dispatcher-header-v2_1
diff --git a/saic-java-api/src/main/java/net/heberling/ismart/asn1/v2_0/Message.java b/saic-java-api/src/main/java/net/heberling/ismart/asn1/v2_0/Message.java
new file mode 100644
index 00000000..45b5578a
--- /dev/null
+++ b/saic-java-api/src/main/java/net/heberling/ismart/asn1/v2_0/Message.java
@@ -0,0 +1,19 @@
+package net.heberling.ismart.asn1.v2_0;
+
+import net.heberling.ismart.asn1.AbstractMessage;
+import org.bn.coders.IASN1PreparedElement;
+
+public class Message
+ extends AbstractMessage {
+
+ private final byte[] reserved;
+
+ Message(MP_DispatcherHeader header, byte[] reserved, MP_DispatcherBody body, E applicationData) {
+ super(header, body, applicationData);
+ this.reserved = reserved;
+ }
+
+ public byte[] getReserved() {
+ return reserved;
+ }
+}
diff --git a/saic-java-api/src/main/java/net/heberling/ismart/asn1/v2_0/MessageCoder.java b/saic-java-api/src/main/java/net/heberling/ismart/asn1/v2_0/MessageCoder.java
new file mode 100644
index 00000000..e963a50d
--- /dev/null
+++ b/saic-java-api/src/main/java/net/heberling/ismart/asn1/v2_0/MessageCoder.java
@@ -0,0 +1,135 @@
+package net.heberling.ismart.asn1.v2_0;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.time.Instant;
+import net.heberling.ismart.asn1.AbstractMessageCoder;
+import net.heberling.ismart.asn1.Util;
+import org.bn.coders.IASN1PreparedElement;
+import org.bn.coders.per.PERUnalignedDecoder;
+
+public class MessageCoder
+ extends AbstractMessageCoder> {
+
+ public MessageCoder(Class applicationDataClass) {
+ super(applicationDataClass);
+ }
+
+ @Override
+ public String encodeRequest(Message message) {
+
+ E request = message.getApplicationData();
+
+ try {
+
+ ByteArrayOutputStream bos;
+ byte[] applicationData;
+ MyPERUnalignedEncoder encoder = new MyPERUnalignedEncoder();
+ if (request != null) {
+ bos = new ByteArrayOutputStream();
+ encoder.encode(request, bos);
+ applicationData = bos.toByteArray();
+ } else {
+ applicationData = new byte[0];
+ }
+
+ MP_DispatcherBody body = message.getBody();
+ final DataEncodingType dataEncoding = new DataEncodingType();
+ dataEncoding.setValue(DataEncodingType.EnumType.perUnaligned);
+ body.setApplicationDataEncoding(dataEncoding);
+ body.setApplicationDataLength(applicationData.length);
+
+ bos = new ByteArrayOutputStream();
+ encoder.encode(body, bos);
+ final byte[] bodyData = bos.toByteArray();
+
+ MP_DispatcherHeader header = message.getHeader();
+ if (header.getProtocolVersion() == null) {
+ header.setProtocolVersion(33);
+ }
+ header.setDispatcherMessageLength(bodyData.length + 3 /*header length*/);
+ header.setDispatcherBodyEncoding(0); // PER
+
+ bos = new ByteArrayOutputStream();
+ bos.write(header.getProtocolVersion());
+ bos.write(header.getDispatcherMessageLength());
+ bos.write(header.getDispatcherBodyEncoding());
+
+ bos.write(message.getReserved());
+
+ bos.write(bodyData);
+
+ bos.write(applicationData);
+
+ byte[] bytes = bos.toByteArray();
+ return "1" + String.format("%04X", bytes.length + 3) + bytesToHex(bytes);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public Message decodeResponse(String message) {
+ try {
+ // TODO: check for message encoding and length
+ byte[] bytes = hexStringToByteArray(message.substring(5));
+ InputStream inputStream = new ByteArrayInputStream(bytes);
+ // not asn.1 encoded
+ MP_DispatcherHeader header = new MP_DispatcherHeader();
+ header.setProtocolVersion(inputStream.read());
+ // messages can be longer than 256 bytes, so this value can be wrong!
+ header.setDispatcherMessageLength(inputStream.read());
+ header.setDispatcherBodyEncoding(inputStream.read());
+
+ // TODO: whats this?
+ byte[] reserved = new byte[16];
+ inputStream.read(reserved);
+
+ final PERUnalignedDecoder decoder = new MyPERUnalignedDecoder();
+ MP_DispatcherBody body = decoder.decode(inputStream, MP_DispatcherBody.class);
+
+ E e = null;
+ if (getApplicationDataClass() != null && body.getApplicationDataLength() > 0) {
+ e = decoder.decode(inputStream, getApplicationDataClass());
+ }
+ return new Message<>(header, reserved, body, e);
+ } catch (Exception e) {
+ throw new RuntimeException("Could not decode: " + message, e);
+ }
+ }
+
+ @Override
+ public Message initializeMessage(
+ String uid,
+ String token,
+ String vin,
+ String applicationID,
+ int applicationDataProtocolVersion,
+ int messageID,
+ E applicationData) {
+ Message message =
+ new Message<>(
+ new MP_DispatcherHeader(),
+ Util.generateReservedBytes(),
+ new MP_DispatcherBody(),
+ applicationData);
+
+ message.getBody().setMessageID(messageID);
+ message.getBody().setEventCreationTime((int) Instant.now().getEpochSecond());
+ message.getBody().setApplicationID(applicationID);
+ message.getBody().setApplicationDataProtocolVersion(applicationDataProtocolVersion);
+ message.getBody().setTestFlag(2);
+ message.getBody().setUid(uid);
+ message.getBody().setToken(token);
+ message.getBody().setVin(vin);
+ message.getBody().setEventID(0);
+
+ return message;
+ }
+
+ @Override
+ public String getVersion() {
+ return "2.1";
+ }
+}