10-значный идентификатор, который глобально и локально уникален - PullRequest
0 голосов
/ 05 декабря 2009

Мне нужно сгенерировать уникальный 10-символьный идентификатор (SIP / VOIP люди должны знать, что это для значения параметра icid в заголовке P-Charging-Vector). Каждый символ должен быть одной из 26 букв ASCII (с учетом регистра), одной из 10 цифр ASCII или дефис-минус.

Он ДОЛЖЕН быть «глобально уникальным (вне машины, генерирующей идентификатор)» и достаточно «локально уникальным (внутри машины, генерирующей идентификатор)», и все, что должно быть упаковано в 10 символов, фу!

Вот мое мнение. Я ПЕРВЫЙ, кодирующий 'ДОЛЖЕН' быть закодированным глобально уникальным локальным IP-адресом в base-63 (его длинное целое без знака, которое будет занимать 1-6 символов после кодирования) и затем столько, сколько я могу из текущей отметки времени (его time_t / long long int, который будет занимать 9-4 символа после кодирования, в зависимости от того, сколько места зашифрованный IP-адрес занимает в первую очередь).

Я также добавил счетчик циклов 'i' к отметке времени, чтобы сохранить уникальность в случае, если функция вызывается более одного раза в секунду.

Это достаточно хорошо, чтобы быть глобально и локально уникальным, или есть другой лучший подход?

Gaurav

#include <stdio.h>
#include <string.h>
#include <sys/time.h>

//base-63 character set
static char set[]="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-";

// b63() returns the next vacant location in char array x
int b63(long long longlong,char *x,int index){
    if(index > 9)
        return index+1;

    //printf("index=%d,longlong=%lld,longlong%63=%lld\n",index,longlong,longlong%63);
    if(longlong < 63){
        x[index] = set[longlong];
        return index+1;
    }  

    x[index] = set[longlong%63];
    return b63(longlong/63,x,index+1);
}

int main(){
    char x[11],y[11] = {0}; /* '\0' is taken care of here */

    //let's generate 10 million ids
    for(int i=0; i<10000000; i++){

        /*  add i to timestamp to take care of sub-second function calls,
            3770168404(is a sample ip address in n/w byte order) =                84.52.184.224 */
        b63((long long)time(NULL)+i,x,b63((long long)3770168404,x,0));

        // reverse the char array to get proper base-63 output
        for(int j=0,k=9; j<10; j++,k--)
            y[j] = x[k];

        printf("%s\n",y);
    }

    return 0;
}

Ответы [ 7 ]

5 голосов
/ 05 декабря 2009

ДОЛЖЕН быть уникальным во всем мире (за пределами машины, генерирующей идентификатор) 'и достаточно локально уникален (в пределах машина, генерирующая идентификатор) ', и все что нужно упаковать в 10 персонажи, фу!

Контролируете ли вы все идентификаторы, генерирующие программное обеспечение? Вы раздавали идентификаторы? Если нет ...

Я ничего не знаю о SIP, но должно быть неправильное понимание того, что у вас есть о спецификации (или спецификация должна быть неправильной). Если другой разработчик попытается создать идентификатор с использованием алгоритма, отличного от того, который вы создали, у вас будут конфликты с их идентификаторами, то есть они будут знать, что в этой системе они будут глобально уникальны.

Я бы вернулся к документации SIP, чтобы посмотреть, есть ли приложение с алгоритмом генерации этих идентификаторов. Или, может быть, более умный пользователь SO, чем я, может ответить, что такое алгоритм SIP для генерации этих идентификаторов.

2 голосов
/ 06 декабря 2009

Я бы серьезно посмотрел на RFC 4122, который описывает генерацию 128-битных GUID. Существует несколько различных алгоритмов генерации, некоторые из которых могут подходить (на ум приходит один из них на основе MAC-адреса). Это большее число, чем у вас 2 ^ 128 = 3,4 * 10 ^ 38 по сравнению с 63 ^ 10 = 9,8 * 10 ^ 17, поэтому вам, возможно, придется пойти на некоторые компромиссы в отношении уникальности. Рассмотрим такие факторы, как частота генерации идентификаторов.

Однако в RFC они рассмотрели некоторые практические вопросы, такие как возможность эффективно генерировать большое количество уникальных значений путем предварительного выделения блоков идентификаторов.

1 голос
/ 06 декабря 2009

Ну, если я отброшу тот факт, что считаю это плохой идеей, и сконцентрируюсь на решении вашей проблемы, вот что я бы сделал:

У вас есть диапазон идентификаторов 10 ^ 63, что соответствует примерно 60 битам. Вы хотите, чтобы он был «глобально» и «локально» уникальным. Давайте сгенерируем первые N битов, которые будут глобально уникальными, а остальные - локально уникальными. Объединение двух будет иметь свойства, которые вы ищете.

Во-первых, глобальная уникальность: IP не будет работать, особенно локальные, в них очень мало энтропии. Я бы пошел с MAC-адресами, они были созданы для того, чтобы быть глобально уникальными. Они охватывают диапазон 256 ^ 6, поэтому используются 6 * 8 = 48 бит.

Теперь для локально уникальных: почему бы не использовать идентификатор процесса? Я делаю предположение, что уникальность каждого процесса, если нет, вам придется думать о чем-то еще. В Linux идентификатор процесса составляет 32 бита. Если мы хотим придираться, 2 старших байта, вероятно, содержат очень мало энтропии, как это было бы в 0 на большинстве машин. Поэтому отбросьте их, если знаете, что делаете.

