Title

NBNS(NetBIOS名の解決)に失敗する

タイトルのとおり。

どうも、Windows相手にNetBIOSの名前解決を試みると失敗することがあるようです。
ネットワーク・アナライザで見ると、リクエストは飛んでるのに応答が来ない。
発生条件は不明ですが、テストで変なリクエスト送ったあとに起きやすい気がするけど、何もしてなくても起きる時もある。

理由は不明ですが、一旦失敗するようになると、特定のIPアドレスから発信したものに対して応答が返らなくなる模様・・・。

全くそのままでIPアドレス変えると動くようになります。

このあたりエラーメッセージを簡略化してたので(接続できませんでしたくらいに)、次からメッセージ入れようと思います。
基本ここで失敗はしないと思っていたので。

どうもSMB/CIFS周りはロバスト性に欠けてやっかいですわー。
Windows同士でも調子わるいときありますもんね。

無駄にSMB/CIFS周り(のバッドノウハウ)に詳しくなっていく・・・。

ComicGlass、SMB2及びSMB Server対応

詳細

後日また詳細に書こうと思いますが、ComicGlassの次バージョンにてSMB2プロトコルへの対応とSMBサーバ機能が利用できるようになりました。

SMB2はWindows Vista以降で利用できるプロトコルでWindowsXP/WindowsServer2003以前で使われていたSMB1の後継プロトコルになります。
現在ほとんどの機器がSMB1に対応して動作していると思いますが、XPのサポート期限も切れ、WindowsServer2003のサポートもまもなく切れますのでSMB2以降が主流になってくるかもしれません。
Windows8以降では高セキュリティ設定をするとSMB1が使えないことがあります。

SMBサーバ機能は、文字通りSMBサーバ(ファイル共有サーバ)として動作する機能です。
WindowsのエクスプローラーからComicGlass内のファイルを読み書きできます。
書き込みはもちろん、読み出しも出来ますし、直接ファイルを開いて確認することも可能になります。
フォルダを内容を含めてコピーしたり、ファイル名をPCから編集することも出来ますのでファイルの管理やバックアップが大変便利になります。

ただし、エクスプローラーで開いたままiPhoneをスリープしたりアプリを終了してしまうとWindows側がタイムアウトするまで困ったことになりますので使い方には少し注意が必要です。
(一応、ファイルへのアクセス中は自動的にiPhoneがスリープしないように実装してあります)

実装について
SMBの実装はVisuality Systems社のCIFS NQを使っています。
製品なので、接続互換性のテストがしっかりされており、(おそらく通信相手の仕様間違いも含めて())色々な相手と正しく通信できると思います。
基本用途は組み込み向け製品向け製品なのでiOSに移植するために実装した部分で不具合がある可能性は多分にありますが、とりあえず安定した通信が期待できるのではないかと。
(一応当面は旧独自実装も使えるよう、選択式にしてあります。これまで問題なかったのに問題が起きたという場合には旧設定にしてみてください。そして発生状況を教えて頂けると大変助かります)

GoogleDrive対応

重い腰を上げて、GoogleDrive対応を実装しました。
前にAPIを少しみた段階で”うえっっ”てなって放置していたのですが、ちょっと癖があるんですよね。
基本はHTTPなので、通信内容に癖があるわけじゃないのですが、SDKが微妙に使いづらいのです。
といっても作りが悪いわけでなく、GoogleDriveだけでなくGoogleの汎用ライブラリを使っているのが原因です。

ファイルのダウンロードは比較的簡単ですが、ストリーミングが少し面倒でした。
どうしても細切れにアクセスすることになるのでパフォーマンスがいまいち。
もう通信自体Googleのソース使わずに全部独自に実装してやろうか・・と思いましたがあまりに大変すぎて断念。

また、ComicGlassではリモートのファイル一覧を開いた状態のまま、アプリのスリープ中や終了後も状態やリストを保持する機能があります。
モバイルデバイスですから、前の状態からいつでも再開できるというのは重要なことです)

