2023年10月14日土曜日

gRPCやってみる(4)

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

gRPCやってみる(3)と同じように中身をじゃんじゃん変えていく。
ファイル名はこんな感じ。
で、
rijndael-srv.csproj
  1. <Project Sdk="Microsoft.NET.Sdk.Web">
  2.  
  3. <PropertyGroup>
  4. <TargetFramework>net6.0</TargetFramework>
  5. <Nullable>enable</Nullable>
  6. <ImplicitUsings>enable</ImplicitUsings>
  7. </PropertyGroup>
  8.  
  9. <ItemGroup>
  10. <Protobuf Include="Protos\rijndael-srv.proto" GrpcServices="Server" />
  11. </ItemGroup>
  12.  
  13. <ItemGroup>
  14. <PackageReference Include="Grpc.AspNetCore" Version="2.40.0" />
  15. </ItemGroup>
  16.  
  17. </Project>
Program.cs
  1. using rijndael_srv.Services;
  2.  
  3. var builder = WebApplication.CreateBuilder(args);
  4.  
  5. // Additional configuration is required to successfully run gRPC on macOS.
  6. // For instructions on how to configure Kestrel and gRPC clients on macOS, visit https://go.microsoft.com/fwlink/?linkid=2099682
  7.  
  8. // Add services to the container.
  9. builder.Services.AddGrpc();
  10.  
  11. var app = builder.Build();
  12.  
  13. // Configure the HTTP request pipeline.
  14. app.MapGrpcService<AES128EncSrvService>();
  15. 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");
  16.  
  17. app.Run();
rijndael-srv.proto
  1. syntax = "proto3";
  2.  
  3. option csharp_namespace = "rijndael_srv";
  4.  
  5. package rijndael_srv;
  6. service AES128EncSrv {
  7. rpc AES128Enc (AES128EncRequest) returns (AES128EncReply);
  8. }
  9. message AES128EncReply{
  10. string encoded=1;
  11. }
  12. message AES128EncRequest{
  13. string plain=1;
  14. string key=2;
  15. }
RijndaelSrvService.cs
  1. using Grpc.Core;
  2. using rijndael_srv;
  3.  
  4. namespace rijndael_srv.Services;
  5.  
  6. public class AES128EncSrvService : AES128EncSrv.AES128EncSrvBase
  7. {
  8. private readonly ILogger<AES128EncSrvService> _logger;
  9. public AES128EncSrvService(ILogger<AES128EncSrvService> logger)
  10. {
  11. _logger = logger;
  12. }
  13.  
  14. public override Task<AES128EncReply> AES128Enc(AES128EncRequest request, ServerCallContext context)
  15. {
  16. return Task.FromResult(new AES128EncReply
  17. {
  18. Encoded = "Hello " + request.Plain
  19. });
  20. }
  21. }


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

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

では、気を取り直して、こまごましたところを実装していきます。
まずは今回32bitDLLをCallするようにしたいので、このプロジェクトを32bitでコンパイルするように指定します。
.csprojに
<RuntimeIdentifier>win-x86</RuntimeIdentifier>
を追加する。
rijndael-srv.csproj
  1. <Project Sdk="Microsoft.NET.Sdk.Web">
  2.  
  3. <PropertyGroup>
  4. <TargetFramework>net6.0</TargetFramework>
  5. <Nullable>enable</Nullable>
  6. <ImplicitUsings>enable</ImplicitUsings>
  7. <RuntimeIdentifier>win-x86</RuntimeIdentifier>
  8. </PropertyGroup>
  9.  
  10. <ItemGroup>
  11. <Protobuf Include="Protos\rijndael-srv.proto" GrpcServices="Server" />
  12. </ItemGroup>
  13.  
  14. <ItemGroup>
  15. <PackageReference Include="Grpc.AspNetCore" Version="2.40.0" />
  16. </ItemGroup>
  17.  
  18. </Project>
