20
Nov
09

Bad example of using if-clause

이런 저런 코드를 많이 보아왔고, 현재도 보고 있는데, 보면 볼 수록 참 답답한 인사들이 많다. 과연 이 사람들이 무슨 생각을 하고 코딩을 하는지 모르겠다.
자 한번 코드를 보자.

if ( ( videoFormat.FORMAT == MX_VIDEO_FORMAT_MPEG2_MpMl ) && ( iSystemTypeEx > VIDEO_SIZE_SD ) && !bPixelAspect )
{
	if( ( temp_videoBitRate >= 20 ) && ( temp_videoBitRate <= 40 ) )
		return _T("Type1-1");
}

if (videoFormat.FORMAT == MX_VIDEO_FORMAT_MPEG2_MpPl && iSystemTypeEx > VIDEO_SIZE_SD && temp_videoBitRate == 50 && videoM == 3 && videoN == ( g_Encoding == ENC_NTSC ? 15 : 12 ) )
   return _T("Type2");

if ( ( videoFormat.FORMAT == MX_VIDEO_FORMAT_MPEG2_MpMl ) && temp_videoBitRate < 50
	&& iSystemTypeEx == VIDEO_SIZE_1080 && bPixelAspect )
	return _T("Type1-2");

if (videoFormat.FORMAT == MX_VIDEO_FORMAT_TYPE3 && iSystemTypeEx > VIDEO_SIZE_SD)
	return _T("Type3");

뭔가 느껴지는게 있지 않은가?

“참 알아보기 힘들다”

바로 그거다. 각 조건들이 마구 널부러져 있다. 이런 상황에선 어떤 조건이 빠지기도 참 쉽고, 알아보기도 힘들다.
더군다나 두번째 if 문을 보자. 도대체가 괄호도 없이 막 되어 있다. 연산자 우선 순위에 맞으니 괜찮다고? 지금 그런 이야기를 하는게 아니다. 이렇게 너저분하고 알아먹기 힘들게 만든 코드는 관리를 무척이나 어렵게 한다.

우선 첫째와 셋제 if 문을 보자. 흠.. 뭔가 공통점이 있다. 둘다 Type1에 관련된 것인데, 저래서는 도무지 알아보기 힘들다.
자 그럼 정리해보자.

if( videoFormat.FORMAT == MX_VIDEO_FORMAT_MPEG2_MpMl )
{
	if( !bPixelAspect )
	{
		if( iSystemTypeEx > VIDEO_SIZE_SD )
		{
			if( ( temp_videoBitRate >= 20 ) && ( temp_videoBitRate <= 40 ) )
				return _T("Type1-1");
		}
	}
	else
	{
		if( iSystemTypeEx == VIDEO_SIZE_1080 )
		{
			if( temp_videoBitRate < 50 )
				return _T("Type1-2");
		}
	}
}

자 이렇게 되면 훨씬 알아보기 쉽지 않을까?
Type1-1과 Type1-2는 bPixelAspect로 일단 갈리고, iSystemTypeEx와 temp_videoBitRate가 서로 다르다는게 명확해진다.
그럼 이 사이에 뭔가를 넣을 때도 훨씬 편하고, 조건 중 빠진게 뭔지 파악도 쉽다.

원래의 코드를 작성한 사람이 대체 무슨 생각으로 한 건지 알 수가 없다.

15
Nov
09

Git for Computer Scientist

Visual Source Safe, CVS, SubVersion, 그리고 Git
한국 사람으로써 발음도 힘든 Git, “깃”인가 “짓”인가? 나는 “짓”이라고 발음하는데 NSCoder Nigher에선 “깃”이라고들 하더라. 근데 어떤 사람들 보면 또 “짓”이라고 하기도 한다.

근데 도대체 이렇게 버젼 컨트롤 프로그램들이 다양한 이유가 뭘까? 도대체 뭐가 다르길래 자꾸만 새로운 것을 내놓을까? Visual Source Safe가 문제가 많다는 것이야 뭐 MS 제품에 빠진 사람들 아니면 다들 아는 것이고, CVS와 Subversion은 CVS 사람들도 Subversion을 Better CVS라고 부르니 뭐 그다지 궁금하지도 않고. ( 사실 좀 차이가 있다. SubVersion이 파일 깨지는 것에 덜 취약하고 속도가 빠르다고 알려져 있다. 그 이유는 CVS는 파일간의 차이를 저장하는데 반해, SubVersion은 완전하게 그 다른 버젼들을 저장하는 것이라고 한다. 내가 읽은 것을 기억하는게 맞다면.. ) 그렇다면 Subversion이 훌륭하게 좋은데 왜 Git이란 새로운 것이 나왔으며, 요새 Git을 많이들 쓰는가? 적어도 NSCoder Night에선 Git이 내세인거 같다.
Git은 다른 것들과 달리 분산형 시스템이다. 즉 commit을 자체에 하는 샘이고, 어떤 주기가 오거나 어떤 명령이 오거나.. 혹은 네트워크에 연결이 될때, 서로 간에 통신을 통해서 transparent하게 그 버젼을 동일하게 유지시켜 준다. 그러므로 빠르다. 또한 항상 네트워크에 연결될 필요도 없다.
근데 그 외는 무슨 차이가 있을까? 우연히 다음의 문서를 보게 되었다. GitHub를 통해서..
관심있으신 분들은 읽어보시기들 바란다.

Git for Computer Scientist

15
Nov
09

QTAudioFrequencyLevels and variable length array in a structure

QuickTime API내에서 사용되는 structure 중 AuidoFrequencyLevels라는 것이 있다. 이것은 다음과 같이 생겼다.

struct QTAudioFrequencyLevels {
  UInt32              numChannels;
  UInt32              numFrequencyBands;
                                              /* numChannels * numFrequencyBands entries, with the frequency bands for a single channel stored contiguously.*/
  Float32             level[1];
};

자, 여기서 제일 마지막 요소인 level을 보자. 크기가 1인 행렬로 되어 있다. (아… 행렬이란 말 참 오랜 만에 써본다.) 뭐 하느라고 이렇게 선언을 했을까? Apple의 Technical QA1459 : Easy Frequency Level audio Metering with MoviewAudio API를 보면 다음과 같이 쓰는 것을 볼 수가 있다.

QTAudioFrequencyLevels *freqResults = NULL;

...

// call this once per movie to establish metering
err = SetMovieAudioFrequencyMeteringNumBands(myMovie,
                                             kQTAudioMeter_StereoMix,
                                             &numberOfBandLevels);
if (err) goto bail;

freqResults = malloc(offsetof(QTAudioFrequencyLevels,
                              level[numberOfBandLevels * numberOfChannels]));
if (freqResults == NULL) {
    err = memFullErr;
    goto bail:
}

freqResults->numChannels = numberOfChannels;
freqResults->numFrequencyBands = numberOfBandLevels;

즉, dynamic length를 가지는 행렬로써 쓰고 있는 것이다.
음….
이거 말이지…
보통 이렇게 쓰지 않던가?

