· 

gRPCやってみる(4)

今回の記事の実体はgoogle Bloggerに書きます。

gRPCやってみる(4)

Bloggerのhtmlから内容を抜き出したものを下の線の下に貼り付けるので、ここからも普通に記事は読めると思うけど、いつまで両対応するかどうか。

気分次第でこっちに書き換えるかもしれないし、今後はずっとgoogle Bloggerかもしれないし。


久々に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のポート番号を指定する。
では、
まぁ、うまくいった。ていうかうまくいくよう、血がにじむような苦労があった。(すまソ。そんなでもない。)
では、、、実行!
おおっ!
サーバー側はというと、
おおっ!
うまくいった。

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