ま、そんなところで。

ニッチな技術系メモとか、車輪を再発明してみたりとか.

C++でMessagePackを使ってみる(1)〜 基本の使い方 〜

仕事でMessagePack - C/C++を使うことがあったのでメモ.

MessagePackって何?

様々なプラットフォームで利用できるオープンソースシリアライズ仕様.
このC/C++実装版は、テンプレートベースのModern C++で記述されていて、ヘッダをインクルードするだけで簡単にプロジェクトに取り込むことができます.

https://msgpack.org/ - 公式サイト

https://github.com/msgpack/msgpack-c - C/C++向け実装

基本的な使い方

ヘッダのインクルード

C向けのインタフェースとC++用インタフェースがあるのでC++向けのヘッダをincludeする.

#include <msgpack/msgpack.hpp>

使ってみよう

対象クラスの定義

以下の条件を満たすように定義.

  • 引数なしデフォルトコンストラクタがpublic
  • MSGPACK_DEFINEで対象変数を指定
#include <iostream>
#include <string>

// 対象クラス
class Hoge {
public:
    // デシリアライズ用にデフォルトコンストラクタを用意しておく
    Hoge()
    : hoge_(0)
    , fuga_("HogeFuga")
    { }

    // パラメータ設定付きコンストラクタ
    Hoge(int hoge, std::string fuga)
    : hoge_(hoge)
    , fuga_(std::move(fuga))
    { }

    // シリアライズ設定. 対象とするメンバ変数を指定する.
    MSGPACK_DEFINE(hoge_, fuga_);
private:
    int hoge_;
    std::string fuga_;
};

ただし、MESSAGE_DEFINEできるのは MessagePackがサポートする型, もしくは、MESSAGE_DEFINEがメンバに定義された型 に限られるので注意.
デフォルトでサポートされている型は, predefined adaptorsを参照.

シリアライズ

基本としてpackerを使ってシリアライズする.

Hoge hoge;
     :
// シリアライズデータ用のバッファ
msgpack::sbuffer buffer;
// シリアライズ
msgpack::pack(buffer, hoge);
     :
// シリアライズされたデータ
char* dataptr = buffer.data();
size_t data_size   = buffer.size();

これだけ.
msgpack::pack はテンプレート関数になっていて、第1引数はメンバに void write(const char*, size_t) をもつオブジェクトであれば何でも良い.
なので、std::ostream, std::fstream, std::stringstream なんかでもOK.
状況に応じて使い分けする.

シリアライズ

msgpack::unpack を使う.

Hoge restored;
try {
    // シリアライズデータ
    char* pdata   = buffer.data();
    size_t data_size   = buffer.size();
    // シリアライズデータを読み出す
    msgpack::object_handle hd = msgpack::unpack(pdata, data_size);
    const msgpack::object& obj = hd.get();
    // デシリアライズ
    obj.convert(restored);
}
catch (std::bad_cast&) {
    // デシリアライズ時に型の不一致を検出するとbad_cast例外
        :
}

とても手軽に使用できるのが魅力的ですね!


参考記事

Google C++ Testをソースコードのままビルドツリーへ組み込む

Google C++ Test ライブラリ作るのめんどくさーい

C++のUnitTestっていえば、GoogleC++Testの人気がほぼ不動になりつつありますね。

使うときはソースコードを入手してライブラリをビルドしてプロジェクトへ組み込むことが多いようですが、実はソースコードのままビルドツリーに組み込んで使えるように設計されているようです.

一つ前のバージョン1.7.0ではソースコードを組み込む用途向けにヘッダとソースコードをそれぞれ1ファイルに結合したfusedセットが提供されていたのですが、最新の1.8.0では削除されてしまったらしくソースコード内に見当たりません. (※script以下にfuse_gtest_files.pyがあるので、これで生成するようになってるのでしょう)

しかし、git cloneやdownloadしたオリジナルのソースコードのままで組み込める設計は維持されているようで、以下の手順を踏めばfusedソースを生成しなくても簡単に組み込み可能です.

