Ориентация главы

Чтобы двоичные данные вошли в текстовый канал (заголовки HTTP, поля JSON, параметры URL), их нужно сперва «перевести» в чисто текстовые байты — модуль codec даёт три стандартных перевода: Base64 (3 байта в 4 символа, разрастание 33% — заголовки данных, JWT, почтовые вложения), Hex (1 байт в 2 символа, разрастание 100% — отладочные дампы, отпечатки ключей, короткие двоичные значения) и Percent (экранирует только нелегальные символы, минимальное разрастание — URL и формы). Тройка разделяет один набор инженерных конвенций: правила ёмкости выводимы (размер буфера не угадывается), отказ не пишет полрезультата (атомарность) и варианты объявляются явно (URL-safe, регистр, пользовательский алфавит — параметры, а не догадки). Эти конвенции роднят главу со строгим разбором главы 26 — каждый вход и выход текстового мира точен до байта.

28.1引入

Три реальных потребности. Почтовое вложение двоичное, а SMTP принимает только текст — Base64 превращает каждые 3 байта в 4 печатных символа, плюс треть к объёму в обмен на проход через почтовый канал. При отладке нужно вывести в журнал короткий кусок ключевого материала — Hex превращает каждый байт в два шестнадцатеричных символа: читаемо человеком, копируемо, сравнимо. Ввод пользователя кладётся в параметр запроса URL — в reports/July 2026 и /, и пробел суть зарезервированные или нелегальные символы URL; кодирование Percent превращает их в %2F и %20, остальные символы проходят как есть — три кодирования под три потребности: «преобразование целиком, читаемое преобразование, минимальное преобразование».

Первый вопрос выбора — всегда сколько терпит канал: разрастание 33% у Base64, 100% у Hex, около 0% у Percent — ответ обычно диктует сам протокол (Content-Transfer-Encoding: base64, percent-encoding из спецификации URL); ваша задача — употребить правильно, а не выбирать свободно.

28.2概念

Сравнение форм тройки

1Base643 байта -> 4 символа (разрастание 33%); стандарт заголовков данных / JWT / вложений
2Hex1 байт -> 2 символа (разрастание 100%); дампы / отпечатки / короткие значения, читаемо человеком
3Percentэкранирует только целевое множество (~0% разрастания); минимальная перепись для URL и форм

Правила ёмкости: размер выхода всегда вычислим в столбик

Выход Base64 = 4 × ceil(n / 3) (с заполнением); выход Hex = 2n; верхняя граница выхода Percent = 3n (экранированным может оказаться каждый байт). Сперва посчитать ёмкость, потом давать буфер — базовое упражнение главы: семейство Encode пишет в буфер вызывающего и при нехватке ёмкости отказывается целиком, не оставляя полрезультата (атомарность: в выходном буфере не остаётся обрубка, который кто-то мог бы по ошибке употребить), а семейство EncodeNew сразу возвращает владеющий буфер (освобождение xrtFree) — распределение берёт на себя библиотека. Выбор между двумя маршрутами полностью изоморфен развилке «буфер вызывающего против владеющего» из главы 25.

Явные варианты: стандартный и URL-safe

В стандартном алфавите Base64 есть + и / — это зарезервированные символы URL, отсюда вариант URL-safe (замена на - и _), включаемый параметром алфавита в xbase64config (в поле Alphabet передавайте NULL для стандартной таблицы по умолчанию); декодирование строго сверяется с той же конфигурацией — несовпадение алфавита есть отказ. У Hex две формы выхода, верхний и нижний регистр (декодер ест обе). Множество экранирования Percent объявляется по сценарию (у сегментов пути и параметров запроса URL зарезервированы разные символы). Принцип тот же, что в главе 26: вариант — явный параметр, а не угаданное умолчание.

Строгость декодирования

Декодирование столь же строго: нелегальные символы (вне алфавита), невыровненная длина (Base64 не кратен 4), усечённые последовательности экранирования (одинокий % у Percent) — отказ целиком с ошибкой: мусор не входит, обрубок не выходит. Это та же философия дизайна, что STRICT у главы о наборах символов (глава 27) и потребление целого входа у главы о числах (глава 26): текстовая граница — стык поверхности атак и поверхности аварий; расплывчатость здесь никогда не была снисходительностью — всегда миной.

