ジャンル不定の日記です。

socatでwebsocketサーバーができなかった件

socatでwebsocketサーバーを作ろうとしてうまく行かなかったが、わかった。
socatに-vオプションつけてターミナルに出力してみたが、なんかバイナリぽいのが出力された。

socatから実行するシェルスクリプトではreadで1行ずつ読んでたが、readじゃバイナリが読めずに変数に空文字が入ってた感じぽい。

今回はクライアントから"test"をsendしてたんだが、
readではなくcatでファイルに出力してバイナリエディタで見てみたが、
"8184E4F2CFA39097BCD7"
↑こんなデータだった。

WebSocketの仕様について調べたが、ハンドシェイクのあとはデータは特殊なフレーム化されて送られるのね。
仕様によると、

1bit目: FIN
最後のフレームの場合1で、続きがある場合は0

byteじゃなくてbitなのね。
今回の"8184E4F2CFA39097BCD7"を2進に変換すると、
"10000001100001001110010011110010110011111010001110010000100101111011110011010111"
1bit目は1なんで1フレームで終わり。

2bit目からの3bitは予約bitで、仕様を拡張するのに使うようで、そんなものはないので今回は"000"。

5bit目からの4bitはデータの種別を表すようで、テキストデータの場合は"0001"

9bit目はマスクの有無
クライアントから送るデータはマスク必須で、サーバーから送るデータはマスク不可。
というわけで"1"

10bit目からの7bitがデータの長さで単位はbyteなんだが、値が126の場合は続く2byteがデータ長。127の場合は続く8byteがデータ長。
125以下の場合は続かずその値がデータ帳となる。
今回は"test"と4byteをsendしたんで、"0000100"(4)

ここまでbit単位になっているが、合計すると16bitで2byteになる。
残りのデータはbyte単位。

3byte目からの4byteはマスク用keyで、7byte目から残り全部はペイロードデータ。
ペイロードデータは、拡張データとアプリデータを連結したもので、予約bitが"000"で何も拡張されてなければ拡張データは存在しないので、ペイロードデータ=アプリデータになる。

アプリデータは単なる平文ではなく、
マスク用keyと元のデータをXORしたものになっている。
マスク用keyは4byteだが、データが5byte以上ある場合はループして5byte目はマスク用keyの1byte目とXORされる感じ。

今回のマスク用keyは2進で
"11100100 11110010 11001111 10100011"
になってる。

アプリデータは
"10010000 10010111 10111100 11010111"

元のデータにマスク用keyをXORしてアプリデータになったわけだが、XOR演算は再度XORすれば元のデータに戻る。
というわけで、XORしてみたが、
"01110100 01100101 01110011 01110100"
ASCIIコードの"test"ですね。

eth0が不安定だったようだが・・・

Orange Pi R1で
Feb 14 22:49:11 OrangePi_R1 kern.info kernel: [ 501.127514] dwmac-sun8i 1c30000.ethernet eth0: Link is Down
Feb 14 22:49:13 OrangePi_R1 kern.info kernel: [ 503.207650] dwmac-sun8i 1c30000.ethernet eth0: Link is Up - 100Mbps/Full - flow control rx/tx
が数分おきにシステムログに出力されてたんだが、
ケーブル代えてみたら治ったぽい。

元々ついてたのはCAT5の記載があった。
代えたやつも記載がないんでCAT5相当の古いやつ?
今どきカテゴリ5のケーブルはないらしいが、Orange Pi R1は100Mbpsだし未開封のケーブル使ったから問題ないのかな。

コマンドラインでBase64のSHA1ハッシュを出力する方法

先日からOrange Pi R1の管理用CGI関連を暇な時間にやってて、とりあえずHTTP+CGIでできるのは公開もして一段落してるが、
websocketの実験も兼ねてsocat+shでwebsocketサーバー作ってる。

WebSocketの接続は
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Origin: http://example.com
Sec-WebSocket-Version: 13
リクエストがこんな感じで、
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
レスポンスはこんな感じになるみたい。