ここがまた問題で、GoogleDriveが返すファイルにアクセスするためのユニークURIが時間が経つと(正確にはセッションが新しくなると)勝手に変わります。
といわけで、前回開いていたファイルが現在どのファイルなのかをマッチングする必要があります。
この辺もURIが変わったら自動的に情報を再取得することで問題を回避しました。
たまに失敗しますので、どうしてもエラーになる場合は接続からやりなおしてください。

SMBとUnicodeファイル名正規化問題

かつて、LinuxからSambaを使ってWindowsのファイル共有にアクセスすると、文字コードの変換の問題で大変苦労しました。

時代は流れ、LinuxでもWindowsでもUnicodeが標準になりました。
というわけで、一般的にSMBを使う分には文字コードで苦労することはほぼなくなりました。

しかし・・・。

とりあえず以下の画像をご覧ください。
これはWindows8のエクスプローラーの画面です。

SnapCrab_NoName_2014-3-23_1-27-56_No-00

何かおかしいところにお気づきでしょうか。
おかしいですよね。
いいおっさんがプリキュア・・・じゃなくて、同一のファイル名が同じフォルダに存在しています。

実はこれ、「ピ」と「プ」の文字がUNICODEの結合文字列になっています。

通常、Windowsで「プリキュア」と入力した場合、「プ」の文字はU+30D7になります。
しかしMacOSで「プリキュア」と(ファイル名として)入力した場合、「プ」の文字はU+30D5 U+309Aになります。

U+30D5は「フ」です。
U+309Aは半濁点記号です。
つまり、「フ」+「゜」の合成で表現されます。
(なお、ここに書いた「゜」はU+309Cで、合成用ではなくそういう1文字ですので念のため)

Unicodeではこのような複数の表現方法があります。
混ざってる何かと困るので、普通は同一コードになるように正規化して扱います。
合成した方を使うのをNFC(Normalization Form Canonical Composition)、分解した方を使うのをNFD(Normalization Form Canonical Decomposition)と言います。

さて、最初の画像のファイル名ですが、「パ」と「プ」の表現方法を結合文字列、合成文字、混ぜてあります。
結果として4パターンの同名ファイル名が出来てしまったわけです。

何故かWindowsは上記のような状態を許してくれます。
許しているというか、初期の頃になんの正規化もせず扱ってしまったため、以後もそのままになっているんだと思います。(憶測)

さて、一方MacOSですが、上記のようなNFD,NFCの混在を許してくれません。
必ずNFDライクな正規化をされます。(正確にNFDでないのがまた困るんですが・・・)
iOSも一緒です。
よってファイル共有をすると大変困ります。
SMBには明確な仕様がない(もしかしたらあるかもしれなけど、ドキュメント読む限り発見できず)。
おそらくWindowsなのでNFCのUTF16 Little endianなんでしょうが、NFDでもプロトコルとしては問題なく(というかノータッチで)動きます。

さて、どう実装するのが正解なのでしょうか。
困りました。

iOSでSMBライブラリを実装する(その5)ホストの検出(ブラウジング)

Windowsでマイネットワークを開くと近くのコンピュータが自動的に表示されます。
これは単純にブロードキャストしてそれぞれが応答をするのではなく、バックアップブラウザを持っているホストに問い合わせをすることで実現しています。

バックアップブラウザはLAN上のコンピュータが自動的に選定されて、どれかがなります。
OSのバージョンによって優先度が決まっていたりしますが、問い合わせるだけなのでその部分はあまり気にしないことにします。

さて、これを実装しようとすると結構面倒で、

1.バックアップブラウザの検索
2.バックアップブラウザからブラウザリスト(ホスト一覧)の取得
と2段階を踏む必要があります。

1はブロードキャストで問い合わせ(GetBackup Request)をすると、バックアップブラウザが応答(GetBackup Response)を返してくれます。
ここで一つ重大な問題があります。

