(미완성)평범한 개발자의 C 프로그래밍 이야기

인증 번호 생성 2/2

 


좀더 유연한 프로그램을 설계하기 위해 변하는 것과 변하지 않는 것을 구분해보겠습니다.

변하는 것: 제품 종류, 사용자 ID, 시리얼 번호 포맷
변하지 않는 것: 암호화 알고리즘, 입력 형식(정수형 텍스트)

변하는 것은 사용자가 요청할 제품의 종류와 사용자 자신의 ID입니다. 시리얼 번호를 받았는데 허가된 사용자 외에 아무나 사용할 수 있다면 시리얼 번호로서의 가치가 없겠지요. 따라서 시리얼 번호에는 사용자 ID에 대한 정보가 들어가서 허가된 사용자인지 인증할 수 있어야 합니다. 제품의 종류에 따라 다른 포맷의 시리얼 번호가 생성될 것입니다. 변하지 않는 것에는 암호화 알고리즘이 있고, 사용자 ID와 시리얼 번호가 정수형 텍스트라는 것이 변하지 않는 것입니다.

변하지 않는 것은 고정된 코드가 동일하게 실행됩니다. 정수형 문자를 읽는 코드와 암호/복호화 코드는 일정하게 실행됩니다. 따라서 제품 디자인에서 크게 고려할 것이 없습니다. 변하는 것을 설계하기 위해서는 좀더 많은 생각이 필요합니다. 한가지씩 생각해보겠습니다.

우선 제품의 종류는 greentea와 blacktea로 가정한다고 전제를 하였습니다. 하지만 늘 그렇듯이 언제 봐뀔지 모르는 사항입니다. 따라서 제품의 종류를 지정하는 기능이 필요하고 제품을 구별하기 위한 정보가 필요합니다. 우리는 정수로된 제품 번호를 지정해서 제품을 구별하겠습니다.

사용자 ID는 사람마다 다른 정수 값을 지정하는 것으로 처리하겠습니다.

시리얼 번호 포맷도 제품 종류가 바뀌면 따라서 바뀔 수 있는 것입니다. 시리얼 번호 포맷은 제품마다 다르게 만들겠습니다. greentea는 3자리-4자리-3자리의 시리얼 번호를 가지고, blacktea는 4자리-5자리-4자리의 시리얼 번호를 갖도록 만들어보겠습니다. 

그럼 변하는 것들은 누가 어떻게 지정해주어야 할까요. 사용자의 제품 구매 시나리오를 생각해보면 사용자가 제품을 고르고 자기 ID를 입력합니다. 그러면 회사에서 시리얼 번호와 제품을 전송해주고 사용자는 제품을 실행하면서 시리얼 번호를 입력하게 됩니다. 사용자가 입력해야하는 것은 제품 번호와 사용자 ID입니다. 변하는 것들 중에서 2가지는 사용자의 입력을 받으면 알 수 있는 것인데 시리얼 번호 포맷은 어떻게 알아낼 수 있을까요? 또 누가 지정해야하는 것일까요?

보통 이런 경우에는 설정 파일을 만들게 됩니다. vi에서 사용하는 .vimrc나 emacs의 .emacs와 같이 프로그램의 동작을 지정하는 설정 파일을 만들어서 시리얼 생성 프로그램이 규격 파일을 읽고 규격에 맞게 시리얼 번호를 생성하도록 만듭니다. 즉 다음과 같이 3가지 단계로 시리얼번호가 생성됩니다.

사용자가 자신의 ID, 제품 번호 전송 -> 설정 파일 생성 -> 시리얼 번호 생성

.vimrc나 .emacs는 제품이 이미 빌드된 상태에서 프로그램의 동작을 설정하는 파일이므로 프로그램이 내부적으로 사용하는 스크립트언어로 작성되어있습니다. 우리는 프로그램을 빌드하는 단계에서 설정 파일이 필요한 것이므로 C언어로 설정 파일을 만들겠습니다.  스크립트를 만들어서 사용하는 것보다 스크립트를 번역할 필요가 없으므로 더 간단해집니다. 또 설정 파일을 작성하는 사람이 .vimrc는 일반 사용자이지만 우리의 경우는 개발자입니다. 따라서 개발 언어를 사용하는 것이 타당합니다.

사용자에게 ID와 제품 번호를 전달받는 것은 웹페이지 등을 이용해서 자동으로  가정하고 우리는 규격 파일의 생성부터 시작해보겠습니다.

다음은 serial.h 파일입니다. 우리가 시리얼 생성을 위해 어떤 포맷을 사용할지는 모릅니다만 제품 번호와같은 항목이 있고, 그 항목은 문자열이라는 것과 자릿수가 정해진다는 것은 정해져있습니다. 따라서 struct serial_data와 같은 구조체를 만들면 그 항목이 어떤 것이든 구체화할 수 있습니다.

그리고 모든 항목을 모아놓은 struct serial_desc 구조체가 있으므로 시리얼을 생성할 때마다 일정하게 struct serial_desc 구조체의 객체만 참조하면 됩니다.

