JS, C++ и C# дают разные результаты кодирования Base64 Guid

У меня есть клиент C++/UnrealEngine, который взаимодействует как с существующим сервером C#, так и с существующим сервером JavaScript (все в Windows).

Моя проблема в том, что когда я кодирую Guids в Base64 на C++, я получаю разные результаты от C# и JS. Насколько я могу судить, это напрямую связано с тем, как кодируются строки Guid, как указано в этом ТАК-ответе.

Я не могу изменить серверы C# и JS, поскольку у них есть другие зависимые системы, так что мне нужно сделать в C++ (или Unreal), чтобы гарантировать, что я получу тот же результат?

//The Guid as a literal string
"3F73B3E6-7351-416F-ACA3-AE639A3F587F"

//C# & JSEncoded
"5rNzP1Fzb0Gso65jmj9Yfw"

//C++ Encoded
"P3Oz5nNRQW+sowAArmOaPw"

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

//https://rcfed.com/Utilities/Base64GUID
//https://toolslick.com/conversion/data/guid
"5rNzP1Fzb0Gso65jmj9Yfw"

//https://www.fileformat.info/tool/guid-base64.htm
"P3Oz5nNRQW+sowAArmOaPw"

Код, используемый для преобразований, приведен ниже:

//In C#
Guid myGuid = new Guid("3F73B3E6-7351-416F-ACA3-AE639A3F587F");

//Encodes / & + for URL & truncates the trailing "= = " padding
string strBase64Guid = Convert.ToBase64String(myGuid.ToByteArray()).Substring(0, 22).Replace("/", "_").Replace("+", "-");
//In JavaScript
//https://stackoverflow.com/questions/55356285/how-to-convert-a-string-to-base64-encoding-using-byte-array-in-javascript
var strGuid = GuidToBase64("3F73B3E6-7351-416F-ACA3-AE639A3F587F");

function GuidToBase64(guid){
    //Guid to ByteArray
    var buffer = [];
    guid.split('-').map((number, index) => {
        var bytesInChar = index < 3 ? number.match(/.{1,2}/g).reverse() : number.match(/.{1,2}/g);
        bytesInChar.map((byte) => { buffer.push(parseInt(byte, 16)); })
    });
    
    var base64String = btoa(String.fromCharCode.apply(null, new Uint8Array(buffer)));
    //Encodes / & + for URL & truncates the trailing "= = " padding
    return base64String.slice(0, 22).replace("/", "_").replace("+", "-");
}
//In C++ (Unreal Engine)
FGuid& myGuid = FGuid("3F73B3E6-7351-416F-ACA3-AE639A3F587F");
FString& strGuid = myGuid.ToString(EGuidFormats::Short);


//Copyright Epic Games, Inc. (AFAIK I'm allowed to paste snippets from the sources, just not the whole thing. UE's source it's all on GitHub anyway.)

//Truncated [Function overloads, & Guid format handling]

uint32 Bytes[4] = { NETWORK_ORDER32(A), NETWORK_ORDER32(B), NETWORK_ORDER32(C), NETWORK_ORDER32(D) };
TCHAR Buffer[25];
int32 Len = FBase64::Encode(reinterpret_cast<const uint8*>(Bytes), sizeof(Bytes), Buffer);
TArrayView<TCHAR> Result(Buffer, Len);
    
//Truncated [Sanitizes '+' & '/' and cuts the '==' padding]
}
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
1
0
81
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

5rNzP1Fzb0Gso65jmj9Yfw декодирует в:

E6 B3 73 3F  51 73  6F 41  AC A3  AE 63 9A 3F 58 7F

P3Oz5nNRQW+sowAArmOaPw декодирует в:

3F 73 B3 E6  73 51  41 6F  AC A3  00 00 AE 63 9A 3F

Ваш код C++ перевернул первые 4 байта. Поменяли местами следующие две пары байтов и полностью разбили последнюю 6-байтовую группу.

Я даже не подумал посмотреть на каждую шестнадцатеричную пару и сравнить их как группу... Последняя пара также выглядит перевернутой, если предположить, что 00 00 — это просто заполняющие символы ==, которые в противном случае были бы обрезаны, чтобы получить 22 символа. нить.

Reahreic 24.04.2024 21:47
Ответ принят как подходящий

Рассматриваемый base64 является результатом кодирования Guid в его двоичном формате, а не в исходном строковом формате.

Руководство в двоичном формате состоит из:

  • 4-байтовое целое число
  • 2-байтовое целое число
  • 2-байтовое целое число
  • 8-байтовый массив

Он не состоит из:

  • 4-байтовое целое число
  • 2-байтовое целое число
  • 2-байтовое целое число
  • 2-байтовое целое число
  • 6-байтовый массив

Как показано в строковом формате.

Ваш код C++ кодирует в base64 массив из четырех 4-байтовых целых чисел (потому что именно так FGuid хранит их — я не знаю почему), что не является тем же форматом Guid, который используют C# и Javascript. Вам необходимо убедиться, что вы используете правильное количество целых чисел, а также правильный размер байтов и порядок байтов для них, чтобы соответствовать тому, что используют C# и Javascript.

IIRC, Javascript uses Big-Endian integers, and C# uses Big- or Little-Endian integers depending on the system (see BitConverter.IsLittleEndian).

5rNzP1Fzb0Gso65jmj9Yfw декодирует как байты:

e6 b3 73 3f 51 73 6f 41 ac a3 ae 63 9a 3f 58 7f

Это следующие значения Guid в двоичном формате:

e6 b3 73 3f = 0x3F73B3E6
51 73 = 0x7351
6f 41 = 0x416F
ac a3 ae 63 9a 3f 58 7f

Итак, вам нужно что-то эквивалентное следующему в вашем коде C++:

uint8 Bytes[16];
memcpy(Bytes+0,  &myGuid.A, 4);
memcpy(Bytes+4,  &myGuid.B, 4);
memcpy(Bytes+8,  &myGuid.C, 4);
memcpy(Bytes+12, &myGuid.D, 4);

uint32 *ptr1 = reinterpret_cast<uint32*>(Bytes+0);
uint16 *ptr2 = reinterpret_cast<uint16*>(Bytes+4);
uint16 *ptr3 = reinterpret_cast<uint16*>(Bytes+2);

*ptr1 = NETWORK_ORDER32(*ptr1);
*ptr2 = NETWORK_ORDER16(*ptr2);
*ptr3 = NETWORK_ORDER16(*ptr3);
// not sure if FGuid stores the remaining
// 8 bytes in the correct order or not. If
// not, swap them around here as needed...

TCHAR Buffer[25];
int32 Len = FBase64::Encode(Bytes, sizeof(Bytes), Buffer);
...

Спасибо, это заставило меня задуматься о том, как .Net хранит свои Guids, поэтому я посмотрел на их декомпилированную реализацию Guid.ToByteArray(); и сравнил ее со структурой байтовых данных FGuid, и это в основном щелкнуло. Для проверки я вручную преобразовал FGuid в байт[16] с помощью сдвига битов. Как ни странно, guid.A был заказан нормально, в то время как guid.B нужно было разбить пополам, затем каждый uint16 поменялся местами в массиве, в то время как guid.C & guid.D каждый требовал обратного порядка 4x uint8.

Reahreic 25.04.2024 15:57

Исходный код @Reahreic Guid доступен в Интернете: github.com/microsoft/referencesource/blob/master/mscorlib/…

Remy Lebeau 25.04.2024 16:18

Другие вопросы по теме