◎ Part 2: 헤더 + 매크로 + 변환 함수들

// Base64 table
const char* base64_table =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

// ------------ 변환 함수들 ------------

// Base64 encode
bool encodeBase64(const char* input, char* output, size_t outSize) {
    // 입력 검증
    if (!input || !output || outSize == 0) {
        return false;
    }

    size_t len = strlen(input);
    size_t required = ((len + 2) / 3) * 4 + 1;  // +1 for null terminator

    // 버퍼 크기 검증
    if (outSize < required) {
        return false;
    }

    size_t outPos = 0;

    for (size_t i = 0; i < len; i += 3) {
        unsigned char b1 = (unsigned char)input[i];
        unsigned char b2 = (i + 1 < len) ? (unsigned char)input[i + 1] : 0;
        unsigned char b3 = (i + 2 < len) ? (unsigned char)input[i + 2] : 0;

        output[outPos++] = base64_table[(b1 >> 2) & 0x3F];
        output[outPos++] = base64_table[(((b1 & 0x03) << 4) | ((b2 >> 4) & 0x0F)) & 0x3F];
        output[outPos++] = (i + 1 < len)
            ? base64_table[(((b2 & 0x0F) << 2) | ((b3 >> 6) & 0x03)) & 0x3F]
            : '=';
        output[outPos++] = (i + 2 < len)
            ? base64_table[b3 & 0x3F]
            : '=';
    }

    output[outPos] = '\0';
    return true;
}

// Base64 decode
bool decodeBase64(const char* input, char* output, size_t outSize) {
    // Base64 디코딩 테이블 (signed char 사용)
    static const int8_t table[256] = {
        -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
        -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
        -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,62,-1,-1,-1,63,
        52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-1,-1,-1,
        -1, 0, 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,-1,-1,-1,-1,-1,
        -1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,
        41,42,43,44,45,46,47,48,49,50,51,-1,-1,-1,-1,-1,
        -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
        -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
        -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
        -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
        -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
        -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
        -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
        -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
    };

    // 입력 검증
    if (!input || !output || outSize == 0) {
        return false;
    }

    size_t len = strlen(input);
    size_t outPos = 0;
    unsigned int val = 0;
    int bits = -8;

    for (size_t i = 0; i < len; i++) {
        unsigned char c = (unsigned char)input[i];

        // 공백 문자 스킵
        if (c == ' ' || c == '\n' || c == '\r' || c == '\t') {
            continue;
        }

        // 패딩 문자 처리
        if (c == '=') {
            break;
        }

        // 디코딩 테이블 조회
        int8_t decoded = table[c];
        if (decoded == -1) {
            // 잘못된 Base64 문자
            return false;
        }

        val = (val << 6) | (unsigned int)decoded;
        bits += 6;

        if (bits >= 0) {
            // 버퍼 오버플로우 체크
            if (outPos >= outSize - 1) {
                return false;
            }
            output[outPos++] = (char)((val >> bits) & 0xFF);
            bits -= 8;
        }
    }

    output[outPos] = '\0';
    return true;
}

// Text to Hex
bool textToHex(const char* input, char* output, size_t outSize) {
    static const char hexDigits[] = "0123456789ABCDEF";

    if (!input || !output || outSize == 0) {
        return false;
    }

    size_t inputLen = strlen(input);

    if (inputLen == 0) {
        output[0] = '\0';
        return true;
    }

    // 필요한 크기: inputLen * 3 (마지막 공백 포함) + 1 (null)
    // 실제로는 inputLen * 3 - 1 + 1 = inputLen * 3
    if (outSize < inputLen * 3) {
        return false;
    }

    size_t outPos = 0;

    for (size_t i = 0; i < inputLen; i++) {
        unsigned char byte = (unsigned char)input[i];

        output[outPos++] = hexDigits[(byte >> 4) & 0x0F];  // 상위 4비트
        output[outPos++] = hexDigits[byte & 0x0F];          // 하위 4비트

        // 마지막 문자가 아니면 공백 추가
        if (i < inputLen - 1) {
            output[outPos++] = ' ';
        }
    }

    output[outPos] = '\0';
    return true;
}

// Hex to Text
bool hexToText(const char* input, char* output, size_t outSize) {
    if (!input || !output || outSize == 0) {
        return false;
    }

    size_t outPos = 0;
    size_t i = 0;

    while (input[i] != '\0' && outPos < outSize - 1) {
        // 공백 및 구분자 스킵
        while (input[i] == ' ' || input[i] == ':' || input[i] == '-' ||
            input[i] == '\t' || input[i] == '\n' || input[i] == '\r') {
            if (input[i] == '\0') break;
            i++;
        }

        if (input[i] == '\0') break;

        // 첫 번째 hex 문자 확인
        int high = hexCharToValue(input[i]);
        if (high == -1) {
            return false;
        }

        // 두 번째 hex 문자 확인 (범위 체크)
        if (input[i + 1] == '\0') {
            return false;  // 홀수 개의 hex 문자
        }

        int low = hexCharToValue(input[i + 1]);
        if (low == -1) {
            return false;
        }

        // 바이트 조합
        output[outPos++] = (char)((high << 4) | low);
        i += 2;
    }

    output[outPos] = '\0';
    return true;
}

// Text to Decimal
bool textToDec(const char* input, char* output, size_t outSize) {
    if (!input || !output || outSize == 0) {
        return false;
    }

    size_t inputLen = strlen(input);

    if (inputLen == 0) {
        output[0] = '\0';
        return true;
    }

    // 필요한 크기: 최대 4바이트 * inputLen
    if (outSize < inputLen * 4 + 1) {
        return false;
    }

    size_t outPos = 0;

    for (size_t i = 0; i < inputLen; i++) {
        unsigned char byte = (unsigned char)input[i];

        // 버퍼 체크 (최대 4바이트 필요: "255 ")
        if (outPos + 4 >= outSize) {
            return false;
        }

        // 숫자 변환
        size_t numLen = uintToStr(byte, output + outPos);
        outPos += numLen;

        // 마지막이 아니면 공백 추가
        if (i < inputLen - 1) {
            output[outPos++] = ' ';
        }
    }

    output[outPos] = '\0';
    return true;
}

// Decimal to Text
bool decToText(const char* input, char* output, size_t outSize) {
    if (!input || !output || outSize == 0) {
        return false;
    }

    size_t outPos = 0;
    size_t i = 0;

    while (input[i] != '\0') {
        // 숫자가 아닌 문자 스킵
        while (input[i] != '\0' && !isdigit((unsigned char)input[i])) {
            i++;
        }

        if (input[i] == '\0') break;

        // 숫자 파싱 (수동으로 안전하게)
        int num = 0;
        int digitCount = 0;

        while (isdigit((unsigned char)input[i])) {
            int digit = input[i] - '0';

            // 오버플로우 체크 (255 초과 방지)
            if (num > 25 || (num == 25 && digit > 5)) {
                return false;  // 255 초과
            }

            num = num * 10 + digit;
            digitCount++;
            i++;

            // 숫자가 너무 길면 에러 (최대 3자리)
            if (digitCount > 3) {
                return false;
            }
        }

        // 출력 버퍼 체크
        if (outPos >= outSize - 1) {
            return false;
        }

        output[outPos++] = (char)num;
    }

    output[outPos] = '\0';
    return true;
}

// Decimal to Hex
bool decToHex(const char* input, char* output, size_t outSize) {
    if (!input || !output || outSize == 0) {
        return false;
    }

    size_t outPos = 0;
    size_t i = 0;
    bool firstNumber = true;

    while (input[i] != '\0') {
        // 숫자가 아닌 문자 스킵
        while (input[i] != '\0' && !isdigit((unsigned char)input[i])) {
            i++;
        }

        if (input[i] == '\0') break;

        // 10진수 파싱
        unsigned long num = 0;
        int digitCount = 0;

        while (isdigit((unsigned char)input[i])) {
            int digit = input[i] - '0';

            // 오버플로우 체크 (UINT_MAX = 4294967295)
            if (num > (UINT_MAX - digit) / 10) {
                return false;
            }

            num = num * 10 + digit;
            digitCount++;
            i++;

            // 최대 10자리
            if (digitCount > 10) {
                return false;
            }
        }

        // 공백 추가 (첫 번째가 아닌 경우)
        if (!firstNumber) {
            if (outPos >= outSize - 1) {
                return false;
            }
            output[outPos++] = ' ';
        }

        // 16진수 변환 (최대 8자리 필요)
        if (outPos + 8 >= outSize) {
            return false;
        }

        size_t hexLen = uintToHex((unsigned int)num, output + outPos);
        outPos += hexLen;
        firstNumber = false;
    }

    output[outPos] = '\0';
    return true;
}

// Hex to Decimal
bool hexToDec(const char* input, char* output, size_t outSize) {
    if (!input || !output || outSize == 0) {
        return false;
    }

    size_t outPos = 0;
    size_t i = 0;
    bool firstNumber = true;

    while (input[i] != '\0') {
        // hex 숫자가 아닌 문자 스킵
        while (input[i] != '\0' && !isxdigit((unsigned char)input[i])) {
            i++;
        }

        if (input[i] == '\0') break;

        // hex 파싱
        unsigned long num = 0;
        int digitCount = 0;

        while (isxdigit((unsigned char)input[i])) {
            int val = hexCharToValue(input[i]);
            if (val == -1) {
                return false;
            }

            // 오버플로우 체크 (UINT_MAX = 0xFFFFFFFF)
            if (num > (UINT_MAX >> 4)) {
                return false;
            }

            num = (num << 4) | val;
            digitCount++;
            i++;

            // 최대 8자리 (32비트)
            if (digitCount > 8) {
                return false;
            }
        }

        // 공백 추가 (첫 번째가 아닌 경우)
        if (!firstNumber) {
            if (outPos >= outSize - 1) {
                return false;
            }
            output[outPos++] = ' ';
        }

        // 10진수 변환 (최대 10자리 필요)
        if (outPos + 10 >= outSize) {
            return false;
        }

        size_t decLen = uintToDecimal((unsigned int)num, output + outPos);
        outPos += decLen;
        firstNumber = false;
    }

    output[outPos] = '\0';
    return true;
}

// Decimal to Octal
bool decToOctal(const char* input, char* output, size_t outSize) {
    if (!input || !output || outSize == 0) {
        return false;
    }

    size_t outPos = 0;
    size_t i = 0;
    bool firstNumber = true;

    // 32비트 unsigned int는 8진수로 최대 11자리 필요 (예: 4294967295 -> 37777777777)
    const size_t MAX_OCTAL_DIGITS = 11;

    while (input[i] != '\0') {
        // 숫자가 아닌 문자 스킵
        while (input[i] != '\0' && !isdigit((unsigned char)input[i])) {
            i++;
        }

        if (input[i] == '\0') break;

        // 10진수 파싱
        unsigned long num = 0;
        int digitCount = 0;

        while (isdigit((unsigned char)input[i])) {
            int digit = input[i] - '0';

            // 오버플로우 체크 (unsigned int 범위: UINT_MAX)
            // unsigned long을 사용하여 파싱하더라도, 최종 변환은 unsigned int 범위 내에서만 처리합니다.
            // num > (UINT_MAX - digit) / 10 대신, num이 이미 UINT_MAX를 초과했는지 확인하는 것이 더 명확합니다.
            // 하지만 기존 코드의 로직을 유지하면서 UINT_MAX까지만 지원합니다.
            if (num > (UINT_MAX - digit) / 10) {
                // 숫자가 UINT_MAX (4294967295)를 초과합니다.
                return false;
            }

            num = num * 10 + digit;
            digitCount++;
            i++;

            // 최대 10자리 (UINT_MAX는 10자리)
            if (digitCount > 10) {
                // 10자리를 초과하면 UINT_MAX를 초과한 것으로 간주하여 실패 처리
                return false;
            }
        }

        // 10자리인데 값이 UINT_MAX를 초과하는 경우 (예: 4294967296)를 처리하기 위해
        // 위 오버플로우 체크가 중요합니다.

        // 공백 추가 (첫 번째가 아닌 경우)
        if (!firstNumber) {
            if (outPos >= outSize - 1) {
                return false; // 버퍼 부족
            }
            output[outPos++] = ' ';
        }

        // 8진수 변환 (최대 11자리 필요)
        if (outPos + MAX_OCTAL_DIGITS >= outSize) {
            return false; // 버퍼 부족
        }

        // 8진수 변환 및 길이 업데이트
        size_t octalLen = uintToOctal((unsigned int)num, output + outPos);
        if (octalLen == 0) {
            return false; // 변환 실패
        }

        outPos += octalLen;
        firstNumber = false;
    }

    output[outPos] = '\0';
    return true;
}