struct QTAudioFrequencyLevels {
  UInt32              numChannels;
  UInt32              numFrequencyBands;
  Float32             level[];

예전부터 이렇게 쓴 것으로 알고 있는데..
근데 GCC 문서를 보니, variable length를 지원하기 위해서 ISO C99에서는 바로 위의 예처럼 length자체가 없는 형식을 쓰는게 표준이고 ISO C90에서는 1이라고 길이를 명시해야 한단다.
근데 GCC에서는 자체 extension으로 0-length array를 지원한다고.
예전에 어디선가 인터뷰를 봤을때, 0-length array를 물어 봤었는데…
나원참 항상 standard 문서를 확인해보는 것도 아니고..
그리고 나는 가능하면 이런 extension은 쓰지 않는다. 왜냐하면 호환성에 문제가 생길 수있기 때문이다. 이를테면 예전에 삼성에 다닐때 ARM 프로세서를 사용하는 embedded system에서 프로그래밍을 했었는데, pSOSystem이 제공하는 컴파일러가 아주 오래된 사양을 지원하는 C/C++ 컴파일러였다. 물론 C++은 추천되는 대상이 아니었다. embedded system은 항상 C를 선호해왔으니까. 요새야 메모리 빵빵하니 C++을 적극 추천하지만… 근데 그때까지만해도 항상 새로운 표준을 열심히 공부하던 나로써는 당황스러웠다. PC에서 잘 되던 문법이 안되는 것이다. 회사에 제대로 된 pSOSystem 매뉴얼도 없고… 알고보니 오랜 표준만을 지원하고 있었던 것이다. 그 이후로는 가능하면 이곳 저곳에서 다 쓸 수있는 문법만을 쓴다. portability가 중요하기 때문에.

아무튼 Apple의 구현또한 옛날 컴파일러에서도 돌아가게 끔 되어 있는 것이다. 이런 사려깊은 것들 같으니라구.

15
Nov
09

PackageMaker와 Script 그리고 relocation 세팅

PackageMaker는 여러가지 헷갈린 세팅이 있고, 뭔가 잘못되었을때 왜 잘못되었는지를 파악하기가 참 힘들다.

인스톨러가 제대로 동작하는지 확인할때, 그 여부를 확인하기 힘든 경우 중 하나가 relocation이 가능하도록 세팅을 할때이다.
이 relocation은 일단 설치가 된 파일을 사용자가 옮겨도, 그 옮겨진 위치를 찾아내서 다음번 업데이트시나 인스톨시에 그 옮겨진 위치에 있는 것을 업그레이드하게 해준다. 그러므로 상당히 강력한 기능이긴 한데, 문제가 있다. 즉 개발자의 build 디렉토리에 있는 것까지 찾아낸다. 그러므로 프로그래머가 자기의 머신에서 인스톨을 해보면, 원래 의도한 디렉토리에 설치를 하는 것이 아니라, 자기가 빌드한 빌드 디렉토리의 것을 업데이트 한다. 그러면 도대체 설치가 되었는지 아닌지 알아보기가 힘들다. 물론 인스톨 로그를 살펴보면 알아 낼수는 있지만, 쉽게 눈에 띄지를 않는다.

두번째 문제는 스크립트에 관련한 것이다. 단순히 설치를 하는 것이 아닌, 뭔가 설치를 하기 전이나 후에 하려면, 스크립트를 사용하게 마련이다.
PackageMaker는 Product setting으로는 Automator를 이용해서 할 수있게 해 놨고, component setting으로는 일반 shell script를 이용할 수있게 해 놓았다.
그러므로 product setting에서는 AppleScript도 바로 사용할 수가 있다.

그런데 여기서 좀 문제가 생긴다. 아시다시피 Snow Leopard는 이제 거의 PPC에서 Intel로의 완전한 이동을 의미하는 OS이다. 더군다나 이미 64bit로 제한 없는 프로그래밍을 가능하게 해 놓았지만, 아직 User들을 고려해서 부팅 자체는 32비트로 하는 것을 기본으로 하고있다. 이런 상황에서 사용자들은 Rosseta와 같은 PPC emulator를 더 이상 설치하지 않는 경우가 왕왕 있다.
바로 엊그제 벌어진 일인데, AppleScript 에디터인 Script Editor에서 스크립트 파일을 Application 형식으로 저장하면 여전히 PPC로 저장이 된다. (Leopard에서)
그리고 .app이라는 확장자를 부여하지만 Bundle의 형태는 아니고 단일한 binary이다. 이게 Rosetta가 설치되지 않은 Mac에서 사용하려고 하면 문제를 일으킨다. 그렇지 않겠는가? PPC로 된 것을 Intel 프로세서에서 돌리려고 하니…
그래서 어떻게 하면 Universal Binary로 만들 수있을까 해서 osacompile의 MAN 페이지를 봤더니 type에 대한 게 제대로 명시가 되어 있지를 않은거다. 참 난감하다 이럴땐.. 도대체 무슨 생각으로들 MAN 페이지를 만드는 건지.
그래서 그냥 Script Editor에서 Application Bundle형식으로 저장했다. 그러면 비로서 Universal Binary의 형태로 나오게 된다. 문제는 이 Bundle형식으로 된 것을 product setting에서 설치 후에 호출되게 세팅을 하면, 되지를 않는 것이다. Application형식은 되는데 말이지.
자.. 이게 문제 하나였고..

이제 문제 둘…
그렇다면 component setting에서 shell script를 이용해서 하는 방법이 있겠다.
우선 AppleScript 스크립트를 스크립트 형식(*.scpt)로 저장하고, 이 놈을 설치하도록 한다. 내 경우는 이것이 제일 마지막에 인스톨 되도록 목록에서 제일 밑에 놓았다. 그리고 이 파일의 스크립트 탭에서 Script 세팅을 해서, post-install과 post-upgrade시에 내 shell script가 동작하도록 해 놓았다.
아… 여기서 한마디. PackageMaker의 설명서에는 나와 있지 않은데, script 세팅을 통해서 설정하는 쉘 스크립트는 굳이 인스톨되는 하나의 컴포넌트로 하지 않아도 된다. PackageMaker가 알아서 압축해서 인스톨러 번들에 잘 넣어 놓는다.
아무튼.. 혹은 post-flight에만 세팅해도 된다. 왜냐하면 어떤 상황이던 post-flight가 최종적으로 수행되는 단계이기 때문이다.
단 이 설명은 10.4 Tiger까지의 이야기다 10.5 Leopard때부터는 post-install하나밖에 없다.
아마 AppleScript 파일은 수행후에 없어질 것이므로, shell script에서 해당 파일을 지우도록 한다.
대충 보자면 다음과 같다.

#!/bin/sh

pathToScript=/Applications/Add_Login_Item_test.scpt

echo "Trying to add login item..."

echo $pathToScript

/usr/bin/osascript $pathToScript

echo "Added to login item..."

echo "Removing Add_Login_Item.scpt"

rm $pathToScript

이 스크립트는 예에서 Add_Login_Item_test.scpt를 설치하고서 거기에 대해서 세팅된 것이므로 pathToScript를 굳이 저렇게 하드 코딩하지 않고도 $1으로 해주면 사실 된다. 근데 문제는 이너므 PackageMaker다. 분명히 relocatable하게 하지 않았는데도, $1을 통해서 들어오는 AppleScript 파일의 패스가 때로는 패키지 내의 것으로 되는 경우가 있다. 분명히 인스톨 된 후에, 인스톨된 위치의 것이어야 하는데도 말이지. 이게 때때로 되기도 하고 안되기도 하고.. 나 원참…
그래서 아예 하드 코드들 해버린 것이다.

또 하나의 문제점. pathToScript로 저장된 path에 white space가 있는 경우이다.
Google에 찾아보면 다음과 같이들 많이 되어 있다.

'/usr/bin/osascript "$pathToScript"'

근데 이렇게 하면 안된다. 이상하지..
혹은 single quote와 double quote 위치를 바꿔보자.. 혹시나 해서..
역시나 안된다.
원래대로라면 되어야 한다. 뭔가 문제가 있는 것일까? 아니면 내가 뭘 착각하고 있는 것일까? 예전엔 되었던거 같은데..
아무튼 Leopard에서 해보면 어떤 quote를 쓰던 간에 $variable을 그 안에 담긴 내용으로 expansion하지 않고, 글자 그대로 $variable로 해석해 버린다.
이거 참…

거의 모든 매킨토시용 프로그래머들이 인스톨러를 사용하지 않는다. 극히 제한적인 경우를 빼곤 말이다. Tiger때와 바로 Leopard로 올라오면서 PackageMaker에 버그가 워낙에 많아서 그때 참 리포트를 많이 했다. 그러고 나서 업데이트 된게 3.0.3버젼이었나 3.0.2버젼이었나 그런데… 아무튼 내가 리포트한 문제가 많이 해결은 되었는데, 여전히 자잘하게 많은 문제가 있다. 아무래도 사용하는 사람들이 그다지 많이 없으니 안되는 것이리라 생각하지만.. 아무래도 이건 영 Apple 스럽지 못한 프로그램이다. 일전엔 여러 컴포넌트를 설치하는 경우에 만들어지는 JavaScript 코드에 문제가 있는 경우를 봤다. 그때 mailing list에 있는 어떤 분이 알려주셔서 쳐다보았는데.. 우와.. 정말 문제였다.

Mac은 그다지 인스톨러들이 필요하지 않다. Windows처럼 바이너리 파일들이 여러개 파일로 지저분하게 널부러져 있지 않기 때문이다. 그리고 Program Files처럼 깊이 깊이 프로그램들이 들어가있지도 않기 때문이다. 하지만 사용자들이 인스톨러를 원하는 경우가 있다. 그럴 경우엔 어쩌랴.. D&D로도 쉽게 되는데, 굳이 인스톨러를 요구하니.. 대개 윈도우즈 사용자들이 맥을 사용하면서 이런 요구를 하는데… 참… 뭐라고 하기에도 그렇고…

암튼 PackageMaker에 대한 대충의 문제와 해결점을 요약하자면 이상과 같았다.

15
Nov
09

defaults system 사용법

login item에 항목을 추가하는 인스톨러를 만들기 위해 참 많은 시간을 허비했다.
PakcageMaker의 안정적이지 않은 동작으로 애를 먹었었고, 사실 최근 버젼들은 비교적 안정적이었다. 하지만 역시 이해하기 힘든 부분이 있었고, 또한 무슨 일이 왜 발생하는지 인스톨 로그를 보고선 파악하기 힘든 면이 있었다. 이에 대해서 별도로 포스팅을 하겠는데, 그전에 defaults system에 대해서 언급을 해야겠다.
Windows에는 registry란 것이 있다. 여기에 어떤 프로그램을 인스톨했으며, 그 프로그램에서 어떤 세팅을 어떻게 했으며 등등을 기록해 놓는 것이다. 이 registry는 Windows를 쓰면 쓸수록 성능이 떨어지게 만드는 주범 중의 하나이기도 하다. 이런 이유에는 registry를 사용하는 Windows 자체의 문제이기도 하지만, InstallShield나 MSI installer setup project를 만드는게 참 어렵기 때문에, 인스톨 프로그램을 만드는 사람들이 제대로 모든 도큐먼트를 충분히 이해하고 만들지 못하는 이유가 있기도 하다.

Mac은 비교적 깔끔하게 이런 관리를 한다. registry란 단일의 공간이 있다기 보다는 preference를 /Library/Preference나 ~/Library/Preference에 각 프로그램 별로 저장을 한다. com.apple.Safari.plist와 같은 것이 바로 그것이다. 산업계 표준에 맞추어서 그 이름도 com.apple.Safari처럼 해 놓았고, plist는 XML이라 그 내용을 보기도 편하다. 단 text XML이라 그 파일이 커지면 느려지고 커지는 단점도 있어서 요새는 Binary XML도 이용하는 추세이다.
Windows에서처럼 registry란 단일 공간내에 기록될 필요가 없어서, 저장공간 (메모리건 하드디스크건)을 차지하는데 그 공간이 비대해질 염려도 적다. 그러므로 세팅을 찾는데 시간도 적게 걸린다. ( Windows Sys Internals 툴을 이용해보면 Windows가 수시로 registry를 업데이트하는데 시간을 많이 보내고 있음을 볼 수있다. Mac에선 적어도 이런 일은 최소화가 된다. 왜 Windows가 아무일도 안하고 가만히 있는 거 같은데도 수시로 HDD를 긁어대는지 그 이유는 이런데 있다. 물론 background process들도 계속 돌고 있고, 그들이 registry에 써대는 이유도 있지만, 기본적으로 Windows는 registry관리에 CPU time의 일정 부분을 줄기차게 할애한다. )

Mac에서는 각 프로그램들에서 세팅을 하면 그것이 이런 XML파일에 저장이 된다. 프로그램이 설치되서 실행이 되면, 그리고 뭔가 세팅을 저장하면 비로소 이 XML이 생기는데, 아쉽게도 프로그램을 지웠을때, 이 파일들이 자동으로 없어지진 않는다. 이렇게 남아도 Windows처럼 registry란 중앙 집중적인 공간에 저장되지 않기 때문에 퍼포먼스 하락에 그다지 일조하지 않는다. 하지만 Mac에는 다른 문제점이 있는데, 여기에 버그가 좀 있다. 바로 receipt DB란 것이다. 여기에 대해선 뒤에서 언급하기로 하고…

아무튼 세팅을 저장하기 위해선 해당 프로그램을 열어서 거기서 지원하는 preference… 메뉴를 이용해서 주로 바꾸게 되는데…
해당 프로그램을 열지 않고서 바꾸려면 어떻게 해야 하는가? 바로 그 XML 파일들을 직접 열어서 바꿔주면 된다. 근데 이 부분은 user interaction이 들어가서, 때때로 shell script등에서 해주기엔 곤란한 부분이 있다. 이때 바로 쓰는게 defaults system이란 것이다. 종종 Mac OS X Hints와 같은 웹사이트에서 이것을 이용해서 hidden setting을 이용하는게 보인다. 특히 Shell programming을 많이 하게 될 Sys Admin들 입장에서는 defaults system을 아는게 상당히 중요하다.
그러다보면 많이 보게 되는게 바로 MAN page인데.. 문제는 이 MAN page의 설명이 당췌 알아보기가 힘들게 되어 있다는 것이다. MAN page가 그렇듯이 항상 긴 설명에 예제는 거의 없다. 어떤 것을 정의 해 놓고서 그게 뭘 뜻하는 건지 설명이 없기 일쑤다.

자 한번 보자

write domain key 'value'
  Writes value as the value for key in domain.
  value must be a property list, and must be enclosed
  in single quotes.
  For example:
     defaults write com.companyname.appname "Default Color" '(255, 0, 0)'
  sets the value for Default Color to an array containing the strings 255, 0, 0
  (the red, green, and blue components).
  Note that the key is enclosed in quotation marks because it contains a space.

write domain 'plist'
  Overwrites the defaults information in domain with that given as plist.
plist must be a property list representation of a dictionary,
and must be enclosed in single quotes.
  For example:
    defaults write com.companyname.appname '{ "Default Color" = (255, 0, 0); "Default Font" = Helvetica; }';
  erases any previous defaults for com.companyname.appname and writes the values for the two names into the defaults system.

자 이 설명으로 key value 쌍으로 세팅할때는 어떻게 하는지, plist는 어떻게 생겼는지 알 수가 있다.

자 그럼 다음을 보자.

-array      Allows the user to specify an array as the value for the
                given preference key:

