I have an application server built as UNICODE running on Windows only. The application accepts lots of clients using multithreading.
Part of the application is responsible for logging connections, to log IPv4 addresses I convert them to UNICODE from ANSI. I achieve this using inet_ntoa + MultiByteToWideChar.
My frustration is that there is no UNICODE version of inet_ntoa and I am forced to convert each IPv4 to UNICODE. Although MultiByteToWideChar is relatively fast, it is still a performance loss and prone to failure, especially when you have 100,000 connections coming in.
Researching a bit I have found functions like RtlIpv4AddressToStringExW, however I do not have experience with these and applying it to real life applications can be fatal failure without proper testing.
Investigating the above function I have seen that there is no call to MultiByteToWideChar, but instead to RtlIpv4AddressToStringW and swprintf_s, which tells me I really don't need to convert anything (?).
Please advise what is the best way to obtain the readable UNICODE version of IN_ADDR.
Any advice is much appreciated.
Update:
I investigated inet_ntoa and there is quite a lot of code there, retrieving stuff from TLS among other stuff. Overkill from my opinion.
Also investigated WSAAddressToString as proposed by @Alex Guteniev. Again a ton of code, for what purpose...
Also investigated RtlIpv4AddressToStringExW, and my findings worry me again how Windows API's are designed and offered.
Windows Implementation:
LONG __stdcall RtlIpv4AddressToStringExW(const struct in_addr *Address, USHORT Port, PWSTR AddressString, PULONG AddressStringLength)
{
PULONG v4; // rdi
PWSTR v5; // rbp
USHORT v6; // si
wchar_t *v7; // rax
signed __int64 v8; // rbx
ULONG v9; // ebx
LONG result; // eax
WCHAR S; // [rsp+20h] [rbp-68h]
char v12[4]; // [rsp+4Ch] [rbp-3Ch]
v4 = AddressStringLength;
v5 = AddressString;
v6 = Port;
if ( Address && AddressStringLength && (AddressString || !*AddressStringLength) )
{
v7 = RtlIpv4AddressToStringW(Address, &S);
v8 = (signed __int64)v7;
if ( v6 )
v8 = (signed __int64)&v7[swprintf_s(v7, (v12 - (char *)v7) >> 1, L":%u", (unsigned __int16)__ROR2__(v6, 8))];
v9 = (unsigned __int64)((v8 - (signed __int64)&S) >> 1) + 1;
if ( *v4 >= v9 )
{
memmove(v5, &S, 2i64 * v9);
result = 0;
*v4 = v9;
return result;
}
*v4 = v9;
}
return -1073741811;
}
PWSTR __stdcall RtlIpv4AddressToStringW(const struct in_addr *Addr, PWSTR S)
{
__int64 v3; // [rsp+20h] [rbp-28h]
__int64 v4; // [rsp+28h] [rbp-20h]
__int64 v5; // [rsp+30h] [rbp-18h]
LODWORD(v5) = Addr->S_un.S_un_b.s_b4;
LODWORD(v4) = Addr->S_un.S_un_b.s_b3;
LODWORD(v3) = Addr->S_un.S_un_b.s_b2;
return &S[swprintf_s(S, 0x10ui64, L"%u.%u.%u.%u", Addr->S_un.S_un_b.s_b1, v3, v4, v5)];
}
RectOS Implementation:
NTSTATUS
NTAPI
RtlIpv4AddressToStringExW(
_In_ const struct in_addr *Address,
_In_ USHORT Port,
_Out_writes_to_(*AddressStringLength, *AddressStringLength) PWCHAR AddressString,
_Inout_ PULONG AddressStringLength)
{
WCHAR Buffer[IPV4_ADDR_STRING_MAX_LEN + IPV4_PORT_STRING_MAX_LEN];
NTSTATUS Status;
ULONG Length;
PWSTR End;
if (!Address || !AddressString || !AddressStringLength)
return STATUS_INVALID_PARAMETER;
Status = RtlStringCchPrintfExW(Buffer,
RTL_NUMBER_OF(Buffer),
&End,
NULL,
0,
Port ? L"%u.%u.%u.%u:%u"
: L"%u.%u.%u.%u",
Address->S_un.S_un_b.s_b1,
Address->S_un.S_un_b.s_b2,
Address->S_un.S_un_b.s_b3,
Address->S_un.S_un_b.s_b4,
WN2H(Port));
ASSERT(Status == STATUS_SUCCESS);
if (!NT_SUCCESS(Status))
return STATUS_INVALID_PARAMETER;
Length = End - AddressString;
if (*AddressStringLength > Length)
{
Status = RtlStringCchCopyW(AddressString,
*AddressStringLength,
Buffer);
ASSERT(Status == STATUS_SUCCESS);
*AddressStringLength = Length + 1;
return STATUS_SUCCESS;
}
*AddressStringLength = Length + 1;
return STATUS_INVALID_PARAMETER;
}
My opinion here is that the ReactOS implementation is FAR better than the one in Windows. I believe RtlStringCchPrintfExW is much safer than swprintf_s.
In the end, is it safe for me (and anyone else) to create their own implementation of formatting?