28.3示例

Полная программа: Base64 в двух позах — стандартной и владеющей

Из образца репозитория examples/codec/base64/main.c:

examples/codec/base64/main.c源码 ↗
















 */

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

#include <xrt.h>



int main(void)
{
	/* sizeof 含结尾零,编码长度用 sizeof-1 显式排除。 */
	static const char Message[] = "xrt codec";
	char Encoded[32];         /* 调用方缓冲:12 字符 + 零足够 */
	bytes pDecoded;           /* 拥有式:解码产物 */
	size_t iEncodedSize;
	size_t iDecodedSize;

	



	 */
	if ( !xrtBase64Encode(
		Message, sizeof(Message) - 1u, Encoded, sizeof(Encoded),
		&iEncodedSize, NULL
	) ) {
		return 1;
	}

	



	 */
	pDecoded = xrtBase64DecodeNew(
		Encoded, iEncodedSize, &iDecodedSize, NULL
	);
	if ( (pDecoded == NULL) || (iDecodedSize != sizeof(Message) - 1u) ||
		(memcmp(pDecoded, Message, iDecodedSize) != 0) ) {
		xrtFree(pDecoded);
		return 2;
	}
	printf("%s -> %s\n", Message, Encoded);
	xrtFree(pDecoded);
	return 0;
}
$ gcc -O1 -DXRT_MODULE_ALL -I single impl.c examples/codec/base64/main.c -lws2_32 -liphlpapixrt codec -> eHJ0IGNvZGVj

Что только что произошло. (1) Вход — 9 байтов, правило ёмкости 4 × ceil(9/3) = 12 символов — буфер выдан по формуле, Encode пишет за один шаг. (2) sizeof-1 исключает хвостовой ноль — старое правило из хеширования главы 12: вход кодирования — байтовая строка, а нулевое завершение — лишь случайность C-строки. (3) Декодирование идёт владеющим маршрутом DecodeNew — круговой проход подтверждает правильность кодирования. (4) «Атомарность» стоит проверить руками: умышленно уменьшите буфер — Encode откажется, а выходная область останется нетронутой: никакого полукодированного base64, оставленного кому-то на ошибочное употребление.

Полная программа: круговой проход кодирования Percent

Из examples/codec/percent/main.c:

examples/codec/percent/main.c源码 ↗
















 */

#include <stdio.h>

#include <xrt.h>



int main(void)
{
	/* 含路径分隔符与空格的原始 segment——两者都必须被转义。 */
	static const char Segment[] = "reports/July 2026";
	char Encoded[64];        /* 最坏情况 3n:17 字节 → 51 字符,64 够用 */
	bytes pDecoded;
	size_t iEncodedSize;
	size_t iDecodedSize;

	


	 */
	if ( !xrtPercentEncode(
		Segment, sizeof(Segment) - 1u, XRT_STR_LITERAL(""),
		Encoded, sizeof(Encoded), &iEncodedSize
	) ) {
		return 1;
	}

	/* 分配型解码:%XX 还原为字节;非法转义(如 %G0)会失败返回 NULL。 */
	pDecoded = xrtPercentDecodeNew(
		(xstrview){ Encoded, iEncodedSize }, &iDecodedSize
	);
	if ( pDecoded == NULL ) {
		return 2;
	}

	/* 解码产物是二进制缓冲:打印时用显式长度,不依赖结尾零。 */
	printf("encoded: %s\ndecoded: %.*s\n",
		Encoded, (int)iDecodedSize, (const char*)pDecoded);
	xrtFree(pDecoded);
	return 0;
}
$ gcc -O1 -DXRT_MODULE_ALL -I single impl.c examples/codec/percent/main.c -lws2_32 -liphlpapiencoded: reports%2FJuly%202026decoded: reports/July 2026

