PicoRubyのTCPソケットどうする?

hasumikin is a programmer.


これはmrubyファミリ (組み込み向け軽量Ruby) Advent Calendar 2025の12/20の記事です。


どうする?っていうか、もうつくっちゃっいました:

https://github.com/picoruby/picoruby/tree/master/mrbgems/picoruby-socket

リソース優先の設計

以前から、picoruby-netというライブラリがありました:

https://github.com/picoruby/picoruby/tree/master/mrbgems/picoruby-net

これはRubyKaigi 2024における@s01さんの発表 Adding Security to Microcontroller Ruby のなかで書かれました。

“Adding Security” というのは要するにTLS接続のことで、それを実現するにはそもそもHTTP通信できることが前提です。 だからpicoruby-netというものがつくられた、という訳です。

このpicoruby-netのAPIは以下のような感じ:

require 'net'
client = Net::HTTPClient.new("example.com")
client.get("/")
=> {status: 200, headers: {...}, body: ...} みたいなやつ

つまり、CRubyのNet:HTTPとは互換性がありません。 CRubyのNet::HTTPは内部でTCPSocketクラスを使用します。 TCPSocketクラスは、このような継承によってつくられています:TCPSocket < IPSocket < BasicSocket < IO < Object

PicoRubyに、というかRaspberry Pi Pico WにHTTPクライアントをつくるのがどのくらいのメモリ消費になるのか、この時点では誰にもわかりませんでした。 ですから、PicoRubyのNet::HTTPClientはSocketとかIOとかそういう詳細をすっとばして直接的にLwIPを利用した実装にしたのは賢明な判断でした。

picoruby-netで明らかになったこと

まず、Pico Wに載せたNet::HTTPSClient(HTTPじゃなくてHTTPS)でも、シンプルなPOSTメソッドが十分動くことがわかりました。 これはPicoRubyをIoTデバイスにするための必須要素です。 よしよし。

HTTPSのGETはメモリがきびしめです。 小さな結果しか返さないWebサーバとの通信なら、まあ動くかな、という感じです。 ただ、ここはもうすこし研究の余地があるかもしれません。 RSAよりECDSAの暗号スイートを優先するように処理すれば、OOMは減らせると思います(サーバが対応していれば。いまどきだいたい対応してるよね?)。

そうこうしているうちにPico 2 Wというメモリ2倍のボードが入手できるようになったので、「じゃあそろそろSocketつくっちゃおうか?」という機運が高まってきました。

picoruby-socketの設計

PicoRubyのTCPSocketはこういう継承です: TCPSocket < BasicSocket < Object

UDPSocketは UDPSocket < BasicSocket < Object

CRubyよりも簡略化しました。まず、CRubyでもIPSocketを直接扱うことはあまりない(よね?)ので、これを省略。

IOクラスの継承は悩ましいけど、これも省略。 ベアメタルマイコンにおけるIOってやつの立ち位置がなかなか微妙で、これについてはそのうちちゃんと記事を書きます。書きたい。 っていうかこれがここ数年の最大のテーマ。

ともかく、read, writeあたりのI/Oメソッドは個別のクラスにそれっぽく動くよう実装しました(ダックタイピングとでも言ったらいいでしょうか)。

SSLSocketとSSLContextどげするか

CRubyではHTTPS通信に、OpenSSL::SSL::SSLSocketというものを使いますね。 言語設計のうえでは、OpenSSL以外のSSLContextを使用したTLSが可能ということになっています。 が、現実問題としてOpenSSL以外のTLS実装を利用することはないと思います(あげあげ)。

翻ってPicoRubyですが、MbedTLS以外を使用することはなさそうです。 というわけなので、MbedTLS::SSL::SSLSocketのようなややこしいものはつくらず、トップレベルにSSLSocketとSSLContextクラスをつくりました。 MbedTLSと密結合です。 べつにいいでしょ(そげか?)。

CRubyはこげ:

require 'openssl'
require 'socket'

ctx = OpenSSL::SSL::SSLContext.new
soc = TCPSocket.new("example.com", 443)
ssl = OpenSSL::SSL::SSLSocket.new(soc, ctx)

PicoRubyはこげ:

require 'mbedtls'
require 'socket'

ctx = SSLContext.new
soc = TCPSocket.new("example.com", 443)
ssl = SSLSocket.new(soc, ctx)

picoruby-socketをラップしたpicoruby-net-httpというのもつくってあります。 require "net/http"とすれば、CRubyなんとなく互換のNet::HTTPが使えます。

今後

今後はpicoruby-netは基本的にはあんまりメンテナンスしません。 ゆるやかに非推奨とします。 が、Pico Wではメモリ制約上picoruby-socketより有利だろうと思われますので、使いたい方はどうぞ。

さて、PicoRubyにTCPSocketがあるということは、ラズパイピコでdRubyが動くということですかね?(ばけばけ〜)