問い合わせは当然ながらIPで行っているわけですが、元はNetBIOSのため、バックアップブラウザが自分に対して応答を返すときに、名前解決を要求してきます。
よってNBNSの問い合わせに応答できるように実装しておかなければなりません。
(IPで送ってるんだからそのアドレスに返してよ・・と思いますがそうはなっていません)

これは本来であればOSの機能なので、アプリで実装してしまうと、他のアプリと同時にポートをBINDしたときに問題が起こりそうですね。

次に2ですが、これはSMBと同じです。
しかし、ダイレクトホスティング(ポート445番)では挙動が不安定でした(Windows7で確認)。
ちゃんとNetBIOSセッションサービスを利用すると安定して取得できます。謎です。

さて、この方法ですと、ワークグループ名の指定が必要です。
また何かしらの理由でバックアップブラウザが存在しないとホスト一覧を取得できません。

以下は禁じ手だと思うのですが、GoodReaderの実装がそうなっていたので紹介しておきます。
確実な方法ですが、ポートスキャンがちょっと攻撃っぽいので今のところComicGlassでは非採用にしました。

1.ネットワークのサブネットマスクからネットワークアドレスとホストアドレスを分離します。
2.全てのホストアドレスのTCP/445番ポートに対して接続を試みます。(つまりポートスキャン)
3.接続できたら、セッションセットアップの途中までやります。途中というのは相手のホスト名が判明するところまでです。(認証の途中)
4.ホスト名(NetBIOS名)が取得できたら切断しちゃいます。

とりあえず上記の方法であればブラウジング機能に頼らずホストを検出できます。ただし同じサブネットの中のみ。
(ポートスキャンしているから当然といえば当然ですが・・・)

iOSでSMBライブラリを実装する(その4)~あると便利なもの

今回はただの愚痴ですが・・・。

SMB/CIFSの仕様をみると、データの途中にいきなり可変長データが入っていたりします。
さらにpaddingがいたるところに出てきます。

また、変数は存在するものの、実際のところどういう値にしていいのか、定数としてどんなものがあるのかわからないことが多々あります。

そんなときはプロトコルアナライザです。
WindowsServer系ならばリソースキットで利用できますが、WireSharkなんかも便利です。

そして、絶対あったほうが良いもの・・。

それはポートミラーリング、またはポートモニタリング機能がついたスイッチングハブです。
完全なリピータハブがあればそれでもいいですが、おそらく10BASE-Tの時代のものしか無いと思うので・・・。

数年前までは何万円もしましたが、今では1万円を切る商品もあるみたいです。

iOS向けSMBライブラリを実装する(その3)~NetBIOS名の解決

Windowsで使われる、NetBIOSまわりを頑張ってみます。

やりたいことは、Windowsコンピュータ名(NETBIOS名)からIPアドレスを取得することです。

NetBIOSはとにかく不可解です。
本当に不可解なんですが、歴史的背景があるので仕方がありません。
どのくらい古いかというと1980年代くらいなので仕様書もイマイチ読みづらく、後の改変も多数あるので本当にわかりづりです。

まずNetBIOSはTCP/IPとは別物です。
現在ではプロトコル部分をNetBEUI、インターフェイス部分をNetBIOSと呼ぶようですが、元はインターフェイス(API)からプロトコルまですべてひっくるめた規格でした。

NetBEUIは現在では使われることは無くなりましたが、NetBIOSのインターフェイスだけは利用され続けているというわけです。
NetBIOSをTCP/IPで利用できるようにしたものがNetBIOS over TCP/IPです。(TCP/IPとか言いながらUDP/IPも多用します)
これ時代もWindows3.x時代のものなので何かと理解しづらいです。

さて、関係ありそうな、NetBIOS over TCP (NBT) Extensions NetBIOS over TCP (NBT) Extensions[MS-NBTE]をまず見てみると
名前解決についてはRFC1002を見るように書いてあります。

