diff --git a/src/UDPC.h b/src/UDPC.h index b6dbee2..b54b6be 100644 --- a/src/UDPC.h +++ b/src/UDPC.h @@ -846,9 +846,71 @@ UDPC_EXPORT const char *UDPC_atostr_cid(UDPC_HContext ctx, UDPC_ConnectionId con * be considered undefined behavior. * * It may be easier to use UDPC_atostr_cid(). + * + * UDPC internally uses UDPC_atostr() for logging. This means that while UDPC + * is running, a string created with UDPC_atostr() may be overwritten + * eventually by UDPC. To avoid this situation, UDPC_atostr_unsafe() or + * UDPC_atostr_unsafe_cid() may be used as the ptr returned by it will not be + * overwritten by UDPC as time passes. */ UDPC_EXPORT const char *UDPC_atostr(UDPC_HContext ctx, UDPC_IPV6_ADDR_TYPE addr); +/*! + * \brief Similar to UPDC_atostr(), but the returned ptr must be free'd. + * + * \warning The returned pointer must be freed with free(), or + * UDPC_atostr_unsafe_free(), or UDPC_atostr_unsafe_free_ptr(). + * + * This function is thread-safe. + * + * UDPC internally uses UDPC_atostr() for logging. This means that while UDPC + * is running, a string created with UDPC_atostr() may be overwritten + * eventually by UDPC. To avoid this situation, UDPC_atostr_unsafe() or + * UDPC_atostr_unsafe_cid() may be used as the ptr returned by it will not be + * overwritten by UDPC as time passes. + * + * It may be easier to use UDPC_atostr_unsafe_cid(). + */ +UDPC_EXPORT const char *UDPC_atostr_unsafe(UDPC_IPV6_ADDR_TYPE addr); + +/*! + * \brief Similar to UPDC_atostr(), but the returned ptr must be free'd. + * + * \warning The returned pointer must be freed with free(), or + * UDPC_atostr_unsafe_free(), or UDPC_atostr_unsafe_free_ptr(). + * + * This function is thread-safe. + * + * UDPC internally uses UDPC_atostr() for logging. This means that while UDPC + * is running, a string created with UDPC_atostr() may be overwritten + * eventually by UDPC. To avoid this situation, UDPC_atostr_unsafe() or + * UDPC_atostr_unsafe_cid() may be used as the ptr returned by it will not be + * overwritten by UDPC as time passes. + */ +UDPC_EXPORT const char *UDPC_atostr_unsafe_cid(UDPC_ConnectionId cid); + +/*! + * \brief Free an addr string created with UDPC_atostr_unsafe(). + * + * This function is thread-safe. + */ +UDPC_EXPORT void UDPC_atostr_unsafe_free(const char *addrBuf); + +/*! + * \brief Free an addr string created with UDPC_atostr_unsafe() and zeroes the + * pointer. + * + * This function is thread-safe. + * + * \code{.c} + * UDPC_ConnectionId aConnectionId = ...; + * const char *addrString = UDPC_atostr_unsafe_cid(aConnectionId); + * // Use addrString somewhere... + * UDPC_atostr_unsafe_free_ptr(&addrString); + * \endcode + */ +UDPC_EXPORT void UDPC_atostr_unsafe_free_ptr(const char **addrBuf); + // ============================================================================= // Helpers diff --git a/src/UDPConnection.cpp b/src/UDPConnection.cpp index a1272bd..e7d1036 100644 --- a/src/UDPConnection.cpp +++ b/src/UDPConnection.cpp @@ -2666,42 +2666,33 @@ const char *UDPC_atostr_cid(UDPC_HContext ctx, UDPC_ConnectionId connectionId) { return UDPC_atostr(ctx, connectionId.addr); } -const char *UDPC_atostr(UDPC_HContext ctx, UDPC_IPV6_ADDR_TYPE addr) { - UDPC::Context *c = UDPC::verifyContext(ctx); - if(!c) { - return nullptr; - } - uint32_t headIndex; - { - // Use a lock to ensure that "atostrBufIndex" is always a valid value. - std::lock_guard indexLock(c->atostrBufIndexMutex); - c->atostrBufIndex = (c->atostrBufIndex + UDPC_ATOSTR_BUFSIZE) % UDPC_ATOSTR_SIZE; - headIndex = c->atostrBufIndex; - } +void UDPC_Helper_atostr(char *buffer, + UDPC_IPV6_ADDR_TYPE addr, + const uint32_t headIndex) { uint32_t index = headIndex; bool usedDouble = false; for(unsigned int i = 0; i < 16; ++i) { if(i != 0 && i % 2 == 0) { - if(headIndex - index > 1 && c->atostrBuf[index - 1] == ':') { + if(headIndex - index > 1 && buffer[index - 1] == ':') { if(usedDouble) { - if(c->atostrBuf[index - 2] != ':') { - c->atostrBuf[index++] = '0'; - c->atostrBuf[index++] = ':'; + if(buffer[index - 2] != ':') { + buffer[index++] = '0'; + buffer[index++] = ':'; } else { // continue use of double :, do nothing here } } else { usedDouble = true; - c->atostrBuf[index++] = ':'; + buffer[index++] = ':'; } } else { - c->atostrBuf[index++] = ':'; + buffer[index++] = ':'; } } if(UDPC_IPV6_ADDR_SUB(addr)[i] == 0 - && (headIndex - index <= 1 || c->atostrBuf[index] == ':')) { + && (headIndex - index <= 1 || buffer[index] == ':')) { continue; } else { std::stringstream sstream; @@ -2709,7 +2700,7 @@ const char *UDPC_atostr(UDPC_HContext ctx, UDPC_IPV6_ADDR_TYPE addr) { << std::hex << (unsigned int) UDPC_IPV6_ADDR_SUB(addr)[i]; std::string out(sstream.str()); unsigned int outOffset = 0; - if(headIndex - index <= 1 || c->atostrBuf[index - 1] == ':') { + if(headIndex - index <= 1 || buffer[index - 1] == ':') { if(out[0] == '0') { if(out[1] == '0') { outOffset = 2; @@ -2722,25 +2713,67 @@ const char *UDPC_atostr(UDPC_HContext ctx, UDPC_IPV6_ADDR_TYPE addr) { continue; } else if(outOffset == 1) { if(out[outOffset] != '0') { - std::memcpy(c->atostrBuf + index, out.c_str() + outOffset, 1); + std::memcpy(buffer + index, out.c_str() + outOffset, 1); ++index; } } else { - std::memcpy(c->atostrBuf + index, out.c_str() + outOffset, 2); + std::memcpy(buffer + index, out.c_str() + outOffset, 2); index += 2; } } } - if(c->atostrBuf[index - 1] == ':' - && (headIndex - index <= 2 || c->atostrBuf[index - 2] != ':')) { - c->atostrBuf[index++] = '0'; + if(buffer[index - 1] == ':' + && (headIndex - index <= 2 || buffer[index - 2] != ':')) { + buffer[index++] = '0'; } - c->atostrBuf[index] = 0; + buffer[index] = 0; +} + +const char *UDPC_atostr(UDPC_HContext ctx, UDPC_IPV6_ADDR_TYPE addr) { + UDPC::Context *c = UDPC::verifyContext(ctx); + if(!c) { + return nullptr; + } + uint32_t headIndex; + { + // Use a lock to ensure that "atostrBufIndex" is always a valid value. + std::lock_guard indexLock(c->atostrBufIndexMutex); + c->atostrBufIndex = (c->atostrBufIndex + UDPC_ATOSTR_BUFSIZE) % UDPC_ATOSTR_SIZE; + headIndex = c->atostrBufIndex; + } + + UDPC_Helper_atostr(c->atostrBuf, addr, headIndex); return c->atostrBuf + headIndex; } +const char *UDPC_atostr_unsafe(UDPC_IPV6_ADDR_TYPE addr) { + char *buf = (char*)std::malloc(40); + std::memset(buf, 0, 40); + + UDPC_Helper_atostr(buf, addr, 0); + + return buf; +} + +const char *UDPC_atostr_unsafe_cid(UDPC_ConnectionId cid) { + return UDPC_atostr_unsafe(cid.addr); +} + +void UDPC_atostr_unsafe_free(const char *addrBuf) { + if (addrBuf) { + std::free((void*)addrBuf); + } +} + +void UDPC_atostr_unsafe_free_ptr(const char **addrBuf) { + if (addrBuf && *addrBuf) { + std::free((void*)*addrBuf); + *addrBuf = nullptr; + } +} + UDPC_IPV6_ADDR_TYPE UDPC_strtoa(const char *addrStr) { UDPC_IPV6_ADDR_TYPE result = in6addr_loopback; std::cmatch matchResults; diff --git a/src/test/TestUDPC.cpp b/src/test/TestUDPC.cpp index 37bd719..1cdd83e 100644 --- a/src/test/TestUDPC.cpp +++ b/src/test/TestUDPC.cpp @@ -155,6 +155,101 @@ TEST(UDPC, atostr_concurrent) { } } +TEST(UDPC, atostr_unsafe) { + const char* results[64] = { + "::1111:1", + "::1111:2", + "::1111:3", + "::1111:4", + "::1111:5", + "::1111:6", + "::1111:7", + "::1111:8", + "::1111:9", + "::1111:a", + "::1111:b", + "::1111:c", + "::1111:d", + "::1111:e", + "::1111:f", + "::1111:10", + "::1111:11", + "::1111:12", + "::1111:13", + "::1111:14", + "::1111:15", + "::1111:16", + "::1111:17", + "::1111:18", + "::1111:19", + "::1111:1a", + "::1111:1b", + "::1111:1c", + "::1111:1d", + "::1111:1e", + "::1111:1f", + "::1111:20", + "::1111:21", + "::1111:22", + "::1111:23", + "::1111:24", + "::1111:25", + "::1111:26", + "::1111:27", + "::1111:28", + "::1111:29", + "::1111:2a", + "::1111:2b", + "::1111:2c", + "::1111:2d", + "::1111:2e", + "::1111:2f", + "::1111:30", + "::1111:31", + "::1111:32", + "::1111:33", + "::1111:34", + "::1111:35", + "::1111:36", + "::1111:37", + "::1111:38", + "::1111:39", + "::1111:3a", + "::1111:3b", + "::1111:3c", + "::1111:3d", + "::1111:3e", + "::1111:3f", + "::1111:40" + }; + std::future futures[32]; + const char* ptrs[32]; + for(unsigned int i = 0; i < 2; ++i) { + for(unsigned int j = 0; j < 32; ++j) { + futures[j] = std::async(std::launch::async, [] (unsigned int id, const char** ptr) { + UDPC_ConnectionId conId = { + {0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0x11, 0x11, 0x0, (unsigned char)(id + 1)}, + 0, + 0 + }; + ptr[id] = UDPC_atostr_unsafe(conId.addr); + }, j, ptrs); + } + for(unsigned int j = 0; j < 32; ++j) { + ASSERT_TRUE(futures[j].valid()); + futures[j].wait(); + } + for(unsigned int j = 0; j < 32; ++j) { + EXPECT_STREQ(ptrs[j], results[j]); + UDPC_atostr_unsafe_free_ptr(ptrs + j); + UDPC_atostr_unsafe_free_ptr(ptrs + j); + } + } +} + TEST(UDPC, strtoa) { struct in6_addr addr;