Added COVSubscribe and COVSubscribeProperty service encoding and unit tests.
This commit is contained in:
@@ -273,6 +273,261 @@ int cov_notify_decode_service_request(uint8_t * apdu,
|
||||
|
||||
return len;
|
||||
}
|
||||
/*
|
||||
12.11.38Active_COV_Subscriptions
|
||||
The Active_COV_Subscriptions property is a List of BACnetCOVSubscription, each of which consists of a Recipient, a
|
||||
Monitored Property Reference, an Issue Confirmed Notifications flag, a Time Remaining value and an optional COV
|
||||
Increment. This property provides a network-visible indication of those COV subscriptions that are active at any given time.
|
||||
Whenever a COV Subscription is created with the SubscribeCOV or SubscribeCOVProperty service, a new entry is added to
|
||||
the Active_COV_Subscriptions list. Similarly, whenever a COV Subscription is terminated, the corresponding entry shall be
|
||||
removed from the Active_COV_Subscriptions list.
|
||||
*/
|
||||
/*
|
||||
SubscribeCOV-Request ::= SEQUENCE {
|
||||
subscriberProcessIdentifier [0] Unsigned32,
|
||||
monitoredObjectIdentifier [1] BACnetObjectIdentifier,
|
||||
issueConfirmedNotifications [2] BOOLEAN OPTIONAL,
|
||||
lifetime [3] Unsigned OPTIONAL
|
||||
}
|
||||
*/
|
||||
|
||||
int cov_subscribe_encode_adpu(
|
||||
uint8_t * apdu,
|
||||
uint8_t invoke_id,
|
||||
BACNET_SUBSCRIBE_COV_DATA * data)
|
||||
{
|
||||
int len = 0; /* length of each encoding */
|
||||
int apdu_len = 0; /* total length of the apdu, return value */
|
||||
uint16_t max_apdu = Device_Max_APDU_Length_Accepted();
|
||||
|
||||
if (apdu && data) {
|
||||
apdu[0] = PDU_TYPE_CONFIRMED_SERVICE_REQUEST;
|
||||
apdu[1] = encode_max_segs_max_apdu(0, max_apdu);
|
||||
apdu[2] = invoke_id;
|
||||
apdu[3] = SERVICE_CONFIRMED_SUBSCRIBE_COV;
|
||||
apdu_len = 4;
|
||||
/* tag 0 - subscriberProcessIdentifier */
|
||||
len = encode_context_unsigned(&apdu[apdu_len],
|
||||
0, data->subscriberProcessIdentifier);
|
||||
apdu_len += len;
|
||||
/* tag 1 - monitoredObjectIdentifier */
|
||||
len = encode_context_object_id(&apdu[apdu_len],
|
||||
1,
|
||||
data->monitoredObjectIdentifier.type,
|
||||
data->monitoredObjectIdentifier.instance);
|
||||
apdu_len += len;
|
||||
/*
|
||||
If both the 'Issue Confirmed Notifications' and
|
||||
'Lifetime' parameters are absent, then this shall
|
||||
indicate a cancellation request.
|
||||
*/
|
||||
if (!data->cancellationRequest) {
|
||||
/* tag 2 - issueConfirmedNotifications */
|
||||
len = encode_context_boolean(&apdu[apdu_len],
|
||||
2, data->issueConfirmedNotifications);
|
||||
apdu_len += len;
|
||||
/* tag 3 - lifetime */
|
||||
len = encode_context_unsigned(&apdu[apdu_len],
|
||||
3, data->lifetime);
|
||||
apdu_len += len;
|
||||
}
|
||||
}
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
/* decode the service request only */
|
||||
int cov_subscribe_decode_service_request(uint8_t * apdu,
|
||||
unsigned apdu_len, BACNET_SUBSCRIBE_COV_DATA * data)
|
||||
{
|
||||
int len = 0; /* return value */
|
||||
uint8_t tag_number = 0;
|
||||
uint32_t len_value = 0;
|
||||
uint32_t decoded_value = 0; /* for decoding */
|
||||
int decoded_type = 0; /* for decoding */
|
||||
|
||||
if (apdu_len && data) {
|
||||
/* tag 0 - subscriberProcessIdentifier */
|
||||
if (decode_is_context_tag(&apdu[len], 0)) {
|
||||
len +=
|
||||
decode_tag_number_and_value(&apdu[len], &tag_number,
|
||||
&len_value);
|
||||
len += decode_unsigned(&apdu[len], len_value, &decoded_value);
|
||||
data->subscriberProcessIdentifier = decoded_value;
|
||||
} else
|
||||
return -1;
|
||||
/* tag 1 - monitoredObjectIdentifier */
|
||||
if (decode_is_context_tag(&apdu[len], 1)) {
|
||||
len +=
|
||||
decode_tag_number_and_value(&apdu[len], &tag_number,
|
||||
&len_value);
|
||||
len +=
|
||||
decode_object_id(&apdu[len], &decoded_type,
|
||||
&data->monitoredObjectIdentifier.instance);
|
||||
data->monitoredObjectIdentifier.type = decoded_type;
|
||||
} else
|
||||
return -1;
|
||||
/* tag 2 - issueConfirmedNotifications - optional */
|
||||
if (decode_is_context_tag(&apdu[len], 2)) {
|
||||
data->cancellationRequest = false;
|
||||
len +=
|
||||
decode_tag_number_and_value(&apdu[len], &tag_number,
|
||||
&len_value);
|
||||
data->issueConfirmedNotifications =
|
||||
decode_context_boolean(&apdu[len]);
|
||||
len += len_value;
|
||||
} else
|
||||
data->cancellationRequest = true;
|
||||
/* tag 3 - lifetime - optional */
|
||||
if (decode_is_context_tag(&apdu[len], 3)) {
|
||||
len +=
|
||||
decode_tag_number_and_value(&apdu[len], &tag_number,
|
||||
&len_value);
|
||||
len += decode_unsigned(&apdu[len], len_value, &decoded_value);
|
||||
data->lifetime = decoded_value;
|
||||
} else
|
||||
data->lifetime = 0;
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
SubscribeCOVProperty-Request ::= SEQUENCE {
|
||||
subscriberProcessIdentifier [0] Unsigned32,
|
||||
monitoredObjectIdentifier [1] BACnetObjectIdentifier,
|
||||
issueConfirmedNotifications [2] BOOLEAN OPTIONAL,
|
||||
lifetime [3] Unsigned OPTIONAL,
|
||||
monitoredPropertyIdentifier [4] BACnetPropertyReference,
|
||||
covIncrement [5] REAL OPTIONAL
|
||||
}
|
||||
|
||||
*/
|
||||
int cov_subscribe_property_encode_adpu(
|
||||
uint8_t * apdu,
|
||||
uint8_t invoke_id,
|
||||
BACNET_SUBSCRIBE_COV_DATA * data)
|
||||
{
|
||||
int len = 0; /* length of each encoding */
|
||||
int apdu_len = 0; /* total length of the apdu, return value */
|
||||
uint16_t max_apdu = Device_Max_APDU_Length_Accepted();
|
||||
|
||||
if (apdu && data) {
|
||||
apdu[0] = PDU_TYPE_CONFIRMED_SERVICE_REQUEST;
|
||||
apdu[1] = encode_max_segs_max_apdu(0, max_apdu);
|
||||
apdu[2] = invoke_id;
|
||||
apdu[3] = SERVICE_CONFIRMED_SUBSCRIBE_COV_PROPERTY;
|
||||
apdu_len = 4;
|
||||
/* tag 0 - subscriberProcessIdentifier */
|
||||
len = encode_context_unsigned(&apdu[apdu_len],
|
||||
0, data->subscriberProcessIdentifier);
|
||||
apdu_len += len;
|
||||
/* tag 1 - monitoredObjectIdentifier */
|
||||
len = encode_context_object_id(&apdu[apdu_len],
|
||||
1,
|
||||
data->monitoredObjectIdentifier.type,
|
||||
data->monitoredObjectIdentifier.instance);
|
||||
apdu_len += len;
|
||||
if (!data->cancellationRequest) {
|
||||
/* tag 2 - issueConfirmedNotifications */
|
||||
len = encode_context_boolean(&apdu[apdu_len],
|
||||
2, data->issueConfirmedNotifications);
|
||||
apdu_len += len;
|
||||
/* tag 3 - lifetime */
|
||||
len = encode_context_unsigned(&apdu[apdu_len],
|
||||
3, data->lifetime);
|
||||
apdu_len += len;
|
||||
}
|
||||
/* tag 4 - monitoredPropertyIdentifier */
|
||||
len = encode_context_enumerated(&apdu[apdu_len],
|
||||
4, data->monitoredPropertyIdentifier);
|
||||
apdu_len += len;
|
||||
/* tag 5 - covIncrement */
|
||||
if (data->covIncrementPresent) {
|
||||
len = encode_context_real(&apdu[apdu_len],
|
||||
5, data->covIncrement);
|
||||
apdu_len += len;
|
||||
}
|
||||
}
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
/* decode the service request only */
|
||||
int cov_subscribe_property_decode_service_request(uint8_t * apdu,
|
||||
unsigned apdu_len, BACNET_SUBSCRIBE_COV_DATA * data)
|
||||
{
|
||||
int len = 0; /* return value */
|
||||
uint8_t tag_number = 0;
|
||||
uint32_t len_value = 0;
|
||||
uint32_t decoded_value = 0; /* for decoding */
|
||||
int decoded_type = 0; /* for decoding */
|
||||
int property = 0; /* for decoding */
|
||||
|
||||
if (apdu_len && data) {
|
||||
/* tag 0 - subscriberProcessIdentifier */
|
||||
if (decode_is_context_tag(&apdu[len], 0)) {
|
||||
len +=
|
||||
decode_tag_number_and_value(&apdu[len], &tag_number,
|
||||
&len_value);
|
||||
len += decode_unsigned(&apdu[len], len_value, &decoded_value);
|
||||
data->subscriberProcessIdentifier = decoded_value;
|
||||
} else
|
||||
return -1;
|
||||
/* tag 1 - monitoredObjectIdentifier */
|
||||
if (decode_is_context_tag(&apdu[len], 1)) {
|
||||
len +=
|
||||
decode_tag_number_and_value(&apdu[len], &tag_number,
|
||||
&len_value);
|
||||
len +=
|
||||
decode_object_id(&apdu[len], &decoded_type,
|
||||
&data->monitoredObjectIdentifier.instance);
|
||||
data->monitoredObjectIdentifier.type = decoded_type;
|
||||
} else
|
||||
return -1;
|
||||
/* tag 2 - issueConfirmedNotifications - optional */
|
||||
if (decode_is_context_tag(&apdu[len], 2)) {
|
||||
data->cancellationRequest = false;
|
||||
len +=
|
||||
decode_tag_number_and_value(&apdu[len], &tag_number,
|
||||
&len_value);
|
||||
data->issueConfirmedNotifications =
|
||||
decode_context_boolean(&apdu[len]);
|
||||
len++;
|
||||
} else
|
||||
data->cancellationRequest = true;
|
||||
/* tag 3 - lifetime - optional */
|
||||
if (decode_is_context_tag(&apdu[len], 3)) {
|
||||
len +=
|
||||
decode_tag_number_and_value(&apdu[len], &tag_number,
|
||||
&len_value);
|
||||
len += decode_unsigned(&apdu[len], len_value, &decoded_value);
|
||||
data->lifetime = decoded_value;
|
||||
} else
|
||||
data->lifetime = 0;
|
||||
/* tag 4 - monitoredPropertyIdentifier */
|
||||
if (decode_is_context_tag(&apdu[len], 4)) {
|
||||
len +=
|
||||
decode_tag_number_and_value(&apdu[len], &tag_number,
|
||||
&len_value);
|
||||
len += decode_enumerated(&apdu[len], len_value, &property);
|
||||
data->monitoredPropertyIdentifier = property;
|
||||
} else
|
||||
return -1;
|
||||
/* tag 5 - covIncrement - optional */
|
||||
if (decode_is_context_tag(&apdu[len], 5)) {
|
||||
data->covIncrementPresent = true;
|
||||
len +=
|
||||
decode_tag_number_and_value(&apdu[len], &tag_number,
|
||||
&len_value);
|
||||
len += decode_real(&apdu[len], &data->covIncrement);
|
||||
} else
|
||||
data->covIncrementPresent = false;
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
int ucov_notify_send(uint8_t * buffer, BACNET_COV_DATA * data)
|
||||
{
|
||||
@@ -353,6 +608,59 @@ int ucov_notify_decode_apdu(uint8_t * apdu,
|
||||
return len;
|
||||
}
|
||||
|
||||
int cov_subscribe_decode_apdu(uint8_t * apdu,
|
||||
unsigned apdu_len, uint8_t * invoke_id, BACNET_SUBSCRIBE_COV_DATA * data)
|
||||
{
|
||||
int len = 0;
|
||||
unsigned offset = 0;
|
||||
|
||||
if (!apdu)
|
||||
return -1;
|
||||
/* optional checking - most likely was already done prior to this call */
|
||||
if (apdu[0] != PDU_TYPE_CONFIRMED_SERVICE_REQUEST)
|
||||
return -2;
|
||||
/* apdu[1] = encode_max_segs_max_apdu(0, Device_Max_APDU_Length_Accepted()); */
|
||||
*invoke_id = apdu[2]; /* invoke id - filled in by net layer */
|
||||
if (apdu[3] != SERVICE_CONFIRMED_SUBSCRIBE_COV)
|
||||
return -3;
|
||||
offset = 4;
|
||||
|
||||
/* optional limits - must be used as a pair */
|
||||
if (apdu_len > offset) {
|
||||
len =
|
||||
cov_subscribe_decode_service_request(&apdu[offset],
|
||||
apdu_len - offset, data);
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
int cov_subscribe_property_decode_apdu(uint8_t * apdu,
|
||||
unsigned apdu_len, uint8_t * invoke_id, BACNET_SUBSCRIBE_COV_DATA * data)
|
||||
{
|
||||
int len = 0;
|
||||
unsigned offset = 0;
|
||||
|
||||
if (!apdu)
|
||||
return -1;
|
||||
/* optional checking - most likely was already done prior to this call */
|
||||
if (apdu[0] != PDU_TYPE_CONFIRMED_SERVICE_REQUEST)
|
||||
return -2;
|
||||
/* apdu[1] = encode_max_segs_max_apdu(0, Device_Max_APDU_Length_Accepted()); */
|
||||
*invoke_id = apdu[2]; /* invoke id - filled in by net layer */
|
||||
if (apdu[3] != SERVICE_CONFIRMED_SUBSCRIBE_COV_PROPERTY)
|
||||
return -3;
|
||||
offset = 4;
|
||||
|
||||
/* optional limits - must be used as a pair */
|
||||
if (apdu_len > offset) {
|
||||
len =
|
||||
cov_subscribe_property_decode_service_request(&apdu[offset],
|
||||
apdu_len - offset, data);
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
/* dummy function stubs */
|
||||
int npdu_encode_pdu(uint8_t * npdu,
|
||||
@@ -470,6 +778,118 @@ void testCOVNotify(Test * pTest)
|
||||
/* FIXME: add more values to the list of values */
|
||||
}
|
||||
|
||||
void testCOVSubscribeData(Test * pTest,
|
||||
BACNET_SUBSCRIBE_COV_DATA * data, BACNET_SUBSCRIBE_COV_DATA * test_data)
|
||||
{
|
||||
ct_test(pTest,
|
||||
test_data->subscriberProcessIdentifier ==
|
||||
data->subscriberProcessIdentifier);
|
||||
ct_test(pTest,
|
||||
test_data->monitoredObjectIdentifier.type ==
|
||||
data->monitoredObjectIdentifier.type);
|
||||
ct_test(pTest,
|
||||
test_data->monitoredObjectIdentifier.instance ==
|
||||
data->monitoredObjectIdentifier.instance);
|
||||
ct_test(pTest, test_data->cancellationRequest == data->cancellationRequest);
|
||||
if (!test_data->cancellationRequest) {
|
||||
ct_test(pTest, test_data->issueConfirmedNotifications == data->issueConfirmedNotifications);
|
||||
ct_test(pTest, test_data->lifetime == data->lifetime);
|
||||
}
|
||||
}
|
||||
|
||||
void testCOVSubscribePropertyData(Test * pTest,
|
||||
BACNET_SUBSCRIBE_COV_DATA * data, BACNET_SUBSCRIBE_COV_DATA * test_data)
|
||||
{
|
||||
testCOVSubscribeData(pTest, data, test_data);
|
||||
ct_test(pTest, test_data->monitoredPropertyIdentifier == data->monitoredPropertyIdentifier);
|
||||
ct_test(pTest, test_data->covIncrementPresent == data->covIncrementPresent);
|
||||
if (test_data->covIncrementPresent) {
|
||||
ct_test(pTest, test_data->covIncrement == data->covIncrement);
|
||||
}
|
||||
}
|
||||
|
||||
void testCOVSubscribeEncoding(Test * pTest, uint8_t invoke_id,
|
||||
BACNET_SUBSCRIBE_COV_DATA * data)
|
||||
{
|
||||
uint8_t apdu[480] = { 0 };
|
||||
int len = 0;
|
||||
int apdu_len = 0;
|
||||
BACNET_SUBSCRIBE_COV_DATA test_data;
|
||||
uint8_t test_invoke_id = 0;
|
||||
|
||||
len = cov_subscribe_encode_adpu(&apdu[0], invoke_id, data);
|
||||
ct_test(pTest, len != 0);
|
||||
apdu_len = len;
|
||||
|
||||
len = cov_subscribe_decode_apdu(&apdu[0], apdu_len,
|
||||
&test_invoke_id, &test_data);
|
||||
ct_test(pTest, len > 0);
|
||||
ct_test(pTest, test_invoke_id == invoke_id);
|
||||
testCOVSubscribeData(pTest, data, &test_data);
|
||||
}
|
||||
|
||||
void testCOVSubscribePropertyEncoding(Test * pTest, uint8_t invoke_id,
|
||||
BACNET_SUBSCRIBE_COV_DATA * data)
|
||||
{
|
||||
uint8_t apdu[480] = { 0 };
|
||||
int len = 0;
|
||||
int apdu_len = 0;
|
||||
BACNET_SUBSCRIBE_COV_DATA test_data;
|
||||
uint8_t test_invoke_id = 0;
|
||||
|
||||
len = cov_subscribe_property_encode_adpu(&apdu[0], invoke_id, data);
|
||||
ct_test(pTest, len != 0);
|
||||
apdu_len = len;
|
||||
|
||||
len = cov_subscribe_property_decode_apdu(&apdu[0], apdu_len,
|
||||
&test_invoke_id, &test_data);
|
||||
ct_test(pTest, len > 0);
|
||||
ct_test(pTest, test_invoke_id == invoke_id);
|
||||
testCOVSubscribePropertyData(pTest, data, &test_data);
|
||||
}
|
||||
|
||||
void testCOVSubscribe(Test * pTest)
|
||||
{
|
||||
uint8_t invoke_id = 12;
|
||||
BACNET_SUBSCRIBE_COV_DATA data;
|
||||
|
||||
data.subscriberProcessIdentifier = 1;
|
||||
data.monitoredObjectIdentifier.type = OBJECT_ANALOG_INPUT;
|
||||
data.monitoredObjectIdentifier.instance = 321;
|
||||
data.cancellationRequest = false;
|
||||
data.issueConfirmedNotifications = true;
|
||||
data.lifetime = 456;
|
||||
|
||||
testCOVSubscribeEncoding(pTest, invoke_id, &data);
|
||||
data.cancellationRequest = true;
|
||||
testCOVSubscribeEncoding(pTest, invoke_id, &data);
|
||||
}
|
||||
|
||||
void testCOVSubscribeProperty(Test * pTest)
|
||||
{
|
||||
uint8_t invoke_id = 12;
|
||||
BACNET_SUBSCRIBE_COV_DATA data;
|
||||
|
||||
data.subscriberProcessIdentifier = 1;
|
||||
data.monitoredObjectIdentifier.type = OBJECT_ANALOG_INPUT;
|
||||
data.monitoredObjectIdentifier.instance = 321;
|
||||
data.cancellationRequest = false;
|
||||
data.issueConfirmedNotifications = true;
|
||||
data.lifetime = 456;
|
||||
data.monitoredPropertyIdentifier = PROP_FILE_SIZE;
|
||||
data.covIncrementPresent = true;
|
||||
data.covIncrement = 1.0;
|
||||
|
||||
testCOVSubscribePropertyEncoding(pTest, invoke_id, &data);
|
||||
|
||||
data.cancellationRequest = true;
|
||||
testCOVSubscribePropertyEncoding(pTest, invoke_id, &data);
|
||||
|
||||
data.cancellationRequest = false;
|
||||
data.covIncrementPresent = false;
|
||||
testCOVSubscribePropertyEncoding(pTest, invoke_id, &data);
|
||||
}
|
||||
|
||||
#ifdef TEST_COV
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
@@ -480,6 +900,10 @@ int main(int argc, char *argv[])
|
||||
/* individual tests */
|
||||
rc = ct_addTestFunction(pTest, testCOVNotify);
|
||||
assert(rc);
|
||||
rc = ct_addTestFunction(pTest, testCOVSubscribe);
|
||||
assert(rc);
|
||||
rc = ct_addTestFunction(pTest, testCOVSubscribeProperty);
|
||||
assert(rc);
|
||||
|
||||
ct_setStream(pTest, stdout);
|
||||
ct_run(pTest);
|
||||
|
||||
+27
-1
@@ -49,7 +49,7 @@ typedef struct BACnet_Property_Value {
|
||||
} BACNET_PROPERTY_VALUE;
|
||||
|
||||
typedef struct BACnet_COV_Data {
|
||||
unsigned subscriberProcessIdentifier;
|
||||
uint32_t subscriberProcessIdentifier;
|
||||
uint32_t initiatingDeviceIdentifier;
|
||||
BACNET_OBJECT_ID monitoredObjectIdentifier;
|
||||
unsigned timeRemaining;
|
||||
@@ -57,6 +57,17 @@ typedef struct BACnet_COV_Data {
|
||||
BACNET_PROPERTY_VALUE listOfValues;
|
||||
} BACNET_COV_DATA;
|
||||
|
||||
typedef struct BACnet_Subscribe_COV_Data {
|
||||
uint32_t subscriberProcessIdentifier;
|
||||
BACNET_OBJECT_ID monitoredObjectIdentifier;
|
||||
bool cancellationRequest; /* true if this is a cancellation request */
|
||||
bool issueConfirmedNotifications; /* optional */
|
||||
unsigned lifetime; /* optional */
|
||||
BACNET_PROPERTY_ID monitoredPropertyIdentifier;
|
||||
bool covIncrementPresent; /* true if present */
|
||||
float covIncrement; /* optional */
|
||||
} BACNET_SUBSCRIBE_COV_DATA;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
@@ -78,13 +89,28 @@ extern "C" {
|
||||
int cov_notify_decode_service_request(uint8_t * apdu,
|
||||
unsigned apdu_len, BACNET_COV_DATA * data);
|
||||
|
||||
int cov_subscribe_property_decode_service_request(uint8_t * apdu,
|
||||
unsigned apdu_len, BACNET_SUBSCRIBE_COV_DATA * data);
|
||||
|
||||
int cov_subscribe_property_encode_adpu(
|
||||
uint8_t * apdu,
|
||||
uint8_t invoke_id,
|
||||
BACNET_SUBSCRIBE_COV_DATA * data);
|
||||
|
||||
int cov_subscribe_decode_service_request(uint8_t * apdu,
|
||||
unsigned apdu_len, BACNET_SUBSCRIBE_COV_DATA * data);
|
||||
|
||||
int cov_subscribe_encode_adpu(
|
||||
uint8_t * apdu,
|
||||
uint8_t invoke_id,
|
||||
BACNET_SUBSCRIBE_COV_DATA * data);
|
||||
|
||||
|
||||
#ifdef TEST
|
||||
#include "ctest.h"
|
||||
void testCOVNotify(Test * pTest);
|
||||
void testCOVSubscribeProperty(Test * pTest);
|
||||
void testCOVSubscribe(Test * pTest);
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
Reference in New Issue
Block a user