Lowent 매뉴얼←↑→

codec — 16 진과 base64

소스
lib/codec.low
층
L0 — 순수 계산(호출자의 버퍼)
권한
없음

바이트를 16 진이나 base64 글자열로 바꾸고 되돌린다. 세상에는 텍스트만 통과시키는 자리가 많다 — 설정 파일 한 줄, 로그, URL, 메일 본문, 화면 출력. 그런 자리에 바이트를 그대로 넣으면 0 바이트나 제어 바이트가 끼어 통로가 망가진다. 16 진(바이트 하나를 0-9 a-f 두 글자로)과 base64(임의 바이트를 A-Z a-z 0-9 + / 64 가지 글자로)는 임의 바이트를 안전한 글자만으로 나르는 포장지다. 16 진은 사람이 읽기 좋지만 크기가 정확히 2 배가 되고, base64 는 읽기 어렵지만 약 4/3 배로 끝난다. 해시값을 눈으로 볼 때는 16 진, 덩어리를 실어 나를 때는 base64 가 보통이다.

use codec as c .

let n option u64 . be c.hex_enc "abc" dst .
guard is_some n . else return 1 .

dst 는 이 모듈이 만드는 것이 아니라 호출자가 미리 잡아 두는 출력 자리다(여기서는 6 바이트 이상). n 은 실제로 쓴 바이트 수이고 결과는 dst[0..n) 이다.

포장은 비밀을 지키지 않는다 — 누구나 되돌린다. 비밀이 필요하면 봉인하는 aead 를 쓴다(33장). 이 모듈은 바이트에서 바이트로 가는 순수 계산이라 index·set·shl·shr·bit_and·bit_or·guard·while 만 쓰고, 내장 연산은 하나도 늘지 않았다.

설계와 경계#

op필요한 dst 크기성공 반환
hex_enc2 × len srcsome (2 × len src)
hex_declen src / 2some (len src / 2)
b64_enc4 × ⌈len src / 3⌉some (4 × ⌈len src / 3⌉)
b64_dec3 × (len src / 4) − 패딩 수some <쓴 바이트 수>

표 50.1 — 필요한 출력 자리

dst 가 공식보다 작으면 결과는 늘 none 이고, 크면 상관없다 — 앞에서부터 쓰고 쓴 수를 돌려준다. 빈 입력은 성공이다(hex_enc ""·b64_enc ""·b64_dec "" 모두 some 0).

op 한눈에#

op시그니처안 될 때
hex_digitfn (v u64) → u8실패 없음(0 … 15 가 전제)
hex_valfn (c u8) → option u640-9 a-f A-F 밖이면 none
hex_encproc (src slice u8, dst mut slice u8) → option u64dst 부족이면 none
hex_decproc (src slice u8, dst mut slice u8) → option u64홀수 길이 · 16 진이 아닌 글자 · dst 부족이면 none
b64_digitfn (v u64) → u8실패 없음(0 … 63 이 전제)
b64_valfn (c u8) → option u64알파벳 밖이면 none(= 도 none)
b64_encproc (src slice u8, dst mut slice u8) → option u64dst 부족이면 none
b64_decproc (src slice u8, dst mut slice u8) → option u64길이가 4 의 배수가 아님 · 알파벳 밖 · 어긋난 패딩 · dst 부족이면 none

표 50.2 — codec 의 op

보통은 hex_enc·hex_dec·b64_enc·b64_dec 넷만 쓰고, 나머지 넷은 글자 하나짜리 부품이다.

op 상세#

모든 변환이 src(읽을 바이트)와 dst(쓸 자리)를 따로 받는다. 제자리 변환을 하지 않는 것은 인코딩이 결과를 키워서 원본을 덮으면 아직 읽지 않은 바이트를 밟기 때문이다. src 의 길이가 곧 입력의 전부다 — 끝 표식을 찾지 않고 len src 만 믿으므로, 큰 버퍼에 든 결과를 다시 넘길 때는 subslice 로 정확히 잘라 준다. 결과의 길이는 dst 의 길이가 아니라 반환된 some n 의 n 이다.

쓰는 법#

버퍼 크기를 먼저 확인하고, 변환하고, 돌려받은 개수로 잘라 쓴다.

module ex_codec .

use codec as c .

proc hex_roundtrip input enc mut slice u8 . . input dec mut slice u8 . . output u64 . effects none . do
  guard ge (len enc) 6 . else return 90 .
  guard ge (len dec) 3 . else return 91 .
  let en option u64 . be c.hex_enc "abc" enc .
  guard is_some en . else return 1 .
  guard eq (some_value en) 6 . else return 2 .
  rem 쓴 수가 곧 경계다 --- enc 통째로 넘기면 뒤의 쓰레기까지 입력이 된다
  let dn option u64 . be c.hex_dec (subslice enc 0 6) dec .
  guard is_some dn . else return 3 .
  guard eq (some_value dn) 3 . else return 4 .
  guard eq (index dec 0) 97 . else return 5 .
  rem 대문자도 받는다: "4A" → 74
  let up option u64 . be c.hex_dec "4A" dec .
  guard is_some up . else return 6 .
  guard eq (index dec 0) 74 . else return 7 .
  return 42 .
end

proc b64_roundtrip input enc mut slice u8 . . input dec mut slice u8 . . output u64 . effects none . do
  rem 2 바이트는 한 묶음(4 글자)이 되고 끝에 = 하나가 붙는다: "aGk="
  guard ge (len enc) 4 . else return 90 .
  guard ge (len dec) 2 . else return 91 .
  let e option u64 . be c.b64_enc "hi" enc .
  guard is_some e . else return 1 .
  guard eq (some_value e) 4 . else return 2 .
  let d option u64 . be c.b64_dec (subslice enc 0 4) dec .
  guard is_some d . else return 3 .
  guard eq (some_value d) 2 . else return 4 .
  return 42 .
end

반례#

모두 멈춤이 아니라 none 이다. 증상은 늘 “결과가 none 이고 guard 가 else 로 빠지는 것” 이다.

입력결과흔한 원인과 dst
c.hex_dec "abc" dstnone홀수 길이 — 길이 검사에서 먼저 걸려 dst 는 그대로
c.hex_dec "zz" dstnone16 진이 아닌 글자 — 훑다가 발견되므로 dst 앞부분이 덮였을 수 있다
c.hex_enc "abcdefgh" dst3none3 바이트 자리에 16 이 필요 — 한 바이트도 쓰지 않는다
c.b64_dec "abc" dstnone길이가 4 의 배수가 아님 — enc 를 subslice 로 자르지 않고 통째로 넘긴 경우가 흔하다
c.b64_dec "a?cd" dstnone알파벳 밖 — URL 에 안전한 -·_ 나 개행이 섞인 경우가 대부분
c.b64_dec "aG==YWJj" dstnone가운데 패딩 — 두 base64 문자열을 그냥 이어 붙이면 이 꼴이 된다

표 50.3 — 자주 틀리는 입력

주의#