2月第3週の振り返り

2月第3週(2/10-2/16)

ASP.NET Core MVC

SignalR以前にFitbit APIの利用に必要なOAuth2.0を理解していないことに気づき調べまわっていた。授業プロジェクトではNuGetのFitbitクライアントパッケージを利用していたが更新されておらず(当時はASP.NET 4で開発していた)、自前で実装するためにFitbit APIリファレンスを読んだところ以下のようなフローであることが分かった。

  1. ユーザをFitbitの/oauth2/authorizeにアクセスさせる。クエリストリングにはresponse_type=code, client_idおよびscopeとstateを指定する

redirect_uriはFitbit連携アプリの管理画面で単一のものを設定している場合は含める必要がない。複数URIを管理画面で指定している(改行で区切る)場合はそのどれかに完全一致する必要があるようだ。そのためオープンリダイレクトの危険性はなさそうであるが、必要がなければ本番環境用クライアントのリダイレクトURI設定は単一にして、クエリストリングには含めないでおくのがよいだろうと思った。

  1. ユーザが自サイトのアプリケーションによるFitbit個人データへのアクセスをAuthorize(認可)したら、Fitbit側が指定のリダイレクトURI(自サイト)にトークン要求用コードをクエリストリングとして付してユーザをリダイレクトさせる
  2. アプリケーションの側で、このリダイレクトによって発生したGETリクエスhttps://[mysite]/my_callback_uri?code=...からクエリストリングを読み取り、トークン要求用コードを取得する
  3. アプリケーションはFitbitの/oauth2/tokenに対しAuthorization: Basic base64_encode("client_id:client_secret"), client_id=XXXXX, grant_type=authorization_code, redirect_uri, code=トークン要求用コードをヘッダーとしたPOSTリクエストを送る
  4. API利用のためのトークン・リフレッシュトークン・認可された権限リストが入ったJSONレスポンスが返される
  5. (必要な権限が認可されていなければ自サイトからユーザにエラーを出す)
  6. Authorization: Bearer トークン の方式で各種APIにリクエストを送る

これがAuthorization Code Grant Flowと呼ばれる方式であるらしい。この方式ではトークン要求用コードはワンタイム(Fitbitの場合)かつ短寿命、トークンも短寿命であるためセキュリティリスクが少ないとされる。

ただし、リダイレクトをブロックしてしまえば未使用のトークン要求用コードを(短寿命であるとはいえ)取得できてしまう。害意を持った人間はこれを利用し、被害者に3.からステップを始めさせるようなリンク、すなわちhttps://[mysite]/my_callback_uri?code=valid_unused_codeを送るか、あるいは攻撃者が運営するサイトや脆弱性のある第三者掲示板などでimgタグのsrc属性に上記リンクを設定したり、HTTPリクエストを送信するようなJavaScriptを埋め込んだりすることで3.のGETリクエストを発生させること(CSRF)ができてしまう。その結果、4.で攻撃者アカウントデータへのアクセスを被害者に認めることになる。もし私のアプリケーションがFitbitプロフィールの変更機能を提供していたとすれば、被害者が自分のアカウントだと誤解して自分の誕生日や名前を攻撃者のアカウントに設定してしまうことが起きうる。

Fitbitは幸いにもデータを書き込むリクエストが限られており、位置情報を含むアクティビティデータはFitbit端末から公式アプリ経由でしかアップロードされないので致命的ではないとはいえ、攻撃者アカウントへの書き込みを被害者に許可するという構図が一般に危険であるということは認識する必要がある。

これを防ぐため、1.の段階でクエリストリングにstate=ランダムで推測困難な文字列を付すということが行われる。そしてユーザがFitbitの認可画面に飛ぶ前に、自サイト側でユーザに対応するサーバ側セッションにその値を記録しておく。

こうするとFitbit側認可ページにアクセスした本人=攻撃者に対応するサーバ側セッションにしかstateは付かない。Fitbit側はリダイレクトの際codeとともにstateもクエリストリングに付してくれるので、GETリクエストに含まれるstateクエリストリングと、アクセス者に対応するサーバ側セッションに記録されたstateの値とを照合し、合致しなければ4.の処理に移らないという対策を自サイト側で講じることができる。

stateが全員共通だったり、推測可能だったりした場合、アクセス者=被害者に対応するサーバ側セッションのstateと、攻撃者が推測して改変したURL中のstateが一致してしまうので対策にはならない。

