Работа с двоичными данными на примере генерации UUID версии 1

Наконец-то платформа 1С с версии 8.3.9 позволяет разработчику работать с двоичными данными в полном объеме при помощи встроенных функций, не прибегая к COM-объектам и внешним компонентам. Рассмотрим упрощенный алгоритм генерации уникального идентификатора версии 1, то есть по алгоритму основанному на времени. Конструкция Новый УникальныйИдентификатор() генерирует уникальный идентификатор версии 4, то есть по алгоритму основанному на случайном числе. Выяснить версию уникального идентификатора очень легко, так как уникальный идентификатор хранит в себе версию, поэтому можно либо самому декодировать UUID и вычислить версию, либо прибегнуть к помощи сайтам, которые умеют декодировать UUID (см. рисунок 1).

Рисунок 1. Результат декодирования UUID версии 4 на сайте https://realityripple.com/Tools/UnUUID/

Уникальный идентификатор состоит из шести полей, которые представляют собой упорядоченную последовательность:

  1. Поле TimeLow - четыре октета;
  2. Поле TimeMid - два октета;
  3. Поле VersionAndTimeHigh - два октета;
  4. Поле VariantAndClockSeqHigh - один октет;
  5. Поле ClockSeqLow - один октет;
  6. Поле Node - шесть октетов.

Каждое поле вычисляется по определенному алгоритму в зависимости от требуемой версии уникального идентификатора. Подробное описание алгоритмов генерации уникального идентификатора приведено в "ГОСТ Р ИСО/МЭК 9834-8-2011 Взаимосвязь открытых систем. Процедуры работы уполномоченных по регистрации ВОС. Часть 8. Создание, регистрация универсально уникальных идентификаторов (УУИд) и их использование в качестве компонентов идентификатора объекта АСН.1".

1) Генерируем случайное 14-битное число для временной последовательности, значение 16383 является максимальным беззнаковым 14-битным десятичным числом. Так как рассматриваем упрощенный алгоритм, то при каждой генерации нового уникального идентификатора будем генерировать случайное число для временной последовательности.

  
ГЧ = Новый ГенераторСлучайныхЧисел;
СлучайноеЧисло = ГЧ.СлучайноеЧисло(0, 16383);
  

2) Время должно быть 60-битовым значением, отсчитываем в 100 наносекундных интервалах всемирного скоординированного времени (UTC), начиная от полуночи 15 октября 1582 года (дата григорианской реформы христианского календаря). Внимание, так как время отсчитываем в 100 наносекундных интервалах UTC, то переменная МеткаВремени должна содержать время UTC.

  
ДатаГригорианскойРеформы = Дата("15821015");
РазностьДатВСек = МеткаВремени - ДатаГригорианскойРеформы;
НаносекундныхИнтервалов = РазностьДатВСек * 1000 * 10000;
  

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

3) Помещаем время, отсчитанное в 100 наносекундных интервалах, в буфер двоичных данных. В байте, который находится в нулевой позиции буфера для бита под номером 4 устанавливаем значение Истина (1), тем самым указываем версию алгоритма.

  	
БДДН = Новый БуферДвоичныхДанных(8, ПорядокБайтов.BigEndian);
БДДН.ЗаписатьЦелое64(0, НаносекундныхИнтервалов);
БДДН.Установить(0, УстановитьБит(БДДН[0], 4, Истина));
  

4) Помещаем случайное 14-битное число в буфер двоичных данных. В байте, который находится в нулевой позиции буфера для бита под номером 7 устанавливаем значение Истина (1), так требует алгоритм.

  
БДДС = Новый БуферДвоичныхДанных(2, ПорядокБайтов.BigEndian);
БДДС.ЗаписатьЦелое16(0, СлучайноеЧисло);
БДДС.Установить(0, УстановитьБит(БДДС[0], 7, Истина));
  

5) Получаем HEX-строки для полей уникального идентификатора: TimeLow, TimeMid, VersionAndTimeHigh, VariantAndClockSeqHigh, ClockSeqLow.

  	
TimeLow             = ПолучитьHexСтрокуИзБуфераДвоичныхДанных(БДДН.ПолучитьСрез(4));
TimeMid             = ПолучитьHexСтрокуИзБуфераДвоичныхДанных(БДДН.ПолучитьСрез(2, 2));
VersionAndTimeHigh  = ПолучитьHexСтрокуИзБуфераДвоичныхДанных(БДДН.ПолучитьСрез(0, 2));

VariantAndClockSeqHigh = ПолучитьHexСтрокуИзБуфераДвоичныхДанных(БДДС.ПолучитьСрез(0, 1));
ClockSeqLow            = ПолучитьHexСтрокуИзБуфераДвоичныхДанных(БДДС.ПолучитьСрез(1));
  