// Octal to Decimal
bool octalToDec(const char* input, char* output, size_t outSize) {
    if (!input || !output || outSize == 0) {
        return false;
    }

    size_t outPos = 0;
    size_t i = 0;
    bool firstNumber = true;

    // 32비트 unsigned int는 10진수로 최대 10자리 필요
    const size_t MAX_DECIMAL_DIGITS = 10;

    while (input[i] != '\0') {
        // 숫자가 아닌 문자 스킵
        while (input[i] != '\0' && !isdigit((unsigned char)input[i])) {
            i++;
        }

        if (input[i] == '\0') break;

        // 8진수 파싱 및 10진수로 변환
        unsigned long num = 0;

        // 8진수 숫자의 시작 위치 기억
        size_t octalStart = i;

        while (isdigit((unsigned char)input[i])) {
            int digit = input[i] - '0';

            // 8진수 유효성 검사 (숫자가 0~7 범위인지 확인)
            if (digit >= 8) {
                // '8' 또는 '9'는 8진수 숫자가 아님
                return false;
            }

            // 오버플로우 체크 (num * 8 + digit <= UINT_MAX)
            if (num > UINT_MAX / 8 || (num == UINT_MAX / 8 && digit > UINT_MAX % 8)) {
                // 결과가 unsigned int 범위를 초과합니다.
                return false;
            }

            num = num * 8 + digit;
            i++;
        }

        // 실제로 파싱된 숫자가 없는 경우 (예: "   ")는 위에서 이미 처리됨
        // 하지만 만약 숫자를 처리하지 못하고 루프를 빠져나왔다면 문제가 발생할 수 있지만, 
        // 위 로직은 isdigit을 통과한 후 while(isdigit)에서 파싱하므로 octalStart와 i가 같다면 
        // while(isdigit)의 첫 번째 문자가 8 또는 9였다는 뜻이며, 이는 이미 false를 반환합니다.

        // 공백 추가 (첫 번째 숫자가 아닌 경우)
        if (!firstNumber) {
            if (outPos >= outSize - 1) {
                return false; // 버퍼 부족
            }
            output[outPos++] = ' ';
        }

        // 10진수 변환 (최대 10자리 필요)
        if (outPos + MAX_DECIMAL_DIGITS >= outSize) {
            return false; // 버퍼 부족
        }

        // 10진수 변환 및 길이 업데이트
        size_t decLen = uintToDec((unsigned int)num, output + outPos);
        if (decLen == 0) {
            return false; // 변환 실패
        }

        outPos += decLen;
        firstNumber = false;
    }

    output[outPos] = '\0';
    return true;
}

// Text to UTF-7
bool textToUtf7(const char* input, char* output, size_t outSize) {
    if (!input || !output || outSize == 0) {
        return false;
    }

    size_t inputLen = strlen(input);
    size_t outPos = 0;
    size_t i = 0;
    const char* direct_chars = " '(),./";

    while (i < inputLen) {
        unsigned char current_char = (unsigned char)input[i];

        // 1. Direct Character
        if (isalnum(current_char) || strchr(direct_chars, current_char)) {
            if (outPos >= outSize - 1) return false;
            output[outPos++] = (char)current_char;
            i++;
        }
        // 2. '+' 특수 처리
        else if (current_char == '+') {
            if (outPos + 2 >= outSize) return false;
            output[outPos++] = '+';
            output[outPos++] = '-';
            i++;
        }
        // 3. Encoded Characters
        else {
            // UTF-16 Big Endian으로 변환
            unsigned char block_bytes[6];  // 최대 3문자 = 6바이트
            size_t block_byte_idx = 0;

            while (i < inputLen &&
                !isalnum((unsigned char)input[i]) &&
                !strchr(direct_chars, (unsigned char)input[i]) &&
                input[i] != '+' &&
                block_byte_idx < 6) {

                unsigned char c = (unsigned char)input[i];

                // UTF-16 Big Endian 변환
                block_bytes[block_byte_idx++] = 0x00;  // 상위 바이트 (ASCII는 0)
                block_bytes[block_byte_idx++] = c;     // 하위 바이트

                i++;
            }

            // '+' 마커
            if (outPos >= outSize - 1) return false;
            output[outPos++] = '+';

            // Base64 인코딩
            size_t b64_len = encode_base64_block(block_bytes, block_byte_idx,
                output + outPos, outSize - outPos);
            if (b64_len == 0) return false;
            outPos += b64_len;

            // '-' 마커
            if (outPos >= outSize - 1) return false;
            output[outPos++] = '-';
        }
    }

    output[outPos] = '\0';
    return true;
}

// UTF-7 to Text
bool utf7ToText(const char* input, char* output, size_t outSize) {
    if (!input || !output || outSize == 0) {
        return false;
    }

    size_t inputLen = strlen(input);
    size_t outPos = 0;
    size_t i = 0;

    // 디코딩된 UTF-16 바이트를 저장할 임시 버퍼 (충분히 크게 설정)
    unsigned char decoded_bytes[512];

    // Base64 블록 저장을 위한 임시 버퍼를 정적 크기로 선언
#define MAX_B64_BLOCK_SIZE 512
    char temp_b64_block[MAX_B64_BLOCK_SIZE];

    while (i < inputLen) {
        // ... (생략) ...

        // 1. Base64 인코딩 블록 시작 감지
        if (input[i] == '+') {
            i++; // '+' 건너뛰기

            // 1-1. "+-" 패턴 처리 (인코딩된 '+' 문자)
            if (input[i] == '-') {
                // ... (생략) ...
                output[outPos++] = '+';
                i++;
                continue;
            }

            // 1-2. Base64 블록 파싱 (다음 '-'가 나올 때까지)
            size_t block_start = i;
            size_t block_len = 0;

            // Base64 블록 추출
            while (i < inputLen && input[i] != '-') {
                if (block_len >= MAX_B64_BLOCK_SIZE - 1) {
                    return false; // 임시 버퍼 오버플로우 방지
                }
                i++;
                block_len++;
            }

            // 임시 버퍼에 Base64 문자열 복사
            // VLA 대신 정적 버퍼를 사용하고 block_len만큼 복사
            memcpy(temp_b64_block, input + block_start, block_len);
            temp_b64_block[block_len] = '\0'; // 널 문자 추가

            // Base64 디코딩 (결과는 UTF-16 바이트 스트림)
            size_t decoded_len = decode_base64_block(temp_b64_block, block_len, decoded_bytes, sizeof(decoded_bytes));

            if (decoded_len == 0 && block_len > 0) {
                return false; // 디코딩 실패
            }

            // UTF-16 바이트에서 ASCII 문자 추출 (0x00 XX 형태에서 XX만 추출)
            for (size_t j = 0; j < decoded_len; j++) {
                // 홀수 인덱스 (하위 바이트)만 유효한 ASCII 문자
                if (j % 2 == 1) {
                    if (outPos >= outSize - 1) return false;
                    output[outPos++] = (char)decoded_bytes[j];
                }
            }

            // 디코딩 후 '-'가 있다면 건너뛰기
            if (i < inputLen && input[i] == '-') {
                i++;
            }
        }
        // 2. 직접 표현 문자 처리
        else {
            if (outPos >= outSize - 1) return false;
            output[outPos++] = input[i];
            i++;
        }
    }

    output[outPos] = '\0';
    return true;
}

// Hex to UCS-2
bool hexToUcs2(const char* input, char* output, size_t outSize)
{
    if (!input || !output || outSize == 0) {
        return false;
    }

    size_t outPos = 0;
    size_t i = 0;

    static const char hexDigits[] = "0123456789ABCDEF";

    while (input[i] != '\0') {
        // 공백 및 구분자 스킵
        while (input[i] == ' ' || input[i] == ':' || input[i] == '-' ||
            input[i] == '\t' || input[i] == '\n' || input[i] == '\r') {
            if (input[i] == '\0') break;
            i++;
        }
        if (input[i] == '\0') break;

        // 첫 번째 hex 문자 (상위 nibble)
        int high = hexCharToValue(input[i]);
        if (high == -1) {
            return false;
        }

        // 두 번째 hex 문자 (하위 nibble)
        if (input[i + 1] == '\0') {
            return false; // 홀수 개의 hex 문자
        }
        int low = hexCharToValue(input[i + 1]);
        if (low == -1) {
            return false;
        }

        unsigned char byte = (unsigned char)((high << 4) | low);

        // UCS-2 Little Endian: 낮은 바이트 먼저, 그 다음 00
        // 출력 버퍼에 4자리 Hex로 기록: byte(Low) + 00(High)

        if (outPos + 4 >= outSize) {
            return false; // 버퍼 부족
        }

        // 낮은 바이트 (Little Endian)
        output[outPos++] = hexDigits[byte >> 4];
        output[outPos++] = hexDigits[byte & 0x0F];

        // 높은 바이트 (항상 00 for BMP)
        output[outPos++] = '0';
        output[outPos++] = '0';

        i += 2;
    }

    output[outPos] = '\0';
    return true;
}

// UCS-2 to Hex
bool ucs2ToHex(const char* input, char* output, size_t outSize)
{
    if (!input || !output || outSize == 0) {
        return false;
    }

    size_t outPos = 0;
    size_t i = 0;

    static const char hexDigits[] = "0123456789ABCDEF";

    while (input[i] != '\0') {
        // 공백 및 구분자 스킵
        while (input[i] == ' ' || input[i] == ':' || input[i] == '-' ||
            input[i] == '\t' || input[i] == '\n' || input[i] == '\r') {
            if (input[i] == '\0') break;
            i++;
        }
        if (input[i] == '\0') break;

        // UCS-2는 4자리 Hex 단위여야 함 (2바이트: Low + High)
        // 4자리 읽기

        int nibbles[4];
        for (int j = 0; j < 4; j++) {
            if (input[i] == '\0') {
                return false; // 4자리 미만으로 끝남
            }

            // 구분자 다시 스킵 (만약 중간에 있을 수 있음)
            while (input[i] == ' ' || input[i] == ':' || input[i] == '-' ||
                input[i] == '\t' || input[i] == '\n' || input[i] == '\r') {
                i++;
                if (input[i] == '\0') return false;
            }

            nibbles[j] = hexCharToValue(input[i]);
            if (nibbles[j] == -1) {
                return false; // 유효하지 않은 hex 문자
            }
            i++;
        }

        // Little Endian: 낮은 바이트가 먼저 옴
        // nibbles[0..1]: Low byte, nibbles[2..3]: High byte
        unsigned char lowByte = (unsigned char)((nibbles[0] << 4) | nibbles[1]);
        unsigned char highByte = (unsigned char)((nibbles[2] << 4) | nibbles[3]);

        // BMP 범위에서는 highByte가 00이어야 정상 (U+0000 ~ U+FFFF)
        // 프로그램의 기존 용도상 highByte가 00이 아닌 경우도 허용할지 결정
        // 여기서는 경고 없이 lowByte만 사용 (대부분의 용례가 ASCII이기 때문)
        // 필요시 아래 주석 해제하여 엄격히 검사 가능
        // if (highByte != 0) return false;  // 서러게이트나 비BMP 문자 거부

        // 출력: lowByte만 Hex로 변환 (2자리)
        if (outPos + 2 >= outSize) {
            return false; // 버퍼 부족
        }

        output[outPos++] = hexDigits[lowByte >> 4];
        output[outPos++] = hexDigits[lowByte & 0x0F];
    }

    output[outPos] = '\0';
    return true;
}