またstateをセッションIDそのものにしてしまうと、3.のページ中で外部のjs(bootstrapなど)や画像(広告バナーなど)を使用していた場合、それらを要求する際のリファラにセッションIDが載ってしまうので望ましくない。

人間の害意を想定して対策を講じるにはこのように様々な考慮が必要であり、素直な認可に比べて面倒なものとなることがよく分かる。

2月第2週の振り返り

2月第2週(2/3-2/9)

ASP.NET Core MVC

C#におけるasync/awaitの仕組みについて理解を試みた。

async修飾子のついたメソッドは基本的にTask(返される値がない)またはTask<最終的に返される値の型>を返すこととされ、またメソッド内でawaitの記述が可能となる。

awaitは別スレッドで実行されているTask/Task<T>の処理が終わるまでメインスレッドはその先に進まないでおくという挙動を示す。これを「待つ」と表現するとブロッキングのそれと混同しかねないので避けるべきだと思った。

非同期とは呼び出し元に戻ってきたときにまだ「最終的に期待する結果(Taskならば処理の完遂、Task<T>ならばreturn T)」が得られていないことを意味する。いつその結果が得られるかは処理の内容による。そうすると最終結果を利用してさらに何かを行いたい時に支障が生じる。そこで結果を利用する処理を行う前にawaitを行うと、最終的に期待する結果が得られたことを保証できる。

よくある無意味なawaitは以下のようなものである。

public async Task<int> DoHeavyCalc(int a, int b){
    // この間I/Oや通信、重い計算などが行われる。仮に5秒かかるとする
    return a+b;
}
int calcResult1 = await DoHeavyCalc(3, 5);
int calcResult2 = await DoHeavyCalc(7,12);
// 時間がかかるだろうから他の軽い処理をここでしておく
System.Console.WriteLine($"calcResult1 is {calcResult1}, calcResult2 is {calcResult2}");

このコードでは

  1. 別スレッドでDoHeavyCalc(3, 5)を実行させ、その最終結果が得られるまで先に進まないでおく
  2. 次に別スレッドでDoHeavyCalc(7, 12)を実行させ、その最終結果が得られるまで先に進まないでおく
  3. 他の軽い処理を行う
  4. 2つのintを踏まえてコンソール出力を行う

ので最低10秒かかる。これでは(メインスレッドが固まることがないとはいえ)逐次実行しているのと変わらない。いよいよ処理が完了している必要があるというタイミングになるまでawaitをするべきではなく、従ってこう書く。

Task<int> task1 = DoHeavyCalc(3, 5);
Task<int> task2 = DoHeavyCalc(7, 12);
// 時間がかかるだろうから他の軽い処理をここでしておく
int calcResult1 = await task1;
int calcResult2 = await task2;
// またはint[] calcResults = await Task.WhenAll(task1, task2);
System.Console.WriteLine($"calcResult1 is {calcResult1}, calcResult2 is {calcResult2}");

この場合、

  1. 別スレッドでtask1の実行が開始され、呼び出し元に直ちに戻る
  2. 別スレッドでtask2の実行が開始され、呼び出し元に直ちに戻る
  3. 他の軽い処理を行う
  4. task1の処理が終わるまで先に進まないでおく(1の時点から5秒経つまでこのフェーズ)
  5. task2の処理が終わるまで先に進まないでおく(1と2はほぼ同時に開始しているのでこのフェーズは一瞬)
  6. 2つのintを踏まえてコンソール出力を行う

となり、所要時間は約5秒となる。

元々並列プログラミングのMPI通信関数を扱うべく非同期概念を理解しようと努めていたので、あまり混乱なくC#のasync/awaitを用いることができた。

来週はSignalRを利用したリアルタイム通信について学んでいきたい。

1月第5週・2月第1週の振り返り

1月第5週・2月第1週(1/27-2/2)

並列プログラミング

