MQTTのパケットキャプチャを読んでみる (Fixed Header & PUBLISHパケット編)

# 初めに
* MQTTのパケットキャプチャを読めるようになりたかったので、フォーマットを自分用にまとめた
* 公式ドキュメントの必要な個所を抜粋したものになるので、基本的にはこちらを参考にして欲しい
    * ただ実際にドキュメントを読んでいて詰まったところは適宜補足を入れている
* また、実際のパケットをWiresharkで取得して、フォーマット通りになっていることを確認した

https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html

# Control Packetのフォーマット
* Control PacketはFixed Header, Variable Header, Payloadから構成されている
* Fixedヘッダは各Control Packetに共通で、Variable HeaderとPayloadはControl Packetの種類によって構成が変わる

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/296478/9e7bce16-8e7d-422e-8290-d63990d6ace2.png)


# Fixed Header
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/296478/be63ee28-b6a7-420b-9443-5ac0983d6dd0.png)

* MQTT Control Packet Typeでこのパケットがなんなのかを示している
* FlagsはControl Packetによって定義が変わるが、2025年時点ではPUBLISHパケット以外はReservedで固定値となっている

## Flag (PUBLISH)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/296478/19096f29-af71-43cc-97de-9dcaca06bb56.png)
* DUP
    * Duplicatedの略?
    * これが再送されたパケットの場合1になる
    * 再送でない、あるいはQoSが0の時は0になる
* QoS (Quolity of Service)
    * パケットの再送に関するポリシーを設定する
        * 0: 一回だけPUBLISHパケットを送信する (一度送ったらそれで終わり)
        * 1: PUBLISHパケットの送信に対してBrokerからPUBACKが返ってこなければ再送する (同じパケットが複数回送信される可能性がある)
        * 2: Publishした後、Brokerともう一往復やりとりをすることでちょうど一回だけPUBLISHパケットがBrokerまで届くようにする
    * QoSの詳細には立ち入らないが、到達保障をしているのはBrokerに対してであってSubscriberではないという点には注意が必要である
* Retain
    * Retainが1の場合、最新のPUBLISHをBrokerに保存させることができる
    * 新しいクライアントがSubscribeした際にそのパケットが送信される

## Remaining Length

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/296478/09667eb5-8aca-4223-b4c8-dbdd8a6f4436.png)

* このパケットの残りバイト数を示しており、最大4バイト分まで使うことができる
    * 最初Fixed Headerの"Fixed"は固定長という意味だと早とちりしていたのでRemaining Lengthは可変長であることに混乱した
* 各バイトの7ビットが1の場合は次のバイトもRemaining Lengthであることを意味し、0の場合はRemaining Lengthはこれで終わりであることを意味している
* つまりMQTTのパケットの最大長は2^(7*4)=268,435,456バイト、つまり268MBまでである
    * MQTTは軽量のデータを扱うのに向いているという印象があったので、仕様的な最大値が意外と大きいことに驚いた

## Variable Header
前述のとおり、Variable HeaderはControl Packetの種類によって異なる
今回はPUBLISHパケットのフォーマットを見てみる

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/296478/6f398319-6233-4b5e-830f-cd55420f9153.png)
* PUBLISHパケットのVariableHeaderはTopic Name、Packet Identifier, Propertiesから構成される
* Topic NameはPUBLISH先のTopic名である最初の2バイトでTopic名の長さを指定する
    * つまりTopicの最大長は65,535である
* 続くバイトにTopic Nameの各文字が入る
* QoSが1以上の場合、Topic NameのあとにPacket Identifierが入る
    * これはメッセージ毎に割り当てられるIDだが、再送時には同じIDを使う
* MQTT v5の場合、そのあとにPropertiesへと続く

## Properties
MQTT v5から追加されたセクションで、Httpのヘッダのように様々な情報をヘッダに入れることができる
Propertiesはkey-value型のデータのようにIdentifierと呼ばれる識別子と、そのValueの組で表現される

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/296478/d2ae967d-45c4-42fb-80a2-2897a336a4c6.png)
* Propertiesの最初のバイトはProperties全体のバイト数を指定する
* 次にIdentifierのバイトが入り、Valueがそのあとに続く
    * IdentifierはMQTTの仕様であらかじめ決められたものもあるが、ユーザが自由に作ってもよい
* 例えばResponse Topicのように可変長のValueを持つIdentifierの場合、間にValueの長さが入る

# Payload
こちらもControl Packetごとに異なる
PUBLISHパケットの場合はPublishしようとしている実際のデータが入る

# パケットキャプチャを読む
* 以下のようにtcpdumpを実行した状態でMQTTのクライアントアプリを立ち上げると、pcapファイルにパケットが保存される
* TLSを設定しているとSSLKEYLOGFILEの設定をしないと中身が見えなくなってしまうので、平文で通信させる

```
$ sudo tcpdump -i any port 1883 -w mqtt.pcap
```

* pcapファイルをWiresharkで開くとMQTTのパケットが見える

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/296478/e7513207-6975-4bb4-94ab-431346c75cbb.png)

## Fiexd Header
0x30からがMQTTパケットが始まっている
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/296478/697dac0b-5631-4e64-b639-57f8319b2c53.png)

* 1バイト目の7-4ビットがControl Packetで、値が3なのでこのパケットはPUBLISHパケットである
* また3-0ビットはすべて0なので、QoSは0かつBrokerでRetainもしないことを意味している
* 続くバイトは0x8Cとなっているが、これは0b10001100なので次のバイトもRemaining Lengthであることがわかる
* 次のバイトは0x01(0b0000001)なので、このバイトでRemaining Lengthは最後であることがわかる。
* したがってRemaining Lengthは00000010001100->140バイトである

## Variable Header
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/296478/49291d26-a511-49d0-b848-5e6a80084fef.png)
* 0x002bがTopic Lengthなので、続く43バイト分がTopicである
* 今回QoSは0なのでPacket Identifierはなく、TopicのあとはPropertiesとなっている

 ## Properties
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/296478/9135bf71-b3c5-4a63-9973-e35fc43876a1.png)
* 0x37がPropetiesの長さなのでPropertiesは残り55バイトである
* 0x08はResponse TopicというPropertyに対応するIdentifier
* Response Topicは可変長なのでResponse Topicの値の長さが入る
    * 0x0034なので続く52バイト分がResponse Topicの値になる
* Response Topicの後はPayloadとなっている

# まとめ
* MQTTのパケットのバイトフォーマットについてまとめた
* Varibable Headerに関してはControl Packetによって異なるのでPUBLISHの例を示した
* Wiresharkを使って実際のパケット取得し、フォーマットの通りに値が入っているかを確認した