Program.csは変更なし。
rijndael-srv.protoはやり取りをbytesでやるよって風に変更する。
rijndael-srv.proto
  1. syntax = "proto3";
  2.  
  3. option csharp_namespace = "rijndael_srv";
  4.  
  5. package rijndael_srv;
  6. service AES128EncSrv {
  7. rpc AES128Enc (AES128EncRequest) returns (AES128EncReply);
  8. }
  9. message AES128EncReply{
  10. bytes encoded=1;
  11. }
  12. message AES128EncRequest{
  13. bytes plain=1;
  14. bytes key=2;
  15. }
RijndaelSrvService.csは実際にDLLをCallするってんで大手術。C#からDLL関数を実行でやったことがここで役に立つわけだ。人生に無駄な努力はないよね。
RijndaelSrvService.cs
  1. using Google.Protobuf;
  2. using Grpc.Core;
  3. using rijndael_srv;
  4. using System.Runtime.InteropServices;
  5.  
  6. namespace rijndael_srv.Services;
  7.  
  8. public class AES128EncSrvService : AES128EncSrv.AES128EncSrvBase
  9. {
  10. private readonly ILogger<AES128EncSrvService> _logger;
  11. public AES128EncSrvService(ILogger<AES128EncSrvService> logger)
  12. {
  13. _logger = logger;
  14. }
  15. [DllImport("rijndael.dll", EntryPoint="AES128Encrypt")]
  16. public static extern int AES128Encrypt(IntPtr plain,IntPtr key,IntPtr ciphered);
  17. [DllImport("rijndael.dll", EntryPoint="AES128Decrypt")]
  18. public static extern int AES128Decrypt(IntPtr ciphered,IntPtr key,IntPtr plain);
  19. public override Task<AES128EncReply> AES128Enc(AES128EncRequest request, ServerCallContext context)
  20. {
  21. int ret;
  22. byte[] encrypted={0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
  23. byte[] plain=request.Plain.ToByteArray();
  24. byte[] key=request.Key.ToByteArray();
  25. int plain_size = Marshal.SizeOf(plain[0]) * plain.Length;
  26. string txt="";
  27. foreach(byte b in plain){
  28. txt=txt+string.Format("{0,3:X2}",b);
  29. }
  30. Console.WriteLine(txt);
  31. txt="";
  32. foreach(byte b in key){
  33. txt=txt+string.Format("{0,3:X2}",b);
  34. }
  35. Console.WriteLine(txt);
  36.  
  37. if(plain_size<16)plain_size=16;
  38. IntPtr plain_intPtr = Marshal.AllocHGlobal(plain_size);
  39. int key_size = Marshal.SizeOf(key[0]) * key.Length;
  40. if(key_size<16)key_size=16;
  41. IntPtr key_intPtr = Marshal.AllocHGlobal(key_size);
  42. int encrypted_size = Marshal.SizeOf(encrypted[0]) * encrypted.Length;
  43. IntPtr encrypted_intPtr = Marshal.AllocHGlobal(encrypted_size);
  44.  
  45. Marshal.Copy(plain, 0, plain_intPtr, plain_size);
  46. Marshal.Copy(key, 0, key_intPtr, key_size);
  47. Marshal.Copy(encrypted, 0, encrypted_intPtr, encrypted_size);
  48. ret=AES128Encrypt(plain_intPtr,key_intPtr,encrypted_intPtr);
  49. Marshal.Copy(encrypted_intPtr, encrypted, 0, encrypted.Length);
  50.  
  51. txt="";
  52. foreach(byte b in encrypted){
  53. txt=txt+string.Format("{0,3:X2}",b);
  54. }
  55. Console.WriteLine(txt);
  56.  
  57. return Task.FromResult(new AES128EncReply
  58. {
  59. Encoded = ByteString.CopyFrom(encrypted)
  60. });
  61. }
  62. }
ではビルド。
まぁうまくいった。
で、AESのDLLをプロジェクトのルートに持ってきておく。
そして実行する。
ここで、サーバーのポート番号がわかる。

ではクライアントを作っていく。
以前やったので、手順自体はもう書かない(手抜き)。gRPCやってみる(2)をみながらやるだけ。
ファイルたちはこんな感じ。
で、ここのファイルの中身を書いていく。
.protoはサーバーのものをコピペしてnamespaceだけ変更。
rijndael-cli.proto
  1. syntax = "proto3";
  2.  
  3. option csharp_namespace = "rijndael_cli";
  4.  
  5. package rijndael_srv;
  6. service AES128EncSrv {
  7. rpc AES128Enc (AES128EncRequest) returns (AES128EncReply);
  8. }
  9. message AES128EncReply{
  10. bytes encoded=1;
  11. }
  12. message AES128EncRequest{
  13. bytes plain=1;
  14. bytes key=2;
  15. }
Projectは32bitだろうが64bitだろうが構わんのでいじらない。
rijndael-cli.csproj
  1. <Project Sdk="Microsoft.NET.Sdk">
  2.  
  3. <PropertyGroup>
  4. <OutputType>Exe</OutputType>
  5. <TargetFramework>net6.0</TargetFramework>
  6. <RootNamespace>rijndael_cli</RootNamespace>
  7. <ImplicitUsings>enable</ImplicitUsings>
  8. <Nullable>enable</Nullable>
  9. </PropertyGroup>
  10.  
  11. <ItemGroup>
  12. <PackageReference Include="Google.Protobuf" Version="3.24.4" />
  13. <PackageReference Include="Grpc.Net.Client" Version="2.57.0" />
  14. <PackageReference Include="Grpc.Tools" Version="2.58.0">
  15. <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
  16. <PrivateAssets>all</PrivateAssets>
  17. </PackageReference>
  18. </ItemGroup>
  19. <ItemGroup>
  20. <Protobuf Include="Protos\rijndael-cli.proto" GrpcServices="Client" />
  21. </ItemGroup>
  22.  
  23. </Project>
実体(Program.cs)はいろんな知恵を集めたうえで、こうする。
Program.cs
  1. //Console.WriteLine("Hello, World!");
  2.  
  3. using System.Threading.Tasks;
  4. using Google.Protobuf;
  5. using Grpc.Net.Client;
  6. using rijndael_cli;
  7.  
  8. byte[] b_plain={0x00,0x11,0x22,0x33,0x44,0x55,0x66,0x77,0x88,0x99,0xaa,0xbb,0xcc,0xdd,0xee,0xff};
  9. byte[] b_key={0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f};
  10. byte[] b_encrypted={0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
  11.  
  12. // The port number must match the port of the gRPC server.
  13. using var channel = GrpcChannel.ForAddress("http://localhost:5154");
  14. var client = new AES128EncSrv.AES128EncSrvClient(channel);
  15. var reply = client.AES128Enc(
  16. new AES128EncRequest { Plain=ByteString.CopyFrom(b_plain),Key=ByteString.CopyFrom(b_key)});
  17. b_encrypted=reply.Encoded.ToByteArray();
  18. string txt="";
  19. foreach(byte b in b_encrypted){
  20. txt=txt+string.Format("{0,3:X2}",b);
  21. }
  22. Console.WriteLine(txt);
  23.  
  24. Console.WriteLine("Press any key to exit...");
  25. Console.ReadKey();
こいつが、簡単そうでクセモノ。AES128EncSrvClientってメンバー名なんてツールが勝手につけちゃうので、一旦実態なしでビルドした後、コード補完で出すか、まぁ、クラス名から予想するかってことになる。あ、あと、ローカルマシンだけで完結する場合は、httpsは使えんのでhttpで。で、httpのポート番号を指定する。
では、
まぁ、うまくいった。ていうかうまくいくよう、血がにじむような苦労があった。(すまソ。そんなでもない。)
では、、、実行!
おおっ!
サーバー側はというと、
おおっ!
うまくいった。

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

0 件のコメント:

コメントを投稿