/* Copyright (C) 2019 Intel Corporation SPDX-License-Identifier: Apache-2.0 Open Drone ID C Library Maintainer: Gabriel Cox gabriel.c.cox@intel.com */ #include "opendroneid.h" #include #include #define ENABLE_DEBUG 1 const float SPEED_DIV[2] = {0.25f, 0.75f}; const float VSPEED_DIV = 0.5f; const int32_t LATLON_MULT = 10000000; const float ALT_DIV = 0.5f; const int ALT_ADDER = 1000; static char *safe_dec_copyfill(char *dstStr, const char *srcStr, int dstSize); static int intRangeMax(int64_t inValue, int startRange, int endRange); static int intInRange(int inValue, int startRange, int endRange); /** * Initialize basic ID data fields to their default values * * @param data (non encoded/packed) structure */ void odid_initBasicIDData(ODID_BasicID_data *data) { if (!data) return; memset(data, 0, sizeof(ODID_BasicID_data)); } /** * Initialize location data fields to their default values * * @param data (non encoded/packed) structure */ void odid_initLocationData(ODID_Location_data *data) { if (!data) return; memset(data, 0, sizeof(ODID_Location_data)); data->Direction = INV_DIR; data->SpeedHorizontal = INV_SPEED_H; data->SpeedVertical = INV_SPEED_V; data->AltitudeBaro = INV_ALT; data->AltitudeGeo = INV_ALT; data->Height = INV_ALT; } /** * Initialize authorization data fields to their default values * * @param data (non encoded/packed) structure */ void odid_initAuthData(ODID_Auth_data *data) { if (!data) return; memset(data, 0, sizeof(ODID_Auth_data)); } /** * Initialize self ID data fields to their default values * * @param data (non encoded/packed) structure */ void odid_initSelfIDData(ODID_SelfID_data *data) { if (!data) return; memset(data, 0, sizeof(ODID_SelfID_data)); } /** * Initialize system data fields to their default values * * @param data (non encoded/packed) structure */ void odid_initSystemData(ODID_System_data *data) { if (!data) return; memset(data, 0, sizeof(ODID_System_data)); data->AreaCount = 1; data->AreaCeiling = INV_ALT; data->AreaFloor = INV_ALT; data->OperatorAltitudeGeo = INV_ALT; } /** * Initialize operator ID data fields to their default values * * @param data (non encoded/packed) structure */ void odid_initOperatorIDData(ODID_OperatorID_data *data) { if (!data) return; memset(data, 0, sizeof(ODID_OperatorID_data)); } /** * Initialize message pack data fields to their default values * * @param data (non encoded/packed) structure */ void odid_initMessagePackData(ODID_MessagePack_data *data) { if (!data) return; memset(data, 0, sizeof(ODID_MessagePack_data)); data->SingleMessageSize = ODID_MESSAGE_SIZE; } /** * Initialize UAS data fields to their default values * * @param data (non encoded/packed) structure */ void odid_initUasData(ODID_UAS_Data *data) { if (!data) return; for (int i = 0; i < ODID_BASIC_ID_MAX_MESSAGES; i++) { data->BasicIDValid[i] = 0; odid_initBasicIDData(&data->BasicID[i]); } data->LocationValid = 0; odid_initLocationData(&data->Location); for (int i = 0; i < ODID_AUTH_MAX_PAGES; i++) { data->AuthValid[i] = 0; odid_initAuthData(&data->Auth[i]); } data->SelfIDValid = 0; odid_initSelfIDData(&data->SelfID); data->SystemValid = 0; odid_initSystemData(&data->System); data->OperatorIDValid = 0; odid_initOperatorIDData(&data->OperatorID); } /** * Encode direction as defined by Open Drone ID * * The encoding method uses 8 bits for the direction in degrees and * one extra bit for indicating the East/West direction. * * @param Direcction in degrees. 0 <= x < 360. Route course based on true North * @param EWDirection Bit flag indicating whether the direction is towards East (0 - 179 degrees) or West (180 - 359) * @return Encoded Direction in a single byte */ static uint8_t encodeDirection(float Direction, uint8_t *EWDirection) { unsigned int direction_int = (unsigned int) roundf(Direction); if (direction_int < 180) { *EWDirection = 0; } else { *EWDirection = 1; direction_int -= 180; } return (uint8_t) intRangeMax(direction_int, 0, UINT8_MAX); } /** * Encode speed into units defined by Open Drone ID * * The quantization algorithm allows for speed to be stored in units of 0.25 m/s * on the low end of the scale and 0.75 m/s on the high end of the scale. * This allows for more precise speeds to be represented in a single Uint8 byte * rather than using a large float value. * * @param Speed_data Speed (and decimal) in m/s * @param mult a (write only) value that sets the multiplier flag * @return Encoded Speed in a single byte or max speed if over max encoded speed. */ static uint8_t encodeSpeedHorizontal(float Speed_data, uint8_t *mult) { if (Speed_data <= UINT8_MAX * SPEED_DIV[0]) { *mult = 0; return (uint8_t) (Speed_data / SPEED_DIV[0]); } else { *mult = 1; int big_value = (int) ((Speed_data - (UINT8_MAX * SPEED_DIV[0])) / SPEED_DIV[1]); return (uint8_t) intRangeMax(big_value, 0, UINT8_MAX); } } /** * Encode Vertical Speed into a signed Integer ODID format * * @param SpeedVertical_data vertical speed (in m/s) * @return Encoded vertical speed */ static int8_t encodeSpeedVertical(float SpeedVertical_data) { int encValue = (int) (SpeedVertical_data / VSPEED_DIV); return (int8_t) intRangeMax(encValue, INT8_MIN, INT8_MAX); } /** * Encode Latitude or Longitude value into a signed Integer ODID format * * This encodes a 64bit double into a 32 bit integer yet still maintains * 10^7 of a degree of accuracy (about 1cm) * * @param LatLon_data Either Lat or Lon double float value * @return Encoded Lat or Lon */ static int32_t encodeLatLon(double LatLon_data) { return (int32_t) intRangeMax((int64_t) (LatLon_data * LATLON_MULT), -180 * LATLON_MULT, 180 * LATLON_MULT); } /** * Encode Altitude value into an int16 ODID format * * This encodes a 32bit floating point altitude into an uint16 compressed * scale that starts at -1000m. * * @param Alt_data Altitude to encode (in meters) * @return Encoded Altitude */ static uint16_t encodeAltitude(float Alt_data) { return (uint16_t) intRangeMax( (int) ((Alt_data + (float) ALT_ADDER) / ALT_DIV), 0, UINT16_MAX); } /** * Encode timestamp data in ODID format * * This encodes a fractional seconds value into a 2 byte int16 * on a scale of tenths of seconds since after the hour. * * @param Seconds_data Seconds (to at least 1 decimal place) since the hour * @return Encoded timestamp (Tenths of seconds since the hour) */ static uint16_t encodeTimeStamp(float Seconds_data) { if (Seconds_data == INV_TIMESTAMP) return INV_TIMESTAMP; else return (uint16_t) intRangeMax((int64_t) roundf(Seconds_data*10), 0, MAX_TIMESTAMP * 10); } /** * Encode area radius data in ODID format * * This encodes the area radius in meters into a 1 byte value * * @param Radius The radius of the drone area/swarm * @return Encoded area radius */ static uint8_t encodeAreaRadius(uint16_t Radius) { return (uint8_t) intRangeMax(Radius / 10, 0, 255); } /** * Encode Basic ID message (packed, ready for broadcast) * * @param outEncoded Output (encoded/packed) structure * @param inData Input data (non encoded/packed) structure * @return ODID_SUCCESS or ODID_FAIL; */ int encodeBasicIDMessage(ODID_BasicID_encoded *outEncoded, ODID_BasicID_data *inData) { if (!outEncoded || !inData || !intInRange(inData->IDType, 0, 15) || !intInRange(inData->UAType, 0, 15)) return ODID_FAIL; outEncoded->MessageType = ODID_MESSAGETYPE_BASIC_ID; outEncoded->ProtoVersion = ODID_PROTOCOL_VERSION; outEncoded->IDType = inData->IDType; outEncoded->UAType = inData->UAType; strncpy(outEncoded->UASID, inData->UASID, sizeof(outEncoded->UASID)); memset(outEncoded->Reserved, 0, sizeof(outEncoded->Reserved)); return ODID_SUCCESS; } /** * Encode Location message (packed, ready for broadcast) * * @param outEncoded Output (encoded/packed) structure * @param inData Input data (non encoded/packed) structure * @return ODID_SUCCESS or ODID_FAIL; */ int encodeLocationMessage(ODID_Location_encoded *outEncoded, ODID_Location_data *inData) { uint8_t bitflag; if (!outEncoded || !inData || !intInRange(inData->Status, 0, 15) || !intInRange(inData->HeightType, 0, 1) || !intInRange(inData->HorizAccuracy, 0, 15) || !intInRange(inData->VertAccuracy, 0, 15) || !intInRange(inData->BaroAccuracy, 0, 15) || !intInRange(inData->SpeedAccuracy, 0, 15) || !intInRange(inData->TSAccuracy, 0, 15)) return ODID_FAIL; if (inData->Direction < MIN_DIR || inData->Direction > INV_DIR || (inData->Direction > MAX_DIR && inData->Direction < INV_DIR)) return ODID_FAIL; if (inData->SpeedHorizontal < MIN_SPEED_H || inData->SpeedHorizontal > INV_SPEED_H || (inData->SpeedHorizontal > MAX_SPEED_H && inData->SpeedHorizontal < INV_SPEED_H)) return ODID_FAIL; if (inData->SpeedVertical < MIN_SPEED_V || inData->SpeedVertical > INV_SPEED_V || (inData->SpeedVertical > MAX_SPEED_V && inData->SpeedVertical < INV_SPEED_V)) return ODID_FAIL; if (inData->Latitude < MIN_LAT || inData->Latitude > MAX_LAT || inData->Longitude < MIN_LON || inData->Longitude > MAX_LON) return ODID_FAIL; if (inData->AltitudeBaro < MIN_ALT || inData->AltitudeBaro > MAX_ALT || inData->AltitudeGeo < MIN_ALT || inData->AltitudeGeo > MAX_ALT || inData->Height < MIN_ALT || inData->Height > MAX_ALT) return ODID_FAIL; if (inData->TimeStamp < 0 || (inData->TimeStamp > MAX_TIMESTAMP && inData->TimeStamp != INV_TIMESTAMP)) return ODID_FAIL; outEncoded->MessageType = ODID_MESSAGETYPE_LOCATION; outEncoded->ProtoVersion = ODID_PROTOCOL_VERSION; outEncoded->Status = inData->Status; outEncoded->Reserved = 0; outEncoded->Direction = encodeDirection(inData->Direction, &bitflag); outEncoded->EWDirection = bitflag; outEncoded->SpeedHorizontal = encodeSpeedHorizontal(inData->SpeedHorizontal, &bitflag); outEncoded->SpeedMult = bitflag; outEncoded->SpeedVertical = encodeSpeedVertical(inData->SpeedVertical); outEncoded->Latitude = encodeLatLon(inData->Latitude); outEncoded->Longitude = encodeLatLon(inData->Longitude); outEncoded->AltitudeBaro = encodeAltitude(inData->AltitudeBaro); outEncoded->AltitudeGeo = encodeAltitude(inData->AltitudeGeo); outEncoded->HeightType = inData->HeightType; outEncoded->Height = encodeAltitude(inData->Height); outEncoded->HorizAccuracy = inData->HorizAccuracy; outEncoded->VertAccuracy = inData->VertAccuracy; outEncoded->BaroAccuracy = inData->BaroAccuracy; outEncoded->SpeedAccuracy = inData->SpeedAccuracy; outEncoded->TSAccuracy = inData->TSAccuracy; outEncoded->Reserved2 = 0; outEncoded->TimeStamp = encodeTimeStamp(inData->TimeStamp); outEncoded->Reserved3 = 0; return ODID_SUCCESS; } /** * Encode Auth message (packed, ready for broadcast) * * @param outEncoded Output (encoded/packed) structure * @param inData Input data (non encoded/packed) structure * @return ODID_SUCCESS or ODID_FAIL; */ int encodeAuthMessage(ODID_Auth_encoded *outEncoded, ODID_Auth_data *inData) { if (!outEncoded || !inData || !intInRange(inData->AuthType, 0, 15)) return ODID_FAIL; if (inData->DataPage >= ODID_AUTH_MAX_PAGES) return ODID_FAIL; if (inData->DataPage == 0) { if (inData->LastPageIndex >= ODID_AUTH_MAX_PAGES) return ODID_FAIL; #if (MAX_AUTH_LENGTH < UINT8_MAX) if (inData->Length > MAX_AUTH_LENGTH) return ODID_FAIL; #endif int len = ODID_AUTH_PAGE_ZERO_DATA_SIZE + inData->LastPageIndex * ODID_AUTH_PAGE_NONZERO_DATA_SIZE; if (len < inData->Length) return ODID_FAIL; } outEncoded->page_zero.MessageType = ODID_MESSAGETYPE_AUTH; outEncoded->page_zero.ProtoVersion = ODID_PROTOCOL_VERSION; outEncoded->page_zero.AuthType = inData->AuthType; outEncoded->page_zero.DataPage = inData->DataPage; if (inData->DataPage == 0) { outEncoded->page_zero.LastPageIndex = inData->LastPageIndex; outEncoded->page_zero.Length = inData->Length; outEncoded->page_zero.Timestamp = inData->Timestamp; memcpy(outEncoded->page_zero.AuthData, inData->AuthData, sizeof(outEncoded->page_zero.AuthData)); } else { memcpy(outEncoded->page_non_zero.AuthData, inData->AuthData, sizeof(outEncoded->page_non_zero.AuthData)); } return ODID_SUCCESS; } /** * Encode Self ID message (packed, ready for broadcast) * * @param outEncoded Output (encoded/packed) structure * @param inData Input data (non encoded/packed) structure * @return ODID_SUCCESS or ODID_FAIL; */ int encodeSelfIDMessage(ODID_SelfID_encoded *outEncoded, ODID_SelfID_data *inData) { if (!outEncoded || !inData || !intInRange(inData->DescType, 0, 255)) return ODID_FAIL; outEncoded->MessageType = ODID_MESSAGETYPE_SELF_ID; outEncoded->ProtoVersion = ODID_PROTOCOL_VERSION; outEncoded->DescType = inData->DescType; strncpy(outEncoded->Desc, inData->Desc, sizeof(outEncoded->Desc)); return ODID_SUCCESS; } /** * Encode System message (packed, ready for broadcast) * * @param outEncoded Output (encoded/packed) structure * @param inData Input data (non encoded/packed) structure * @return ODID_SUCCESS or ODID_FAIL; */ int encodeSystemMessage(ODID_System_encoded *outEncoded, ODID_System_data *inData) { if (!outEncoded || !inData || !intInRange(inData->OperatorLocationType, 0, 3) || !intInRange(inData->ClassificationType, 0, 7) || !intInRange(inData->CategoryEU, 0, 15) || !intInRange(inData->ClassEU, 0, 15)) return ODID_FAIL; if (inData->OperatorLatitude < MIN_LAT || inData->OperatorLatitude > MAX_LAT || inData->OperatorLongitude < MIN_LON || inData->OperatorLongitude > MAX_LON) return ODID_FAIL; if (inData->AreaRadius > MAX_AREA_RADIUS) return ODID_FAIL; if (inData->AreaCeiling < MIN_ALT || inData->AreaCeiling > MAX_ALT || inData->AreaFloor < MIN_ALT || inData->AreaFloor > MAX_ALT || inData->OperatorAltitudeGeo < MIN_ALT || inData->OperatorAltitudeGeo > MAX_ALT) return ODID_FAIL; outEncoded->MessageType = ODID_MESSAGETYPE_SYSTEM; outEncoded->ProtoVersion = ODID_PROTOCOL_VERSION; outEncoded->Reserved = 0; outEncoded->OperatorLocationType = inData->OperatorLocationType; outEncoded->ClassificationType = inData->ClassificationType; outEncoded->OperatorLatitude = encodeLatLon(inData->OperatorLatitude); outEncoded->OperatorLongitude = encodeLatLon(inData->OperatorLongitude); outEncoded->AreaCount = inData->AreaCount; outEncoded->AreaRadius = encodeAreaRadius(inData->AreaRadius); outEncoded->AreaCeiling = encodeAltitude(inData->AreaCeiling); outEncoded->AreaFloor = encodeAltitude(inData->AreaFloor); outEncoded->CategoryEU = inData->CategoryEU; outEncoded->ClassEU = inData->ClassEU; outEncoded->OperatorAltitudeGeo = encodeAltitude(inData->OperatorAltitudeGeo); outEncoded->Timestamp = inData->Timestamp; outEncoded->Reserved2 = 0; return ODID_SUCCESS; } /** * Encode Operator ID message (packed, ready for broadcast) * * @param outEncoded Output (encoded/packed) structure * @param inData Input data (non encoded/packed) structure * @return ODID_SUCCESS or ODID_FAIL; */ int encodeOperatorIDMessage(ODID_OperatorID_encoded *outEncoded, ODID_OperatorID_data *inData) { if (!outEncoded || !inData || !intInRange(inData->OperatorIdType, 0, 255)) return ODID_FAIL; outEncoded->MessageType = ODID_MESSAGETYPE_OPERATOR_ID; outEncoded->ProtoVersion = ODID_PROTOCOL_VERSION; outEncoded->OperatorIdType = inData->OperatorIdType; strncpy(outEncoded->OperatorId, inData->OperatorId, sizeof(outEncoded->OperatorId)); memset(outEncoded->Reserved, 0, sizeof(outEncoded->Reserved)); return ODID_SUCCESS; } /** * Check whether the data fields of a pack structure are valid * * @param msgs Pointer to the buffer containing the messages * @param amount The amount of messages in the pack * @return ODID_SUCCESS or ODID_FAIL; */ static int checkPackContent(ODID_Message_encoded *msgs, int amount) { if (amount <= 0 || amount > ODID_PACK_MAX_MESSAGES) return ODID_FAIL; int numMessages[6] = { 0 }; // Counters for relevant parts of ODID_messagetype_t for (int i = 0; i < amount; i++) { uint8_t MessageType = decodeMessageType(msgs[i].rawData[0]); // Check for illegal content. This also avoids recursive calls between // decodeOpenDroneID() and decodeMessagePack()/checkPackContent() if (MessageType <= ODID_MESSAGETYPE_OPERATOR_ID) numMessages[MessageType]++; else return ODID_FAIL; } // Allow max one of each message except Basic ID and Authorization. if (numMessages[ODID_MESSAGETYPE_BASIC_ID] > ODID_BASIC_ID_MAX_MESSAGES || numMessages[ODID_MESSAGETYPE_LOCATION] > 1 || numMessages[ODID_MESSAGETYPE_AUTH] > ODID_AUTH_MAX_PAGES || numMessages[ODID_MESSAGETYPE_SELF_ID] > 1 || numMessages[ODID_MESSAGETYPE_SYSTEM] > 1 || numMessages[ODID_MESSAGETYPE_OPERATOR_ID] > 1) return ODID_FAIL; return ODID_SUCCESS; } /** * Encode message pack. I.e. a collection of multiple encoded messages * * @param outEncoded Output (encoded/packed) structure * @param inData Input data (non encoded/packed) structure * @return ODID_SUCCESS or ODID_FAIL; */ int encodeMessagePack(ODID_MessagePack_encoded *outEncoded, ODID_MessagePack_data *inData) { if (!outEncoded || !inData || inData->SingleMessageSize != ODID_MESSAGE_SIZE) return ODID_FAIL; if (checkPackContent(inData->Messages, inData->MsgPackSize) != ODID_SUCCESS) return ODID_FAIL; outEncoded->MessageType = ODID_MESSAGETYPE_PACKED; outEncoded->ProtoVersion = ODID_PROTOCOL_VERSION; outEncoded->SingleMessageSize = inData->SingleMessageSize; outEncoded->MsgPackSize = inData->MsgPackSize; for (int i = 0; i < inData->MsgPackSize; i++) memcpy(&outEncoded->Messages[i], &inData->Messages[i], ODID_MESSAGE_SIZE); return ODID_SUCCESS; } /** * Dencode direction from Open Drone ID packed message * * @param Direction_enc encoded direction * @param EWDirection East/West direction flag * @return direction in degrees (0 - 359) */ static float decodeDirection(uint8_t Direction_enc, uint8_t EWDirection) { if (EWDirection) return (float) Direction_enc + 180; else return (float) Direction_enc; } /** * Dencode speed from Open Drone ID packed message * * @param Speed_enc encoded speed * @param mult multiplier flag * @return decoded speed in m/s */ static float decodeSpeedHorizontal(uint8_t Speed_enc, uint8_t mult) { if (mult) return ((float) Speed_enc * SPEED_DIV[1]) + (UINT8_MAX * SPEED_DIV[0]); else return (float) Speed_enc * SPEED_DIV[0]; } /** * Decode Vertical Speed from Open Drone ID Packed Message * * @param SpeedVertical_enc Encoded Vertical Speed * @return decoded Vertical Speed in m/s */ static float decodeSpeedVertical(int8_t SpeedVertical_enc) { return (float) SpeedVertical_enc * VSPEED_DIV; } /** * Decode Latitude or Longitude value into a signed Integer ODID format * * @param LatLon_enc Either Lat or Lon ecoded int value * @return decoded (double) Lat or Lon */ static double decodeLatLon(int32_t LatLon_enc) { return (double) LatLon_enc / LATLON_MULT; } /** * Decode Altitude from ODID packed format * * @param Alt_enc Encoded Altitude to decode * @return decoded Altitude (in meters) */ static float decodeAltitude(uint16_t Alt_enc) { return (float) Alt_enc * ALT_DIV - (float) ALT_ADDER; } /** * Decode timestamp data from ODID packed format * * @param Seconds_enc Encoded Timestamp * @return Decoded timestamp (seconds since the hour) */ static float decodeTimeStamp(uint16_t Seconds_enc) { if (Seconds_enc == INV_TIMESTAMP) return INV_TIMESTAMP; else return (float) Seconds_enc / 10; } /** * Decode area radius data from ODID format * * This decodes a 1 byte value to the area radius in meters * * @param Radius_enc Encoded area radius * @return The radius of the drone area/swarm in meters */ static uint16_t decodeAreaRadius(uint8_t Radius_enc) { return (uint16_t) ((int) Radius_enc * 10); } /** * Get the ID type of the basic ID message * * @param inEncoded Input message (encoded/packed) structure * @param idType Output: The ID type of this basic ID message * @return ODID_SUCCESS or ODID_FAIL; */ int getBasicIDType(ODID_BasicID_encoded *inEncoded, enum ODID_idtype *idType) { if (!inEncoded || !idType || inEncoded->MessageType != ODID_MESSAGETYPE_BASIC_ID) return ODID_FAIL; *idType = (enum ODID_idtype) inEncoded->IDType; return ODID_SUCCESS; } /** * Decode Basic ID data from packed message * * @param outData Output: decoded message * @param inEncoded Input message (encoded/packed) structure * @return ODID_SUCCESS or ODID_FAIL; */ int decodeBasicIDMessage(ODID_BasicID_data *outData, ODID_BasicID_encoded *inEncoded) { if (!outData || !inEncoded || inEncoded->MessageType != ODID_MESSAGETYPE_BASIC_ID || !intInRange(inEncoded->IDType, 0, 15) || !intInRange(inEncoded->UAType, 0, 15)) return ODID_FAIL; outData->IDType = (ODID_idtype_t) inEncoded->IDType; outData->UAType = (ODID_uatype_t) inEncoded->UAType; safe_dec_copyfill(outData->UASID, inEncoded->UASID, sizeof(outData->UASID)); return ODID_SUCCESS; } /** * Decode Location data from packed message * * @param outData Output: decoded message * @param inEncoded Input message (encoded/packed) structure * @return ODID_SUCCESS or ODID_FAIL; */ int decodeLocationMessage(ODID_Location_data *outData, ODID_Location_encoded *inEncoded) { if (!outData || !inEncoded || inEncoded->MessageType != ODID_MESSAGETYPE_LOCATION || !intInRange(inEncoded->Status, 0, 15)) return ODID_FAIL; outData->Status = (ODID_status_t) inEncoded->Status; outData->Direction = decodeDirection(inEncoded->Direction, inEncoded-> EWDirection); outData->SpeedHorizontal = decodeSpeedHorizontal(inEncoded->SpeedHorizontal, inEncoded->SpeedMult); outData->SpeedVertical = decodeSpeedVertical(inEncoded->SpeedVertical); outData->Latitude = decodeLatLon(inEncoded->Latitude); outData->Longitude = decodeLatLon(inEncoded->Longitude); outData->AltitudeBaro = decodeAltitude(inEncoded->AltitudeBaro); outData->AltitudeGeo = decodeAltitude(inEncoded->AltitudeGeo); outData->HeightType = (ODID_Height_reference_t) inEncoded->HeightType; outData->Height = decodeAltitude(inEncoded->Height); outData->HorizAccuracy = (ODID_Horizontal_accuracy_t) inEncoded->HorizAccuracy; outData->VertAccuracy = (ODID_Vertical_accuracy_t) inEncoded->VertAccuracy; outData->BaroAccuracy = (ODID_Vertical_accuracy_t) inEncoded->BaroAccuracy; outData->SpeedAccuracy = (ODID_Speed_accuracy_t) inEncoded->SpeedAccuracy; outData->TSAccuracy = (ODID_Timestamp_accuracy_t) inEncoded->TSAccuracy; outData->TimeStamp = decodeTimeStamp(inEncoded->TimeStamp); return ODID_SUCCESS; } /** * Get the page number of the authorization message * * @param inEncoded Input message (encoded/packed) structure * @param pageNum Output: The page number of this authorization message * @return ODID_SUCCESS or ODID_FAIL; */ int getAuthPageNum(ODID_Auth_encoded *inEncoded, int *pageNum) { if (!inEncoded || !pageNum || inEncoded->page_zero.MessageType != ODID_MESSAGETYPE_AUTH || !intInRange(inEncoded->page_zero.AuthType, 0, 15) || !intInRange(inEncoded->page_zero.DataPage, 0, ODID_AUTH_MAX_PAGES - 1)) return ODID_FAIL; *pageNum = inEncoded->page_zero.DataPage; return ODID_SUCCESS; } /** * Decode Auth data from packed message * * @param outData Output: decoded message * @param inEncoded Input message (encoded/packed) structure * @return ODID_SUCCESS or ODID_FAIL; */ int decodeAuthMessage(ODID_Auth_data *outData, ODID_Auth_encoded *inEncoded) { if (!outData || !inEncoded || inEncoded->page_zero.MessageType != ODID_MESSAGETYPE_AUTH || !intInRange(inEncoded->page_zero.AuthType, 0, 15) || !intInRange(inEncoded->page_zero.DataPage, 0, ODID_AUTH_MAX_PAGES - 1)) return ODID_FAIL; if (inEncoded->page_zero.DataPage == 0) { if (inEncoded->page_zero.LastPageIndex >= ODID_AUTH_MAX_PAGES) return ODID_FAIL; #if (MAX_AUTH_LENGTH < UINT8_MAX) if (inEncoded->page_zero.Length > MAX_AUTH_LENGTH) return ODID_FAIL; #endif int len = ODID_AUTH_PAGE_ZERO_DATA_SIZE + inEncoded->page_zero.LastPageIndex * ODID_AUTH_PAGE_NONZERO_DATA_SIZE; if (len < inEncoded->page_zero.Length) return ODID_FAIL; } outData->AuthType = (ODID_authtype_t) inEncoded->page_zero.AuthType; outData->DataPage = inEncoded->page_zero.DataPage; if (inEncoded->page_zero.DataPage == 0) { outData->LastPageIndex = inEncoded->page_zero.LastPageIndex; outData->Length = inEncoded->page_zero.Length; outData->Timestamp = inEncoded->page_zero.Timestamp; memset(outData->AuthData, 0, sizeof(outData->AuthData)); memcpy(outData->AuthData, inEncoded->page_zero.AuthData, ODID_AUTH_PAGE_ZERO_DATA_SIZE); } else { memset(outData->AuthData, 0, sizeof(outData->AuthData)); memcpy(outData->AuthData, inEncoded->page_non_zero.AuthData, ODID_AUTH_PAGE_NONZERO_DATA_SIZE); } return ODID_SUCCESS; } /** * Decode Self ID data from packed message * * @param outData Output: decoded message * @param inEncoded Input message (encoded/packed) structure * @return ODID_SUCCESS or ODID_FAIL; */ int decodeSelfIDMessage(ODID_SelfID_data *outData, ODID_SelfID_encoded *inEncoded) { if (!outData || !inEncoded || inEncoded->MessageType != ODID_MESSAGETYPE_SELF_ID) return ODID_FAIL; outData->DescType = (ODID_desctype_t) inEncoded->DescType; safe_dec_copyfill(outData->Desc, inEncoded->Desc, sizeof(outData->Desc)); return ODID_SUCCESS; } /** * Decode System data from packed message * * @param outData Output: decoded message * @param inEncoded Input message (encoded/packed) structure * @return ODID_SUCCESS or ODID_FAIL; */ int decodeSystemMessage(ODID_System_data *outData, ODID_System_encoded *inEncoded) { if (!outData || !inEncoded || inEncoded->MessageType != ODID_MESSAGETYPE_SYSTEM) return ODID_FAIL; outData->OperatorLocationType = (ODID_operator_location_type_t) inEncoded->OperatorLocationType; outData->ClassificationType = (ODID_classification_type_t) inEncoded->ClassificationType; outData->OperatorLatitude = decodeLatLon(inEncoded->OperatorLatitude); outData->OperatorLongitude = decodeLatLon(inEncoded->OperatorLongitude); outData->AreaCount = inEncoded->AreaCount; outData->AreaRadius = decodeAreaRadius(inEncoded->AreaRadius); outData->AreaCeiling = decodeAltitude(inEncoded->AreaCeiling); outData->AreaFloor = decodeAltitude(inEncoded->AreaFloor); outData->CategoryEU = (ODID_category_EU_t) inEncoded->CategoryEU; outData->ClassEU = (ODID_class_EU_t) inEncoded->ClassEU; outData->OperatorAltitudeGeo = decodeAltitude(inEncoded->OperatorAltitudeGeo); outData->Timestamp = inEncoded->Timestamp; return ODID_SUCCESS; } /** * Decode Operator ID data from packed message * * @param outData Output: decoded message * @param inEncoded Input message (encoded/packed) structure * @return ODID_SUCCESS or ODID_FAIL; */ int decodeOperatorIDMessage(ODID_OperatorID_data *outData, ODID_OperatorID_encoded *inEncoded) { if (!outData || !inEncoded || inEncoded->MessageType != ODID_MESSAGETYPE_OPERATOR_ID) return ODID_FAIL; outData->OperatorIdType = (ODID_operatorIdType_t) inEncoded->OperatorIdType; safe_dec_copyfill(outData->OperatorId, inEncoded->OperatorId, sizeof(outData->OperatorId)); return ODID_SUCCESS; } /** * Decode Message Pack from packed message * * The various Valid flags in uasData are set true whenever a message has been * decoded and the corresponding data structure has been filled. The caller must * clear these flags before calling decodeMessagePack(). * * @param uasData Output: Structure containing buffers for all message data * @param pack Pointer to an encoded packed message * @return ODID_SUCCESS or ODID_FAIL; */ int decodeMessagePack(ODID_UAS_Data *uasData, ODID_MessagePack_encoded *pack) { if (!uasData || !pack || pack->MessageType != ODID_MESSAGETYPE_PACKED) return ODID_FAIL; if (pack->SingleMessageSize != ODID_MESSAGE_SIZE) return ODID_FAIL; if (checkPackContent(pack->Messages, pack->MsgPackSize) != ODID_SUCCESS) return ODID_FAIL; for (int i = 0; i < pack->MsgPackSize; i++) { decodeOpenDroneID(uasData, pack->Messages[i].rawData); } return ODID_SUCCESS; } /** * Decodes the message type of a packed Open Drone ID message * * @param byte The first byte of the message * @return The message type: ODID_messagetype_t */ ODID_messagetype_t decodeMessageType(uint8_t byte) { switch (byte >> 4) { case ODID_MESSAGETYPE_BASIC_ID: return ODID_MESSAGETYPE_BASIC_ID; case ODID_MESSAGETYPE_LOCATION: return ODID_MESSAGETYPE_LOCATION; case ODID_MESSAGETYPE_AUTH: return ODID_MESSAGETYPE_AUTH; case ODID_MESSAGETYPE_SELF_ID: return ODID_MESSAGETYPE_SELF_ID; case ODID_MESSAGETYPE_SYSTEM: return ODID_MESSAGETYPE_SYSTEM; case ODID_MESSAGETYPE_OPERATOR_ID: return ODID_MESSAGETYPE_OPERATOR_ID; case ODID_MESSAGETYPE_PACKED: return ODID_MESSAGETYPE_PACKED; default: return ODID_MESSAGETYPE_INVALID; } } /** * Parse encoded Open Drone ID data to identify the message type. Then decode * from Open Drone ID packed format into the appropriate Open Drone ID structure * * This function assumes that msgData points to a buffer containing all * ODID_MESSAGE_SIZE bytes of an Open Drone ID message. * * The various Valid flags in uasData are set true whenever a message has been * decoded and the corresponding data structure has been filled. The caller must * clear these flags before calling decodeOpenDroneID(). * * @param uasData Structure containing buffers for all message data * @param msgData Pointer to a buffer containing a full encoded Open Drone ID * message * @return The message type: ODID_messagetype_t */ ODID_messagetype_t decodeOpenDroneID(ODID_UAS_Data *uasData, uint8_t *msgData) { if (!uasData || !msgData) return ODID_MESSAGETYPE_INVALID; switch (decodeMessageType(msgData[0])) { case ODID_MESSAGETYPE_BASIC_ID: { ODID_BasicID_encoded *basicId = (ODID_BasicID_encoded *) msgData; enum ODID_idtype idType; if (getBasicIDType(basicId, &idType) == ODID_SUCCESS) { // Find a free slot to store the current message in or overwrite old data of the same type. for (int i = 0; i < ODID_BASIC_ID_MAX_MESSAGES; i++) { enum ODID_idtype storedType = uasData->BasicID[i].IDType; if (storedType == ODID_IDTYPE_NONE || storedType == idType) { if (decodeBasicIDMessage(&uasData->BasicID[i], basicId) == ODID_SUCCESS) { uasData->BasicIDValid[i] = 1; return ODID_MESSAGETYPE_BASIC_ID; } } } } break; } case ODID_MESSAGETYPE_LOCATION: { ODID_Location_encoded *location = (ODID_Location_encoded *) msgData; if (decodeLocationMessage(&uasData->Location, location) == ODID_SUCCESS) { uasData->LocationValid = 1; return ODID_MESSAGETYPE_LOCATION; } break; } case ODID_MESSAGETYPE_AUTH: { ODID_Auth_encoded *auth = (ODID_Auth_encoded *) msgData; int pageNum; if (getAuthPageNum(auth, &pageNum) == ODID_SUCCESS) { ODID_Auth_data *authData = &uasData->Auth[pageNum]; if (decodeAuthMessage(authData, auth) == ODID_SUCCESS) { uasData->AuthValid[pageNum] = 1; return ODID_MESSAGETYPE_AUTH; } } break; } case ODID_MESSAGETYPE_SELF_ID: { ODID_SelfID_encoded *selfId = (ODID_SelfID_encoded *) msgData; if (decodeSelfIDMessage(&uasData->SelfID, selfId) == ODID_SUCCESS) { uasData->SelfIDValid = 1; return ODID_MESSAGETYPE_SELF_ID; } break; } case ODID_MESSAGETYPE_SYSTEM: { ODID_System_encoded *system = (ODID_System_encoded *) msgData; if (decodeSystemMessage(&uasData->System, system) == ODID_SUCCESS) { uasData->SystemValid = 1; return ODID_MESSAGETYPE_SYSTEM; } break; } case ODID_MESSAGETYPE_OPERATOR_ID: { ODID_OperatorID_encoded *operatorId = (ODID_OperatorID_encoded *) msgData; if (decodeOperatorIDMessage(&uasData->OperatorID, operatorId) == ODID_SUCCESS) { uasData->OperatorIDValid = 1; return ODID_MESSAGETYPE_OPERATOR_ID; } break; } case ODID_MESSAGETYPE_PACKED: { ODID_MessagePack_encoded *pack = (ODID_MessagePack_encoded *) msgData; if (decodeMessagePack(uasData, pack) == ODID_SUCCESS) return ODID_MESSAGETYPE_PACKED; break; } default: break; } return ODID_MESSAGETYPE_INVALID; } /** * Safely fill then copy string to destination (when decoding) * * This prevents overrun and guarantees copy behavior (fully null padded) * This function was specially made because the encoded data may not be null * terminated (if full size). * Therefore, the destination must use the last byte for a null (and is +1 in size) * * @param dstStr Destination string * @param srcStr Source string * @param dstSize Destination size */ static char *safe_dec_copyfill(char *dstStr, const char *srcStr, int dstSize) { memset(dstStr, 0, dstSize); // fills destination with nulls strncpy(dstStr, srcStr, dstSize-1); // copy only up to dst size-1 (no overruns) return dstStr; } /** * Safely range check a value and return the minimum or max within the range if exceeded * * @param inValue Value to range-check * @param startRange Start of range to compare * @param endRange End of range to compare * @return same value if it fits, otherwise, min or max of range as appropriate. */ static int intRangeMax(int64_t inValue, int startRange, int endRange) { if ( inValue < startRange ) { return startRange; } else if (inValue > endRange) { return endRange; } else { return (int) inValue; } } /** * Determine if an Int is in range * * @param inValue Value to range-check * @param startRange Start of range to compare * @param endRange End of range to compare * @return 1 = yes, 0 = no */ static int intInRange(int inValue, int startRange, int endRange) { if (inValue < startRange || inValue > endRange) { return 0; } else { return 1; } } /** * This converts a horizontal accuracy float value to the corresponding enum * * @param Accuracy The horizontal accuracy in meters * @return Enum value representing the accuracy */ ODID_Horizontal_accuracy_t createEnumHorizontalAccuracy(float Accuracy) { if (Accuracy >= 18520) return ODID_HOR_ACC_UNKNOWN; else if (Accuracy >= 7408) return ODID_HOR_ACC_10NM; else if (Accuracy >= 3704) return ODID_HOR_ACC_4NM; else if (Accuracy >= 1852) return ODID_HOR_ACC_2NM; else if (Accuracy >= 926) return ODID_HOR_ACC_1NM; else if (Accuracy >= 555.6f) return ODID_HOR_ACC_0_5NM; else if (Accuracy >= 185.2f) return ODID_HOR_ACC_0_3NM; else if (Accuracy >= 92.6f) return ODID_HOR_ACC_0_1NM; else if (Accuracy >= 30) return ODID_HOR_ACC_0_05NM; else if (Accuracy >= 10) return ODID_HOR_ACC_30_METER; else if (Accuracy >= 3) return ODID_HOR_ACC_10_METER; else if (Accuracy >= 1) return ODID_HOR_ACC_3_METER; else if (Accuracy > 0) return ODID_HOR_ACC_1_METER; else return ODID_HOR_ACC_UNKNOWN; } /** * This converts a vertical accuracy float value to the corresponding enum * * @param Accuracy The vertical accuracy in meters * @return Enum value representing the accuracy */ ODID_Vertical_accuracy_t createEnumVerticalAccuracy(float Accuracy) { if (Accuracy >= 150) return ODID_VER_ACC_UNKNOWN; else if (Accuracy >= 45) return ODID_VER_ACC_150_METER; else if (Accuracy >= 25) return ODID_VER_ACC_45_METER; else if (Accuracy >= 10) return ODID_VER_ACC_25_METER; else if (Accuracy >= 3) return ODID_VER_ACC_10_METER; else if (Accuracy >= 1) return ODID_VER_ACC_3_METER; else if (Accuracy > 0) return ODID_VER_ACC_1_METER; else return ODID_VER_ACC_UNKNOWN; } /** * This converts a speed accuracy float value to the corresponding enum * * @param Accuracy The speed accuracy in m/s * @return Enum value representing the accuracy */ ODID_Speed_accuracy_t createEnumSpeedAccuracy(float Accuracy) { if (Accuracy >= 10) return ODID_SPEED_ACC_UNKNOWN; else if (Accuracy >= 3) return ODID_SPEED_ACC_10_METERS_PER_SECOND; else if (Accuracy >= 1) return ODID_SPEED_ACC_3_METERS_PER_SECOND; else if (Accuracy >= 0.3f) return ODID_SPEED_ACC_1_METERS_PER_SECOND; else if (Accuracy > 0) return ODID_SPEED_ACC_0_3_METERS_PER_SECOND; else return ODID_SPEED_ACC_UNKNOWN; } /** * This converts a timestamp accuracy float value to the corresponding enum * * @param Accuracy The timestamp accuracy in seconds * @return Enum value representing the accuracy */ ODID_Timestamp_accuracy_t createEnumTimestampAccuracy(float Accuracy) { if (Accuracy > 1.5f) return ODID_TIME_ACC_UNKNOWN; else if (Accuracy > 1.4f) return ODID_TIME_ACC_1_5_SECOND; else if (Accuracy > 1.3f) return ODID_TIME_ACC_1_4_SECOND; else if (Accuracy > 1.2f) return ODID_TIME_ACC_1_3_SECOND; else if (Accuracy > 1.1f) return ODID_TIME_ACC_1_2_SECOND; else if (Accuracy > 1.0f) return ODID_TIME_ACC_1_1_SECOND; else if (Accuracy > 0.9f) return ODID_TIME_ACC_1_0_SECOND; else if (Accuracy > 0.8f) return ODID_TIME_ACC_0_9_SECOND; else if (Accuracy > 0.7f) return ODID_TIME_ACC_0_8_SECOND; else if (Accuracy > 0.6f) return ODID_TIME_ACC_0_7_SECOND; else if (Accuracy > 0.5f) return ODID_TIME_ACC_0_6_SECOND; else if (Accuracy > 0.4f) return ODID_TIME_ACC_0_5_SECOND; else if (Accuracy > 0.3f) return ODID_TIME_ACC_0_4_SECOND; else if (Accuracy > 0.2f) return ODID_TIME_ACC_0_3_SECOND; else if (Accuracy > 0.1f) return ODID_TIME_ACC_0_2_SECOND; else if (Accuracy > 0.0f) return ODID_TIME_ACC_0_1_SECOND; else return ODID_TIME_ACC_UNKNOWN; } /** * This decodes a horizontal accuracy enum to the corresponding float value * * @param Accuracy Enum value representing the accuracy * @return The maximum horizontal accuracy in meters */ float decodeHorizontalAccuracy(ODID_Horizontal_accuracy_t Accuracy) { switch (Accuracy) { case ODID_HOR_ACC_UNKNOWN: return 18520; case ODID_HOR_ACC_10NM: return 18520; case ODID_HOR_ACC_4NM: return 7808; case ODID_HOR_ACC_2NM: return 3704; case ODID_HOR_ACC_1NM: return 1852; case ODID_HOR_ACC_0_5NM: return 926; case ODID_HOR_ACC_0_3NM: return 555.6f; case ODID_HOR_ACC_0_1NM: return 185.2f; case ODID_HOR_ACC_0_05NM: return 92.6f; case ODID_HOR_ACC_30_METER: return 30; case ODID_HOR_ACC_10_METER: return 10; case ODID_HOR_ACC_3_METER: return 3; case ODID_HOR_ACC_1_METER: return 1; default: return 18520; } } /** * This decodes a vertical accuracy enum to the corresponding float value * * @param Accuracy Enum value representing the accuracy * @return The maximum vertical accuracy in meters */ float decodeVerticalAccuracy(ODID_Vertical_accuracy_t Accuracy) { switch (Accuracy) { case ODID_VER_ACC_UNKNOWN: return 150; case ODID_VER_ACC_150_METER: return 150; case ODID_VER_ACC_45_METER: return 45; case ODID_VER_ACC_25_METER: return 25; case ODID_VER_ACC_10_METER: return 10; case ODID_VER_ACC_3_METER: return 3; case ODID_VER_ACC_1_METER: return 1; default: return 150; } } /** * This decodes a speed accuracy enum to the corresponding float value * * @param Accuracy Enum value representing the accuracy * @return The maximum speed accuracy in m/s */ float decodeSpeedAccuracy(ODID_Speed_accuracy_t Accuracy) { switch (Accuracy) { case ODID_SPEED_ACC_UNKNOWN: return 10; case ODID_SPEED_ACC_10_METERS_PER_SECOND: return 10; case ODID_SPEED_ACC_3_METERS_PER_SECOND: return 3; case ODID_SPEED_ACC_1_METERS_PER_SECOND: return 1; case ODID_SPEED_ACC_0_3_METERS_PER_SECOND: return 0.3f; default: return 10; } } /** * This decodes a timestamp accuracy enum to the corresponding float value * * @param Accuracy Enum value representing the accuracy * @return The maximum timestamp accuracy in seconds */ float decodeTimestampAccuracy(ODID_Timestamp_accuracy_t Accuracy) { switch (Accuracy) { case ODID_TIME_ACC_UNKNOWN: return 0.0f; case ODID_TIME_ACC_0_1_SECOND: return 0.1f; case ODID_TIME_ACC_0_2_SECOND: return 0.2f; case ODID_TIME_ACC_0_3_SECOND: return 0.3f; case ODID_TIME_ACC_0_4_SECOND: return 0.4f; case ODID_TIME_ACC_0_5_SECOND: return 0.5f; case ODID_TIME_ACC_0_6_SECOND: return 0.6f; case ODID_TIME_ACC_0_7_SECOND: return 0.7f; case ODID_TIME_ACC_0_8_SECOND: return 0.8f; case ODID_TIME_ACC_0_9_SECOND: return 0.9f; case ODID_TIME_ACC_1_0_SECOND: return 1.0f; case ODID_TIME_ACC_1_1_SECOND: return 1.1f; case ODID_TIME_ACC_1_2_SECOND: return 1.2f; case ODID_TIME_ACC_1_3_SECOND: return 1.3f; case ODID_TIME_ACC_1_4_SECOND: return 1.4f; case ODID_TIME_ACC_1_5_SECOND: return 1.5f; default: return 0.0f; } } #ifndef ODID_DISABLE_PRINTF /** * Print array of bytes as a hex string * * @param byteArray Array of bytes to be printed * @param asize Size of array of bytes to be printed */ void printByteArray(uint8_t *byteArray, uint16_t asize, int spaced) { if (ENABLE_DEBUG) { int x; for (x=0;xUASID, ODID_ID_SIZE); const char ODID_BasicID_data_format[] = "UAType: %d\nIDType: %d\nUASID: %s\n"; printf(ODID_BasicID_data_format, BasicID->IDType, BasicID->UAType, buf); } /** * Print formatted Location Data * * @param Location structure to be printed */ void printLocation_data(ODID_Location_data *Location) { const char ODID_Location_data_format[] = "Status: %d\nDirection: %.1f\nSpeedHori: %.2f\nSpeedVert: "\ "%.2f\nLat/Lon: %.7f, %.7f\nAlt: Baro, Geo, Height above %s: %.2f, "\ "%.2f, %.2f\nHoriz, Vert, Baro, Speed, TS Accuracy: %.1f, %.1f, %.1f, "\ "%.1f, %.1f\nTimeStamp: %.2f\n"; printf(ODID_Location_data_format, Location->Status, (double) Location->Direction, (double) Location->SpeedHorizontal, (double) Location->SpeedVertical, Location->Latitude, Location->Longitude, Location->HeightType ? "Ground" : "TakeOff", (double) Location->AltitudeBaro, (double) Location->AltitudeGeo, (double) Location->Height, (double) decodeHorizontalAccuracy(Location->HorizAccuracy), (double) decodeVerticalAccuracy(Location->VertAccuracy), (double) decodeVerticalAccuracy(Location->BaroAccuracy), (double) decodeSpeedAccuracy(Location->SpeedAccuracy), (double) decodeTimestampAccuracy(Location->TSAccuracy), (double) Location->TimeStamp); } /** * Print formatted Auth Data * * @param Auth structure to be printed */ void printAuth_data(ODID_Auth_data *Auth) { if (Auth->DataPage == 0) { const char ODID_Auth_data_format[] = "AuthType: %d\nDataPage: %d\nLastPageIndex: %d\nLength: %d\n"\ "Timestamp: %u\nAuthData: "; printf(ODID_Auth_data_format, Auth->AuthType, Auth->DataPage, Auth->LastPageIndex, Auth->Length, Auth->Timestamp); for (int i = 0; i < ODID_AUTH_PAGE_ZERO_DATA_SIZE; i++) printf("0x%02X ", Auth->AuthData[i]); } else { const char ODID_Auth_data_format[] = "AuthType: %d\nDataPage: %d\nAuthData: "; printf(ODID_Auth_data_format, Auth->AuthType, Auth->DataPage); for (int i = 0; i < ODID_AUTH_PAGE_NONZERO_DATA_SIZE; i++) printf("0x%02X ", Auth->AuthData[i]); } printf("\n"); } /** * Print formatted SelfID Data * * @param SelfID structure to be printed */ void printSelfID_data(ODID_SelfID_data *SelfID) { // Ensure the description is null-terminated char buf[ODID_STR_SIZE + 1] = { 0 }; memcpy(buf, SelfID->Desc, ODID_STR_SIZE); const char ODID_SelfID_data_format[] = "DescType: %d\nDesc: %s\n"; printf(ODID_SelfID_data_format, SelfID->DescType, buf); } /** * Print formatted System Data * * @param System_data structure to be printed */ void printSystem_data(ODID_System_data *System_data) { const char ODID_System_data_format[] = "Operator Location Type: %d\n" "Classification Type: %d\nLat/Lon: %.7f, %.7f\n" "Area Count, Radius, Ceiling, Floor: %d, %d, %.2f, %.2f\n" "Category EU: %d, Class EU: %d, Altitude: %.2f, Timestamp: %u\n"; printf(ODID_System_data_format, System_data->OperatorLocationType, System_data->ClassificationType, System_data->OperatorLatitude, System_data->OperatorLongitude, System_data->AreaCount, System_data->AreaRadius, (double) System_data->AreaCeiling, (double) System_data->AreaFloor, System_data->CategoryEU, System_data->ClassEU, (double) System_data->OperatorAltitudeGeo, System_data->Timestamp); } /** * Print formatted OperatorID Data * * @param OperatorID structure to be printed */ void printOperatorID_data(ODID_OperatorID_data *operatorID) { // Ensure the ID is null-terminated char buf[ODID_ID_SIZE + 1] = { 0 }; memcpy(buf, operatorID->OperatorId, ODID_ID_SIZE); const char ODID_OperatorID_data_format[] = "OperatorIdType: %d\nOperatorId: %s\n"; printf(ODID_OperatorID_data_format, operatorID->OperatorIdType, buf); } #endif // ODID_DISABLE_PRINTF