// Text to Binary
bool textToBinary(const char* input, char* output, size_t outSize) {
    if (!input || !output || outSize == 0) {
        return false;
    }

    size_t inputLen = strlen(input);

    if (inputLen == 0) {
        output[0] = '\0';
        return true;
    }

    size_t required = inputLen * 9;
    if (outSize < required) {
        return false;
    }

    size_t outPos = 0;

    for (size_t i = 0; i < inputLen; i++) {
        unsigned char byte = (unsigned char)input[i];

        // 비트마스크로 직접 변환
        output[outPos++] = (byte & 0x80) ? '1' : '0';
        output[outPos++] = (byte & 0x40) ? '1' : '0';
        output[outPos++] = (byte & 0x20) ? '1' : '0';
        output[outPos++] = (byte & 0x10) ? '1' : '0';
        output[outPos++] = (byte & 0x08) ? '1' : '0';
        output[outPos++] = (byte & 0x04) ? '1' : '0';
        output[outPos++] = (byte & 0x02) ? '1' : '0';
        output[outPos++] = (byte & 0x01) ? '1' : '0';

        // 마지막이 아니면 공백
        if (i < inputLen - 1) {
            output[outPos++] = ' ';
        }
    }

    output[outPos] = '\0';
    return true;
}

// Binary to Text
bool binaryToText(const char* input, char* output, size_t outSize) {
    if (!input || !output || outSize == 0) {
        return false;
    }

    char temp[9];
    int tempIdx = 0;
    size_t outPos = 0;

    for (size_t i = 0; input[i] != '\0'; i++) {
        if (input[i] == '0' || input[i] == '1') {
            if (tempIdx >= 8) {
                // 8비트 초과
                return false;
            }
            temp[tempIdx++] = input[i];

            if (tempIdx == 8) {
                temp[8] = '\0';

                unsigned char byte;
                if (!parseBinaryByte(temp, &byte)) {
                    return false;
                }

                if (outPos >= outSize - 1) {
                    return false;
                }

                output[outPos++] = (char)byte;
                tempIdx = 0;
            }
        }
        else if (input[i] == ' ' || input[i] == '\t' ||
            input[i] == '\n' || input[i] == '\r') {
            // 공백 허용
            continue;
        }
        else {
            // 잘못된 문자
            return false;
        }
    }

    // 남은 비트 체크
    if (tempIdx != 0) {
        // 불완전한 바이트
        return false;
    }

    output[outPos] = '\0';
    return true;
}

// Escape
bool escapeString(const char* input, char* output, size_t outSize) {
    if (!input || !output || outSize == 0) {
        return false;
    }

    size_t inputLen = strlen(input);
    size_t required = inputLen * 6 + 1;  // 각 문자당 \uXXXX (6바이트)

    if (outSize < required) {
        return false;
    }

    size_t outPos = 0;

    for (size_t i = 0; i < inputLen; i++) {
        unsigned char c = (unsigned char)input[i];
        snprintf(output + outPos, outSize - outPos, "\\u%04x", c);
        outPos += 6;
    }

    output[outPos] = '\0';
    return true;
}

// Unescape
bool unescapeString(const char* input, char* output, size_t outSize) {
    if (!input || !output || outSize == 0) {
        return false;
    }

    size_t outPos = 0;

    for (size_t i = 0; input[i] != '\0'; i++) {
        if (input[i] == '\\') {
            if (input[i + 1] == '\0') {
                return false;
            }

            i++;

            switch (input[i]) {
            case 'n':
                if (outPos >= outSize - 1) return false;
                output[outPos++] = '\n';
                break;
            case 'r':
                if (outPos >= outSize - 1) return false;
                output[outPos++] = '\r';
                break;
            case 't':
                if (outPos >= outSize - 1) return false;
                output[outPos++] = '\t';
                break;
            case '\\':
                if (outPos >= outSize - 1) return false;
                output[outPos++] = '\\';
                break;
            case '\"':
                if (outPos >= outSize - 1) return false;
                output[outPos++] = '\"';
                break;
            case '\'':
                if (outPos >= outSize - 1) return false;
                output[outPos++] = '\'';
                break;
            case 'b':
                if (outPos >= outSize - 1) return false;
                output[outPos++] = '\b';
                break;
            case 'f':
                if (outPos >= outSize - 1) return false;
                output[outPos++] = '\f';
                break;
            case '0':
                if (outPos >= outSize - 1) return false;
                output[outPos++] = '\0';
                break;

            case 'u': {
                // \uXXXX
                if (input[i + 1] == '\0' || input[i + 2] == '\0' ||
                    input[i + 3] == '\0' || input[i + 4] == '\0') {
                    return false;
                }

                int d1 = hexCharToValue(input[i + 1]);
                int d2 = hexCharToValue(input[i + 2]);
                int d3 = hexCharToValue(input[i + 3]);
                int d4 = hexCharToValue(input[i + 4]);

                if (d1 < 0 || d2 < 0 || d3 < 0 || d4 < 0) {
                    return false;
                }

                unsigned int codepoint = (d1 << 12) | (d2 << 8) | (d3 << 4) | d4;

                // UTF-8로 인코딩 (최대 4바이트)
                char utf8[4];
                size_t utf8Len = encodeUTF8(codepoint, utf8);

                if (utf8Len == 0 || outPos + utf8Len > outSize - 1) {
                    return false;
                }

                for (size_t j = 0; j < utf8Len; j++) {
                    output[outPos++] = utf8[j];
                }

                i += 4;
                break;
            }

            case 'x': {
                // \xXX
                if (input[i + 1] == '\0' || input[i + 2] == '\0') {
                    return false;
                }

                int h1 = hexCharToValue(input[i + 1]);
                int h2 = hexCharToValue(input[i + 2]);

                if (h1 < 0 || h2 < 0) {
                    return false;
                }

                if (outPos >= outSize - 1) return false;
                output[outPos++] = (char)((h1 << 4) | h2);
                i += 2;
                break;
            }

            default:
                return false;
            }
        }
        else {
            if (outPos >= outSize - 1) return false;
            output[outPos++] = input[i];
        }
    }

    output[outPos] = '\0';
    return true;
}

// HTML Encode
bool encodeHTML(const char* input, char* output, size_t outSize) {
    if (!input || !output || outSize == 0) {
        return false;
    }

    size_t outPos = 0;

    for (size_t i = 0; input[i] != '\0'; i++) {
        const char* entity = NULL;
        size_t entityLen = 0;
        unsigned char c = (unsigned char)input[i];

        // HTML 엔티티 매핑
        switch (c) {
        case '&':  entity = "&";   entityLen = 5; break;
        case '<':  entity = "&lt;";    entityLen = 4; break;
        case '>':  entity = "&gt;";    entityLen = 4; break;
        case '"':  entity = "&quot;";  entityLen = 6; break;
        case '\'': entity = "&#39;";   entityLen = 5; break;
            // 추가 엔티티
        case '\n': entity = "&#10;";   entityLen = 5; break;
        case '\r': entity = "&#13;";   entityLen = 5; break;
        default:
            // 출력 불가능한 문자는 숫자 엔티티로
            if (c < 32 || c == 127) {
                // &#XX; 형식 (최대 6바이트)
                if (outPos + 6 >= outSize) return false;
                int written = snprintf(output + outPos, outSize - outPos,
                    "&#%u;", c);
                if (written < 0 || (size_t)written >= outSize - outPos) {
                    return false;
                }
                outPos += written;
                continue;
            }
            // 일반 문자
            if (outPos >= outSize - 1) return false;
            output[outPos++] = c;
            continue;
        }

        // 엔티티 쓰기
        if (outPos + entityLen >= outSize) {
            return false;
        }

        memcpy(output + outPos, entity, entityLen);
        outPos += entityLen;
    }

    output[outPos] = '\0';
    return true;
}

// HTML Decode
bool decodeHTML(const char* input, char* output, size_t outSize) {
    if (!input || !output || outSize == 0) {
        return false;
    }

    size_t outPos = 0;
    size_t inputLen = strlen(input);

    for (size_t i = 0; i < inputLen; i++) {
        if (outPos >= outSize - 1) {
            return false;
        }

        if (input[i] == '&') {
            size_t remaining = inputLen - i;
            bool matched = false;

            // 각 엔티티의 최소 길이 체크 후 비교
            if (remaining >= 5 && memcmp(input + i, "&", 5) == 0) {
                output[outPos++] = '&';
                i += 4;
                matched = true;
            }
            else if (remaining >= 4 && memcmp(input + i, "&lt;", 4) == 0) {
                output[outPos++] = '<';
                i += 3;
                matched = true;
            }
            else if (remaining >= 4 && memcmp(input + i, "&gt;", 4) == 0) {
                output[outPos++] = '>';
                i += 3;
                matched = true;
            }
            else if (remaining >= 6 && memcmp(input + i, "&quot;", 6) == 0) {
                output[outPos++] = '"';
                i += 5;
                matched = true;
            }
            else if (remaining >= 5 && memcmp(input + i, "&#39;", 5) == 0) {
                output[outPos++] = '\'';
                i += 4;
                matched = true;
            }

            if (!matched) {
                output[outPos++] = input[i];
            }
        }
        else {
            output[outPos++] = input[i];
        }
    }

    output[outPos] = '\0';
    return true;
}

// HexToBase64
bool hexToBase64(const char* input, char* output, size_t outSize)
{
    if (!input || !output || outSize == 0) {
        return false;
    }

    unsigned char binary[MAX_BUFFER];
    size_t binPos = 0;
    size_t i = 0;

    // 1. Hex → binary
    while (input[i] != '\0' && binPos < MAX_BUFFER) {
        // 구분자 스킵
        while (input[i] == ' ' || input[i] == ':' || input[i] == '-' ||
            input[i] == '\t' || input[i] == '\n' || input[i] == '\r') {
            i++;
        }
        if (input[i] == '\0') break;

        int high = hexCharToValue(input[i++]);
        if (high == -1) return false;

        if (input[i] == '\0') return false;  // 홀수 hex
        int low = hexCharToValue(input[i++]);
        if (low == -1) return false;

        binary[binPos++] = (unsigned char)((high << 4) | low);
    }

    if (binPos == 0) {
        output[0] = '\0';
        return true;
    }

    static const char base64_chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

    size_t outPos = 0;
    size_t pos = 0;

    // 2. binary → Base64 (3바이트씩 처리)
    while (pos < binPos) {
        size_t remaining = binPos - pos;  // 남은 바이트 수 (1~3)

        unsigned char b1 = binary[pos++];
        unsigned char b2 = (remaining >= 2) ? binary[pos++] : 0;
        unsigned char b3 = (remaining >= 3) ? binary[pos++] : 0;

        unsigned int block = (b1 << 16) | (b2 << 8) | b3;

        if (outPos + 4 >= outSize) return false;

        output[outPos++] = base64_chars[(block >> 18) & 0x3F];
        output[outPos++] = base64_chars[(block >> 12) & 0x3F];
        output[outPos++] = (remaining >= 2) ? base64_chars[(block >> 6) & 0x3F] : '=';
        output[outPos++] = (remaining >= 3) ? base64_chars[block & 0x3F] : '=';
    }

    output[outPos] = '\0';
    return true;
}

