2023年10月28日土曜日

ゼロからのCMake(1)

今まで避けてきた「CMake」に今さら挑戦するっていうちょっと恥ずい取り組み。

参考サイトはこちら。すばらしい。CMakeはインストールされているとして、はじめてのCMakeプロジェクトをやってみる。


.cppとしながらも、中身はC的なソースコード。
ではビルドして実行してみる。
できた。
./buildの中にいろんなものがてきちゃうので、hello worldをCMakeでビルドするのは正義かどうか悩んじゃうけど、お勉強ですので。
こちら、説明とか一切書かずに参考サイトにもたれっきりの記事なので、次の項目までやる。

ここでは過去自力で作成したKeeloqを題材にする。

./study2.cpp
#include <stdio.h>
#include <stdint.h>
#include "keeloq/include/keeloq.hpp"
int main(int argc,char** argv){
    uint32_t pr=0xf741e2db;
    uint64_t kr=0x5cec6701b79fd949;
    uint32_t cr;
    cr=keeloq_enc(pr,kr);
    printf("%08X\n",cr);
    pr=keeloq_dec(cr,kr);
    printf("%08X\n",pr);
    return 0;
}
./CMakeLists.txt
cmake_minimum_required(VERSION 3.1)
project(study2 CXX)
add_subdirectory(./keeloq)
add_executable(study2 study2.cpp)
target_link_libraries(study2 keeloq)
./keeloq/CMakeLists.txt
cmake_minimum_required(VERSION 3.1)
project(keeloq_lib
    VERSION 1.0.0
    DESCRIPTION "Keeloq library"
    LANGUAGES CXX)
add_library(keeloq STATIC ./src/keeloq.cpp)
#target_compile_features(keeloq PRIVATE cxx_std_11)
target_include_directories(keeloq INTERFACE ./include)
set_target_properties(keeloq
    PROPERTIES
    VERSION ${PROJECT_VERSION})
./keeloq/include/keeloq.hpp
#ifndef _KEELOQ_H_
#define _KEELOQ_H_
#include <stdint.h>
uint32_t keeloq_enc(uint32_t pr,uint64_t kr);
uint32_t keeloq_dec(uint32_t cr,uint64_t kr);
#endif
./keeloq/src/keeloq.cpp
#include <stdint.h>
uint32_t keeloq_enc(uint32_t pr,uint64_t kr){
    uint32_t nlf_table=0x3a5c742e;
    uint32_t nlf_arg;
    uint32_t nlf;
    uint32_t newz;
    uint32_t i;
    for(i=0;i<528;i++){
        nlf_arg=((pr&0x80000000)>>(31-4))
               |((pr&0x04000000)>>(26-3))
               |((pr&0x00100000)>>(20-2))
               |((pr&0x00000200)>>(9-1))
               |((pr&0x00000002)>>(1-0));
        nlf=((nlf_table>>nlf_arg)&0x00000001);
        newz=nlf^((pr&0x00010000)>>16)^(pr&0x00000001)^(uint32_t)(kr&0x0000000000000001);
        pr=(newz<<31)|(pr>>1);
        kr=((kr&0x0000000000000001)<<63)|(kr>>1);
    }
    return pr;
}
uint32_t keeloq_dec(uint32_t cr,uint64_t kr){
    uint32_t nlf_table=0x3a5c742e;
    uint32_t nlf_arg;
    uint32_t nlf;
    uint32_t newz;
    uint32_t i;
    for(i=0;i<528;i++){
        nlf_arg=((cr&0x40000000)>>(30-4))
               |((cr&0x02000000)>>(25-3))
               |((cr&0x00080000)>>(19-2))
               |((cr&0x00000100)>>(8-1))
               |((cr&0x00000001)>>(0-0));
        nlf=((nlf_table>>nlf_arg)&0x00000001);
        newz=nlf^((cr&0x00008000)>>15)^((cr&0x80000000)>>31)^(uint32_t)((kr&0x0000000000008000)>>15);
        cr=(cr<<1)|(newz);
        kr=((kr&0x8000000000000000)>>63)|(kr<<1);
    }
    return cr;
}

