2019年11月28日木曜日

[swift4] iOSアプリで暗号化したMQTT通信(subscribe)をやりたい

まずPodfileです。


platform :ios, '11.0'
target 'YourProject' do
  # Comment the next line if you're not using Swift and don't want to use dynamic frameworks
  use_frameworks!
  # Pods for YourProject
  pod 'Moscapsule', :git => 'https://github.com/flightonary/Moscapsule.git', :branch => 'swift4'
  pod 'OpenSSL-Universal'
end

次に実装を示します。

import Moscapsule した上で、以下のようにFirstViewController等に記述して下さい。

{
    ...

    private var _timerTimer?
    private var mqttConfigMQTTConfig!
    private var mqttClientMQTTClient!
    
    // MARK: - オーバーライド
    override func viewDidLoad() {
        super.viewDidLoad()
        //MQTTコールバック登録
        moscapsule_init()
        mqttConfig = MQTTConfig(clientId: NSUUID().uuidString, host: "ホスト名", port: ポート番号, keepAlive: 60)
        mqttConfig.mqttAuthOpts = MQTTAuthOpts(username: "", password: "")
        mqttConfig.cleanSession = true
        if let path = Bundle.main.path(forResource: "ファイル名", ofType: "crt") {
            mqttConfig.mqttServerCert = MQTTServerCert(cafile: path, capath: nil)
        }
        mqttConfig.onMessageCallback = { mqttMessage in
            if mqttMessage.topic == "トピック" {
                if let msg = mqttMessage.payloadString {
                    DispatchQueue.main.async { self.onMQTT(msg: msg) }
                }
            }
        }
    }
    
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        //MQTT接続、一定時間毎にsubscribe発行したいため、タイマーを用いる
        wakeupTimer()
    }
    
    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        mqttClient.unsubscribe("トピック", requestCompletion: nil)
        sleepTimer()
    }
        
    // MARK: - MQTT発行、受信
    func wakeupTimer() {
        mqttClient = MQTT.newConnection(mqttConfig)
        _timer = Timer.scheduledTimer(timeInterval: 10, target: self, selector: #selector(onTimer), userInfo: nil, repeats: true)
        _timer?.fire()
    }

    func sleepTimer() {
        mqttClient.disconnect()
        _timer?.invalidate()
    }

    @objc func onTimer() {
        if mqttClient.isConnected {
            mqttClient.subscribe("トピック", qos: 0)
        else {
            DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: {
                self._timer?.fire()
            })
            print("1秒タイマー動作")
        }
    }
    
    func onMQTT(msg m: String) {
        mqttClient.unsubscribe("トピック", requestCompletion: nil)
        let ary: [String] = m.components(separatedBy: "}")
        for item in ary {
            let data: [String] = item.components(separatedBy: ",")
            if (data.count > 個数) {
                お好きなように!
            }
        }
    }
}

1. 概要

すでにサーバーでbroker(mosquitto)が動作していることが前提となります。
そのbrokerに対して、一定時間(ここでは10秒)ごとに、subscribeし、onMQTTで文字列を受け取ります。
通信が途切れた場合は、1秒後に再接続を試みます。

2. セキュリティ

平文を避けるため、brokerではTLSを動作させるものとします。
たぶんmosquittoの仕様だと思うのですが、TLSでアクセスするには、サーバー証明書が要るようです。
moscapsuleを使った本実装例は、コマンドラインからの
 mosquitto_sub -h ホスト -p ポート -t トピック --cafile ファイル
の実行と、等価なはずです。

3. さらなるセキュリティ

やってないですが、mqttClientCertで、クライアント証明書を指定すればいいようです。
mosquitto_subのオプション--certと--keyを使うことと、等価なはずです。

4. 受信文字列

文字列はカンマ区切りの羅列を想定しています。
JSONを使うのが一般的なようですが、オレオレサーバー<=>アプリ間なら、単なるカンマ区切りがおすすめです。

5. タイマー

別にタイマーである必要はないのですが、iOSアプリでMQTTを受信して何かさせたいとなると、こうする以外ないような気がします。
アプリがバックグラウンドに隠れたり、アプリ内でタブを切り替えたりすることに備えて、AppDelegateに以下の実装も加えて下さい。

    func applicationDidEnterBackground(_ application: UIApplication) {
        デリゲート.sleepTimer()
    }
    func applicationWillEnterForeground(_ application: UIApplication) {
        デリゲート.wakeupTimer()

    }

6. 謝辞

@fuku2014さんの記事を参考にさせていただきました。
https://qiita.com/fuku2014/items/2b8f76c6e60bc9ff14db

なお、brokerの立て方、証明書の作り方等は、以下に詳しいです。
https://ficus-forum.myvnc.com/t/mosquitto-tls-ssl/154
https://qiita.com/udai1532/items/c0f58e73f76900a8469f

2 件のコメント:

  1. 久々にpod installしたら、Moscapsuleのところでエラーが出ました。
    とりあえずswift4のbranchを指定すること(赤字)で、回避できました。

    返信削除
  2. iOS Simulator向けにビルドできないという致命的な問題が発生しました。
    OpenSSL-Universalの中にあるライブラリがx86_64であることが原因のようです。
    とりあえずXcodeをRossetaモードで動かし、一度ビルドするとpod類のリンクに成功し、回避できましたが、恒久対応とは言えませんね…

    返信削除