// Base64ToHex
bool base64ToHex(const char* input, char* output, size_t outSize)
{
    if (!input || !output || outSize == 0) {
        return false;
    }

    // Base64 디코딩을 위한 임시 바이너리 버퍼
    unsigned char binary[MAX_BUFFER];
    size_t binPos = 0;

    size_t i = 0;
    while (input[i] != '\0') {
        // 4개 문자씩 하나의 블록 처리
        char block[4];
        int val[4];
        int valCount = 0;

        // Base64 문자 4개 읽기 (패딩 = 허용)
        for (int j = 0; j < 4; j++) {
            if (input[i] == '\0') {
                return false; // 블록 중간에 끝나면 오류
            }

            char c = input[i++];
            if (c == '=') {
                block[j] = '=';
                val[j] = -1; // 패딩 표시
            }
            else {
                int v = base64_char_to_value(c);
                if (v == -1) {
                    return false; // 유효하지 않은 Base64 문자
                }
                block[j] = c;
                val[j] = v;
                valCount++;
            }
        }

        // 패딩 처리: 마지막 블록에서 = 개수 확인
        if (block[3] == '=') {
            if (block[2] != '=') return false; // 잘못된 패딩
            if (valCount != 2) return false;
        }
        else if (block[2] == '=') {
            if (valCount != 3) return false;
        }
        else {
            if (valCount != 4) return false;
        }

        // 24비트 블록 구성
        unsigned int bits = 0;
        if (valCount >= 1) bits |= (val[0] << 18);
        if (valCount >= 2) bits |= (val[1] << 12);
        if (valCount >= 3) bits |= (val[2] << 6);
        if (valCount >= 4) bits |= (val[3]);

        // 출력 바이트 수 계산
        int byteCount = (valCount * 6) / 8; // 2 -> 1바이트, 3 -> 2바이트, 4 -> 3바이트

        // 바이너리 버퍼에 저장
        if (byteCount >= 1) {
            if (binPos >= MAX_BUFFER) return false;
            binary[binPos++] = (unsigned char)((bits >> 16) & 0xFF);
        }
        if (byteCount >= 2) {
            if (binPos >= MAX_BUFFER) return false;
            binary[binPos++] = (unsigned char)((bits >> 8) & 0xFF);
        }
        if (byteCount >= 3) {
            if (binPos >= MAX_BUFFER) return false;
            binary[binPos++] = (unsigned char)(bits & 0xFF);
        }
    }

    // 바이너리를 Hex 문자열로 변환
    size_t outPos = 0;
    static const char hexDigits[] = "0123456789ABCDEF";

    for (size_t k = 0; k < binPos; k++) {
        if (outPos + 2 >= outSize) {
            return false; // 출력 버퍼 부족
        }
        unsigned char byte = binary[k];
        output[outPos++] = hexDigits[byte >> 4];
        output[outPos++] = hexDigits[byte & 0x0F];
    }

    output[outPos] = '\0';
    return true;
}

//IP to Dec
/**
 * @brief IPv4 주소(A.B.C.D)를 32비트 정수 형태의 10진수 문자열로 변환합니다.
 * @param input "192.168.0.1" 형식의 문자열
 * @param output 결과 10진수 문자열을 저장할 버퍼
 * @param outSize 출력 버퍼의 크기
 * @return 성공 시 true, 유효하지 않은 IP 형식일 경우 false
 */
bool ipToDec(const char* input, char* output, size_t outSize) {
    if (!input || !output || outSize == 0) {
        return false;
    }

    size_t i = 0;
    unsigned int octets[4] = { 0 }; // A, B, C, D 각 마디 저장
    int octetCount = 0;

    // 1. IP 주소 파싱 (4개의 마디 추출)
    while (octetCount < 4) {
        // 숫자가 나올 때까지 스킵 (점 '.' 이나 공백 등)
        while (input[i] != '\0' && !isdigit((unsigned char)input[i])) {
            i++;
        }

        if (input[i] == '\0') break;

        unsigned int val = 0;
        int digitCount = 0;

        // 마디 숫자 읽기 (0~255)
        while (isdigit((unsigned char)input[i])) {
            val = val * 10 + (input[i] - '0');
            digitCount++;
            i++;

            // 한 마디가 255를 넘거나 3자리를 초과하면 유효하지 않음
            if (val > 255 || digitCount > 3) {
                return false;
            }
        }

        octets[octetCount++] = val;
    }

    // 정확히 4마디가 파싱되었는지 확인
    if (octetCount != 4) {
        return false;
    }

    // 2. 32비트 정수로 결합
    // 공식: (A << 24) | (B << 16) | (C << 8) | D
    unsigned int fullIP = (octets[0] << 24) | (octets[1] << 16) | (octets[2] << 8) | octets[3];

    // 3. 결합된 정수를 10진수 문자열로 변환
    // sprintf를 사용하여 안전하게 문자열로 변환합니다.
    // 32비트 무부호 정수의 최대값은 4,294,967,295로 최대 10자리 + 널 문자 필요
    if (outSize < 11) {
        return false;
    }

    // sprintf_s 또는 _snprintf를 사용하는 것이 안전하지만 표준 sprintf로 작성
    int len = sprintf(output, "%u", fullIP);

    return (len > 0);
}

//Dec to IP
/**
 * @brief 10진수 문자열 형태의 정수를 IPv4 주소(A.B.C.D) 형식으로 변환합니다.
 * @param input "3232235521" 형식의 10진수 문자열
 * @param output 결과 IPv4 주소를 저장할 버퍼 (최소 16바이트 권장)
 * @param outSize 출력 버퍼의 크기
 * @return 성공 시 true, 유효하지 않은 숫자이거나 버퍼 부족 시 false
 */
bool decToIp(const char* input, char* output, size_t outSize) {
    if (!input || !output || outSize == 0) {
        return false;
    }

    // 1. 입력 10진수 문자열을 숫자(unsigned int)로 파싱
    unsigned long long tempNum = 0; // 오버플로우 체크를 위해 더 큰 타입 사용
    int i = 0;

    // 숫자가 아닌 공백 등 스킵
    while (input[i] != '\0' && !isdigit((unsigned char)input[i])) {
        i++;
    }

    if (input[i] == '\0') return false;

    while (isdigit((unsigned char)input[i])) {
        tempNum = tempNum * 10 + (input[i] - '0');

        // IPv4 최대값 4,294,967,295 (0xFFFFFFFF) 체크
        if (tempNum > 4294967295ULL) {
            return false;
        }
        i++;
    }

    unsigned int fullIP = (unsigned int)tempNum;

    // 2. 32비트 정수에서 각 8비트 마디 추출
    unsigned char a = (unsigned char)((fullIP >> 24) & 0xFF);
    unsigned char b = (unsigned char)((fullIP >> 16) & 0xFF);
    unsigned char c = (unsigned char)((fullIP >> 8) & 0xFF);
    unsigned char d = (unsigned char)(fullIP & 0xFF);

    // 3. 마디를 "A.B.C.D" 형식의 문자열로 조립
    // IPv4 문자열의 최대 길이는 "255.255.255.255" (15자 + 널 문자 = 16바이트)
    if (outSize < 16) {
        return false;
    }

    int len = sprintf(output, "%u.%u.%u.%u", a, b, c, d);

    return (len > 0);
}


//IP to Hex (앞에 0x 붙여서 표현)
/**
 * @brief IPv4 주소(A.B.C.D)를 "0x" 접두사가 붙은 8자리 16진수 문자열로 변환합니다.
 * @param input "192.168.0.1" 형식의 문자열
 * @param output 결과 16진수 문자열을 저장할 버퍼 (최소 11바이트: 0x + 8자리 + \0)
 * @param outSize 출력 버퍼의 크기
 * @return 성공 시 true, 유효하지 않은 형식일 경우 false
 */
bool ipToHex(const char* input, char* output, size_t outSize) {
    if (!input || !output || outSize == 0) {
        return false;
    }

    size_t i = 0;
    unsigned int octets[4] = { 0 };
    int octetCount = 0;

    // 1. IP 주소 파싱 (A.B.C.D 추출)
    while (octetCount < 4) {
        // 숫자가 나올 때까지 점('.')이나 공백 무시
        while (input[i] != '\0' && !isdigit((unsigned char)input[i])) {
            i++;
        }

        if (input[i] == '\0') break;

        unsigned int val = 0;
        int digitCount = 0;

        while (isdigit((unsigned char)input[i])) {
            val = val * 10 + (input[i] - '0');
            digitCount++;
            i++;

            // 한 마디가 255를 초과하거나 3자리를 넘으면 오류
            if (val > 255 || digitCount > 3) {
                return false;
            }
        }
        octets[octetCount++] = val;
    }

    // 정확히 4개 마디가 파싱되지 않았다면 실패
    if (octetCount != 4) {
        return false;
    }

    // 2. 32비트 정수로 결합
    unsigned int fullIP = (octets[0] << 24) | (octets[1] << 16) | (octets[2] << 8) | octets[3];

    // 3. 16진수 문자열로 변환 (0x 접두사 포함)
    // "0x" (2) + "XXXXXXXX" (8) + "\0" (1) = 총 11바이트 필요
    if (outSize < 11) {
        return false;
    }

    // %08X: 8자리 고정, 대문자 16진수, 빈 자리는 0으로 채움
    int len = sprintf(output, "0x%08X", fullIP);

    return (len > 0);
}


//Hex to IP
/**
 * @brief 16진수 문자열(0x... 또는 ...)을 IPv4 주소(A.B.C.D) 형식으로 변환합니다.
 * @param input "0xC0A80001" 또는 "C0A80001" 형식의 문자열
 * @param output 결과 IPv4 주소를 저장할 버퍼 (최소 16바이트 권장)
 * @param outSize 출력 버퍼의 크기
 * @return 성공 시 true, 유효하지 않은 형식일 경우 false
 */
bool hexToIp(const char* input, char* output, size_t outSize) {
    if (!input || !output || outSize == 0) {
        return false;
    }

    size_t i = 0;
    unsigned int fullIP = 0;
    int hexCount = 0;

    // 1. 접두사 "0x" 또는 "0X"가 있다면 건너뛰기
    if (input[i] == '0' && (input[i + 1] == 'x' || input[i + 1] == 'X')) {
        i += 2;
    }

    // 2. 16진수 문자열을 32비트 정수로 변환
    while (input[i] != '\0' && hexCount < 8) {
        char c = input[i];
        int val = 0;

        if (c >= '0' && c <= '9') {
            val = c - '0';
        }
        else if (c >= 'a' && c <= 'f') {
            val = c - 'a' + 10;
        }
        else if (c >= 'A' && c <= 'F') {
            val = c - 'A' + 10;
        }
        else if (isspace((unsigned char)c)) {
            // 공백은 무시하고 다음 문자로
            i++;
            continue;
        }
        else {
            // 16진수 이외의 문자가 나오면 오류
            return false;
        }

        fullIP = (fullIP << 4) | (unsigned int)val;
        hexCount++;
        i++;
    }

    // 8자리 16진수가 아니면 유효하지 않은 IP로 간주 (상황에 따라 조절 가능)
    if (hexCount != 8) {
        return false;
    }

    // 3. 32비트 정수에서 각 8비트 마디 추출
    unsigned char a = (unsigned char)((fullIP >> 24) & 0xFF);
    unsigned char b = (unsigned char)((fullIP >> 16) & 0xFF);
    unsigned char c = (unsigned char)((fullIP >> 8) & 0xFF);
    unsigned char d = (unsigned char)(fullIP & 0xFF);

    // 4. "A.B.C.D" 형식으로 출력 버퍼에 저장
    if (outSize < 16) {
        return false;
    }

    int len = sprintf(output, "%u.%u.%u.%u", a, b, c, d);

    return (len > 0);
}


