mirror of
https://github.com/irungentoo/toxcore.git
synced 2024-03-22 13:30:51 +08:00
Add groupchat API function that returns an IP address string for a peer
This function will return an IP address string associated with a peer. If the peer is not accepting direct connections a placeholder value will be returned, indicating that their real IP address is unknown. We do not return TCP relay IP addresses because a TCP connection with a peer may use multiple relays simultaneously.
This commit is contained in:
parent
5f863a5492
commit
b2315c50e0
|
@ -9,6 +9,7 @@
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include "auto_test_support.h"
|
#include "auto_test_support.h"
|
||||||
|
#include "../toxcore/tox_private.h"
|
||||||
|
|
||||||
typedef struct State {
|
typedef struct State {
|
||||||
size_t peer_joined_count;
|
size_t peer_joined_count;
|
||||||
|
@ -42,6 +43,22 @@ typedef struct State {
|
||||||
|
|
||||||
#define PEER_LIMIT 20
|
#define PEER_LIMIT 20
|
||||||
|
|
||||||
|
static void print_ip(Tox *tox, uint32_t groupnumber, uint32_t peer_id)
|
||||||
|
{
|
||||||
|
Tox_Err_Group_Peer_Query err;
|
||||||
|
size_t length = tox_group_peer_get_ip_address_size(tox, groupnumber, peer_id, &err);
|
||||||
|
|
||||||
|
ck_assert_msg(err == TOX_ERR_GROUP_PEER_QUERY_OK, "failed to get ip address size: error %d", err);
|
||||||
|
|
||||||
|
uint8_t ip_str[TOX_GROUP_PEER_IP_STRING_MAX_LENGTH];
|
||||||
|
tox_group_peer_get_ip_address(tox, groupnumber, peer_id, ip_str, &err);
|
||||||
|
ip_str[length] = '\0';
|
||||||
|
|
||||||
|
ck_assert_msg(err == TOX_ERR_GROUP_PEER_QUERY_OK, "failed to get ip address: error %d", err);
|
||||||
|
|
||||||
|
fprintf(stderr, "%s\n", ip_str);
|
||||||
|
}
|
||||||
|
|
||||||
static bool all_group_peers_connected(AutoTox *autotoxes, uint32_t tox_count, uint32_t groupnumber, size_t name_length)
|
static bool all_group_peers_connected(AutoTox *autotoxes, uint32_t tox_count, uint32_t groupnumber, size_t name_length)
|
||||||
{
|
{
|
||||||
for (size_t i = 0; i < tox_count; ++i) {
|
for (size_t i = 0; i < tox_count; ++i) {
|
||||||
|
@ -119,6 +136,9 @@ static void group_peer_join_handler(Tox *tox, uint32_t groupnumber, uint32_t pee
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fprintf(stderr, "%s joined with IP: ", peer_name);
|
||||||
|
print_ip(tox, groupnumber, peer_id);
|
||||||
|
|
||||||
state->peer_id = peer_id;
|
state->peer_id = peer_id;
|
||||||
++state->peer_joined_count;
|
++state->peer_joined_count;
|
||||||
}
|
}
|
||||||
|
@ -178,6 +198,11 @@ static void group_peer_self_join_handler(Tox *tox, uint32_t groupnumber, void *u
|
||||||
ck_assert_msg(query_err == TOX_ERR_GROUP_STATE_QUERIES_OK, "%d", query_err);
|
ck_assert_msg(query_err == TOX_ERR_GROUP_STATE_QUERIES_OK, "%d", query_err);
|
||||||
ck_assert(memcmp(topic, TOPIC, TOPIC_LEN) == 0);
|
ck_assert(memcmp(topic, TOPIC, TOPIC_LEN) == 0);
|
||||||
|
|
||||||
|
uint32_t peer_id = tox_group_self_get_peer_id(tox, groupnumber, nullptr);
|
||||||
|
|
||||||
|
fprintf(stderr, "self joined with IP: ");
|
||||||
|
print_ip(tox, groupnumber, peer_id);
|
||||||
|
|
||||||
++state->self_joined_count;
|
++state->self_joined_count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -341,6 +366,7 @@ static void group_announce_test(AutoTox *autotoxes)
|
||||||
ck_assert(memcmp(tox0_pk_query, tox0_self_pk, TOX_GROUP_PEER_PUBLIC_KEY_SIZE) == 0);
|
ck_assert(memcmp(tox0_pk_query, tox0_self_pk, TOX_GROUP_PEER_PUBLIC_KEY_SIZE) == 0);
|
||||||
|
|
||||||
fprintf(stderr, "Peer 0 disconnecting...\n");
|
fprintf(stderr, "Peer 0 disconnecting...\n");
|
||||||
|
|
||||||
// tox 0 disconnects then reconnects
|
// tox 0 disconnects then reconnects
|
||||||
Tox_Err_Group_Disconnect d_err;
|
Tox_Err_Group_Disconnect d_err;
|
||||||
tox_group_disconnect(tox0, groupnumber, &d_err);
|
tox_group_disconnect(tox0, groupnumber, &d_err);
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
e0fa59db7d25204f917e4b114e1607cb3819fe9da74de5f9e807bcf76abefe42 /usr/local/bin/tox-bootstrapd
|
8cb8b2f7bbc0ce71551365ed72ae468c731eafdce93e01d8c9a04ed5d904fcd9 /usr/local/bin/tox-bootstrapd
|
||||||
|
|
|
@ -3517,6 +3517,63 @@ int gc_get_peer_public_key_by_peer_id(const GC_Chat *chat, uint32_t peer_id, uin
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @brief Puts a string of the IP associated with `ip_port` in `ip_str` if the
|
||||||
|
* connection is direct, otherwise puts a placeholder in the buffer indicating that
|
||||||
|
* the IP cannot be displayed.
|
||||||
|
*/
|
||||||
|
non_null()
|
||||||
|
static void get_gc_ip_ntoa(const IP_Port *ip_port, Ip_Ntoa *ip_str)
|
||||||
|
{
|
||||||
|
net_ip_ntoa(&ip_port->ip, ip_str);
|
||||||
|
|
||||||
|
if (!ip_str->ip_is_valid) {
|
||||||
|
ip_str->buf[0] = '-';
|
||||||
|
ip_str->buf[1] = '\0';
|
||||||
|
ip_str->length = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int gc_get_peer_ip_address_size(const GC_Chat *chat, uint32_t peer_id)
|
||||||
|
{
|
||||||
|
const int peer_number = get_peer_number_of_peer_id(chat, peer_id);
|
||||||
|
const GC_Connection *gconn = get_gc_connection(chat, peer_number);
|
||||||
|
|
||||||
|
if (gconn == nullptr) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const IP_Port *ip_port = peer_number == 0 ? &chat->self_ip_port : &gconn->addr.ip_port;
|
||||||
|
|
||||||
|
Ip_Ntoa ip_str;
|
||||||
|
get_gc_ip_ntoa(ip_port, &ip_str);
|
||||||
|
|
||||||
|
return ip_str.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
int gc_get_peer_ip_address(const GC_Chat *chat, uint32_t peer_id, uint8_t *ip_addr)
|
||||||
|
{
|
||||||
|
const int peer_number = get_peer_number_of_peer_id(chat, peer_id);
|
||||||
|
const GC_Connection *gconn = get_gc_connection(chat, peer_number);
|
||||||
|
|
||||||
|
if (gconn == nullptr) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ip_addr == nullptr) {
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
|
|
||||||
|
const IP_Port *ip_port = peer_number == 0 ? &chat->self_ip_port : &gconn->addr.ip_port;
|
||||||
|
|
||||||
|
Ip_Ntoa ip_str;
|
||||||
|
get_gc_ip_ntoa(ip_port, &ip_str);
|
||||||
|
|
||||||
|
assert(ip_str.length <= IP_NTOA_LEN);
|
||||||
|
memcpy(ip_addr, ip_str.buf, ip_str.length);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
unsigned int gc_get_peer_connection_status(const GC_Chat *chat, uint32_t peer_id)
|
unsigned int gc_get_peer_connection_status(const GC_Chat *chat, uint32_t peer_id)
|
||||||
{
|
{
|
||||||
const int peer_number = get_peer_number_of_peer_id(chat, peer_id);
|
const int peer_number = get_peer_number_of_peer_id(chat, peer_id);
|
||||||
|
|
|
@ -390,6 +390,29 @@ int gc_get_peer_nick_size(const GC_Chat *chat, uint32_t peer_id);
|
||||||
non_null(1) nullable(3)
|
non_null(1) nullable(3)
|
||||||
int gc_get_peer_public_key_by_peer_id(const GC_Chat *chat, uint32_t peer_id, uint8_t *public_key);
|
int gc_get_peer_public_key_by_peer_id(const GC_Chat *chat, uint32_t peer_id, uint8_t *public_key);
|
||||||
|
|
||||||
|
/** @brief Returns the length of the IP address for the peer designated by `peer_id`.
|
||||||
|
* Returns -1 if peer_id is invalid.
|
||||||
|
*/
|
||||||
|
non_null()
|
||||||
|
int gc_get_peer_ip_address_size(const GC_Chat *chat, uint32_t peer_id);
|
||||||
|
|
||||||
|
/** @brief Copies peer_id's IP address to `ip_addr`.
|
||||||
|
*
|
||||||
|
* If the peer is forcing TCP connections this will be a placeholder value indicating
|
||||||
|
* that their real IP address is unknown to us.
|
||||||
|
*
|
||||||
|
* If `peer_id` designates ourself, it will write either our own IP address or a
|
||||||
|
* placeholder value, depending on whether or not we're forcing TCP connections.
|
||||||
|
*
|
||||||
|
* `ip_addr` should have room for at least IP_NTOA_LEN bytes.
|
||||||
|
*
|
||||||
|
* Returns 0 on success.
|
||||||
|
* Returns -1 if peer_id is invalid or doesn't correspond to a valid peer connection.
|
||||||
|
* Returns -2 if `ip_addr` is null.
|
||||||
|
*/
|
||||||
|
non_null(1) nullable(3)
|
||||||
|
int gc_get_peer_ip_address(const GC_Chat *chat, uint32_t peer_id, uint8_t *ip_addr);
|
||||||
|
|
||||||
/** @brief Gets the connection status for peer associated with `peer_id`.
|
/** @brief Gets the connection status for peer associated with `peer_id`.
|
||||||
*
|
*
|
||||||
* If `peer_id` designates ourself, the return value indicates whether we're capable
|
* If `peer_id` designates ourself, the return value indicates whether we're capable
|
||||||
|
|
|
@ -1514,30 +1514,29 @@ void ipport_copy(IP_Port *target, const IP_Port *source)
|
||||||
*target = *source;
|
*target = *source;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @brief Converts IP into a string.
|
|
||||||
*
|
|
||||||
* Writes error message into the buffer on error.
|
|
||||||
*
|
|
||||||
* @param ip_str contains a buffer of the required size.
|
|
||||||
*
|
|
||||||
* @return Pointer to the buffer inside `ip_str` containing the IP string.
|
|
||||||
*/
|
|
||||||
const char *net_ip_ntoa(const IP *ip, Ip_Ntoa *ip_str)
|
const char *net_ip_ntoa(const IP *ip, Ip_Ntoa *ip_str)
|
||||||
{
|
{
|
||||||
assert(ip_str != nullptr);
|
assert(ip_str != nullptr);
|
||||||
|
|
||||||
|
ip_str->ip_is_valid = false;
|
||||||
|
|
||||||
if (ip == nullptr) {
|
if (ip == nullptr) {
|
||||||
snprintf(ip_str->buf, sizeof(ip_str->buf), "(IP invalid: NULL)");
|
snprintf(ip_str->buf, sizeof(ip_str->buf), "(IP invalid: NULL)");
|
||||||
|
ip_str->length = (uint16_t)strlen(ip_str->buf);
|
||||||
return ip_str->buf;
|
return ip_str->buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ip_parse_addr(ip, ip_str->buf, sizeof(ip_str->buf))) {
|
if (!ip_parse_addr(ip, ip_str->buf, sizeof(ip_str->buf))) {
|
||||||
snprintf(ip_str->buf, sizeof(ip_str->buf), "(IP invalid, family %u)", ip->family.value);
|
snprintf(ip_str->buf, sizeof(ip_str->buf), "(IP invalid, family %u)", ip->family.value);
|
||||||
|
ip_str->length = (uint16_t)strlen(ip_str->buf);
|
||||||
return ip_str->buf;
|
return ip_str->buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* brute force protection against lacking termination */
|
/* brute force protection against lacking termination */
|
||||||
ip_str->buf[sizeof(ip_str->buf) - 1] = '\0';
|
ip_str->buf[sizeof(ip_str->buf) - 1] = '\0';
|
||||||
|
ip_str->length = (uint16_t)strlen(ip_str->buf);
|
||||||
|
ip_str->ip_is_valid = true;
|
||||||
|
|
||||||
return ip_str->buf;
|
return ip_str->buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -343,11 +343,14 @@ bool ipv6_ipv4_in_v6(const IP6 *a);
|
||||||
/** this would be TOX_INET6_ADDRSTRLEN, but it might be too short for the error message */
|
/** this would be TOX_INET6_ADDRSTRLEN, but it might be too short for the error message */
|
||||||
#define IP_NTOA_LEN 96 // TODO(irungentoo): magic number. Why not INET6_ADDRSTRLEN ?
|
#define IP_NTOA_LEN 96 // TODO(irungentoo): magic number. Why not INET6_ADDRSTRLEN ?
|
||||||
|
|
||||||
|
/** Contains a null terminated string of an IP address. */
|
||||||
typedef struct Ip_Ntoa {
|
typedef struct Ip_Ntoa {
|
||||||
char buf[IP_NTOA_LEN];
|
char buf[IP_NTOA_LEN]; // a string formatted IP address or an error message.
|
||||||
|
uint16_t length; // the length of the string (not including the null byte).
|
||||||
|
bool ip_is_valid; // if this is false `buf` will contain an error message.
|
||||||
} Ip_Ntoa;
|
} Ip_Ntoa;
|
||||||
|
|
||||||
/** @brief Converts IP into a string.
|
/** @brief Converts IP into a null terminated string.
|
||||||
*
|
*
|
||||||
* Writes error message into the buffer on error.
|
* Writes error message into the buffer on error.
|
||||||
*
|
*
|
||||||
|
|
|
@ -43,6 +43,8 @@ static_assert(FILE_ID_LENGTH == CRYPTO_SYMMETRIC_KEY_SIZE,
|
||||||
"FILE_ID_LENGTH is assumed to be equal to CRYPTO_SYMMETRIC_KEY_SIZE");
|
"FILE_ID_LENGTH is assumed to be equal to CRYPTO_SYMMETRIC_KEY_SIZE");
|
||||||
static_assert(TOX_DHT_NODE_IP_STRING_SIZE == IP_NTOA_LEN,
|
static_assert(TOX_DHT_NODE_IP_STRING_SIZE == IP_NTOA_LEN,
|
||||||
"TOX_DHT_NODE_IP_STRING_SIZE is assumed to be equal to IP_NTOA_LEN");
|
"TOX_DHT_NODE_IP_STRING_SIZE is assumed to be equal to IP_NTOA_LEN");
|
||||||
|
static_assert(TOX_GROUP_PEER_IP_STRING_MAX_LENGTH == IP_NTOA_LEN,
|
||||||
|
"TOX_GROUP_PEER_IP_STRING_MAX_LENGTH is assumed to be equal to IP_NTOA_LEN");
|
||||||
static_assert(TOX_DHT_NODE_PUBLIC_KEY_SIZE == CRYPTO_PUBLIC_KEY_SIZE,
|
static_assert(TOX_DHT_NODE_PUBLIC_KEY_SIZE == CRYPTO_PUBLIC_KEY_SIZE,
|
||||||
"TOX_DHT_NODE_PUBLIC_KEY_SIZE is assumed to be equal to CRYPTO_PUBLIC_KEY_SIZE");
|
"TOX_DHT_NODE_PUBLIC_KEY_SIZE is assumed to be equal to CRYPTO_PUBLIC_KEY_SIZE");
|
||||||
static_assert(TOX_FILE_ID_LENGTH == CRYPTO_SYMMETRIC_KEY_SIZE,
|
static_assert(TOX_FILE_ID_LENGTH == CRYPTO_SYMMETRIC_KEY_SIZE,
|
||||||
|
|
|
@ -124,6 +124,10 @@ uint32_t tox_group_peer_public_key_size(void)
|
||||||
{
|
{
|
||||||
return TOX_GROUP_PEER_PUBLIC_KEY_SIZE;
|
return TOX_GROUP_PEER_PUBLIC_KEY_SIZE;
|
||||||
}
|
}
|
||||||
|
uint32_t tox_group_peer_ip_string_max_length(void)
|
||||||
|
{
|
||||||
|
return TOX_GROUP_PEER_IP_STRING_MAX_LENGTH;
|
||||||
|
}
|
||||||
uint32_t tox_dht_node_ip_string_size(void)
|
uint32_t tox_dht_node_ip_string_size(void)
|
||||||
{
|
{
|
||||||
return TOX_DHT_NODE_IP_STRING_SIZE;
|
return TOX_DHT_NODE_IP_STRING_SIZE;
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
|
||||||
#include "ccompat.h"
|
#include "ccompat.h"
|
||||||
|
#include "group_chats.h"
|
||||||
#include "mem.h"
|
#include "mem.h"
|
||||||
#include "network.h"
|
#include "network.h"
|
||||||
#include "tox_struct.h"
|
#include "tox_struct.h"
|
||||||
|
@ -166,3 +167,57 @@ uint16_t tox_dht_get_num_closelist_announce_capable(const Tox *tox){
|
||||||
return num_cap;
|
return num_cap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifndef VANILLA_NACL
|
||||||
|
size_t tox_group_peer_get_ip_address_size(const Tox *tox, uint32_t group_number, uint32_t peer_id,
|
||||||
|
Tox_Err_Group_Peer_Query *error)
|
||||||
|
{
|
||||||
|
assert(tox != nullptr);
|
||||||
|
|
||||||
|
tox_lock(tox);
|
||||||
|
const GC_Chat *chat = gc_get_group(tox->m->group_handler, group_number);
|
||||||
|
|
||||||
|
if (chat == nullptr) {
|
||||||
|
SET_ERROR_PARAMETER(error, TOX_ERR_GROUP_PEER_QUERY_GROUP_NOT_FOUND);
|
||||||
|
tox_unlock(tox);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int ret = gc_get_peer_ip_address_size(chat, peer_id);
|
||||||
|
tox_unlock(tox);
|
||||||
|
|
||||||
|
if (ret == -1) {
|
||||||
|
SET_ERROR_PARAMETER(error, TOX_ERR_GROUP_PEER_QUERY_PEER_NOT_FOUND);
|
||||||
|
return -1;
|
||||||
|
} else {
|
||||||
|
SET_ERROR_PARAMETER(error, TOX_ERR_GROUP_PEER_QUERY_OK);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool tox_group_peer_get_ip_address(const Tox *tox, uint32_t group_number, uint32_t peer_id, uint8_t *ip_addr,
|
||||||
|
Tox_Err_Group_Peer_Query *error)
|
||||||
|
{
|
||||||
|
assert(tox != nullptr);
|
||||||
|
|
||||||
|
tox_lock(tox);
|
||||||
|
const GC_Chat *chat = gc_get_group(tox->m->group_handler, group_number);
|
||||||
|
|
||||||
|
if (chat == nullptr) {
|
||||||
|
SET_ERROR_PARAMETER(error, TOX_ERR_GROUP_PEER_QUERY_GROUP_NOT_FOUND);
|
||||||
|
tox_unlock(tox);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int ret = gc_get_peer_ip_address(chat, peer_id, ip_addr);
|
||||||
|
tox_unlock(tox);
|
||||||
|
|
||||||
|
if (ret == -1) {
|
||||||
|
SET_ERROR_PARAMETER(error, TOX_ERR_GROUP_PEER_QUERY_PEER_NOT_FOUND);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
SET_ERROR_PARAMETER(error, TOX_ERR_GROUP_PEER_QUERY_OK);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* VANILLA_NACL */
|
||||||
|
|
|
@ -156,6 +156,51 @@ uint16_t tox_dht_get_num_closelist(const Tox *tox);
|
||||||
*/
|
*/
|
||||||
uint16_t tox_dht_get_num_closelist_announce_capable(const Tox *tox);
|
uint16_t tox_dht_get_num_closelist_announce_capable(const Tox *tox);
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
*
|
||||||
|
* :: DHT groupchat queries.
|
||||||
|
*
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maximum size of a peer IP address string.
|
||||||
|
*/
|
||||||
|
#define TOX_GROUP_PEER_IP_STRING_MAX_LENGTH 96
|
||||||
|
|
||||||
|
uint32_t tox_group_peer_ip_string_max_length(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the length of the peer's IP address in string form. If the group number or ID
|
||||||
|
* is invalid, the return value is unspecified.
|
||||||
|
*
|
||||||
|
* @param group_number The group number of the group we wish to query.
|
||||||
|
* @param peer_id The ID of the peer whose IP address length we want to retrieve.
|
||||||
|
*/
|
||||||
|
size_t tox_group_peer_get_ip_address_size(const Tox *tox, uint32_t group_number, uint32_t peer_id,
|
||||||
|
Tox_Err_Group_Peer_Query *error);
|
||||||
|
/**
|
||||||
|
* Write the IP address associated with the designated peer_id for the designated group number
|
||||||
|
* to ip_addr.
|
||||||
|
*
|
||||||
|
* If the peer is forcing TCP connections a placeholder value will be written instead,
|
||||||
|
* indicating that their real IP address is unknown to us.
|
||||||
|
*
|
||||||
|
* If `peer_id` designates ourself, it will write either our own IP address or a placeholder value,
|
||||||
|
* depending on whether or not we're forcing TCP connections.
|
||||||
|
*
|
||||||
|
* Call tox_group_peer_get_ip_address_size to determine the allocation size for the `ip_addr` parameter.
|
||||||
|
*
|
||||||
|
* @param group_number The group number of the group we wish to query.
|
||||||
|
* @param peer_id The ID of the peer whose public key we wish to retrieve.
|
||||||
|
* @param ip_addr A valid memory region large enough to store the IP address string.
|
||||||
|
* If this parameter is NULL, this function call has no effect.
|
||||||
|
*
|
||||||
|
* @return true on success.
|
||||||
|
*/
|
||||||
|
bool tox_group_peer_get_ip_address(const Tox *tox, uint32_t group_number, uint32_t peer_id, uint8_t *ip_addr,
|
||||||
|
Tox_Err_Group_Peer_Query *error);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
Loading…
Reference in New Issue
Block a user