相変わらずの、いきなりな感じ。まぁ詳しくは参考サイトを見てほしす。おじさんはただただ賢者の足跡をたどるのみ。
はいー。うまく行きましたよ。
イイネCMake。


2023年10月24日火曜日

gRPCやってみる(5)

 C++でgRPCクライアントを作成したい。

gRPCはgoogleが開発したものなはずで、ってことはdotnet縛りってことはないよね。試してみる。
gRPCの公式ページからC++を選択すると、Quick startに飛ばされるので、これに従ってやってみる。ざっと見た感じWindowsではなくLinuxを想定しているっぽいのでWSLで。

hoge@Inspiron:~$ export MY_INSTALL_DIR=$HOME/.local
hoge@Inspiron:~$ mkdir -p $MY_INSTALL_DIR
hoge@Inspiron:~$ export PATH="$MY_INSTALL_DIR/bin:$PATH"
hoge@Inspiron:~$ sudo apt install -y cmake
[sudo] password for hoge:
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
cmake is already the newest version (3.22.1-1ubuntu1.22.04.1).
0 upgraded, 0 newly installed, 0 to remove and 30 not upgraded.
hoge@Inspiron:~$ cmake --version
cmake version 3.22.1

CMake suite maintained and supported by Kitware (kitware.com/cmake).
hoge@Inspiron:~$ wget -q -O cmake-linux.sh https://github.com/Kitware/CMake/releases/download/v3.19.6/cmake-3.19.6-Linux-x86_64.sh
hoge@Inspiron:~$ sh cmake-linux.sh -- --skip-license --prefix=$MY_INSTALL_DIR
CMake Installer Version: 3.19.6, Copyright (c) Kitware
This is a self-extracting archive.
The archive will be extracted to: /home/hoge/.local

Using target directory: /home/hoge/.local
Extracting, please wait...

Unpacking finished successfully
hoge@Inspiron:~$ rm cmake-linux.sh
hoge@Inspiron:~$ sudo apt install -y build-essential autoconf libtool pkg-config
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
autoconf is already the newest version (2.71-2).
autoconf set to manually installed.
build-essential is already the newest version (12.9ubuntu3).
libtool is already the newest version (2.4.6-15build2).
pkg-config is already the newest version (0.29.2-1ubuntu3).
pkg-config set to manually installed.
0 upgraded, 0 newly installed, 0 to remove and 30 not upgraded.
hoge@Inspiron:~$

言われたとおりにやりまくったらこうなった。
cmakeにしてもその他のアプリにしても実はすべてすでにインストールされていた状態でのログなのであまり後で参考になるような情報はない。
sh cmake-linux.sh -- --skip-license --prefix=$MY_INSTALL_DIR
が何をやってくれたのか、
まぁ、こんな感じで何かを展開してくれている。

で、quickstartによると、grpcリポジトリのクローンを作成する。
$ git clone --recurse-submodules -b v1.58.0 --depth 1 --shallow-submodules https://github.com/grpc/grpc
ってすると、クッソ長いこと何かをやる。
grpcってフォルダを作ってその中にめっちゃたくさん展開される。
で、
gRPC とプロトコル バッファーをビルドしてインストールする
ってところを進めていく、すなわちこうやっていくと、
hoge@Inspiron:~$ cd grpc
hoge@Inspiron:~/grpc$ mkdir -p cmake/build
hoge@Inspiron:~/grpc$ pushd cmake/build
~/grpc/cmake/build ~/grpc
hoge@Inspiron:~/grpc/cmake/build$ cmake -DgRPC_INSTALL=ON \
      -DgRPC_BUILD_TESTS=OFF \
      -DCMAKE_INSTALL_PREFIX=$MY_INSTALL_DIR \
      ../..
いやー普段CMake使わんからなんのこっちゃかだよ。でどうやら
ってなっているから。いろんな行き違いでこうなったんやろう。CMake使わんからわからん。で、CMAKE_ROOTは~/.local/share/cmake-3.19って教えてやることにする。
で、~/.bashrcに
export CMAKE_ROOT="$HOME/.local/share/smake-3.19"
を追加して、
source ./.bashrc
って反映させると
エラーはなくなったけど、PCに元から入っていたCMake 3.22を使いたくなった時に困っちゃうな、、、まぁないと思うけど。
で、grpcフォルダで再度
hoge@Inspiron:~/grpc/cmake/build$ cmake -DgRPC_INSTALL=ON \
      -DgRPC_BUILD_TESTS=OFF \
      -DCMAKE_INSTALL_PREFIX=$MY_INSTALL_DIR \
      ../..