//Text to URL
/**
 * @brief 일반 텍스트를 URL 인코딩(Percent-Encoding) 형식으로 변환합니다.
 * @param input 변환할 원본 문자열
 * @param output 인코딩된 결과를 저장할 버퍼
 * @param outSize 출력 버퍼의 크기
 * @return 성공 시 true, 버퍼 부족 시 false
 */
bool textToUrl(const char* input, char* output, size_t outSize) {
    if (!input || !output || outSize == 0) {
        return false;
    }

    size_t i = 0;
    size_t outPos = 0;

    while (input[i] != '\0') {
        unsigned char c = (unsigned char)input[i];

        // RFC 3986 기준: 인코딩하지 않아도 되는 안전한 문자들
        // 알파벳, 숫자, 그리고 - _ . ~
        if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') {
            if (outPos + 1 >= outSize) return false;
            output[outPos++] = (char)c;
        }
        else {
            // 그 외의 문자는 %XX 형식으로 변환 (3바이트 필요)
            if (outPos + 3 >= outSize) return false;

            // sprintf_s 사용 (Visual Studio 권장)
#ifdef _MSC_VER
            sprintf_s(output + outPos, outSize - outPos, "%%%02X", c);
#else
            snprintf(output + outPos, outSize - outPos, "%%%02X", c);
#endif

            outPos += 3;
        }
        i++;
    }

    // 널 문자 종료
    if (outPos >= outSize) return false;
    output[outPos] = '\0';

    return true;
}


//URL to Text
/**
 * @brief URL 인코딩된 문자열(%XX)을 원본 텍스트로 복원합니다.
 * @param input URL 인코딩된 문자열 (예: "Hello%20World")
 * @param output 디코딩된 결과를 저장할 버퍼
 * @param outSize 출력 버퍼의 크기
 * @return 성공 시 true, 형식 오류나 버퍼 부족 시 false
 */
bool urlToText(const char* input, char* output, size_t outSize) {
    if (!input || !output || outSize == 0) {
        return false;
    }

    size_t i = 0;
    size_t outPos = 0;

    while (input[i] != '\0') {
        if (outPos + 1 >= outSize) return false;

        if (input[i] == '%') {
            // % 뒤에 최소 2글자가 더 있어야 함
            if (input[i + 1] != '\0' && input[i + 2] != '\0') {
                int hi = 0, lo = 0;

                // 첫 번째 16진수 문자 변환
                char c1 = input[i + 1];
                if (isdigit((unsigned char)c1)) hi = c1 - '0';
                else if (c1 >= 'a' && c1 <= 'f') hi = c1 - 'a' + 10;
                else if (c1 >= 'A' && c1 <= 'F') hi = c1 - 'A' + 10;
                else return false; // 유효하지 않은 Hex

                // 두 번째 16진수 문자 변환
                char c2 = input[i + 2];
                if (isdigit((unsigned char)c2)) lo = c2 - '0';
                else if (c2 >= 'a' && c2 <= 'f') lo = c2 - 'a' + 10;
                else if (c2 >= 'A' && c2 <= 'F') lo = c2 - 'A' + 10;
                else return false; // 유효하지 않은 Hex

                // 두 값을 합쳐서 하나의 바이트로 복원
                output[outPos++] = (char)((hi << 4) | lo);
                i += 3; // %XX 세 글자를 처리했으므로 3칸 이동
            }
            else {
                return false; // % 뒤에 문자가 부족함
            }
        }
        else if (input[i] == '+') {
            // 일부 구형 규격에서 공백을 '+'로 인코딩하는 경우 처리
            output[outPos++] = ' ';
            i++;
        }
        else {
            // 일반 문자는 그대로 복사
            output[outPos++] = input[i];
            i++;
        }
    }

    output[outPos] = '\0';
    return true;
}

// Clipboard helper
bool copyToClipboard(const char* text) {
    if (!text) {
        return false;
    }

    size_t len = strlen(text);
    if (len == 0) {
        return false;
    }

    // ANSI 메모리 준비
    HGLOBAL hMemAnsi = GlobalAlloc(GMEM_MOVEABLE, len + 1);
    if (!hMemAnsi) {
        return false;
    }

    char* pMemAnsi = (char*)GlobalLock(hMemAnsi);
    if (!pMemAnsi) {
        GlobalFree(hMemAnsi);
        return false;
    }

    memcpy(pMemAnsi, text, len + 1);
    GlobalUnlock(hMemAnsi);

    // 유니코드 메모리 준비
    int wideLen = MultiByteToWideChar(CP_UTF8, 0, text, -1, NULL, 0);
    HGLOBAL hMemUnicode = NULL;

    if (wideLen > 0) {
        hMemUnicode = GlobalAlloc(GMEM_MOVEABLE, wideLen * sizeof(wchar_t));
        if (hMemUnicode) {
            wchar_t* pMemUnicode = (wchar_t*)GlobalLock(hMemUnicode);
            if (pMemUnicode) {
                MultiByteToWideChar(CP_UTF8, 0, text, -1, pMemUnicode, wideLen);
                GlobalUnlock(hMemUnicode);
            }
            else {
                GlobalFree(hMemUnicode);
                hMemUnicode = NULL;
            }
        }
    }

    // 클립보드 열기 (재시도)
    bool success = false;
    for (int retry = 0; retry < 3; retry++) {
        if (OpenClipboard(NULL)) {
            EmptyClipboard();

            // ANSI 설정
            bool ansiOk = (SetClipboardData(CF_TEXT, hMemAnsi) != NULL);

            // 유니코드 설정
            bool unicodeOk = true;
            if (hMemUnicode) {
                unicodeOk = (SetClipboardData(CF_UNICODETEXT, hMemUnicode) != NULL);
            }

            CloseClipboard();

            // 둘 중 하나라도 성공하면 OK
            if (ansiOk || unicodeOk) {
                success = true;

                // 성공한 핸들은 클립보드가 소유
                // 실패한 핸들만 해제
                if (!ansiOk) {
                    GlobalFree(hMemAnsi);
                }
                if (hMemUnicode && !unicodeOk) {
                    GlobalFree(hMemUnicode);
                }

                break;
            }
        }

        Sleep(10);
    }

    // 완전 실패 시 메모리 해제
    if (!success) {
        GlobalFree(hMemAnsi);
        if (hMemUnicode) {
            GlobalFree(hMemUnicode);
        }
        return false;
    }

    return true;
}

 

1. Base64 테이블

// Base64 table
const char* base64_table =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
  • Base64는 64개 문자만 사용해서 인코딩하는 방식입니다.
  • 이 문자열의 인덱스(0~63)가 Base64 코드입니다.
  • 인덱스 0 → 'A', 인덱스 1 → 'B' ... 인덱스 63 → '/'
    >> 예: 숫자 0을 인덱스로 쓰면 base64_table[0] = 'A' 를 얻습니다.

2. 변환 함수들 (encodeBase64, decodeBase64, textToHex 등)

 - Base64 인코딩

bool encodeBase64(const char* input, char* output, size_t outSize) {
  • 함수선언
  • 반환값: bool (성공/실패 여부)
  • const char* input: 인코딩할 입력 문자열 (읽기만 함)
  • char* output: 인코딩된 결과를 쓸 출력 버퍼
  • size_t outSize: 출력 버퍼의 최대 크기 (오버플로우 방지)
    if (!input || !output || outSize == 0) {
        return false;
    }
  • 입력검증
  • !input : 입력 포인터가 NULL이면 실패
  • !output : 출력 포인터가 NULL이면 실패
  • outSize == 0 : 출력 버퍼의 크기가 0이면 실패
  • 예)
    >> encodeBase64(NULL, buf, 100);      //  return false (input NULL)
    >> encodeBase64("text", NULL, 100);   //  return false (output NULL)
    >> encodeBase64("text", buf, 0);      //  return false (outSize 0)
    size_t len = strlen(input);
  • 입력 길이 계산
  • 입력 문자열의 길이를 계산 (널 문자 제외)
  • 예: "ABC" → len = 3
    size_t required = ((len + 2) / 3) * 4 + 1;  // +1 for null terminator
  • 필요한 버퍼 크기 계산
  • 이 식이 Base64의 핵심입니다.
  • 계산 공식 분석
  • Base64의 규칙
    >> 입력: 3바이트 단위로 읽음
    >> 출력: 4문자로 변환

 

  > 따라서

입력 3바이트   →  출력 4문자
입력 1바이트   →  출력 2문자 (패딩 "=" 2개)
입력 2바이트   →  출력 3문자 (패딩 "=" 1개)

 

  > 구체적 예시

  • 예1: 입력 "ABC" (3바이트)
len = 3
(len + 2) / 3 = (3 + 2) / 3 = 5 / 3 = 1  (정수 나눗셈)
required = 1 * 4 + 1 = 5

출력: "QUJD\0"  (4글자 + 널 문자)

 

  > 구체적 예시

  • 예2: 입력 "A" (1바이트)
len = 1
(len + 2) / 3 = (1 + 2) / 3 = 3 / 3 = 1
required = 1 * 4 + 1 = 5

출력: "QQ==\0"  (2글자 + 패딩 2개 + 널 문자)

 

  > 구체적 예시

  • 예3: 입력 "AB" (2바이트)
len = 2
(len + 2) / 3 = (2 + 2) / 3 = 4 / 3 = 1  (정수 나눗셈)
required = 1 * 4 + 1 = 5

출력: "QUI=\0"  (3글자 + 패딩 1개 + 널 문자)

 

  > 구체적 예시

  • 예4: 입력 "ABCDEF" (6바이트)
len = 6
(len + 2) / 3 = (6 + 2) / 3 = 8 / 3 = 2  (정수 나눗셈)
required = 2 * 4 + 1 = 9

출력: "QUJDREVG\0"  (8글자 + 널 문자)

 

  • 왜 (len + 2) / 3 일까?
    >> 올림(ceiling) 계산
len = 1  →  (1 + 2) / 3 = 1  (올림)
len = 2  →  (2 + 2) / 3 = 1  (올림)
len = 3  →  (3 + 2) / 3 = 1
len = 4  →  (4 + 2) / 3 = 2  (올림)
len = 5  →  (5 + 2) / 3 = 2  (올림)
len = 6  →  (6 + 2) / 3 = 2
  • 정수 나눗셈으로 자동으로 올림 처리됩니다.
    if (outSize < required) {
        return false;
    }
  • 버퍼 크기 검증
  • 사용자가 제공한 버퍼 크기(outSize)가 필요한 크기(required)보다 작으면 실패
  • 오버플로우 방지

  > 예시

