Notice
Recent Posts
Recent Comments
Link
«   2025/05   »
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
Archives
Today
Total
관리 메뉴

psj2867

punycode encoding/decoding 알고리즘(bootstring) - 2 본문

기타

punycode encoding/decoding 알고리즘(bootstring) - 2

psj2867 2023. 5. 9. 14:07

1. python 구현 코드
2. 코드 디버깅 추적 및 주석

1. python 구현 코드

예외 처리 코드는 이해를 위해 제외했습니다.
실제 사용하실 때는 python 기본 인코딩을 이용하시면 됩니다.
"a다b가다c".encode('idna').decode()

INITIAL_N = 0x80
INITIAL_BIAS = 72
BASE = 36
TMIN = 1
TMAX = 26
SKEW = 38
DAMP = 700
MAX_INT = 0x7FFFFFFF

def adapt(delta, num_points, first_time):
    delta //= 2 if not first_time else DAMP
    delta += delta // num_points
    k = 0
    while delta > ((BASE - TMIN) * TMAX) // 2:
        delta //= BASE - TMIN
        k += BASE
    return k + (((BASE - TMIN + 1) * delta) // (delta + SKEW))

def encode(inputs):
    # 초기 기본값들
    i, n, delta, bias = 0, 0x80, 0, 72
    base, TMIN, TMAX = 36, 1, 26
    outputs = []
    # digits = [a-z0-9]
    digits = [ chr(i) for i in range(ord("a"),ord("z")+1) ] + [ chr(i) for i in range(ord("0"),ord("9")+1) ] 
    input_codes = [ ord(i) for i in inputs] # "q다w나" -> [113, 45796, 119, 45208 ]
    input_length = len(inputs)

    isBasic = lambda _n : _n < 0x80
    basic_codes = [ chr(i) for i in input_codes if isBasic(i) ] # 나온 순서대로 기본 코드들
    outputs += basic_codes

    handle_count = basic_count = len(basic_codes)
    # handle_count 는 처리한 코드 수
    # basic_count 는 기본 코드 수

    if basic_count > 0 : # 기본 코드가 하나라도 있으면
        delimiter = "-"
        outputs += [delimiter] # delimiter 추가
        
    while handle_count < input_length : # input 에 있는 모든 코드를 처리할 때까지
        nextN = min(cp for cp in input_codes if cp >= n) # 현재까지 처리 안 한 코드 중 가장 작은 값, initalN = 0x80 은 기본 코드 최댓값 +1
        delta += ( nextN - n ) * ( handle_count + 1 ) # ( 현 처리 할 값 - 이전 n 값 ) * ( 현재 처리하고 있는 것, 기본 코드 포함 길이 )
        n = nextN # n은 현재 처리할 코드 값
        for code_point in input_codes:            
            if code_point < n:
                delta += 1 # n의 위치 
                # delta = 이전 delta + ( m - n ) * ( handle_count + 1 ) + code index - 이전 code index
            elif code_point == n: # 입력값에 같은 코드가 여러개 있을 수 있다.
                currDelta = delta
                k = base
                while True:
                    t = min(max(k - bias, TMIN),TMAX)
                    if currDelta < t:
                        break 
                    digit = (currDelta - t) % (base - t) + t 
                    outputs += [digits[digit]]
                    currDelta = (currDelta - t) // (base - t)
                    k += base
                digit = currDelta
                outputs += [ digits[digit] ]
                bias = adapt (delta, handle_count + 1, handle_count == basic_count );
                delta = 0
                handle_count += 1
        delta += 1
        n += 1
    return "".join(i for i in outputs)


def decode(input):
    # n - delta 의 n 값
    # i - delta 의 위치 값
    # out - out 의 길이    
    n, i, output_length, bias, base = 0x80, 0, 0, 72, 36
    input_length = len(input)
    output = []
    # digits = {'a':0,'b':1,...,'9':35} digit에 대응되는 value 모음
    digits = { chr(_i) : _idx for _idx, _i in enumerate(range(ord("a"),ord("z")+1)) } | { chr(_i) : _idx + 26 for _idx, _i in enumerate(range(ord("0"),ord("9")+1)) }

    output += input[0:input.rfind("-")] # 마지막 delimeter 이전의 코드 복사
    output_length = len(output)
    in_index = output_length+1 if output_length else 0 # 기본코드가 존재하면 delimeter 제외하고 다음 index
    while in_index < input_length:
        oldi = i
        weight = 1
        k = base
        # generalized variable-length representation 표현에 의해서 t 보다 작은 digit 까지 weight 곲해서 누적, Little endian
        while True:
            digit = digits[input[in_index]]
            in_index += 1
            i += digit * weight
            t = min(max(k - bias, TMIN),TMAX)            
            if digit < t: break          
            weight *= (base - t)
            k += base        
        bias = adapt(i - oldi, output_length + 1, oldi == 0)        
        # i = n * (out+1) + index
        n += i // (output_length+1)        
        
        i %= output_length+1
        print(n,i)
        output.insert(i,chr(n))

        i += 1
        output_length += 1

    return "".join(i for i in output)



2. 코드 디버깅 추적 및 주석

2.1 "a다b가다c"를 예시로 인코딩 코드 추적


input_codes=[97, 45796, 98, 44032, 45796, 99] # code point 변환
output = ["a", "b", "c", "-"]   # 0x80=128 미만 code 분리(handle_count=3, basic_count = 3)

## "가" 변환
initialN = 128보다 큰 code 중 가장 작은 값 m=44,032 선정 (delta = 0, n = 128, m = 44032)
delta += (m-n)*(handle_count+1) = (44032-128)*(3+1) = 175,616
n = m  # (m-n) 계산 했으니 임시 변수 m -> n 으로 (n = m = 44,032)
delta += 2 = 175618 # input_codes중 이전 n위치(처음 나온 n이기에 0) 이후의 n보다 작은 code 수, 디코딩 과정 중 삽입할 위치 ( delta = 175,618, q=delta=175,618 k = base=36, bias=72 ,t = 1 )

# Generalized variable-length integers 표현으로 변환, q < t 일 때까지 반복
# t = [1,1,26,26], w=[1,35,35,10]
t = min(max(k - bias, TMIN),TMAX)
digit = (q - t) % (base - t) + t = (175618-1)%(36-1) + 1 = 23
q = (q - t) // (base - t) = (175618-1)//(36-1) = 5017
k += base  # ( t = 1, q = 5017, k = 72 )
# 결과 = [23,12,33,11],  23*1 + 12*1*35 + 33*1*35*35 + 11*35*35*10 = 175618
output = ["a", "b", "c", "-", 23,12,33,11]
delta = 0
delta += 1 # n의 마지막 code 이후로 나오는 n 보다 작은 code 수, 44032 이후 [99] 하나

delta++, n++ # 단순화된 인코딩의 문제점 1번 out+1 보정을 위해 delta값 증가(=index 증가), 다음으로 자기보다 큰값 정할 n을 위해 증가

## "다" 변환
다음 현재 n=44,033 보다 크고 나머지 중 가장 작은 m=45,796 선정 (n = 44,033, m = 45,796, delta = 2)

delta += (m-n)*(handle_count+1) = (45796-44033)*(4+1) = 2 + 8815 = 8817
n = m  # (m-n) 계산 했으니 임시 변수 m -> n 으로 (n = m = 45,796)


## index 1 번 45796 변환
delta += 1 = 8818 # input_codes중 이전 n위치(처음 나온 n이기에 0) 이후의 n보다 작은 code 수
# delta 에 "ab가c" 의 "c", 회전 한번, "ab" 를 더해서 delta += 3 것과 같고 삽입할 위치
# 즉 delta = 8815 + 3 = 1763 * 5 + 3 = N * out + index
# ( delta = 8818, q=delta=8818, bias=32 ,t = 1 )
# Generalized variable-length integers 표현으로 변환, t=[4,26,26], w=[1,32,10,10]
# 결과 = [18,35,24],  18*1 + 35*1*32 + 24*1*32*10 = 8818
output = ["a", "b", "c", "-", 23,12,33,11, 18,35,24]
delta = 0


## index 5 번 45796 변환
delta += 2 = 2 = 0 * 6 + 2 # input_codes중 이전 n위치(index 1) 이후의 n보다 작은 code 수, [98, 44032] 둘 ( delta = 2, q=delta=2, bias=64 ,t = 1 )
# Generalized variable-length integers 표현으로 변환, t=[1,8], w= [1,35,28]
# 결과 = [2,0],  2*1 + 0*1*35 = 2
output = ["a", "b", "c", "-", 23,12,33,11, 18,35,24, 2,0]

#모든 code를 다 변환했으니 종료 후 output 을 [a-z0-9] 로 변환
output = "abc-xm7ls9yca" = ["a", "b", "c", "-", 23,12,33,11, 18,35,24, 2,0]

정리하면 delta 는 아래와 같고 이 값을 Generalized variable-length integers 로 변환한다.
delta = [ 0+2+(44032-128)*(3+1), 2+1+(45796-44033)*(4+1), 0+2+0 ] = [175618, 8818, 2]
# 이전 나머지 code + 현재 앞의 code + (m-n)*(handle_count+1)
즉, 역으로 계산하면 들어갈 위치와 m-n 으로 n 값을 얻을 수 있다.


2.2 "a다b가다c" -> "abc-xm7ls9yca" 를 예시로 디코딩 추적


output = ["a", "b", "c"] # 가장 뒤에 나오는 "-" 앞의 기본 코드들 복사
input = [ 23,12,33,11, 18,35,24, 2,0]  # code 로 변환
out = len(output)  # 진행중인 출력 길이, 이후 길이 변경 시마다 계산
i = 0 # 처음 시작은 0 index 부터

# Generalized variable-length integers 표현 변환
# (i=0, n=0x80=128, out=3, w=1, k=base=36, bias=72)
# bias 와 TMIN, TMAX, base 가 같으므로 encoding 과정의 t 와 w가 같다.
# t = [1,1,26,26], w=[1,35,35,10]
# [23,12,33,11, 18,35,24, 2,0] 에서 11부터 t 보다 작으므로 [23,12,33,11] 까지를 변환
i += 23*1 + 12*1*35 + 33*1*35*35 + 11*35*35*10 = 175618
# n, i 추출
n += i // (out+1) = 128 + 175618 // (3+1) = 44032 # 이전 n 값, 초기값 0x80=128
i = i % (out+1) = 0 + 175618 % (3+1) = 2 # (n=44032='가', i=2) # 이전 i 값, 초기값 0
output = ["a", "b", "가", "c"]
i++ # out+1 에 의한 보정값

# Generalized variable-length integers 표현 변환
# (i=3, n=44032, out=4, w=1, k=base=36, bias=32)
# t = [4,26,26], w=[1,32,10]
# [18,35,24, 2,0] 에서 24부터 t 보다 작으므로 [18,35,24] 까지를 변환
i += 18*1 + 35*1*32 + 24*1*32*10 = 3 + 8818 = 8821 
# n, i 추출
n += i // (out+1) = 44032 + 8821 // (4+1) = 45796
i = i % (out+1) = 8821 % (4+1) = ( 2 + 3 + 1 + (45796-44032)*5 ) % 5 = 1 # (n=45796='다', i=1)
#(이전 i 값 + i 차이값 + 보정값 + (N-prevN)*(out+1)) % *(out+1) = 1
output = ["a", "다", "b", "가", "c"]
i++

# (i=2, n=45796, out=5, w=1, k=base=36, bias=64)
# t = [1,8], w=[1,35]
# [2,0] 에서 11부터 t 보다 작으므로 [2,0] 까지를 변환
i += 2*1 + 0*1*35 = 2 + 2 = 4
# n, i 추출
n += i // (out+1) = 45796 + 4 // (5+1) = 45796
i = i % (out+1) = 4 % (5+1) = 4 # (n=45796='다', i=4)
output = ["a", "다", "b", "가", "다", "c"]

'기타' 카테고리의 다른 글

webrtc 간단한 설명  (0) 2023.11.16
printf에 관하여  (0) 2023.06.15
punycode encoding/decoding 알고리즘(bootstring)  (0) 2023.05.09
iptables 정리  (0) 2023.04.18
PLT와 GOT의 간단한 동작 구조  (0) 2023.03.28
Comments