ってすると、なんじゃかんじゃ執り行われる。
そして
make -j 4
make install
popd
ってする。make -j 4はめっちゃ時間がかかる。だから-j 4を推奨しているんやろうけど。
で、helloworldをビルドして実行して、サーバーを起動。
別のターミナルから、クライアントを起動。
まぁちゃんとできているっぽい。
ここから自分用のものを作っていくわけだが、しんどいので、一旦、筆を置く。

2023年10月15日日曜日

Arduino UNO R4 Minimaでタイマー割り込み

 Arduino UNO R4 Minimaでタイマー割り込みってどうやるのかを調べてみる。

正確にタイミングを制御したいときって、delayとかdelayMicrosecondsとかじゃだめだよね。Arduino UNO R3まではATMEGA328Pのタイマーを使ったり(レジスタ直叩き)、TimerOneとかMsTimer2とかライブラリを使ってた。でUNO R4ではどうなん?

で調べてみると、FspTimerってのが最初っからあるらしい。

参考サイト
Arduino UNO R4のFspTimerライブラリの使い方

study1.ino
#include <FspTimer.h>

static FspTimer fsp_timer;
static PinStatus led_state;

void timer_interrupt_callback(timer_callback_args_t* arg){
	if(led_state==LOW){
		led_state=HIGH;
	}else{
		led_state=LOW;
	}
	digitalWrite(LED_BUILTIN,led_state);
}

void setup(void){
	led_state=HIGH;
	pinMode(LED_BUILTIN, OUTPUT);
	digitalWrite(LED_BUILTIN,led_state);
	uint8_t timer_type = 0;
	int8_t channel = FspTimer::get_available_timer(timer_type);
	fsp_timer.begin(TIMER_MODE_PERIODIC, timer_type, channel,10.0f, 0.0f, timer_interrupt_callback, nullptr);
	fsp_timer.setup_overflow_irq();
	fsp_timer.open();
	fsp_timer.start();
}

void loop(void){

}

これで、10Hzでシンボルが入れ替わるのでパルスとしては5Hzになる。

はいなー。できたよー。
ところで、begin関数の引数のDutyはTIMER_MODE_PERIODICでは無視されるらしい。まぁ当然か。

2023年10月14日土曜日

gRPCやってみる(4)

久々にgRPCに取り組んでみる。お題としてAES暗号文を作ってくれるサービスってことで。

gRPCやってみる(3)と同じように中身をじゃんじゃん変えていく。
ファイル名はこんな感じ。
で、
rijndael-srv.csproj
<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>net6.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
  </PropertyGroup>

  <ItemGroup>
    <Protobuf Include="Protos\rijndael-srv.proto" GrpcServices="Server" />
  </ItemGroup>

  <ItemGroup>
    <PackageReference Include="Grpc.AspNetCore" Version="2.40.0" />
  </ItemGroup>

</Project>
Program.cs
using rijndael_srv.Services;

var builder = WebApplication.CreateBuilder(args);

// Additional configuration is required to successfully run gRPC on macOS.
// For instructions on how to configure Kestrel and gRPC clients on macOS, visit https://go.microsoft.com/fwlink/?linkid=2099682

// Add services to the container.
builder.Services.AddGrpc();

var app = builder.Build();

// Configure the HTTP request pipeline.
app.MapGrpcService<AES128EncSrvService>();
app.MapGet("/", () => "Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909");

app.Run();
rijndael-srv.proto
syntax = "proto3";

option csharp_namespace = "rijndael_srv";

package rijndael_srv;
 
service AES128EncSrv {
  rpc AES128Enc (AES128EncRequest) returns (AES128EncReply);
}
 
message AES128EncReply{
  string encoded=1;
}
 
message AES128EncRequest{
  string plain=1;
  string key=2;
}
RijndaelSrvService.cs
using Grpc.Core;
using rijndael_srv;