ようやくMPIを用いた並列計算によるLU分解法の完全実装に成功し、無事レポートとして提出することができた。来学期以降も同じ課題が出るはずなので実際のコードは示さないが、アルゴリズムの意図を流れに沿って示すことで理解の一助になればと思う。以下で各プロセスは計算するを分担している。

  1. LUP分解(PA=LU)
    • Doolittle法を使うので、Lの対角成分は1と仮定する
    • 左上からL, Uを交互に一列/一行ずつ確定していきたい
    • そのためループはk = 0, 1, ... (N-1)で回す
      • A[k][k]が担当範囲に含まれるプロセスは、A[k][k]とそれより下の要素の中で絶対値が大きいものを探し、Aおよび列ベクトルbのk行とlargest_abs_row行を入れ替える(部分ピボット選択)。得られた最大の対角要素で列を割り、Lのk列目を得る。Lのk列目情報をlargest_abs_rowの情報とともに他のプロセスに送信する。そのLと既に確定した自分のUを使ってk+1行/列以降の未確定範囲全体に減算を行う(k=0のときUの0行目は自明である)。Uのk+1行目が確定する
      • 担当範囲が現在のkよりも左にあるプロセスは、既に自分が担当すべき範囲のL/Uを求め終わっているので、情報の受信後行入れ替えだけを行う
      • 担当範囲が現在のkよりも右にあるプロセスは、受信したLと既に確定した自分のUを使ってk+1行/列以降の未確定範囲全体に減算を行う(k=0のときUの0行目は自明である)。Uのk+1行目が確定する
    • 結果として各プロセスは自分の担当範囲におけるLとUが確定した状態になる
  2. L(Ux)=Lc=Pbからcを求める
    • 列ベクトルcもプロセス間で分担して求める
    • (まず左端のプロセスが上から順に担当範囲のcを確定する)
    • 以下を繰り返す
      • 確定されようとしているcの行が担当範囲にまだ達していないプロセスは何もしない
      • 自分の担当するcが既に確定したプロセス群は自分の知るLとcを使って、下のほうのcを右側のプロセスが求められるよう、左隣から受け取った「引くべき値の部分和」にL*cを足して右隣のプロセスに渡す(リレーする)
      • 当該ループで確定することになっているcを担当するプロセスは左隣のプロセスから「引くべき部分和」を受け取り、Pbから引いてcを確定させていく
  3. Ux=cからxを求める
    • Lc=Pb同様だが方向が逆転し、またxの確定のためにはUで割る必要がある

とこのようになる。とはいえ既存のコードを言葉で表現するとこうだという話であり、この表現からコードに起こすのは難しいかもしれない。

一つ言えるのは、手書きで変数の動きを追うのであれば6x6行列を3プロセスで分担するケースを考えるべきだということである。上述したようにプロセスは担当範囲の値確定前・確定中・確定後の3種類に分けられ、また各プロセスが1行/1列しか担当しない場合は担当範囲ので値を確定させる順番が考慮から抜けてしまうからである。そして具体的な値ではなく成分をそのまま u_{02}, l_{10}のように扱うことでプログラム中で使うべき添え字が明確になるだろう。

ASP.NET Core MVC

ようやく今期の科目を完遂したので来週からはWebアプリケーションの続きをやっていく。

1月第4週の振り返り

1月第4週(1/20-26)

並列プログラミング

取り組んでいるのはPEごとにメモリが分散している&リモートでバッチ実行されるプログラムであり、デバッガで変数の動きを確認することができない。printfデバッグはその時の当該PEの状態はわかるものの通信が正しく行われているかは確認できない。

そこで4x4行列を2PE/4PEで解いた際の処理を頭で模倣して、ループごとに全PEの状態を紙に書きだす机上デバッグをしたところ、計算機がどのようにタスクを処理しているのかが理解でき、ようやくLU分解法全体の並列処理が完成したと考えられるようになった。

断言できないのは、肝心のOakforest-PACSが現在月末処理中でジョブを実行してくれないからである。とはいえもはや無限ループに悩まされてはおらず、またLU分解部分までは今週頭の稼働期間中に正しい結果が出ていることが確認できたので恐らく正しかろうと思われ、あとはLc=b, Ux=cの代入だけなのでレポートとして出せそうである。

机上デバッグの結果、非人間的な処理方法をhuman-readableな概念で記述できるようになったので、コードそのものは公開できないにせよコメントと抽象化した処理内容を学習の成果として公開する予定である。

1月第3週の振り返り

1月第3週(1/13-1/19)

並列プログラミング

自分で一から書いたコードがMPI_Barrierに到達しないで永久ループしているようで何が原因か見当もつかず困っている。レポート課題として提出できるよう試験終了後に集中的に取り組みたい。

メディアプログラミング

