顯示具有 C 標籤的文章。 顯示所有文章
顯示具有 C 標籤的文章。 顯示所有文章

2024年3月13日 星期三

Node.js 開發筆記 - 將 C structure 轉成 Javascript code 流程

在 C code 處理記憶體時,有些偷閒招數是強制型態轉換,這時要移植到 node.js 運行的 js code 有點繞,整個開發過程就不斷在 c code 輸出 structure 的資訊,跟 js code 那端交叉比對,理解後也沒什麼難,就是要計算記憶體位置

C code: 

// 宣告
typedef struct 
{
char flag[16];
unsigned int number1;
unsigned int number2;
unsigned int number3;
unsigned int number4;
} MyHeader;


// 用法:
{
    MyHeader *header;
    header = (MyHeader *)buffer;
}

接著在 js code 要對應處理稍微累了點,但也還行:

const buffer = ....;
const headerOffset = 0;
const headerData = buffer.slice(headerOffset, headerOffset + 32);

headerFlag = headerData.slice(0, 16).toString('ascii');
headerNumber1 = headerData.readUInt32LE(16);
headerNumber2 = headerData.readUInt32LE(20);
headerNumber3 = headerData.readUInt32LE(24);
headerNumber4 = headerData.readUInt32LE(28);

如此可以做簡單的 C Code 轉 node.js Javascript code。

建議移植時,要分別在 C & JS 輸出數值來比對,小步小步進行,避免程式碼很大包,最後出包了難 debug (大概稱得上 test-driven development 吧?)

2022年8月25日 星期四

C 開發筆記 - 使用 libwebsockets 製作簡易的 WebSocket Client

已經有用過 Golang 開發小工具,以及其他現成工具如 websocat 等,做 WebSocket Server 的測試。這次回過頭來再仔細看看 libwebsockets 這套,寫一點 C 語言。

程式架構不會太難,比較難的反而是一些小東西,像是跟 ws.ptt.cc 連線時,PTT 會做 Request Header(Origin欄位) 檢查,當作簡單的管控,然而 libwebsockets 則是要設定  LWS_SERVER_OPTION_JUST_USE_RAW_ORIGIN 才能不加料 XD

#include <include/libwebsockets/lws-context-vhost.h>

For backwards-compatibility reasons, by default lws prepends "http://" to the origin you give in the client connection info struct. If you give this flag when you create the context, only the string you give in the client connect info for .origin (if any) will be used directly.

大概在這種地方耗掉半小時以上。另外,還有做 wss 連線時,也須設定 LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT 等等,以及如果想要略過 wss 的 SSL 憑證檢查,有滿多設定項目可以試試:LCCSCF_USE_SSL | LCCSCF_ALLOW_SELFSIGNED | LCCSCF_SKIP_SERVER_CERT_HOSTNAME_CHECK | LCCSCF_ALLOW_INSECURE;

其他則是依照 libwebsockets 的架構,要設計連線時的 callback 處理機制,算是體驗了一下。未來就多一個 C 語言工具可以做 WebSocket Server 連線測試。


常見的錯誤訊息:
  • E: SSL_new failed: error:00000063:lib(0)::reason(99)
  • W: [wsicli|0|WS/h1/default/ws.ptt.cc]: lws_client_ws_upgrade: got bad HTTP response '403'
  • E: lws_ssl_client_bio_create: Unable to get hostname
執行 logs:

% make
gcc -g -Wall main.c `pkg-config libwebsockets --libs --cflags`

% ./a.out   
Usage> ./a.out url
./a.out ws://localhost:8000/
./a.out wss://ws.ptt.cc/bbs app://cmd
./a.out wss://ws.ptt.cc/bbs https://term.ptt.cc