namespace rijndael_srv.Services;

public class AES128EncSrvService : AES128EncSrv.AES128EncSrvBase
{
    private readonly ILogger<AES128EncSrvService> _logger;
    public AES128EncSrvService(ILogger<AES128EncSrvService> logger)
    {
        _logger = logger;
    }

    public override Task<AES128EncReply> AES128Enc(AES128EncRequest request, ServerCallContext context)
    {
        return Task.FromResult(new AES128EncReply
        {
            Encoded = "Hello " + request.Plain
        });
    }
}


一旦ビルドして、うまくいっている。

(いちおう愚痴もコピペしておく)
rijndael-srv.protoでmessage内に宣言した変数をAES128EncService.csから使用するわけなんだけど、、、最初の1文字を大文字にせんといかん、、、のです。何なんやこのクソ仕様。ツールが変数名に対して勝手に手を加えるとかまじダメやろ。

では、気を取り直して、こまごましたところを実装していきます。
まずは今回32bitDLLをCallするようにしたいので、このプロジェクトを32bitでコンパイルするように指定します。
.csprojに
<RuntimeIdentifier>win-x86</RuntimeIdentifier>
を追加する。
rijndael-srv.csproj
<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>net6.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
    <RuntimeIdentifier>win-x86</RuntimeIdentifier>
  </PropertyGroup>

  <ItemGroup>
    <Protobuf Include="Protos\rijndael-srv.proto" GrpcServices="Server" />
  </ItemGroup>

  <ItemGroup>
    <PackageReference Include="Grpc.AspNetCore" Version="2.40.0" />
  </ItemGroup>

</Project>
Program.csは変更なし。
rijndael-srv.protoはやり取りをbytesでやるよって風に変更する。
rijndael-srv.proto
syntax = "proto3";

option csharp_namespace = "rijndael_srv";

package rijndael_srv;
 
service AES128EncSrv {
  rpc AES128Enc (AES128EncRequest) returns (AES128EncReply);
}
 
message AES128EncReply{
  bytes encoded=1;
}
 
message AES128EncRequest{
  bytes plain=1;
  bytes key=2;
}
RijndaelSrvService.csは実際にDLLをCallするってんで大手術。C#からDLL関数を実行でやったことがここで役に立つわけだ。人生に無駄な努力はないよね。
RijndaelSrvService.cs
using Google.Protobuf;
using Grpc.Core;
using rijndael_srv;
using System.Runtime.InteropServices;

namespace rijndael_srv.Services;

public class AES128EncSrvService : AES128EncSrv.AES128EncSrvBase
{
    private readonly ILogger<AES128EncSrvService> _logger;
    public AES128EncSrvService(ILogger<AES128EncSrvService> logger)
    {
        _logger = logger;
    }
    [DllImport("rijndael.dll", EntryPoint="AES128Encrypt")]
    public static extern int AES128Encrypt(IntPtr plain,IntPtr key,IntPtr ciphered);
    [DllImport("rijndael.dll", EntryPoint="AES128Decrypt")]
    public static extern int AES128Decrypt(IntPtr ciphered,IntPtr key,IntPtr plain);
    
    public override Task<AES128EncReply> AES128Enc(AES128EncRequest request, ServerCallContext context)
    {
        int ret;
        byte[] encrypted={0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};        
        byte[] plain=request.Plain.ToByteArray();
        byte[] key=request.Key.ToByteArray();
        int plain_size = Marshal.SizeOf(plain[0]) * plain.Length;
        string txt="";
        foreach(byte b in plain){
            txt=txt+string.Format("{0,3:X2}",b);
        }
        Console.WriteLine(txt);
        txt="";
        foreach(byte b in key){
            txt=txt+string.Format("{0,3:X2}",b);
        }
        Console.WriteLine(txt);

        if(plain_size<16)plain_size=16;
        IntPtr plain_intPtr = Marshal.AllocHGlobal(plain_size);
        int key_size = Marshal.SizeOf(key[0]) * key.Length;
        if(key_size<16)key_size=16;
        IntPtr key_intPtr = Marshal.AllocHGlobal(key_size);
        int encrypted_size = Marshal.SizeOf(encrypted[0]) * encrypted.Length;
        IntPtr encrypted_intPtr = Marshal.AllocHGlobal(encrypted_size);

        Marshal.Copy(plain, 0, plain_intPtr, plain_size);            
		Marshal.Copy(key, 0, key_intPtr, key_size);            
        Marshal.Copy(encrypted, 0, encrypted_intPtr, encrypted_size);            
        ret=AES128Encrypt(plain_intPtr,key_intPtr,encrypted_intPtr);
        Marshal.Copy(encrypted_intPtr, encrypted, 0, encrypted.Length);

        txt="";
        foreach(byte b in encrypted){
            txt=txt+string.Format("{0,3:X2}",b);
        }
        Console.WriteLine(txt);

        return Task.FromResult(new AES128EncReply
        {
            Encoded = ByteString.CopyFrom(encrypted)
        });
    }
}
ではビルド。
まぁうまくいった。
で、AESのDLLをプロジェクトのルートに持ってきておく。
そして実行する。
ここで、サーバーのポート番号がわかる。