RFC1002の内容は「NetBIOS-over-TCP」となっています。
日付は March 1987・・・。
読んでみると・・・なんか意味不明・・ですががんばって読む。

とりあえずパケットのフォーマットはこう

                       1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3
    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |         NAME_TRN_ID           | OPCODE  |   NM_FLAGS  | RCODE |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |          QDCOUNT              |           ANCOUNT             |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |          NSCOUNT              |           ARCOUNT             |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

で、名前解決のリクエストはこういう固定値を入れる

4.2.12.  NAME QUERY REQUEST

                        1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3
    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |         NAME_TRN_ID           |0|  0x0  |0|0|1|0|0 0|B|  0x0  |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |          0x0001               |           0x0000              |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |          0x0000               |           0x0000              |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                                                               |
   /                         QUESTION_NAME                         /
   /                                                               /
   |                                                               |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |           NB (0x0020)         |        IN (0x0001)            |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Bはブロードキャストフラグなので、1で固定みたいです。
なのでフラグ部分は0x0110。

QUESTION_NAMEの部分はRFC 883を使ってコード化しろって書いてます! 
これがまだ何故??というやり方で頭に???浮かびっぱなしです。

とりあえずNETBIOS名は15文字です。本来は16文字でしたが、最後の1バイトを別の用途に割り当てたため15文字。
15文字に満たない場合はスペース(0x20)で埋めます。

16文字目はサフィックスで種別を表すのですが、
0x00だとワークステーション サービス
0x20だとファイル サーバー サービス
0x1bだとドメイン マスタ ブラウザ
・・・などなどを表すようですが、、
これはもともとのLAN Managerの仕様ではなくMicrosoftの拡張仕様です。
どうやら0x00はクライアント、0x20がサーバサービスを示すようなのでここでは0x20が正解だと思われます。(実際にはどちらも名前登録されていて、反応もしてくれるので良いのですが)

で、肝心のNETBIOS名の格納方法です。
最初の1バイトは0x20を指定します。0x20=長さが32byteの意味で、NETBIOS名の場合は32byte固定と書いてあるので必ず0x20を指定。

NEBIOS名+サフィックスの16byteを以下の手順で符号化して32Byteにします。
最後にNULL(0x00)くっつけるので、長さを表す先頭の0x20と合わせて34byteになります。
これがQUESTION_NAMEになります。

NETBIOS名の符号化は、
まず文字コードを4バイト単位にわけます。
空白文字(0x20)であれば0x2と0x0です。
0x0をA、0x1をB、0x2をC・・・という感じで文字を割り当てます。
0x20であれば「CA」になります。

つまりNETBIOS名が「FRED」であれば「EGFCEFEECACACACACACACACACACACACA」になります。

さて、これをUDPでブロードキャストすれば該当ホストがレスポンスを返してくれます。
iOSで1024番以下のポートをbindできるのか?とちょっと不安でしたが、やってみたら大丈夫でした。

レスポンスの内容は以下のとおり

4.2.13.  POSITIVE NAME QUERY RESPONSE

                        1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3
    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |         NAME_TRN_ID           |1|  0x0  |1|T|1|?|0 0|0|  0x0  |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |          0x0000               |           0x0001              |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |          0x0000               |           0x0000              |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                                                               |
   /                            RR_NAME                            /
   /                                                               /
   |                                                               |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |           NB (0x0020)         |         IN (0x0001)           |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                              TTL                              |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |           RDLENGTH            |                               |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+                               |
   |                                                               |
   /                       ADDR_ENTRY ARRAY                        /
   /                                                               /
   |                                                               |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

TTLより前はリクエストと同じ。
ADDR_ENTRY ARRAYにIPアドレスが入ります。
(複数ある場合は複数入ります。パケットの送信アドレスを見るか、自分のネットワークマスクと較べてどれを利用するか決めます)