char buf[5];  // 5바이트만 할당
encodeBase64("ABC", buf, 5);    //  OK (required = 5)
encodeBase64("ABCD", buf, 5);   //  FAIL (required = 9)

 

    size_t outPos = 0;
  • 출력 위치 초기화
  • 출력 버퍼에 현재 쓴 위치를 기록
  • 처음에는 **맨 앞(0)**에서 시작
    for (size_t i = 0; i < len; i += 3) {
  • 3바이트씩 읽는 루프
  • i += 3 : 3바이트씩 증가
  • Base64는 항상 3바이트 단위로 처리

 > 예시 (입력 "ABCDEFGH" = 8바이트):

반복 1: i = 0  → input[0], input[1], input[2] 읽음 ("ABC")
반복 2: i = 3  → input[3], input[4], input[5] 읽음 ("DEF")
반복 3: i = 6  → input[6], input[7] 읽음 ("GH" + 패딩)
루프 종료: i = 9 > 8

 

        unsigned char b1 = (unsigned char)input[i];
        unsigned char b2 = (i + 1 < len) ? (unsigned char)input[i + 1] : 0;
        unsigned char b3 = (i + 2 < len) ? (unsigned char)input[i + 2] : 0;
  • 바이트 읽기
  • b1 현재 바이트 (항상 존재)
  • b2 다음 바이트 (없으면 0으로 채움)
  • b3 그 다음 바이트 (없으면 0으로 채움)

 

 > 예시 (입력 "A"):

b1 = 'A' (65)
b2 = 0 (없음)
b3 = 0 (없음)

 

        output[outPos++] = base64_table[(b1 >> 2) & 0x3F];
  • 첫 번째 Base64 문자

 > 비트 분석

b1 = 'A' = 65 = 01000001 (이진)
b1 >> 2  =     00010000 (2비트 오른쪽)
                      = 16 (10진)
  • b1 >> 2 : b1을 오른쪽으로 2비트 시프트 (상위 6비트 추출)
0x3F = 00111111 (6개 비트가 1)
16 & 0x3F = 16
  • & 0x3F : 하위 6비트만 남기기 (0~63 범위)
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
 0123456789...                              16 = 'Q'
  • base64_table[16]
  • 결과: output[outPos++] = 'Q'
        output[outPos++] = base64_table[(((b1 & 0x03) << 4) | ((b2 >> 4) & 0x0F)) & 0x3F];
  • 두 번째 Base64 문자

 > 비트 조합

b1 = 01000001
     ......01  (하위 2비트) = 1
  • b1 & 0x03 : b1의 하위 2비트
1 << 4 = 00010000 = 16
  • << 4 : 왼쪽으로 4비트 시프트 (앞으로 당김) 
b2 = 00000000
     0000....  (상위 4비트) = 0
  • b2 >> 4 : b2의 상위 4비트
0 & 0x0F = 0
  • & 0x0F : 하위 4비트만 남기기
00010000  (b1 하위 2비트, 시프트)
| 0000   (b2 상위 4비트)
= 00010000 = 16
  • | (또는): 6비트 조합
16 & 0x3F = 16
  • & 0x3F : 최종 6비트 범위 확인
    결과: base64_table[16] = 'Q' 
        output[outPos++] = (i + 1 < len)
            ? base64_table[(((b2 & 0x0F) << 2) | ((b3 >> 6) & 0x03)) & 0x3F]
            : '=';
  • 세 번째 Base64 문자 (패딩 처리)
  • 조건: i + 1 < len
  • 실제 2번째 바이트가 존재하면? → 계산
  • 없으면 → 패딩 문자 '='

 > 비트 조합 (2번째 바이트가 있을 때)

b2 = 01000010
     ....0010  (하위 4비트) = 2
  • b2 & 0x0F : b2의 하위 4비트 
2 << 2 = 00001000 = 8
  • << 2 : 왼쪽으로 2비트 시프트
b3 = 01000011
     01......  (상위 2비트) = 1
  • b3 >> 6 : b3의 상위 2비트
1 & 0x03 = 1
  • & 0x03 : 하위 2비트만
00001000  (b2 하위 4비트, 시프트)
| 0000001 (b3 상위 2비트)
= 00001001 = 9
  • | (또는): 6비트 조합
  • 결과: base64_table[9] = 'J' 또는 '=' (없을 때)
        output[outPos++] = (i + 2 < len)
            ? base64_table[b3 & 0x3F]
            : '=';
  • 네 번째 Base64 문자 (패딩 처리)
  • 조건: i + 2 < len
  • 실제 3번째 바이트가 존재하면? → 계산
  • 없으면 → 패딩 문자 '='
b3 = 01000011
     00000011  (하위 6비트) = 3
  • b3 & 0x3F : b3의 하위 6비트
  • 결과: base64_table[3] = 'D' 또는 '=' (없을 때)
    output[outPos] = '\0';
  • 널 문자 추가
  • 출력 버퍼의 끝에 널 문자('\0') 추가
  • C 문자열의 종료 표시
  • outPos는 이미 마지막 문자 다음 위치

 > 예시

output = ['Q', 'U', 'J', 'D', '\0']
          0     1     2     3     4
                            outPos = 4

 

    return true;
}
  • 성공 반환
  • 모든 검증 통과, 인코딩 완료
  • true 반환 (성공)

 

 - Base64 디코딩

