Open narutaro opened 1 year ago
//
// ContentView.swift
// mqtt2
//
// Created by Inoue, Masayuki on 2023/07/02.
//
import Foundation
import SwiftUI
import CocoaMQTT
struct ContentView: View {
let mqtt = MQTTClient()
var body: some View {
VStack {
Text("MQTT Control").font(.title)
Button("Initialize"){ mqtt.initialize(host: mqtt.host, identifier: mqtt.identifier) }.padding()
Button("Connect"){ mqtt.connect() }.padding()
Button("Subscribe"){ mqtt.subscribe(topic: "some/topic") }.padding()
Button("Publish"){ mqtt.publish(with: "{\"message\":\"hello world!\"}")}.padding()
Button("Disconnect"){ mqtt.disconnect() }.padding()
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
class MQTTClient: ObservableObject, CocoaMQTTDelegate {
var mqttClient: CocoaMQTT?
var identifier = "client_1"
//var host = "broker.emqx.io"
var host = "<id>-ats.iot.ap-northeast-1.amazonaws.com"
var topic = "some/topic"
func getClientCertFromP12File(certName: String, certPassword: String) -> CFArray? {
// get p12 file path
let resourcePath = Bundle.main.path(forResource: certName, ofType: "p12")
guard let filePath = resourcePath, let p12Data = NSData(contentsOfFile: filePath) else {
print("Failed to open the certificate file: \(certName).p12")
return nil
}
// create key dictionary for reading p12 file
let key = kSecImportExportPassphrase as String
let options : NSDictionary = [key: certPassword]
var items : CFArray?
let securityError = SecPKCS12Import(p12Data, options, &items)
guard securityError == errSecSuccess else {
if securityError == errSecAuthFailed {
print("ERROR: SecPKCS12Import returned errSecAuthFailed. Incorrect password?")
print(securityError)
} else {
print("Failed to open the certificate file: \(certName).p12")
}
return nil
}
guard let theArray = items, CFArrayGetCount(theArray) > 0 else {
return nil
}
let dictionary = (theArray as NSArray).object(at: 0)
guard let identity = (dictionary as AnyObject).value(forKey: kSecImportItemIdentity as String) else {
return nil
}
let certArray = [identity] as CFArray
return certArray
}
func initialize(host: String, identifier: String) {
// If any previous instance exists then clean it
if mqttClient != nil {
mqttClient = nil
}
self.identifier = identifier
self.host = host
// TODO: Guard
mqttClient = CocoaMQTT(clientID: identifier, host: host, port: 8883)
mqttClient?.willMessage = CocoaMQTTMessage(topic: "/will", string: "dieout")
mqttClient?.keepAlive = 60
mqttClient?.delegate = self
// TLS
mqttClient?.enableSSL = true
// mqttClient?.allowUntrustCACertificate = true
let clientCertArray = getClientCertFromP12File(certName: "client-legacy", certPassword: "<pass>")
var sslSettings: [String: NSObject] = [:]
sslSettings[kCFStreamSSLCertificates as String] = clientCertArray
mqttClient?.sslSettings = sslSettings
print("[Initialize] \(mqttClient! as Any)")
}
func connect(){
_ = mqttClient?.connect()
}
func publish(with message: String) {
let publishProperties = MqttPublishProperties()
publishProperties.contentType = "JSON"
mqttClient?.publish(topic, withString: message, qos: .qos1)
}
func subscribe(topic: String) {
self.topic = topic
mqttClient?.subscribe(topic, qos: .qos1)
}
func unSubscribe(topic: String) {
mqttClient?.unsubscribe(topic)
}
func disconnect(){
mqttClient?.disconnect()
}
// MARK: CocoaMQTTDelegate
func mqtt(_ mqtt: CocoaMQTT, didConnectAck ack: CocoaMQTTConnAck) {
TRACE("ack: \(ack)")
}
func mqtt(_ mqtt: CocoaMQTT, didPublishMessage message: CocoaMQTTMessage, id: UInt16) {
TRACE("message: \(message.string.description), id: \(id)")
}
func mqtt(_ mqtt: CocoaMQTT, didPublishAck id: UInt16) {
TRACE("id: \(id)")
}
func mqtt(_ mqtt: CocoaMQTT, didReceiveMessage message: CocoaMQTTMessage, id: UInt16) {
TRACE("message: \(message.string.description), id: \(id), topic: \(message.topic)")
}
func mqtt(_ mqtt: CocoaMQTT, didSubscribeTopics success: NSDictionary, failed: [String]) {
TRACE("subscribed: \(success), failed: \(failed)")
}
func mqtt(_ mqtt: CocoaMQTT, didUnsubscribeTopics topics: [String]) {
TRACE("topic: \(topics)")
}
func mqttDidPing(_ mqtt: CocoaMQTT) {
TRACE()
}
func mqttDidReceivePong(_ mqtt: CocoaMQTT) {
TRACE()
}
func mqttDidDisconnect(_ mqtt: CocoaMQTT, withError err: Error?) {
TRACE("\(err.description)")
}
func TRACE(_ message: String = "", fun: String = #function) {
let names = fun.components(separatedBy: ":")
var prettyName: String
if names.count == 2 {
prettyName = names[0]
} else {
prettyName = names[1]
}
if fun == "mqttDidDisconnect(_:withError:)" {
prettyName = "didDisconnect"
}
print("[TRACE] [\(prettyName)]: \(message)")
}
}
とりあえず動くコード
証明書とTLS
AWS IoT Coreとの接続するので証明書やTLSを動かす。CocoaMQTTのreadmeにp12ファイルを使う例があるのでやってみる。
p12 file
p12ファイルについてはここ - p12ファイルことPKCS #12の証明書ファイルに関してざっくりまとめる
このコマンドでp12のバイナリファイルができる。
p12ファイルの中身の確認方法
⚠️ 上記で生成したp12ファイルはそのままでは使えなかった。具体的には以下のコードでインポートしようとすると
ERROR: SecPKCS12Import returned errSecAuthFailed. Incorrect password?
となる。💡 OpenSSL 3.1.1でp12ファイルを作成する際は
-legacy
オプションをつけることで読み込むことのできるファイルが生成された。どうも、OpenSSL3系ではこのオプションにより1系と互換性のあるファイルが生成されるようだ。