はやし雑記

はやしです

proto の Unmarshal は Marshal する proto と違っててもエラーにならないことがある

go で proto の Marshal/Unmarshal したときに、軽くハマっておや?となったので調べてみた

こんな感じの proto があるとする

syntax = "proto3";

package hoge;

message A {
  B b = 1;
}

message B {
  C c1 = 1;
  C c2 = 2;
}

message C {
  D d = 1;
}

message D {
  int32 e = 1;
}

A を Marshal して []byte にして、ゴニョゴニョして、再度 A に戻すコードを書くつもりが、 間違えて B を Marshal して、それを A に戻す実装になってしまっていた。

func main() {
    b := hoge.B{
        C1: &hoge.C{
            D: &hoge.D{
                E: 1,
            },
        },
        C2: &hoge.C{
            D: &hoge.D{
                E: 1,
            },
        },
    }
    bytes, _ := proto.Marshal(&b)
    fmt.Printf("%s\n", hex.EncodeToString(bytes))

    var a hoge.A
    if err := proto.Unmarshal(bytes, &a); err != nil {
        fmt.Printf("Error: %s", err)
    }
}

Marshall と Unmarshal する proto が異なるので、エラーでわかるだろうと思っていたが、 ↑のコードは Unmarshal でエラーにならない

こんな感じに Unmarshal される

hoge.A{
    B: &hoge.B{
        C1: &hoge.C{
            D:nil,
        },
        C2: nil,
    },
}

なぜこうなるのか、ドキュメントを参考にバイナリを読んでみた

developers.google.com

bytes の hex dump は 0a040a02080112040a020801 と出力された

色分けしたりすると、こんな感じになる

B を Marshal しているので、1バイト目が C1 に対応する 00001010 となり、7バイト目に C2 に対応する 00010010 が来る

では、これを A だと思って Unmarshal するとどうなるか

まず、A の最初のフィールドは B なので、1, 2バイト目の 00001010 00000100 は特に問題無い(元はC1)

3, 4 バイト目の 00001010 00000010 は C1 と解釈されるので、こちらも問題無い(元はD)

次のフィールドとして期待されるのは D で、D の ID は LEN のはずだが、 5, 6 バイト目の 00001000 00000001 は ID が VARINT となっている

ここでおかしい〜となってエラーが返るかと思いきや、エラーにはならず、D: nil になる

では、どんな場合でもエラーにならないのか?

proto を少し修正して、D の field を string にしてみる

message D {
  string e = 1;
}

どうように実行してみると

func main() {
    b := hoge.B{
        C1: &hoge.C{
            D: &hoge.D{
                E: "",
            },
        },
    }
    bytes, _ := proto.Marshal(&b)
    fmt.Printf("%s\n", hex.EncodeToString(bytes))

    var a hoge.A
    if err := proto.Unmarshal(bytes, &a); err != nil {
        fmt.Printf("Error: %s", err)
        return
    }
}

これもエラーにならない(C2 は除いた) バイナリは 0a020a00 となり、読み解くと以下のような感じになる

E が空文字だからか、D は Size: 0 で E については何も書いてない

それはそうという感じ

次に、E に文字を入れて

b := hoge.B{
    C1: &hoge.C{
        D: &hoge.D{
            E: "!",
        },
    },
}

でやってみると、 Error: proto: cannot parse invalid wire-format data になった

A で Marshal すると、1--6バイト目まではすんなり通るが、 7バイト目は Message (LEN) のはずなので、ID: 1 => I64, Tag: 4 と解釈され、 次に来るべきSizeなどが無く、不完全なデータだからエラーになっているのだろうか?

では、E を少し変えて

b := hoge.B{
    C1: &hoge.C{
        D: &hoge.D{
            E: "\"\n!!!!!!!!!!",
        },
    },
}

としてみると、エラー無く通るようになる

バイナリは 0a100a0e0a0c220a21212121212121212121 で、読み解くと以下のようになる

つまり、"\"\n!!!!!!!!!!" の文字列 (12文字) は Size: 10 の文字列を持つ Message と等価(紫点線で囲んだところ)なので、 エラー無く Unmarshal できるということみたい

"!" の文字数が多かったり少なかったりすると、当然 Size と Data の数が合わなくなるので、エラーになる

スッキリした 可変長データだとこういうことが起こるんだな〜

結論としては、Mershal する proto と Unmarshal する proto が違ってても、エラーにならないことがある

Cloudflare のアレを Bypass する話

www.zenrows.com

Cloudflare はいろんな方法でbotを検知しているらしい

  • IP address reputation
  • HTTP request headers
  • TLS fingerprinting (TLSの実装はOSやブラウザに依るが、UAとミスマッチがあれば偽装とバレる)
  • CAPTCHAs
  • Canvas fingerprinting
  • JavaScript challenge
  • ...

もしクローリング等で Cloudflare に bot とばれないようにしようと思うと、上記をクリアしないといけない

JavaScript challenge では JS でゴニョゴニョ計算して本当にブラウザからのアクセスかどうかをチェックするらしい 記事ではそれのリバースエンジニアリングについて書いてある

まぁ Automated Browsers を使えばいいんだろうけど、パフォーマンス的な問題があるんだろう

CORS をキャッシュしようという話

httptoolkit.tech

オリジン間リソース共有 (CORS) - HTTP | MDN

ブラウザからcross originにリクエストするときは実際のリクエストの前にpreflight request (OPTIONS request) が飛ぶ

preflight requestの間はメインのリクエストがブロックされる

preflight requestは通常cacheされず、毎回叩かれる(Serverless系を使ってるとその分コストかかる)

なのでキャッシュするといいよという話