ランタイム不整合とかマクロ違いの不整合とかを回避できるので、意外におすすめ。

どうやって・・?

手順です.

1.以下の2つインクルードパスを設定する.

2. プロジェクト内でgtest-all.ccをインクルード

プロジェクト内のcppファイルのいずれかで、gtest-all.ccファイルをインクルード.
一番分かり易いのは、インクルード専用のcppファイルをプロジェクトに一つ追加する形ですかね.

ex) gtest_inc.cppを新規に追加

// 内容は以下のinclude一行
// これでGoogleTestのコード全体を取り込む.  
#include <src/gtest-all.cc>

ソースコードを配置してインクルードパスさえちゃんと設定すればお手軽にUnitTestをビルドできます.
プラットフォームごとにライブラリをビルドしなくても済むので、配布を前提とするプロジェクトではとても便利です.


関連サイト

GitHub - google/googletest: Googletest - Google Testing and Mocking Framework

Ubuntu 18.04 のターミナルフォントにリスト表示されないフォントを指定する

ターミナルで好きなフォントを使いたいんだが・・

今まで使っていたメインPCのマザーボードがお亡くなりになりました.
急遽新マシンを準備しまして、Ubuntu 18.04を入れたところ、ターミナルのフォントがちょっと気に入らない...

そーいや、フォント替えたんだっけ。
ずっとUpgradeで乗り切ってきたので忘れてました.

そんなこんなで、新しいUbuntuターミナルのフォントにvl-gothicを指定しようと奮闘したメモです.

あれ?フォントがリストにでてこない!?

まずは、vl-gothicをインストールしました.

$ sudo apt install fonts-vlgothic

端末の設定を開いて・・・

あれ?インストールしたVLゴシックを探すが見つからず・・・表示されない!?..

うーん、困った・・・・

gsettingsというツールを使え!

いろいろと調べたところ gsettings というコマンドを使えばイケるかもしれないことが判明.

まず、プロファイルのリストを表示.

$ gsettings get org.gnome.Terminal.ProfilesList list

すると、こんな感じでプロファイルのUUIDのリストが得られる.

$ gsettings get org.gnome.Terminal.ProfilesList list 
['b1dcc9dd-5262-4d8d-a863-c897e6d979b9', 'a740b961-15ff-48e3-85d3-5517c979f041']

変更したいプロファイルにアクセス.
キーのリストを表示してみます.

# a740b961-15ff-48e3-85d3-5517c979f041の場合. 0からのインデックス値で指定
# UUIDをそのまま指定してもOK.
$ gsettings list-keys org.gnome.Terminal.Legacy.Profile:/:1/

fontというのがあるので、これを取得してみます.

$ gsettings get org.gnome.Terminal.Legacy.Profile:/:1/ font
'Monospace 12'

”[フォント名] [サイズ]” という書式でsetしてやれば良さそうです.

$ gsettings set org.gnome.Terminal.Legacy.Profile:/:1/ font "VL Gothic 9"

プロファイルのフォント設定を書き換えたら、書き換えたプロファイルを選んで「文字の外観」のCustom fontのチェックを外します. f:id:zervoid:20200506014722p:plain

これでターミナルのフォントが変われば無事完了です.


参考記事

How to change gnome-terminal profile preferences using dconf or gsettings? - Ask Ubuntu
gnome-terminal のフォントを一覧以外のフォントに変更 - Qiita

pyenv環境へOpenCVをインストールする

pyenvで作ったpython環境へ最新のOpenCVをビルドしてインストールする手順のメモです.

0. 前提環境

  • Ubuntu 18.04
  • 開発用パッケージ(build-essential, cmake)インストール済み.

1. ソースコードのダウンロード

まず下記からOpenCVソースコードをDLする.
https://opencv.org/releases.html

続いて、OpenCVのContribのソースコードをダウンロードする.
ここでは、OpenCV本体のバージョンに対応すたバージョンをダウンロードするように注意. https://github.com/opencv/opencv_contrib/releases

