From 6a2af9612d899b39a1b9a6a42786a4d464b0f81f Mon Sep 17 00:00:00 2001 From: Armin Date: Tue, 4 Jun 2024 13:18:33 +0200 Subject: [PATCH 1/3] Add attachmentId to RequestMetaData --- build.gradle | 4 +- .../kotlin/de/cyface/model/RequestMetaData.kt | 39 ++++++++++--------- 2 files changed, 22 insertions(+), 21 deletions(-) diff --git a/build.gradle b/build.gradle index bce24d8..7152a3e 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ /* - * Copyright 2020-2023 Cyface GmbH + * Copyright 2020-2024 Cyface GmbH * * This file is part of the Cyface Serialization. * @@ -20,8 +20,6 @@ * The build gradle file for the Cyface Serialization. * * @author Armin Schnabel - * @version 1.1.0 - * @since 1.0.0 */ buildscript { ext.kotlin_version = "1.7.10" diff --git a/libs/model/src/main/kotlin/de/cyface/model/RequestMetaData.kt b/libs/model/src/main/kotlin/de/cyface/model/RequestMetaData.kt index b982ee8..3855567 100644 --- a/libs/model/src/main/kotlin/de/cyface/model/RequestMetaData.kt +++ b/libs/model/src/main/kotlin/de/cyface/model/RequestMetaData.kt @@ -1,5 +1,5 @@ /* - * Copyright 2021-2023 Cyface GmbH + * Copyright 2021-2024 Cyface GmbH * * This file is part of the Serialization. * @@ -26,8 +26,6 @@ import java.nio.charset.Charset * The metadata as transmitted in the request header or pre-request body. * * @author Armin Schnabel - * @version 2.0.0 - * @since 6.0.0 * @property deviceType The worldwide unique identifier of the device uploading the data. * @property measurementIdentifier The device wide unique identifier of the uploaded measurement. * @property operatingSystemVersion The operating system version, such as Android 9.0.0 or iOS 11.2. @@ -39,10 +37,11 @@ import java.nio.charset.Charset * @property endLocation The `GeoLocation` at the end of the track represented by the transmitted measurement. * @property modality The modality type used to capture the measurement. * @property formatVersion The format version of the upload file. - * @property logCount The number of log files captured for this measurement, e.g. metrics captured during distance-based image capturing. - * @property imageCount The number of images captured for this measurement. This allows the backend to notice when all images are transmitted. - * @property videoCount The number of videos captured for this measurement. This allows the backend to notice when all videos are transmitted. + * @property logCount Count of log files captured for this measurement, e.g. metrics captured during image capturing. + * @property imageCount Count of images captured for this measurement. Allows to notice when all images are transmitted. + * @property videoCount Count of videos captured for this measurement. Allows to notice when all videos are transmitted. * @property filesSize The number of bytes of the files collected for this measurement (log, image and video data). + * @property attachmentIdentifier The identifier of the attachment, if this measurement is an attachment. */ @Suppress("unused") // Part of the API data class RequestMetaData( @@ -60,7 +59,8 @@ data class RequestMetaData( val logCount: Int, val imageCount: Int, val videoCount: Int, - val filesSize: Long + val filesSize: Long, + val attachmentIdentifier: String? = null, ) : Serializable { init { @@ -70,10 +70,11 @@ data class RequestMetaData( require(deviceType.isNotEmpty() && deviceType.length <= MAX_GENERIC_METADATA_FIELD_LENGTH) { "Field deviceType had an invalid length of ${deviceType.length.toLong()}" } - require(measurementIdentifier.isNotEmpty() && measurementIdentifier.length <= MAX_MEASUREMENT_ID_LENGTH) { + require(measurementIdentifier.isNotEmpty() && measurementIdentifier.length <= MAX_ID_LENGTH) { "Field measurementId had an invalid length of ${measurementIdentifier.length.toLong()}" } - require(operatingSystemVersion.isNotEmpty() && operatingSystemVersion.length <= MAX_GENERIC_METADATA_FIELD_LENGTH) { + require(operatingSystemVersion.isNotEmpty() && + operatingSystemVersion.length <= MAX_GENERIC_METADATA_FIELD_LENGTH) { "Field osVersion had an invalid length of ${operatingSystemVersion.length.toLong()}" } require(applicationVersion.isNotEmpty() && applicationVersion.length <= MAX_GENERIC_METADATA_FIELD_LENGTH) { @@ -101,17 +102,18 @@ data class RequestMetaData( require(imageCount >= 0) { "Invalid imageCount: $imageCount" } require(videoCount >= 0) { "Invalid videoCount: $videoCount" } require(filesSize >= 0) { "Invalid filesSize: $filesSize" } + require(attachmentIdentifier == null || attachmentIdentifier.length <= MAX_ID_LENGTH) { + "Field attachmentId had an invalid length of ${attachmentIdentifier!!.length.toLong()}" + } } /** * This class represents a geolocation at the start or end of a track. * * @author Armin Schnabel - * @version 1.0.0 - * @since 6.0.0 * @property timestamp The timestamp this location was captured on in milliseconds since 1st January 1970 (epoch). - * @property latitude Geographical latitude in coordinates (decimal fraction) raging from -90° (south) to 90° (north). - * @property longitude Geographical longitude in coordinates (decimal fraction) ranging from -180° (west) to 180° (east). + * @property latitude Geographical latitude (decimal fraction) raging from -90° (south) to 90° (north). + * @property longitude Geographical longitude (decimal fraction) ranging from -180° (west) to 180° (east). */ data class GeoLocation( val timestamp: Long, @@ -123,6 +125,7 @@ data class RequestMetaData( /** * Used to serialize objects of this class. Only change this value if this classes attribute set changes. */ + @Suppress("ConstPropertyName") private const val serialVersionUID = -1700430112854515404L /** @@ -136,16 +139,16 @@ data class RequestMetaData( private const val DEFAULT_CHARSET = "UTF-8" /** - * Maximum size of a metadata field, with plenty space for future development. This prevents attackers from putting + * Maximum size of a metadata field, with plenty space for future development. Prevents attackers from putting * arbitrary long data into these fields. */ const val MAX_GENERIC_METADATA_FIELD_LENGTH = 30 /** - * The maximum length of the measurement identifier in characters (this is the amount of characters of - * {@value Long#MAX_VALUE}). + * The maximum length of the measurement or attachment identifier in characters (this is the amount of + * characters of {@value Long#MAX_VALUE}). */ - private const val MAX_MEASUREMENT_ID_LENGTH = 20 + private const val MAX_ID_LENGTH = 20 /** * The minimum length of a track stored with a measurement. @@ -163,4 +166,4 @@ data class RequestMetaData( */ const val CURRENT_TRANSFER_FILE_FORMAT_VERSION = 3 } -} \ No newline at end of file +} From 7bbaba44f2284f646c2db33b8c0df108f3b51675 Mon Sep 17 00:00:00 2001 From: Armin Date: Mon, 24 Jun 2024 13:21:00 +0200 Subject: [PATCH 2/3] Fix code smells and convert MeasurementIdentifier to Kotlin --- .../cyface/model/MeasurementIdentifier.java | 142 ------- .../de/cyface/model/MeasurementIdentifier.kt | 134 ++++++ .../kotlin/de/cyface/model/RequestMetaData.kt | 387 +++++++++++++----- 3 files changed, 420 insertions(+), 243 deletions(-) delete mode 100644 libs/model/src/main/java/de/cyface/model/MeasurementIdentifier.java create mode 100644 libs/model/src/main/kotlin/de/cyface/model/MeasurementIdentifier.kt diff --git a/libs/model/src/main/java/de/cyface/model/MeasurementIdentifier.java b/libs/model/src/main/java/de/cyface/model/MeasurementIdentifier.java deleted file mode 100644 index 5dd05d9..0000000 --- a/libs/model/src/main/java/de/cyface/model/MeasurementIdentifier.java +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Copyright 2021 Cyface GmbH - * - * This file is part of the Serialization. - * - * The Serialization is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * The Serialization is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with the Serialization. If not, see . - */ -package de.cyface.model; - -import org.apache.commons.lang3.Validate; - -import java.io.Serializable; - -/** - * Describes the world wide unique identifier of a measurement. This identifier consists of a device identifier and the - * actual measurement from that device. - *

- * The natural order of MeasurementIdentifier is lexicographically by device identifier and then by - * measurement identifier. - * - * @author Klemens Muthmann - * @version 1.0.0 - * @since 2.0.0 - */ -public final class MeasurementIdentifier implements Comparable, Serializable { - - /** - * Used to serialize objects of this class. Only change this value if this classes attribute set changes. - */ - private static final long serialVersionUID = 181303400330020850L; - /** - * The world wide unique identifier of the device that captured this measurement. - */ - private String deviceIdentifier; - /** - * The device wide unique identifier of the measurement. - */ - private long measurementIdentifier; - - /** - * The default no arguments constructor as required by Apache Flink to serialize and deserialize objects of this - * class. Do not use this in you own code, it creates an unusable MeasurementIdentifier. - */ - public MeasurementIdentifier() { - // Nothing to do here. - } - - /** - * Creates a new completely initialized object of this class. - * - * @param deviceIdentifier The world wide unique identifier of the device that captured this measurement - * @param measurementIdentifier The device wide unique identifier of the measurement - */ - public MeasurementIdentifier(final String deviceIdentifier, final long measurementIdentifier) { - setDeviceIdentifier(deviceIdentifier); - setMeasurementIdentifier(measurementIdentifier); - } - - /** - * @return The world wide unique identifier of the device that captured this measurement - */ - public String getDeviceIdentifier() { - return deviceIdentifier; - } - - /** - * @return The device wide unique identifier of the measurement - */ - public long getMeasurementIdentifier() { - return measurementIdentifier; - } - - /** - * @param deviceIdentifier The world wide unique identifier of the device that captured this measurement - */ - public void setDeviceIdentifier(final String deviceIdentifier) { - Validate.notEmpty(deviceIdentifier); - - this.deviceIdentifier = deviceIdentifier; - } - - /** - * @param measurementIdentifier The device wide unique identifier of the measurement - */ - public void setMeasurementIdentifier(final long measurementIdentifier) { - Validate.isTrue(measurementIdentifier >= 0); - - this.measurementIdentifier = measurementIdentifier; - } - - @Override - public int hashCode() { - final var prime = 31; - int result = 1; - result = prime * result + ((deviceIdentifier == null) ? 0 : deviceIdentifier.hashCode()); - result = prime * result + (int)(measurementIdentifier ^ (measurementIdentifier >>> 32)); - return result; - } - - @Override - public String toString() { - return "MeasurementIdentifier [deviceIdentifier=" + deviceIdentifier + ", measurementIdentifier=" - + measurementIdentifier + "]"; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - var other = (MeasurementIdentifier)obj; - if (deviceIdentifier == null) { - if (other.deviceIdentifier != null) - return false; - } else if (!deviceIdentifier.equals(other.deviceIdentifier)) - return false; - return measurementIdentifier == other.measurementIdentifier; - } - - @Override - public int compareTo(final MeasurementIdentifier measurementIdentifier) { - final var deviceIdentifierComparison = this.getDeviceIdentifier() - .compareTo(measurementIdentifier.getDeviceIdentifier()); - return deviceIdentifierComparison == 0 - ? Long.compare(this.getMeasurementIdentifier(), measurementIdentifier.getMeasurementIdentifier()) - : deviceIdentifierComparison; - } -} diff --git a/libs/model/src/main/kotlin/de/cyface/model/MeasurementIdentifier.kt b/libs/model/src/main/kotlin/de/cyface/model/MeasurementIdentifier.kt new file mode 100644 index 0000000..f755caf --- /dev/null +++ b/libs/model/src/main/kotlin/de/cyface/model/MeasurementIdentifier.kt @@ -0,0 +1,134 @@ +/* + * Copyright 2021-2024 Cyface GmbH + * + * This file is part of the Serialization. + * + * The Serialization is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Serialization is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with the Serialization. If not, see . + */ +package de.cyface.model + +import org.apache.commons.lang3.Validate +import java.io.Serializable + +/** + * Describes the worldwide unique identifier of a measurement. This identifier consists of a device identifier and the + * actual measurement from that device. + * + * The natural order of [MeasurementIdentifier] is lexicographically by device identifier and then by + * measurement identifier. + * + * @author Klemens Muthmann + */ +class MeasurementIdentifier : Comparable, Serializable { + /** + * The worldwide unique identifier of the device that captured this measurement. + */ + private var deviceIdentifier: String? = null + + /** + * The device wide unique identifier of the measurement. + */ + private var measurementIdentifier: Long = 0 + + /** + * The default no arguments constructor as required by Apache Flink to serialize and deserialize objects of this + * class. Do not use this in you own code, it creates an unusable [MeasurementIdentifier]. + */ + constructor() + + /** + * Creates a new completely initialized object of this class. + * + * @param deviceIdentifier The worldwide unique identifier of the device that captured this measurement + * @param measurementIdentifier The device wide unique identifier of the measurement + */ + constructor(deviceIdentifier: String?, measurementIdentifier: Long) { + setDeviceIdentifier(deviceIdentifier) + setMeasurementIdentifier(measurementIdentifier) + } + + /** + * @return The worldwide unique identifier of the device that captured this measurement + */ + fun getDeviceIdentifier(): String? { + return deviceIdentifier + } + + /** + * @return The device wide unique identifier of the measurement + */ + fun getMeasurementIdentifier(): Long { + return measurementIdentifier + } + + /** + * @param deviceIdentifier The worldwide unique identifier of the device that captured this measurement + */ + @Suppress("MemberVisibilityCanBePrivate") // API + fun setDeviceIdentifier(deviceIdentifier: String?) { + Validate.notEmpty(deviceIdentifier) + + this.deviceIdentifier = deviceIdentifier + } + + /** + * @param measurementIdentifier The device wide unique identifier of the measurement + */ + @Suppress("MemberVisibilityCanBePrivate") // API + fun setMeasurementIdentifier(measurementIdentifier: Long) { + Validate.isTrue(measurementIdentifier >= 0) + + this.measurementIdentifier = measurementIdentifier + } + + override fun hashCode(): Int { + val prime = 31 + var result = 1 + result = prime * result + (if ((deviceIdentifier == null)) 0 else deviceIdentifier.hashCode()) + result = prime * result + (measurementIdentifier xor (measurementIdentifier ushr 32)).toInt() + return result + } + + override fun toString(): String { + return ("MeasurementIdentifier [deviceIdentifier=" + deviceIdentifier + ", measurementIdentifier=" + + measurementIdentifier + "]") + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null) return false + if (javaClass != other.javaClass) return false + val otherId = other as MeasurementIdentifier + if (deviceIdentifier == null) { + if (otherId.deviceIdentifier != null) return false + } else if (deviceIdentifier != otherId.deviceIdentifier) return false + return measurementIdentifier == otherId.measurementIdentifier + } + + override fun compareTo(other: MeasurementIdentifier): Int { + val deviceIdentifierComparison = getDeviceIdentifier()!! + .compareTo(other.getDeviceIdentifier()!!) + return if (deviceIdentifierComparison == 0) this.getMeasurementIdentifier() + .compareTo(other.getMeasurementIdentifier()) + else deviceIdentifierComparison + } + + companion object { + /** + * Used to serialize objects of this class. Only change this value if this classes attribute set changes. + */ + @Suppress("ConstPropertyName") + private const val serialVersionUID = 181303400330020850L + } +} diff --git a/libs/model/src/main/kotlin/de/cyface/model/RequestMetaData.kt b/libs/model/src/main/kotlin/de/cyface/model/RequestMetaData.kt index 3855567..1fadaca 100644 --- a/libs/model/src/main/kotlin/de/cyface/model/RequestMetaData.kt +++ b/libs/model/src/main/kotlin/de/cyface/model/RequestMetaData.kt @@ -19,151 +19,336 @@ package de.cyface.model +import de.cyface.model.RequestMetaData.MeasurementMetaData.GeoLocation import java.io.Serializable import java.nio.charset.Charset /** - * The metadata as transmitted in the request header or pre-request body. + * The metadata as transmitted in the request header or pre-request body for the measurement file. * * @author Armin Schnabel - * @property deviceType The worldwide unique identifier of the device uploading the data. - * @property measurementIdentifier The device wide unique identifier of the uploaded measurement. - * @property operatingSystemVersion The operating system version, such as Android 9.0.0 or iOS 11.2. - * @property deviceType The type of device uploading the data, such as Pixel 3 or iPhone 6 Plus. - * @property applicationVersion The version of the app that transmitted the measurement. - * @property length The length of the measurement in meters. - * @property locationCount The count of geolocations in the transmitted measurement. - * @property startLocation The `GeoLocation` at the beginning of the track represented by the transmitted measurement. - * @property endLocation The `GeoLocation` at the end of the track represented by the transmitted measurement. - * @property modality The modality type used to capture the measurement. - * @property formatVersion The format version of the upload file. - * @property logCount Count of log files captured for this measurement, e.g. metrics captured during image capturing. - * @property imageCount Count of images captured for this measurement. Allows to notice when all images are transmitted. - * @property videoCount Count of videos captured for this measurement. Allows to notice when all videos are transmitted. - * @property filesSize The number of bytes of the files collected for this measurement (log, image and video data). - * @property attachmentIdentifier The identifier of the attachment, if this measurement is an attachment. + * @property identifier The identifier which identifies the data object transmitted. */ @Suppress("unused") // Part of the API -data class RequestMetaData( - val deviceIdentifier: String, - val measurementIdentifier: String, - val operatingSystemVersion: String, - val deviceType: String, - val applicationVersion: String, - val length: Double, - val locationCount: Long, - val startLocation: GeoLocation?, - val endLocation: GeoLocation?, - val modality: String, - val formatVersion: Int, - val logCount: Int, - val imageCount: Int, - val videoCount: Int, - val filesSize: Long, - val attachmentIdentifier: String? = null, +data class RequestMetaData( + val identifier: T, + val deviceMetaData: DeviceMetaData, + val applicationMetaData: ApplicationMetaData, + val measurementMetaData: MeasurementMetaData, + val attachmentMetaData: AttachmentMetaData, ) : Serializable { + /** + * An identifier for a transmitted measurement file. + * + * @author Armin Schnabel + * @property deviceId The world-unique identifier of the device which collected the data. + * @property measurementId The device-unique identifier of the measurement transmitted. + */ + open class MeasurementIdentifier( + val deviceId: String, + val measurementId: String, + ) : Serializable { - init { - require(deviceIdentifier.toByteArray(Charset.forName(DEFAULT_CHARSET)).size == UUID_LENGTH) { - "Field deviceId was not exactly 128 Bit, which is required for UUIDs!" + init { + require(deviceId.toByteArray(Charset.forName(DEFAULT_CHARSET)).size == UUID_LENGTH) { + "Field deviceId was not exactly 128 Bit, which is required for UUIDs!" + } + require(measurementId.isNotEmpty() && measurementId.length <= MAX_ID_LENGTH) { + "Field measurementId had an invalid length of ${measurementId.length.toLong()}" + } } - require(deviceType.isNotEmpty() && deviceType.length <= MAX_GENERIC_METADATA_FIELD_LENGTH) { - "Field deviceType had an invalid length of ${deviceType.length.toLong()}" + + companion object { + /** + * Used to serialize objects of this class. Only change this value if this classes attribute set changes. + */ + @Suppress("ConstPropertyName") + private const val serialVersionUID = 1L + + /** + * The length of a universal unique identifier. + */ + const val UUID_LENGTH = 36 + + /** + * The default char set to use for encoding and decoding strings transmitted as metadata. + */ + const val DEFAULT_CHARSET = "UTF-8" + + /** + * The maximum length of the measurement or attachment identifier in characters (this is the amount of + * characters of {@value Long#MAX_VALUE}). + */ + const val MAX_ID_LENGTH = 20 } - require(measurementIdentifier.isNotEmpty() && measurementIdentifier.length <= MAX_ID_LENGTH) { - "Field measurementId had an invalid length of ${measurementIdentifier.length.toLong()}" + + override fun toString(): String { + return "MeasurementIdentifier(deviceId='$deviceId', measurementId='$measurementId')" } - require(operatingSystemVersion.isNotEmpty() && - operatingSystemVersion.length <= MAX_GENERIC_METADATA_FIELD_LENGTH) { - "Field osVersion had an invalid length of ${operatingSystemVersion.length.toLong()}" + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as MeasurementIdentifier + + if (deviceId != other.deviceId) return false + if (measurementId != other.measurementId) return false + + return true } - require(applicationVersion.isNotEmpty() && applicationVersion.length <= MAX_GENERIC_METADATA_FIELD_LENGTH) { - "Field applicationVersion had an invalid length of ${applicationVersion.length.toLong()}" + + override fun hashCode(): Int { + var result = deviceId.hashCode() + result = 31 * result + measurementId.hashCode() + return result } - require(length >= MINIMUM_TRACK_LENGTH) { - "Field length had an invalid value smaller then 0.0: $length" + } + + /** + * An identifier for a transmitted attachment file. + * + * @author Armin Schnabel + * @property deviceId The world-unique identifier of the device which collected the data. + * @property measurementId The device-unique identifier of the measurement transmitted. + * @property attachmentId The device-unique identifier of the attachment file transmitted. + */ + class AttachmentIdentifier( + deviceId: String, + measurementId: String, + val attachmentId: String, + ) : MeasurementIdentifier(deviceId, measurementId) { + init { + require(attachmentId.isNotEmpty() && attachmentId.length <= MAX_ID_LENGTH) { + "Field attachmentId had an invalid length of ${attachmentId.length.toLong()}" + } } - require(locationCount >= MINIMUM_LOCATION_COUNT) { - "Field locationCount had an invalid value smaller then 0: $locationCount" + + companion object { + /** + * Used to serialize objects of this class. Only change this value if this classes attribute set changes. + */ + @Suppress("ConstPropertyName") + private const val serialVersionUID = 1L } - require(locationCount == MINIMUM_LOCATION_COUNT || startLocation != null) { - "Start location should only be defined if there is at least one location in the uploaded track!" + + override fun toString(): String { + return "AttachmentIdentifier(attachmentId='$attachmentId')" } - require(locationCount == MINIMUM_LOCATION_COUNT || endLocation != null) { - "End location should only be defined if there is at least one location in the uploaded track!" + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + if (!super.equals(other)) return false + + other as AttachmentIdentifier + + return attachmentId == other.attachmentId } - require(modality.isNotEmpty() && modality.length <= MAX_GENERIC_METADATA_FIELD_LENGTH) { - "Field modality had an invalid length of ${modality.length.toLong()}" + + override fun hashCode(): Int { + var result = super.deviceId.hashCode() // The attachmentId is device-unique + result = 31 * result + attachmentId.hashCode() + return result } - require(formatVersion == CURRENT_TRANSFER_FILE_FORMAT_VERSION) { - "Unsupported formatVersion: ${formatVersion.toLong()}" + } + + /** + * The metadata which describes the device which collected the data. + * + * @author Armin Schnabel + * @property operatingSystemVersion The operating system version, such as Android 9.0.0 or iOS 11.2. + * @property deviceType The type of device uploading the data, such as Pixel 3 or iPhone 6 Plus. + */ + data class DeviceMetaData( + val operatingSystemVersion: String, + val deviceType: String, + ) : Serializable { + init { + require( + operatingSystemVersion.isNotEmpty() && + operatingSystemVersion.length <= MAX_GENERIC_METADATA_FIELD_LENGTH + ) { + "Field osVersion had an invalid length of ${operatingSystemVersion.length.toLong()}" + } + require(deviceType.isNotEmpty() && deviceType.length <= MAX_GENERIC_METADATA_FIELD_LENGTH) { + "Field deviceType had an invalid length of ${deviceType.length.toLong()}" + } } - require(logCount >= 0) { "Invalid logCount: $logCount" } - require(imageCount >= 0) { "Invalid imageCount: $imageCount" } - require(videoCount >= 0) { "Invalid videoCount: $videoCount" } - require(filesSize >= 0) { "Invalid filesSize: $filesSize" } - require(attachmentIdentifier == null || attachmentIdentifier.length <= MAX_ID_LENGTH) { - "Field attachmentId had an invalid length of ${attachmentIdentifier!!.length.toLong()}" + + companion object { + /** + * Used to serialize objects of this class. Only change this value if this classes attribute set changes. + */ + @Suppress("ConstPropertyName") + private const val serialVersionUID = 1L } } /** - * This class represents a geolocation at the start or end of a track. + * The metadata which describes the application which collected the data. * * @author Armin Schnabel - * @property timestamp The timestamp this location was captured on in milliseconds since 1st January 1970 (epoch). - * @property latitude Geographical latitude (decimal fraction) raging from -90° (south) to 90° (north). - * @property longitude Geographical longitude (decimal fraction) ranging from -180° (west) to 180° (east). + * @property applicationVersion The version of the app that transmitted the measurement. + * @property formatVersion The format version of the upload file. */ - data class GeoLocation( - val timestamp: Long, - val latitude: Double, - val longitude: Double - ) + data class ApplicationMetaData( + val applicationVersion: String, + val formatVersion: Int, + ) : Serializable { + init { + require(applicationVersion.isNotEmpty() && applicationVersion.length <= MAX_GENERIC_METADATA_FIELD_LENGTH) { + "Field applicationVersion had an invalid length of ${applicationVersion.length.toLong()}" + } + require(formatVersion == CURRENT_TRANSFER_FILE_FORMAT_VERSION) { + "Unsupported formatVersion: ${formatVersion.toLong()}" + } + } - companion object { - /** - * Used to serialize objects of this class. Only change this value if this classes attribute set changes. - */ - @Suppress("ConstPropertyName") - private const val serialVersionUID = -1700430112854515404L + companion object { + /** + * Used to serialize objects of this class. Only change this value if this classes attribute set changes. + */ + @Suppress("ConstPropertyName") + private const val serialVersionUID = 1L + + /** + * The current version of the transferred file. This is always specified by the first two bytes of the file + * transferred and helps compatible APIs to process data from different client versions. + */ + const val CURRENT_TRANSFER_FILE_FORMAT_VERSION = 3 + } + } + + /** + * The metadata which describes the measurement the data was collected for. + * + * @author Armin Schnabel + * @property length The length of the measurement in meters. + * @property locationCount The count of geolocations in the transmitted measurement. + * @property startLocation The first [GeoLocation] captured by the transmitted measurement. + * @property endLocation The last [GeoLocation] captured by the transmitted measurement. + * @property modality The modality type used to capture the measurement. + */ + data class MeasurementMetaData( + val length: Double, + val locationCount: Long, + val startLocation: GeoLocation?, + val endLocation: GeoLocation?, + val modality: String, + ) : Serializable { + init { + require(length >= MINIMUM_TRACK_LENGTH) { + "Field length had an invalid value smaller then 0.0: $length" + } + require(locationCount >= MINIMUM_LOCATION_COUNT) { + "Field locationCount had an invalid value smaller then 0: $locationCount" + } + require(locationCount == MINIMUM_LOCATION_COUNT || startLocation != null) { + "Start location should only be defined if there is at least one location in the uploaded track!" + } + require(locationCount == MINIMUM_LOCATION_COUNT || endLocation != null) { + "End location should only be defined if there is at least one location in the uploaded track!" + } + require(modality.isNotEmpty() && modality.length <= MAX_GENERIC_METADATA_FIELD_LENGTH) { + "Field modality had an invalid length of ${modality.length.toLong()}" + } + } /** - * The length of a universal unique identifier. + * This class represents a geolocation at the start or end of a track. + * + * @author Armin Schnabel + * @property timestamp The Unix timestamp this location was captured on in milliseconds. + * @property latitude Geographical latitude (decimal fraction) raging from -90° (south) to 90° (north). + * @property longitude Geographical longitude (decimal fraction) ranging from -180° (west) to 180° (east). */ - private const val UUID_LENGTH = 36 + data class GeoLocation( + val timestamp: Long, + val latitude: Double, + val longitude: Double + ) + + companion object { + /** + * Used to serialize objects of this class. Only change this value if this classes attribute set changes. + */ + @Suppress("ConstPropertyName") + private const val serialVersionUID = 1L + + /** + * The minimum length of a track stored with a measurement. + */ + private const val MINIMUM_TRACK_LENGTH = 0.0 + + /** + * The minimum valid amount of locations stored inside a measurement. + */ + private const val MINIMUM_LOCATION_COUNT = 0L + } + } + + /** + * The metadata which describes the attachments which were collected together with the measurement. + * + * @author Armin Schnabel + * @property logCount Number of log files captured for this measurement, e.g. image capturing metrics. + * @property imageCount Number of image files captured for this measurement. + * @property videoCount Number of video files captured for this measurement. + * @property filesSize The number of bytes of the files collected for this measurement (log, image and video data). + */ + data class AttachmentMetaData( + val logCount: Int, + val imageCount: Int, + val videoCount: Int, + val filesSize: Long, + ) : Serializable { + init { + require(logCount >= 0) { "Invalid logCount: $logCount" } + require(imageCount >= 0) { "Invalid imageCount: $imageCount" } + require(videoCount >= 0) { "Invalid videoCount: $videoCount" } + require(filesSize >= 0) { "Invalid filesSize: $filesSize" } + } + + companion object { + /** + * Used to serialize objects of this class. Only change this value if this classes attribute set changes. + */ + @Suppress("ConstPropertyName") + private const val serialVersionUID = 1L + } + } + companion object { /** - * The default char set to use for encoding and decoding strings transmitted as metadata. + * Used to serialize objects of this class. Only change this value if this classes attribute set changes. */ - private const val DEFAULT_CHARSET = "UTF-8" + @Suppress("ConstPropertyName") + private const val serialVersionUID = 2L /** * Maximum size of a metadata field, with plenty space for future development. Prevents attackers from putting * arbitrary long data into these fields. */ const val MAX_GENERIC_METADATA_FIELD_LENGTH = 30 + } - /** - * The maximum length of the measurement or attachment identifier in characters (this is the amount of - * characters of {@value Long#MAX_VALUE}). - */ - private const val MAX_ID_LENGTH = 20 + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false - /** - * The minimum length of a track stored with a measurement. - */ - private const val MINIMUM_TRACK_LENGTH = 0.0 + other as RequestMetaData<*> - /** - * The minimum valid amount of locations stored inside a measurement. - */ - private const val MINIMUM_LOCATION_COUNT = 0L + if (identifier != other.identifier) return false + if (deviceMetaData != other.deviceMetaData) return false + if (applicationMetaData != other.applicationMetaData) return false + if (measurementMetaData != other.measurementMetaData) return false + if (attachmentMetaData != other.attachmentMetaData) return false - /** - * The current version of the transferred file. This is always specified by the first two bytes of the file - * transferred and helps compatible APIs to process data from different client versions. - */ - const val CURRENT_TRANSFER_FILE_FORMAT_VERSION = 3 + return true + } + + override fun hashCode(): Int { + return identifier.hashCode() } } From 5cb6d3016cb587187cede1c2d78709194d7b552b Mon Sep 17 00:00:00 2001 From: Armin Date: Mon, 15 Jul 2024 11:26:27 +0200 Subject: [PATCH 3/3] Remove RequestMetaData (not part of serialization task) --- .../kotlin/de/cyface/model/RequestMetaData.kt | 354 ------------------ 1 file changed, 354 deletions(-) delete mode 100644 libs/model/src/main/kotlin/de/cyface/model/RequestMetaData.kt diff --git a/libs/model/src/main/kotlin/de/cyface/model/RequestMetaData.kt b/libs/model/src/main/kotlin/de/cyface/model/RequestMetaData.kt deleted file mode 100644 index 1fadaca..0000000 --- a/libs/model/src/main/kotlin/de/cyface/model/RequestMetaData.kt +++ /dev/null @@ -1,354 +0,0 @@ -/* - * Copyright 2021-2024 Cyface GmbH - * - * This file is part of the Serialization. - * - * The Serialization is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * The Serialization is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with the Serialization. If not, see . - */ - -package de.cyface.model - -import de.cyface.model.RequestMetaData.MeasurementMetaData.GeoLocation -import java.io.Serializable -import java.nio.charset.Charset - -/** - * The metadata as transmitted in the request header or pre-request body for the measurement file. - * - * @author Armin Schnabel - * @property identifier The identifier which identifies the data object transmitted. - */ -@Suppress("unused") // Part of the API -data class RequestMetaData( - val identifier: T, - val deviceMetaData: DeviceMetaData, - val applicationMetaData: ApplicationMetaData, - val measurementMetaData: MeasurementMetaData, - val attachmentMetaData: AttachmentMetaData, -) : Serializable { - /** - * An identifier for a transmitted measurement file. - * - * @author Armin Schnabel - * @property deviceId The world-unique identifier of the device which collected the data. - * @property measurementId The device-unique identifier of the measurement transmitted. - */ - open class MeasurementIdentifier( - val deviceId: String, - val measurementId: String, - ) : Serializable { - - init { - require(deviceId.toByteArray(Charset.forName(DEFAULT_CHARSET)).size == UUID_LENGTH) { - "Field deviceId was not exactly 128 Bit, which is required for UUIDs!" - } - require(measurementId.isNotEmpty() && measurementId.length <= MAX_ID_LENGTH) { - "Field measurementId had an invalid length of ${measurementId.length.toLong()}" - } - } - - companion object { - /** - * Used to serialize objects of this class. Only change this value if this classes attribute set changes. - */ - @Suppress("ConstPropertyName") - private const val serialVersionUID = 1L - - /** - * The length of a universal unique identifier. - */ - const val UUID_LENGTH = 36 - - /** - * The default char set to use for encoding and decoding strings transmitted as metadata. - */ - const val DEFAULT_CHARSET = "UTF-8" - - /** - * The maximum length of the measurement or attachment identifier in characters (this is the amount of - * characters of {@value Long#MAX_VALUE}). - */ - const val MAX_ID_LENGTH = 20 - } - - override fun toString(): String { - return "MeasurementIdentifier(deviceId='$deviceId', measurementId='$measurementId')" - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as MeasurementIdentifier - - if (deviceId != other.deviceId) return false - if (measurementId != other.measurementId) return false - - return true - } - - override fun hashCode(): Int { - var result = deviceId.hashCode() - result = 31 * result + measurementId.hashCode() - return result - } - } - - /** - * An identifier for a transmitted attachment file. - * - * @author Armin Schnabel - * @property deviceId The world-unique identifier of the device which collected the data. - * @property measurementId The device-unique identifier of the measurement transmitted. - * @property attachmentId The device-unique identifier of the attachment file transmitted. - */ - class AttachmentIdentifier( - deviceId: String, - measurementId: String, - val attachmentId: String, - ) : MeasurementIdentifier(deviceId, measurementId) { - init { - require(attachmentId.isNotEmpty() && attachmentId.length <= MAX_ID_LENGTH) { - "Field attachmentId had an invalid length of ${attachmentId.length.toLong()}" - } - } - - companion object { - /** - * Used to serialize objects of this class. Only change this value if this classes attribute set changes. - */ - @Suppress("ConstPropertyName") - private const val serialVersionUID = 1L - } - - override fun toString(): String { - return "AttachmentIdentifier(attachmentId='$attachmentId')" - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - if (!super.equals(other)) return false - - other as AttachmentIdentifier - - return attachmentId == other.attachmentId - } - - override fun hashCode(): Int { - var result = super.deviceId.hashCode() // The attachmentId is device-unique - result = 31 * result + attachmentId.hashCode() - return result - } - } - - /** - * The metadata which describes the device which collected the data. - * - * @author Armin Schnabel - * @property operatingSystemVersion The operating system version, such as Android 9.0.0 or iOS 11.2. - * @property deviceType The type of device uploading the data, such as Pixel 3 or iPhone 6 Plus. - */ - data class DeviceMetaData( - val operatingSystemVersion: String, - val deviceType: String, - ) : Serializable { - init { - require( - operatingSystemVersion.isNotEmpty() && - operatingSystemVersion.length <= MAX_GENERIC_METADATA_FIELD_LENGTH - ) { - "Field osVersion had an invalid length of ${operatingSystemVersion.length.toLong()}" - } - require(deviceType.isNotEmpty() && deviceType.length <= MAX_GENERIC_METADATA_FIELD_LENGTH) { - "Field deviceType had an invalid length of ${deviceType.length.toLong()}" - } - } - - companion object { - /** - * Used to serialize objects of this class. Only change this value if this classes attribute set changes. - */ - @Suppress("ConstPropertyName") - private const val serialVersionUID = 1L - } - } - - /** - * The metadata which describes the application which collected the data. - * - * @author Armin Schnabel - * @property applicationVersion The version of the app that transmitted the measurement. - * @property formatVersion The format version of the upload file. - */ - data class ApplicationMetaData( - val applicationVersion: String, - val formatVersion: Int, - ) : Serializable { - init { - require(applicationVersion.isNotEmpty() && applicationVersion.length <= MAX_GENERIC_METADATA_FIELD_LENGTH) { - "Field applicationVersion had an invalid length of ${applicationVersion.length.toLong()}" - } - require(formatVersion == CURRENT_TRANSFER_FILE_FORMAT_VERSION) { - "Unsupported formatVersion: ${formatVersion.toLong()}" - } - } - - companion object { - /** - * Used to serialize objects of this class. Only change this value if this classes attribute set changes. - */ - @Suppress("ConstPropertyName") - private const val serialVersionUID = 1L - - /** - * The current version of the transferred file. This is always specified by the first two bytes of the file - * transferred and helps compatible APIs to process data from different client versions. - */ - const val CURRENT_TRANSFER_FILE_FORMAT_VERSION = 3 - } - } - - /** - * The metadata which describes the measurement the data was collected for. - * - * @author Armin Schnabel - * @property length The length of the measurement in meters. - * @property locationCount The count of geolocations in the transmitted measurement. - * @property startLocation The first [GeoLocation] captured by the transmitted measurement. - * @property endLocation The last [GeoLocation] captured by the transmitted measurement. - * @property modality The modality type used to capture the measurement. - */ - data class MeasurementMetaData( - val length: Double, - val locationCount: Long, - val startLocation: GeoLocation?, - val endLocation: GeoLocation?, - val modality: String, - ) : Serializable { - init { - require(length >= MINIMUM_TRACK_LENGTH) { - "Field length had an invalid value smaller then 0.0: $length" - } - require(locationCount >= MINIMUM_LOCATION_COUNT) { - "Field locationCount had an invalid value smaller then 0: $locationCount" - } - require(locationCount == MINIMUM_LOCATION_COUNT || startLocation != null) { - "Start location should only be defined if there is at least one location in the uploaded track!" - } - require(locationCount == MINIMUM_LOCATION_COUNT || endLocation != null) { - "End location should only be defined if there is at least one location in the uploaded track!" - } - require(modality.isNotEmpty() && modality.length <= MAX_GENERIC_METADATA_FIELD_LENGTH) { - "Field modality had an invalid length of ${modality.length.toLong()}" - } - } - - /** - * This class represents a geolocation at the start or end of a track. - * - * @author Armin Schnabel - * @property timestamp The Unix timestamp this location was captured on in milliseconds. - * @property latitude Geographical latitude (decimal fraction) raging from -90° (south) to 90° (north). - * @property longitude Geographical longitude (decimal fraction) ranging from -180° (west) to 180° (east). - */ - data class GeoLocation( - val timestamp: Long, - val latitude: Double, - val longitude: Double - ) - - companion object { - /** - * Used to serialize objects of this class. Only change this value if this classes attribute set changes. - */ - @Suppress("ConstPropertyName") - private const val serialVersionUID = 1L - - /** - * The minimum length of a track stored with a measurement. - */ - private const val MINIMUM_TRACK_LENGTH = 0.0 - - /** - * The minimum valid amount of locations stored inside a measurement. - */ - private const val MINIMUM_LOCATION_COUNT = 0L - } - } - - /** - * The metadata which describes the attachments which were collected together with the measurement. - * - * @author Armin Schnabel - * @property logCount Number of log files captured for this measurement, e.g. image capturing metrics. - * @property imageCount Number of image files captured for this measurement. - * @property videoCount Number of video files captured for this measurement. - * @property filesSize The number of bytes of the files collected for this measurement (log, image and video data). - */ - data class AttachmentMetaData( - val logCount: Int, - val imageCount: Int, - val videoCount: Int, - val filesSize: Long, - ) : Serializable { - init { - require(logCount >= 0) { "Invalid logCount: $logCount" } - require(imageCount >= 0) { "Invalid imageCount: $imageCount" } - require(videoCount >= 0) { "Invalid videoCount: $videoCount" } - require(filesSize >= 0) { "Invalid filesSize: $filesSize" } - } - - companion object { - /** - * Used to serialize objects of this class. Only change this value if this classes attribute set changes. - */ - @Suppress("ConstPropertyName") - private const val serialVersionUID = 1L - } - } - - companion object { - /** - * Used to serialize objects of this class. Only change this value if this classes attribute set changes. - */ - @Suppress("ConstPropertyName") - private const val serialVersionUID = 2L - - /** - * Maximum size of a metadata field, with plenty space for future development. Prevents attackers from putting - * arbitrary long data into these fields. - */ - const val MAX_GENERIC_METADATA_FIELD_LENGTH = 30 - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as RequestMetaData<*> - - if (identifier != other.identifier) return false - if (deviceMetaData != other.deviceMetaData) return false - if (applicationMetaData != other.applicationMetaData) return false - if (measurementMetaData != other.measurementMetaData) return false - if (attachmentMetaData != other.attachmentMetaData) return false - - return true - } - - override fun hashCode(): Int { - return identifier.hashCode() - } -}