PDA

Просмотр полной версии : [Function] Конвертация IP-адреса



punkochel
04.04.2023, 22:43
Описание:
Функции преобразования IP-адреса в целочисленное значение и обратно в строку. Сравнение целых чисел происходит быстрее чем сравнение строк.

Функции:
stock IPv4ToInteger(const ip_address[])
{ // by punkochel (pro-pawn.ru)
new count,
part[4],
str_value[3+1];

for(new i, k; ip_address[i]; i++) {
if(ip_address[i] == '.') {
part[count++] = strval(str_value);
k = 0;
continue;
}
str_value[k++] = ip_address[i];
str_value[k] = '\0';
}
part[count] = strval(str_value);

return part[3] | (part[2] << 8) | (part[1] << 16) | (part[0] << 24);
}

stock IntegerToIPv4(ip_address, result[])
{ // by punkochel (pro-pawn.ru)
new part[4];
for(new i; i < 4; i++)
part[i] = (ip_address >> i*8) & 255;

format(result, 15+1, "%d.%d.%d.%d", part[3], part[2], part[1], part[0]);
return 1;
}

Для тех кто использует плагин sscanf (https://github.com/Y-Less/sscanf):
stock IPv4ToInteger(const ip_address[])
{ // by punkochel (pro-pawn.ru)
new a, b, c, d;
sscanf(ip_address, !"p<.>iiii", a, b, c, d);
return d | (c << 8) | (b << 16) | (a << 24);
}

Автор: punkochel (https://pro-pawn.ru/member.php?9227-punkochel)


*Исключительно для pro-pawn.ru (https://pro-pawn.ru/)
Копирование данной статьи на других ресурсах без разрешения автора или Daniel_Cortez (https://pro-pawn.ru/member.php?100-Daniel_Cortez) запрещено!

Shaolinka
05.04.2023, 16:05
Стоило бы внести проверку на то, не является ли одно из чисел IP выше 8 бит(> 255), дабы не нарушался алгоритм. Насчёт конвертации из целого в строку с айпи адресом, то там имеется проблема в том, что цикл стартует от нуля и вплоть до трёх. Потому сдвигаться вправо на 8 бит будут биты из диапазона: 0 * 8 - 8; 1 * 8 = 8; 2 * 8 = 16. Но никак не 8 <= 24; Потому таков цикл должен стартовать от единицы и до трёх включительно. По-поводу версии с sscanf'om, то можешь воспользоваться массивом, проитерировавшись по нему вскоре, присваивая результат возвращаемой переменной исходя из формулы: массив, куда сохранились целые числа айпишника[i - 1] << i * 8; Таков цикл обязан стартовать, как обычно от единицы до трёх включительно. А так-то молодец, полезно, в общем доступе не замечал такого, в основном приходилось импровизировать собственноручно. Глядишь, сподвигнешь кого не выделять 64 байта(16 * 4) впустую, отдавая должное обыкновенной переменной в 4 байта

VVWVV
05.04.2023, 19:14
Лучше использовать возвращаемое значение для ошибок, а сам результат возвращать через ссылку. Кроме того, можно использовать упакованный массив для октетов, а уже после возвращать целую ячейку. Значение лучше конвертировать без функции strval - это намного дешевле вызова функции.


stock IPv4ToInteger(&dst, const src[]) {
new i = 0, chr, buf[3 char], j = 0, val = 0;
while ((chr = src[i++]) != EOS) {
switch (chr) {
case '0'..'9': {
val = val * 10 + chr - '0';
if (val > 255) {
return -1;
}
}
case '.': {
if (j > 3) {
return -1;
}
buf{j++} = val;
val = 0;
}
default: {
return -1;
}
}
}
dst = buf[0] | val;
return 0;
}

punkochel
05.04.2023, 19:16
Стоило бы внести проверку на то, не является ли одно из чисел IP выше 8 бит(> 255), дабы не нарушался алгоритм. Насчёт конвертации из целого в строку с айпи адресом, то там имеется проблема в том, что цикл стартует от нуля и вплоть до трёх. Потому сдвигаться вправо на 8 бит будут биты из диапазона: 0 * 8 - 8; 1 * 8 = 8; 2 * 8 = 16. Но никак не 8 <= 24; Потому таков цикл должен стартовать от единицы и до трёх включительно. По-поводу версии с sscanf'om, то можешь воспользоваться массивом, проитерировавшись по нему вскоре, присваивая результат возвращаемой переменной исходя из формулы: массив, куда сохранились целые числа айпишника[i - 1] << i * 8; Таков цикл обязан стартовать, как обычно от единицы до трёх включительно. А так-то молодец, полезно, в общем доступе не замечал такого, в основном приходилось импровизировать собственноручно. Глядишь, сподвигнешь кого не выделять 64 байта(16 * 4) впустую, отдавая должное обыкновенной переменной в 4 байта

Честно говоря, я многое не понял что ты имеешь ввиду)
Валидность IP это прерогатива вызывающего, если ему это нужно, то он может конечно использовать и регулярные выражения. В общем оставим это на совести скриптера.
По поводу массива в функции со sscanf, то тут выбор сделан в пользу скорости (https://pro-pawn.ru/showthread.php?12821-%D0%9C%D0%B8%D1%84%D1%8B-%D0%BE-Pawn-%D1%81%D0%BA%D1%80%D0%B8%D0%BF%D1%82%D0%B8%D0%BD%D0%B3%D0%B5-3), ибо читабельность сохраняется.
Все что касается сдвигов, то тут никаких проблем нет.
Благодарю за здоровую критику.

- - - Добавлено - - -


Лучше использовать возвращаемое значение для ошибок, а сам результат возвращать через ссылку. Кроме того, можно использовать упакованный массив для октетов, а уже после возвращать целую ячейку. Значение лучше конвертировать без функции strval - это намного дешевле вызова функции.


stock IPv4ToInteger(&dst, const src[]) {
new i = 0, chr, buf[3 char], j = 0, val = 0;
while ((chr = src[i++]) != EOS) {
switch (chr) {
case '0'..'9': {
val = val * 10 + chr - '0';
if (val > 255) {
return -1;
}
}
case '.': {
if (j > 3) {
return -1;
}
buf{j++} = val;
val = 0;
}
default: {
return -1;
}
}
}
dst = buf[0] | val;
return 0;
}


Да, пожалуй ты прав, что strval здесь не лучшее решение.
Что касается возврата ошибок, то я не думаю что стоит каждый раз проверять валидность IP. Лучше это делать в другом месте. Функция лишь выполняет то зачем и вызывается.

Shaolinka
05.04.2023, 19:45
Честно говоря, я многое не понял что ты имеешь ввиду)
Валидность IP это прерогатива вызывающего, если ему это нужно, то он может конечно использовать и регулярные выражения. В общем оставим это на совести скриптера.
По поводу массива в функции со sscanf, то тут выбор сделан в пользу скорости (https://pro-pawn.ru/showthread.php?12821-%D0%9C%D0%B8%D1%84%D1%8B-%D0%BE-Pawn-%D1%81%D0%BA%D1%80%D0%B8%D0%BF%D1%82%D0%B8%D0%BD%D0%B3%D0%B5-3), ибо читабельность сохраняется.
Все что касается сдвигов, то тут никаких проблем нет.
Благодарю за здоровую критику.

- - - Добавлено - - -



Да, пожалуй ты прав, что strval здесь не лучшее решение.
Что касается возврата ошибок, то я не думаю что стоит каждый раз проверять валидность IP. Лучше это делать в другом месте. Функция лишь выполняет то зачем и вызывается.

Разница не столь существенна в том, чтобы обратиться к индексу массива, а не к физическому адресу переменной. В прочем да, если значений немного, то можно обойтись и переменными. Дело вкуса вообщем. Насчёт сдвигов то да, щас досмотрел, что цикл сработает от нуля и до трёх. А значений то, по сути дела, четыре, поэтому да, мой косяк, не учёл.

- - - Добавлено - - -


Лучше использовать возвращаемое значение для ошибок, а сам результат возвращать через ссылку. Кроме того, можно использовать упакованный массив для октетов, а уже после возвращать целую ячейку. Значение лучше конвертировать без функции strval - это намного дешевле вызова функции.


stock IPv4ToInteger(&dst, const src[]) {
new i = 0, chr, buf[3 char], j = 0, val = 0;
while ((chr = src[i++]) != EOS) {
switch (chr) {
case '0'..'9': {
val = val * 10 + chr - '0';
if (val > 255) {
return -1;
}
}
case '.': {
if (j > 3) {
return -1;
}
buf{j++} = val;
val = 0;
}
default: {
return -1;
}
}
}
dst = buf[0] | val;
return 0;
}


Имею вопрос к окончательному выражению: что по итогу будет вмещать в себя нулевая ячейка массива buf? По сути дела, первое из значений(допустим 192). Потом, если мы "склеим" двоичное представление этого числа с переменной val(которая, допустим, хранит 255), то получим 255:
(192: 0000 0000 0000 0000 0000 0000 1100 0000)
(255: 0000 0000 0000 0000 0000 0000 1111 1111)
(255: 0000 0000 0000 0000 0000 0000 1111 1111).
Хотя, по сути дела, должны были получить величину, составленную из чисел. Или всё же, имеется ввиду, что обращение идёт непосредственно к неупакованной ячейке массива, размером в 4 байта, которая, по сути, на момент данной операции должна вмещать в себя три числа и, в последующем, должна склеить ещё последнее, хранящееся в переменной val? И почему именно switch? Разве условие вида: '0' <= chr <= '9' не будет обрабатываться быстрее?

Shaolinka
24.04.2023, 12:28
Решил, всё же, проверить алгоритм конвертации из числа в строку. По итогу, работает он слегка не так как нужно, а именно записывает данные с конца, а не с начала. Первые числа мы ведь записываем в старшие байты, потому сдвигаться изначально нужно до них. Я лично решил это следующим образом: добавив переменную pos с изначальным значением -1(потому что использовал преинкремент), которая выступала в роли индекса ячейки, куда пойдёт запись нужного числа. Плюс к тому, было принято решение использовать декремент в цикле, дабы замутить некое подобие обратного цикла, который будет сдвигаться должным образом: с начала и до конца. Естественно, можно замутить и инкремент в цикле, но, тогда уж, по умолчанию, переменная pos должна принимать в себя значение 4(это с учётом, если будет использоваться предекремент при записи в ячейку).

punkochel
29.04.2023, 07:42
Я делал функции, чтобы они были совместимы с функциями MySQL: INET_NTOA и INET_ATON.
Чтобы сделать конвертацию с обратной записью байтов, достаточно изменить порядок элементов массива:
// return part[3] | (part[2] << 8) | (part[1] << 16) | (part[0] << 24);
return part[0] | (part[1] << 8) | (part[2] << 16) | (part[3] << 24);

// format(result, 15+1, "%d.%d.%d.%d", part[3], part[2], part[1], part[0]);
format(result, 15+1, "%d.%d.%d.%d", part[0], part[1], part[2], part[3]);