ダウンロードしたら、適当なディレクトリに以下のように展開しておく.

.
├── build/   # ビルド作業ディレクトリ
├── opencv-3.4.1/            # OpenCVのソース
└── opencv_contrib-3.4.1/    # OpenCV-contribのソース

2. numpyをインストールする

pythonOpenCVモジュールのビルドの際にnumpyモジュールのヘッダが必要になるので、pipでnumpyをインストールしておく.
pyenvの環境へnumpyをインストールすると、依存ライブラリ開発用のC言語用ヘッダファイルもインストールされる.

numpyのインストール

# python環境を切り替える
$ pyenv global [インストールしたいpython環境]
# pipでnumpyをインストール
$ pip install numpy

pipでpython環境へnumpyがインストールされると、一緒にC言語用のヘッダファイルもデプロイされている.

numpyのインストール先

# python 3.6.5の場合.
# pyenvのディレクトリがHOME直下にある場合は下記場所にインストールされるはず.
${HOME}/.pyenv/versions/3.6.5/lib/python3.6/site-packages/numpy/

3. OpenCVモジュールのビルドとインストール

ビルド作業ディレクトリ(build)に移動し、cmakeを実行する.
cmakeではpyenvにOpenCVのライブラリがデプロイされるように、インストール先などをcmakeのプロパティを使って指定する.
ここではOpenCVライブラリのインストール先をpython環境下の usr/local とした.

pyenv環境は ${HOME}/.pyenv 、インストール先のpythonバージョンとして3.6.5を想定した場合、以下のようなコマンドになる.
とにかく設定パラメータが多いので注意.

buildディレクトリでcmakeを実行

# buildへ移動
$ cd build
# cmakeでMakefile生成
$ cmake ${PWD}/../opencv-3.4.1 \     # <--- buildディレクトリからopencvのディレクトリを参照してます
-DCMAKE_BUILD_TYPE=RELEASE \
-DCMAKE_INSTALL_PREFIX=${HOME}/.pyenv/versions/3.6.5/usr/local/ \    # <--- make install先ディレクトリ
-DINSTALL_C_EXAMPLES=OFF \
-DBUILD_NEW_PYTHON_SUPPORT=ON \
-DBUILD_opencv_python3=ON \
-DBUILD_opencv_legacy=OFF \
-DINSTALL_PYTHON_EXAMPLES=ON \
-DBUILD_EXAMPLES=ON \
-DPYTHON_EXECUTABLE=${HOME}/.pyenv/versions/3.6.5/bin/python \
-DPYTHON_LIBRARY=${HOME}/.pyenv/versions/3.6.5/lib/libpython3.6m.a \
-DPYTHON_INCLUDE_DIR=${HOME}/.pyenv/versions/3.6.5/include/python3.6m \
-DPYTHON_INCLUDE_DIRS=${HOME}/.pyenv/versions/3.6.5/include/python3.6m \
-DPYTHON_INCLUDE_DIRS2=${HOME}/.pyenv/versions/3.6.5/include/python3.6m \
-DINCLUDE_DIRS=${HOME}/.pyenv/versions/3.6.5/include/python3.6m \
-DINCLUDE_DIRS2=${HOME}/.pyenv/versions/3.6.5/include/python3.6m \
-DPYTHON_PACKAGES_PATH=${HOME}/.pyenv/versions/3.6.5/lib/python3.6/site-packages \
-DPYTHON_NUMPY_INCLUDE_DIR=${HOME}/.pyenv/versions/3.6.5/lib/python3.6/site-packages/numpy/core/include \
-DOPENCV_EXTRA_MODULES_PATH=${PWD}/../opencv_contrib-3.4.1/modules

cmakeが無事完了したら、カレントディレクトリにMakefileが生成されているはずなので、makeを実行.
時間がかかるので並列ビルドをするとよいです.

Makeの実行

# 8スレッドで並列ビルド&インストール
$ make -j8 install

ビルドとインストールが完了したら、
${HOME}/.pyenv/versions/3.6.5/usr/local/
に、ビルドされたOpenCVライブラリ一式がインストールされていることを確認.