実際には名前の解放パケットとかも飛んでくるようですが(それを見てキャッシュから削除する)、iOSでそれを監視し続けるのは非現実的なので無視します。

なお、IPv6の場合はLLMNR(Link-Local Multicast Name Resolution)を使う必要があります。
今回はIPv4だけでいいのでそっちはやりません。
(そのくらいの知識がある人はIPアドレスでわかるでしょうし・・・)

さて、これでコンピュータ名(NETBIOS名)を解決できるようになりました。
まぁ、なったところでどう使うかは悩ましいところなんですけどね。

iOSでSMBライブラリを実装してみる(その2)~NTLM認証

Windows関連情報は運用を目的にしたものが多く、コーディング例はあんまり聞かないのでメモ的に書いておきます。

SMBの認証にはLM認証、NTLMv1認証、NTLMv2認証、Kerberos認証のいずれかを使います。
Kerberos認証は鍵交換でActiveDirectly環境想定のものなので除外。

LM認証はNTLMv1認証はだいぶ古いにもかかわらずつい最近まで使われていましたがWindows7からはデフォルト無効になっているようなのでこちらも使わない方針で行きます。
ようするに面倒なのでNTLMv2だけ対応します。
あと、まず非対応ということはあり得ないので、Unicodeだけ対応します。

さて、NTLMは「NT LAN Manager Authentication Protocol」のことらしいです。
Microsoftの仕様書名は[MS-NLMP]です。

基本的には認証情報をバイト列に並べているだけなので仕様書を読みながら実装すればなんとか出来ます。
SMB/CIFSがアライメントやら謎のpaddingやらとても実装しにくいのに比べると可変長データをpayloadにまとめているので少しだけ気楽です。

クライアント側で生成する必要があるのは、NEGOTIATE_MESSAGEとAUTHENTICATE_MESSAGEです。
前者は本当に必要な情報を連結してバイト列にしているだけなので楽勝。それでも実際実装してみると一発ではなかなか思い通りにならないのですが・・・。

後者のAUTHENTICATE_MESSAGEもほとんど同じですが、NEGOTIATE_MESSAGEに対してサーバから返されたCHALLENGE_MESSAGEに含まれる情報を使ってチャレンジレスポンスを計算する必要があります。
それがNTLMv2なわけですね。
AUTHENTICATE_MESSAGEのうち、計算が必要なのはNTLMv2 Responseというフィールドです。

といっても、使うのはmd4とmd5だけなんですね。
試行錯誤の結果正しい結果を出せるようになったのでメモ。

入力
・ドメイン(大文字のみ,UTF16LittileEndian)
・ユーザ名(大文字のみ,UTF16LittileEndian)
・パスワード(UTF16LittileEndian)
※ドメインとユーザー名は全て大文字に変換しておく
※文字列のNULLターミネーションは不要(含めない)

サーバから(CHALLENGE_MESSAGEの内容)
・Server challenge (8byte)
・TargetInfo (可変長)

(クライアント側で)生成するもの
・タイムスタンプ(8byte)
・Client challenge (8byte乱数)

1.パスワードのmd4ハッシュを求める
2.ユーザ名とドメイン名を連結する(例:「user」+[Domain]→「USERDOMAIN」 )
※ここを先入観でDOMAIN+USERの順番だと思い込んでハマりました・・・
3.2に対して、1を鍵として、hmac_md5を計算する
4.以下を連結する(これをblobと言うらしい)
  署名(固定値=0x01010000)
  4byteの予約領域その1(0x00000000)
  タイムスタンプ(8byte)
  Client challenge(8byte)
  4byteの予約領域その2(0x00000000)
  TargetInfo
  4byteの予約領域その3(0x00000000)
5.Server challengeと4を連結する
6.5に対して、3を鍵として、hmac_md5を計算する
7.6と4を連結する

7で出来たものがNTLMv2レスポンスになります。
ややこしいですね。