6) HEX-строку для поля Node получаем из шестнадцатеричного представления MAC-адреса сетевой карты, из которого убираем двоеточие. Средствами платформы невозможно получить MAC-адрес сетевой карты, поэтому переменная АдресСетевойКарты должна содержать строку вида "XX:XX:XX:XX:XX:XX".

  
Node = СтрЗаменить(АдресСетевойКарты, ":", "");
  

7) После того, как все поля уникального идентификатора вычислены, собираем их в определенной последовательности и получаем новый уникальный идентификатор в шестнадцатеричном представлении. Итоговый код функции:

  
// Функция генерирует UUID по варианту 1 (на основании MAC-адреса и текущего времени)
//
// Параметры:
//  МеткаВремени  - Дата - время UTC
//  АдресСетевойКарты  - Строка - MAC-адрес сетевой карты формата "XX:XX:XX:XX:XX:XX"
//
// Возвращаемое значение:
//   Строка   - уникальный идентификатор в формате "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
//
Функция СформироватьUUIDver1(МеткаВремени, АдресСетевойКарты) Экспорт 
	
	ГЧ = Новый ГенераторСлучайныхЧисел;
	СлучайноеЧисло = ГЧ.СлучайноеЧисло(0, 16383); // Максимальное 14-битное число.

	ДатаГригорианскойРеформы = Дата("15821015");
	РазностьДатВСек = МеткаВремени - ДатаГригорианскойРеформы;
	НаносекундныхИнтервалов = РазностьДатВСек * 1000 * 10000;
	
	БДДН = Новый БуферДвоичныхДанных(8, ПорядокБайтов.BigEndian);
	БДДН.ЗаписатьЦелое64(0, НаносекундныхИнтервалов);
	БДДН.Установить(0, УстановитьБит(БДДН[0], 4, Истина));
	
	БДДС = Новый БуферДвоичныхДанных(2, ПорядокБайтов.BigEndian);
	БДДС.ЗаписатьЦелое16(0, СлучайноеЧисло);
	БДДС.Установить(0, УстановитьБит(БДДС[0], 7, Истина));
	
	TimeLow             = ПолучитьHexСтрокуИзБуфераДвоичныхДанных(БДДН.ПолучитьСрез(4));
	TimeMid             = ПолучитьHexСтрокуИзБуфераДвоичныхДанных(БДДН.ПолучитьСрез(2, 2));
	VersionAndTimeHigh  = ПолучитьHexСтрокуИзБуфераДвоичныхДанных(БДДН.ПолучитьСрез(0, 2));
	
	VariantAndClockSeqHigh = ПолучитьHexСтрокуИзБуфераДвоичныхДанных(БДДС.ПолучитьСрез(0, 1));
	ClockSeqLow            = ПолучитьHexСтрокуИзБуфераДвоичныхДанных(БДДС.ПолучитьСрез(1));
	
	Node = СтрЗаменить(АдресСетевойКарты, ":", "");
	
	Возврат НРег(TimeLow + "-" + TimeMid + "-" + VersionAndTimeHigh + "-" + VariantAndClockSeqHigh + ClockSeqLow + "-" + Node);

КонецФункции // СформироватьUUIDver1()
  

Результат выполнения функции проверяем на сайте по декодированию уникального идентификатора (см. рисунок 2).

Рисунок 2. Результат декодирования UUID версии 1 на сайте https://realityripple.com/Tools/UnUUID/

Все хорошо, уникальный идентификатор декодируется, он версии 1, содержит в себе закодированную дату UTC.

Комментарии

  1. Также столкнулся с проблемой получения UID первой версии (с меткой времени). Я делаю чутка по-другому: сначала все, как у Вас, т.е. получаю текущую дату отнимаю от нее дату принятия григорианского календаря, а дальше просто перевожу в шестнадцатиричную систему счисления. Вы выкрутились чуть более красиво, чтобы не писать код перевода в HEX. Только я еще не просто умножаю на 10 млн, но еще и случайное число прибавляю к этой цифре (типа у меня очень точное время отметилось. Прям с точностью до десятых долей микросекунд). А вот последние символы... будь то адрес сетевой карты или что там еще, можно ведь спокойно получить из того же самого уникального идентификатора, который генерирует сама 1С (UID4 = Строка(Новый УникальныйИдентификатор))
    Также видел у одного человека способ еще проще. В транзакции создаем какой-нибудь элемент справочника или документ. Считываем из него уникальный идентификатор и отменяем транзакцию. Уникальный идентификатор, сгенерированный для ссылочного типа всегда будет первой версии (за исключением предопределенных элементов справочника). Мне этот способ не понравился, т.к. код не портируемый (в другой конфигурации может не оказаться нужного справочника/документа), поэтому я воспользовался вышеуказанным своим. Но, за статью спасибо. Мне понравилась идея писать в буфер двоичных данных а потом считывать из него как Hex-строку

    ОтветитьУдалить

Отправить комментарий