diff --git a/libs/model/src/main/java/de/cyface/model/RequestMetaData.java b/libs/model/src/main/java/de/cyface/model/RequestMetaData.java deleted file mode 100644 index 31402af..0000000 --- a/libs/model/src/main/java/de/cyface/model/RequestMetaData.java +++ /dev/null @@ -1,324 +0,0 @@ -/* - * Copyright 2021-2022 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 java.io.Serializable; -import java.nio.charset.Charset; - -import org.apache.commons.lang3.Validate; - -/** - * The metadata as transmitted in the request header or pre-request body. - * - * @author Armin Schnabel - * @version 1.0.1 - * @since 6.0.0 - */ -public class RequestMetaData implements Serializable { - - /** - * Used to serialize objects of this class. Only change this value if this classes attribute set changes. - */ - private static final long serialVersionUID = -1700430112854515404L; - /** - * The length of a universal unique identifier. - */ - private static final int UUID_LENGTH = 36; - /** - * The default char set to use for encoding and decoding strings transmitted as metadata. - */ - private static final String DEFAULT_CHARSET = "UTF-8"; - /** - * Maximum size of a metadata field, with plenty space for future development. This prevents attackers from putting - * arbitrary long data into these fields. - */ - public static final int 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}). - */ - private static final int MAX_MEASUREMENT_ID_LENGTH = 20; - /** - * The minimum length of a track stored with a measurement. - */ - private static final double MINIMUM_TRACK_LENGTH = 0.0; - /** - * The minimum valid amount of locations stored inside a measurement. - */ - private static final long MINIMUM_LOCATION_COUNT = 0L; - /** - * 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. - */ - public static final int CURRENT_TRANSFER_FILE_FORMAT_VERSION = 3; - /** - * The worldwide unique identifier of the device uploading the data. - */ - final String deviceIdentifier; - /** - * The device wide unique identifier of the uploaded measurement. - */ - final String measurementIdentifier; - /** - * The operating system version, such as Android 9.0.0 or iOS 11.2. - */ - final String operatingSystemVersion; - /** - * The type of device uploading the data, such as Pixel 3 or iPhone 6 Plus. - */ - final String deviceType; - /** - * The version of the app that transmitted the measurement. - */ - final String applicationVersion; - /** - * The length of the measurement in meters. - */ - final double length; - /** - * The count of geolocations in the transmitted measurement. - */ - final long locationCount; - /** - * The {@code GeoLocation} at the beginning of the track represented by the transmitted measurement. - */ - final GeoLocation startLocation; - /** - * The {@code GeoLocation} at the end of the track represented by the transmitted measurement. - */ - final GeoLocation endLocation; - /** - * The modality type used to capture the measurement. - */ - final String modality; - /** - * The format version of the upload file. - */ - final int formatVersion; - - /** - * Creates a new completely initialized object of this class. - * - * @param deviceIdentifier The worldwide unique identifier of the device uploading the data. - * @param measurementIdentifier The device wide unique identifier of the uploaded measurement. - * @param operatingSystemVersion The operating system version, such as Android 9.0.0 or iOS 11.2. - * @param deviceType The type of device uploading the data, such as Pixel 3 or iPhone 6 Plus. - * @param applicationVersion The version of the app that transmitted the measurement. - * @param length The length of the measurement in meters. - * @param locationCount The count of geolocations in the transmitted measurement. - * @param startLocation The {@code GeoLocation} at the beginning of the track represented by the transmitted - * measurement. - * @param endLocation The {@code GeoLocation} at the end of the track represented by the transmitted measurement. - * @param modality The modality type used to capture the measurement. - * @param formatVersion The format version of the upload file. - */ - public RequestMetaData(final String deviceIdentifier, final String measurementIdentifier, - final String operatingSystemVersion, final String deviceType, final String applicationVersion, - final double length, final long locationCount, final GeoLocation startLocation, - final GeoLocation endLocation, final String modality, final int formatVersion) { - - Validate.notNull(deviceIdentifier, "Field deviceId was null!"); - Validate.isTrue(deviceIdentifier.getBytes(Charset.forName(DEFAULT_CHARSET)).length == UUID_LENGTH, - "Field deviceId was not exactly 128 Bit, which is required for UUIDs!"); - Validate.notNull(deviceType, "Field deviceType was null!"); - Validate.isTrue(!deviceType.isEmpty() && deviceType.length() <= MAX_GENERIC_METADATA_FIELD_LENGTH, - "Field deviceType had an invalid length of %d!", deviceType.length()); - Validate.notNull(measurementIdentifier, "Field measurementId was null!"); - Validate.isTrue( - !measurementIdentifier.isEmpty() && measurementIdentifier.length() <= MAX_MEASUREMENT_ID_LENGTH, - "Field measurementId had an invalid length of %d!", measurementIdentifier.length()); - Validate.notNull(operatingSystemVersion, "Field osVersion was null!"); - Validate.isTrue( - !operatingSystemVersion.isEmpty() - && operatingSystemVersion.length() <= MAX_GENERIC_METADATA_FIELD_LENGTH, - "Field osVersion had an invalid length of %d!", operatingSystemVersion.length()); - Validate.notNull(applicationVersion, "Field applicationVersion was null!"); - Validate.isTrue( - !applicationVersion.isEmpty() && applicationVersion.length() <= MAX_GENERIC_METADATA_FIELD_LENGTH, - "Field applicationVersion had an invalid length of %d!", applicationVersion.length()); - Validate.isTrue(length >= MINIMUM_TRACK_LENGTH, - "Field length had an invalid value %d which is smaller then 0.0!", length); - Validate.isTrue(locationCount >= MINIMUM_LOCATION_COUNT, - "Field locationCount had an invalid value %d which is smaller then 0!", locationCount); - Validate.isTrue(locationCount == MINIMUM_LOCATION_COUNT || startLocation != null, - "Start location should only be defined if there is at least one location in the uploaded track!"); - Validate.isTrue(locationCount == MINIMUM_LOCATION_COUNT || endLocation != null, - "End location should only be defined if there is at least one location in the uploaded track!"); - Validate.notNull(modality, "Field modality was null!"); - Validate.isTrue(!modality.isEmpty() && modality.length() <= MAX_GENERIC_METADATA_FIELD_LENGTH, - "Field modality had an invalid length of %d!", modality.length()); - Validate.isTrue(formatVersion == CURRENT_TRANSFER_FILE_FORMAT_VERSION, "Unsupported formatVersion: %d", - formatVersion); - - this.deviceIdentifier = deviceIdentifier; - this.measurementIdentifier = measurementIdentifier; - this.operatingSystemVersion = operatingSystemVersion; - this.deviceType = deviceType; - this.applicationVersion = applicationVersion; - this.length = length; - this.locationCount = locationCount; - this.startLocation = startLocation; - this.endLocation = endLocation; - this.modality = modality; - this.formatVersion = formatVersion; - } - - /** - * @return The worldwide unique identifier of the device uploading the data. - */ - public String getDeviceIdentifier() { - return deviceIdentifier; - } - - /** - * @return The device wide unique identifier of the uploaded measurement. - */ - public String getMeasurementIdentifier() { - return measurementIdentifier; - } - - /** - * @return The operating system version, such as Android 9.0.0 or iOS 11.2. - */ - public String getOperatingSystemVersion() { - return operatingSystemVersion; - } - - /** - * @return The type of device uploading the data, such as Pixel 3 or iPhone 6 Plus. - */ - public String getDeviceType() { - return deviceType; - } - - /** - * @return The version of the app that transmitted the measurement. - */ - public String getApplicationVersion() { - return applicationVersion; - } - - /** - * @return The length of the measurement in meters. - */ - public double getLength() { - return length; - } - - /** - * @return The count of geolocations in the transmitted measurement. - */ - public long getLocationCount() { - return locationCount; - } - - /** - * @return The {@code GeoLocation} at the beginning of the track represented by the transmitted measurement. - */ - public GeoLocation getStartLocation() { - return startLocation; - } - - /** - * @return The {@code GeoLocation} at the end of the track represented by the transmitted measurement. - */ - public GeoLocation getEndLocation() { - return endLocation; - } - - /** - * @return The modality type used to capture the measurement. - */ - public String getModality() { - return modality; - } - - /** - * @return The format version of the upload file. - */ - public int getFormatVersion() { - return formatVersion; - } - - @Override - public String toString() { - return "RequestMetaData{" + - "deviceIdentifier='" + deviceIdentifier + '\'' + - ", measurementIdentifier='" + measurementIdentifier + '\'' + - ", operatingSystemVersion='" + operatingSystemVersion + '\'' + - ", deviceType='" + deviceType + '\'' + - ", applicationVersion='" + applicationVersion + '\'' + - ", length=" + length + - ", locationCount=" + locationCount + - ", startLocation=" + startLocation + - ", endLocation=" + endLocation + - ", modality='" + modality + '\'' + - ", formatVersion=" + formatVersion + - '}'; - } - - /** - * This class represents a geolocation at the start or end of a track. - * - * @author Armin Schnabel - * @version 1.0.0 - * @since 6.0.0 - */ - public static class GeoLocation { - /** - * The timestamp this location was captured on in milliseconds since 1st January 1970 (epoch). - */ - private final long timestamp; - /** - * Geographical latitude in coordinates (decimal fraction) raging from -90° (south) to 90° (north). - */ - private final double latitude; - /** - * Geographical longitude in coordinates (decimal fraction) ranging from -180° (west) to 180° (east). - */ - private final double longitude; - - public GeoLocation(long timestamp, double latitude, double longitude) { - this.timestamp = timestamp; - this.latitude = latitude; - this.longitude = longitude; - } - - public long getTimestamp() { - return timestamp; - } - - public double getLatitude() { - return latitude; - } - - public double getLongitude() { - return longitude; - } - - @Override - public String toString() { - return "GeoLocation{" + - "timestamp=" + timestamp + - ", latitude=" + latitude + - ", longitude=" + longitude + - '}'; - } - } -} \ No newline at end of file diff --git a/libs/model/src/main/kotlin/de/cyface/model/RequestMetaData.kt b/libs/model/src/main/kotlin/de/cyface/model/RequestMetaData.kt new file mode 100644 index 0000000..b982ee8 --- /dev/null +++ b/libs/model/src/main/kotlin/de/cyface/model/RequestMetaData.kt @@ -0,0 +1,166 @@ +/* + * Copyright 2021-2023 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 java.io.Serializable +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. + * @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 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 filesSize The number of bytes of the files collected for this measurement (log, image and video data). + */ +@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 +) : Serializable { + + init { + require(deviceIdentifier.toByteArray(Charset.forName(DEFAULT_CHARSET)).size == UUID_LENGTH) { + "Field deviceId was not exactly 128 Bit, which is required for UUIDs!" + } + 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) { + "Field measurementId had an invalid length of ${measurementIdentifier.length.toLong()}" + } + 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) { + "Field applicationVersion had an invalid length of ${applicationVersion.length.toLong()}" + } + 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()}" + } + require(formatVersion == CURRENT_TRANSFER_FILE_FORMAT_VERSION) { + "Unsupported formatVersion: ${formatVersion.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" } + } + + /** + * 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). + */ + 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. + */ + private const val serialVersionUID = -1700430112854515404L + + /** + * The length of a universal unique identifier. + */ + private const val UUID_LENGTH = 36 + + /** + * The default char set to use for encoding and decoding strings transmitted as metadata. + */ + private const val DEFAULT_CHARSET = "UTF-8" + + /** + * Maximum size of a metadata field, with plenty space for future development. This 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}). + */ + private const val MAX_MEASUREMENT_ID_LENGTH = 20 + + /** + * 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 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 + } +} \ No newline at end of file