Basic packet parsing
This commit is contained in:
commit
a927a5ac1a
23 changed files with 2197 additions and 0 deletions
1
.envrc
Normal file
1
.envrc
Normal file
|
@ -0,0 +1 @@
|
||||||
|
use nix
|
10
.gitignore
vendored
Normal file
10
.gitignore
vendored
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
.direnv/
|
||||||
|
maeqtt
|
||||||
|
mqtt/properties/GeneratedProperties.go
|
||||||
|
*.test
|
||||||
|
*.exe
|
||||||
|
*.out
|
||||||
|
*.exe~
|
||||||
|
*.dll
|
||||||
|
*.so
|
||||||
|
*.dylib
|
5
go.mod
Normal file
5
go.mod
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
module badat.dev/maeqtt/v2
|
||||||
|
|
||||||
|
go 1.16
|
||||||
|
|
||||||
|
require github.com/gdexlab/go-render v1.0.1 // indirect
|
2
go.sum
Normal file
2
go.sum
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
github.com/gdexlab/go-render v1.0.1 h1:rxqB3vo5s4n1kF0ySmoNeSPRYkEsyHgln4jFIQY7v0U=
|
||||||
|
github.com/gdexlab/go-render v1.0.1/go.mod h1:wRi5nW2qfjiGj4mPukH4UV0IknS1cHD4VgFTmJX5JzM=
|
64
main.go
Normal file
64
main.go
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"badat.dev/maeqtt/v2/mqtt/packets"
|
||||||
|
"badat.dev/maeqtt/v2/mqtt/properties"
|
||||||
|
"github.com/gdexlab/go-render/render" // For testing
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
listen_addr := ":1883"
|
||||||
|
listener, err := net.Listen("tcp", listen_addr)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
conn, err := listener.Accept()
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Failed accepting connection ", err)
|
||||||
|
}
|
||||||
|
go handleConnection(conn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleConnection(con net.Conn) {
|
||||||
|
defer closeConnection(con)
|
||||||
|
|
||||||
|
for {
|
||||||
|
reader := bufio.NewReader(con)
|
||||||
|
packet, err := packets.ReadPacket(reader)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Error reading packet ", err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
log.Println(render.AsCode(packet))
|
||||||
|
clientId := "aa"
|
||||||
|
resp := packets.ConnackPacket{
|
||||||
|
ResonCode: packets.ConnectReasonCodeSuccess,
|
||||||
|
SessionPresent: false,
|
||||||
|
Properties: properties.ConnackPacketProperties{
|
||||||
|
AssignedClientIdentifier: properties.AssignedClientIdentifier{Value: &clientId},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
err = resp.Write(con)
|
||||||
|
log.Println("Wrote response")
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Error writing response ", err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func closeConnection(con net.Conn) {
|
||||||
|
err := con.Close()
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Failed to close connection", err)
|
||||||
|
}
|
||||||
|
}
|
685
mqtt/GeneratedProperties.go
Normal file
685
mqtt/GeneratedProperties.go
Normal file
|
@ -0,0 +1,685 @@
|
||||||
|
package mqtt
|
||||||
|
// This code has been generated with the genProps.py script. Do not modify
|
||||||
|
|
||||||
|
|
||||||
|
import "bufio"
|
||||||
|
|
||||||
|
type PayloadFormatIndicator struct {
|
||||||
|
value *byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p PayloadFormatIndicator) id() int {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PayloadFormatIndicator) parse(r *bufio.Reader) error {
|
||||||
|
val, err := r.ReadByte()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.value = &val
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type MessageExpiryInterval struct {
|
||||||
|
value *uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p MessageExpiryInterval) id() int {
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *MessageExpiryInterval) parse(r *bufio.Reader) error {
|
||||||
|
val, err := decodeUint32(r)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.value = &val
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type ContentType struct {
|
||||||
|
value *string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p ContentType) id() int {
|
||||||
|
return 3
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ContentType) parse(r *bufio.Reader) error {
|
||||||
|
val, err := decodeUTF8String(r)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.value = &val
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type ResponseTopic struct {
|
||||||
|
value *string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p ResponseTopic) id() int {
|
||||||
|
return 8
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ResponseTopic) parse(r *bufio.Reader) error {
|
||||||
|
val, err := decodeUTF8String(r)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.value = &val
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type CorrelationData struct {
|
||||||
|
value *[]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p CorrelationData) id() int {
|
||||||
|
return 9
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *CorrelationData) parse(r *bufio.Reader) error {
|
||||||
|
val, err := decodeBinaryData(r)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.value = &val
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type SubscriptionIdentifier struct {
|
||||||
|
value *int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p SubscriptionIdentifier) id() int {
|
||||||
|
return 11
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *SubscriptionIdentifier) parse(r *bufio.Reader) error {
|
||||||
|
val, err := decodeVariableByteInt(r)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.value = &val
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type SessionExpiryInterval struct {
|
||||||
|
value *uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p SessionExpiryInterval) id() int {
|
||||||
|
return 17
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *SessionExpiryInterval) parse(r *bufio.Reader) error {
|
||||||
|
val, err := decodeUint32(r)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.value = &val
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type AssignedClientIdentifier struct {
|
||||||
|
value *string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p AssignedClientIdentifier) id() int {
|
||||||
|
return 18
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *AssignedClientIdentifier) parse(r *bufio.Reader) error {
|
||||||
|
val, err := decodeUTF8String(r)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.value = &val
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type ServerKeepAlive struct {
|
||||||
|
value *uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p ServerKeepAlive) id() int {
|
||||||
|
return 19
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ServerKeepAlive) parse(r *bufio.Reader) error {
|
||||||
|
val, err := decodeUint16(r)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.value = &val
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type AuthenticationMethod struct {
|
||||||
|
value *string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p AuthenticationMethod) id() int {
|
||||||
|
return 21
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *AuthenticationMethod) parse(r *bufio.Reader) error {
|
||||||
|
val, err := decodeUTF8String(r)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.value = &val
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type AuthenticationData struct {
|
||||||
|
value *[]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p AuthenticationData) id() int {
|
||||||
|
return 22
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *AuthenticationData) parse(r *bufio.Reader) error {
|
||||||
|
val, err := decodeBinaryData(r)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.value = &val
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type RequestProblemInformation struct {
|
||||||
|
value *byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p RequestProblemInformation) id() int {
|
||||||
|
return 23
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *RequestProblemInformation) parse(r *bufio.Reader) error {
|
||||||
|
val, err := r.ReadByte()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.value = &val
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type WillDelayInterval struct {
|
||||||
|
value *uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p WillDelayInterval) id() int {
|
||||||
|
return 24
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *WillDelayInterval) parse(r *bufio.Reader) error {
|
||||||
|
val, err := decodeUint32(r)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.value = &val
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type RequestResponseInformation struct {
|
||||||
|
value *byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p RequestResponseInformation) id() int {
|
||||||
|
return 25
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *RequestResponseInformation) parse(r *bufio.Reader) error {
|
||||||
|
val, err := r.ReadByte()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.value = &val
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type ResponseInformation struct {
|
||||||
|
value *string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p ResponseInformation) id() int {
|
||||||
|
return 26
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ResponseInformation) parse(r *bufio.Reader) error {
|
||||||
|
val, err := decodeUTF8String(r)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.value = &val
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type ServerReference struct {
|
||||||
|
value *string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p ServerReference) id() int {
|
||||||
|
return 28
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ServerReference) parse(r *bufio.Reader) error {
|
||||||
|
val, err := decodeUTF8String(r)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.value = &val
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type ReasonString struct {
|
||||||
|
value *string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p ReasonString) id() int {
|
||||||
|
return 31
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ReasonString) parse(r *bufio.Reader) error {
|
||||||
|
val, err := decodeUTF8String(r)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.value = &val
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type ReceiveMaximum struct {
|
||||||
|
value *uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p ReceiveMaximum) id() int {
|
||||||
|
return 33
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ReceiveMaximum) parse(r *bufio.Reader) error {
|
||||||
|
val, err := decodeUint16(r)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.value = &val
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type TopicAliasMaximum struct {
|
||||||
|
value *uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p TopicAliasMaximum) id() int {
|
||||||
|
return 34
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *TopicAliasMaximum) parse(r *bufio.Reader) error {
|
||||||
|
val, err := decodeUint16(r)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.value = &val
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type TopicAlias struct {
|
||||||
|
value *uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p TopicAlias) id() int {
|
||||||
|
return 35
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *TopicAlias) parse(r *bufio.Reader) error {
|
||||||
|
val, err := decodeUint16(r)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.value = &val
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type MaximumQoS struct {
|
||||||
|
value *byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p MaximumQoS) id() int {
|
||||||
|
return 36
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *MaximumQoS) parse(r *bufio.Reader) error {
|
||||||
|
val, err := r.ReadByte()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.value = &val
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type RetainAvailable struct {
|
||||||
|
value *byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p RetainAvailable) id() int {
|
||||||
|
return 37
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *RetainAvailable) parse(r *bufio.Reader) error {
|
||||||
|
val, err := r.ReadByte()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.value = &val
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type MaximumPacketSize struct {
|
||||||
|
value *uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p MaximumPacketSize) id() int {
|
||||||
|
return 39
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *MaximumPacketSize) parse(r *bufio.Reader) error {
|
||||||
|
val, err := decodeUint32(r)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.value = &val
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type WildcardSubscriptionAvailable struct {
|
||||||
|
value *byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p WildcardSubscriptionAvailable) id() int {
|
||||||
|
return 40
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *WildcardSubscriptionAvailable) parse(r *bufio.Reader) error {
|
||||||
|
val, err := r.ReadByte()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.value = &val
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type SubscriptionIdentifierAvailable struct {
|
||||||
|
value *byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p SubscriptionIdentifierAvailable) id() int {
|
||||||
|
return 41
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *SubscriptionIdentifierAvailable) parse(r *bufio.Reader) error {
|
||||||
|
val, err := r.ReadByte()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.value = &val
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type SharedSubscriptionAvailable struct {
|
||||||
|
value *byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p SharedSubscriptionAvailable) id() int {
|
||||||
|
return 42
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *SharedSubscriptionAvailable) parse(r *bufio.Reader) error {
|
||||||
|
val, err := r.ReadByte()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.value = &val
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type PublishPacketProperties struct {
|
||||||
|
PayloadFormatIndicator PayloadFormatIndicator
|
||||||
|
MessageExpiryInterval MessageExpiryInterval
|
||||||
|
ContentType ContentType
|
||||||
|
ResponseTopic ResponseTopic
|
||||||
|
CorrelationData CorrelationData
|
||||||
|
SubscriptionIdentifier SubscriptionIdentifier
|
||||||
|
TopicAlias TopicAlias
|
||||||
|
UserProperty UserProperty
|
||||||
|
}
|
||||||
|
func (p *PublishPacketProperties) arrayOf() []Property {
|
||||||
|
return []Property {
|
||||||
|
&p.PayloadFormatIndicator,
|
||||||
|
&p.MessageExpiryInterval,
|
||||||
|
&p.ContentType,
|
||||||
|
&p.ResponseTopic,
|
||||||
|
&p.CorrelationData,
|
||||||
|
&p.SubscriptionIdentifier,
|
||||||
|
&p.TopicAlias,
|
||||||
|
&p.UserProperty,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
type WillProperties struct {
|
||||||
|
PayloadFormatIndicator PayloadFormatIndicator
|
||||||
|
MessageExpiryInterval MessageExpiryInterval
|
||||||
|
ContentType ContentType
|
||||||
|
ResponseTopic ResponseTopic
|
||||||
|
CorrelationData CorrelationData
|
||||||
|
WillDelayInterval WillDelayInterval
|
||||||
|
UserProperty UserProperty
|
||||||
|
}
|
||||||
|
func (p *WillProperties) arrayOf() []Property {
|
||||||
|
return []Property {
|
||||||
|
&p.PayloadFormatIndicator,
|
||||||
|
&p.MessageExpiryInterval,
|
||||||
|
&p.ContentType,
|
||||||
|
&p.ResponseTopic,
|
||||||
|
&p.CorrelationData,
|
||||||
|
&p.WillDelayInterval,
|
||||||
|
&p.UserProperty,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
type SubscribePacketProperties struct {
|
||||||
|
SubscriptionIdentifier SubscriptionIdentifier
|
||||||
|
UserProperty UserProperty
|
||||||
|
}
|
||||||
|
func (p *SubscribePacketProperties) arrayOf() []Property {
|
||||||
|
return []Property {
|
||||||
|
&p.SubscriptionIdentifier,
|
||||||
|
&p.UserProperty,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
type ConnectPacketProperties struct {
|
||||||
|
SessionExpiryInterval SessionExpiryInterval
|
||||||
|
AuthenticationMethod AuthenticationMethod
|
||||||
|
AuthenticationData AuthenticationData
|
||||||
|
RequestProblemInformation RequestProblemInformation
|
||||||
|
RequestResponseInformation RequestResponseInformation
|
||||||
|
ReceiveMaximum ReceiveMaximum
|
||||||
|
TopicAliasMaximum TopicAliasMaximum
|
||||||
|
UserProperty UserProperty
|
||||||
|
MaximumPacketSize MaximumPacketSize
|
||||||
|
}
|
||||||
|
func (p *ConnectPacketProperties) arrayOf() []Property {
|
||||||
|
return []Property {
|
||||||
|
&p.SessionExpiryInterval,
|
||||||
|
&p.AuthenticationMethod,
|
||||||
|
&p.AuthenticationData,
|
||||||
|
&p.RequestProblemInformation,
|
||||||
|
&p.RequestResponseInformation,
|
||||||
|
&p.ReceiveMaximum,
|
||||||
|
&p.TopicAliasMaximum,
|
||||||
|
&p.UserProperty,
|
||||||
|
&p.MaximumPacketSize,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
type ConnackPacketProperties struct {
|
||||||
|
SessionExpiryInterval SessionExpiryInterval
|
||||||
|
AssignedClientIdentifier AssignedClientIdentifier
|
||||||
|
ServerKeepAlive ServerKeepAlive
|
||||||
|
AuthenticationMethod AuthenticationMethod
|
||||||
|
AuthenticationData AuthenticationData
|
||||||
|
ResponseInformation ResponseInformation
|
||||||
|
ServerReference ServerReference
|
||||||
|
ReasonString ReasonString
|
||||||
|
ReceiveMaximum ReceiveMaximum
|
||||||
|
TopicAliasMaximum TopicAliasMaximum
|
||||||
|
MaximumQoS MaximumQoS
|
||||||
|
RetainAvailable RetainAvailable
|
||||||
|
UserProperty UserProperty
|
||||||
|
MaximumPacketSize MaximumPacketSize
|
||||||
|
WildcardSubscriptionAvailable WildcardSubscriptionAvailable
|
||||||
|
SubscriptionIdentifierAvailable SubscriptionIdentifierAvailable
|
||||||
|
SharedSubscriptionAvailable SharedSubscriptionAvailable
|
||||||
|
}
|
||||||
|
func (p *ConnackPacketProperties) arrayOf() []Property {
|
||||||
|
return []Property {
|
||||||
|
&p.SessionExpiryInterval,
|
||||||
|
&p.AssignedClientIdentifier,
|
||||||
|
&p.ServerKeepAlive,
|
||||||
|
&p.AuthenticationMethod,
|
||||||
|
&p.AuthenticationData,
|
||||||
|
&p.ResponseInformation,
|
||||||
|
&p.ServerReference,
|
||||||
|
&p.ReasonString,
|
||||||
|
&p.ReceiveMaximum,
|
||||||
|
&p.TopicAliasMaximum,
|
||||||
|
&p.MaximumQoS,
|
||||||
|
&p.RetainAvailable,
|
||||||
|
&p.UserProperty,
|
||||||
|
&p.MaximumPacketSize,
|
||||||
|
&p.WildcardSubscriptionAvailable,
|
||||||
|
&p.SubscriptionIdentifierAvailable,
|
||||||
|
&p.SharedSubscriptionAvailable,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
type DisconnectPacketProperties struct {
|
||||||
|
SessionExpiryInterval SessionExpiryInterval
|
||||||
|
ServerReference ServerReference
|
||||||
|
ReasonString ReasonString
|
||||||
|
UserProperty UserProperty
|
||||||
|
}
|
||||||
|
func (p *DisconnectPacketProperties) arrayOf() []Property {
|
||||||
|
return []Property {
|
||||||
|
&p.SessionExpiryInterval,
|
||||||
|
&p.ServerReference,
|
||||||
|
&p.ReasonString,
|
||||||
|
&p.UserProperty,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
type AuthPacketProperties struct {
|
||||||
|
AuthenticationMethod AuthenticationMethod
|
||||||
|
AuthenticationData AuthenticationData
|
||||||
|
ReasonString ReasonString
|
||||||
|
UserProperty UserProperty
|
||||||
|
}
|
||||||
|
func (p *AuthPacketProperties) arrayOf() []Property {
|
||||||
|
return []Property {
|
||||||
|
&p.AuthenticationMethod,
|
||||||
|
&p.AuthenticationData,
|
||||||
|
&p.ReasonString,
|
||||||
|
&p.UserProperty,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
type PubackPacketProperties struct {
|
||||||
|
ReasonString ReasonString
|
||||||
|
UserProperty UserProperty
|
||||||
|
}
|
||||||
|
func (p *PubackPacketProperties) arrayOf() []Property {
|
||||||
|
return []Property {
|
||||||
|
&p.ReasonString,
|
||||||
|
&p.UserProperty,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
type PubrecPacketProperties struct {
|
||||||
|
ReasonString ReasonString
|
||||||
|
UserProperty UserProperty
|
||||||
|
}
|
||||||
|
func (p *PubrecPacketProperties) arrayOf() []Property {
|
||||||
|
return []Property {
|
||||||
|
&p.ReasonString,
|
||||||
|
&p.UserProperty,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
type PubrelPacketProperties struct {
|
||||||
|
ReasonString ReasonString
|
||||||
|
UserProperty UserProperty
|
||||||
|
}
|
||||||
|
func (p *PubrelPacketProperties) arrayOf() []Property {
|
||||||
|
return []Property {
|
||||||
|
&p.ReasonString,
|
||||||
|
&p.UserProperty,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
type PubcompPacketProperties struct {
|
||||||
|
ReasonString ReasonString
|
||||||
|
UserProperty UserProperty
|
||||||
|
}
|
||||||
|
func (p *PubcompPacketProperties) arrayOf() []Property {
|
||||||
|
return []Property {
|
||||||
|
&p.ReasonString,
|
||||||
|
&p.UserProperty,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
type SubackPacketProperties struct {
|
||||||
|
ReasonString ReasonString
|
||||||
|
UserProperty UserProperty
|
||||||
|
}
|
||||||
|
func (p *SubackPacketProperties) arrayOf() []Property {
|
||||||
|
return []Property {
|
||||||
|
&p.ReasonString,
|
||||||
|
&p.UserProperty,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
type UnsubackPacketProperties struct {
|
||||||
|
ReasonString ReasonString
|
||||||
|
UserProperty UserProperty
|
||||||
|
}
|
||||||
|
func (p *UnsubackPacketProperties) arrayOf() []Property {
|
||||||
|
return []Property {
|
||||||
|
&p.ReasonString,
|
||||||
|
&p.UserProperty,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
type UnsubscribePacketProperties struct {
|
||||||
|
UserProperty UserProperty
|
||||||
|
}
|
||||||
|
func (p *UnsubscribePacketProperties) arrayOf() []Property {
|
||||||
|
return []Property {
|
||||||
|
&p.UserProperty,
|
||||||
|
}
|
||||||
|
}
|
70
mqtt/packets/Connack.go
Normal file
70
mqtt/packets/Connack.go
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
package packets
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"badat.dev/maeqtt/v2/mqtt/properties"
|
||||||
|
"badat.dev/maeqtt/v2/mqtt/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ConnectReasonCode byte
|
||||||
|
|
||||||
|
const (
|
||||||
|
ConnectReasonCodeSuccess ConnectReasonCode = 0
|
||||||
|
ConnectReasonCodeUnspecified = 128
|
||||||
|
ConnectReasonCodeMalformedPacket = 129
|
||||||
|
ConnectReasonCodeProtocolError = 130
|
||||||
|
ConnectReasonCodeImplErorr = 131
|
||||||
|
ConnectReasonCodeUnsupportedProtoVer = 132
|
||||||
|
ConnectReasonCodeClientIDNotValid = 133
|
||||||
|
ConnectReasonCodeBadUsernameOrPassword = 134
|
||||||
|
ConnectReasonCodeNotAuthorized = 135
|
||||||
|
ConnectReasonCodeServerUnavaliable = 136
|
||||||
|
ConnectReasonCodeServerBusy = 137
|
||||||
|
ConnectReasonCodeBanned = 138
|
||||||
|
ConnectReasonCodeBadAuthenticationMethod = 140
|
||||||
|
ConnectReasonCodeTopicNameInvalid = 144
|
||||||
|
ConnectReasonCodePacketTooLarge = 149
|
||||||
|
ConnectReasonCodeQuotaExceeded = 151
|
||||||
|
ConnectReasonCodePayloadFormatInvalid = 153
|
||||||
|
ConnectReasonCodeRetainNotSupported = 154
|
||||||
|
ConnectReasonCodeQoSNotSupported = 155
|
||||||
|
ConnectReasonCodeUseAnotherServer = 156
|
||||||
|
ConnectReasonCodeServerMoved = 157
|
||||||
|
ConnectReasonCodeConnectionRateExceeded = 159
|
||||||
|
)
|
||||||
|
|
||||||
|
type ConnackPacket struct {
|
||||||
|
ResonCode ConnectReasonCode
|
||||||
|
SessionPresent bool
|
||||||
|
Properties properties.ConnackPacketProperties
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p ConnackPacket) Write(w io.Writer) error {
|
||||||
|
buf := bytes.NewBuffer([]byte{})
|
||||||
|
var ackFlags [8]bool
|
||||||
|
ackFlags[0] = p.SessionPresent
|
||||||
|
err := types.WriteBits(buf, ackFlags)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = buf.WriteByte(byte(p.ResonCode))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = properties.WriteProps(buf, p.Properties.ArrayOf())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
conPack := controlPacket{
|
||||||
|
packetType: PacketTypeConnack,
|
||||||
|
flags: 0,
|
||||||
|
reader: buf,
|
||||||
|
}
|
||||||
|
|
||||||
|
return conPack.write(w)
|
||||||
|
}
|
124
mqtt/packets/Connect.go
Normal file
124
mqtt/packets/Connect.go
Normal file
|
@ -0,0 +1,124 @@
|
||||||
|
package packets
|
||||||
|
|
||||||
|
import (
|
||||||
|
"badat.dev/maeqtt/v2/mqtt/properties"
|
||||||
|
"badat.dev/maeqtt/v2/mqtt/types"
|
||||||
|
"bufio"
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Will struct {
|
||||||
|
retain bool
|
||||||
|
properties properties.WillProperties
|
||||||
|
}
|
||||||
|
|
||||||
|
type ConnectPacket struct {
|
||||||
|
ClientId *string
|
||||||
|
Username *string
|
||||||
|
Password *[]byte
|
||||||
|
CleanStart bool
|
||||||
|
KeepAliveInterval uint16
|
||||||
|
|
||||||
|
Will *Will // Optional
|
||||||
|
Properties properties.ConnectPacketProperties
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c ConnectPacket) visit(visitor PacketVisitor) {
|
||||||
|
visitor.visitConnect(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseConnectPacket(control controlPacket) (ConnectPacket, error) {
|
||||||
|
packet := ConnectPacket{}
|
||||||
|
|
||||||
|
if control.packetType != PacketTypeConnect {
|
||||||
|
panic("Wrong packet type for parseConnectPacket")
|
||||||
|
}
|
||||||
|
if control.flags != 0 {
|
||||||
|
return packet, errors.New("Malformed connect packet")
|
||||||
|
}
|
||||||
|
|
||||||
|
r := bufio.NewReader(control.reader)
|
||||||
|
|
||||||
|
protocolName, err := types.DecodeUTF8String(r)
|
||||||
|
if err != nil {
|
||||||
|
return packet, err
|
||||||
|
}
|
||||||
|
if protocolName != "MQTT" {
|
||||||
|
return ConnectPacket{}, errors.New("Malformed connect packet, invalid protocol name")
|
||||||
|
}
|
||||||
|
|
||||||
|
protocolVersion, err := r.ReadByte()
|
||||||
|
if err != nil {
|
||||||
|
return ConnectPacket{}, err
|
||||||
|
}
|
||||||
|
if protocolVersion != 5 {
|
||||||
|
return ConnectPacket{}, errors.New("Malformed connect packet, unsupported protocol version")
|
||||||
|
}
|
||||||
|
|
||||||
|
connectFlags, err := types.DecodeBits(r)
|
||||||
|
if err != nil {
|
||||||
|
return packet, err
|
||||||
|
}
|
||||||
|
userNameFlag := connectFlags[7]
|
||||||
|
passwordFlag := connectFlags[6]
|
||||||
|
willRetainFlag := connectFlags[5]
|
||||||
|
willFlag := connectFlags[2]
|
||||||
|
packet.CleanStart = connectFlags[1]
|
||||||
|
reserved := connectFlags[0]
|
||||||
|
|
||||||
|
if reserved {
|
||||||
|
return ConnectPacket{}, errors.New("Malformed connect packet, reserved connect flag set")
|
||||||
|
}
|
||||||
|
|
||||||
|
QOSLevel := types.BoolToUint(connectFlags[4])*2 + types.BoolToUint(connectFlags[3])
|
||||||
|
if QOSLevel > 3 {
|
||||||
|
return ConnectPacket{}, errors.New("Malformed connect packet, invalid QOS Level")
|
||||||
|
}
|
||||||
|
|
||||||
|
keepAlive, err := types.DecodeUint16(r)
|
||||||
|
if err != nil {
|
||||||
|
return packet, err
|
||||||
|
}
|
||||||
|
packet.KeepAliveInterval = keepAlive
|
||||||
|
|
||||||
|
err = properties.ParseProperties(r, packet.Properties.ArrayOf())
|
||||||
|
if err != nil {
|
||||||
|
return packet, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse payload(3.1.3)
|
||||||
|
clientId, err := types.DecodeUTF8String(r)
|
||||||
|
if err != nil {
|
||||||
|
return packet, err
|
||||||
|
}
|
||||||
|
packet.ClientId = &clientId
|
||||||
|
|
||||||
|
if willFlag {
|
||||||
|
packet.Will = &Will{}
|
||||||
|
err = properties.ParseProperties(r, packet.Will.properties.ArrayOf())
|
||||||
|
if err != nil {
|
||||||
|
return packet, err
|
||||||
|
}
|
||||||
|
packet.Will.retain = willRetainFlag
|
||||||
|
}
|
||||||
|
|
||||||
|
var username string
|
||||||
|
if userNameFlag {
|
||||||
|
username, err = types.DecodeUTF8String(r)
|
||||||
|
if err != nil {
|
||||||
|
return packet, err
|
||||||
|
}
|
||||||
|
packet.Username = &username
|
||||||
|
}
|
||||||
|
|
||||||
|
var password []byte
|
||||||
|
if passwordFlag {
|
||||||
|
password, err = types.DecodeBinaryData(r)
|
||||||
|
if err != nil {
|
||||||
|
return packet, err
|
||||||
|
}
|
||||||
|
packet.Password = &password
|
||||||
|
}
|
||||||
|
|
||||||
|
return packet, nil
|
||||||
|
}
|
32
mqtt/packets/ControlPacket.go
Normal file
32
mqtt/packets/ControlPacket.go
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
package packets
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"badat.dev/maeqtt/v2/mqtt/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type controlPacket struct {
|
||||||
|
packetType PacketType
|
||||||
|
flags uint
|
||||||
|
reader io.Reader
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c controlPacket) write(w io.Writer) error {
|
||||||
|
buf := bytes.NewBuffer([]byte{})
|
||||||
|
|
||||||
|
var fixedHeader byte = (byte(c.packetType) << 4 & 0b11110000) + (byte(c.flags) & 0b1111)
|
||||||
|
_, err := buf.Write([]byte{fixedHeader})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
data, err := io.ReadAll(c.reader)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
types.WriteDataWithVarIntLen(buf, data)
|
||||||
|
w.Write(buf.Bytes())
|
||||||
|
return nil
|
||||||
|
}
|
104
mqtt/packets/Disconnect.go
Normal file
104
mqtt/packets/Disconnect.go
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
package packets
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"badat.dev/maeqtt/v2/mqtt/properties"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DisconnectReasonCode byte
|
||||||
|
|
||||||
|
const (
|
||||||
|
DisconnectReasonCodeNormal DisconnectReasonCode = 0
|
||||||
|
DisconnectReasonCodeWithWill DisconnectReasonCode = 0
|
||||||
|
DisconnectReasonCodeUnspecified = 128
|
||||||
|
DisconnectReasonCodeMalformedPacket = 129
|
||||||
|
DisconnectReasonCodeProtocolError = 130
|
||||||
|
DisconnectReasonCodeImplErorr = 131
|
||||||
|
DisconnectReasonCodeNotAuthorized = 135
|
||||||
|
DisconnectReasonCodeServerBusy = 137
|
||||||
|
DisconnectReasonServerShuttingDown = 139
|
||||||
|
DisconnectReasonCodeKeepAliveTimeout = 141
|
||||||
|
DisconnectReasonCodeSessionTakenOver = 142
|
||||||
|
DisconnectReasonCodeTopicFilterInvalid = 143
|
||||||
|
DisconnectReasonCodeTopicNameInvalid = 144
|
||||||
|
DisconnectReasonCodeReceiveMaxiumExceeded = 147
|
||||||
|
DisconnectReasonCodeTopicAliasInvalid = 148
|
||||||
|
DisconnectReasonCodePacketTooLarge = 149
|
||||||
|
DisconnectReasonCodeMessageRateTooHigh = 150
|
||||||
|
DisconnectReasonCodeQuotaExceeded = 151
|
||||||
|
DisconnectReasonCodeAdminiAction = 152
|
||||||
|
DisconnectReasonCodePayloadFormatInvalid = 153
|
||||||
|
DisconnectReasonCodeRetainNotSupported = 154
|
||||||
|
DisconnectReasonCodeQoSNotSupported = 155
|
||||||
|
DisconnectReasonCodeUseAnotherServer = 156
|
||||||
|
DisconnectReasonCodeServerMoved = 157
|
||||||
|
DisconnectReasonCodeSharedSubscriptionNotSupported = 158
|
||||||
|
DisconnectReasonCodeConnectionRateExceeded = 159
|
||||||
|
DisconnectReasonCodeMaximumConnectTime = 160
|
||||||
|
DisconnectReasonCodeSubscriptionIdNotSupported = 161
|
||||||
|
DisconnectReasonCodeWildcardSubscriptionNotSupported = 162
|
||||||
|
)
|
||||||
|
|
||||||
|
type DisconnectPacket struct {
|
||||||
|
ReasonCode DisconnectReasonCode
|
||||||
|
Properties properties.DisconnectPacketProperties
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseDisconnectPacket(control controlPacket) (DisconnectPacket, error) {
|
||||||
|
packet := DisconnectPacket{}
|
||||||
|
|
||||||
|
if control.packetType != PacketTypeDisconnect {
|
||||||
|
panic("Wrong packet type for parseDisconnect")
|
||||||
|
}
|
||||||
|
if control.flags != 0 {
|
||||||
|
return packet, errors.New("Malformed disconnect packet")
|
||||||
|
}
|
||||||
|
|
||||||
|
r := bufio.NewReader(control.reader)
|
||||||
|
|
||||||
|
|
||||||
|
// If there is less then a byte in the reader assume the reason code == 0
|
||||||
|
reason,err := r.ReadByte()
|
||||||
|
if err == io.EOF {
|
||||||
|
reason = 0
|
||||||
|
} else if err != nil {
|
||||||
|
return packet, err
|
||||||
|
}
|
||||||
|
packet.ReasonCode = DisconnectReasonCode(reason)
|
||||||
|
|
||||||
|
// If there are less than 2 bytes remaining in the reader assume that the packet has no properties
|
||||||
|
_, err = r.Peek(2)
|
||||||
|
if err == nil {
|
||||||
|
err = properties.ParseProperties(r,packet.Properties.ArrayOf())
|
||||||
|
} else if err != io.EOF {
|
||||||
|
return packet, err
|
||||||
|
} else if err == io.EOF {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return packet, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p DisconnectPacket) Write(w io.Writer) error {
|
||||||
|
buf := bytes.NewBuffer([]byte{})
|
||||||
|
buf.WriteByte(byte(p.ReasonCode))
|
||||||
|
err := properties.WriteProps(w, p.Properties.ArrayOf())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
control := controlPacket {
|
||||||
|
packetType: PacketTypeDisconnect,
|
||||||
|
flags: 0,
|
||||||
|
reader: buf,
|
||||||
|
}
|
||||||
|
return control.write(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p DisconnectPacket) visit(v PacketVisitor) {
|
||||||
|
v.visitDisconnect(p)
|
||||||
|
}
|
35
mqtt/packets/Ping.go
Normal file
35
mqtt/packets/Ping.go
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
package packets
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
type PingreqPacket struct {}
|
||||||
|
|
||||||
|
func ParesPingreq(control controlPacket) (PingreqPacket, error) {
|
||||||
|
packet := PingreqPacket{}
|
||||||
|
|
||||||
|
if control.packetType != PacketTypePingreq {
|
||||||
|
panic("Wrong packet type for parsePingreq")
|
||||||
|
}
|
||||||
|
if control.flags != 0 {
|
||||||
|
return packet, errors.New("Malformed connect packet")
|
||||||
|
}
|
||||||
|
|
||||||
|
return packet, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type PingrespPacket struct {}
|
||||||
|
|
||||||
|
func (p PingrespPacket) Write(w io.Writer) error {
|
||||||
|
control := controlPacket {
|
||||||
|
packetType: PacketTypePingresp,
|
||||||
|
flags: 0,
|
||||||
|
reader: bytes.NewReader([]byte{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
return control.write(w)
|
||||||
|
}
|
92
mqtt/packets/PubAckRecRel.go
Normal file
92
mqtt/packets/PubAckRecRel.go
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
package packets
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"badat.dev/maeqtt/v2/mqtt/properties"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
type PubackReasonCode byte
|
||||||
|
|
||||||
|
const (
|
||||||
|
PubackReasonCodeSuccess PubackReasonCode = 0
|
||||||
|
PubackReasonCodeNoMatchingSubscribers = 16
|
||||||
|
PubackReasonCodeUnspecifiedError = 128
|
||||||
|
PubackReasonCodeImplementationSpecyficEror = 131
|
||||||
|
PubackReasonCodeNotAuthorized = 135
|
||||||
|
PubackReasonCodeTopicNameInvalid = 144
|
||||||
|
PubackReasonCodePacketIDInUse = 145
|
||||||
|
PubackReasonCodeQuotaExceeded = 151
|
||||||
|
PubackReasonCodePayloadFormatInvalid = 153
|
||||||
|
)
|
||||||
|
|
||||||
|
type PubackPacket struct {
|
||||||
|
PacketID uint16
|
||||||
|
Properties properties.PubackPacketProperties
|
||||||
|
Reason PubackReasonCode
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p PubackPacket) Write(w io.Writer) error {
|
||||||
|
resp := pubRespPacket{
|
||||||
|
PacketType: PacketTypePuback,
|
||||||
|
PacketID: p.PacketID,
|
||||||
|
Properties: p.Properties.ArrayOf(),
|
||||||
|
Reason: byte(p.Reason),
|
||||||
|
}
|
||||||
|
return resp.Write(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
type PubrecPacket struct {
|
||||||
|
PacketID uint16
|
||||||
|
Properties properties.PubrecPacketProperties
|
||||||
|
Reason PubackReasonCode
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p PubrecPacket) Write(w io.Writer) error {
|
||||||
|
resp := pubRespPacket{
|
||||||
|
PacketType: PacketTypePubrec,
|
||||||
|
PacketID: p.PacketID,
|
||||||
|
Properties: p.Properties.ArrayOf(),
|
||||||
|
Reason: byte(p.Reason),
|
||||||
|
}
|
||||||
|
return resp.Write(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
type PubrelReasonCode byte
|
||||||
|
|
||||||
|
const (
|
||||||
|
PubrelReasonCodeSuccess PubackReasonCode = 0
|
||||||
|
PubrelReasonPacketIDNotFound = 146
|
||||||
|
)
|
||||||
|
|
||||||
|
type PubrelPacket struct {
|
||||||
|
PacketID uint16
|
||||||
|
Properties properties.PubrecPacketProperties
|
||||||
|
Reason PubrelReasonCode
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p PubrelPacket) Write(w io.Writer) error {
|
||||||
|
resp := pubRespPacket{
|
||||||
|
PacketType: PacketTypePubrel,
|
||||||
|
PacketID: p.PacketID,
|
||||||
|
Properties: p.Properties.ArrayOf(),
|
||||||
|
Reason: byte(p.Reason),
|
||||||
|
}
|
||||||
|
return resp.Write(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
type PubcompPacket struct {
|
||||||
|
PacketID uint16
|
||||||
|
Properties properties.PubcompPacketProperties
|
||||||
|
Reason PubrelReasonCode
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p PubcompPacket) Write(w io.Writer) error {
|
||||||
|
resp := pubRespPacket{
|
||||||
|
PacketType: PacketTypePubrel,
|
||||||
|
PacketID: p.PacketID,
|
||||||
|
Properties: p.Properties.ArrayOf(),
|
||||||
|
Reason: byte(p.Reason),
|
||||||
|
}
|
||||||
|
return resp.Write(w)
|
||||||
|
}
|
62
mqtt/packets/Publish.go
Normal file
62
mqtt/packets/Publish.go
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
package packets
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"badat.dev/maeqtt/v2/mqtt/properties"
|
||||||
|
"badat.dev/maeqtt/v2/mqtt/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PublishPacket struct {
|
||||||
|
Dup bool
|
||||||
|
Retain bool
|
||||||
|
QOSLevel byte
|
||||||
|
TopicName string
|
||||||
|
Payload []byte
|
||||||
|
PacketId *uint16
|
||||||
|
Properties properties.PublishPacketProperties
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p PublishPacket) visit(v PacketVisitor) {
|
||||||
|
v.visitPublish(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parsePublishPacket(control controlPacket) (PublishPacket, error) {
|
||||||
|
var err error
|
||||||
|
r := bufio.NewReader(control.reader)
|
||||||
|
packet := PublishPacket{}
|
||||||
|
|
||||||
|
if control.packetType != PacketTypePublish {
|
||||||
|
return packet, errors.New("Wrong packet type for parseConnectPacket")
|
||||||
|
}
|
||||||
|
|
||||||
|
packet.Retain = control.flags&1 == 1
|
||||||
|
packet.QOSLevel = byte((control.flags >> 1) & 0b11)
|
||||||
|
packet.Dup = (control.flags>>3)&1 == 0
|
||||||
|
|
||||||
|
packet.TopicName, err = types.DecodeUTF8String(r)
|
||||||
|
if err != nil {
|
||||||
|
return packet, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if packet.QOSLevel != 0 {
|
||||||
|
packId, err := types.DecodeUint16(r)
|
||||||
|
if err != nil {
|
||||||
|
return packet, err
|
||||||
|
}
|
||||||
|
packet.PacketId = &packId
|
||||||
|
}
|
||||||
|
err = properties.ParseProperties(r, packet.Properties.ArrayOf())
|
||||||
|
if err != nil {
|
||||||
|
return packet, err
|
||||||
|
}
|
||||||
|
|
||||||
|
packet.Payload, err = io.ReadAll(r)
|
||||||
|
if err != nil {
|
||||||
|
return packet, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return packet, nil
|
||||||
|
}
|
180
mqtt/packets/Subscriptions.go
Normal file
180
mqtt/packets/Subscriptions.go
Normal file
|
@ -0,0 +1,180 @@
|
||||||
|
package packets
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"badat.dev/maeqtt/v2/mqtt/properties"
|
||||||
|
"badat.dev/maeqtt/v2/mqtt/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TopicFilter struct {
|
||||||
|
Topic string
|
||||||
|
MaxQoS uint
|
||||||
|
NoLocal bool
|
||||||
|
RetainAsPublished bool
|
||||||
|
RetainHandling uint
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseTopicFilter(r *bufio.Reader) (TopicFilter, error) {
|
||||||
|
filter := TopicFilter{}
|
||||||
|
var err error
|
||||||
|
|
||||||
|
filter.Topic, err = types.DecodeUTF8String(r)
|
||||||
|
if err != nil {
|
||||||
|
return filter, err
|
||||||
|
}
|
||||||
|
|
||||||
|
options, err := types.DecodeBits(r)
|
||||||
|
if err != nil {
|
||||||
|
return filter, err
|
||||||
|
}
|
||||||
|
filter.MaxQoS = types.BoolsToUint(options[0], options[1])
|
||||||
|
filter.NoLocal = options[2]
|
||||||
|
filter.RetainAsPublished = options[3]
|
||||||
|
filter.RetainHandling = types.BoolsToUint(options[4], options[5])
|
||||||
|
return filter, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Both sub and unsubscribe packets are identitcal so we can reuse the parsing logic
|
||||||
|
type SubscriptionPacket struct {
|
||||||
|
PacketId uint16
|
||||||
|
TopicFilters []TopicFilter
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseSubscriptionPacket(control controlPacket, props []properties.Property) (SubscriptionPacket, error) {
|
||||||
|
var err error
|
||||||
|
r := bufio.NewReader(control.reader)
|
||||||
|
packet := SubscriptionPacket{}
|
||||||
|
|
||||||
|
if control.flags != 2 {
|
||||||
|
return packet, errors.New("Malformed subscription packet")
|
||||||
|
}
|
||||||
|
|
||||||
|
packet.PacketId, err = types.DecodeUint16(r)
|
||||||
|
if err != nil {
|
||||||
|
return packet, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = properties.ParseProperties(r, props)
|
||||||
|
if err != nil {
|
||||||
|
return packet, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for err != io.EOF {
|
||||||
|
filter, err := parseTopicFilter(r)
|
||||||
|
packet.TopicFilters = append(packet.TopicFilters, filter)
|
||||||
|
if err != nil {
|
||||||
|
return packet, err
|
||||||
|
}
|
||||||
|
_, err = r.Peek(1)
|
||||||
|
if err != nil || err != io.EOF {
|
||||||
|
return packet, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return packet, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type SubscribePacket struct {
|
||||||
|
*SubscriptionPacket
|
||||||
|
props properties.SubscribePacketProperties
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseSubscribePacket(control controlPacket) (SubscribePacket, error) {
|
||||||
|
if control.packetType != PacketTypeSubscribe {
|
||||||
|
panic("Wrong packet type for parseSubscribePacket")
|
||||||
|
}
|
||||||
|
|
||||||
|
pack := SubscribePacket{}
|
||||||
|
subscriptionPack, err := parseSubscriptionPacket(control, pack.props.ArrayOf())
|
||||||
|
if err != nil {
|
||||||
|
return pack, err
|
||||||
|
}
|
||||||
|
pack.PacketId = subscriptionPack.PacketId
|
||||||
|
pack.TopicFilters = subscriptionPack.TopicFilters
|
||||||
|
return pack, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type SubackReasonCode byte
|
||||||
|
|
||||||
|
const (
|
||||||
|
SubackReasonGrantedQoSZero PubackReasonCode = 0
|
||||||
|
SubackReasonGrantedQoSOne = 1
|
||||||
|
SubackReasonGrantedQoSTwo = 2
|
||||||
|
SubackReasonUnspecified = 128
|
||||||
|
SubackReasonImplSpecificError = 131
|
||||||
|
SubackReasonNotAuthorized = 135
|
||||||
|
SubackReasonTopicFilterInvalid = 143
|
||||||
|
SubackReasonPacketIDInUse = 145
|
||||||
|
SubackReasonQuotaExceeded = 151
|
||||||
|
SubackReasonSharedSubNotSupported = 151
|
||||||
|
SubackReasonSubIDUnsupported = 151
|
||||||
|
SubackReasonWildcardSubUnsupported = 151
|
||||||
|
)
|
||||||
|
|
||||||
|
type SubAckPacket struct {
|
||||||
|
PacketID uint16
|
||||||
|
Properties properties.SubackPacketProperties
|
||||||
|
Reason SubackReasonCode
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func (p SubAckPacket) Write(w io.Writer) error {
|
||||||
|
resp := pubRespPacket{
|
||||||
|
PacketType: PacketTypeSuback,
|
||||||
|
PacketID: p.PacketID,
|
||||||
|
Properties: p.Properties.ArrayOf(),
|
||||||
|
Reason: byte(p.Reason),
|
||||||
|
}
|
||||||
|
return resp.Write(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
type UnsubscribePacket struct {
|
||||||
|
*SubscriptionPacket
|
||||||
|
props properties.UnsubscribePacketProperties
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseUnsubscribePacket(control controlPacket) (UnsubscribePacket, error) {
|
||||||
|
if control.packetType != PacketTypeUnsubscribe {
|
||||||
|
panic("Wrong packet type for parseSubscribePacket")
|
||||||
|
}
|
||||||
|
|
||||||
|
pack := UnsubscribePacket{}
|
||||||
|
subscriptionPack, err := parseSubscriptionPacket(control, pack.props.ArrayOf())
|
||||||
|
if err != nil {
|
||||||
|
return pack, err
|
||||||
|
}
|
||||||
|
pack.PacketId = subscriptionPack.PacketId
|
||||||
|
pack.TopicFilters = subscriptionPack.TopicFilters
|
||||||
|
return pack, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type UnsubackReasonCode byte
|
||||||
|
|
||||||
|
const (
|
||||||
|
UnsubackReasonSuccess PubackReasonCode = 0
|
||||||
|
UnSubackReasonUnspecified = 128
|
||||||
|
UnSubackReasonImplSpecificError = 131
|
||||||
|
UnSubackReasonNotAuthorized = 135
|
||||||
|
UnSubackReasonTopicFilterInvalid = 143
|
||||||
|
UnSubackReasonPacketIDInUse = 145
|
||||||
|
)
|
||||||
|
|
||||||
|
type UnsubAckPacket struct {
|
||||||
|
PacketID uint16
|
||||||
|
Properties properties.UnsubackPacketProperties
|
||||||
|
Reason UnsubackReasonCode
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func (p UnsubAckPacket) Write(w io.Writer) error {
|
||||||
|
resp := pubRespPacket{
|
||||||
|
PacketType: PacketTypeUnsuback,
|
||||||
|
PacketID: p.PacketID,
|
||||||
|
Properties: p.Properties.ArrayOf(),
|
||||||
|
Reason: byte(p.Reason),
|
||||||
|
}
|
||||||
|
return resp.Write(w)
|
||||||
|
}
|
42
mqtt/packets/ack.go
Normal file
42
mqtt/packets/ack.go
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
package packets
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"badat.dev/maeqtt/v2/mqtt/properties"
|
||||||
|
"badat.dev/maeqtt/v2/mqtt/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Boilerplate struct for de/serializing various ack packets
|
||||||
|
type pubRespPacket struct {
|
||||||
|
PacketType PacketType
|
||||||
|
PacketID uint16
|
||||||
|
Properties []properties.Property
|
||||||
|
Reason byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p pubRespPacket) Write(w io.Writer) error {
|
||||||
|
buf := bytes.NewBuffer([]byte{})
|
||||||
|
err := types.WriteUint16(buf, p.PacketID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = buf.WriteByte(byte(p.Reason))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = properties.WriteProps(buf, p.Properties)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
conPack := controlPacket{
|
||||||
|
packetType: PacketTypePuback,
|
||||||
|
flags: 0,
|
||||||
|
reader: buf,
|
||||||
|
}
|
||||||
|
return conPack.write(w)
|
||||||
|
}
|
82
mqtt/packets/packets.go
Normal file
82
mqtt/packets/packets.go
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
package packets
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"badat.dev/maeqtt/v2/mqtt/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PacketVisitor interface {
|
||||||
|
visitConnect(ConnectPacket)
|
||||||
|
visitPublish(PublishPacket)
|
||||||
|
visitDisconnect(DisconnectPacket)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ClientPacket interface {
|
||||||
|
visit(PacketVisitor)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ServerPacket interface {
|
||||||
|
Encode() (bytes.Buffer, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type PacketType byte
|
||||||
|
|
||||||
|
const (
|
||||||
|
PacketTypeReserved PacketType = 0 // Forbidden
|
||||||
|
PacketTypeConnect = 1
|
||||||
|
PacketTypeConnack = 2
|
||||||
|
PacketTypePublish = 3
|
||||||
|
PacketTypePuback = 4
|
||||||
|
PacketTypePubrec = 5
|
||||||
|
PacketTypePubrel = 6
|
||||||
|
PacketTypePubcomp = 7
|
||||||
|
PacketTypeSubscribe = 8
|
||||||
|
PacketTypeSuback = 9
|
||||||
|
PacketTypeUnsubscribe = 10
|
||||||
|
PacketTypeUnsuback = 11
|
||||||
|
PacketTypePingreq = 12
|
||||||
|
PacketTypePingresp = 13
|
||||||
|
PacketTypeDisconnect = 14
|
||||||
|
PacketTypeAuth = 15
|
||||||
|
)
|
||||||
|
|
||||||
|
func ReadPacket(r *bufio.Reader) (*ClientPacket, error) {
|
||||||
|
println("AAAA")
|
||||||
|
fixedHeader, err := r.ReadByte()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
println("BBB")
|
||||||
|
|
||||||
|
highestFourBits := uint((fixedHeader >> 4) & 0b1111)
|
||||||
|
lowerFourBits := uint(fixedHeader & 0b1111)
|
||||||
|
|
||||||
|
dataLength, err := types.DecodeVariableByteInt(r)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
reader := io.LimitReader(r, int64(dataLength))
|
||||||
|
control := controlPacket{
|
||||||
|
packetType: PacketType(highestFourBits),
|
||||||
|
flags: lowerFourBits,
|
||||||
|
reader: reader,
|
||||||
|
}
|
||||||
|
|
||||||
|
var packet ClientPacket
|
||||||
|
switch control.packetType {
|
||||||
|
case PacketTypeConnect:
|
||||||
|
packet, err = parseConnectPacket(control)
|
||||||
|
case PacketTypePublish:
|
||||||
|
packet, err = parsePublishPacket(control)
|
||||||
|
case PacketTypeDisconnect:
|
||||||
|
packet, err = parseDisconnectPacket(control)
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("Unknown packet type %v", control.packetType)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &packet, err
|
||||||
|
}
|
121
mqtt/properties/Properties.go
Normal file
121
mqtt/properties/Properties.go
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
package properties
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"badat.dev/maeqtt/v2/mqtt/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Property interface {
|
||||||
|
id() int
|
||||||
|
parse(r *bufio.Reader) error
|
||||||
|
write(w io.Writer) error
|
||||||
|
hasValue() bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func findMatchingProp(props []Property, id int) *Property {
|
||||||
|
for _, prop := range props {
|
||||||
|
if prop.id() == id {
|
||||||
|
return (&prop)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseProperties(r *bufio.Reader, props []Property) error {
|
||||||
|
propLen, err := types.DecodeVariableByteInt(r)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
limitReader := io.LimitReader(r, int64(propLen))
|
||||||
|
r = bufio.NewReader(limitReader)
|
||||||
|
|
||||||
|
for {
|
||||||
|
propId, err := r.ReadByte()
|
||||||
|
if err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
prop := findMatchingProp(props, int(propId))
|
||||||
|
if prop == nil {
|
||||||
|
return errors.New("Malformed packet invalid propid")
|
||||||
|
}
|
||||||
|
err = (*prop).parse(r)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func WriteProps(w io.Writer, props []Property) error {
|
||||||
|
buf := bytes.NewBuffer([]byte{})
|
||||||
|
for _, p := range props {
|
||||||
|
if p.hasValue() {
|
||||||
|
buf.Write([]byte{byte(p.id())})
|
||||||
|
err := p.write(buf)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return types.WriteDataWithVarIntLen(w, buf.Bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
type KVPair struct {
|
||||||
|
key string
|
||||||
|
value string
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserProperty struct {
|
||||||
|
values []KVPair
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*UserProperty) id() int {
|
||||||
|
return 38
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *UserProperty) parse(r *bufio.Reader) error {
|
||||||
|
key, err := types.DecodeUTF8String(r)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
value, err := types.DecodeUTF8String(r)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
u.values = append(u.values, KVPair{
|
||||||
|
key: key,
|
||||||
|
value: value,
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *UserProperty) write(w io.Writer) error {
|
||||||
|
for _, k := range u.values {
|
||||||
|
err := types.WriteUTF8String(w, k.key)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = types.WriteUTF8String(w, k.value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *UserProperty) hasValue() bool {
|
||||||
|
return len(u.values) > 0
|
||||||
|
}
|
104
mqtt/properties/genProps.py
Normal file
104
mqtt/properties/genProps.py
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
import json
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
|
print("package properties")
|
||||||
|
print("// This code has been generated with the genProps.py script. Do not modify\n\n")
|
||||||
|
|
||||||
|
print("import \"bufio\"")
|
||||||
|
print("import \"io\"")
|
||||||
|
print("import . \"badat.dev/maeqtt/v2/mqtt/types\"")
|
||||||
|
|
||||||
|
TYPE_TO_GOTYPE = {
|
||||||
|
"Byte": "byte",
|
||||||
|
"Two Byte Integer": "uint16",
|
||||||
|
"Four Byte Integer": "uint32",
|
||||||
|
"Binary Data": "[]byte",
|
||||||
|
"UTF-8 Encoded String": "string",
|
||||||
|
"Variable Byte Integer": "uint32",
|
||||||
|
}
|
||||||
|
TYPE_DECODE_CODE = {
|
||||||
|
"Byte": "val, err := r.ReadByte()",
|
||||||
|
"Two Byte Integer": "val, err := DecodeUint16(r)",
|
||||||
|
"Four Byte Integer": "val, err := DecodeUint32(r)",
|
||||||
|
"Binary Data": "val, err := DecodeBinaryData(r)",
|
||||||
|
"UTF-8 Encoded String": "val, err := DecodeUTF8String(r)",
|
||||||
|
"Variable Byte Integer": "val, err := DecodeVariableByteInt(r)"
|
||||||
|
}
|
||||||
|
|
||||||
|
TYPE_WRITE_CODE = {
|
||||||
|
"Byte": "(func () error {_, err := w.Write([]byte{*p.Value}); return err})()",
|
||||||
|
"Two Byte Integer": "WriteUint16(w, *p.Value)",
|
||||||
|
"Four Byte Integer": "WriteUint32(w, *p.Value)",
|
||||||
|
"Binary Data": "WriteBinaryData(w, *p.Value)",
|
||||||
|
"UTF-8 Encoded String": "WriteUTF8String(w, *p.Value)",
|
||||||
|
"Variable Byte Integer": "WriteVariableByteInt(w, *p.Value)"
|
||||||
|
}
|
||||||
|
|
||||||
|
applicationToProp = defaultdict(lambda: [])
|
||||||
|
|
||||||
|
with open("./properties.json") as f:
|
||||||
|
properties = json.load(f)
|
||||||
|
|
||||||
|
for prop in properties:
|
||||||
|
prop["name"] = "".join(prop["name"].split(" "))
|
||||||
|
|
||||||
|
for prop in properties:
|
||||||
|
for application in prop["appliesTo"]:
|
||||||
|
applicationToProp[application].append(prop)
|
||||||
|
|
||||||
|
val = prop["val"]
|
||||||
|
name= prop["name"]
|
||||||
|
if val == "38":
|
||||||
|
#needs manual handling
|
||||||
|
continue
|
||||||
|
|
||||||
|
gotype = TYPE_TO_GOTYPE[prop["type"]]
|
||||||
|
godecode = TYPE_DECODE_CODE[prop["type"]]
|
||||||
|
gowrite = TYPE_WRITE_CODE[prop["type"]]
|
||||||
|
|
||||||
|
print("""
|
||||||
|
type {name} struct {{
|
||||||
|
Value *{gotype}
|
||||||
|
}}
|
||||||
|
|
||||||
|
func (p {name}) id() int {{
|
||||||
|
return {val}
|
||||||
|
}}
|
||||||
|
|
||||||
|
func (p *{name}) parse(r *bufio.Reader) error {{
|
||||||
|
{godecode}
|
||||||
|
if err != nil {{
|
||||||
|
return err
|
||||||
|
}}
|
||||||
|
p.Value = &val
|
||||||
|
return nil
|
||||||
|
}}
|
||||||
|
|
||||||
|
func (p {name}) hasValue() bool {{
|
||||||
|
return p.Value != nil
|
||||||
|
}}
|
||||||
|
|
||||||
|
func (p {name}) write(w io.Writer) error {{
|
||||||
|
return {gowrite}
|
||||||
|
}}
|
||||||
|
""".format(name= name, gotype = gotype, gowrite = gowrite, godecode = godecode, val = val
|
||||||
|
))
|
||||||
|
|
||||||
|
for k,v in applicationToProp.items():
|
||||||
|
if k == "Will Properties":
|
||||||
|
arrName = "WillProperties"
|
||||||
|
else:
|
||||||
|
arrName = k.lower().capitalize() + "PacketProperties"
|
||||||
|
|
||||||
|
print("type", arrName, "struct {")
|
||||||
|
|
||||||
|
for prop in v:
|
||||||
|
print(prop["name"], prop["name"])
|
||||||
|
print("}")
|
||||||
|
print(f"func (p *{arrName}) ArrayOf() []Property {{")
|
||||||
|
print("return []Property {")
|
||||||
|
for prop in v:
|
||||||
|
propName = prop["name"]
|
||||||
|
print(f"&p.{propName},")
|
||||||
|
print("}")
|
||||||
|
print("}")
|
189
mqtt/properties/properties.json
Normal file
189
mqtt/properties/properties.json
Normal file
|
@ -0,0 +1,189 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"val": "1",
|
||||||
|
"name": "Payload Format Indicator",
|
||||||
|
"type": "Byte",
|
||||||
|
"appliesTo": ["PUBLISH", "Will Properties"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"val": "2",
|
||||||
|
"name": "Message Expiry Interval",
|
||||||
|
"type": "Four Byte Integer",
|
||||||
|
"appliesTo": ["PUBLISH", "Will Properties"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"val": "3",
|
||||||
|
"name": "Content Type",
|
||||||
|
"type": "UTF-8 Encoded String",
|
||||||
|
"appliesTo": ["PUBLISH", "Will Properties"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"val": "8",
|
||||||
|
"name": "Response Topic",
|
||||||
|
"type": "UTF-8 Encoded String",
|
||||||
|
"appliesTo": ["PUBLISH", "Will Properties"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"val": "9",
|
||||||
|
"name": "Correlation Data",
|
||||||
|
"type": "Binary Data",
|
||||||
|
"appliesTo": ["PUBLISH", "Will Properties"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"val": "11",
|
||||||
|
"name": "Subscription Identifier",
|
||||||
|
"type": "Variable Byte Integer",
|
||||||
|
"appliesTo": ["PUBLISH", "SUBSCRIBE"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"val": "17",
|
||||||
|
"name": "Session Expiry Interval",
|
||||||
|
"type": "Four Byte Integer",
|
||||||
|
"appliesTo": ["CONNECT", "CONNACK", "DISCONNECT"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"val": "18",
|
||||||
|
"name": "Assigned Client Identifier",
|
||||||
|
"type": "UTF-8 Encoded String",
|
||||||
|
"appliesTo": ["CONNACK"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"val": "19",
|
||||||
|
"name": "Server Keep Alive",
|
||||||
|
"type": "Two Byte Integer",
|
||||||
|
"appliesTo": ["CONNACK"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"val": "21",
|
||||||
|
"name": "Authentication Method",
|
||||||
|
"type": "UTF-8 Encoded String",
|
||||||
|
"appliesTo": ["CONNECT", "CONNACK", "AUTH"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"val": "22",
|
||||||
|
"name": "Authentication Data",
|
||||||
|
"type": "Binary Data",
|
||||||
|
"appliesTo": ["CONNECT", "CONNACK", "AUTH"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"val": "23",
|
||||||
|
"name": "Request Problem Information",
|
||||||
|
"type": "Byte",
|
||||||
|
"appliesTo": ["CONNECT"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"val": "24",
|
||||||
|
"name": "Will Delay Interval",
|
||||||
|
"type": "Four Byte Integer",
|
||||||
|
"appliesTo": ["Will Properties"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"val": "25",
|
||||||
|
"name": "Request Response Information",
|
||||||
|
"type": "Byte",
|
||||||
|
"appliesTo": ["CONNECT"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"val": "26",
|
||||||
|
"name": "Response Information",
|
||||||
|
"type": "UTF-8 Encoded String",
|
||||||
|
"appliesTo": ["CONNACK"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"val": "28",
|
||||||
|
"name": "Server Reference",
|
||||||
|
"type": "UTF-8 Encoded String",
|
||||||
|
"appliesTo": ["CONNACK", "DISCONNECT"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"val": "31",
|
||||||
|
"name": "Reason String",
|
||||||
|
"type": "UTF-8 Encoded String",
|
||||||
|
"appliesTo": [
|
||||||
|
"CONNACK",
|
||||||
|
"PUBACK",
|
||||||
|
"PUBREC",
|
||||||
|
"PUBREL",
|
||||||
|
"PUBCOMP",
|
||||||
|
"SUBACK",
|
||||||
|
"UNSUBACK",
|
||||||
|
"DISCONNECT",
|
||||||
|
"AUTH"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"val": "33",
|
||||||
|
"name": "Receive Maximum",
|
||||||
|
"type": "Two Byte Integer",
|
||||||
|
"appliesTo": ["CONNECT", "CONNACK"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"val": "34",
|
||||||
|
"name": "Topic Alias Maximum",
|
||||||
|
"type": "Two Byte Integer",
|
||||||
|
"appliesTo": ["CONNECT", "CONNACK"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"val": "35",
|
||||||
|
"name": "Topic Alias",
|
||||||
|
"type": "Two Byte Integer",
|
||||||
|
"appliesTo": ["PUBLISH"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"val": "36",
|
||||||
|
"name": "Maximum QoS",
|
||||||
|
"type": "Byte",
|
||||||
|
"appliesTo": ["CONNACK"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"val": "37",
|
||||||
|
"name": "Retain Available",
|
||||||
|
"type": "Byte",
|
||||||
|
"appliesTo": ["CONNACK"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"val": "38",
|
||||||
|
"name": "User Property",
|
||||||
|
"type": "UTF-8 String Pair",
|
||||||
|
"appliesTo": [
|
||||||
|
"CONNECT",
|
||||||
|
"CONNACK",
|
||||||
|
"PUBLISH",
|
||||||
|
"Will Properties",
|
||||||
|
"PUBACK",
|
||||||
|
"PUBREC",
|
||||||
|
"PUBREL",
|
||||||
|
"PUBCOMP",
|
||||||
|
"SUBSCRIBE",
|
||||||
|
"SUBACK",
|
||||||
|
"UNSUBSCRIBE",
|
||||||
|
"UNSUBACK",
|
||||||
|
"DISCONNECT",
|
||||||
|
"AUTH"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"val": "39",
|
||||||
|
"name": "Maximum Packet Size",
|
||||||
|
"type": "Four Byte Integer",
|
||||||
|
"appliesTo": ["CONNECT", "CONNACK"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"val": "40",
|
||||||
|
"name": "Wildcard Subscription Available",
|
||||||
|
"type": "Byte",
|
||||||
|
"appliesTo": ["CONNACK"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"val": "41",
|
||||||
|
"name": "Subscription Identifier Available",
|
||||||
|
"type": "Byte",
|
||||||
|
"appliesTo": ["CONNACK"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"val": "42",
|
||||||
|
"name": "Shared Subscription Available",
|
||||||
|
"type": "Byte",
|
||||||
|
"appliesTo": ["CONNACK"]
|
||||||
|
}
|
||||||
|
]
|
86
mqtt/types/Decoding.go
Normal file
86
mqtt/types/Decoding.go
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func DecodeBits(r *bufio.Reader) ([8]bool, error) {
|
||||||
|
bitflags, err := r.ReadByte()
|
||||||
|
if err != nil {
|
||||||
|
return [8]bool{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var res [8]bool
|
||||||
|
for i := range res {
|
||||||
|
res[i] = (bitflags>>i)&1 == 1
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func DecodeUint16(r *bufio.Reader) (uint16, error) {
|
||||||
|
buf := make([]byte, 2)
|
||||||
|
_, err := r.Read(buf)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return binary.BigEndian.Uint16(buf), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func DecodeUint32(r *bufio.Reader) (uint32, error) {
|
||||||
|
buf := make([]byte, 4)
|
||||||
|
_, err := r.Read(buf)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return binary.BigEndian.Uint32(buf), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func DecodeDataWithVarIntLen(r *bufio.Reader) ([]byte, error) {
|
||||||
|
len, err := DecodeVariableByteInt(r)
|
||||||
|
if err != nil {
|
||||||
|
return []byte{}, err
|
||||||
|
}
|
||||||
|
buffer := make([]byte, len)
|
||||||
|
_, err = r.Read(buffer)
|
||||||
|
return buffer, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func DecodeBinaryData(r *bufio.Reader) ([]byte, error) {
|
||||||
|
len, err := DecodeUint16(r)
|
||||||
|
if err != nil {
|
||||||
|
return []byte{}, err
|
||||||
|
}
|
||||||
|
buffer := make([]byte, len)
|
||||||
|
_, err = r.Read(buffer)
|
||||||
|
return buffer, err
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func DecodeUTF8String(r *bufio.Reader) (string, error) {
|
||||||
|
binary, err := DecodeBinaryData(r)
|
||||||
|
return string(binary[:]), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func DecodeVariableByteInt(r *bufio.Reader) (value uint32, err error) {
|
||||||
|
multiplier := uint32(1)
|
||||||
|
value = 0
|
||||||
|
|
||||||
|
for {
|
||||||
|
encodedByte, err := r.ReadByte()
|
||||||
|
if err != nil {
|
||||||
|
return value, err
|
||||||
|
}
|
||||||
|
value += uint32((encodedByte & 127)) * multiplier
|
||||||
|
if multiplier > 128*128*128 {
|
||||||
|
return value, errors.New("Malformed Variable Byte Integer")
|
||||||
|
}
|
||||||
|
multiplier *= 128
|
||||||
|
|
||||||
|
if encodedByte&128 == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return value, nil
|
||||||
|
}
|
83
mqtt/types/Encoding.go
Normal file
83
mqtt/types/Encoding.go
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
func WriteBits(w io.Writer, data [8]bool) error {
|
||||||
|
encoded := byte(0)
|
||||||
|
for i, v := range data {
|
||||||
|
encoded = encoded | byte(BoolToUint(v)<<i)
|
||||||
|
}
|
||||||
|
_, err := w.Write([]byte{encoded})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func WriteUint16(w io.Writer, v uint16) error {
|
||||||
|
buf := make([]byte, 2)
|
||||||
|
binary.BigEndian.PutUint16(buf, v)
|
||||||
|
_, err := w.Write(buf)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func WriteUint32(w io.Writer, v uint32) error {
|
||||||
|
buf := make([]byte, 4)
|
||||||
|
binary.BigEndian.PutUint32(buf, v)
|
||||||
|
_, err := w.Write(buf)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const uint32Max uint32 = ^uint32(0)
|
||||||
|
func WriteDataWithVarIntLen(w io.Writer, data []byte) error {
|
||||||
|
if len(data) > int(uint32Max) {
|
||||||
|
return errors.New("Tried to write more data than max varint size")
|
||||||
|
}
|
||||||
|
|
||||||
|
err := WriteVariableByteInt(w, uint32(len(data)))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = w.Write(data)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint16Max uint16 = ^uint16(0)
|
||||||
|
func WriteBinaryData(w io.Writer, data []byte) error {
|
||||||
|
if len(data) > int(uint16Max) {
|
||||||
|
return errors.New("Tried to write more data than max uint16 size")
|
||||||
|
}
|
||||||
|
|
||||||
|
err := WriteUint16(w, uint16(len(data)))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = w.Write(data)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func WriteUTF8String(w io.Writer, str string) error {
|
||||||
|
return WriteBinaryData(w, []byte(str))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func WriteVariableByteInt(w io.Writer, v uint32) error {
|
||||||
|
for {
|
||||||
|
encodedByte := byte(v % 128)
|
||||||
|
v = v / 128
|
||||||
|
|
||||||
|
if v > 0 {
|
||||||
|
encodedByte = encodedByte | 128
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Write([]byte{encodedByte})
|
||||||
|
|
||||||
|
if v == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
19
mqtt/types/Utils.go
Normal file
19
mqtt/types/Utils.go
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
package types
|
||||||
|
|
||||||
|
// Returns 1 if b is true and 0 if false
|
||||||
|
func BoolToUint(b bool) uint {
|
||||||
|
if b {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reinterprets a slice of bools as a uint
|
||||||
|
// This method doesn't check for overflow
|
||||||
|
func BoolsToUint(b ...bool) uint {
|
||||||
|
var res uint
|
||||||
|
for i, v := range b {
|
||||||
|
res += BoolToUint(v) << i
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
5
shell.nix
Normal file
5
shell.nix
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
{ pkgs ? import <nixpkgs> {} }:
|
||||||
|
pkgs.mkShell {
|
||||||
|
# nativeBuildInputs is usually what you want -- tools you need to run
|
||||||
|
nativeBuildInputs = with pkgs; [ (enableDebugging mosquitto) wireshark delve ];
|
||||||
|
}
|
Loading…
Reference in a new issue