今回の記事の実体はgoogle Bloggerに書きます。
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>
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
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のポート番号を指定する。
では、
まぁ、うまくいった。ていうかうまくいくよう、血がにじむような苦労があった。(すまソ。そんなでもない。)
では、、、実行!
おおっ!
サーバー側はというと、
おおっ!
うまくいった。
いやーだいぶさぼった。なんかやる気おきんのよねー。あれもこれも。
コメントをお書きください