顔検出で用いられるHaar-like特徴量の白黒パターンのことがずっと分からないままだったが、ようやく理解の糸口をつかめた。様々なサイズで領域を指定して白黒パターン群を領域に適用し、白部分と黒部分の輝度差をとって、その領域における輝度差の特徴が「顔」「not 顔」のいずれに類似するかということを機械学習によって決定するものであるようだ。白黒パターンは「鼻の側面は陰になり、鼻梁と隣接する頬は明るい」「黒目は白目より暗い」といったことを踏まえてそれを検出できるよう設計されていると理解した。

ASP.NET Core MVC

MVCにおけるViewModelの意義を理解した。POSTリクエストには任意のフィールドを含めることができるので、Modelを直接Viewに渡してしまうとCreate/Updateの操作において変更を想定していないフィールドが名前さえ一致していれば書き換えられてしまう。アノテーションを使ってバインドするフォームデータを制約することもできるが、文字列による指定でありIDEを前提としないとフィールド名の変更に追従できないことが考えられる。また複数のModelをViewに渡したいときや、Viewのフォームでは姓名を別の欄に入力するが実際にDBに保存するのはフルネーム(仮定の話。実際そのような設計がよいかは分からない)であるときなど、Model側がViewに配慮して新しいフィールドを追加するべきではない。

そこでViewModelというViewへの便宜を図った存在に必要最小限のフィールドを設定し、Modelの一部を詰めて表示する。POSTリクエストはViewにバインドされたViewModelで受け取り、必要な分だけControllerの中でModelに移し替える。これによりフォーマッティングのModelからの分離と、Modelに対して行える変更の制限とを実現できる。

Dependency Injectionは名前が直感的な理解を妨げているように思われた。各クラスにおいて必要な(Dependency)別クラスのインスタンスを、利用側のクラスが内部で生成するのではなく、コンストラクタが呼び出される段階で引数として外部から与えられる(Inject)ようにする。クラスのオンデマンド外部供給とでも呼ぶべきだろう。

この時別クラスそのもの(具象クラス)ではなくそのクラスが持つメソッドを定義したインターフェースを引数の型に指定することで、実運用時に具象クラスを渡せるのはもちろんながら、インターフェースを実装してさえいればモックも許容されるので、開発段階で別クラスの実装完了を待ったり、それが自分の期待通りに動くことを確認したりせずとも、「もし別クラスが自分の期待する(または仕様書にある)通りの挙動を示しさえすればこのクラスは正しく動作できるか」を確認できる(=単体テストが実行できる)という恩恵があるわけだ。

1月第2週の振り返り

1月第2週(1/6-1/12)

法医学

法医学において画像診断は死因判定と生体診察の両方に用いられるが、特に宗教的理由から解剖が忌避される地域において死後画像診断が積極的に導入されている。PMI: postmortem (cross-sectional) imagingと呼称される。

非破壊検査であるため、解剖では骨の破損状況や内容物が失われてしまうもの(骨折・腐敗遺体の脳内出血・空気塞栓・緊張性気胸など)や小型の異物を発見することに向く。一方で軟骨の損傷については発見が難しい。また外傷と病気の両方が考えられるものについて鑑別が不可能である(クモ膜下出血・動脈解離など)。

メディアプログラミング

今回は画像処理について。

昔映画館で流行ったような気がする赤青メガネであるが、赤-シアンのペアになっている。光のRGBのうちRを透過する赤フィルムと、G+Bを透過するシアンフィルムを用意することで、Rチャンネルの輝度配列とG+Bチャンネルの輝度配列を合成した画像を左右の目で分けて受けることができる。RとG+Bをずらして合成することで、左右の目で受け取る輝度情報に視差を生じさせ、そのずらす量に応じて距離が異なっているように知覚させる。そのため「目の前に飛び出して見える」ものは左右の視差が大きい=RとG+Bの像の位置が大きく異なる部分で、遠くに見えるものはその逆である。

撮像素子は光電効果を利用して光の強さを測定する。デジタルカメラでは、Bayer配列と呼ばれる並びのR/G/Bカラーフィルタを通すことで、各画素はRGBいずれかの単一波長の光の強さを測定し、他色の輝度情報を周辺の画素による測定結果から補間することで、各画素に当たっていたであろう光の成分を再現する。

