aboutsummaryrefslogtreecommitdiff
path: root/inform
diff options
context:
space:
mode:
authorMike Crute <mcrute@gmail.com>2016-09-17 20:44:37 -0700
committerMike Crute <mcrute@gmail.com>2016-09-17 20:48:38 -0700
commit80aac9a2da274b4f539e5b6cafbfd06b1a75c9d9 (patch)
tree41f877f4ebb6ec9ebe4fa6251ee962f07ba5a477 /inform
downloadgo-inform-80aac9a2da274b4f539e5b6cafbfd06b1a75c9d9.tar.bz2
go-inform-80aac9a2da274b4f539e5b6cafbfd06b1a75c9d9.tar.xz
go-inform-80aac9a2da274b4f539e5b6cafbfd06b1a75c9d9.zip
Initial import
Diffstat (limited to 'inform')
-rw-r--r--inform/codec.go83
-rw-r--r--inform/crypto.go88
-rw-r--r--inform/inform.go133
-rw-r--r--inform/rx_messages.go99
-rw-r--r--inform/tx_messages.go28
5 files changed, 431 insertions, 0 deletions
diff --git a/inform/codec.go b/inform/codec.go
new file mode 100644
index 0000000..e74c3d7
--- /dev/null
+++ b/inform/codec.go
@@ -0,0 +1,83 @@
1package inform
2
3import (
4 "bytes"
5 "encoding/binary"
6 "errors"
7 "io"
8)
9
10type Codec struct {
11 // KeyBag contains a mapping of comma-separated MAC addresses to their AES
12 // keys
13 KeyBag map[string]string
14}
15
16func (c *Codec) Unmarshal(fp io.Reader) (*InformWrapper, error) {
17 w := NewInformWrapper()
18
19 var magic int32
20 binary.Read(fp, binary.BigEndian, &magic)
21 if magic != PROTOCOL_MAGIC {
22 return nil, errors.New("Invalid magic number")
23 }
24
25 binary.Read(fp, binary.BigEndian, &w.Version)
26 io.ReadFull(fp, w.MacAddr)
27 binary.Read(fp, binary.BigEndian, &w.Flags)
28
29 iv := make([]byte, 16)
30 io.ReadFull(fp, iv)
31
32 binary.Read(fp, binary.BigEndian, &w.DataVersion)
33
34 var dataLen int32
35 binary.Read(fp, binary.BigEndian, &dataLen)
36
37 p := make([]byte, dataLen)
38 io.ReadFull(fp, p)
39
40 key, ok := c.KeyBag[w.FormattedMac()]
41 if !ok {
42 return nil, errors.New("No key found")
43 }
44
45 u, err := Decrypt(p, iv, key)
46 if err != nil {
47 return nil, err
48 }
49
50 w.Payload = u
51
52 return w, nil
53}
54
55func (c *Codec) Marshal(msg *InformWrapper) ([]byte, error) {
56 b := &bytes.Buffer{}
57 payload := msg.Payload
58 var iv []byte
59
60 if msg.IsEncrypted() {
61 key, ok := c.KeyBag[msg.FormattedMac()]
62 if !ok {
63 return nil, errors.New("No key found")
64 }
65
66 var err error
67 payload, iv, err = Encrypt(payload, key)
68 if err != nil {
69 return nil, err
70 }
71 }
72
73 binary.Write(b, binary.BigEndian, PROTOCOL_MAGIC)
74 binary.Write(b, binary.BigEndian, msg.Version)
75 b.Write(msg.MacAddr)
76 binary.Write(b, binary.BigEndian, msg.Flags)
77 b.Write(iv)
78 binary.Write(b, binary.BigEndian, msg.DataVersion)
79 binary.Write(b, binary.BigEndian, int32(len(payload)))
80 b.Write(payload)
81
82 return b.Bytes(), nil
83}
diff --git a/inform/crypto.go b/inform/crypto.go
new file mode 100644
index 0000000..90118c1
--- /dev/null
+++ b/inform/crypto.go
@@ -0,0 +1,88 @@
1package inform
2
3import (
4 "bytes"
5 "crypto/aes"
6 "crypto/cipher"
7 "crypto/rand"
8 "encoding/hex"
9 "errors"
10)
11
12func Pad(src []byte, blockSize int) []byte {
13 padLen := blockSize - (len(src) % blockSize)
14 padText := bytes.Repeat([]byte{byte(padLen)}, padLen)
15 return append(src, padText...)
16}
17
18func Unpad(src []byte, blockSize int) ([]byte, error) {
19 srcLen := len(src)
20 paddingLen := int(src[srcLen-1])
21 if paddingLen >= srcLen || paddingLen > blockSize {
22 return nil, errors.New("Padding size error")
23 }
24 return src[:srcLen-paddingLen], nil
25}
26
27func decodeHexKey(key string) (cipher.Block, error) {
28 decodedKey, err := hex.DecodeString(key)
29 if err != nil {
30 return nil, err
31 }
32
33 block, err := aes.NewCipher(decodedKey)
34 if err != nil {
35 return nil, err
36 }
37
38 return block, nil
39}
40
41func makeAESIV() ([]byte, error) {
42 iv := make([]byte, 16)
43 if _, err := rand.Read(iv); err != nil {
44 return nil, err
45 }
46 return iv, nil
47}
48
49// Returns ciphertext and IV, does not modify payload
50func Encrypt(payload []byte, key string) ([]byte, []byte, error) {
51 ct := make([]byte, len(payload))
52 copy(ct, payload)
53 ct = Pad(ct, aes.BlockSize)
54
55 iv, err := makeAESIV()
56 if err != nil {
57 return nil, nil, err
58 }
59
60 block, err := decodeHexKey(key)
61 if err != nil {
62 return nil, nil, err
63 }
64
65 mode := cipher.NewCBCEncrypter(block, iv)
66 mode.CryptBlocks(ct, ct)
67
68 return ct, iv, nil
69}
70
71func Decrypt(payload, iv []byte, key string) ([]byte, error) {
72 b := make([]byte, len(payload))
73
74 block, err := decodeHexKey(key)
75 if err != nil {
76 return nil, err
77 }
78
79 mode := cipher.NewCBCDecrypter(block, iv)
80 mode.CryptBlocks(b, payload)
81
82 u, err := Unpad(b, aes.BlockSize)
83 if err != nil {
84 return nil, err
85 }
86
87 return u, nil
88}
diff --git a/inform/inform.go b/inform/inform.go
new file mode 100644
index 0000000..b159084
--- /dev/null
+++ b/inform/inform.go
@@ -0,0 +1,133 @@
1package inform
2
3import (
4 "bytes"
5 "encoding/json"
6 "errors"
7 "fmt"
8 "github.com/mitchellh/mapstructure"
9)
10
11const (
12 PROTOCOL_MAGIC int32 = 1414414933 // TNBU
13 INFORM_VERSION int32 = 0
14 DATA_VERSION int32 = 1
15
16 ENCRYPTED_FLAG = 1
17 COMPRESSED_FLAG = 2
18)
19
20// Wrapper around an inform message, serializes directly into the wire
21// protocol
22type InformWrapper struct {
23 Version int32
24 MacAddr []byte
25 Flags int16
26 DataVersion int32
27 Payload []byte
28}
29
30// Create InformWrapper with sane defaults
31func NewInformWrapper() *InformWrapper {
32 return &InformWrapper{
33 Version: INFORM_VERSION,
34 MacAddr: make([]byte, 6),
35 Flags: 0,
36 DataVersion: DATA_VERSION,
37 }
38}
39
40// Update the payload data with JSON value
41func (i *InformWrapper) UpdatePayload(v interface{}) error {
42 if d, err := json.Marshal(v); err != nil {
43 return err
44 } else {
45 i.Payload = d
46 return nil
47 }
48}
49
50// Format Mac address bytes as lowercase string with colons
51func (i *InformWrapper) FormattedMac() string {
52 return fmt.Sprintf("%02x:%02x:%02x:%02x:%02x:%02x",
53 i.MacAddr[0], i.MacAddr[1], i.MacAddr[2],
54 i.MacAddr[3], i.MacAddr[4], i.MacAddr[5])
55}
56
57func (i *InformWrapper) String() string {
58 b := &bytes.Buffer{}
59
60 fmt.Fprintf(b, "Version: \t%d\n", i.Version)
61 fmt.Fprintf(b, "Mac Addr: \t%s\n", i.FormattedMac())
62 fmt.Fprintf(b, "Flags: \t%d\n", i.Flags)
63 fmt.Fprintf(b, " Encrypted: \t%t\n", i.IsEncrypted())
64 fmt.Fprintf(b, " Compressed: \t%t\n", i.IsCompressed())
65 fmt.Fprintf(b, "Data Version: \t%d\n", i.DataVersion)
66 fmt.Fprintf(b, "Payload: \t%q\n", i.Payload)
67
68 return b.String()
69}
70
71func (i *InformWrapper) IsEncrypted() bool {
72 return i.Flags&ENCRYPTED_FLAG != 0
73}
74
75func (i *InformWrapper) SetEncrypted(e bool) {
76 if e {
77 i.Flags |= ENCRYPTED_FLAG
78 } else {
79 i.Flags &= ENCRYPTED_FLAG
80 }
81}
82
83func (i *InformWrapper) IsCompressed() bool {
84 return i.Flags&COMPRESSED_FLAG != 0
85}
86
87func (i *InformWrapper) SetCompressed(c bool) {
88 if c {
89 i.Flags |= COMPRESSED_FLAG
90 } else {
91 i.Flags &= COMPRESSED_FLAG
92 }
93}
94
95// Decode payload to a map and try to determine the inform type
96func (i *InformWrapper) decodePayload() (map[string]interface{}, string, error) {
97 var m map[string]interface{}
98
99 if err := json.Unmarshal(i.Payload, &m); err != nil {
100 return nil, "", err
101 }
102
103 t, ok := m["_type"]
104 if !ok {
105 return nil, "", errors.New("Message contains no type")
106 }
107
108 st, ok := t.(string)
109 if !ok {
110 return nil, "", errors.New("Message type is not a string")
111 }
112
113 return m, st, nil
114}
115
116// Decode payload JSON as a inform message
117func (i *InformWrapper) JsonMessage() (interface{}, error) {
118 msg, t, err := i.decodePayload()
119 if err != nil {
120 return nil, err
121 }
122
123 switch t {
124 case "noop":
125 var o NoopMessage
126 if err := mapstructure.Decode(msg, &o); err != nil {
127 return nil, err
128 }
129 return o, nil
130 }
131
132 return nil, errors.New(fmt.Sprintf("Message type %s is invalid", t))
133}
diff --git a/inform/rx_messages.go b/inform/rx_messages.go
new file mode 100644
index 0000000..3f80fcd
--- /dev/null
+++ b/inform/rx_messages.go
@@ -0,0 +1,99 @@
1package inform
2
3// TODO: Convert string time to time.Time
4// Response packet
5type NoopMessage struct {
6 Type string `json:"_type"`
7 Interval int `json:"interval"`
8 ServerTimeUTC string `json:"server_time_in_utc"`
9}
10
11type AlarmEntry struct {
12 Tag string `json:"tag"`
13 Type string `json:"string"`
14 Value string `json:"val"` // float or int observed
15}
16
17type AlarmMessage struct {
18 Entries []*AlarmEntry `json:"entries"`
19 Index string `json:"index"`
20 Id string `json:"sId"`
21 Time int `json:"time"`
22}
23
24type InterfaceMessage struct {
25 IP string `json:"ip"`
26 MacAddress string `json:"mac"`
27 Name string `json:"name"`
28 Type string `json:"type"`
29 ReceivedBytes int `json:"rx_bytes"`
30 ReceivedDropped int `json:"rx_dropped"`
31 ReceivedErrors int `json:"rx_errors"`
32 ReceivedPackets int `json:"rx_packets"`
33 TransmittedBytes int `json:"tx_bytes"`
34 TransmittedDropped int `json:"tx_dropped"`
35 TransmittedErrors int `json:"tx_errors"`
36 TransmittedPackets int `json:"tx_packets"`
37}
38
39type RadioMessage struct {
40 Gain int `json:"builtin_ant_gain"`
41 BuiltinAntenna bool `json:"builtin_antenna"`
42 MaxTransmitPower int `json:"max_txpower"`
43 Name string `json:"name"`
44 RadioProfile string `json:"radio"`
45 // "scan_table": []
46}
47
48type AccessPointMessage struct {
49 BasicSSID string `json:"bssid"`
50 ExtendedSSID string `json:"essid"`
51 ClientConnectionQuality int `json:"ccq"`
52 Channel int `json:"channel"`
53 Id string `json:"id"`
54 Name string `json:"name"`
55 StationNumber string `json:"num_sta"` // int?
56 RadioProfile string `json:"radio"`
57 Usage string `json:"usage"`
58 ReceivedBytes int `json:"rx_bytes"`
59 ReceivedDropped int `json:"rx_dropped"`
60 ReceivedErrors int `json:"rx_errors"`
61 ReceivedPackets int `json:"rx_packets"`
62 ReceivedCrypts int `json:"rx_crypts"`
63 ReceivedFragments int `json:"rx_frags"`
64 ReceivedNetworkIDs int `json:"rx_nwids"`
65 TransmittedBytes int `json:"tx_bytes"`
66 TransmittedDropped int `json:"tx_dropped"`
67 TransmittedErrors int `json:"tx_errors"`
68 TransmittedPackets int `json:"tx_packets"`
69 TransmitPower int `json:"tx_power"`
70 TransmitRetries int `json:"tx_retries"`
71}
72
73// TODO: Convert time to time.Time
74type IncomingMessage struct {
75 Alarms []*AlarmMessage `json:"alarm"`
76 ConfigVersion string `json:"cfgversion"`
77 Default bool `json:"default"`
78 GuestToken string `json:"guest_token"`
79 Hostname string `json:"hostname"`
80 InformURL string `json:"inform_url"`
81 IP string `json:"ip"`
82 Isolated bool `json:"isolated"`
83 LocalVersion string `json:"localversion"`
84 Locating bool `json:"locating"`
85 MacAddress string `json:"mac"`
86 IsMfi string `json:"mfi"` // boolean as string
87 Model string `json:"model"`
88 ModelDisplay string `json:"model_display"`
89 PortVersion string `json:"portversion"`
90 Version string `json:"version"`
91 Serial string `json:"serial"`
92 Time int `json:"time"`
93 Trackable string `json:"trackable"` // boolean as string
94 Uplink string `json:"uplink"`
95 Uptime int `json:"uptime"`
96 Interfaces []*InterfaceMessage `json:"if_table"`
97 Radios []*RadioMessage `json:"radio_table"`
98 AccessPoints []*AccessPointMessage `json:"vap_table"`
99}
diff --git a/inform/tx_messages.go b/inform/tx_messages.go
new file mode 100644
index 0000000..5efddd6
--- /dev/null
+++ b/inform/tx_messages.go
@@ -0,0 +1,28 @@
1package inform
2
3type AdminMetadata struct {
4 Id string `json:"_id"`
5 Language string `json:"lang"`
6 Username string `json:"name"`
7 Password string `json:"x_password"`
8}
9
10// TODO: Convert string time to time.Time
11type CommandMessage struct {
12 Metadata *AdminMetadata `json:"_admin"`
13 Id string `json:"_id"`
14 Type string `json:"_type"` // cmd
15 Command string `json:"cmd"` // mfi-output
16 DateTime string `json:"datetime"` // 2016-07-28T01:17:55Z
17 DeviceId string `json:"device_id"`
18 MacAddress string `json:"mac"`
19 Model string `json:"model"`
20 OffVoltage int `json:"off_volt"`
21 Port int `json:"port"`
22 MessageId string `json:"sId"` // ??
23 ServerTime string `json:"server_time_in_utc"`
24 Time string `json:"time"`
25 Timer int `json:"timer"`
26 Value int `json:"val"`
27 Voltage int `json:"volt"`
28}