MD4はRFC 1320
MD5はRFC 1321
HMACは RFC 2104に規定されています。
なお、それぞれC言語コードが附属しています。
たいして計算量も無いので最適化も不要でしょうから、そのまま使ってもOKかと思います。ただしライセンス表記が必要です。
探せばそのあたりをクリアしたコードも見つかります。
(僕はmd4はそのまま使いました。md5は以前準備したコードがあったのでそっちを利用)

基本的に、SMBと同じく全てリトルエンディアンが使われます。
普通ネットワークバイトオーダーと言えばビッグエンディアンなんですけどね。
最初はエンディアン気にしてコード書いてましたが、どうせiPhoneもリトルエンディアンだし途中から面倒になってリトルエンディアン環境想定で作りました。

さて、これでSMBサーバと認証ができるようになりました。

次回はWindowsファイル共有に使うNETBIOS名(ホスト名)をNetBIOS over TCP/IPを使って名前解決(IPアドレスを取得する)をやってみたいと思います。
まぁ、IPアドレスでアクセスすれば良いので不要な気もしますが。。

欲を言えばBROWSERプロトコルを使ってネットワーク上のNETBIOSホスト名を自動収集したいところですが、、まぁNASとかはBonjourに対応してるから不要な気もします。

2015-05-18 修正 challengeのスペルが違うというお恥ずかしいミスがありましたのでこっそり修正。

iOS上でSMB/CIFS(Windowsファイル共有)をゼロから実装してみる

何故か全くライブラリが見つからないSMB/CIFS。
ググってみても、「SMBのiOSライブラリないの?(そもそもObj-cに限定しなくても無い)」「sambaがGPL、ぐぬぬぬ・・・」という記事しか見当たらない。組込み向けの製品はいくつか見つかりましたが、たぶん個人で買えるようなものじゃない。
きっと実装は厄介なんだろうなと見る気もしてなかったですが、風邪でダウンしていたので、その時に一度気合を入れてテストしてみました。

なお、tangoというOSSがあるようですが、うまく動かない。
恐らく認証関連が古いせいで最近のWindowsでは動かないぽい。
extended security flagが立っていると即座にエラー返すようなのできっとダメだろう、ってことでtangoは諦める。

SMBはSMB1とSMB2があって、SMB2は大きなバッファが扱えて高速LANで効率がいいようですが、NASとかで対応していないものもあるかもしれないし、iPhoneのWifiからではほとんど速度は変わらない(と思う)のでまずSMB1にトライ。(SMB1の仕様書読んでいるうちに、ファイルオフセット指定が32bitしかなく、2G超えるファイルダメじゃん!って気づいたけど気づかなかったことにします:→追記で訂正)

悪名高いMicrosoftの仕様書を読み解くわけですが、SMBは魑魅魍魎な方言やら実はこうでしたー! 残念! みたいな実装があって苦しむと噂には聞いております。

さて、さて、
仕様書読むと案外単純な通信です。
これはいける。