Turn は Radian より良いらしい

www.computerenhance.com

角度の単位で Radian より Turn が良いという話

turn (角度) - Wikipedia

Turn とかいうの初めて聞いた

角度の単位でよく使う Degree は1周 0--360° で、Radian は 0--2π 今回の話題のTurn は1周 0--1

つまり、rad = turn * 2 * pi

π より 2π のほうがよく使うから 2π=τ を普段遣いしようというのがあるとか

三角関数の計算をするときに、普通は Radian を使うので、sin(h * tau) (0 <= h <= 1, tau = 2 * pi) みたいなコードが頻出する

sin(x) の実装を見てみると、y = (4 / pi) * x みたいな記述がある

つまり、πで掛けて割っているので無駄だと

Turn は 0--1 なので、無駄が無いので良いということらしい

go の sin の実装見ても、x * (4 / Pi) をしてる

https://cs.opensource.google/go/go/+/refs/tags/go1.19.1:src/math/sin.go;l=218;drc=d7df872267f9071e678732f9469824d629cac595

CUDA sincospi は Calculate the sine and cosine of the first input argument × π. らしい

CUDA Math API :: CUDA Toolkit Documentation

左右分離キーボードを買った

左右分離キーボードを購入した。

Mistel の静音赤軸のやつを買った。

このところ自宅からリモートワークをちょいちょいするようになった。 自宅では今年の2月頃に購入したKeychronの青軸を使っていたのだが、 青軸はとにかくうるさくて、会議の時などに他の人に迷惑かけてないか気になっていた。

blog.hayashikun.com

夏に祖父母と会った時に、大学院卒業祝いをもらったので、 静かなキーボードを買うことにした。 少し前に会社で左右分離キーボードの話をしてから気になっていたものあり、 Mistelの静音赤軸左右分離キーボードを購入した。

これまで左右分離キーボードやそれに類するタイプのキーボードを使ったことが無かったので、 使いにくいのではないかという不安はあったけど、いざ使ってみると非常に快適で、 予想以上に違和感は無かった。

唯一の違和感は、"B"が左側にあることで、これまでずっと"B"を右手でタイプしていたので、 慣れるまでは"B"を入力しようとするときに左手が空を切ってしまうことがあった。

"B"は左手で入力するのが正しいらしい。(正しさとは?)

いろんな人に”B”をどっちで入力しているか聞いてみたけど、結構右手で入力してる人は多かった。 (体感では右手派と左手派は同数くらいな気がする)

あとは、Magic trackpadをこれまではキーボードの右側に置いていたが、 それを真ん中に置くようにしたので、それも少し慣れるまでに時間がかかった。

ただ、どちらも使い始めて数時間もすると完全に慣れてしまったので、 もう違和感はほとんどなくなった。

左右分離キーボードにすると肩が縮こまらないので肩凝り予防に良いらしい。 (とはいえ、自分はそんなにPCで肩が凝ったことは無いのだが)

左右分離キーボード、おすすめです。

顔検出をenvoyのproxy-wasmで動かした

前回、前々回の続きです。

blog.hayashikun.com

blog.hayashikun.com

これがやりたくてやってました。

画像をPOSTすると、その画像に含まれる顔が検出されて、ログに出力されます。 重要なのは、顔検出を行なっているのがWebアプリケーションではなく、envoy proxyのwasmのfilterだということです。

リポジトリは前回と同じで、諸々はproxy-wasmのディレクトリに入っています。

github.com

appはpythonのwebサーバーで、データのサイズを返すだけです。

docker-compose up をすれば、appとenvoyが立ち上がります。

続きを読む

顔検出 (CenterFace) をブラウザのWASMで動かした

前回の続きです。

blog.hayashikun.com

この記事では、前回の記事で実装したCenterFaceをwasmで動くようにして、それをブラウザで動かしたことについて書きます。

以下のように、Webカメラの映像から顔を検出して、顔の上に緑の四角を描画するというのをやりました。

https://github.com/hayashikun/wasabi/blob/master/web-wasm.gif?raw=true

今回実装したものは、こちらにデプロイしているので、PCやスマホから試すことができます。

wasbi

試してみるとわかると思いますが、結構軽快に動作しています。

リポジトリは前回と同じです。

wasmとして動かしているのが web-wasm です。前回同様にRustで書いています。 github.com

web のフロントエンドが web です。Typescriptで書いていて、webpackでバンドルしました。 github.com

続きを読む

RustでCenterFaceのONNXを動かした

RustでCenterFaceのONNXモデルを動かして、それをWebAssemblyで動くようにして、最終的にproxy-wasmで動くようにする、というのをGWにやったので書いておきます。

全部で3記事の予定で、この記事ではCenterFaceのONNXモデルをRustで動かしたことについて書きます。

第2次岸田内閣 閣僚等名簿 | 首相官邸ホームページ

リポジトリはこちらになります。

github.com

リポジトリの src/center_face.rs がCenterFaceを動かすコアの部分で、 src/detect.rs が顔検出のONNXモデルで顔を検出して、ボックスとランドマークを追加した画像を吐くプログラムです。

ONNXはtractで動かしました。

GitHub - sonos/tract: Tiny, no-nonsense, self-contained, Tensorflow and ONNX inference

このプログラムをcargoで動かすときは、以下のようにします。 引数として、対象の画像ファイルのファイルパスを指定します。 オプション引数として、検出の際に画像を縮小する度合いを指定します。指定しなければ縮小されません。

image.jpg を渡すと、image_processed.jpg が出力されます。画像サイズは入力と同じです。

cargo run --package wasabi --bin detect image.jpg 2
続きを読む