% ./a.out wss://ws.ptt.cc/bbs https://term.ptt.cc
[2022/08/25 20:45:29:8530] N: lws_create_context: LWS: 4.3.2-no_hash, NET CLI SRV H1 H2 WS ConMon IPV6-on
[2022/08/25 20:45:29:8539] N: __lws_lc_tag:  ++ [wsi|0|pipe] (1)
[2022/08/25 20:45:29:8964] N: __lws_lc_tag:  ++ [vh|0|default||-1] (1)
wsCallback - LWS_CALLBACK_OPENSSL_LOAD_EXTRA_CLIENT_VERIFY_CERTS
[INFO] libwebsockets version: 4.3.2
[INFO] lws_parse_uri - Server: ws.ptt.cc:443
[INFO] lws_parse_uri - URLScheme: wss
[INFO] lws_parse_uri - URLPath: bbs
[INFO] clientConnectInfo.addres: ws.ptt.cc
[INFO] clientConnectInfo.port: 443
[INFO] clientConnectInfo.path: /bbs
[INFO] clientConnectInfo.host: ws.ptt.cc
[INFO] clientConnectInfo.origin: https://term.ptt.cc
[INFO] clientConnectInfo.ssl_connection: 109
[2022/08/25 20:45:29:9789] N: __lws_lc_tag:  ++ [wsicli|0|WS/h1/default/ws.ptt.cc] (1)
call lws_client_connect_via_info done, webSocket is NULL(NO)
noop
[2022/08/25 20:45:30:0229] N: lws_gate_accepts: on = 0
[2022/08/25 20:45:30:0575] N: lws_gate_accepts: on = 0
wsCallback - LWS_CALLBACK_CLIENT_ESTABLISHED
wsCallback - LWS_CALLBACK_CLIENT_WRITEABLE
wsCallback - LWS_CALLBACK_CLIENT_RECEIVE
RECEIVER: HTTP/1.1 200 OK

     ??      PTT                ?P   ??        ?P  ??      ???i?i?i?i?i?i??
             140.112.172.11 ?P     ???i???i??            ???i?i?i?i?i
  ?z?w?{     ?????~?{        ???d?i?i???i??   ?P   ???i?i?i?i?i??  ?P
  ?x?V?|?{   ptt.cc            ???i?i?i?i?i???i??    ???i?i?i?i?i
  ?x?V  ?x                   ???i?i?i???i?i?i????  ???i?i?i?i?i??  ??  ?P
?w?}    ?x?z?w?w?{  ?P       ????      ?i?i?i?i?????i?i?i?i    ?P
        ?|?t  ?V?x      ?P           ???i?i?i?i???i?i?i??            ?P
                ?x?z?w?w?w?{         ?i?i?i?i?i?h?h?g?g?f?f?e?e?d?c?b
            ?z?w?r?}?V?V  ?| ???i?i?i?i?i?h?h?g?g?f?f?e?e?d?d?c?c?b
            ?x?V?V           ???i?i
wsCallback - LWS_CALLBACK_CLIENT_RECEIVE
RECEIVER:       ?i?h?h?g?g?f?f?e?e?d?c?b

wsCallback - LWS_CALLBACK_CLIENT_RECEIVE
RECEIVER:            ?f?d?d?e?g?f?e         ?e?d?g??     ???âg?d?e       ????            
               ?g?h  ?h         ?e?d         ?b      ???c?g?d                   
?п?J?N???A?ΥH guest ???[?A?ΥH new ???U:               
^C   ?b?c?e?f?e?g?h?g?f?d    ?n  ??   ??  ???n?l?? ??  ???? ?l?k  ?h            

2022年6月16日 星期四

C開發筆記 - 使用 JS Engine : Duktape 接收與傳遞 Javascript Object

對嵌入式設備來說,如果資源夠大的話,直接用 Node.js 絕對是個不錯方案,受限於硬體資源限制,採用了 Duktape JS Engine。

寫 C Code 操作 Duktape JS Engine 時,呼叫的方式還滿像以前學生時代寫組語的感覺,要先一步步把參數先配置好,接著在呼叫函示去運算。這次就筆記一下,如何從 JS Code 傳遞 Object 到 C Code,以及 C Code 要怎樣傳遞 Object 回 JS Code。

從 JS Code 傳遞 Object 到 C Code:

//
// _js_helper_fetch_url_more 是從 C Code 植入 function,並且接送一個 JS Object,有 url 跟 request_header 兩個屬性
//
if (typeof _js_helper_fetch_url_more !== "undefined") {
    var ret = _js_helper_fetch_url_more({
        url: 'https://ipinfo.io/country',
        request_header: [
            'Content-Type: HelloWorld',
            'X-MAN: HelloWorld',
        ],  
    }); 
    console.log(JSON.stringify(ret));
}

在 C Code 要取得傳進來的 JS Object,並存取 url 跟 request_header 兩個屬性:

if (duk_is_object(ctx, -1) && duk_has_prop_string(ctx, -1, "url")) {
    duk_get_prop_string(ctx, -1, "url");
    const char *url = duk_to_string(ctx, -1);
    duk_pop(ctx);
}

if (duk_has_prop_string(ctx, -1, "request_header")) {
    duk_get_prop_string(ctx, -1, "request_header");
    if (duk_is_object(ctx, -1) && duk_has_prop_string(ctx, -1, "length")) {
        duk_get_prop_string(ctx, -1, "length");
        int header_length = duk_get_int(ctx, -1);
        printf("#JS_Helper_Fetch_Url_More> access input_object.request_header.length: %d\n", header_length);
        duk_pop(ctx);

        for (int i=0 ; i<header_length ; ++i) {
            duk_get_prop_index(ctx, -1, i);
            const char *header_value = duk_get_string(ctx, -1);
            printf("#JS_Helper_Fetch_Url_More> access input_object.request_header[%d] = [%s]\n", i, header_value);
            duk_pop(ctx);
        }
    }
    duk_pop(ctx);
}

最後,想要從 C Code 傳遞一個 JS Object 回去的方式:

//
// 傳回 {"status": true, "errorCode": 0}
//

duk_idx_t obj_idx;
obj_idx = duk_push_object(ctx);

int errorCode = 0;
duk_push_int(ctx, errorCode);
duk_put_prop_string(ctx, obj_idx, "error_code");

if (errorCode == 0) {
    duk_push_true(ctx);
} else {
    duk_push_false(ctx);
}
duk_put_prop_string(ctx, obj_idx, "status");

更多完整資訊,請參考:github.com/changyy/study-duktape

2019年11月27日 星期三

[Go] 透過 Golang 操作系統內建 WebView @ macOS, WKWebView

最近在幫同事想想,到底有沒有什麼更方便的環境去協助同事分析網頁的組成。再加上自己想順便了解一下 golang 是如何跨平台的,就隨意找了這套:github.com/zserge/webview ,看了看,學到不少東西。

心得一:所謂的跨平台,是別人幫你做完所有苦力

在 webview.h 裡頭就定義清楚在 Windows / Linux / macOS 上所使用的 WebView 元件為何,超佛心的幫你串好常見的用法

心得二:macOS 的 WKWebView 就跟 iOS 內的沒啥兩樣

簡稱想要做到更細的東西,都得靠 hack ,黑來黑去好累啊。像是想要追蹤一個網頁形成過程到底用了哪些 resource ,於似乎想要追蹤所有 request ,接著又要想想該如何向 UIWebView 那麼方便查看,接著又要想想是不是要用 WKURLSchemeHandler ,又該怎樣把系統內定的 http/https 黑回來 NSURLProtocol 處理等等,還是要搞個 proxy mode 追蹤?

想著想著...啊不就換套 CEF 就好 XD

於是乎,我就放棄 zserge/webview 在 macOS 上操弄 WKWebView !不過也趁這個機會我練了一下怎樣從 C call ObjectiveC 或者說從 Go 怎樣操作 ObjectiveC,也是總緣份吧

這次操弄 zserge/webview 大概玩了:

- 欣賞 zserge/webview 大大怎樣提供跨平台呼叫瀏覽網頁的元件

- 從 C 操作 WKWebView 物件,接著再呼叫它的 methods 換掉 User-Agent 資訊

WEBVIEW_API void webview_set_user_agent(struct webview *w, const char *user_agent) {
  // https://developer.apple.com/documentation/webkit/wkwebview/1414950-customuseragent?language=objc
  objc_msgSend(w->priv.webview, sel_registerName("setCustomUserAgent:"), get_nsstring(user_agent));
}


- 從 C 操作 WKWebView 物件,接著再呼叫它的 methods 添加監聽 didStartProvisionalNavigation 和 didFinishNavigation

static void wk_webview_didStartProvisionalNavigation(id self, SEL cmd, id webView, id navigation) {
  // https://developer.apple.com/documentation/webkit/wknavigationdelegate/1455621-webview?language=objc
  webview_print_log("at wk_webview_didStartProvisionalNavigation");
  //id absoluteString = objc_msgSend(objc_msgSend(webView, sel_registerName("URL")), sel_registerName("absoluteString"));
  webview_print_log( get_webview_current_url(webView) );
}

static void wk_webview_didFinishNavigation(id self, SEL cmd, id webView, id navigation) {
  // https://developer.apple.com/documentation/webkit/wknavigationdelegate/1455629-webview?language=objc
  webview_print_log("at wk_webview_didFinishNavigation");
}

// ...

  Class __WKNavigationDelegate = objc_allocateClassPair(
      objc_getClass("NSObject"), "__WKNavigationDelegate", 0);
  class_addProtocol(__WKNavigationDelegate,
                    objc_getProtocol("WKNavigationDelegate"));

// ...

  class_addMethod(
      __WKNavigationDelegate,
      sel_registerName(
          "webView:didStartProvisionalNavigation:"),
      (IMP)wk_webview_didStartProvisionalNavigation, "v@:@@");
  class_addMethod(
      __WKNavigationDelegate,
      sel_registerName(
          "webView:didFinishNavigation:"),
      (IMP)wk_webview_didFinishNavigation, "v@:@@");


收工!

2019年11月16日 星期六

[C] 查看目前程式的記憶體用量 @ macOS 10.15

記得碩士生活很常被記憶體追殺,那時都是靠 unix tools 或是 /proc/ 查看指定的記憶體,都忘了其實是靠 process 自己去查詢資料,這次工作要協助 debug 抓資訊,就把它完成了:

#include <stdio.h>
#include <stdlib.h>

// Memory Usage
#include <sys/types.h>
#include <sys/sysctl.h>

void simple_wait() {
size_t wait_buf_size = 32;
char *wait_buf;
wait_buf = (char *)malloc(wait_buf_size * sizeof(char));
getline(&wait_buf, &wait_buf_size, stdin);
free(wait_buf);
}

void show_memory_usage() {
struct rusage usage;
printf("\n-- Memory Usage -- Begin --\n");

if(0 == getrusage(RUSAGE_SELF, &usage)) {
printf("\tBytes:\t%ld\n", usage.ru_maxrss);
printf("\t= \t%.3f KB\n", usage.ru_maxrss / 1024.0);
printf("\t= \t%.3f MB\n", usage.ru_maxrss / 1024.0 / 1024.0);
} else {
printf("\tREAD ERROR\n");
}

printf("-- End -- Memory Usage --\n");
}

int main(int argc, char *argv[]) {

show_memory_usage();
printf("press enter to continue\n");
simple_wait();

return 0;
}


用法:

$ gcc t.c
$ ./a.out

-- Memory Usage -- Begin --
Bytes: 671744
= 656.000 KB
= 0.641 MB
-- End -- Memory Usage --
press enter to continue


只是在 man page: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/getrusage.2.html 的描述:

ru_maxrss    the maximum resident set size utilized (in kilobytes).

但看數據總覺得是 bytes 啊 XD 先記錄起來,有空再追蹤

2019年7月24日 星期三

[C] 透過 CMAKE 搜尋 OpenSSL 函式庫產出 MD5 資料

最近幫同事 debug embedded linux 問題,恰好需要用 MD5 來做 hash ,隨意找找就用 OpenSSL 函式庫就對了。再加上原先寫的小程式已經靠 CMAKE 維護了,就繼續整合使用

CMakeLists.txt:

$ cat CMakeLists.txt
cmake_minimum_required(VERSION 3.11)
project (MD5)

SET(SOURCE_DIR ${CMAKE_SOURCE_DIR}/src)

# macOS
# $ mkdir b ; cd b;
# $ cmake .. -DOPENSSL_ROOT_DIR=/usr/local/opt/openssl -DOPENSSL_LIBRARIES=/usr/local/opt/openssl/lib
#
find_package(OpenSSL REQUIRED)
include_directories(${OPENSSL_INCLUDE_DIR})

ADD_EXECUTABLE(main
${SOURCE_DIR}/main.c
)
TARGET_LINK_LIBRARIES(main OpenSSL::SSL)


程式碼:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <openssl/md5.h>

const char * get_md5_string(const unsigned char *in, char *output) {
int i;
for(i = 0 ; i < MD5_DIGEST_LENGTH ; i++) {
sprintf(output + (i)*2, "%02x", in[i]);
}
output[MD5_DIGEST_LENGTH*2] = '\0';
return output;
}

int main(int argc, char *argv[]) {
unsigned char digest[MD5_DIGEST_LENGTH];
char readable_digest[MD5_DIGEST_LENGTH*2 + 1];

char *test = "HelloWorld";
char *input;

if (argc > 1)
input = argv[1];
else
input = test;

MD5_CTX context;
MD5_Init(&context);
MD5_Update(&context, input, strlen(input));
MD5_Final(digest, &context);

get_md5_string(digest, readable_digest);

printf("MD5: [%s]\n", readable_digest);

return 0;
}


編譯跟運行:

$ mkdir b
$ cd b ; çmake ..
$ make
$ ./main HelloWorld
MD5: [68e109f0f40ca72a15e05cc22786f8e6]
$ echo -ne "HelloWorld" | md5
68e109f0f40ca72a15e05cc22786f8e6

2016年12月3日 星期六

[C] 使用 SQLite - sqlite3_bind_text 之 value 一直配對(綁定)錯誤

最近整理兩年前的程式,一直有空時會重構,由於時間拉太長了,我已不記得改過什麼,又懶得看 git log,就這樣東摸摸西摸摸,最後發現 bug 了。

有一張表:

CREATE TABLE IF NOT EXISTS test (
id INTEGER PRIMARY KEY AUTOINCREMENT,
data TEXT NOT NULL
);


接著,不斷新增資料,並且透過 sqlite3_bind_text 維護:

INSERT OR IGNORE INTO test (data) VALUES (?),(?),(?)

然而,透過 sqlite3_bind_text 綁定時,一直出錯,起初以為是 UTF-8 問題,但早期程式也沒問題,透過觀察,發現記憶體不如預期,追了好一陣子,發現是 sqlite3_bind_text 的參數問題 Orz

https://www.sqlite.org/c3ref/c_static.html
Constants Defining Special Destructor Behavior

typedef void (*sqlite3_destructor_type)(void*);
#define SQLITE_STATIC      ((sqlite3_destructor_type)0)
#define SQLITE_TRANSIENT   ((sqlite3_destructor_type)-1)


原因在於我一直用 SQLITE_STATIC 啦,但我的結構已經是 std::vector<std::unordered_map<std::string, std::string>> values 這種稍微複雜的,並且透過 iterator 不斷變換資料,因此,必須改用 SQLITE_TRANSIENT 才對。

std::vector<std::unordered_map<std::string, std::string>> values;
std::vector<std::string> fields ({ "data" });
for (auto item : values) {
for (auto field: fields) {
if (item.find(field) != item.end()) {
if (SQLITE_OK != sqlite3_bind_text(stmt, i, item[field].c_str(), -1, SQLITE_TRANSIENT)) {
}
} else {
if (SQLITE_OK != sqlite3_bind_null(stmt, i)) {
}
}
}
}


最後還是埋了個 gtest 定期測試了。

2014年8月10日 星期日

iOS 開發筆記 - 透過 CocoaPods - FMDB / FMDatabase 管理 SQLite Databases

好久沒用 C/C++ 處理 SQLite 的操作,原本有意直接在 Objective-C 一樣寫 C 來處理,但想起來最近一直把玩 CocoaPods ,就搜尋一下,發現 FMDB 還滿多的推薦的,且 github.com/ccgus/fmdb 上頭也有很猛的人數 XD 就下海來使用 FMDB 啦!

主要看中 FMDB 的特色:提供 Data Sanitization 機制!就是寫 PHP 時,會透過 mysql_real_escape_string 來處理 raw data ,避免資料格式破壞 SQL 語法。

把玩筆記:

#import "FMDatabase.h"
// $ vim Podfile
// pod 'FMDB'

- (void)insert {
// Documents/test.db
NSString *dbPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0] stringByAppendingPathComponent:@"test.db"];
BOOL needInitTable = ![[NSFileManager defaultManager] fileExistsAtPath:dbPath];
FMDatabase *db = [FMDatabase databaseWithPath:dbPath];
if ([db open]) {
// init table
if (needInitTable && ![db executeStatements:@"CREATE TABLE IF NOT EXISTS t (id VARCHAR(8), number INT)"]) {
NSLog(@"table init error");
return;
}

// insert data
if (![db executeUpdate:@"INSERT OR IGNORE INTO t (id, number) VALUES ( :id, :number )" withParameterDictionary:@{
@"id" : @"id_data",
@"number": @(12345)
}] ) {
NSLog(@"insert error");
}
[db close];
}
}

- (NSArray *)query {
// Documents/test.db
NSString *dbPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0] stringByAppendingPathComponent:@"test.db"];
BOOL needInitTable = ![[NSFileManager defaultManager] fileExistsAtPath:dbPath];
FMDatabase *db = [FMDatabase databaseWithPath:dbPath];
if (!needInitTable) {
if ([db open]) {
NSMutableArray *output = [[NSMutableArray alloc] init];
FMResultSet *rs = [db executeQuery:"SELECT id, number FROM test;"];
while ([rs next]) {
// records to NSArray
NSMutableDictionary *item = [[NSMutableDictionary alloc] init];

item[@"id"] = [rs stringForColumn:@"id"];
item[@"number"] = @([rs intForColumn:@"number");

[output addObject:item];
}
[db close];
return output;
}
}
return @[];
}

2014年7月21日 星期一

[CPP] Unit Test for C++ Usage via Google Test @ Ubuntu 14.04

跟友人閒聊把玩 CPP 的心得,就被推坑到 Google test 啦:Google C++ Testing Framework

看一下簡介就...準備跳槽了 XD

Who Is Using Google Test?

In addition to many internal projects at Google, Google Test is also used by the following notable projects:
  • The Chromium projects (behind the Chrome browser and Chrome OS)
  • The LLVM compiler
  • Protocol Buffers (Google's data interchange format)
安裝:

$ sudo apt-get install cmake libgtest-dev
$ mkdir gtest && cd gtest
$ cmake /usr/src/gtest/
-- The CXX compiler identification is GNU 4.8.2
-- The C compiler identification is GNU 4.8.2
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Found PythonInterp: /usr/bin/python (found version "2.7.6")
-- Looking for include file pthread.h
-- Looking for include file pthread.h - found
-- Looking for pthread_create
-- Looking for pthread_create - not found
-- Looking for pthread_create in pthreads
-- Looking for pthread_create in pthreads - not found
-- Looking for pthread_create in pthread
-- Looking for pthread_create in pthread - found
-- Found Threads: TRUE
-- Configuring done
-- Generating done
-- Build files have been written to: /path/gtest

$ make
Scanning dependencies of target gtest
[ 50%] Building CXX object CMakeFiles/gtest.dir/src/gtest-all.cc.o
Linking CXX static library libgtest.a
[ 50%] Built target gtest
Scanning dependencies of target gtest_main
[100%] Building CXX object CMakeFiles/gtest_main.dir/src/gtest_main.cc.o
Linking CXX static library libgtest_main.a
[100%] Built target gtest_main

$ sudo cp *.a /usr/lib


試用:

$ vim gtest.cpp
#include <gtest/gtest.h>

int main(int argc, char** argv) {
        testing::InitGoogleTest(&argc, argv);
        return RUN_ALL_TESTS();
}
$ g++ gtest.cpp -lgtest -lpthread
$ ./a.out
[==========] Running 0 tests from 0 test cases.
[==========] 0 tests from 0 test cases ran. (0 ms total)
[  PASSED  ] 0 tests.


測試失敗範例:

$ vim gtest.cpp
#include <gtest/gtest.h>

TEST(MyJob, Action1) {
    ASSERT_EQ(0, 1);
}

int main(int argc, char** argv) {
        testing::InitGoogleTest(&argc, argv);
        return RUN_ALL_TESTS();
}


$ g++ gtest.cpp -lgtest -lpthread
$ ./a.out

[==========] Running 1 test from 1 test case.
[----------] Global test environment set-up.
[----------] 1 test from MyJob
[ RUN      ] MyJob.Action1
gtest.cpp:4: Failure
Value of: 1
Expected: 0
[  FAILED  ] MyJob.Action1 (1 ms)
[----------] 1 test from MyJob (1 ms total)

[----------] Global test environment tear-down
[==========] 1 test from 1 test case ran. (1 ms total)
[  PASSED  ] 0 tests.
[  FAILED  ] 1 test, listed below:
[  FAILED  ] MyJob.Action1

 1 FAILED TEST


測試成功的範例:

$ vim gtest.cpp
#include <gtest/gtest.h>

TEST(MyJob, Action1) {
    ASSERT_EQ(0, 0);
}

int main(int argc, char** argv) {
        testing::InitGoogleTest(&argc, argv);
        return RUN_ALL_TESTS();
}


$ g++ gtest.cpp -lgtest -lpthread
$ ./a.out

[==========] Running 1 test from 1 test case.
[----------] Global test environment set-up.
[----------] 1 test from MyJob
[ RUN      ] MyJob.Action1
[       OK ] MyJob.Action1 (0 ms)
[----------] 1 test from MyJob (0 ms total)

[----------] Global test environment tear-down
[==========] 1 test from 1 test case ran. (1 ms total)
[  PASSED  ] 1 test.

2013年10月26日 星期六

[Linux] 研究 c-icap modules 與 Squid 相關設定 @ Ubuntu 12.04

前鎮子剛用 pyicap 完成簡單的 ICAP 實作,但 pyicap 還是有一點不順,所以就來試試 c-icap 吧!盡管在 wikipeida 上查閱了幾種 icap server 的實作,但不少已經說不繼續維護,然後就叫大家去看 c-icap 啦,再加上 c-icap 也可以在 apt-get 上方便取得,所以,就來摸摸 so 吧!(據說中國那邊還很盛行,ISP還是會幫忙把網頁加上自家廣告)

先聊一下 c-icap 的部分,它是一隻用 C 實作 ICAP server 的角色,而 Squid 本身就內建 ICAP client 的角色了。使用 c-icap 的理由,則是他提供模組的方式,將你想做的事寫成 module ,設定後可變成 c-icap 的一個 service ,僅需依照 c-icap 的框架實作必要的函式,可省去實作 ICAP 協定。

此篇記錄著重在如何實作 c-icap module ,而網路資料不多,最佳解就是看程式碼 :P

$ apt-get source c-icap libc-icap-mod-urlcheck

此過程會產生兩個目錄:c-icap-0.1.6 和 c-icap-modules-0.1.6,其中 c-icap-modules-0.1.6 環境可以專門拿來編 c-icap modules,但其實最簡單的 c-icap-module 是在 c-icap-0.1.6/services/echo/srv_echo.c,如其名,echo 這只是個範例,但註解非常豐富,看他就夠啦。

其中比較特別的,就是查看 c-icap-module 框架,請參考這段:

CI_DECLARE_MOD_DATA ci_service_module_t service =
{
"echo", /* mod_name, The module name */
"Echo demo service", /* mod_short_descr,  Module short description */
ICAP_RESPMOD | ICAP_REQMOD, /* mod_type, The service type is responce or request modification */
echo_init_service, /* mod_init_service. Service initialization */
NULL, /* post_init_service. Service initialization after c-icap configured. Not used here */                                            
echo_close_service, /* mod_close_service. Called when service shutdowns. */
echo_init_request_data, /* mod_init_request_data */
echo_release_request_data, /* mod_release_request_data */
echo_check_preview_handler, /* mod_check_preview_handler */
echo_end_of_data_handler, /* mod_end_of_data_handler */
echo_io, /* mod_service_io */
NULL,
NULL
};


各項簡介:
  • 此 c-icap module service 啓動與關閉:echo_init_service, echo_close_service
  • 當 request 進來:echo_init_request_data
  • 取得 request preview:echo_check_preview_handler
  • 當 request 處理中:echo_io
  • 當 request 處理完:echo_release_request_data

舉個例來說,如果要用 PCRE 去做處理,一開始要初始化 PCRE 的項目可以擺在 echo_init_service ,而釋放 PCRE 的資源可以擺在 echo_close_service,而每次處理 request 前需要初始化資源則擺在 echo_init_request_data,釋放在 echo_release_request_data。

至於更深的應用就要去 c-icap-modules-0.1.6/service 翻 clamav 和 url_check,這兩項都跟資安相關,前者是掃毒軟體,後者則是可以拿來過濾惡意網站。有興趣可以翻一輪,比較特別的是處理 request 進來的資料,有用到 ci_simple_file_* 和 ci_membuf_* 系列的函式庫,簡言之就是記憶體擺不下就放檔案,或是有些函式只支援掃檔,那就先擺在檔案去進行。這邊便就不多講了。

設定 c-icap:

$ sudo cp output/changyy.so /usr/lib/c_icap/
$ sudo vim /etc/c-icap/c-icap.conf
Service changyy changyy.so
$ sudo /etc/init.d/c-icap restart


設定 squid:

icap_enable on
icap_send_client_ip on
icap_send_client_username on
icap_client_username_encode off
icap_client_username_header X-Authenticated-User
icap_preview_enable on
icap_preview_size 1024

icap_service service_req reqmod_precache bypass=1 icap://localhost:1344/changyy
adaptation_access service_req allow all
icap_service service_resp respmod_precache bypass=1 icap://localhost:1344/changyy
adaptation_access service_resp allow all


其中 icap://localhost:1344/changyy 在此的意義就是 c-icap 中的 Service changyy 啦,最後一提的是 c-icap.conf 有 DebugLevel ,這個數字攸關 debug 訊息,在 c-icap-modules 中許多實作都會用 ci_debug_printf 來印訊息,其中第一個數字若小於等於 DebugLevel 才會被印出來。而輸出的 log 可以在 /var/log/c-icap/server.log 查到。