C#で伺か(SSP)にSSTP通信する

SSTPは`Sakura Script Transfer Protocol`です…というお約束を書きつつ。

# ポイント

* `SSTP`はいわゆる`トランスポート層`より上層のプロトコルである。(要出展)
* よってヘッダー規定などがあるが、一例として**TCP通信のbodyとして書けばOK**

プロトコル、という言葉が出るとついつい特殊な通信方式と捕らえて迷走してしまいますが、フロントエンド風に言えば「JSONでやりとりしてね」(通信自体はHTTP)みたいな感じでしょうか。

ヘッダーをBodyに書くのはパケット通信上よく考えれば普通ですが、簡単なHTTPばっかり触っていると思いつかなくなりSSTP通信用のライブラリを探すことに終始してしまいました。

まあここを見るのが一番でした。
[Pythonで伺かのSSTPを使って通信する方法 - Qiita](https://qiita.com/Nao_string/items/ce97c15415e994c482db)[^1]

あとはここを見てもTCPにのっけるだけということがわかるはず。
[Rubyから伺かのSakura Scriptを触る - もち](https://omochist.hatenadiary.org/entry/20061121/1164121012)
昔、WindowsのRubyで書いたときは確かにShift_JISにしないと文字化けしました。

SSTPに関しては参考元にある
[非公式 SSTP/1.x プロトコル仕様書](https://www.ooyashima.net/db/sstp.html)
か
[伺か - SSTPプロトコル](http://usada.sakura.vg/contents/sstp.html)(テキストエンコードをShift_JISに)
あたりを。

[^1]: >このようにテキストをゴーストに渡して喋らせたいときはSenderにゴースト名(0側)を渡す。
  
  とあるが、仕様からはいまいち読み取れないので機会があれば要検証。
  送信者側なので受けての名前は違和感。

# 環境

* Windows7 64bit
* SSP/2.4.40 (20190513-0; Windows NT 6.1) ゴースト エミリー

# コード

```csharp


using System;
using System.Text;
using System.IO;
using System.Net;
using System.Net.Sockets;


namespace SSTPClient {
    class SSTPSender {
        public static void Main(string[] args) {
            string host = "127.0.0.1";
            int    port = 9801;

            string message = @"NOTIFY SSTP/1.5
Sender: リサ
Event: OnRelease
Script: \0\s[1]This text is sent from Python.\nCan you see this？\1This is a test\e
Option: nodescript, notranslate
Charset: UTF-8

";

            //message = message.Replace("\n", "\r\n"); // 最初から　\r\nだったようだ。
            Console.WriteLine(message.Replace("\r\n", "CRFL\n"));
            Byte[] data = System.Text.Encoding.UTF8.GetBytes(message);
            Byte[] bytesReceived = new Byte[256];

            TcpClient client = new TcpClient(host, port);
            NetworkStream ns = client.GetStream();
            ns.ReadTimeout = 20000;
            ns.WriteTimeout = 10000;
            ns.Write(data, 0, data.Length);
            //Console.WriteLine(client.Connected);

            //サーバーから送られたデータを受信する
            System.IO.MemoryStream ms = new System.IO.MemoryStream();
            byte[] resBytes = new byte[256];
            int resSize = 0;
            do
            {
                //データの一部を受信する
                resSize = ns.Read(resBytes, 0, resBytes.Length);
                //Readが0を返した時はサーバーが切断したと判断
                if (resSize == 0)
                {
                    Console.WriteLine("サーバーが切断しました。");
                    break;
                }
                //受信したデータを蓄積する
                ms.Write(resBytes, 0, resSize);
                //まだ読み取れるデータがあるか、データの最後が\nでない時は、
                // 受信を続ける
            } while (ns.DataAvailable || resBytes[resSize - 1] != '\n');
            //受信したデータを文字列に変換
            string resMsg = System.Text.Encoding.UTF8.GetString(ms.GetBuffer(), 0, (int)ms.Length);
            ms.Close();
            //末尾の\nを削除
            resMsg = resMsg.TrimEnd('\n');
            Console.WriteLine(resMsg);

            //閉じる
            ns.Close();
            client.Close();
            Console.WriteLine("切断しました。");

            Console.ReadKey (true);
        }
    }
}
```

`This text is sent from Python.`(大嘘)

C#のTCP通信のコードは以下から
[TCPクライアント・サーバープログラムを作成する - .NET Tips (VB.NET,C#...)](https://dobon.net/vb/dotnet/internet/tcpclientserver.html)

一応デフォルトのエミリーちゃんでテストしましたが、参考元のPythonのようにちゃんとスクリプトを喋らせることができました。

SSP側は事前にちゃんとサーバー設定しておくこと(Windowsなら設定時のファイアーウォールで許可する必要があるかもしれません)

***

# もっと簡単に

C#でもっと手軽に通信するならば、
[伺か関連ツール/SSTPLib - Ukiwiki](http://ukiya.sakura.ne.jp/index.php?%E4%BC%BA%E3%81%8B%E9%96%A2%E9%80%A3%E3%83%84%E3%83%BC%E3%83%AB%2FSSTPLib)
が有力そうです。
こちらはローカル間での通信を前提として、
`SendMessage`を使ったプロセス間通信として実現しています。

* ウィンドウメッセージ
* ウィンドウプロシージャ
* COPYDATASTRUCT

このあたりの単語を押さえておけば調べやすそうです。
ただ、今回はビルド環境がないのと、事前知識がなかったため、既知のTCP通信で送信するスクリプトを作成しました。