こんな感じのツリーが作られていればOK.

.
├── bin
│     ├── opencv_annotation
│     ├── opencv_createsamples
│     ├── opencv_interactive-calibration
│     ├── opencv_traincascade
│     ├── opencv_version
│     ├── opencv_visualisation
│     └── opencv_waldboost_detector
│
├── include
│     ├── opencv
│     └── opencv2
│
├── lib
│     ├── libopencv_aruco.so -> libopencv_aruco.so.3.4
│     ├── libopencv_aruco.so.3.4 -> libopencv_aruco.so.3.4.1
│     ├── libopencv_aruco.so.3.4.1
│     ├── libopencv_bgsegm.so -> libopencv_bgsegm.so.3.4
│     ├── libopencv_bgsegm.so.3.4 -> libopencv_bgsegm.so.3.4.1
│     ├── libopencv_bgsegm.so.3.4.1
│     ├── libopencv_bioinspired.so -> libopencv_bioinspired.so.3.4
│     ├── libopencv_bioinspired.so.3.4 -> libopencv_bioinspired.so.3.4.1
│     ├── libopencv_bioinspired.so.3.4.1
:     :
:     :  (途中省略)
:     :
│     ├── pkgconfig
│     └── python3.6
│             └── site-packages
│                     └── cv2.cpython-36m-xxxxx-xxxxxxx.so   # python連携用ライブラリ
└── share
        └── OpenCV

4. python環境からOpenCVが使えるようにする

ファイルが無事インストールされたら、pythonコードからOpenCVライブラリが認識されるように、
インストール先python環境の site-packages に cv2.cpython-36m-xxxxx-xxxxxxx.so ライブラリへのシンボリックリンクを配置します.

site-packagesにpython連携ライブラリのリンクを配置

# 配置先ディレクトリ
$ cd ${HOME}/.pyenv/versions/3.6.5/lib/python3.6/site-packages
# cv2という名前でライブラリ参照できるように.
$ ln -s ${HOME}/.pyenv/versions/3.6.5/usr/local/lib/python3.6/site-packages/cv2.cpython-36m-xxxxx-xxxxxxx.so  cv2.so

5. ロードされることを確認

interactiveモードでopencv2のライブラリがロードされることを確認します.

$ python3
>>> import cv2
>>> cv2.__version__
'3.4.1'

無事ロードできました!


参考サイト

Install opencv3 for python 3.5.0 with pyenv on ubuntu 14.04 · GitHub

pyenvでpython環境のバージョンを自由に切り替える

複数のpython環境を作るために pyenv をUbuntumacOSへ導入したのでメモ.

1. pyenvって何?

pythonのバージョンを簡単に切り替えることができるようにしてくれるツール.
たとえばv2.x系、v3.x系など複数のpython環境をコマンド1つで切り替えることが出来る.
ディレクトリ単位でもバージョン指定できるので、非常に便利.

2. Install

2-1. Ubuntuの場合

対象:Ubuntu 16.04 LTS

Readmeを参考にpyenv-installer をダウンロードして実行する.

pyenv-installerの実行

curl -L https://github.com/pyenv/pyenv-installer/raw/master/bin/pyenv-installer | bash

インストールが完了したら、pyenvが有効になるように .bashrc に以下を追加して、ターミナル起動時に有効になるように仕込んでおく.
ここは意外と忘れやすく、忘れると気づきにくいので結構ハマる.

.bashrcに追加

eval "$(pyenv init -)"

2-2. MacOSX/macOSの場合

対象:macOS 10.13 High Sierra

Homebrewで簡単にインストールできます.

brewでインストール

brew install pyenv

こちらもインストールが完了したら、pyenvが有効になるように .bash_profile に以下を追加するのを忘れずに.

.bash_profileに追加

eval "$(pyenv init -)"

3. python環境の追加/削除

pyenvでインストール可能なバージョンの一覧は以下のコマンドで.
anacondaなども指定できる. すごい.