                  defaults write somedomain preferenceKey -array element1 element2 element3

                 The specified array overwrites the value of the key if the
                 key was present at the time of the write. If the key was not
                 present, it is created with the new value.

-array-add  Allows the user to add new elements to the end of an array
                 for a key which has an array as its value. Usage is the same
                 as -array above. If the key was not present, it is created
                 with the specified array as its value.

-dict       Allows the user to add a dictionary to the defaults database
               for a domain.  Keys and values are specified in order:

                  defaults write somedomain preferenceKey -dict key1 value1 key2 value2

               The specified dictionary overwrites the value of the key if
               the key was present at the time of the write. If the key was
               not present, it is created with the new value.

-dict-add   Allows the user to add new key/value pairs to a dictionary
               for a key which has a dictionary as its value. Usage is the
               same as -dict above. If the key was not present, it is cre-
               ated with the specified dictionary as its value.

저 array element가 어떻게 생겼는지 알 수가 없다. 또한 dictionary 경우엔 key과 value로 어떤 형태의 데이터를 받아들이는지 알기가 힘들다.
도대체 element는 어떤 type의 것일까?

Login item을 넣기 위해서 구글에서 찾아보았다.
근데 그다지 도움이 되지를 않았다. 대개 간단한 경우에 대해서만 했기 때문이다.
그래서 고생 고생끝에 되는 형태를 찾았는데..

#!/bin/sh
defaults write loginwindow AutoLaunchedApplicationDictionary -array-add '{"Hide" = "True"; "Path" = "/Applications/NEXIO Sync.app";}'
echo "Writing to defaults system is done"

자.. 위의 것은 shell script 파일로 만든 것이다. 편하게 사용하려면 execution 권한을 저 파일에 주면 되는 것이고, 일일히 sh를 호출하기 싫어서 She-Bang라인을 넣어 주었다.
음.. AutoLaunchedApplicationDictionary 아이템에 -array-add 이후의 아이템을 넣는 것인데, 그 자체가 dictionary 형태이다.
즉 한 dictionay를 array로 넣는 것이다. 왜 그럼 -dict-add를 넣으면 안될까? 그건 AutoLaunchedApplcationDictionary의 형태 때문이다. 자 보자..

loginwindow-plist

자.. AuoLaunchedApplicaitonDictionary 키 자체가 여러 아이템을 가지고 있게 되는 형태고, 그 아이템마다 세팅이 있는 것이다.
그러므로 각 아이템이 dictionary이니까, dictionary 자체를 array의 한 아이템으로 넣는 것이다.
그러므로 만약 -dict-add를 이용하면 이미 있는 item1과 item 2가 없어지면서, 한 이이템들이 아닌 AutoLaunchedApplicaitonDictionary 자체의 property들로 key와 value가 세팅이 된다.

이렇게 알고 보면 참 간단한데, 전혀 설명이 없는 상태에서 MAN 페이지의 설명만 봐서는 알기가 힘들다. add 버젼과 아닌 버젼의 차이는, add가 있는 경우는 이미 기존에 있는 것들에 덧붙여서 넣는 것이고, 없는 것은 기존에 있던 것을 갈아치우는 것이다. 이것에 대한 설명은 다행이 있다.

암튼 이렇다.

ADDED : 아.. sed에 익숙한 분이시면 sed를 이용하시는 것도 한 방법이 되겠다.
근데 text processing이 귀찮으니까, 그냥 defautls system을 이용하시는게..
sed를 이용해서 plist파일을 바꾸는 것이 여기에 포스팅 되어 있으니 참고하시면 jump start를 하는데 도움이 될 듯..

12
Nov
09

Mac OS X에서 System folder로 파일 카피할 때의 권한 문제

Unix에선 가능하면 시스템 폴더로 파일을 카피하지 않는게 좋다. 사실 모든 OS에서 다 그렇다. 이걸 안지키고 막가파식으로 간 OS가 바로 Windows이지 않는가? 아무 프로그램이나 Windows 디렉토리 내에 다 써대고 말이지..
하지만 가끔 필요할 때가 있다. 예를 들어 보자. Final Cut Pro의 plugin들은 /Library/Application Support/Final Cut Pro 어쩌구/Plugin에 들어간다. 만약 Plugin이 필요로하는 어떤 화일을 거기에다 넣어 놓는다고 하자. 그럼 그냥 카피를 해선 들어가지 않는다. 왜냐하면 거기에 파일을 생성하려면 Administrator의 권한이 필요하기 때문이다.

자 그럼 어떻게 하면 소스코드에서 파일 카피를 할때, 권한을 설정할까?
Mac에서는 여러가지 접근법이 있는데, 해당 프로세스의 권한을 올리기, 혹은 그 프로세스가 호출하는 다른 프로세스의 권한을 올리기 (근데 이때는 원래 caller process에서 pre-authorization이 필요하다.) 후자를 애플 문서에선 Factored program이라고 한다. 근데 여기에 조심할게 있다. 내가 못해서 그런지는 모르겠는데, NSFileManager의 copy… 메소드와 NSWorkspace의 performOperation을 이용해서는 도저히 카피가 되지를 않는다. 아무리 Authorization을 미리해도, 되지를 않는것이다.
Apple이 이 함수들의 설명에서 권한 문제에 대해서 언급을 하면 참 좋을텐데.

근데 오늘 Cocoa mailing list에서 답변을 얻었다. Factored program 모델에서 사용하는 식으로 AuthorizationExecuteWithPrivileges()를 이용해서 Unix command인 cp를 호출하면 되는 것이다. 근데 재미난 것이, 이땐 pre-authorization을 하지 않아도 되고, cp 자체가 authorization을 하지 않는 것으로 알고 있다. 분명 Factored approach에선 호출하는 프로세스에서 pre-authorization을 하고, 호출되는 프로세스에서 authorization을 하라고 되어 있는데도 말이지….

아무튼 되는 것을 포스팅해보자.

SFAuthorization *authorization = [SFAuthorization authorization];
AuthorizationFlags myFlags = kAuthorizationFlagDefaults |
							 kAuthorizationFlagInteractionAllowed |
							 kAuthorizationFlagExtendRights; //| kAuthorizationFlagPreAuthorize; // Don't need Pre-Authorization.

NSError *authErr;
NSBundle *thisBundle = [NSBundle bundleForClass:[self class]];

NSString *authorizationRightName = [[NSString alloc] initWithFormat:@"%@.Copy", [thisBundle bundleIdentifier]];
BOOL result = [authorization obtainWithRight:[authorizationRightName UTF8String]
									   flags:myFlags error:&authErr];

//	BOOL isSuccessful;
//	isSuccessful = [[NSFileManager defaultManager] copyPath:sourceFile toPath:PlugInPath handler:nil];
char *myArguments[] = { NULL, NULL, NULL };
myArguments[0] = (char *)[[theURL path] UTF8String];
myArguments[1] = (char *)[destFilePath UTF8String];

//OSStatus myStatus = AuthorizationExecuteWithPrivileges(mAuthorizationRef, "/bin/cp", kAuthorizationFlagDefaults, myArguments, NULL);
OSStatus myStatus = AuthorizationExecuteWithPrivileges([authorization authorizationRef], "/bin/cp", kAuthorizationFlagDefaults, myArguments

Apple의 BetterAuthorization이란 샘플코드가 Apple이 미는 방식으로 제대로 권한을 올리고 할 수있다고 한다. 근데.. 바빠 죽겠는데 그거 분석하고 이해해서 코딩할 시간이..
대충 보면 set뭐시기를 이용해서 프로그램 자체의 권한을 변경시켜버리는 것을 하지 말라는 것인거 같은데…

이 authorization쪽 문서가 많이 약하다. Apple에서 좀더 신경을 써 주면 좋겠다.

12
Nov
09

OpenSSL : Decryption

어제 올린 포스트에서 언급했듯이, 오늘은 Decryption 쪽을 포스팅해본다. 오늘 올릴 소스코드는 완전한 것이 아니라, 어떤 method의 한 부분을 보여주는데, 적어도 OpenSSL의 함수를 사용하는 부분에 대해서만은 완전한 소스의 형태를 갖추고 있다.

	const char *validationRawData = [validataionData bytes];
	if( validataionData )
	{
		int validationLength = [validataionData length];
		int totalLength = 0; // For decrypted length

		int result = 0;
		unsigned char inputBuffer[8], outputBuffer[8];
		int inputLength = 0, outputLength = 0;

		bzero( inputBuffer, sizeof( inputBuffer ) );
		bzero( outputBuffer, sizeof( outputBuffer ) );

		char finalBuffer[64];
		bzero( finalBuffer, sizeof( finalBuffer ) );

		// Initialize cipher context
		EVP_CIPHER_CTX ctx;
		EVP_CIPHER_CTX_init( &amp;ctx );

		// Copy a key
		unsigned char keyForDES[8];
		memcpy( keyForDES, kKeyText, 8 ); // &quot;LXFExpor&quot;

		result = EVP_DecryptInit( &amp;ctx, EVP_des_ecb(), keyForDES, NULL);
		if( result )
		{

			int byteLeft = validationLength;
			int bytesToCopy = 0;
			int inputLoc = 0, outputLoc = 0;

			while( byteLeft &gt; 0 )
			{
				if( byteLeft &lt; 8 )
				{
					bytesToCopy = byteLeft;
					byteLeft = 0;
				}
				else
				{
					bytesToCopy = 8;
					byteLeft -= 8;
				}

				bzero( inputBuffer, sizeof( inputBuffer ) );
				memcpy( inputBuffer, validationRawData + inputLoc, bytesToCopy );
				inputLoc += bytesToCopy;

				bzero( outputBuffer, sizeof( outputBuffer ) );
				inputLength = bytesToCopy;
				result = EVP_DecryptUpdate( &amp;ctx, outputBuffer, &amp;outputLength, inputBuffer, inputLength );
				if( result )
				{
					memcpy( finalBuffer + outputLoc, outputBuffer, outputLength );
					outputLoc += outputLength;

					isSuccessful = YES;
				}
				else
				{
					isSuccessful = NO;
					break;
				}
			}

			bzero( outputBuffer, sizeof( outputBuffer ) );
			result = EVP_DecryptFinal( &amp;ctx, outputBuffer, &amp;outputLength );
			if( result )
			{
				memcpy( finalBuffer + outputLoc, outputBuffer, outputLength );
				outputLoc += outputLength;

				totalLength = outputLoc;

				isSuccessful = YES;
			}
			else
				isSuccessful = NO;

		}
		else
			isSuccessful = NO;

역시 주의할 부분은 EVP_DecryptUpdate() 함수의 호출부분이다. 이 함수의 호출 결과가 바로 바로 생각하는 단계의 것을 반영하지 않고, 다음 단계에 가서야 반영될 수있다. 직접 디버깅해 보면 좋을 것이다.

덧대서 말하자면 Mac에서는 libcrypto.dylib과 libssl.dylib을 링크해야 한다.
Windows에서는 좀 다르다. 현재 비공식 배포판이 있는데, Unix나 Mac의 것과 조금 다른 이름의 라이브러리로 되어 있다.
쉽게 파악할 수있기에 여기서는 설명을 하지 않겠다.

여기서 잠깐 부언 설명을 하자면, output buffer와 input buffer의 데이터 타입이 unsigned char의 어레이로 되어 있다. 이 점은 참 시사하는 바가 있다. 물론 사용할 수있는 암호화 기법마다 키의 길이가 다르고 output으로 나오는 길이가 다르기 때문에 int나 long이나 혹은 long long과 같은 데이터 타입을 사용하기가 좀 그렇다. char의 어레이로 하는 편이 여러가지로 난데, 여기엔 아마 또 하나의 고려사항이 있는 듯하다. byte ordering이 그것이다. 다양한 플랫폼으로 포팅을 할 때, 유념해야 할 사항 중의 하나가 바로 byte ordering이다. 의외로 미국에는 특히 인도계나 중국계가 그런데, Unix를 모르는 전산과 출신들이 많다. 컴퓨터하면 Windows만 아는 것이다. 근데 생각해 보자, 전산을 한 사람이 Windows는 모를 수있어도, Unix를 모른다는게 말이 되는가? 내가 본 소스 중에 이 byte ordering을 생각하지 않고 막 짠 것들이 꽤 있다. 근데, 자기가 테스트 한 머신에서는 되니까 그냥 되는구나하고 submit를 해 버린거다. 경력 15년이란다.. 어이그.. 내가 그런 것들 밑에서 일한다는게.. 정말..
아무튼… 그 소스 코드는 Unix를 모르는, Windows만 아는 사람이 만든거다. Unix를 쓰면 바로 Byte Ordering에 노출될텐데 말이지. 하긴…Intel Processor가 골치 아픈 구조이긴 하다. 세상의 거의 모든 Processor들이 다 Big Endian인데, Intel Processor들은 혼자서 Little Endian이다. 이거 참 웃기는 구조다. 통신도 Big Endian이 기본이다. 거의 모든 표준 규약이 Big Endian을 기반으로 한다. 왜 인텔의 프로세서 디자이너들은 little endian으로 만들었을까? 하다 못해 ARM core도 인텔이 fab을 한건 little endian으로 돈다. 다른 업체들이 만든건 Big Endian으로 돌지..
뭐 투덜대는건 그만하고… OpenSSL의 이런 구조는 porting할때 상당히 편하게 만드는 구조다. 이런 면은 참 생각을 잘하고 만든거 같다.

근데.. 그래도 역시 처음 사용하는 사람을 어리둥정하게 만드는 것은, 어디서부터 시작해야할지 모르게 만드는 함수 이름들과 MAN 페이지의 설명.. 이 OpenSSL을 보면, C++의 namespace보다 Objective-C처럼 클래스나 메소드 이름을 잘 정하는 것이 실제로 더 생산성을 높이고 관리하게 편하게 만드는 것이라는 것을 알 수있다. 물론 기본을 잘하고 namespace가 있으면 더 좋겠지만, 실제론 그런가?

근데 Cocoa나 Foundation 클래스 중에 이 OpenSSL을 wrapping해서 만든게 분명 있을텐데… KeyChain에 관련된 것과 CFAuthorization인가 등이 관련이 있을거 같은데 (그리고 특히 Keychain에 관련된 것들은 확실히 사용하고 있다.), 아무리 봐도 직접적으로 DES나 RSA, MD5, blowfish등을 언급한 도큐먼트가 Cocoa엔 없다.. 이거 참……

참.. 그리고 Mac에는 이거 외에도 또 암호화 기술을 사용할 수있게 하는 것이 있다. CC_로 시작되는 건데, 이름하여 Common Crypto API다. 몇가지 함수 이름을 보자면 CC_MD5_Init, CC_SHA1_Update, CCCryptorCreate 등등이다.

지금보니 뭔가 구조가 OpenSSL의 것과 비슷하다.
CCCryptorCreate(), CCCryptorUpdate(), CCCryptorFinal()… 비슷하지 않은가?
함수 이름들이 훨씬 정리가 잘 되어 있다. 근데 Mac이외에는 이 Common Crypto API의 MAN페이지가 검색되지 않는 것으로 보아, Apple사가 만들고 Mac에서만 쓰이는 것 같다.
혹은 BSD? 음.. 제목쪽에 BSD Library Functions Manual 라고 써져있는 것으로 보아 BSD Unix에서 쓰이는 것인가보다.

ADDED : 보니까 이 Common Crypto library는 Apple의 Open Source인 것같다.
여기 링크가 있다. http://developer.apple.com/opensource/security/

11
Nov
09

SSH vs. SSL : 바보 같은 설명

미국에서 살다보면 이런 저런 사소한 것에서 한국 사람들과 미국 사람들이 어떻게 다른지 느끼게 된다. 이것은 참으로 사소해서 누군가가 대놓고 “뭐가 다른데?”하고 물어보면 선뜻 입 밖으로 말이 잘 안나오게 된다. 흡사 “Mac이 Windows랑 다른게 뭐야?”라는 질문과도 비슷한 질문이다. 근데 이 사소한 것들이 참 큰 영향을 미친다. 흔히들 말하지 않는가? 사람들은 대개 사소한 것으로 싸운다. 큰 일은 싸움 자체가 잘 되지 않는다. 그런거다.

앞의 글을 포스트하고서 SSL을 이야기 하다가 갑자기 SSH가 튀어나와서 혹시나 딴지 걸 거 같아서, 웹을 찾아 보았다.
도대체 SSL과 SSH가 뭐가 다른가? 아마 인터뷰하다가 갑자기 이런 말이 튀어 나오면 대답 못할 거 같다. 몰라서 대답 못하는것이 아니라, 생각을 정리할 시간, 그리고 뭐가 달랐더라를 생각해야 하기 때문이다.
아무튼 찾은 것을 보자.

WsFTP : Users’ Guide

  • SSL : SSL (Secure Socket Layer) is a protocol for encrypting and decrypting data sent across direct internet connections. When a client makes an SSL connection with a server, all data sent to and from that server is encoded with a complex mathematical algorithm that makes it extremely difficult to decode anything that is intercepted.
  • SSH : SSH (Secure Shell) is a security protocol that allows you to make a secure connection to a server that has the SSH and SFTP (Secure File Transfer Protocol) protocols installed. Where FTP servers usually `listen’ on port 21 for connection, SSH servers use port 22.

참 미국적인 설명이다. 뭔가 거창하게는 써져 있는데, 별로 이해하는데 도움이 되지 않는다. 특히나 SSH에 대한 설명이 압권이다. security protocol이라고? 그러면 SSL은 security protocol아닌가? 그리고 SSH와 SFTP가 설치된 서버에 접속할때 쓰이는 거라는데.. 이건 뭐 설명도 아니다. 왜 그런지 다른 것과 비교해서 볼까? 누군가가 Web site가 뭐에요?라고 물어 본다고 하자. 그럼 대답이 Web server로 서비스하는 사이트야. 음… 이게 대답인가? 전혀 대답이 안되어 있지 않은가? Web이 뭔지 Web site가 뭔지, Web server로 서비스를 하는게 뭔지.. 전혀 설명이 안되어 있지 않은가? 더 쉽게 비유해 볼까? “야구가 뭐야?”란 질문에 “야구 선수들이 하는 게임이야”라고 답해주는 것과 아무런 차이가 없지 않은가?

미국 사람들이 이런 식의 말을 참 잘한다. 그럼 더 물어 보려고 해도 상당히 미묘하게 물어봐야 하기 때문에, 영어가 짧은 외국인 입장에선 참 뭐라고 물어보기가 힘들다. 지네는 뭐가 잘못된 것인지 모른다. 이런 바보들…

자 쉽게 설명해 보자. ( 쉽게 설명하면 개념 자체를 잡기는 쉬우나, 세세하게는 틀릴 수있다. 자세하게 설명하면 내용은 정확하나 이해하기 어려울 수있다. 그래서 항상 먼저 쉽게 설명하는 식으로 개념을 잡고, 더 알고 싶으면 자세하게 살펴보는게 좋다.)

SSL은 Secure Socket Layer의 약자다. 즉 통신을 하는데, OSI 7레이어 모델이 있지 않은가? 그런식으로 소켓 레이어가 있다. link 레이어 위에 쯤 속하겠지? 그게 network layer인가 그렇지? 음.. 어떻게 보면 link layer에 들어가겠는데?
아무튼.. TCP/IP로 통신을 하려면 socket을 만들어야 한다. 그리고 통신의 주체 사이에 이 소켓을 통해서 데이터를 주고 받게 된다. 바로 이 레이어에 암호화 기술을 넣어서, 보내고 받는 데이터가 다 암호화가 되어 있고, 그것을 편하게 구현하고 사용할 수있게 만들어 놓은게 SSL이라는 것이다. 그러므로 OpenSSL에는 각종 지원하는 암호화 기법이 다 함수로 들어가 있고, 여기에 덧대어서 바로 SSL secure socket을 만들고 관리하고 없애고 하는 함수들이 들어가 있다.
즉 이것은 end-user용 프로그램이나 그런 개념이 아니라, 프로그래머들이 이용해 주어야 할 개념이다.

SSH는 Secure Shell의 약자다. Unix를 쓰면 Bourne Shell이니 C-Shell이니 TCShell이니 하는 거, 그냥 공기같은 개념 아닌가? 예전엔 원격 컴퓨터를 이용하는 방법엔 두가지가 있었다. 하나는 telnet이고 다른 하나는 rlogin이다. 뭐 요새같은 GUI의 시대야 Apple Remote Access나 MS Remote Desktop혹은 VNC같은 것들이 바로 그런 용도인데, 옛날에는 Command-Line 환경이니까, Unix 명령을 내릴 수있는 프롬프트가 뜨는게 다였다. 암튼 그런식으로 원격지의 컴퓨터를 접속할 수있는데.. 문제는 이때 사용되는 ID와 password가 그냥 clear-text로 보내지는거다. clear-text란 암호화가 되지 않고 그냥 그대로 가는 것을 말한다. 그래서 통신 포트를 가로채서 packet 검사를 해보면 사람들이 로그인할때, 어떤 아이디에 어떤 암호를 쓰는지 다 볼 수가 있었다. 91년이었나 92년이었나.. KETEL엔가 접속할 때였다. 번개가 치는 비오는 날이었는데, 노이즈가 싹 끼는거다. 정말로 우연히 KETEL 시스템의 관리자로 들어가진 적이 있다. 아마도 로그인 정보가 번개와 비에 의한 노이즈로 변경이 가해졌고, 아마 그게 우연히 관리자의 것과 같았나보다. 여담이지만 갑자기 그게 생각나네…
뭐 순간 rm -r / 하면 께임 끝! 인데…… 그래도 착했던 나는 살포시 log-out을 해 주었다.
암튼.. clear text를 그대로 보내면 이런 저런 위험이 많이 도사린다. 노이즈에 의한 거야 뭘로도 어쩔 수없는 거겠지만, 감청/도청은 막아야 하지 않겠는가? 그래서 일종의 접속 단말 프로그램으로 ssh가 나왔다. 이 ssh는 rlogin처럼 원격 컴퓨터에 대한 Unix prompt상태로도 들어갈 수있게 해주고 ssh -x하면, 원격 컴퓨터의 X-Window 환경을 내 로컬 컴퓨터에서 띄울 수있다. ( local X-Window를 이용해서). 즉.. end-user 프로그램에 가까운 개념이 SSH이다. 물론 여기서도 암호화가 사용되기 때문에.. 왜냐하면 정보를 clear-text로 보내면 안되니까… 역시 암호화 관련 함수가 있게 마련이다.

에..이렇게 한번 쉽게 정리해 보았다.
다음에라도 보면, 쉽게 기억 나겠지. :)

하지만.. 이해하기 쉬우면 틀린 내용도 많은 법.. 쉽게 이해하자면 그렇다는 소리다.

11
Nov
09

OpenSSL 사용법

OpenSSL을 사용하려고 하면 몇가지 문제에 봉착하게 된다.

우선 쉽게 설명이 잘 되어 있는 문서가 전무하다. OpenSSL 웹사이트를 가면 몇가지 설명을 볼 수가 있는데, 대개 제대로 설명이 안되어 있다.  예를 들어 DES로 encryption과 decryption을 한다고 하자. MAN -s 3 ssl 로 OpenSSL에 대한 설명을 보면, 뭘 어디서부터 손대야 할지 알 수가 없다. 그래서 찾다 찾다 DES를 직접 건드려보기로 했다. 이때 중요한 함수가 몇개 있다.

void DES_random_key(DES_cblock *ret);

int DES_set_key(const_DES_cblock *key, DES_key_schedule *schedule);
int DES_key_sched(const_DES_cblock *key, DES_key_schedule *schedule);
int DES_set_key_checked(const_DES_cblock *key,
       DES_key_schedule *schedule);
void DES_set_key_unchecked(const_DES_cblock *key,
       DES_key_schedule *schedule);

void DES_set_odd_parity(DES_cblock *key);
int DES_is_weak_key(const_DES_cblock *key);

void DES_ecb_encrypt(const_DES_cblock *input, DES_cblock *output,
       DES_key_schedule *ks, int enc);

여기서 어떤 함수를 써야 하는지 파악하기 위해서, 간단한 예제라도 있으면 좋겠는데, 전혀 그렇지 않다.
굳이 찾자면, OpenSSL의 test 폴더에 있는 샘플 코드를 보는 것이다.
아무튼 MAN 페이지를 아무리 찾아봐도, 저 schedule 변수가 뭐할때 쓰는건지, checked와 unchecked key의 차이는 뭔지 알 수가 없다. 물론 이것은 암호화 자체의 spec에 준하는 함수이기 때문에 DES 자체를 이해하는 것이 필요하다. 하지만 DES 문서를 봐도 때로는 알 수가 없다.
그러다가 OpenSSL의 mailing list에서, 직접 특정 암호화 함수를 사용하지 말고 high-level 함수를 이용하는 것이 더 좋다라는 답변을 들었다. 음.. 그게 뭐지? 힌트로 얻은 것이 EVP_EncryptInit_ex()였다.
아.. command라인에서 쓰더라도 openssl 명령을 쓰고 그 옆에 어떤 암호화 기법을 쓸지를 정해주지 않던가? (물론 바로 암호화 명령을 써도 되지만) 음.. 함수들도 그렇게 구성이 되어 있구나.. 그런 것을 알게 되었다. 근데 이런 설명이 MAN 페이지엔 제대로 되어 있지 않다. (혹 이 시점에서 MAN 페이지 구석 구석 찾아보고 “있는데?” 하는 분들 있을지 모르겠다. 여기서 말하고자 하는 건, 눈에 확 띄지 않는다는 뜻이다. )

그래서 MAN 페이지를 뚤어지게 찾아보다가 찾아내서 만든 코드가 아래의 코드다.

- (char *)generateCipherText:(NSString *)inputString withCipherTextLength:(int *)cipherTextLength
{
	BOOL isSuccessful = YES;

	const char *inputCString = [inputString cStringUsingEncoding:NSASCIIStringEncoding];
	int sourceLength = strlen( inputCString );

	int result = 0;
	unsigned char inputBuffer[16], outputBuffer[16];
	int outputLength = 0;
	int inputLength = 0;

	bzero( inputBuffer, 16 );
	bzero( outputBuffer, 16 );

	char *finalBuffer;
	finalBuffer = (char *)malloc( 64 );
	bzero( finalBuffer, 64 );

	// Initialize cipher context
	EVP_CIPHER_CTX ctx;
	EVP_CIPHER_CTX_init( &amp;ctx );

	// Copy a key
	unsigned char keyForDES[8];
	memcpy( keyForDES, kKeyText, 8 ); // &quot;LXFExpor&quot;

	result = EVP_EncryptInit(&amp;ctx, EVP_des_ecb(), keyForDES, NULL);
	if( result )
	{
		int byteLeft = sourceLength;
		int bytesToCopy;
		int inputLoc = 0, outputLoc = 0;

		while( byteLeft &gt; 0 )
		{
			if( byteLeft &lt; 8 )
			{
				bytesToCopy = byteLeft;
				byteLeft = 0;
			}
			else
			{
				bytesToCopy = 8;
				byteLeft -= 8;
			}

			bzero( inputBuffer, 16 );
			memcpy( inputBuffer, inputCString+inputLoc, bytesToCopy );
			inputLoc += bytesToCopy;

			bzero( outputBuffer, 16 );
			inputLength = bytesToCopy;
			result = EVP_EncryptUpdate(&amp;ctx, outputBuffer, &amp;outputLength, inputBuffer, inputLength);
			if( result )
			{
				memcpy( finalBuffer+outputLoc, outputBuffer, outputLength );
				outputLoc += outputLength;
			}
			else
			{
				isSuccessful = NO;
				break;
			}
		}

		bzero( outputBuffer, 16 );
		result = EVP_EncryptFinal( &amp;ctx, outputBuffer, &amp;outputLength );
		if( result )
		{
			memcpy( finalBuffer+outputLoc, outputBuffer, outputLength );
			outputLoc += outputLength;

			*cipherTextLength = outputLoc;

			isSuccessful = YES;
		}
		else
		{
			isSuccessful = NO;
		}
	}
	else
		isSuccessful = NO;

	EVP_CIPHER_CTX_cleanup(&amp;ctx);

	if( isSuccessful == NO )
	{
		NSGetCriticalAlertPanel( @&quot;Cirtical Information&quot;, @&quot;It couldn't create a registration file.&quot;, @&quot;OK&quot;, nil, nil );

		return (char *)-1;
	}
	else
		return finalBuffer;
}

위의 코드는 암호화를 하는 부분, 즉 cipher 과정이다. 혹 암호화에 익숙하지 않으신 분들에겐 encryption이란 말이 더 편하게 들릴지 모르겠다. encryption == cipher, decryption == decipher로 이해하시면 되겠다.

간단해 보이지 않는가? 암호화 하는 부분만 떼어내면 참 간단한 코드다. 그런데 이게 예가 MAN page에 나오질 않는다.
Oreilly에서 OpenSSL에 관련된 책이 단 한권 나오긴 한다. 물론 SSL에 대한 것이어서 암호화에 대한 것보단 socket을 열어서 SSH로 connection을 하는 쪽에 더 촛점이 맞추어져 있는거 같긴하다. 그 부분으로써 암호화에 대한 설명이 나온다. 왜냐하면 SSH 세션을 열면, 그 안에서 통신의 안전을 위해서 암호화 기법이 사용되기 때문이다.

근데 보기엔 간단해 보이지만, 저 코드를 완성하는데 꼬박 하루가 걸렸다. 왜일까?
OpenSSL의 구현에 묘한 점이 있기때문인데, 바로 EVP_EncryptUpdate()이 호출되는 부분이다. 앞에서 언급한 “도대체 어디서부터 시작해야 하나”라는 것이 OpenSSL을 사용하기 힘들게 만드는 첫번째 이유라면, 두번째가 바로 이 함수때문 같다. (암호화 자체를 이해하는거야… 당연히 암호화에 대해 아는 사람이 OpenSSL을 시도하는 거라고 생각하니 제끼고.. )

이 함수가 동작하는데 좀 묘한 면이 있는데.. 사실 Encryption을 할때와 Decryptiond을 할때가 조금 패턴이 다르다.
즉 이 함수가 호출될때, input에 들어있는 original text가 output에 encryption이 되어서 바로 들어갈때도 있고, 아닐때도 있다는 것이다. 아마 기억에 encryption때는 바로 바로 들어갔던거 같고, decryption때가 바로 안들어가고, 그 다음 iteration때 들어갔던 것 같다. 그때, outputLength가 실제 변환된 것만큼 되지 않고 0으로 되더라는 것이다. 그래서.. 아.. 뭘 잘못 사용하고 있나보다 하고 그거 알아내려다가 하루 웬 종일 지나갔다. 알고보니 다음 단계에서 세팅되는 것이다.
아마 decryption때 그런것 같은데, encryption때는 EVP_EncryptFinal()가 호출되기 직전, 즉 마지막으로 encryption을 할때 그랬던 것같다. 아 맞다. 그런 거 같다. 그래서 outputLength가 0이 되더라도 놀라지 말고 끝까지, 즉 EVP_EncryptFinal()를 호출하라는 것이다.

OpenSSL이 이렇게 움직이는 이유는 bit stuffing때문인거 같다. MPEG4/H-264의 CABAC을 구현할때랑 JPEG Encoder/Decoder를 구현할때, 이 bit stuffing을 많이 사용했는데, 처음 구현할때는 고생했다. (USC에서..) 근데 회사에서 하니 아무래도 한번 해 본거라, 대충 어떻게 할지 방향이 이미 잡혀 있기에 고생 자체는 안했다. 시간이 들었을 뿐.
근데 내가 bit stuffing을 구현하는 것은 modulo 연산을 이용해서, 실제 일정 길이의 비트가 필요하기 전에, 그것을 충분히 대응해 줄 수있게 버퍼를 미리 채워주게 구현해서 아무런 문제가 없는데, 아마 그렇게 구현하지 않으면 (modulo 연산이 생각보다 헷갈린 면이 있어서 ), 해당 스텝내에 encoding 혹은 decoding을 하려 하면, 일정양의 비트 데이터를 확보해야 하는데, 미처 준비가 안될 수있다. 그럼 다음 스템으로 그 처리를 넘기면 그때는 확실하게 확보가 되기 때문에 (아닐 수도 있다. 데이터의 끝부분에서 미쳐 준비가 안되는 일이 생기곤 한다. ) 그렇게 구현이 되었겠다 싶다.

이런 설명이 있어야 한다. 없으니 제대로 동작하는데도 마치 안되는 것처럼 생각되어서 하루를 낭비하지 않는가?
으… USC에서 내 개인 프로젝트 못하고 이것으로 하루 웬종일 썼다.

다음에는 decoding 부분을 포스팅해 보겠다.

08
Nov
09

기술 블로깅에 대해서

궁금한 점이 있다.

프로그래머들을 보면 전문성이란 측면에서 한국 사람들이 확실히 떨어지는 것을 본다.
깊이가 없고, 기술적인 배경보다는 운용의 묘를 더 살리는 것 같다. 삼성이란 기업에서도 그랬었다. 삼성이나 금성이 제품을 만들때, 대부분의 기술은 외국의 기술을 가져다 쓴다. 그것을 용도에 맞게 변경하고 추가할 거 추가하고 그런다. 그리고 기술적인 판단을 내릴때 그다지 기술적이지 못한 결정을 내린다.

그리고 인터넷에 떠다니는 정보들을 보면, 유럽이나 미국에서는 굉장히 기술적인 내용이 많고 잘 되어 있다. 개념 설명부터 시작해서 실제 구현이나 각종 것들.. 그리고 오픈 소스를 봐도 수준 차이가 한참 많이 난다는 것을 쉽게 확인할 수있다.

왜 이런 차이가 날까? 이곳 미국에서 살다보니 느끼는 것이 있다. 한국 사람들.. 머리가 참 좋다. 물론 다른 나라 사람들도 머리가 좋다. 그런데 머리가 좋은 방식에 차이가 있다. 한국 사람들은 감정을 내세우지 않는다면, 그리고 기다려 줄 줄 안다면 금상첨화로 좋은력과 머리를 가지고 있다. 하지만 거기에 맹점이 있다.

또한 생활 방식에 있어서의 차이가 기술 블로깅등에서의 차이점을 만드는 것 같다.

여기 미국에서 보면, 사람들이 자기네가 얼마나 힘들게 일하는지 꽤나 말하는 것을 볼 수가 있는데.. 제발 좀 한국 사람들이나 일본 사람들 일하는 것 좀 보고 말해 주었으면 좋겠다. 모든 것은 상대적이지만 특히 시간이란 것, 그리고 노력이라는 면은 굉장히 상대적이다. 한국 사람들이 천천히 하자라고 할때와 미국 사람들이 천천히 하자라고 할때, 그 속도에 심각한 차이가 있다. 한국 사람들의 천천히는 미국 사람들의 빨리 빨리 보다 훨씬 빠르다. 이건 내가 몇년을 이곳에 살면서 느낀 것이다. 뉴욕이 미국에선 참 바쁜 곳이라고 하는데, 거기서도 아침 일찍 가게를 여는 사람들은 역시 한국 사람들이다. 서울에서 느껴지는 긴박감이 뉴욕에선 그다지 느껴지지 않는다. 물론 뉴욕은 바쁜 도시다.. 하지만 역시 상대적인 것이다.

한국에서는 회사에서 11시 12시 새벽 1시 2시에 퇴근한다. 그나마 퇴근 제대로 하면 다행이다. 퇴근 후 회식으로 포장되는 음주는 업무의 연장이라는 미명하에 자행되곤 한다. 그리고 집에 들어오면 마누라들이 한마디씩들 하겠지. 대학 졸업후, 혹은 대학에서도 어떤론이나 어떤 아이디어에 빠지면, “철없는 놈” 혹은 “실제를 모르는 놈”, “세상을 모르는 놈”으로 치부되는게 한국이다. 나이가 들어 30세 40세 50세가 되면 전문성이 더 높아지고, 기술적 수준이 깊어지고, 그것을 바탕으로 블로깅을 하고, 정보를 공유하면서 또 새로 배우고 하는 그런 바탕 위에서 블로깅이 있어야 하는데 (이과적 블로깅 이야기다. 물론 문과도 방향은 다르겠지만 비슷한 면이 있을 것이다. ) 한국에서는 대개 기술 블로깅이나 BBS등을 들어가면 대개 깊지 못하고 몇년이 지나도 초심자의 수준에서 머물러있다.  원리를 앍고 생각하면 그런 질문이나 글이 나올 수가 없는데, 몇년이 지나도 거기서 거기다.

이 말은 무슨 뜻일까? 정말 실력있는 사람들은 바빠서 블로깅 할 틈이 없다는 면도 있겠고.. 혹은 이미 30 중반만 되도 명퇴를 걱정해야 하는 사회 풍조.. 그리고 기술이 있는 사람들을 나이가 들었다는 이유로 가벼이 여기는 풍조.. (회사에서 나이가 많이 들었다는 이유로 짜르는 것은 정말 웃기는 이야기다. 나이가 있기에, 실패의 경험도 많고, 되는 이유와 안되는 이유도 알고, 생각도 깊어진다. 그것이 얼마나 중요한 것인지 모르는가들? ) 그리고 대학 졸업후엔 공부하지 않는 풍조…

아니 대학때도 마찬가지다. 전공 공부가 더 중요한 것인데 우리네는 쓸데 없이 지나칠 정도로 외국어 공부(라고 말하지만 영어 공부)와 취직 준비로 바쁘다. 회사에선 필요한 사람을 뽑기 위한 것인데, 그 시험이 시험을 위한 실력 쌓기를 불러 일으키는 것이다. 익히 아는 바지만 시험 점수가 좋은 것과 실력이 좋은 것은 딱히 일치하진 않는다. 물론 완벽한 평가 기준이란 정말 힘든 것이어서 그나마 객관적인 것을 찾으려다 보니 시험을 보게 되는 것인데, 그렇다고 해서 시험이 만능이 되면, 지금 한국 사회와 같은 문제가 생기는 것이다. 방송에선 그것을 시청률 지상주의라고 하지. 외국에선 시청률은 참고 사항이라는데..

아마 결혼해서도 집에서 전공 공부를 하고, 이리 저리 해보면, 당장 마누라에게 듣기 싫은 소리를 들어야 할것이다. “그거 한다고 밥이 나와?” “그래봤자 회사 짤리는 건 마찬가지야”.. 나이가 들어 “현실”이란 것에 눈을 뜨면, 더 이상 기술, 과학.. 그런 것은 뒷전이 된다. ( 이공계 월급 더 주자, 대우 잘해주자가 문제의 핵심이 아니다, 이 국회의원과 나랏님들아..  물론 일하고 가치를 만드는 것만큼 대우가 있어야겠지. 그것은 그것대로 해결해야지만, 문제를 해결하는 핵심이 아니라는 것이다. )

이러니 기술이 깊어질 수가 있을까? 이러니 제대로 된 오픈 소스나 블로그 글들이 올라올 수있을까?

나의 이 허접한 블로그도 참 창피한 것이어서, 내 입으로 이런 말을 하는 것도 얼굴 화끈해지는 일이지만.. 그래도 할 말은 해야지.
(여기서 사카이 같은 사람들의 답변은 정중히 거부합니다.)

한국에도 훌륭한 블로그나 오픈소스들이 나와야 하는거 아닌가?

미국에 살다보니 왜 사람들이 오픈 소스를 하는지 알겠다. 한국에선 철없는 사람들이 하는 것으로 치부들 하지. 그거 돈도 안되는거 왜 하냐고. 근데 미국에선 그게 돈이더라. 얘네들이 자본주의 동물들인데, 돈 안되는거 하겠는가? 단지 직접적으로 돈이 안되는 것일 뿐이다. 간접적으로 그리고 그게 미끼가 되어 더 돈이 된다. 뭐라고 설명하긴 힘든데, 여기서 살아들 보시면 그 모습을 보게 된다. 이 놈들이 어떤 놈들인데 시간 낭비하면서 남 좋은 일 할까…

한국에서도 좀 회사 업무 외에 개인 시간들이 나면 좋겠고, 물론 그 바탕엔 한국 사회의 “현실 지상주의”가 좀 변화가 생기면 좋겠다는 생각이 든다. 삼성이나 LG에서 비디오 사업부에서 개발하거나 연구 총괄같은데 있는 사람들이 개인 시간에 멋진 MPEG 라이브러니나, 인코딩 기술, 회사에선 좋은 아이디어인데도 윗사람들이 몰라줘서 못하는 것들.. 그런 것을 해 볼 수있다면, 그것들이 만들어 내는 기회 비용이 얼마나 크겠는가? 서울대, 과기대 포항공대만이 다가 아니다. 내가 삼성에 들어가면서 신입사원 연수교육을 받을때 느낀 것이 있다. 정말 그때 합숙하면서 많은 생각을 했다. 소위 사람들이 무시하는 대학 출신의 사람들…

정말 우수하고 좋은 사람들이 너무 많다. 단지 고등학교때, 그리고 달달 암기와는 거리가 있는 사람들이었을 뿐, 그 사람들이 멍청하거나 그런게 아니다. 다른 방식으로 머리가 좋은 사람들이다. 그 사람들이 힘을 함쳤을때 만들어 낼 수있는 가치.. 엄청난거다.

일본은 외국 유학한 사람들보다 자기네 나라 학교를 나온 사람들을 더 쳐준다고 한다. 일본 학교는 좋으니까라고 할 수도 있겠지만, 일본이라고 어디 처음부터 그랬을까? 한국 사람들도 충분히 훌륭하다. 단지 자기네들을 스스로들 인정하지 않는다는데 문제가 있고, “그거 해봐야 되겠어?”라고 미리 초치는 분위기가 만연해 있기 때문이다. 뭐든지 가방끈으로만 판단하고..

미국도 가방끈 긴 놈이 일단 연봉은 많은 것은 사실이지만, 회사만 좋다면 (이게 무슨 의민지는 아시리라 본다. 단지 회사 이름이 유명하고, 연봉 많이 주는 것을 의미하는 것이 아니다. ) 굳이 석박사 따지 않은 사람들도 엄청 연봉 높고 잘 나가는 사람들 많다.

높이 나는 새는 멀리 본다. 낮게 나는 새는 자세히 본다.

한국 내에 있으면 한국의 단점이 너무 잘 보인다. 외국에 있으면 단점이 있음에도 불구하고 장점도 많다는 것을 본다.
외국에 있으면 애국자가 된다고들 한다. 단지 한국이 그리워서 그러는게 아니다. 실제 외국에서 살다보니, 한국이 참 안타까운 점이 많다. 이야.. 정말 좋은 요소를 많이 가지고 있는데.. 왜 다른 조그만 것들에 얽매여서 그 장점을 발휘하지 못하나..

얼마전에 YouTube에서 옛날 상공회의소 대표였던 독일 출신 이.. 누구 (지금 어느 정부 소유 회사의 사장이 된거 같던데) 선생의 강의가 비디오로 올라온게 있다. 그것을 보고 무릎을 탁쳤다. 그런 사람 말에 귀를 기울여야 한다.

하지만 한국 내에선 “야.. 우리가 이렇게 가능성 많데. 훌륭하데.. ” 그런 수준에서 자위나 하고 말겠지..
“이승엽의 데드볼로 요미우리 자이언츠의 공격 물꼬가 열렸다” 이런 식으로… 본질이 그게 아닌데 말이지..

한국이여.. 부디 잘되어라. 대륙의 고구려와 해상왕국 백제의 기상을 물려 받아 다시 펄펄 나는 그때가 와야 한다.

단 남보다 잘 나갈때, 남을 보듬고 겸손할 수있는 그런 성품을 가지면서 말이다.

(발해가 망할때.. 그 밑에 있던 말갈/거란 족들이 고구려 인들을 무척이나 증오했었다고 한다. 왜 그랬을까? 그런 역사는 반복되지 말아야 한다. 제발들 좀 남에게 보이려고 좋은 옷 사고, 좋은 시계 차고, 좋은 차 사지 말자들.. 내실을 갖추고 겸손하자들.. )

 




 

November 2009
S M T W T F S
« Oct    
1234567
891011121314
15161718192021
22232425262728
2930  

RSS Feed

Archives