데이터의 타입이나 형태는 변하지 않습니다. 어떤 데이터가 들어올지는 변합니다. 헤더 파일에 변하지 않는 데이터의 속성들을 정의했습니다.

 

 

#if !defined(_SERIAL_H_)
#define _SERIAL_H_
 
#include <stdio.h>
#include <string.h>
 
 
 
typedef struct serial_desc
{
    char *product_name;
    struct serial_data *serial_data_array;
} serial_desc;
 
 
/**
 * descriptor of each data
 */
typedef struct serial_data
{
    /**
     * name as C-string
     */
    char *name;
    /**
     * maximum digit
     */
    int digit;
} serial_data;
 
 
int serial_init(serial_desc *);
void serial_final(serial_desc *);
void serial_generate(serial_desc *);
void serial_verify(serial_desc *);
void serial_view(serial_desc *);
 
#endif

 

다음은 규격 파일의 예입니다.

serial_data_table[]이 만들어졌습니다. 이제 데이터의 형태가 구체화되었습니다. 시리얼 생성에 3개의 데이터 항목이 들어가고 각각의 이름과 자릿수가 지정되었습니다. 개발자가 아니더라도 알기 쉽게 작성되었습니다. 혹시라도 시리얼 생성을 관리하는 담당자가 개발부서가 아니더라도 여기있는 데이터의 형식만 알고있다면 데이터를 추가하거나 자릿수를 바꾸는 등의 작업을 할 수 있습니다. 시리얼 생성이라는 작업이 누구나 할 수 있는 유연한 작업이 된 것입니다.


#include "serial.h"

 


serial_data serial_data_table[] =
{
    {
        "PRODUCT ID",
        2,
    },
    {
        "EXPIRE DATE",
        6,
    },
    {
        "USER ID",
        4,
    },
    SERIAL_SENTINEL 
};


serial_desc my_serial_desc =
{
    "serial for greetea",
    serial_data_table
};


/**
 * g_serial_data must be declared with user's serial-data
 */
serial_desc *g_serial_data = &my_serial_desc;

 

다음은 serial_core.c 파일입니다. 시리얼 생성에 필요한 serial_desc 데이터가 제대로 준비되었는지 확인하는 serial_init 함수와 시리얼 생성이 완료되었다고 표시하는 serial_final 함수가 있습니다.

시리얼을 생성하는 함수는 serial_generate함수입니다. serial_generate함수는 my_encode함수를 이용해서 시리얼에 들어가는 각 데이터를 나름대로 변환해서 시리얼을 만듭니다. 반대로 serial_view 함수는 my_decode함수를 이용해서 my_encode함수에서 변환한 데이터를 복원해서 문자열로 만듭니다.

my_encode나 my_decode도 사실은 변하는 것입니다. 시리얼을 만들때 기존 데이터를 어떻게 처리할지는 정해지지 않은 것이니까요. 암호화를 할 수도 있고, 압축을 할 수도 있고 얼마든지 나름의 방식으로 처리할 수 있는 것입니다. 여기서는 최대한 단순하게 만들기위해 지금처럼 고정해놓았지만 serial.h에 나름의 인코딩/디코딩 스펙을 정하는 표현을 만들어보시기 바랍니다.

 

#include <stdint.h>
#include <string.h>
 
#include "serial.h"
 
#if defined(VERBOSE_MODE)
#define PRINT(fmt, args...) printf("[%s:%d]: " fmt, \
                                     __FUNCTION__, __LINE__, ##args)
#else
#define PRINT(fmt, args...)
#endif
 
 
int serial_init(serial_desc *desc)
{
    serial_data *data_array = desc->serial_data_array;
    int i;
    int ret = 0;
    
    PRINT("initialize serial generator\n");
    PRINT("making serial for %s\n", desc->product_name);
    PRINT("verification data are:\n");
 
    for (i = 0; ; i++)
    {
        if (data_array[i].name == NULL && data_array[i].digit == 0)
        {
            ret = 1;
            break;
        }
        else if (data_array[i].name != NULL && data_array[i].digit == 0)
        {
            ret = 2;
            break;
        }
        else if (data_array[i].name == NULL && data_array[i].digit != 0)
        {
            ret = 3;
            break;
        }
        else if (data_array[i].verify_func == NULL)
        {
            ret = 4;
            break;
        }
        else
        {
            PRINT("[%s(%d)]\n", data_array[i].name, data_array[i].digit);
        }
    }
 
    if (ret != 0)
    {
        printf("incorrect information at %d-th descriptor\n", ret);
    }
    
    return ret;
}
 
void serial_final(serial_desc *desc)
{
    PRINT("finish serial generator\n");
 
    return;
}
 
char *my_encode(char *data, size_t len)
{
    for (;len > 0; len--)
    {
        *data = (*data) + 1;
        data++;
    }
    return data;
}
 
char *my_decode(char *data, size_t len)
{
   for (;len > 0; len--)
   {
       *data = (*data) - 1;
       data++;
   }
   return data;
}
 