RGBは光の波長ごとの強度を表現しているが、人間の色覚と合致するわけではない。写真の中で日陰に次のような色で画素に表現された物体があるとき、日向にある物体と似た色をしているのはどちらかという問いに対し、ピクセルから直接取得できるRGBでは#0000ff◆◆#00009bユークリッド距離が#0000ff◆◆#6400ffのそれと等しいため、判断できない。しかし環境光による色変化を補正しようとする作用が働き(色の恒常性)、ヒトは通常前者を日向にある物体と似た色だと認識する。そこで人間の色覚に適合するよう、照らされ具合(照度)とは独立に色合いを表現する方法への需要が生じる。

HSV色空間は、物体が反射する光の主波長(色相:Hue)・主波長成分への偏り(飽和度/彩度:Saturation)・主波長成分の分光反射率(明度:Value)によって色を表現するものであり、人間の色覚と親和的であるとされる。

ASP.NET Core MVC

https://docs.microsoft.com/en-us/aspnet/core/data/ef-mvc/ に沿ってWebアプリケーションの設計を学んでいる。チュートリアルASP.NET Core 2に準拠しており最新のLTS版である3.1ではないので記述を変える必要があるところもあったが、概ね順調に進み今週は/ef-mvc/introまで終えた。

モデルの定義の中で関連するデータへのアクセスを容易にするためのナビゲーションプロパティにList<T>ではなく抽象型であるICollection<T>を使うことで、DBから条件を満たすデータを取り出そうという時にList<T>.FindAll()のような不適切な操作(条件を満たしていれば順番は不問であるのに、不要なList<T>化を行ってしまう)による取得を防止することができる。他方DBに格納する時は、仮に新規データをList<他モデル>ではなくDictionary<string, 他モデル>という形で扱っていたとしてもICollectionにキャストすれば受け入れられる。Values.ToList()の必要はない。

この「データ取得操作の規律性」・「データ登録操作の利便性」が、具象クラスではなくインタフェースによる定義を行う根拠なのだろうと思う。

ナビゲーションプロパティについて、 Microsoft.EntityFrameworkCore.ProxiesをNuGetで導入してoptions.UseLazyLoadingProxies()することでLazy Loadingがデフォルトになる(同時にすべてのナビゲーションプロパティにvirtual修飾子が必要となる)。

このLazy LoadingとEager Loadingのコンセプトについて混乱していたが、どうもEF Core 3では以下のようであると認識するに至った(正確性の保証はできない)。

  1. 既定でナビゲーションプロパティは読み込まれず、アクセスするとnullになる
  2. entity.Collection/Reference(e => e.NavProp).Load()をするとそのエンティティについてのみナビゲーションプロパティが読み込まれる(Explicit Loading)
  3. Model.Include(e => e.NavProp)をするとOUTER JOINが行われ、全件について当該ナビゲーションプロパティにアクセス可能になる(Eager Loading)
  4. options.UseLazyLoadingProxies()をするとナビゲーションプロパティへの参照が入った時点で自動的に.Load()する(Lazy Loading)

しかしLazy Loadingは当該ナビゲーションプロパティへの参照が行われるたびその一件についてのみ取得するので、1+Nクエリ問題を引き起こすことがある。

生成されたSQL文はEF Core 3では何もせずともログとして流れているが、warning扱いでVisual Studioの出力には出てこない。確認できるようにするためにはappsettings.Development.jsonで"Microsoft.EntityFrameworkCore.Database.Command": "Information"をLogLevelの中に追加すればよい(以下を参考にした)。

devadjust.exblog.jp

年末年始の振り返り

年末年始(12/30-1/5)

 

Azure for Studentsのサブスクリプションが期限を迎えるというアラートを受けて、かつて授業の最終課題として作ったWebアプリケーションの再構築に取り組む計画を立てた。

離散フーリエ変換を必要とするものだが、不規則な間隔でサンプリングされ、また当時取得できたデータでは欲しい周波数がナイキスト周波数を超えてしまっていた。今はサンプリング周波数が向上しているらしいので、これもかつては諦めていたOAuth越しのデータ取得によって新しいデータを利用して、必要であれば補間し、計算量を落とすためにFFT可能にしたい、さらにかつて不要としていたRDBの読み書きも実装したいと理想を追求すればきりがないが、それでも最低限の機能しかそろわない。

大規模な開発ではないので必要性が薄いが、初歩的なテストによって機能の保証も行うようにしたいところだ。