久々に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をプロジェクトのルートに持ってきておく。
そして実行する。
ここで、サーバーのポート番号がわかる。
ではクライアントを作っていく。
ファイルたちはこんな感じ。
で、ここのファイルの中身を書いていく。
.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のポート番号を指定する。
では、
まぁ、うまくいった。ていうかうまくいくよう、血がにじむような苦労があった。(すまソ。そんなでもない。)
では、、、実行!
おおっ!
サーバー側はというと、
おおっ!
うまくいった。
いやーだいぶさぼった。なんかやる気おきんのよねー。あれもこれも。