Что только что произошло. (1) Кодирование тронуло ровно два символа: / → %2F, пробел → %20, буквы как были — форма минимальной переписи видна с одного взгляда. (2) Круговой проход без потерь — декодирование Percent строго обратно кодированию; одиночный % или нелегальный hex на стороне декодера — отказ целиком. (3) Точка приземления для URL: поставьте это за парсером строки запроса из главы 25 — и получится полный URL-инструментарий: разбор (string) → кодирование (codec) → передача.

Рельеф ловушек на стороне декодера

У декодирования на три класса ловушек больше, чем у кодирования, и каждый стоит назвать по имени. Дрейф алфавитов: отправитель URL-safe, получатель декодирует стандартной таблицей — -/_ вне стандартной таблицы, отказ целиком — везение; коварнее, когда обе стороны «терпимо» обрабатывают каждая своё и данные молча расползаются. Двусмысленность заполнения: наличие =-заполнения у Base64 диктуется протоколом (сегменты JWT его не несут); реализация, снисходительно прощающая пропуски, — строгий режим отвергает невыровненный вход именно затем, чтобы такая двусмысленность осталась за дверью. Детектор вложенного кодирования: увидев %2520, понимайте — это окаменелость двойного Percent-кодирования; декодер не чинит ошибки процесса выше по течению, он может лишь строго отказаться. Общее решение всех трёх одно: одна и та же конфигурация на обоих концах, строгий режим, отказ — сигнал процесса, а не шум данных.

Граница с перекодированием главы 27

Пара, которую путают: xrtTranscode главы о наборах символов (глава 27) и кодирование этой главы — оба суть «преобразования байтовой формы», где граница? Перекодирование меняет байтовое представление того же символа (китайский знак в UTF-8 и в UTF-16 — один и тот же символ): семантика неизменна, круговой проход без потерь. Кодирование меняет форму предъявления байтов (двоичный поток становится печатными символами): семантика — «эквивалентная перевозка», а не «тот же символ». Мнемоника: вход и выход — две записи одного и того же? Тогда перекодирование; иначе — кодирование. Сцепка тоже обычна: двоичные данные → Base64 → UTF-16 в API Windows, каждый шаг сам по себе корректен.

28.4契约

  • Ёмкость выводима: Base64 4⌈n/3⌉, Hex 2n, Percent ≤ 3n; сперва расчёт, потом буфер.
  • Атомарность: нехватка буфера вызывающего — отказ целиком, полрезультата не пишется.
  • Два маршрута: Encode/Decode — в буфер вызывающего, EncodeNew/DecodeNew — владеющие продукты (xrtFree).
  • Явные варианты: URL-safe, регистр, множество экранирования — параметры; декодер строго сверяется с объявленным алфавитом.
  • Строгое декодирование: нелегальные символы, невыровненная длина, усечённое экранирование — отказ целиком.

От примеров к инженерии: три хозяина кодирования

Три способа крепления кодирования в инженерии. Выход протокола: сборка JWT, перенос двоичных данных в заголовках HTTP — кодирование на выходе, декодирование на входе, по одному разу с каждой стороны канала и никогда дважды (двойное кодирование — реальная авария: Percent дважды превращает %20 в %2520). Журнальные дампы: Hex — разово, коротко, человеку; в паре со строками формата главы 26 для пометки смещений. Персистентные поля: двоичные данные внутри JSON/конфигурационных файлов (в главе 31 вы увидите поддержку Base64 в JSON) — по одному кодированию при записи и чтении, семантика поля объявляется в схеме. Общая дисциплина трёх хозяев: кодирование — разовое пограничное действие, а не промежуточное состояние — «закодировали ещё раз» и «недокодировали» оба означают ошибку в дизайне процесса.

Таблица сравнения разрастания

100 байтов данныхBase64HexPercent (худший случай, всё экранировано)
Размер выхода136 (с заполнением 4⌈100/3⌉)200≤300
Каналпочта/заголовки данных/JSONдампы/отпечаткиURL/формы
Читаемостьнетхорошаяисходный текст сохранён

Ключевое напоминание таблицы: «минимальное разрастание» Percent выполняется только вне худшего случая — почти чистый ASCII расширяется около нуля, а полностью экранированные двоичные данные бьют худшую тройку. Выбрать Percent для двоичных данных — ошибка выбора (он придуман для немногих специальных символов внутри текста); семантика множества экранирования и определяет его применимость.