void serial_generate(serial_desc *desc)
{
    int i;
    serial_data *data_array = desc->serial_data_array;
    char buf[SERIAL_LENGTH_MAX];
    char result[SERIAL_LENGTH_MAX];
 
    result[0] = '\0';
    
    for (i = 0; data_array[i].name != NULL; i++)
    {
        memset(buf, 0, SERIAL_LENGTH_MAX);
        printf("insert [%s(%d)]: ", data_array[i].name, data_array[i].digit);
        scanf("%s", buf);
 
        my_encode(buf, data_array[i].digit);
        strcat(result, buf);
    }
 
    printf("%s\n", result);
 
    return;
}
 
 
void serial_view(serial_desc *desc)
{
    int i;
    serial_data *data_array = desc->serial_data_array;
    char serial[SERIAL_LENGTH_MAX];
    int serial_index;
    char buf[SERIAL_LENGTH_MAX];
    int total_len;
    
    memset(serial,0,SERIAL_LENGTH_MAX);
    printf("insert serial: ");
    scanf("%s", serial);
 
    my_decode(serial, strnlen(serial, SERIAL_LENGTH_MAX));
 
    total_len = 0;
    for (i = 0; data_array[i].name != NULL; i++)
    {
        total_len += data_array[i].digit;
    }
 
    if (total_len != strnlen(serial, SERIAL_LENGTH_MAX))
    {
        printf("Serial length error: input %d-digit serial\n", total_len);
        return;
    }
    
    serial_index = 0;
    for (i = 0; data_array[i].name != NULL; i++)
    {
        strncpy(buf, &serial[serial_index], data_array[i].digit);
        buf[data_array[i].digit] = '\0';
        
        printf("serial data [%s(%d)]=%s\n",
               data_array[i].name,
               data_array[i].digit,
               buf);
 
        serial_index += data_array[i].digit;
    }
 
    return;
}
 
다음은 시리얼을 생성하는 프로그램 serial_gen.c 입니다.
너무나 간단하지 않나요? serial_gen.c는 더이상 변할게 없으므로 시리얼 데이터의 포맷이 아무리 바껴도 수정할 필요가 없습니다. 이렇게 소스로 존재할 필요도 없이 미리 컴파일해서 오브젝트 코드로 배포할 수도 있습니다. 사업모델에 따라 얼마든지 융통성을 발휘해서 배포하거나 웹에서 처리하도록하거나 할 수 있습니다.
 
#include <stdint.h>
#include <stdio.h>
 
#include "serial.h"
 
extern serial_desc *g_serial_data;
 
int main(void)
{
    serial_init(g_serial_data);
 
    serial_generate(g_serial_data);
 
    serial_final(g_serial_data);
    
    return 0;
}

 

 

다음은 시리얼 번호에서 데이터를 뽑아내는 serial_view.c 파일입니다.

serial_gen.c와 마찬가지로 간단하고 변하지 않습니다.

#include <stdint.h>
#include <stdio.h>
 
#include "serial.h"
 
extern serial_desc *g_serial_data;
 
int main(void)
{
    serial_init(g_serial_data);
 
    serial_view(g_serial_data);
 
    serial_final(g_serial_data);
    
    return 0;
}
 
 
마지막으로 Makefile을 보겠습니다.
gen과 view 2개의 프로그램을 만듭니다. gen을 실행하면 시리얼 번호를 만들고, view를 실행하면 시리얼 번호를 입력해서 시리얼 번호 생성에 들어간 데이터를 확인할 수 있습니다.
serial_core.c는 소스로 존재할 필요도 없이 serial_core.o가 될 수 있습니다. 또 제품이 바뀌면 desc_greentea.c를 desc_blacktea.c로 바꿀 수도 있습니다.
웹으로 시리얼을 생성하거나 GUI 어플로도 다양한 제품들의 시리얼 생성 시스템을 꾸밀 수 있습니다. 이렇게 변하는 것과 변하지 않는 것을 소스 파일로도 분리한다면 빌드 시스템 또한 다양하게 적용할 수 있게 됩니다.
 
all:
    gcc -Wall -o gen serial_core.c serial_gen.c desc_greentea.c -DVERBOSE_MODE
    gcc -Wall -o view serial_core.c serial_view.c desc_greentea.c -DVERBOSE_MODE

댓글

댓글 본문
작성자
비밀번호
  1. 리밍즈
    kr이라는 프로그램을 사용하고 있는데 사용자번호 5자리와 컴퓨터번호 4자리-4자리-4자리로 구성되어잇는데 이것을 알려주면 해독번호를 알려줍니다. 그런데 2002년에 나온 프로금램이라 현재는 사용자도 없고 회사도 없습니다. 저는 이프로그램으로 자료를 입력해뇌서 꼭 인증번호를 만들어야 하는대 컴퓨터가 메인보드가 바귀면 새로운 인증번호를 입력해야 해야 합니다. 이 인증번호 해독 프로그램을 만들어 주실 수있나요 사례는 따로 하겠습니다.
    kdk3355@naver.com

    예 사용자번호 85457
    컴퓨터번호 (프로그램 설치하면 발생함)
    0010-7485-5183
    인증먼호
    0010-2922-8855(예문임)
    이런식입니다.
    연락 부탁드립니다.
  2. gurugio
버전 관리
gurugio
현재 버전
선택 버전
graphittie 자세히 보기