- 投稿日:2020-01-14T14:59:26+09:00
高校生がC#でSHA256暗号化アルゴリズムを完全独自実装してみた。
高校生の僕がSHA256暗号化アルゴリズムを外部ライブラリやランタイムに一切頼らず、独自で実装してみました。
当記事は、マイクロソフト様にケンカを売る記事ではございません。前置き
この記事をご覧になっている方々は、様々な分野でお仕事をされている方がいらっしゃると思います。
そういった方々含め、プログラミングを実際にされていない方も「SHA256」という単語は一度でも目に入れた方も多くいらっしゃるのではないでしょうか。SHA256とは、アメリカ国家安全保障局によって設計され、
2001年にアメリカ国立標準技術研究所によって連邦情報処理標準とされた暗号化アルゴリズムのひとつです。
SHA256の他にも、「SHA-1」や「SHA-512」といった多くの暗号化アルゴリズムがあります。そのひとつである「SHA-1」は、2017年にGoogleによって攻撃手段が公開され、
それまでSHA-1を使用していたシステムは半強制的に、より安全な暗号化アルゴリズムへの移行を余儀なくされた。
という事件がありましたね。そういった背景もある暗号化アルゴリズム達ですが、今回はなかでも多くの場面で使われている
「SHA-256」をプログラミング言語 C# を使って独自で実装してみたいと思います。ビットコインを支えているブロックチェーンでも、SHA-256は活躍しています。
ブロックチェーンに関しましては、下記記事をご参照頂くとわかりやすいと思います。SHA256の特徴
ここまでで、様々な暗号化アルゴリズムがあることがわかりました。
SHA256の特徴は
- 一方向性
- ▶復号化が困難である。
ここで、”困難”と言ったのは、総当り攻撃による突破が可能であるからです。
論理的には、2^80(2の80乗)という回数総当たりを仕掛ければどれかが当たる、ということです。
現在では、遥かに少ない2^63(2の63乗)という回数で突破できる手法が見つかっているそうです。
- 衝突困難
- ▶SHA-1で使用された攻撃手法。衝突させることが困難である。
簡単に言うと、異なる2つの平文から同一のハッシュ値が得られる脆弱性に強いということです。
詳しく知りたい方は、下記の記事を参照下さい。参考記事: 巷で話題のGoogleのSHA-1衝突やってみた
実装してみる
ここからは、実際にプログラムを書いてC#で暗号化アルゴリズムを再現してみたいと思います。
大まかな処理フロー
かなり大雑把ではありますが、大まかな処理フローは以下の通りです。
1.平文(16進数)を2進数に変換
aであれば16進数612進数1100001に変換されます。2.パディング処理
パディング(Padding)と呼ばれる空埋め処理を行います。
1ブロックのサイズは64byteですので、それ以下であれば空の部分を埋める必要があります。
但し、ただ埋めるだけではダメです。
例として以下のようなブロックがあったとします。
14byte分足りません。これを埋めます。//50byte 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000これを、ただ埋めると何が起こるかは安易に予想できます。
Xの部分を0で埋めたとすると、異なるブロックであっても全く同じメッセージブロックが生まれてしまいます。//46byte 1111 0101 0111 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0100 0000 0110 0000 0000 0000 0000 XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX //50byte 1111 0101 0111 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0100 0000 0110 0000 0000 0000 0000 0000 0000 XXXX XXXX XXXX XXXX XXXX XXXX XXXX ↓ 全く同じブロックが生まれる。そこで、もとのブロックのブロック長を末端に記録します。
Bの部分がブロック長を記録した部分です。//46byte 1111 0101 0111 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0100 0000 0110 0000 0000 0000 0000 XXXX XXXX XXXX XXXX XXXX BBBB BBBB BBBB BBBB //50byte 1111 0101 0111 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0100 0000 0110 0000 0000 0000 0000 0000 0000 XXXX XXXX XXXX BBBB BBBB BBBB BBBBブロック長が64byte丁度だったり、万が一記録する余裕が無かっただった場合は次のブロックで記録します。
3.ブロックをメッセージブロックに分割
パディングが済んだ512byteのブロックを64byte長のメッセージブロックへと分割します。
4.初期ベクトルを使用してハッシュ値を算出
初回ラウンドでは、初期ベクトル(IV)を使用します。
/// <summary> /// 初期ハッシュ /// </summary> private uint[] initial_hash = { 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19 };5.合計64ラウンド処理を行う
行う処理は簡単に以下の通りです。
論理代数
ここからは論理代数の分野です。
正直なところ、僕もふわっとしか理解できていません。
一通り調べてはみましたが、情報そのものが正しいものであるか区別する手段がありませんでしたので、
あくまで参考として記載します。
詳しい方がいらっしゃいましたら、コメント欄にて解説をお願い致します。参考記事: 論理代数を学ぼう
参考記事: 論理代数と論理関数 湊 真一Ch
Ch(E,F,G) = (E \Lambda F) Xor (\rightharpoondown E \Lambda G)private uint Ch(uint x, uint y, uint z) { return (x & y) ^ (~x & z); }Maj (変数多数決関数)
Maj(A,B,C) = (A \Lambda B) Xor (A \Lambda C) Xor (B \Lambda C)private uint Maj(uint x, uint y, uint z) { return (x & y) ^ (x & z) ^ (y & z); }RotR (Rotation Right)
private uint Rot_R(uint x, byte n) { return (x >> n) | (x << (32 - n)); }RotL (Rotation Left)
/// <summary> /// 左ローテート /// </summary> /// <param name="x"></param> /// <param name="n"></param> /// <returns></returns> private uint Rot_L(uint x, byte n) { return (x << n) | (x >> (32 - n)); }Σ0 (Sigma0)
\sum 0 (A) = (A \ggg 2) Xor (A \ggg 13) Xor (A \ggg 22)private uint Sigma0(uint x) { return Rot_R(x, 2) ^ Rot_R(x, 13) ^ Rot_R(x, 22); }Σ1 (Sigma1)
\sum 1 (E) = (E \ggg 6) Xor (E \ggg 11) Xor (E \ggg 25)private uint Sigma1(uint x) { return Rot_R(x, 6) ^ Rot_R(x, 11) ^ Rot_R(x, 25); }C#ソースコード
ここからは、実際のソースコードを紹介します。
定数K
/// <summary> /// Kと呼ばれる定数 /// </summary> private readonly uint[] const_k = new uint[64] { 0x428A2F98, 0x71374491, 0xB5C0FBCF, 0xE9B5DBA5, 0x3956C25B, 0x59F111F1, 0x923F82A4, 0xAB1C5ED5, 0xD807AA98, 0x12835B01, 0x243185BE, 0x550C7DC3, 0x72BE5D74, 0x80DEB1FE, 0x9BDC06A7, 0xC19BF174, 0xE49B69C1, 0xEFBE4786, 0x0FC19DC6, 0x240CA1CC, 0x2DE92C6F, 0x4A7484AA, 0x5CB0A9DC, 0x76F988DA, 0x983E5152, 0xA831C66D, 0xB00327C8, 0xBF597FC7, 0xC6E00BF3, 0xD5A79147, 0x06CA6351, 0x14292967, 0x27B70A85, 0x2E1B2138, 0x4D2C6DFC, 0x53380D13, 0x650A7354, 0x766A0ABB, 0x81C2C92E, 0x92722C85, 0xA2BFE8A1, 0xA81A664B, 0xC24B8B70, 0xC76C51A3, 0xD192E819, 0xD6990624, 0xF40E3585, 0x106AA070, 0x19A4C116, 0x1E376C08, 0x2748774C, 0x34B0BCB5, 0x391C0CB3, 0x4ED8AA4A, 0x5B9CCA4F, 0x682E6FF3, 0x748F82EE, 0x78A5636F, 0x84C87814, 0x8CC70208, 0x90BEFFFA, 0xA4506CEB, 0xBEF9A3F7, 0xC67178F2 };ComputeHash() 関数 - ハッシュ値を算出
/// <summary> /// SHA256ハッシュを生成します。 /// </summary> /// <param name="plainText">暗号化したい文字列</param> /// <returns>暗号化された文字列</returns> public string ComputeHash(string plainText) { var p = Padding(plainText); var block_list = Parse(p); var s = new uint[8]; Array.Copy(initial_hash, s, initial_hash.Length); //ブロック数分ループ foreach (var blocks in block_list) { //ブロックリストの中のブロックにスコープを当てる foreach(var block in block_list) { var pair = MakePair(s); var expanded_block = ExpandBlock(block); for (int n = 0; n < 64; ++n) { var CH = Ch(pair["e"], pair["f"], pair["g"]); var MAJ = Maj(pair["a"], pair["b"], pair["c"]); var SIG0 = Sigma0(pair["a"]); var SIG1 = Sigma1(pair["e"]); var WJ_KJ = (const_k[n] + expanded_block[n]); var T1_TEMP = (pair["h"] + WJ_KJ + CH); var T1 = (T1_TEMP + SIG1); var T2 = (SIG0 + MAJ); pair["h"] = pair["g"]; pair["g"] = pair["f"]; pair["f"] = pair["e"]; pair["e"] = (pair["d"] + T1); pair["d"] = pair["c"]; pair["c"] = pair["b"]; pair["b"] = pair["a"]; pair["a"] = (T1 + T2); } s[0] = (pair["a"] + s[0]); s[1] = (pair["b"] + s[1]); s[2] = (pair["c"] + s[2]); s[3] = (pair["d"] + s[3]); s[4] = (pair["e"] + s[4]); s[5] = (pair["f"] + s[5]); s[6] = (pair["g"] + s[6]); s[7] = (pair["h"] + s[7]); } } return MakeHash(s); }ToUInt32Array() 関数 - 2進数に変換
/// <summary> /// Stringを2進数に変換します。 /// </summary> /// <param name="plain_text">暗号化する文字列</param> /// <returns>2進数配列</returns> private uint[] ToUInt32Array(string plain_text) { //文字列を16進数に変換. 実体はbyte配列 var a = Encoding.ASCII.GetBytes(plain_text); //パディング結果を格納する配列 uint[] result = { }; foreach (var n in a) { //16進数を2進数に変換 var j = int.Parse(Convert.ToString(n, 2)); //2進数を8桁に揃える 0を先頭に追加 result = Extend<uint>(result, 0); //8桁に揃えたものを配列化 for (int nb = 0; nb < 7; nb++) { result = Extend<uint>(result, (uint)StrMid(j, nb)); } } return result; }CalculateK() 関数 - Kを算出
/// <summary> /// 与えられた配列から定数Kを算出します。 /// </summary> /// <param name="plain_bits">算出された数</param> /// <returns>定数K</returns> private uint CalculateK(uint[] plain_bits) { uint k = 0; var length = plain_bits.Length; while ((length + 1 + k) % 512 != 448) { k += 1; } return k; }Padding() 関数 - パディング
/// <summary> /// パディングと呼ばれる、空埋め処理を行います。 /// </summary> /// <param name="plain_text">パディングする2進数配列</param> /// <returns>パディングされた2進数配列</returns> private uint[] Padding(string plain_text) { var plain_bits = ToUInt32Array(plain_text); var length = plain_bits.Length; var k = CalculateK(plain_bits); //処理する値を保持するバッファ uint[] buf = { }; buf = Extend<uint>(plain_bits, 1); for(int r = 0; r < k; r++) { buf = Extend<uint>(buf, 0); } var bytStr = Convert.ToString(length, 2); //64桁右寄せゼロ埋め //0000000000000000000000000000000000000000000000000000000000011000 bytStr = bytStr.ToString().PadLeft(64, '0'); //↑で得た64桁の数列を配列に変換 //(64)[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0 ] uint[] bytStr_toArray = { }; for(int x = 0; x <= 63; x++) { var num_str = bytStr.Substring(x, 1); var num = uint.Parse(num_str); bytStr_toArray = Extend<uint>(bytStr_toArray, num); } //位取り バッファにAppend foreach(var b in bytStr_toArray) { buf = Extend<uint>(buf, b); } //ブロックサイズはパディング後512である //Assert(buf.Length != 512) return buf; }Parse() 関数 - ブロックの分割
/// <summary> /// パディングされた配列を512バイトのブロック長に分割します。 /// </summary> /// <param name="plain_bits">分割する2進数配列</param> /// <returns>分割された2進数ブロック一覧を格納したジャグ配列</returns> private uint[][] Parse(uint[] plain_bits) { const int BLOCK_SIZE = 512; var length = plain_bits.Length; var num_blocks = length / BLOCK_SIZE; //ブロックを保持するジャグ配列 var blocks = new uint[num_blocks][]; for(int n = 0; n < num_blocks; n++) { var block = new uint[BLOCK_SIZE]; //Array.Copy(plain_bits, block, BLOCK_SIZE); ←[追記]で発生した問題の原因 Array.Copy(plain_bits, n * BLOCK_SIZE, block, 0, BLOCK_SIZE); //ブロックリストに追加 blocks[n] = block; } return blocks; }ExpandBlock() 関数 - ブロックのプレ処理
/// <summary> /// ブロックを処理します。 /// </summary> /// <param name="block">64バイトの2進数ブロック配列</param> /// <returns>処理された2進数ブロック配列</returns> private uint[] ExpandBlock(uint[] block) { uint[] result = { }; for(int x = 0; x < 16; x++) { //例 //バイナリ -> ヘックスデミカル -> uint OR バイナリ -> decimal //↓チャンクバイナリ 01100001011000100110001110000000 は ヘックスデミカル 0x61626380 である. //CHUNK: [ 32 ] [0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0] //= 0x61626380 したがって uint は 1633837952 //コピー先のチャンク配列 uint[] chunk_array = new uint[32]; //ブロックから、 x*32 から 32先までをコピー Array.Copy(block, x*32, chunk_array, 0, 32); //バイナリに変換 var chunk_binary_str = ToBinary(chunk_array); var chunk_demical = Convert.ToUInt32(chunk_binary_str, 2); result = Extend<uint>(result, chunk_demical); for(int y = 16; y < 64; y++) { var T1 = Sub0(result[y - 15]) + result[y - 16]; var T2 = T1 + result[y - 7]; var T3 = T2 + Sub1(result[y - 2]); } result = Extend<uint>(result, T3); } return result; }MakePair() 関数 - ペアを生成
/// <summary> /// 計算をより楽に、分かりやすくするためにStringとのペアを生成します。 /// </summary> /// <param name="hash">ペアと対になる2進数配列</param> /// <returns>ペアを格納したディクショナリ</returns> private Dictionary<string, uint> MakePair(uint[] hash) { var dictionary = new Dictionary<string, uint>(); dictionary.Add("a", hash[0]); dictionary.Add("b", hash[1]); dictionary.Add("c", hash[2]); dictionary.Add("d", hash[3]); dictionary.Add("e", hash[4]); dictionary.Add("f", hash[5]); dictionary.Add("g", hash[6]); dictionary.Add("h", hash[7]); return dictionary; }MakeHash() 関数 - 16進数に変換
/// <summary> /// 2進数を16進数の文字列に変換します。 /// </summary> /// <param name="s">変換したい2進数配列</param> /// <returns>変換された文字列</returns> private string MakeHash(uint[] s) { string result = ""; foreach(var v in s) { result += Convert.ToString((int)v, 16); } return result; }ToBinary() 関数 - バイナリに変換
/// <summary> /// チャンクをバイナリに変換します。 /// </summary> /// <param name="chunk"></param> /// <returns>変換されたバイナリ</returns> private string ToBinary(uint[] chunk) { string result = string.Empty; foreach(var n in chunk) { result += n.ToString(); } return result; }BinaryTodecimal() 関数 - デシマルに変換
Convert.ToString(value, 16)を使う方法もあります。/// <summary> /// バイナリをデミカル Integer に変換します。 /// </summary> /// <param name="binary_str">バイナリ</param> /// <returns>デミカル Integer</returns> private int BinaryToDecimal(string binary_str) { char[] binary_char_array = binary_str.ToCharArray(); Array.Reverse(binary_char_array); int result = 0; for (int i = 0; i < binary_char_array.Length; i++) { if (binary_char_array[i] == '1') { if (i == 0) { result += 1; } else { result += (int)Math.Pow(2, i); } } } return result; }自作便利関数
やっていて、色々と不便に感じた部分や簡略化したい部分を関数化したものです。
Extend() 関数 - 配列を拡張
C#では型付けが厳しい為、定義後に配列の大きさを柔軟に変更することができません。
そこで、既存の配列を拡張する関数を作ってみました。/// <summary> /// 配列を右辺へ拡張し、値を格納します。 /// </summary> /// <typeparam name="T">アンマネージド型</typeparam> /// <param name="source">ソース元である配列</param> /// <param name="num">格納する値</param> /// <returns>拡張された配列</returns> private T[] Extend<T>(IList<T> source, T num) { var result = new T[source.Count+1]; for (int n = 0; n < source.Count; n++) { result[n] = source[n]; } result[source.Count] = num; return result; }使い方:
uint[] test = { 1 }; //before [ 1 ] uint[] res = Extend<uint>(test, 3); //after [ 1, 3 ]PrintArray() 関数 - 配列をいい感じに出力
C#では、
Console.WriteLine()で大体のことはできますが、
配列をそのまま出力しようとするとSystem.Int32と、型がそのまま出力されてしまいます。
そのままではデバッグするのもストレスなので、いい感じに出力してくれる関数を作ってみました。/// <summary> /// 配列をいい感じに出力します。 /// </summary> /// <typeparam name="T">アンマネージド型</typeparam> /// <param name="source">ソース元である配列</param> private void PrintArray<T>(IList<T> source) { Console.WriteLine(string.Format("({0})", source.Count) + "[ " + string.Join(", ", source) + " ]"); }//こんな配列が int[] hoge = { 0, 1, 2, 3, 4 }; //こうすると Console.WriteLine(hoge); //こうなるから //Output: System.Int32 //自作関数でラップすると PrintArray(hoge); //いい感じに出力される //Output: [ 0, 1, 2, 3, 4 ]StrMid() 関数 - 数値の中から数値を取り出す
例えば、こんな数字から
123456
3のインデックス部分を取り出したいとします。
パッと思いつくのは、stringにしてSubString()する方法でした。//int文字列から特定の位置にある数字を取り出す //12345 // ↑indexN //return 2 /// <summary> /// Integer から指定されたインデックスにある数値を取り出します。 /// 12345678 の インデックス 5 の場合 返り値は 5 です。 /// </summary> /// <param name="source">ソース元である数値</param> /// <param name="indexN">インデックス</param> /// <returns>取り出された数値</returns> private int StrMid(int source, int indexN) { var s = source.ToString().Substring(indexN, 1); return int.Parse(s); }実行してみた!
環境は .NET Framework 4.7.2 です。
WinFormで適当にそれっぽいものを作って実際に実行してみます。使用例:
using (SHA256NotManaged sha256 = SHA256NotManaged.Create()) { var plainText = textBox1.Text; var encryptedText = sha256.ComputeHash(plainText); textBox2.Text = encryptedText; }実行結果:
出てきたハッシュをSHA256データーベースで検索してみます。
正しいようです。
処理時間
今回は、アルゴリズムに集点をおいて、可読性を優先してコードを書いたのもあり
速度については一切考慮しておりませんが、一応ランタイムのものと比較してみます。比較にあたって、下記記事を参考にソースコードを拝借いたしました。
ありがとうございました。using (SHA256NotManaged sha256 = SHA256NotManaged.Create()) { TimeSpan elapsed = StopwatchEx.Context(() => { sha256.ComputeHash("abc"); }, 1000); Console.WriteLine($"自作: {elapsed.TotalMilliseconds}"); } using (System.Security.Cryptography.SHA256 sha256_ = System.Security.Cryptography.SHA256.Create()) { TimeSpan elapsed = StopwatchEx.Context(() => { sha256_.ComputeHash(abc); }, 1000); Console.WriteLine($"本家: {elapsed.TotalMilliseconds}"); }今回は5回ほど計測してみました。
それぞれ1000回の試行にかかった平均時間(ミリ秒)です。
ランタイムのものと比較すると、約800倍近く時間がかかっていますね。
手も足も出ませんでした。
正直ランタイムといい勝負するのではないかと期待していた打ち砕かれました。自作: 857.9315 本家: 1.1778 自作: 920.8264 本家: 1.8223 自作: 804.2666 本家: 1.009 自作: 803.6653 本家: 1.0139 自作: 821.0931 本家: 1.0187最後に
一番苦戦したのは理論代数の分野の理解です。
一向に理解できないせいで計算があわず、2時間格闘しました。。。
結局ふわっとしか理解できていません。
詳しい方がいらっしゃれば、些細な情報でもコメント欄にて解説頂けるとありがたいです。フルソースコードは僕のGithubリポジトリにあります。
コピペで動きます。
当記事内で間違いや誤字脱字等ありましたら、お手数ですがコメント欄にてご指摘いただけますと幸いです。
最後まで読んでいただき、ありがとうございました。
追記 (不具合修正)
- 一定の文字数を超えると、生成されるハッシュの値が異なる現象
- その他一部で意図しない動作
を確認しました。
本題です。
なぜこのプログラムで一定以上の文字数を超えると算出されるハッシュ値が異なるか?
結論から申し上げますと、ブロック長の記録の例外処理を完全に忘れていました。
「何らかの理由で記録するスペースがなかった場合、次のブロックで記録する」
という処理です。
ただのヒューマンエラーでした。最初、この謎現象の原因を掴むべくデバッグしていましたら
右が正しいものです。
ブロック末端&チャンクが異なっています。
デバッグを続けていくと、この現象は
一定の文字数以上(ブロック数2以上)で発生することがわかりました。
したがって、ブロック分割部分で何かしらの問題が発生していると予想できます。そこで、実際に中身を見てました。
予想的中。
1ブロック目と2ブロック目のブロックがそれぞれ同一の値となっていることが確認できます。
問題の分割関数を覗いてみます。問題箇所を特定できました。
人為的ミスは、一歩引いてソースを見てみないと中々発見できませんね。。
2進数のプレーンテキスト配列の0から512までを毎回コピーしていたことが原因でした。
ブロックが複数ある場合にのみ発生する不具合だったので、発見に時間がかかりました。
修正後のソースコードは以下の通りです。//ブロックが複数ある場合も想定されるので、 //n*512から512先までをコピーしなければいけない。 Array.Copy(plain_bits, n * BLOCK_SIZE, block, 0, BLOCK_SIZE);
以上、折角の機会なので不具合に対する適切な対処法と僕なりのデバッグ手法を紹介してみました。
- 投稿日:2020-01-14T14:50:21+09:00
Roslynで構文を解析する
exeに「.cs」を投げたら解析するような簡単なRoslynアプリケーションを作成する
準備
まず、「新しいプロジェクト」でコンソールアプリを作成する。
Nugetで「Microsoft.CodeAnalysis.CSharp」をインストールする。
これは、Roslyn APIを利用するためのパッケージである。「using」で以下の物を追加する
- Microsoft.CodeAnalysis;
- Microsoft.CodeAnalysis.CSharp;
- Microsoft.CodeAnalysis.CSharp.Syntax;
これで、Roslyn APIを利用できる状態になった。
中身
Roslynでの解析は、VS拡張機能のような修正機能か文字列を受け取って解析する機能の二つである。
今回は文字列で受け取りたいのでpublic void main(string[] args){ using(FileStream fs = new FileStream(args[0],FileMode.Open)){ StreamReader sr = new StreamReader(fs); string Text = sr.ReadToEnd(); //解析するやつ } }みたいな感じで読みます。(StreamReaderってusingでやったほうがいいんですかね...)
stringのC#コード文字列を解析できる形に変換する。
まず、CSharpSyntaxTree.ParseTextメソッドでSyntaxTreeに変換が出来、さらにノードへの変換はSyntaxTree.GetRootメソッドで出来る。SyntaxTree tr = CSharpSyntaxTree.ParseText(Text); SyntaxNode node = tr.GetRoot();これでNodeに変換できたので好きなように扱えるようになった。
今回は簡単に全ノードを巡回して情報を書き出す。
それには、SyntaxWalkerを利用する。
これを利用して以下のようなクラスを作成して巡回し書くノードの情報を書き出す。public class AllNodeWriter : SyntaxWalker { Public override void Visit(SyntaxNode node) { Console.WriteLine(node.Kind()); base.Visit(node); } }そして、以下のように呼び出す。
AllNodeWriter writer = new AllNodeWriter(); writer.Visit(node);終わりに
今回は、情報を書き出しただけでしたがここで変換したノードを書き換えて文字列に戻すことも出来ますし、なんなら解析結果を別言語に変換するなんてことも出来るって感じです。(労力は考えてない)
雰囲気だけでも伝わってくれているといいなって感じです。
やればやるほど楽しいですし、拡張機能を作れるようになると非常に助かります。
稚拙な文章でしたが最後まで読んでいただきありがとうございました。
- 投稿日:2020-01-14T14:50:21+09:00
RoslynでC#の構文を解析する
exeに「.cs」を投げたら解析するような簡単なRoslynアプリケーションを作成する
なぜ
RoslynはVisualStudioに搭載されているコンパイラであるが、これのAPIが提供、公開されているので誰でも構文解析が楽しめます。
Roslynに私は無限の可能性を感じているのでこれを書きました。準備
まず、「新しいプロジェクト」でコンソールアプリを作成する。
Nugetで「Microsoft.CodeAnalysis.CSharp」をインストールする。
これは、Roslyn APIを利用するためのパッケージである。「using」で以下の物を追加する
- Microsoft.CodeAnalysis;
- Microsoft.CodeAnalysis.CSharp;
- Microsoft.CodeAnalysis.CSharp.Syntax;
これで、Roslyn APIを利用できる状態になった。
中身
Roslynでの解析は、VS拡張機能のような修正機能か文字列を受け取って解析する機能の二つである。
今回は文字列で受け取りたいのでpublic void main(string[] args){ using(FileStream fs = new FileStream(args[0],FileMode.Open)){ StreamReader sr = new StreamReader(fs); string Text = sr.ReadToEnd(); //解析するやつ } }みたいな感じで読みます。(StreamReaderってusingでやったほうがいいんですかね...)
stringのC#コード文字列を解析できる形に変換する。
まず、CSharpSyntaxTree.ParseTextメソッドでSyntaxTreeに変換が出来、さらにノードへの変換はSyntaxTree.GetRootメソッドで出来る。SyntaxTree tr = CSharpSyntaxTree.ParseText(Text); SyntaxNode node = tr.GetRoot();これでNodeに変換できたので好きなように扱えるようになった。
今回は簡単に全ノードを巡回して情報を書き出す。
それには、SyntaxWalkerを利用する。
これを利用して以下のようなクラスを作成して巡回し書くノードの情報を書き出す。public class AllNodeWriter : SyntaxWalker { Public override void Visit(SyntaxNode node) { Console.WriteLine(node.Kind()); base.Visit(node); } }そして、以下のように呼び出す。
AllNodeWriter writer = new AllNodeWriter(); writer.Visit(node);終わりに
今回は、情報を書き出しただけでしたがここで変換したノードを書き換えて文字列に戻すことも出来ますし、なんなら解析結果を別言語に変換するなんてことも出来るって感じです。(労力は考えてない)
雰囲気だけでも伝わってくれているといいなって感じです。
やればやるほど楽しいですし、拡張機能を作れるようになると非常に助かります。
稚拙な文章でしたが最後まで読んでいただきありがとうございました。
- 投稿日:2020-01-14T10:34:18+09:00
[Microsoft] もっともシンプルなASP.NET Core JWT認証のサンプル(統合テスト付き)
これはなに?
ASP.NET Coreにおいて、JWTトークンを発行してもらうコードと、JWTトークンを検証するコードのサンプルです。
dotnetのバージョンは、すでにサポート切れの2.2です。
たぶん3.1でもそんなに変わらないはずです。統合テストも作っています。
サンプルプログラム
プログラム全体はこちらに置いています。
https://github.com/sengokyu/Ex.JwtAuth
git cloneしてdotnet test ExJwtAuth.Testsとしてもらえれば、テストが動きます。JWTトークンを発行してもらうコード
ログインIDとパスワードを受け付けて、JWTトークンをJSONとして返しています。
ログインIDとパスワードを受け付けていますが、何もしておらず素通しです。JWTトークンは、
System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandlerクラスを使用すると生成できます。(サンプルは、すべてをASP.NET MVCのコントローラの中でやっています。いわゆるファットコントローラというやつです。よいこは真似してはダメです)。
ExJwtAuth/Controllers/AuthenticationController.cs[AllowAnonymous] [HttpPost] public IActionResult Login( [Required][FromBody]LoginParam loginParam ) { var handler = new JwtSecurityTokenHandler(); // JWT内に入れるクレームです。 var claims = new[] { new Claim(ClaimTypes.Name, loginParam.Username) }; var subject = new ClaimsIdentity(claims); var credentials = new SigningCredentials( JwtSecurityConfiguration.SecurityKey, SecurityAlgorithms.HmacSha256); // ここでトークンを生成しています。 var token = handler.CreateJwtSecurityToken( subject: subject, signingCredentials: credentials); var tokenText = handler.WriteToken(token); var result = new { token = tokenText }; return Ok(result); }JWTトークンを検証するコード
JWTトークンの検証は、あらかじめ用意されている機能を追加するだけです。
Startupクラスの中で有効化します。ExJwtAuth/Startup.cspublic void ConfigureServices(IServiceCollection services) { services .AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters { // トークンを生成する側と同じ署名鍵を使うようにします。 IssuerSigningKey = JwtSecurityConfiguration.SecurityKey, // ここでは、単純のために検証処理を飛ばしています。 ValidateAudience = false, ValidateIssuer = false, }; }); // 省略 }追加した機能を有効にします。
Useする順番大事です。ExJwtAuth/Startup.cspublic void Configure(IApplicationBuilder app, IHostingEnvironment env) { app .UseAuthentication() .UseMvc(); }ログイン状態を検証するコード
ログイン状態を検証するために、ログインしているユーザ名をJSONで返すコードを追加しました。
ExJwtAuth/Controllers/AuthenticatinController.cs[Authorize()] [HttpGet] public IActionResult GetAction() { var username = HttpContext.User.Identity.Name; var result = new { username = username }; return Ok(result); }サンプルテストプログラム
統合テストでは、以下の内容をテストしています。
- JWTトークン無しでアクセスして HTTP 401 が返ること
- ログインID/パスワードをPOSTしてJWTトークンが返ること
- JWTトークン付きでアクセスしてログインIDが返ること
テストのアサーションには、FluentAssertionsを使用しています。
Assert.Equal(期待値, 実際の値)みたいに書くよりは、書きやすく読みやすいのではないでしょうか。テストクラスの雛形
テストクラスは
IClassFixtureを実装します。xUnitテストランナーに対して、テスト間で情報を持ちまわるようにすることを指示しています。コンストラクタで
WebApplicationFactoryクラスを受け取ります。このクラスを使ってHTTPクライアントを生成します。public class AuthenticationApiTests : IClassFixture<WebApplicationFactory<Startup>> { private readonly WebApplicationFactory<Startup> factory; public AuthenticationApiTests(WebApplicationFactory<Startup> factory) { this.factory = factory; } }JWTトークン無しでアクセスして HTTP 401 が返ることを確認するテスト
[Fact]をつけたものが、xUnitに認識されて実行されます。HTTP Clientを作って、トップページへアクセスし、HTTPステータスコードを確認しています。
[Fact] public async Task Test未認証だと401() { // Given var uri = "/"; using (var client = factory.CreateClient()) { // When var response = await client.GetAsync(uri); // Then response.StatusCode.Should().Be(HttpStatusCode.Unauthorized); } }その他のテスト
長くなるので省略します。githubを見てください。
https://github.com/sengokyu/Ex.JwtAuth/blob/master/ExJwtAuth.Tests/AuthenticationApiTests.csJWT認証に必要なパッケージ
以下のパッケージが追加で必要でした。
おまけ
テストクラスのテストメソッド名を日本語にするのは、結構アリだと思うんですけどどうでしょう?
- 投稿日:2020-01-14T10:34:18+09:00
[Microsoft] ASP.NET CoreでJWT認証する最もシンプルなサンプル(統合テスト付き)
これはなに?
ASP.NET Coreにおいて、JWTトークンを発行してもらうコードと、JWTトークンを検証するコードのサンプルです。
dotnetのバージョンは、すでにサポート切れの2.2です。
たぶん3.1でもそんなに変わらないはずです。統合テストも作っています。
サンプルプログラム
プログラム全体はこちらに置いています。
https://github.com/sengokyu/Ex.JwtAuth
git cloneしてdotnet test ExJwtAuth.Testsとしてもらえれば、テストが動きます。JWTトークンを発行してもらうコード
ログインIDとパスワードを受け付けて、JWTトークンをJSONとして返しています。
ログインIDとパスワードを受け付けていますが、何もしておらず素通しです。JWTトークンは、
System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandlerクラスを使用すると生成できます。(サンプルは、すべてをASP.NET MVCのコントローラの中でやっています。いわゆるファットコントローラというやつです。よいこは真似してはダメです)。
ExJwtAuth/Controllers/AuthenticationController.cs[AllowAnonymous] [HttpPost] public IActionResult Login( [Required][FromBody]LoginParam loginParam ) { var handler = new JwtSecurityTokenHandler(); // JWT内に入れるクレームです。 var claims = new[] { new Claim(ClaimTypes.Name, loginParam.Username) }; var subject = new ClaimsIdentity(claims); var credentials = new SigningCredentials( JwtSecurityConfiguration.SecurityKey, SecurityAlgorithms.HmacSha256); // ここでトークンを生成しています。 var token = handler.CreateJwtSecurityToken( subject: subject, signingCredentials: credentials); var tokenText = handler.WriteToken(token); var result = new { token = tokenText }; return Ok(result); }JWTトークンを検証するコード
JWTトークンの検証は、あらかじめ用意されている機能を追加するだけです。
Startupクラスの中で有効化します。ExJwtAuth/Startup.cspublic void ConfigureServices(IServiceCollection services) { services .AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters { // トークンを生成する側と同じ署名鍵を使うようにします。 IssuerSigningKey = JwtSecurityConfiguration.SecurityKey, // ここでは、単純のために検証処理を飛ばしています。 ValidateAudience = false, ValidateIssuer = false, }; }); // 省略 }追加した機能を有効にします。
Useする順番大事です。ExJwtAuth/Startup.cspublic void Configure(IApplicationBuilder app, IHostingEnvironment env) { app .UseAuthentication() .UseMvc(); }ログイン状態を検証するコード
ログイン状態を検証するために、ログインしているユーザ名をJSONで返すコードを追加しました。
ExJwtAuth/Controllers/AuthenticatinController.cs[Authorize()] [HttpGet] public IActionResult GetAction() { var username = HttpContext.User.Identity.Name; var result = new { username = username }; return Ok(result); }サンプルテストプログラム
統合テストでは、以下の内容をテストしています。
- JWTトークン無しでアクセスして HTTP 401 が返ること
- ログインID/パスワードをPOSTしてJWTトークンが返ること
- JWTトークン付きでアクセスしてログインIDが返ること
テストのアサーションには、FluentAssertionsを使用しています。
Assert.Equal(期待値, 実際の値)みたいに書くよりは、書きやすく読みやすいのではないでしょうか。テストクラスの雛形
テストクラスは
IClassFixtureを実装します。xUnitテストランナーに対して、テスト間で情報を持ちまわるようにすることを指示しています。コンストラクタで
WebApplicationFactoryクラスを受け取ります。このクラスを使ってHTTPクライアントを生成します。public class AuthenticationApiTests : IClassFixture<WebApplicationFactory<Startup>> { private readonly WebApplicationFactory<Startup> factory; public AuthenticationApiTests(WebApplicationFactory<Startup> factory) { this.factory = factory; } }JWTトークン無しでアクセスして HTTP 401 が返ることを確認するテスト
[Fact]をつけたものが、xUnitに認識されて実行されます。HTTP Clientを作って、トップページへアクセスし、HTTPステータスコードを確認しています。
[Fact] public async Task Test未認証だと401() { // Given var uri = "/"; using (var client = factory.CreateClient()) { // When var response = await client.GetAsync(uri); // Then response.StatusCode.Should().Be(HttpStatusCode.Unauthorized); } }その他のテスト
長くなるので省略します。githubを見てください。
https://github.com/sengokyu/Ex.JwtAuth/blob/master/ExJwtAuth.Tests/AuthenticationApiTests.csJWT認証に必要なパッケージ
以下のパッケージが追加で必要でした。
おまけ
テストクラスのテストメソッド名を日本語にするのは、結構アリだと思うんですけどどうでしょう?
- 投稿日:2020-01-14T10:34:18+09:00
[Microsoft] ASP.NET CoreでJWT認証するそこそこシンプルなサンプル(統合テスト付き)
これはなに?
ASP.NET Coreにおいて、JWTトークンを発行してもらうコードと、JWTトークンを検証するコードのサンプルです。
dotnetのバージョンは、すでにサポート切れの2.2です。
たぶん3.1でもそんなに変わらないはずです。統合テストも作っています。
サンプルプログラム
プログラム全体はこちらに置いています。
https://github.com/sengokyu/Ex.JwtAuth
git cloneしてdotnet test ExJwtAuth.Testsとしてもらえれば、テストが動きます。JWTトークンを発行してもらうコード
ログインIDとパスワードを受け付けて、JWTトークンをJSONとして返しています。
ログインIDとパスワードを受け付けていますが、何もしておらず素通しです。JWTトークンは、
System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandlerクラスを使用すると生成できます。(サンプルは、すべてをASP.NET MVCのコントローラの中でやっています。いわゆるファットコントローラというやつです。よいこは真似してはダメです)。
ExJwtAuth/Controllers/AuthenticationController.cs[AllowAnonymous] [HttpPost] public IActionResult Login( [Required][FromBody]LoginParam loginParam ) { var handler = new JwtSecurityTokenHandler(); // JWT内に入れるクレームです。 var claims = new[] { new Claim(ClaimTypes.Name, loginParam.Username) }; var subject = new ClaimsIdentity(claims); var credentials = new SigningCredentials( JwtSecurityConfiguration.SecurityKey, SecurityAlgorithms.HmacSha256); // ここでトークンを生成しています。 var token = handler.CreateJwtSecurityToken( audience: JwtSecurityConfiguration.Audience, issuer: JwtSecurityConfiguration.Issuer, subject: subject, signingCredentials: credentials); var tokenText = handler.WriteToken(token); var result = new { token = tokenText }; return Ok(result); }JWTトークンを検証するコード
JWTトークンの検証は、あらかじめ用意されている機能を追加するだけです。
Startupクラスの中で有効化します。
AddJwtBearerメソッドに渡すオプションは、また別途DIするようにしています。ExJwtAuth/Startup.cspublic void ConfigureServices(IServiceCollection services) { services .AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, configureOptions: null); // 省略 // JwtBearerOptionsの設定は別クラスでやる services .AddSingleton<IConfigureOptions<JwtBearerOptions>, JwtBearerConfigureOptions>(); }追加した機能を有効にします。
Useする順番大事です。ExJwtAuth/Startup.cspublic void Configure(IApplicationBuilder app, IHostingEnvironment env) { app .UseAuthentication() .UseMvc(); }JWTを検証する設定
Webサーバに渡されたJWTを検証する設定は別クラスにしました。
AudienceもIssurerも検証するようにしています。Audienceの検証は、手抜きして何が来てもOKなようにしています。
実際はデータベースを検索したりします。Configurations/JwtBearerConfigureOptions.csusing Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Tokens; namespace ExJwtAuth.Configuratins { public class JwtBearerConfigureOptions : IConfigureNamedOptions<JwtBearerOptions> { public void Configure(string name, JwtBearerOptions options) { if (name != JwtBearerDefaults.AuthenticationScheme) { return; } options.TokenValidationParameters = new TokenValidationParameters { AudienceValidator = this.AudienceValidatorDelegate, ValidIssuer = JwtSecurityConfiguration.Issuer, IssuerSigningKey = JwtSecurityConfiguration.SecurityKey, ValidateAudience = true, ValidateIssuer = true, ValidateIssuerSigningKey = true, }; } public void Configure(JwtBearerOptions options) { Configure(JwtBearerDefaults.AuthenticationScheme, options); } public bool AudienceValidatorDelegate(IEnumerable<string> audiences, SecurityToken securityToken, TokenValidationParameters validationParameters) { // 実際は、データベースを見たりする。 return true; } } }ログイン状態を検証するコード
ログイン状態を検証するために、ログインしているユーザ名をJSONで返すコードを追加しました。
ExJwtAuth/Controllers/AuthenticatinController.cs[Authorize()] [HttpGet] public IActionResult GetAction() { var username = HttpContext.User.Identity.Name; var result = new { username = username }; return Ok(result); }統合テスト
統合テストでは、以下の内容をテストしています。
- JWTトークン無しでアクセスして HTTP 401 が返ること
- ログインID/パスワードをPOSTしてJWTトークンが返ること
- JWTトークン付きでアクセスしてログインIDが返ること
テストのアサーションには、FluentAssertionsを使用しています。
Assert.Equal(期待値, 実際の値)みたいに書くよりは、書きやすく読みやすいのではないでしょうか。テストクラスの雛形
テストクラスは
IClassFixtureを実装します。xUnitテストランナーに対して、テスト間で情報を持ちまわるようにすることを指示しています。コンストラクタで
WebApplicationFactoryクラスを受け取ります。このクラスを使ってHTTPクライアントを生成します。public class AuthenticationApiTests : IClassFixture<WebApplicationFactory<Startup>> { private readonly WebApplicationFactory<Startup> factory; public AuthenticationApiTests(WebApplicationFactory<Startup> factory) { this.factory = factory; } }JWTトークン無しでアクセスして HTTP 401 が返ることを確認するテスト
[Fact]をつけたものが、xUnitに認識されて実行されます。HTTP Clientを作って、トップページへアクセスし、HTTPステータスコードを確認しています。
[Fact] public async Task Test未認証だと401() { // Given var uri = "/"; using (var client = factory.CreateClient()) { // When var response = await client.GetAsync(uri); // Then response.StatusCode.Should().Be(HttpStatusCode.Unauthorized); } }その他のテスト
長くなるので省略します。githubを見てください。
https://github.com/sengokyu/Ex.JwtAuth/blob/master/ExJwtAuth.Tests/AuthenticationApiTests.csJWT認証に必要なパッケージ
以下のパッケージが追加で必要でした。
リンク
- JSON Web Token(JWT)のClaimについて JWTに入れるクレームの解説があります。
おまけ
テストクラスのテストメソッド名を日本語にするのは、結構アリだと思うんですけどどうでしょう?
- 投稿日:2020-01-14T10:16:40+09:00
【C#】会社への反骨精神からツールを完成させた話
目的意識を持てない作業はやりたくない
過去の記事でも述べてますが私は現在新卒2年目の駆け出しエンジニアです。
弊社では1,2年目はOJT期間として設けられ(手厚い)教育を施されます。
OJT期間には社会人の鉄則「報連相」を身につけるため、会社単位で毎月報告書の提出が義務付けられています。この作業に関しては納得して快く提出していました。
また、担当プロジェクトの定例会議での進捗報告やマネージャーへの報告は欠かさず実施していました。
しかし部内の決まりとして若手はOJT期間に簡易的な週報を作成するというものがあります。
週報のフォーマットは以下です。20200110_週報.txt【週報】 1/6~1/10 ■今週の報告 1/6 ○○作業 →完了 1/7 ★★作業 ⇒進捗率80% 1/8 ★★作業 ⇒完了 1/9 休み 1/10 ××作業 ⇒進捗率30% ■来週の取り組み ・××作業の続き ・△△作業この週報は部のファイルサーバに毎週格納するという決まりでした。
書く内容はプロジェクトの業務進捗とその他OJT作業などの進捗を総括したものです。このフォーマットではまず書く意味があるのかと疑問を抱きます。
また最初のうちはOJT指導者などが週報を閲覧している形跡がありましたが入社して1年が過ぎたころには誰も閲覧しなくなりました。週報を書きたくないと理由をつけて説明しても「OJT期間はやろう」の一点張りで、要求を受け入れてくれませんでした。
これが会社か。。。
と思いながらもOJT期間中は渋々週報を書くことにしました。せめて楽したい
やりたくないことはせめて楽したい!
週報作成を自動化しようと思い立ち、何で作ろうかと考えました。
いくつか候補はありましたが(Excelに入力し、VBAを使ってtxtファイルを出力するなど)
ちょうどその時業務でC#を扱っていたので、Windows Formで作ろう!と思いました。アプリについて
前提
・WindowsFormなのでもちろんWindows環境での動作になります。
機能
・フォームの入力内容から上記フォーマットのテキストファイルを作成する。
・フォーム上で作成日の日付を指定するだけで作業日を含めた過去5営業日を週報に自動出力(土日は非営業日とする)。
・自動的にファイル名を作成日の日付で"YYYYMMdd_週報.txt"にする。使用方法
①WeekReportForm.exeを実行しアプリを起動する。
④フォーム右下のExportボタンをクリックしてダイアログで保存先パスを指定する。
⑤ダイアログのOKボタンをクリックすると週報が作成される。
※入力内容をクリアしたい場合はフォーム右上のクリアボタンをクリックする。
ソースコード
ほとんどコードを書いてません。
IDEが勝手にやってくれます。ありがとうございます。
Visual StudioでメインフォームにTabControlを置いてその上にそれぞれテキストボックスを置きました。日付入力はDateTimePickerを使用しました。あとはExportボタンとクリアボタンにクリックイベントハンドラを紐づけて、その中にコードを書きました。
Form1.csusing System; using System.Collections.Generic; using System.IO; using System.Text; using System.Windows.Forms; namespace WeekReportForm { public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void Form1_Load(object sender, EventArgs e) { } /// <summary> /// Exportボタンクリック時動作 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void button1_Click(object sender, EventArgs e) { // 各テキストボックスの値を取得 string text1 = textBox1.Text; string text2 = textBox2.Text; string text3 = textBox3.Text; string text4 = textBox4.Text; string text5 = textBox5.Text; string next = textBox6.Text; string filepath = ""; // 日付を取得 DateTime Today = dateTimePicker1.Value; string day = Today.Month.ToString() + "/" + Today.Day.ToString(); FolderBrowserDialog fbDialog = new FolderBrowserDialog(); // ダイアログの説明文を指定する fbDialog.Description = "週報保存先フォルダを選択"; // デフォルトのフォルダを指定する fbDialog.SelectedPath = @"C:"; // 「新しいフォルダーの作成する」ボタンを表示する fbDialog.ShowNewFolderButton = true; //フォルダを選択するダイアログを表示する if (fbDialog.ShowDialog() == DialogResult.OK) { filepath = fbDialog.SelectedPath + @"\"; } else { return; } // ファイル名決定 string filename = filepath + Today.Year.ToString() + CheckNum(Today.Month.ToString()) + CheckNum(Today.Day.ToString()) + "_週報.txt"; List<int> dayList = WeekCheck(Today.DayOfWeek); if (dayList[0] == -1) { MessageBox.Show("休日に週報は作成できません。"); return; } DateTime first = Today.AddDays(-dayList[0]); string firstday = first.Month.ToString() + "/" + first.Day.ToString(); DateTime second = Today.AddDays(-dayList[1]); string secondday = second.Month.ToString() + "/" + second.Day.ToString(); DateTime third = Today.AddDays(-dayList[2]); string thirdday = third.Month.ToString() + "/" + third.Day.ToString(); DateTime fourth = Today.AddDays(-dayList[3]); string fourthday = fourth.Month.ToString() + "/" + fourth.Day.ToString(); // タイトル作成 StringBuilder sb = new StringBuilder(); sb.Append("【週報】 " + firstday + "~" + day + "\n"); // 今週の報告 sb.Append("■今週の報告\n"); sb.Append(firstday + " " + text1 + "\n\n"); sb.Append(secondday + " " + text2 + "\n\n"); sb.Append(thirdday + " " + text3 + "\n\n"); sb.Append(fourthday + " " + text4 + "\n\n"); sb.Append(day + " " + text5 + "\n\n"); sb.Append("■来週の取り組み\n"); sb.Append(next); File.AppendAllText(filename,sb.ToString()); MessageBox.Show("週報が作成されました。"); } /// <summary> /// 5営業日判定 /// </summary> /// <param name="dow">作成日の曜日</param> /// <returns>営業日リスト</returns> private List<int> WeekCheck(DayOfWeek dow) { List<int> retList = new List<int>(); switch (dow) { case DayOfWeek.Monday: retList.Add(6); retList.Add(5); retList.Add(4); retList.Add(3); break; case DayOfWeek.Tuesday: retList.Add(6); retList.Add(5); retList.Add(4); retList.Add(1); break; case DayOfWeek.Wednesday: retList.Add(6); retList.Add(5); retList.Add(2); retList.Add(1); break; case DayOfWeek.Thursday: retList.Add(6); retList.Add(3); retList.Add(2); retList.Add(1); break; case DayOfWeek.Friday: retList.Add(4); retList.Add(3); retList.Add(2); retList.Add(1); break; default: retList.Add(-1); break; } return retList; } /// <summary> /// ファイル名0追加メソッド /// 作成日の月または日が一桁の場合先頭に0を付け二桁にする。 /// </summary> /// <param name="i"></param> /// <returns></returns> private string CheckNum(string i) { string str = ""; if (i.Length == 1) { str = "0" + i; } else { str = i; } return str; } /// <summary> /// クリアボタンクリック時動作 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void button2_Click(object sender, EventArgs e) { // 全てのテキストボックスの値をクリアする textBox1.ResetText(); textBox2.ResetText(); textBox3.ResetText(); textBox4.ResetText(); textBox5.ResetText(); textBox6.ResetText(); } } }アイテムの名前などは指定しなかったのでこんなクソみたいなコードになりました。
本当にすみません。あとがき
会社が無駄な文化を残しておいてくれたお陰でC#の勉強をすることができました。
機会を与えて下さって感謝しています。
開発の原点は便利にしたい、楽したい、だと思うので結果的には"いい開発"になったかなと思います。
毎年入社してくる後輩にはこっそりこのツールを配布して楽してもらおうと思います。ちなみにGitHubで全てコードは公開しています。同じような境遇の方がいらっしゃったら、コードをいじってフォーマットを整えて使ってみてください!
GitHub/WeekReportForm
- 投稿日:2020-01-14T09:42:15+09:00
WebSocketでブラウザにプッシュ通知するサーバをC#で実装
はじめに
- C#を使用してWebSocketサーバを実装しクライアントであるブラウザにプッシュ通知を行うのを試しました
- IISは使用せずSystem.Net.WebSocketsを使って自前で実装を行っています
- MSDNの記事を参考に実装しました
サンプルのイメージ
- サーバ側からWebSocketで値がとんできたらポップアップ表示する
サーバ側の実装
- 全体はこんな感じです
- 以下で詳細を説明します
using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.WebSockets; using System.Text; using System.Threading; using System.Threading.Tasks; namespace WebSocketServerSample02 { class Program { static void Main(string[] args) { Run(); Console.ReadKey(); } static async Task Run() { HttpListener s = new HttpListener(); s.Prefixes.Add("http://IPアドレス:ポート番号/"); s.Start(); Console.WriteLine("サーバ起動"); var hc = await s.GetContextAsync(); if (!hc.Request.IsWebSocketRequest) { hc.Response.StatusCode = 400; hc.Response.Close(); return; } var wsc = await hc.AcceptWebSocketAsync(null); var ws = wsc.WebSocket; for (int i = 0; i != 5; ++i) { await Task.Delay(2000); var response = "push_event " + DateTime.Now.ToLongTimeString(); var buffer = Encoding.UTF8.GetBytes(response); var segment = new ArraySegment<byte>(buffer); await ws.SendAsync(segment, WebSocketMessageType.Text, true, CancellationToken.None); } await ws.CloseAsync(WebSocketCloseStatus.NormalClosure, "Done", CancellationToken.None); } } }WebSocketの通信を受付してるところ
HttpListener s = new HttpListener(); s.Prefixes.Add("http://IPアドレス:ポート番号/"); s.Start(); Console.WriteLine("サーバ起動"); var hc = await s.GetContextAsync(); if (!hc.Request.IsWebSocketRequest) { hc.Response.StatusCode = 400; hc.Response.Close(); return; } var wsc = await hc.AcceptWebSocketAsync(null);
- HttpListenerを使用してHTTPサーバとしての処理を起動
- GetContextAsync()でHttpListenerでのWebSocket接続の受信要求を待ち状態にする
- Request.IsWebSocketRequestを参照してWebSocket接続でない場合はステータスコード400を返却
- AcceptWebSocketAsync(null)でWebSocket接続を受入可能状態にする
定期的にクライアントに通信しているところ
var ws = wsc.WebSocket; for (int i = 0; i != 5; ++i) { await Task.Delay(2000); var response = "push_event " + DateTime.Now.ToLongTimeString(); var buffer = Encoding.UTF8.GetBytes(response); var segment = new ArraySegment<byte>(buffer); await ws.SendAsync(segment, WebSocketMessageType.Text, true, CancellationToken.None); } await ws.CloseAsync(WebSocketCloseStatus.NormalClosure, "Done", CancellationToken.None);
- 「push_event 現在時刻」をクライアント側に送信する値をresponseに格納
- SendAsyncでクライアントに送信
クライアント側の実装
- サーバ側からの通信が来た場合、webSocket.onmessageに記載された処理が動作
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>WebSocketClient</title> <script src="js/jquery-1.11.3.js"></script> <script type="text/javascript"> var webSocket; window.onload = function() { url = "http://IPアドレス:ポート番号/"; webSocket = new WebSocket(url.replace("http://", "ws://").replace("https://", "wss://")); var messageArea = document.getElementById("messageArea"); var appendMessage = function(value, color) { var messageElement = document.createElement("div"); messageElement.style.color = color; messageElement.innerText = value; messageArea.insertBefore(messageElement, messageArea.firstChild); } webSocket.onopen = function() { appendMessage("Opened", "blue"); } webSocket.onclose = function() { appendMessage("Closed", "red"); } webSocket.onmessage = function(message) { var data = message.data; alert(data); appendMessage(data, "black"); } webSocket.onerror = function(message) { appendMessage(message, "red"); } } </script> </head> <body> <div id="messageArea"></div> </body> </html
- 投稿日:2020-01-14T03:46:10+09:00
チームでのゲーム制作について~感想とコード~
はじめまして感想会
こんにちわ!佐藤です。
今回はじめてのチーム制作だったのですが、完成品と自分たちとの想像したゲームが全く違うものになってしまいゲーム作りって難しいなと感じました。
次は絶対に計画性をたててしっかり制作に取り組みたいです。制作で使ったプログラム
ホラーってなんだって考えたときにパズルだったんですよね。
という事でパズルを考えるうえで参考にさせていただいたのがOKsaiyowaさんのシンプルなパズルの入れ替えロジックです!
んでこちらがゲームに合うように代えさせていただいたコードです!どぞ!
public class スライドパズル : MonoBehaviour { int x_MoveCount = 1; int z_MoveCount = 1; Vector3 thisObjPosition; Vector3 saveThisObjPosition; void Update() { if (Input.anyKey == false) { return; } thisObjPosition = this.gameObject.transform.position; if (Input.GetKeyDown(KeyCode.LeftArrow) && x_MoveCount > -1) { saveThisObjPosition = this.gameObject.transform.position; thisObjPosition.x -= 1; this.gameObject.transform.position = thisObjPosition; x_MoveCount -= 1; } if (Input.GetKeyDown(KeyCode.RightArrow) && x_MoveCount < 1) { saveThisObjPosition = this.gameObject.transform.position; thisObjPosition.x += 1; this.gameObject.transform.position = thisObjPosition; x_MoveCount += 1; } if (Input.GetKeyDown(KeyCode.UpArrow) && z_MoveCount < 1) { saveThisObjPosition = this.gameObject.transform.position; thisObjPosition.z += 1; this.gameObject.transform.position = thisObjPosition; z_MoveCount += 1; } if (Input.GetKeyDown(KeyCode.DownArrow) && z_MoveCount > -1) { saveThisObjPosition = this.gameObject.transform.position; thisObjPosition.z -= 1; this.gameObject.transform.position = thisObjPosition; z_MoveCount -= 1; } } void OnTriggerEnter(Collider other) { //衝突してほしいゲームオブジェクトでなければ抜ける if (other.gameObject.tag == "cube") { Debug.Log("hit"); other.transform.position = saveThisObjPosition; } //other.transform.position = saveThisObjPosition; } }どうだったでしょうか?参考にする際はぜひOKsaiyowaさんの方のコードを見ていただくと非常にわかりやすいと思います!
というわけで見ていただいた方ありがとうございました!!!!
- 投稿日:2020-01-14T00:21:52+09:00
.NET Coreのサポートポリシーをまとめた
はじめに
.NET Coreのサポートポリシーは公式サイトに記載されています。
原文を読みたい方はこちらをご覧ください。
.NET Core official support policy注意事項
- この記事は随時更新される予定です
- 記事中に登場する日付は現地時間ベースです
サポートの段階
Long Term Support (LTS)
長期サポートです。
LTSは最初のリリースから3年間サポートされます。Current
Currentリリースには将来変更される可能性のある機能が含まれます。
後続のCurrentリリース、またはLTSのリリースから3ヶ月間サポートされます。Maintenance
サポート終了に向けて、セキュリティアップデートのみ提供されます。
Currentの場合は3ヶ月間、LTSの場合は1年間サポートされます。バージョンごとのサポート
バージョン 元のリリース日 最新のパッチバージョン パッチリリース日 サポートレベル サポート終了 .NET Core 3.1 2019年12月3日 3.1.0 3.1.0 LTS 2022年12月3日 .NET Core 3.0 2019年9月23日 3.0.1 2019年11月19日 Maintenance 2020年3月3日 .NET Core 2.2 2018年12月4日 2.2.8 2019年11月19日 End of life 2019年12月23日 .NET Core 2.1 2018年5月30日 2.1.14 2019年11月19日 LTS 2021年8月21日 .NET Core 2.0 2017年8月14日 2.0.9 2018年7月10日 End of life 2018年10月1日 .NET Core 1.1 2016年11月16日 1.1.13 2019年5月14日 End of life 2019年6月27日 .NET Core 1.0 2016年6月27日 1.0.16 2019年5月14日 End of life 2019年6月27日 サポートされるOS
LTSのバージョンに限り、サポートされるOSとOSのバージョンを記載します。
ここに記載されていないバージョンにおけるサポート内容を見たい方はこちらをご覧ください。
core/os-lifecycle-policy.md at master · dotnet/core.NET Core 3.1
詳しくはこちらをご覧ください。
core/3.1-supported-os.md at master · dotnet/coreWindows
OS バージョン アーキテクチャー Windows Client 7 SP1+, 8.1 x64, x86 Windows 10 Client Version 1607+ x64, x86 Nano Server Version 1803+ x64, ARM32 Windows Server 2012 R2+ x64, x86 macOS
OS バージョン アーキテクチャー Mac OS X 10.13+ x64 Linux
OS バージョン アーキテクチャー Red Hat Enterprise Linux 6+ x64 Red Hat Enterprise Linux 7+ x64 CentOS 7+ x64 Oracle Linux 7+ x64 Fedora 29+ x64 Debian 9+ x64, ARM32, ARM64 Ubuntu 16.04+ x64, ARM32, ARM64 Linux Mint 18+ x64 openSUSE 15+ x64 SUSE Enterprise Linux (SLES) 12 SP2+ x64 Alpine Linux 3.8+ x64, ARM64 .NET Core 2.1
詳しくはこちらをご覧ください。
core/2.1-supported-os.md at master · dotnet/coreWindows
OS バージョン アーキテクチャー Windows Client 7 SP1+, 8.1 x64, x86 Windows 10 Client Version 1607+ x64, x86 Windows Server 2008 R2 SP1+ x64, x86 macOS
OS バージョン アーキテクチャー Mac OS X (macOS) 10.12+ x64 Linux
OS バージョン アーキテクチャー Red Hat Enterprise Linux 6 x64 Red Hat Enterprise Linux 7+ x64 CentOS 7+ x64 Oracle Linux 7+ x64 Fedora 29, 30 x64 Debian 9 x64, ARM32 Ubuntu 19.04, 18.04, 16.04 x64, ARM32* Linux Mint 18, 17 x64 openSUSE 15+ x64 SUSE Enterprise Linux (SLES) 12 SP2+ x64 Alpine Linux 3.7+ x64 参考