ではクライアントを作っていく。
以前やったので、手順自体はもう書かない(手抜き)。gRPCやってみる(2)をみながらやるだけ。
ファイルたちはこんな感じ。
で、ここのファイルの中身を書いていく。
.protoはサーバーのものをコピペしてnamespaceだけ変更。
rijndael-cli.proto
syntax = "proto3";

option csharp_namespace = "rijndael_cli";

package rijndael_srv;
 
service AES128EncSrv {
  rpc AES128Enc (AES128EncRequest) returns (AES128EncReply);
}
 
message AES128EncReply{
  bytes encoded=1;
}
 
message AES128EncRequest{
  bytes plain=1;
  bytes key=2;
}
Projectは32bitだろうが64bitだろうが構わんのでいじらない。
rijndael-cli.csproj
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net6.0</TargetFramework>
    <RootNamespace>rijndael_cli</RootNamespace>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Google.Protobuf" Version="3.24.4" />
    <PackageReference Include="Grpc.Net.Client" Version="2.57.0" />
    <PackageReference Include="Grpc.Tools" Version="2.58.0">
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
      <PrivateAssets>all</PrivateAssets>
    </PackageReference>
  </ItemGroup>
  <ItemGroup>
    <Protobuf Include="Protos\rijndael-cli.proto" GrpcServices="Client" />
  </ItemGroup>

</Project>
実体(Program.cs)はいろんな知恵を集めたうえで、こうする。
Program.cs
//Console.WriteLine("Hello, World!");

using System.Threading.Tasks;
using Google.Protobuf;
using Grpc.Net.Client;
using rijndael_cli;

byte[] b_plain={0x00,0x11,0x22,0x33,0x44,0x55,0x66,0x77,0x88,0x99,0xaa,0xbb,0xcc,0xdd,0xee,0xff};
byte[] b_key={0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f};
byte[] b_encrypted={0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};

// The port number must match the port of the gRPC server.
using var channel = GrpcChannel.ForAddress("http://localhost:5154");
var client = new AES128EncSrv.AES128EncSrvClient(channel);
var reply = client.AES128Enc(
                  new AES128EncRequest { Plain=ByteString.CopyFrom(b_plain),Key=ByteString.CopyFrom(b_key)});
b_encrypted=reply.Encoded.ToByteArray();
string txt="";
foreach(byte b in b_encrypted){
    txt=txt+string.Format("{0,3:X2}",b);
}
Console.WriteLine(txt);

Console.WriteLine("Press any key to exit...");
Console.ReadKey();
こいつが、簡単そうでクセモノ。AES128EncSrvClientってメンバー名なんてツールが勝手につけちゃうので、一旦実態なしでビルドした後、コード補完で出すか、まぁ、クラス名から予想するかってことになる。あ、あと、ローカルマシンだけで完結する場合は、httpsは使えんのでhttpで。で、httpのポート番号を指定する。
では、
まぁ、うまくいった。ていうかうまくいくよう、血がにじむような苦労があった。(すまソ。そんなでもない。)
では、、、実行!
おおっ!
サーバー側はというと、
おおっ!
うまくいった。

いやーだいぶさぼった。なんかやる気おきんのよねー。あれもこれも。