◎ 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 = "<"; entityLen = 4; break; case '>': entity = ">"; entityLen = 4; break; case '"': entity = """; entityLen = 6; break; case '\'': entity = "'"; entityLen = 5; break; // 추가 엔티티 case '\n': entity = " "; entityLen = 5; break; case '\r': entity = " "; 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, "<", 4) == 0) { output[outPos++] = '<'; i += 3; matched = true; } else if (remaining >= 4 && memcmp(input + i, ">", 4) == 0) { output[outPos++] = '>'; i += 3; matched = true; } else if (remaining >= 6 && memcmp(input + i, """, 6) == 0) { output[outPos++] = '"'; i += 5; matched = true; } else if (remaining >= 5 && memcmp(input + i, "'", 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 반환 (성공)
'IT > Coding' 카테고리의 다른 글
| [C언어] Data Conversion Tool - RawVera #04 (0) | 2025.12.16 |
|---|---|
| [C언어] Data Conversion Tool - RawVera #03 (0) | 2025.12.14 |
| [C언어] Data Conversion Tool - RawVera #01 (1) | 2025.12.14 |
| Python 3.14의 주요 개선사항 (0) | 2025.10.09 |
| [Python] 파이썬 2,3 가상환경 사용법 완전 가이드 (0) | 2025.10.03 |
