Является ли reinterpret_cast плохим при работе с низкоуровневой манипуляцией байтами?

Я пишу сервер веб-сокетов, и мне приходится иметь дело с замаскированными данными, которые мне нужно разоблачить.

Маска имеет тип unsigned char [4], а данные также являются буфером без знака char *.

Я не хочу XOR байт за байтом, я бы предпочел XOR 4 байта за раз.

uint32_t * const end = reinterpret_cast<uint32_t *>(data_+length);
for(uint32_t *i = reinterpret_cast<uint32_t *>(data_); i != end; ++i) {
*i ^= mask_;
}

Есть ли что-то не так с использованием reinterpret_cast в этой ситуации?

Альтернативой может быть следующий код, который не так понятен и не так быстр:

uint64_t j = 0;
uint8_t *end = data_+length;
for(uint8_t *i = data_; i != end; ++i,++j) {
*i ^= mask_[j % 4];
}

Я все уши за альтернативы, в том числе те, которые зависят от функций C ++ 11.

9

Решение

Вот несколько потенциальных проблем с опубликованным подходом:

  1. В некоторых системах объекты типа больше чем char должен быть правильно выровнен, чтобы быть доступным. Типичное требование для uint32_t заключается в том, что объект выровнен по адресу, кратному четырем.
  2. Если length / sizeof(uint32_t) != 0 цикл может никогда не закончиться.
  3. В зависимости от порядка работы системы mask должен содержать разные значения. Если mask производится *reinterpret_cast<uint32_t>(char_mask) подходящего массива это не должно быть массивом.

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

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

char* it(data);
if (4 < length) {
for (char* end(data + length - 4); it < end; it += 4) {
it[0] ^= mask_[0];
it[1] ^= mask_[1];
it[2] ^= mask_[2];
it[3] ^= mask_[3];
}
}
it != data + length && *it++ ^= mask_[0];
it != data + length && *it++ ^= mask_[1];
it != data + length && *it++ ^= mask_[2];
it != data + length && *it++ ^= mask_[3];

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

8

Другие решения

Там нет ничего особенно плохого в reinterpret_cast в этом случае. Но будь осторожен.

32-битный цикл в его нынешнем виде некорректен, потому что он не подходит для случая, когда полезная нагрузка не кратна 32-битному размеру. Полагаю, два возможных решения:

  • заменить != с < в проверке цикла (есть причина, почему люди используют <, и это не потому, что они тупые …) и делают завершающие 1-3 байта побайтно
  • расположите буфер так, чтобы размер буфера для части полезной нагрузки был кратен 32 битам, и просто XOR дополнительных байтов. (Предположительно код проверяет длину полезной нагрузки при возврате байтов вызывающей стороне, так что это не имеет значения.)

Кроме того, в зависимости от того, как структурирован код, вам также может понадобиться справиться с неправильным доступом к данным для некоторых процессоров. Если у вас есть весь буфер в буфере, заголовок и все, в буфере, который выровнен на 32 бита, и если длина полезной нагрузки <126 байт или> 65 535 байт, тогда и ключ маскирования, и полезная нагрузка будут смещены.

Как бы то ни было, мой сервер использует что-то вроде первого цикла:

for(int i=0;i<n;++i)
payload[i]^=key[i&3];

В отличие от 32-битного варианта, это в принципе невозможно ошибиться.

2

По вопросам рекламы [email protected]