流れは、
・ネゴシエーション
・セッションリクエスト(認証)
・ツリー接続、一覧取得、(ここでパイプ、RPCの仕様書を読まされる(涙))
・ファイル一覧の取得 (ワイルドカード*でFindFileする。ルートディレクトリだと失敗するのでちょっと変える必要ある
・データの転送(実は簡単な命令っぽい)

という感じで、バイナリデータなのでC言語では扱いやすい(はず・・)

しかしセッションリクエストで早速ハマる。

[MS-CIFS]306ページ、2.2.4.53 SMB_COM_SESSION_SETUP_ANDX (0x73)

This command was introduced in the LAN Manager 1.0 dialect.
The formats of the request and response messages have changed since the command was first defined. The CIFS format, as defined for the NT LAN Manager dialect, is presented here. This format MUST be used when the NT LAN Manager dialect has been negotiated.
(後略)

このコマンドはLAN Manager1.0のときの(方言で?)定義された。
このコマンドが最初に定義されてから、リクエストと応答メッセージのフォーマットが変わったよ。

うーん。LAN Manager1.0はIBMの頃の仕様でもう使われていないので、NT LMを使うみたいね。
Wordデータは26 bytesなので、WordCoundは13になるはず??? よくわからん

他の通信をパケットキャプチャしてみるとWordCountが12=24Byteしかない。1バイトどこいったー?

試しにきにせず26Byteで送ってみたら見事エラーでございます・・・。

と思ったら別のファイルの[MS-SMB]の方の53ページにextended security flagを使う時にはSecurityBlobLengthを使えって書いてあった。・・・といった具合に仕様書読むにもコツが必要な模様。

とりあえずWindows7とNAS相手にツリー一覧取得までは出来たので、頑張ればなんとかなるかなー。
どうも一番面倒なのはツリー一覧取得するところだったっぽい。
もう少しちゃんとエラー処理とか、ネゴシエーションやらないと実用的には使えないと思うので作業量は多そうですが。

時間かかる要素その2は、SMB通信に使うNTLMの暗号化をどうするか。
とりあえず外部ライブラリ使って認証が通ることは確認しましたが、ここも自前で実装するとなると読まなければならない仕様書がまた増えて面倒くさい。
まぁ、こちらの方が内容はある程度クリアなので実装することは出来そうですが、まっとうにやったら時間かかりそうです。まっとうじゃない方法探します。

NTLMもUIWebViewでは使えるので、API公開してくれたらいいのに・・・。

ComicGlassへはうまく出来たら実装します。

追記:

だいぶ分かってきました。
上記記述にいくつか間違いがあったので一応訂正。

ファイルのリードについては見ていた資料が古かったようで、新しい資料にはOffsetの上位バイトのフィールドがありました。
(SMB_COM_READ_ANDX==0xeE最後の4バイト,Optinal)
よって2GBを超えるファイルもオフセット付で扱えます。
またTimeoutがTimeout_or_MaxCountHighになっており、ファイルを読む場合はMaxCountHighとして扱うように変わってました。
ネゴシエーションの時に対応非対応を設定できるようです。
(しかしWindowsXPの通信見てみると使ってない・・・)

ときおり出てくるDataOffsetとかの意味がわかって受信データのpaddingサイズ不要が要らないことがわかりました。(DataOffsetでデータの先頭がわかる)

自動着色(擬似カラー)

掲示板で擬似カラーフィルタを実装してほしいというご要望があったので実装してみました。

やりかたは濃淡の応じて色を割り当てているだけです。
いわゆるPseudo-color技術ですね。

ポイントは漫画の着色にどのような色を使うかということですが、どうやら、暗い色を青系統、明るい色を茶系にすると人が自然に見えるようです。
というわけで擬似4色刷りのような感じになります。

すごくうまくいく場合と、そうでもない場合があるのですが、それは画像次第ですね。
著作権的にOKな良いサンプルがなかったのですが、一例としてはこんな画像になります。

4color
(佐藤秀峰「ブラックジャックによろしく」 漫画onWeb)

元からカラーの画像に適用してしまうと色味がおかしくなってしまいます。
そこでページのモノクロ/カラー判定アルゴリズムも開発しました(ってそんな大げさなものではないですが)
画像中の代表画素(1024画素)を使ってカラー判定する軽量アルゴです。
擬似カラー部分も含めてほとんどストレスない速度で動くはず。

このフィルタはたぶん次のバージョンに実装しますのでお楽しみに。

次のバージョンでは以下の不具合にも対処します(今してます・・・)

・縦長すぎる画像で縦横比がおかしくなる
・リモートファイルの順序がソートされていない
・ストリーミングモードでRARの順序がおかしい

履歴、リモートフォルダのショートカットも実装中です。
バグがまだ多数あるので先にそちらの修正版をリリースするかもしれませんが、どちらにしても近日中・・・の予定です。

カレンダー

2024年11月
 123
45678910
11121314151617
18192021222324
252627282930  

▲Pagetop