bool decodeBase64(const char* input, char* output, size_t outSize) {
  • 함수 선언
  • 반환값: bool (성공/실패 여부)
  • const char* input: 디코딩할 Base64 문자열 (읽기만 함)
  • char* output: 디코딩된 결과를 쓸 출력 버퍼
  • size_t outSize: 출력 버퍼의 최대 크기
    static const int8_t table[256] = {
        -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
// ... 중략 ...
        -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,
        // ... 중략 ...
    };
인덱스 ASCII 문자 테이블 값 의미
43 + 62 Base64 문자 +는 값 62
47 / 63 Base64 문자 /는 값 63
48~57 0~9 52~61 Base64 문자 0~9는 값 52~61
65~90 A~Z 0~25 Base64 문자 A~Z는 값 0~25
97~122 a~z 26~51 Base64 문자 a~z는 값 26~51
그 외 - -1 유효하지 않은 문자 

   
 > 구체적 예시

Base64 테이블:
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"

문자 'A' (ASCII 65)  → table[65] = 0
문자 'B' (ASCII 66)  → table[66] = 1
문자 'Z' (ASCII 90)  → table[90] = 25
문자 'a' (ASCII 97)  → table[97] = 26
문자 'z' (ASCII 122) → table[122] = 51
문자 '0' (ASCII 48)  → table[48] = 52
문자 '9' (ASCII 57)  → table[57] = 61
문자 '+' (ASCII 43)  → table[43] = 62
문자 '/' (ASCII 47)  → table[47] = 63
문자 '@' (ASCII 64)  → table[64] = -1  (유효하지 않음)

 

 > 왜 int8_t를 사용할까?

static const int8_t table[256] = {
    -1, -1, -1, ...  // -1은 "유효하지 않은 문자" 표시
    0, 1, 2, ...     // 0~63은 Base64 값
};
  • -1 : 유효하지 않은 문자 표시 (감지 가능)
  • 0~63 : Base64 값 (6비트)
  • int8_t : -128 ~ 127 범위 (충분함)

 > 만약 uint8_t (0~255)였다면

static const uint8_t table[256] = {
    255, 255, ...  // -1 대신 255 사용 (헷갈림)
};

 

    if (!input || !output || outSize == 0) {
        return false;
    }
  • 입력 검증
  • !input 입력 포인터가 NULL이면 실패
  • !output 출력 포인터가 NULL이면 실패
  • outSize == 0 출력 버퍼의 크기가 0이면 실패

 > 예시

decodeBase64(NULL, buf, 100);       //  return false
decodeBase64("QQ==", NULL, 100);    //  return false
decodeBase64("QQ==", buf, 0);       //  return false
decodeBase64("QQ==", buf, 100);     //  OK

 

    size_t len = strlen(input);
  • 입력 길이 계산
  • 입력 Base64 문자열의 길이 (널 문자 제외)
  • 예: "QQ==" → len = 4
    size_t outPos = 0;
  • 변수 초기화
  • 출력 버퍼의 현재 쓴 위치 (처음에는 0)
    unsigned int val = 0;
  • 비트를 임시로 저장하는 변수
  • 6비트씩 쌓아서 8비트(1바이트)가 되면 출력
    int bits = -8;
  • 현재 val에 몇 비트의 유효 데이터가 있는지 기록
  • -8 : "아직 데이터가 없음"을 의미
  • 값이 증가: -8 → -2 → 0 → 6 → 8 → ...
    for (size_t i = 0; i < len; i++) {
  • Base64 문자 하나씩 처리 루프
  • 입력 Base64 문자를 하나씩 읽음 (인코딩과 달리 1개씩)
        unsigned char c = (unsigned char)input[i];
  • 현재 문자 읽기
  • 현재 위치의 문자를 읽음
  • 예: input[0] → 'Q' (ASCII 81)
        if (c == ' ' || c == '\n' || c == '\r' || c == '\t') {
            continue;
        }
  • 공백 문자 스킵
  • 공백 문자는 무시하고 다음 루프로
  • Base64는 공백으로 포맷팅되는 경우가 많음

 > 예시

입력: "QQ== QU== "
처리:
  'Q' → 처리
  'Q' → 처리
  '=' → 처리
  '=' → 처리
  ' ' → 스킵!
  'Q' → 처리
  'U' → 처리
  '=' → 처리
  '=' → 처리
  ' ' → 스킵!

 

        if (c == '=') {
            break;
        }
  • 패딩 문자 처리
  • 패딩 문자 '='를 만나면 디코딩 종료
  • 더 이상의 데이터가 없다는 뜻

 > 예시

입력: "QQ=="
처리:
  'Q' → 처리
  'Q' → 처리
  '=' → break! (루프 종료)
  '=' → 처리 안 함

 

        int8_t decoded = table[c];
  • 테이블 조회
  • 현재 문자 c의 ASCII 값을 인덱스로 사용
  • 테이블에서 해당 Base64 값을 조회

 > 예시

c = 'Q' (ASCII 81)
decoded = table[81] = 16  // 'Q'는 Base64 값 16

c = 'U' (ASCII 85)
decoded = table[85] = 20  // 'U'는 Base64 값 20

c = '@' (ASCII 64)
decoded = table[64] = -1  // 유효하지 않음

 

        if (decoded == -1) {
            // 잘못된 Base64 문자
            return false;
        }
  • 잘못된 문자 검사
  • 테이블에서 -1이 나오면 유효하지 않은 문자
  • 디코딩 실패로 반환

 > 예시

input = "QQ@="  // '@'는 유효하지 않은 문자
// decodeBase64 실행 → return false (실패)

 

        val = (val << 6) | (unsigned int)decoded;
  • 비트 누적
  • 기존 val을 6비트 왼쪽으로 시프트하고 새 데이터 추가
  • Base64는 6비트 단위로 저장됨

 > 구체적 예시 (입력 "QQ==")

val = 0
val << 6 = 0
val | 16 = 16 = 00010000
val = 00010000 (6비트)
  • 1번째: 'Q' (값 16)
val = 00010000
val << 6 = 00010000 << 6 = 0001000000000000 (12비트)
val | 16 = 0001000000010000
val = 0001000000010000 (12비트)
  • 2번째: 'Q' (값 16)
        bits += 6;
  • 유효 비트 개수 증가
  • Base64 문자 하나가 6비트이므로 6 증가
  • -8 → -2 → 4 → 10 → ...
초기:        bits = -8
1번째 'Q':   bits = -8 + 6 = -2
2번째 'Q':   bits = -2 + 6 = 4
3번째 '=':   break (루프 종료)

최종: bits = 4
  • 비트 변화 추적
        if (bits >= 0) {
  • 8비트 이상 모았을 때 출력
  • 유효 비트가 8비트 이상 모였을 때만 실행
  • 1바이트(8비트)를 만들 수 있는 상태

 > 실행 조건

bits = -8 : 실행 안 함 (8비트 미만)
bits = -2 : 실행 안 함 (8비트 미만)
bits = 4  : 실행 안 함 (8비트 미만)
bits = 0  : 실행 함! (정확히 8비트)
bits = 6  : 실행 함! (14비트)

 

             if (outPos >= outSize - 1) {
                return false;
            }
  • 버퍼 오버플로우 체크
  • 출력할 데이터가 버퍼 크기를 초과하면 실패
  • -1 : 널 문자 공간 예약

 > 예시

char buf[5];
decodeBase64("QQ==", buf, 5);

outPos = 0, 1, 2, 3 (최대 4개)
4번째 바이트 쓸 때: outPos >= 4 ? → return false

 

            output[outPos++] = (char)((val >> bits) & 0xFF);
  • 8비트 추출 및 출력
  • val 에서 필요한 8비트를 추출하여 출력
  • val >> bits : 필요한 위치로 시프트
  • & 0xFF : 하위 8비트만 남기기

 > 구체적 예시

1번째 'Q' (16):
  val = 00010000 (6비트)
  bits = -2
  bits >= 0? NO → 스킵

2번째 'Q' (16):
  val = (00010000 << 6) | 16
      = 0001000000010000 (12비트)
  bits = 4
  bits >= 0? YES → 추출!
  
  val >> bits = 0001000000010000 >> 4 = 00010000
  & 0xFF = 00010000 = 16 = 'Q'
  
  output[0] = 'Q'
  outPos = 1
  bits = 4 - 8 = -4

3번째 '=':
  break (루프 종료)

최종 output = "Q"
  • 입력 "QQ==" 디코딩:
'A' (65) = 01000001 (8비트)

인코딩:
  01000001 → 010000 (6비트) + 01 (2비트)
  010000 = 16 = 'Q'
  010000 (2비트) + 패딩(4비트) = 01000000 = 32 = 'g'
  
결과: "QQ=="

디코딩:
  'Q' = 16 = 010000 (6비트)
  'Q' = 16 = 010000 (6비트)
  '=' = 패딩
  '=' = 패딩
  
  010000 (6비트) + 010000 (6비트) = 01000001 (8비트)
  01000001 = 65 = 'A'
  
결과: "A"
  • 실제 계산 (ASCII 표현)
            bits -= 8;
  • 비트 개수 감소
  • 8비트를 사용했으므로 8 감소
  • -4 → 0 → 6 → ...

 > 비트 변화 완전 추적

초기:           bits = -8, val = 0
1번째 'Q' (16): bits = -2, val = 0001000000000000
2번째 'Q' (16): bits = 4,  val = 0001000000010000
                → bits >= 0 ? YES
                → output[0] = 'Q'
                → bits = -4
3번째 '=':      break

최종: output = "Q", outPos = 1

 

    output[outPos] = '\0';
  • 널 문자 추가
  • 출력 버퍼의 끝에 널 문자('\0') 추가
  • C 문자열의 종료 표시

 > 예시

output = ['A', 'B', 'C', '\0']
          0     1     2     3
                    outPos = 3

 

    return true;
}
  • 성공 반환
  • 모든 검증 통과, 디코딩 완료
  • true 반환 (성공)

 

 - Text to Hex

bool textToHex(const char* input, char* output, size_t outSize) {
  • 함수 선언
  • 반환값: bool (성공/실패 여부)
  • const char* input: 16진수로 변환할 텍스트 문자열
  • char* output: 변환된 16진수 결과를 쓸 출력 버퍼
  • size_t outSize: 출력 버퍼의 최대 크기
    static const char hexDigits[] = "0123456789ABCDEF";
  • 16진수 숫자 테이블
  • 16진수 문자들을 저장하는 문자열
  • 인덱스 0~15에 해당 문자가 저장됨

 > 테이블 매핑

인덱스 문자 의미
0~9 '0'~'9' 10진수 0~9
10 'A' 16진수 A (10)
11 'B' 16진수 B (11)
12 'C' 16진수 C (12)
13 'D' 16진수 D (13)
14 'E' 16진수 E (14)
15 'F' 16진수 F (15)

 

 

> 구체적 예시

hexDigits[0]  = '0'
hexDigits[9]  = '9'
hexDigits[10] = 'A'
hexDigits[15] = 'F'

 

 > 사용

unsigned char byte = 165;  // 10100101
상위 4비트: (165 >> 4) & 0x0F = 10 → hexDigits[10] = 'A'
하위 4비트: 165 & 0x0F = 5 → hexDigits[5] = '5'

결과: "A5"

 

 > 왜 static 사용?

static const char hexDigits[] = "0123456789ABCDEF";
  • static : 함수가 호출될 때마다 다시 만들지 않음
  • 메모리 효율성 : 한 번만 초기화
  • const : 읽기만 하므로 변경 불가
    if (!input || !output || outSize == 0) {
        return false;
    }
  • 입력 검증
  • !input 입력 포인터가 NULL이면 실패
  • !output 출력 포인터가 NULL이면 실패
  • outSize == 0 출력 버퍼의 크기가 0이면 실패

 > 예시

textToHex(NULL, buf, 100);       //  return false
textToHex("ABC", NULL, 100);     //  return false
textToHex("ABC", buf, 0);        //  return false
textToHex("ABC", buf, 100);      //  OK

 

    size_t inputLen = strlen(input);
  • 입력 길이 계산
  • 입력 문자열의 길이를 계산 (널 문자 제외)
  • 예: "ABC" → inputLen = 3
    if (inputLen == 0) {
        output[0] = '\0';
        return true;
    }
  • 빈 문자열 처리
  • 입력이 빈 문자열이면?
    >> 출력도 빈 문자열 (\0만)
    >> 성공(true) 반환

 > 예시

textToHex("", buf, 100);
// output = "\0"
// return true

 

    size_t outPos = 0;
  • 출력 위치 초기화
  • 출력 버퍼에 현재 쓰는 위치를 기록
  • 처음에는 맨 앞(0)에서 시작
    if (outSize < inputLen * 3) {
        return false;
    }
  • 필요한 버퍼 크기 계산
  • 계산 공식 분석
  • 각 입력 문자 → 2개 16진수 문자 + 1개 공백 = 3바이트

 > 예1: 입력 "A" (1문자)

'A' → "41 " (2글자 + 공백 = 3바이트) + 널 = 4바이트
그런데 마지막에는 공백이 없음!
'A' → "41\0" (2글자 + 널 = 3바이트)

필요: inputLen * 3 = 1 * 3 = 3바이트

 

> 예2: 입력 "AB" (2문자)

'A' → "41 " (3바이트)
'B' → "42\0" (2글자 + 널 = 3바이트)

총: 6바이트
필요: inputLen * 3 = 2 * 3 = 6바이트

 

 > 예3: 입력 "ABC" (3문자)

'A' → "41 " (3바이트)
'B' → "42 " (3바이트)
'C' → "43\0" (2글자 + 널 = 3바이트)

총: 9바이트
필요: inputLen * 3 = 3 * 3 = 9바이트

 

 > 공식 설명

각 문자당: 16진수 2글자 + 공백 1개 = 3바이트
마지막 문자만: 16진수 2글자 + 널 1개 = 3바이트 (공백 없음)

총합: inputLen * 3 - 1 + 1 = inputLen * 3

(마지막 공백 대신 널 문자)

 

 > 구체적 계산

입력: "ABC"
inputLen = 3
필요 크기: 3 * 3 = 9

출력: "41 42 43\0"
      A  B  C
      0  1  2  3  4  5  6  7  8
      
문자 'A' (65)  → 0x41 → "41 "  (위치 0, 1, 2)
문자 'B' (66)  → 0x42 → "42 "  (위치 3, 4, 5)
문자 'C' (67)  → 0x43 → "43"   (위치 6, 7)
널 문자        →         "\0"   (위치 8)

 

    for (size_t i = 0; i < inputLen; i++) {
        unsigned char byte = (unsigned char)input[i];
  • 버퍼 크기 검증
  • 사용자가 제공한 버퍼 크기(outSize)가 필요한 크기보다 작으면 실패
  • 오버플로우 방지

 > 예시

char buf[9];
textToHex("ABC", buf, 9);    //  OK (필요: 9바이트)
textToHex("ABC", buf, 8);    //  FAIL (필요: 9바이트, 제공: 8)

 

   for (size_t i = 0; i < inputLen; i++) {
  • 각 문자를 16진수로 변환하는 루프
  • 입력 문자를 하나씩 읽음
  • i = 0, 1, 2, ... 순서대로 처리

 > 예시 (입력 "ABC"):

반복 1: i = 0, input[0] = 'A'
반복 2: i = 1, input[1] = 'B'
반복 3: i = 2, input[2] = 'C'
루프 종료: i = 3 > 2

 

        unsigned char byte = (unsigned char)input[i];
  • 현재 문자를 바이트로 읽기
  • 현재 문자를 부호 없는 정수(0~255)로 변환
  • 예: 'A' → 65, 'B' → 66

 > 예시

input[0] = 'A'
byte = 65 = 01000001 (이진)

 

        output[outPos++] = hexDigits[(byte >> 4) & 0x0F];
  • 상위 4비트를 16진수로 변환

 > 비트 분석

byte = 'A' = 65 = 01000001 (이진)
byte >> 4  =       00000100 (4비트 오른쪽)
                         = 4 (10진)
  • byte >> 4 : 바이트를 오른쪽으로 4비트 시프트 (상위 4비트 추출)
0x0F = 00001111 (4개 비트가 1)
4 & 0x0F = 4
  • & 0x0F : 하위 4비트만 남기기 (0~15 범위)
hexDigits = "0123456789ABCDEF"
            0123456789...
                        4 = '4'
  • hexDigits[4]
  • 결과: output[outPos++] = '4' (outPos++이므로 위치도 증가)
입력 바이트 이진 상위 4비트 10진 16진수
'A' 65 01000001 0100 4 '4'
'B' 66 01000010 0100 4 '4'
'C' 67 01000011 0100 4 '4'
' ' 32 00100000 0010 2 '2'
'0' 48 00110000 0011 3 '3'
'Z' 90 01011010 0101 5 '5'

 

        output[outPos++] = hexDigits[byte & 0x0F];   

 

 > 비트 분석

byte = 'A' = 65 = 01000001 (이진)
0x0F = 00001111 (4개 비트가 1)
byte & 0x0F = 00000001 = 1 (10진)
  • byte & 0x0F : 바이트의 하위 4비트만 추출
hexDigits = "0123456789ABCDEF"
             0123456789...
                     1 = '1'
  • hexDigits[1]
  • 결과: output[outPos++] = '1' (outPos++이므로 위치도 증가)

 > 구체적 예시

입력 바이트 이진 하위 4비트 10진 16진수
'A' 65 01000001 0001 1 '1'
'B' 66 01000010 0010 2 '2'
'C' 67 01000011 0011 3 '3'
' ' 32 00100000 0000 0 '0'
'0' 48 00110000 0000 0 '0'
'Z' 90 01011010 1010 10 'A'

 

        if (i < inputLen - 1) {
            output[outPos++] = ' ';
        }
  • 마지막 문자가 아니면 공백 추가
  • 마지막 문자 직전까지 공백 추가
  • 마지막 문자 뒤에는 공백 없음 (널 문자가 올 예정)

 > 조건 분석

inputLen = 3 (입력 "ABC")
i = 0: 0 < 2 ? YES  → 공백 추가
i = 1: 1 < 2 ? YES  → 공백 추가
i = 2: 2 < 2 ? NO   → 공백 없음 (마지막)
  • i < inputLen - 1

 > 구체적 예시

입력: "ABC"

i=0, 'A':
  output[0] = '4' (상위)
  output[1] = '1' (하위)
  i < 2 ? YES → output[2] = ' '
  outPos = 3

i=1, 'B':
  output[3] = '4' (상위)
  output[4] = '2' (하위)
  i < 2 ? YES → output[5] = ' '
  outPos = 6

i=2, 'C':
  output[6] = '4' (상위)
  output[7] = '3' (하위)
  i < 2 ? NO → 공백 없음
  outPos = 8

최종 상태:
output = ['4', '1', ' ', '4', '2', ' ', '4', '3', ?, ?]
outPos = 8

 

    output[outPos] = '\0';
  • 널 문자 추가
  • 출력 버퍼의 끝에 널 문자('\0') 추가
  • C 문자열의 종료 표시

 > 예시

output = ['4', '1', ' ', '4', '2', ' ', '4', '3', '\0']
          0     1     2     3     4     5     6     7     8
                                                  outPos = 8
  • 결과: 문자열 "41 42 43"
    output[outPos] = '\0';
}
  • 성공 반환
  • 모든 검증 통과, 변환 완료
  • true 반환 (성공)

 

 - Hex to Text

static inline int hexCharToValue(char c) {
  • 헬퍼 함수: hexCharToValue
  • static inline
    >> static = 이 파일 내에서만 사용
    >> inline = 함수 호출 오버헤드 제거 (컴파일러가 코드를 직접 삽입)
  • 반환값: int (-1 = 유효하지 않은 문자, 0~15 = 16진수 값)
  • 목적: 16진수 문자('0'~'9', 'A'~'F', 'a'~'f')를 숫자(0~15)로 변환
    if (c >= '0' && c <= '9') return c - '0';
  • 16진수 숫자 판별 (0~9)

 > .비트 분석

'0' = 48
'1' = 49
'2' = 50
...
'9' = 57
  • 문자 '0'~'9'의 ASCII 값
'0' - '0' = 48 - 48 = 0 ✓
'1' - '0' = 49 - 48 = 1 ✓
'2' - '0' = 50 - 48 = 2 ✓
...
'9' - '0' = 57 - 48 = 9 ✓
  • c - '0' 계산

 > 예시

hexCharToValue('0') → 0
hexCharToValue('5') → 5
hexCharToValue('9') → 9

 

    if (c >= 'A' && c <= 'F') return c - 'A' + 10;
  • 16진수 대문자 판별 (A~F)

 > 계산 과정

'A' = 65
'B' = 66
'C' = 67
'D' = 68
'E' = 69
'F' = 70
  • 문자 'A'~'F'의 ASCII 값
'A' - 'A' + 10 = 0 + 10 = 10  (16진수 A) ✓
'B' - 'A' + 10 = 1 + 10 = 11  (16진수 B) ✓
'C' - 'A' + 10 = 2 + 10 = 12  (16진수 C) ✓
'D' - 'A' + 10 = 3 + 10 = 13  (16진수 D) ✓
'E' - 'A' + 10 = 4 + 10 = 14  (16진수 E) ✓
'F' - 'A' + 10 = 5 + 10 = 15  (16진수 F) ✓
  • c - 'A' + 10 계산

 > 예시

hexCharToValue('A') → 10
hexCharToValue('F') → 15

 

    if (c >= 'a' && c <= 'f') return c - 'a' + 10;
  • 16진수 소문자 판별 (a~f)

 > 계산 과정

'a' = 97
'b' = 98
'c' = 99
'd' = 100
'e' = 101
'f' = 102
  • 문자 'a'~'f'의 ASCII 값
'a' - 'a' + 10 = 0 + 10 = 10  (16진수 a) ✓
'b' - 'a' + 10 = 1 + 10 = 11  (16진수 b) ✓
'f' - 'a' + 10 = 5 + 10 = 15  (16진수 f) ✓
  • c - 'a' + 10 계산

 > 예시

hexCharToValue('a') → 10
hexCharToValue('f') → 15

 

    return -1;
  • 유효하지 않은 문자
  • 위의 어떤 조건도 만족하지 않으면 -1 반환
  • "이건 16진수 문자가 아님" 을 의미

 > 예시

hexCharToValue('G') → -1  (F 다음)
hexCharToValue('z') → -1  (16진수 범위 벗어남)
hexCharToValue(' ') → -1  (공백)
hexCharToValue('@') → -1  (특수문자)

 

bool hexToText(const char* input, char* output, size_t outSize) {
  • 함수 선언
  • 반환값: bool (성공/실패 여부)
  • const char* input: 16진수 문자열 (예: "41 42 43")
  • char* output: 변환된 텍스트 결과
  • size_t outSize: 출력 버퍼의 최대 크기
    if (!input || !output || outSize == 0) {
        return false;
    }
  • 입력 검증
  • !input 입력 포인터가 NULL이면 실패
  • !output 출력 포인터가 NULL이면 실패
  • outSize == 0 출력 버퍼의 크기가 0이면 실패

 > 예시

hexToText(NULL, buf, 100);         //  return false
hexToText("41 42", NULL, 100);     //  return false
hexToText("41 42", buf, 0);        //  return false
hexToText("41 42", buf, 100);      //  OK

 

    size_t outPos = 0;
  • 변수 초기화
  • 출력 버퍼의 현재 쓰는 위치 (처음에는 0)
    size_t i = 0;
  • 입력 문자열의 현재 읽는 위치 (처음에는 0)
    while (input[i] != '\0' && outPos < outSize - 1) {
  • 메인 루프: 16진수 쌍 처리
  • 루프 조건
    >> input[i] != '\0' : 입력 문자열이 끝나지 않았으면 계속
    >> outPos < outSize - 1 : 출력 버퍼에 공간이 있으면 계속
       >>> -1 : 널 문자 공간 예약

 > 예시

input = "41 42 43"
outPos = 0, 1, 2, ... (최대 outSize-2까지만)

 

        while (input[i] == ' ' || input[i] == ':' || input[i] == '-' ||
            input[i] == '\t' || input[i] == '\n' || input[i] == '\r') {
            if (input[i] == '\0') break;
            i++;
        }
  • 공백 및 구분자 스킵

 > 처리하는 구분자들

문자 이름 ASCII 용도
  공백 32 16진수 구분
: 콜론 58 MAC 주소 (AA:BB:CC)
- 하이픈 45 범위 표현 (AA-BB-CC)
\t 9 공백
\n 줄바꿈 10 개행
\r  캐리지 리턴 13 개행

 

 > 구체적 예시

입력: "41 42:43-44"
처리:
  i=0: '4' → 16진수 (스킵 안 함)
  i=0: 숫자 처리
  ...
  i=2: ' ' → 스킵!
  i=3: '4' → 16진수
  i=3: 숫자 처리
  ...
  i=5: ':' → 스킵!
  i=6: '4' → 16진수
  ...

 

while (input[i] == ' ' || input[i] == ':' || ...) {
    if (input[i] == '\0') break;  // 문자열 끝이면 탈출
    i++;  // 다음 문자로
}
  • nested while 구조
i=2, input[2]=' ' → 조건 만족
  input[2] == '\0'? NO
  i++ → i=3

i=3, input[3]='4' → 조건 불만족
  while 탈출
  • 흐름
        if (input[i] == '\0') break;
  • 문자열 끝 확인
  • 공백 스킵 후에도 문자열이 끝나면 루프 탈출
  • 더 이상 처리할 16진수가 없음

 > 예시

입력: "41 42 "  (공백으로 끝남)
처리:
  i=0~3: "41 42" 처리
  i=5: ' ' → 스킵
  i=6: '\0' → break

 

        int high = hexCharToValue(input[i]);
        if (high == -1) {
            return false;
        }
  • 첫 번째 16진수 문자 확인
  • 상위 4비트를 나타낼 16진수 문자 읽기
  • 유효하지 않으면 실패 반환

 > 예시

입력: "41 42"
i=0: input[0]='4' → hexCharToValue('4')=4 → high=4
i=0: input[0]='G' → hexCharToValue('G')=-1 → return false

 

        if (input[i + 1] == '\0') {
            return false;  // 홀수 개의 hex 문자
        }
  • 두 번째 16진수 문자 존재 여부 확인
  • 16진수는 항상 쌍으로 나와야 함 (2개씩)
  • input[i+1]이 널 문자면 홀수 개 → 실패

 > 예시

입력: "41 42 4"  (홀수 개)
처리:
  "41" → OK (2개)
  "42" → OK (2개)
  "4" → input[i+1]='\0' → return false

 

        int low = hexCharToValue(input[i + 1]);
        if (low == -1) {
            return false;
        }
  • 두 번째 16진수 문자 확인
  • 하위 4비트를 나타낼 16진수 문자 읽기
  • 유효하지 않으면 실패 반환

 > 예시

입력: "41 4G"
처리:
  i=3: input[3]='4' → high=4
  i=4: input[4]='G' → hexCharToValue('G')=-1 → return false

 

        output[outPos++] = (char)((high << 4) | low);
  • 두 문자를 하나의 바이트로 조합

 > 비트 조합 과정

high = 4 = 0000 0100
high << 4 = 0100 0000 (64)
  • high << 4 : 상위 4비트를 왼쪽으로 4비트 시프트
high << 4 = 0100 0000 (64)
low = 1 = 0000 0001
|           0100 0001 (65 = 'A')
  • | low : 하위 4비트를 또는(OR) 연산으로 합치기
  • 결과: output[0] = 65 = 'A'

 > 구체적 예시

16진수 high low 계산 결과 문자
41 4 1 (4<<4)|1 65 'A'
42 4 2 (4<<4)|2 66 'B'
43 4 3 (4<<4)|3 67 'C'
FF 15 15 (15<<4)|15 255 (최댓값)
00 0 0 (0<<4)|0 0 '\0
48 4 8 (4<<4)|8 72 'H'
65 6 5 (6<<4)|5 101 'e'

 

high = 4 (문자 '4')
low = 1 (문자 '1')

4 << 4 = 0000 0100 << 4 = 0100 0000 (이진)
1 = 0000 0001 (이진)

0100 0000 | 0000 0001 = 0100 0001 (이진) = 65 (10진) = 'A'
  • 상세 계산 (입력 "41")
        i += 2;
  • 위치 이동
  • 2개의 16진수 문자를 읽었으므로 2칸 전진
  • 공백까지 포함하면 보통 3칸 (다음 루프에서 공백 스킵)

 > 예시

입력: "41 42 43"
      01 23 45 67

i=0: '4' 읽음
i=1: '1' 읽음
i += 2 → i=2

i=2: ' ' → while 루프에서 스킵 → i=3
i=3: '4' 읽음
i=4: '2' 읽음
i += 2 → i=5

...

 

    output[outPos] = '\0';
  • 널 문자 추가
  • 출력 버퍼의 끝에 널 문자('\0') 추가
  • C 문자열의 종료 표시

 > 예시

output = ['A', 'B', 'C', '\0']
          0     1     2     3
                    outPos = 3

 

    return true;
}
  • 성공 반환
  • 모든 검증 통과, 변환 완료
  • true 반환 (성공)

+ Recent posts