pyenvでインストール可能な環境一覧表示

pyenv install --list

以下のコマンドでインストール/アンインストールできる.
pyenvのpython環境本体は${HOME}/.pyenv/以下に作成されるので、他ユーザやシステムへの影響はほとんどないので安心.

pythonの追加/削除

# 3.6.5の追加
pyenv install 3.6.5
# 3.3.1の削除
pyenv uninstall 3.3.1

4. python環境の切り替え

local指定で切り替えると、ディレクトリ単位でpython環境を切り替えることが出来る.
global指定の場合は全体.
ただし、システムが特定のpythonに依存している場合はglobal設定で使用されるpythonが変化してしまうので特定のコマンドが動かなくなるななどが考えられるので注意.
localのバージョン設定はディレクトリ内に.python-versionが配置される.
このファイルがあると、global設定よりも優先適用されるので注意.

python環境の切り替え

# global環境を3.6.5へ切り替え
pyenv global 3.6.5
# local環境を3.3.1へ切り替え
pyenv local 3.3.1
# システム標準に戻す
pyenv global system

(続)S/MIMEメールのsmime.p7mをOpenSSLで開く(multipartの分離)

の続き.

smime.p7mに含まれている添付ファイルも気になって仕方がない

Gmailに添付されたsmime.p7m, smime.p7sに添付ファイルが含まれている場合は 、添付ファイルを全て含むMIME multipart形式のテキストがデコードされます.

この状態でもメール本文だけは何とか読めるんですが、添付ファイルはそれぞれbase64エンコードされてて見るにはもうひと手間必要です.

これをうまく分離することができればメールソフト無しでS/MIMEの閲覧くらいはできるようになるなぁ・・と.

smime.p7mに含まれている添付ファイルも分離してみよう

検討の方向としては、

  • なるべくPCを選ばず初期の環境で利用できること(プリインストール限定)
  • opensslで復号・検証して復号化したmultipartメッセージをとりあえず前提とする

といったところでしょうか.

でも、添付ファイルはちょっと厄介です.
使われているエンコード方式に従ってデコードし、適切な拡張子で個別のファイルへ復元しなければなりません.

いろいろ調べてみた所、pythonのemailパッケージが使えそうです.
さっそくemailパッケージのサンプルコードを参考にしてスクリプトを作ることにします.

その結果・・

程なく完成.
サンプルが充実してるので助かりました.

Github : mpsep (MIME MultiPart Separator)

multipertのテキストをメール本文と添付ファイルへ復元するスクリプトです.
openssl コマンドにつなげて以下のように使います.

# 復号化→署名検証→メールと添付ファイル分離
openssl smime -decrypt -in smime.p7m -inform der -inkey {自分の秘密鍵} | \
openssl smime -verify -inkey {自分の秘密鍵} -noverify | \
mpsep.py -d {添付ファイル出力先Directory}

これで目論見どおり、OpenSSLとmpsepで暗号化メッセージを完全に閲覧できちゃいました.

コマンド全体をスクリプト化すれば、smime.p7mからメールを開くまでは1コマンドで実現できそうですね.

ようやく出張先でsmime.p7mが気になって仕方がない・・という問題からは開放されたぜ、ってことで。


参考情報

19.1. email — An email and MIME handling package — Python 3.6.14 documentation
19.1.8. email: Examples — Python 3.6.14 documentation

S/MIMEメールのsmime.p7mをOpenSSLで開く

S/MIMEの添付ファイルsmime.p7mが気になって仕方がない

S/MIMEってメール署名と暗号化ができて、仕事関係の内容をコソコソと関係者へメールするのにはもってこいなんだけど、OSに個人の秘密鍵と証明書インストールして対応メーラー準備しなきゃいけなかったりして、いろいろ面倒だったりします。

出張で自分のPCが触れない状況でGmailしていると、smime.p7mとかS/MIMEメールの添付ファイルが気になって仕方がないわけで・・。 自分の証明書と秘密鍵で何とか開けないものか・・・といろいろ調べてみたところ方法が見つかったのでメモ。