Типичные ошибки

Ловушка 1: принять Base64 за шифрование:Ловушка 1: принять Base64 за шифрование

Симптом: аудит безопасности обнаруживает, что «зашифрованное поле» — просто Base64; на пентесте токен декодируется в один шаг.

Причина: Base64 — кодирование, а не шифрование — обратимо любым, никакой конфиденциальности; вводящая в заблуждение сила «64» в имени — родня заблуждения «хеш не шифрование» (глава 12).

错误写法
char arrToken[64];
xrtBase64Encode(SecretKey, sizeof(SecretKey), arrToken, sizeof(arrToken), NULL, NULL);
Send(arrToken);   /* "секретность кодированием" - получатель декодирует одним шагом и берёт ключ */
正确写法
/* правильный канал для конфиденциальных данных: криптография тома 8 (шифрование) или система токенов (глава 11 SecureText) */
/* Base64 решает только "двоичные данные в текстовый канал", но не секретность */
Ловушка 2: стандартный Base64 прямо в URL:Ловушка 2: стандартный Base64 прямо в URL

Симптом: значение Base64 с + или /, помещённое в URL, изредка не декодируется на сервере или данные расползаются — только на определённых значениях.

Причина: + в строке запроса трактуется как пробел, / — разделитель пути — стандартный алфавит сталкивается с набором символов URL.

错误写法
xrtBase64Encode(Data, Size, arrUrl, sizeof(arrUrl), NULL, NULL);   /* в алфавите по умолчанию есть + / */
AppendQueryParam(Url, arrUrl);   /* + становится пробелом, / режет путь - декодирование обречено */
正确写法
xrtBase64Encode(Data, Size, arrUrl, sizeof(arrUrl), NULL, &tUrlSafe);   /* алфавит URL-safe: - и _ */
AppendQueryParam(Url, arrUrl);   /* все символы безопасны для URL */

Практические задания

Базовое: проверка формул ёмкости

Для входов в 1, 2, 3, 4, 9 и 10 байтов посчитайте длину выхода Base64 в столбик и проверьте кодированием (шесть пар чисел: сперва вручную, потом сверка); затем умышленно коротким буфером проверьте атомарный отказ и нетронутую выходную область.

Продвинутое: Hex-дампер

Реализуйте dump(视图, 前缀) (представление, префикс): вывод потока байтов в Hex, по 16 байтов на строку, смещение в начале строки — шестнадцатеричный вид отладчика это и есть он. Подсказка: смещения помечайте строками формата из главы 26 (hex в духе %06X).

Вызов: сборщик URL

Реализуйте url_encode_query(基准URL, 键值对数组) (базовый URL, массив пар ключ-значение): ключи и значения кодируются Percent по отдельности, строка запроса собирается построителем главы 25, на выходе — полный URL. Критерии приёмки: десять входов с китайскими ключами и значениями и &/=/пробелом/+ кодируются так, что стандартный парсер URL восстанавливает их верно; круговой проход декодирования совпадает байт в байт.

Итоги

ТемаКратко
ТройкаBase64 (разрастание 33%) / Hex (100%) / Percent (≈0%) — выбор по правилам канала
ЁмкостьBase64 4⌈n/3⌉, Hex 2n, Percent ≤3n — сперва расчёт, потом буфер
Атомарностьнехватка буфера — отказ целиком, полрезультата не пишется
Два маршрутавариант To в буфер / вариант New владеющий — изоморфно главам о контейнерах
ВариантыURL-safe (-/_), регистр Hex, множество экранирования Percent — явные параметры
Красная линиякодирование — не шифрование; конфиденциальное — через том 8 или SecureText
Три хозяинавыход протокола кодирует один раз / журнальные дампы / персистентные поля — кодирование есть пограничное действие, не промежуточное состояние
Граница с перекодированиемдве записи одного символа = перекодирование; двоичное в печатное = кодирование

📖 Справочники API в этой главе:codec (26 записей)

Полные сигнатуры, ограничения параметров, возвращаемые значения и ошибки