Итак, теперь вы увидите, что у вас есть проблема, так как она будет использовать до 70 бит для генерации приличного (но не пуленепробиваемого) глобального и локального уникального идентификатора (в любом случае, используя мою технику). И поскольку я бы также посоветовал ввести случайное число (длиной не менее 8 бит) на всякий случай, оно точно не подойдет. Поэтому на вашем месте я бы хэшировал ~ 78 сгенерированных битов в SHA1 (например) и конвертировал первые 60 бит результирующего хэша в ваш формат идентификатора. Для этого обратите внимание, что у вас есть выбор из 63 символов, то есть почти полный диапазон из 6 бит. Итак, разбейте хеш на 6 битных частей и используйте первые 10 частей, чтобы выбрать 10 символов вашего идентификатора из 63 символов. Очевидно, что диапазон из 6 битов равен 64 возможным значениям (вам нужно только 63), поэтому, если у вас есть 6-битная часть, равная 63, либо возведите ее в 62 или предположите по модулю 63 и выберите 0. Это слегка сместит распределение, но это не так уж плохо.

Итак, это должно дать вам приличный глобальный и локальный псевдо-уникальный идентификатор.

Несколько последних моментов: в соответствии с парадоксом дня рождения , вы получите ~ 1% вероятности столкновений после генерации ~ 142 миллионов идентификаторов и 99% после генерации 3 миллиардов идентификаторов. Поэтому, если вы добились большого коммерческого успеха и генерировали миллионы идентификаторов, получите больший идентификатор.

Наконец, я думаю, что предоставил решение вашей проблемы «лучше, чем хуже», но я не могу не думать, что вы атакуете эту проблему неправильно, и, возможно, как уже упоминали другие, неправильно читая спецификации , Так что используйте это, если нет других способов, которые были бы более «пуленепробиваемыми» (централизованный поставщик идентификаторов, гораздо более длинный идентификатор ...).

Редактировать: Я перечитал ваш вопрос, и вы говорите, что вызываете эту функцию, возможно, много раз в секунду. Я предполагал, что это должно было служить неким идентификатором приложения, сгенерированным один раз в начале вашего приложения, и никогда не изменявшимся до его выхода. Так как это не так, вы должны обязательно добавить случайное число, и если вы генерируете много идентификаторов, сделайте это как минимум 32-битным числом. И прочитайте и перечитайте Парадокс Дня Рождения, на который я ссылался выше. И запустите ваш генератор чисел в высоко энтропийное значение, например, в значение usec текущей временной метки. Или даже пойти так далеко, чтобы получить ваши случайные значения из / dev / urandom. Честно говоря, мое мнение о том, что 60 бит, вероятно, недостаточно ...

1 голос
/ 05 декабря 2009

Машины в локальных сетях NAT часто имеют IP из небольшого диапазона, и не все 32-битные значения будут действительными (например, многоадресная рассылка и т. Д.). Машины могут также захватывать одну и ту же метку времени, особенно если гранулярность велика (например, секунды); имейте в виду, что год очень часто будет одинаковым, поэтому именно младшие биты придадут вам наибольшую «уникальность».

Возможно, вы захотите взять различные значения, хешировать их с помощью криптографического хэша и преобразовать их в символы, которые вам разрешено использовать, с усечением до 10 символов.

Но вы имеете дело со значением менее 60 бит; Вы должны тщательно подумать о последствиях столкновения. Возможно, вы подошли к проблеме неправильно ...

1 голос
/ 05 декабря 2009

Разве вы не можете просто иметь распределенную таблицу идентификаторов?

0 голосов
/ 07 декабря 2009

@ Дуг Т. Нет, я не контролирую все программное обеспечение, генерирующее идентификаторы. Я согласен, что без стандартизированного алгоритма возможны коллизии, я поднимал эту проблему в соответствующих списках рассылки.

@ Флориан Принимая подсказку от вашего ответа. Я решил использовать / dev / urandom PRNG для 32-битного случайного числа в качестве уникального пространства идентификатора. Я предполагаю, что у каждой машины будет своя собственная шумовая сигнатура, и можно предположить, что она в любой момент может быть уникальной в мире. Уникальный по времени компонент, который я использовал ранее, остается прежним.

Эти уникальные идентификаторы создаются для сопоставления всей информации о выставлении счетов, собранной из различных сетевых функций, которые независимо генерировали информацию о начислении платы за конкретный вызов во время обработки вызова.

Вот обновленный код ниже:

Gaurav

 #include <stdio.h>
 #include <string.h>
 #include <time.h>

 //base-63 character set
 static char set[]="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-";

 // b63() returns the next vacant location in char array x
 int b63(long long longlong, char *x, int index){
     if(index > 9)
         return index+1;

     if(longlong < 63){
         x[index] = set[longlong];
         return index+1;
     }  

     x[index] = set[longlong%63];
     return b63(longlong/63, x, index+1);
 }

 int main(){
     unsigned int number;
     char x[11], y[11] = {0};

     FILE *urandom = fopen("/dev/urandom", "r");
     if(!urandom)
         return -1;

     //let's generate a 1 billion ids
     for(int i=0; i<1000000000; i++){

         fread(&number, 1, sizeof(number), urandom);

         // add i to timestamp to take care of sub-second function calls, 
         b63((long long)time(NULL)+i, x, b63((long long)number, x, 0));

         // reverse the char array to get proper base-63 output
         for(int j=0, k=9; j<10; j++, k--)
             y[j] = x[k];

         printf("%s\n", y);
     }

     if(urandom)
     fclose(urandom);

     return 0;
 }
0 голосов
/ 05 декабря 2009

Хм, использование системных часов может быть слабым местом ... что если кто-то вернет часы назад? Вы можете заново сгенерировать тот же идентификатор снова. Но если вы собираетесь использовать часы, вы можете вызвать gettimeofday () вместо time (); по крайней мере, таким образом вы получите лучшее разрешение, чем одна секунда.

...