実は凄かったopensslのコマンド

opensslにはsmimeコマンドがあって、秘密鍵と証明書でファイルの暗号化/復号化ができるよ、と。

OpenSSLでファイルをS/MIMEで暗号化/復号化

# 暗号化(-outがなければ標準出力)
openssl smime -encrypt -in {暗号化したいファイル} \
                          [-out {暗号化出力ファイル名}] \
                          [-outform (smime or pem or der)] \
                          {受信者の証明書ファイル(PEM)}...
# 復号化(-outがなければ標準出力)
openssl smime -decrypt -in {暗号化ファイル} \
                          [-inform (smime or pem or der)] \
                          -inkey {受信者の秘密鍵ファイル(PEM形式)} \
                          [-out {復号したデータ出力先}]

同じように署名検証もできちゃいますよ、と。

OpenSSLでファイルをS/MIMEで署名/署名検証

# 署名(-outがなければ標準出力)
# -nocertsをつけると、署名に署名を行った証明書を含めません. 
# (サイズダウンできますが、検証時に秘密鍵だけで検証できなくなります. ちょっと不便.)
openssl smime -sign -in {署名するファイル}\
                          -signer {署名者の証明書ファイル(PEM形式)} \
                          -inkey {署名者の秘密鍵ファイル(PEM形式)} \
                          [-nocerts] \
                          [-out {署名したpkcs7形式データ}] \
                          [-outform (smime or pem or der)]
# 署名検証(-outがなければ標準出力)
# -recip もしくは -inkey いずれかを指定
# ただし、inkey指定での検証は、署名に署名者の証明書が付加されている場合のみ
# -noverifyは証明書のCA署名を確認しないオプション. 自己署名証明書の場合につけるとよいです.
openssl smime -verify -in {署名済みpkcs7形式データファイル} \
                          [-inform (smime or pem or der)] \
                          -recip {署名者の証明書ファイル(PEM形式)} \
                          [-noverify] \
                          [-out {元データ出力先}]
# 署名時に署名に使用した証明書が埋め込まれている場合はこちらでもOK.
openssl smime -verify -in {署名済みpkcs7形式データファイル} \
                          [-inform (smime or pem or der)] \
                          -inkey {受信者の秘密鍵ファイル(PEM形式)} \
                          [-noverify] \
                          [-out {元データ出力先}]

ちなみに、S/MIMEで署名/暗号化されたメールの添付ファイル smime.p7mDER形式 になっているようです.
入力ファイルの形式を正しく指定してやらないとうまく動かないので注意.

また、復元したメールの生データの文字コードiso-2022-jp (7bitのJIS)だったりするので、ヘッダの文字コード指定を見て文字コードをShift-JISとかUTF-8とかにうまくなおしてやれば良さげです.

これでp7mを覗く道筋が見えてきました!

暗号化メールの覗き方

まとめると、少なくとも手元に自分の秘密鍵さえあれば、以下のようにコマンドをパイプして元データを表示できますぜ!ってことです.

# 復号化→署名検証→文字コード変換
openssl smime -decrypt -in smime.p7m -inform der -inkey {自分の秘密鍵} | \  # 復号化
openssl smime -verify -inkey {自分の秘密鍵} -noverify | \ # 署名検証
iconv -f iso-2022-jp -t utf-8 # 文字コード変換

メッセージが「署名+暗号化」、「署名のみ」、「暗号化のみ」の各種ケースで行わなければならないコマンドが変化しますが、 基本的に復号→検証→文字コード変換の手順になるのは変わらないので、不要なステップはコマンドをSkipしてパイプでつなげればよさそうです.

添付ファイルを含むmultipartメールもうまい具合に復元できればいいんだけど・・ pythonとかでスクリプト組めば良さそうです.

そのうちやってみることにしよう.

(追記)

やってみた。
(続)S/MIMEメールのsmime.p7mをOpenSSLで開く(multipartの分離) - ま、そんなところで。


参考サイト