ここで重要なのは、
リクエストのSec-WebSocket-Keyを元にSec-WebSocket-Acceptを作る。
作り方は、
Sec-WebSocket-Keyの値に"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"を追加した文字列のSHA1ハッシュをBase64した文字列がSec-WebSocket-Acceptになる。
なので、
"dGhlIHNhbXBsZSBub25jZQ==258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
を変換して
"s3pPLMBiTxaQ9kYGzzhZRbK+xOo="
にできればいいんだが、
sha1sumコマンドにはBase64を出力する機能もバイナリを出力する機能もない・・・

で調べたが、
$ echo -n "dGhlIHNhbXBsZSBub25jZQ==258EAFA5-E914-47DA-95CA-C5AB0DC85B11"|openssl dgst -binary -sha1|base64
s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
sha1sumを使わずにopensslコマンドを使う。
$ echo -n "dGhlIHNhbXBsZSBub25jZQ==258EAFA5-E914-47DA-95CA-C5AB0DC85B11"|sha1sum|xxd -r -p|base64
s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
↑xxdコマンドでsha1sumの16進文字列をバイナリに変換する。
$ echo -ne "$(echo -n "dGhlIHNhbXBsZSBub25jZQ==258EAFA5-E914-47DA-95CA-C5AB0DC85B11" | sha1sum | cut -f1 -d" " | sed -e 's/\(.\{2\}\)/\\x\1/g')" | base64
s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
↑sedを使ってるがわけわからんコードw
の3種類の方法を発見した。

opensslはBuildrootの標準で入ってない。
xxdは母艦のArch Linuxに無いから
Buildrootにもないかと思ったがあった!が、-rオプションに対応していない・・・
sed使うコードはわけわからん。
というわけで考えたが、openssl入れてまで実現したくないな・・・
なんだが、websocketの実験もしたいし、とりあえず母艦で実験は続けるか。

STM32が使えない・・・

Orange Pi R1関連も一旦落ち着いた感じなんで、届いたまま放置してたSTM32を使ってみようと思った。

はんだ付けは苦労したがなんとかできたと思う。
DIP変換基板はヘッダーピンを装着してブレッドボードにさせる感じが良かったと思うんだが、
手持ちのヘッダーピンが太いようで入らなかったんでソケットを装着した。

開発ツールはSTM32CubeIDEというSTM公式のツールが情報多い感じなんで(というか、他の情報が全然ない)それ入れようとした。
AURにあったんだが依存パッケージがmakepkgできず諦めて、公式からインストーラーを持ってきて入れた。

中華ST-Linkには端子が10本で、
RST、GND、SWIM、3.3V、5.0V、SWDIO、GND、SWCLK、3.3V、5.0V
となってる。
このうちSWDIOとSWCLKをつないで、3.3VとGNDで電源供給すればいいぽい。
STM32F042K6T6は1番ピンがVDD、32番ピンがVSSとなっているが、VDDが3.3VでVSSがGNDのことみたい。
24番ピン(PA14)がSWCLKで、23番ピン(PA13)がSWDIOとのこと。
最初、角にあるPA14でLEDを点滅させようかと思ったが、SWCLKは空けておいたほうが良さげだから9番ピン(PA3)に変更した。

CubeIDEでプロジェクトを作成して、ICの画面で24番ピンをクリックしてSWCLKにしたら23番ピンもSWDIOに変わった。
9番ピンをクリックしてGPIO outputにして、
上部メニューの "Project > Generate code"したら左サイドバーの "Core > Src > main.c"の"MX_GPIO_Init"にGPIOの設定が出てきた。
mainのwhile(1){}ブロック内にコードを書けばいいみたいだが、
  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
    HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_3);
    HAL_Delay(1000);
  }
  /* USER CODE END 3 */
}
とした。

で、"Run > Debung As"からビルドしてデバッグができるみたいなんだができなかった。
調べてもわからず、
統合開発環境は機能が多すぎて難しいから、CUIで使えそうなstlinkとopenocdを入れて試してみたんだが、
$ sudo st-info --probe
Found 1 stlink programmers
 serial: 413a010032124353354b4e00
openocd: "\x41\x3a\x01\x00\x32\x12\x43\x53\x35\x4b\x4e\x00"
  flash: 0 (pagesize: 0)
   sram: 0
 chipid: 0x0000
  descr: unknown device
こんな感じで、なんかST-Linkは認識しているがSTM32を認識していないような?

これまたわからないんで、最終手段としてWindowsにCubeIDEを入れて試してみたが、
Linuxのときと表示内容は違うが同じところでエラー。
やはりST-Linkにデバイスがつながっていないようなエラーが出た。

頑張ってshでCGI作ったんだが・・・

頑張ってshでCGI作ったんだが、メインPC上のApacheで問題なかったが、
Orange Pi R1のuhttpdで実行したら502 Bad Gatewayになった。
途中までOrange Pi R1の方でもテストしながら作っててできる感じだったのに、最終段階で何故か・・・

エラー原因確認したが、
shの.コマンドで設定ファイルを読み込むようにしてたんだが、
設定ファイルはCGIと同じディレクトリなんで
. ./config.txt
って感じでロードしてた。
これで502 Bad Gatewayになる。

CGIでpwdコマンド出力してみたら、CGIは/cgi-bin以下にあるんだがドキュメントルートの/var/wwwだった。
CGIの設置ディレクトリじゃなくてドキュメントルートでCGIが実行される感じなんだね。
絶対PATHで読み込めばいいんだが、設置場所変えたらダメだし、$0の場所にcdしなきゃダメな感じかな。
これ不便すぎる。

tinyhttpdなんてのがあった

BuildrootにWebsocketサーバとかないかと思ってmenuconfig眺めてたんだが、tinyhttpdなんてのを発見した。
uhttpdより小さそうなhttpdぽい。

情報少なくてわかりにくいが、
https://sourceforge.net/projects/tinyhttpd/
↑ここのやつだと思うんだが、
ソース眺めてみると、これそのままビルドしたら待受ポートが動的割当のhttpdができあがってport80を指定できないような・・・
それだと使えないな。
CGIはPOSTかクエリ付きか、リクエストファイルに実行権限がついてる場合はCGIになるみたい。

ナイロンネジ注文した

Wikiの方に書いてるが、Orange pi R1がルーターとして使いこなせそうになったんだが、
ケースというかディスプレイのVESAマウント穴に固定するつもりで寸法測ったが、
メーカー公式だと寸法が45x60mmとのことだが、55x46mmだった。
メーカーのはコネクタの飛び出し分も入れてるとして、短辺側が実測のがデカイw
ネジ穴間隔は42x40mmくらい。
ネジ穴の大きさはm3がちょうど入る。

が、ネジ穴周りが金属ネジだとダメなとこショートしそう。
というわけで、樹脂製のネジを買おうと思ったんだが、Amazon公式発送だと2000円以上じゃなきゃダメな感じ。
中華業者発送だと結局遅くなるし、eBayで注文した。
m3ナット、m3-6mmネジ、m3-6-6mmオスメスソケット
の3つを25個ずつで350円くらい。

dnsmasqの設定

dnsmasqの設定をググって調べると
interfaceとかlisten-addressで指定してbind-interfacesを指定すると特定アドレスのみで待ち受けられるような記述が多いが、
これDHCPだと機能しなくてDNS用の設定と思う。
設定しても 0.0.0.0:bootps でワイルドカードになってる。

あと、
dhcp-option=option:router,
dhcp-option=option:netmask,
↑これは設定不要?
説明書見ると、デフォルトゲートウェイとDNSサーバーは自分自身、
ネットマスクとブロードキャストは自身と同じ設定、
になるとか書いてある。
なので、DNSを無効にする場合は
dhcp-option=option:dns-server,
だけ設定する必要があるような・・・