├── .github └── workflows │ └── go.yml ├── .gitignore ├── LICENSE ├── README.md ├── ccm └── ccm.go ├── certmanager.go ├── certmanagerfile.go ├── demo └── main.go ├── discover └── discover.go ├── examples └── basic │ └── basic-main.go ├── fabric.go ├── flows.go ├── go.mod ├── go.sum ├── matter-cert.go ├── matter.go ├── mattertlv ├── tlv_test.go ├── tlvdec.go └── tlvenc.go ├── messages.go ├── notes.txt ├── onboarding_payload ├── manual.go └── qr.go ├── securechannel.go ├── sigma.go ├── spake2p.go ├── symbols ├── README.md ├── gen │ └── process.go ├── info.go └── info.json └── util.go /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a golang project 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go 3 | 4 | name: Go 5 | 6 | on: 7 | push: 8 | branches: [ "main" ] 9 | pull_request: 10 | branches: [ "main" ] 11 | 12 | jobs: 13 | 14 | build: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v3 18 | 19 | - name: Set up Go 20 | uses: actions/setup-go@v4 21 | with: 22 | go-version: '1.21' 23 | 24 | - name: Build 25 | run: go build demo/main.go 26 | 27 | - name: Test 28 | run: go test -v ./... 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pem 2 | tmp 3 | main 4 | symbols/xml 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2023, Tomas Petrilak 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gomat 2 | Simple matter protocol library 3 | 4 | [![Go Reference](https://pkg.go.dev/badge/github.com/tom-code/gomat.svg)](https://pkg.go.dev/github.com/tom-code/gomat) 5 | ![go build](https://github.com/tom-code/gomat/actions/workflows/go.yml/badge.svg) 6 | [![Go Report Card](https://goreportcard.com/badge/github.com/tom-code/gomat)](https://goreportcard.com/report/github.com/tom-code/gomat) 7 | 8 | ### goal of project 9 | The goal is to create golang library and supporting tools to access matter devices. 10 | 11 | ### status of project 12 | - it can 13 | - commission devices 14 | - send commands to devices 15 | - read attributes from devices 16 | - subscribe and receive events 17 | - decode onboarding info (qr text, manual pair code) 18 | - discover commissionable devices 19 | - discover commissioned devices 20 | - open commissioning window 21 | 22 | 23 | #### tested devices 24 | - tested against virtual devices which are part of reference implementation https://github.com/project-chip/connectedhomeip 25 | - tested with yeelight cube 26 | 27 | ### general info 28 | - it is best to understand matter to use this, but here is most important info: 29 | - device access is managed using certificates 30 | - easiest way how to talk to device is to have signed certificate of device admin user (alternative is setup ACLs and use non-admin user) 31 | - certificates are signed by CA 32 | - during commissioning procedure root CA certificate is pushed to device together with id of device admin user 33 | - root CA certificate is something you need to create once and store. loosing CA keys usually means that you will have to commission devices again 34 | - to talk to device you have to commission it first 35 | - to commission device you usually need its pin/passcode and device be in state open for commisioning 36 | - device gets into commisioning window open state often by "factory reset" 37 | - when device is commissioned - connected to some fabric, it can be commissionined into other fabrics using api, where existing admin user sets device to be open for additional commissioning. During that device can be connected to additional fabric(s) - additional root CA installed and additional admin user configured 38 | 39 | ### how to use test application 40 | - compile 41 | ``` 42 | git clone git@github.com:tom-code/gomat.git 43 | cd gomat 44 | go build -o gomat demo/main.go 45 | ``` 46 | 47 | - create directory to hold keys and certificates `mkdir pem` 48 | - generate CA key and certificate using `./gomat ca-bootstrap` 49 | - generate controller key and certificate using `./gomat ca-createuser 100` 50 | - 100 is example node-id of controller 51 | - find device IP 52 | - discover command can be used to discover matter devices and their ip address `./gomat discover commissionable -d` 53 | - find device commissioning passcode/pin 54 | - device may show it 55 | - it can be extracted from QR code. use decode-qr to extract passcode from text representation of QR code `./gomat decode-qr MT:-24J0AFN00SIQ663000` 56 | - it can be extracted from manual pairing code. use command decode-mc to extract passcode from manual pairing code `./gomat decode-mc 35792000079` 57 | - perform commissioning of device. This authenticates using passcode, uploads CA certificate to device, signs and uploads device's own certificate and sets admin user id. 58 | - required for commisioning: 59 | - ip address of device 60 | - device commissioning passcode/pin 61 | - ca key and certificate 62 | - controller node key and certificate 63 | - example: `./gomat commission --ip 192.168.5.178 --pin 123456 --controller-id 100 --device-id 500` 64 | - light on! 65 | `./gomat cmd on --ip 192.168.5.178 --controller-id 100 --device-id 500` 66 | - set color hue=150 saturation=200 transition_time=10 67 | `./gomat cmd color --ip 192.168.5.220 --controller-id 100 --device-id 500 150 200 10` 68 | 69 | 70 | ### how to use api 71 | #### Example applications 72 | - [Simple Example application](examples/basic/basic-main.go) - bootstrap ca, commission, send commands to device 73 | - [Demo application](demo/main.go) 74 | 75 | #### commission device using api 76 | create ca with root certificate, create admin user, then commission device: 77 | ``` 78 | package main 79 | 80 | import ( 81 | "net" 82 | 83 | "github.com/tom-code/gomat" 84 | ) 85 | 86 | 87 | func main() { 88 | var fabric_id uint64 = 0x100 89 | var admin_user uint64 = 5 90 | var device_id uint64 = 10 91 | device_ip := "192.168.5.178" 92 | pin := 123456 93 | 94 | cm := gomat.NewFileCertManager(fabric_id) 95 | cm.BootstrapCa() 96 | cm.Load() 97 | cm.CreateUser(admin_user) 98 | fabric := gomat.NewFabric(fabric_id, cm) 99 | gomat.Commission(fabric, net.ParseIP(device_ip), pin, admin_user, device_id) 100 | } 101 | ``` 102 | 103 | #### send ON command to commissioned device using api 104 | ``` 105 | package main 106 | 107 | import ( 108 | "net" 109 | 110 | "github.com/tom-code/gomat" 111 | ) 112 | 113 | 114 | func main() { 115 | var fabric_id uint64 = 0x100 116 | var admin_user uint64 = 5 117 | var device_id uint64 = 10 118 | device_ip := "192.168.5.178" 119 | 120 | cm := gomat.NewFileCertManager(fabric_id) 121 | cm.Load() 122 | fabric := gomat.NewFabric(fabric_id, cm) 123 | 124 | secure_channel, err := gomat.StartSecureChannel(net.ParseIP(device_ip), 5540, 55555) 125 | if err != nil { 126 | panic(err) 127 | } 128 | defer secure_channel.Close() 129 | secure_channel, err = gomat.SigmaExchange(fabric, admin_user, device_id, secure_channel) 130 | if err != nil { 131 | panic(err) 132 | } 133 | 134 | on_command := gomat.EncodeInvokeCommand(1, // endpoint 135 | 6, // api cluster (on/off) 136 | 1, // on command 137 | []byte{}, // no extra data 138 | ) 139 | secure_channel.Send(on_command) 140 | resp, err := secure_channel.Receive() 141 | if err != nil { 142 | panic(err) 143 | } 144 | resp.Tlv.Dump(0) 145 | } 146 | ``` 147 | 148 | #### discover IP address of previously commissioned device using api 149 | Device exposes its info using mdns under identifier [compressed-fabric-id]-[device-id]. 150 | For this reason to discover commissioned device fabric info is required. 151 | ``` 152 | package main 153 | 154 | import ( 155 | "encoding/hex" 156 | "fmt" 157 | "strings" 158 | 159 | "github.com/tom-code/gomat" 160 | "github.com/tom-code/gomat/discover" 161 | ) 162 | 163 | 164 | 165 | func main() { 166 | var fabric_id uint64 = 0x100 167 | var device_id uint64 = 10 168 | 169 | 170 | cm := gomat.NewFileCertManager(fabric_id) 171 | cm.Load() 172 | fabric := gomat.NewFabric(fabric_id, cm) 173 | 174 | identifier := fmt.Sprintf("%s-%016X", hex.EncodeToString(fabric.CompressedFabric()), device_id) 175 | identifier = strings.ToUpper(identifier) 176 | identifier = identifier + "._matter._tcp.local." 177 | fmt.Printf("%s\n", identifier) 178 | devices := discover.DiscoverComissioned("", true, identifier) 179 | for _, d := range devices { 180 | fmt.Printf("host:%s ip:%v\n", d.Host, d.Addrs) 181 | } 182 | } 183 | ``` 184 | 185 | #### extract pairing passcode from QR code and manual pairing code 186 | Following example shows how to extract passcode from textual representation of QR code or from manual pairing code. 187 | Manual pairing code can have dash characters at any position(they are discarded) 188 | ``` 189 | package main 190 | 191 | import ( 192 | "fmt" 193 | 194 | "github.com/tom-code/gomat/onboarding_payload" 195 | ) 196 | 197 | 198 | func main() { 199 | setup_qr_code := "MT:-24J0AFN00SIQ663000" 200 | qr_decoded := onboarding_payload.DecodeQrText(setup_qr_code) 201 | fmt.Printf("passcode: %d\n", qr_decoded.Passcode) 202 | 203 | 204 | manual_pair_code := "357-920-000-79" 205 | code_decoded := onboarding_payload.DecodeManualPairingCode(manual_pair_code) 206 | fmt.Printf("passcode: %d\n", code_decoded.Passcode) 207 | } 208 | 209 | ``` 210 | 211 | #### Set color of light to specific hue color 212 | ``` 213 | package main 214 | 215 | import ( 216 | "fmt" 217 | "net" 218 | 219 | "github.com/tom-code/gomat" 220 | "github.com/tom-code/gomat/mattertlv" 221 | ) 222 | 223 | 224 | func main() { 225 | var fabric_id uint64 = 0x100 226 | var admin_user uint64 = 5 227 | var device_id uint64 = 10 228 | device_ip := "192.168.5.178" 229 | 230 | cm := gomat.NewFileCertManager(fabric_id) 231 | cm.Load() 232 | fabric := gomat.NewFabric(fabric_id, cm) 233 | 234 | 235 | secure_channel, err := gomat.StartSecureChannel(net.ParseIP(device_ip), 5540, 55555) 236 | if err != nil { 237 | panic(err) 238 | } 239 | defer secure_channel.Close() 240 | secure_channel, err = gomat.SigmaExchange(fabric, admin_user, device_id, secure_channel) 241 | if err != nil { 242 | panic(err) 243 | } 244 | 245 | var tlv mattertlv.TLVBuffer 246 | tlv.WriteUInt8(0, byte(hue)) // hue 247 | tlv.WriteUInt8(1, byte(saturation)) // saturation 248 | tlv.WriteUInt8(2, byte(time)) // time 249 | to_send := gomat.EncodeInvokeCommand(1, 0x300, 6, tlv.Bytes()) 250 | secure_channel.Send(to_send) 251 | 252 | resp, err := secure_channel.Receive() 253 | if err != nil { 254 | panic(err) 255 | } 256 | status, err := resp.Tlv.GetIntRec([]int{1,0,1,1,0}) 257 | if err != nil { 258 | panic(err) 259 | } 260 | fmt.Printf("result status: %d\n", status) 261 | } 262 | ``` 263 | 264 | 265 | #### certificate manager 266 | NewFabric function accepts certificate manager object as input parameter. Certificate manager must implement interface CertificateManager and user can supply own implementation. Supplied CertManager created by NewFileCertManager is very simple and stores all data in .pem files under pem directory. 267 | 268 | #### notes 269 | consider move to https://pkg.go.dev/filippo.io/nistec 270 | -------------------------------------------------------------------------------- /ccm/ccm.go: -------------------------------------------------------------------------------- 1 | // Package ccm implements a CCM, Counter with CBC-MAC 2 | // as per RFC 3610. 3 | // 4 | // See https://tools.ietf.org/html/rfc3610 5 | // original location: https://github.com/qwerty-iot/dtls/blob/a3300364a283fcb490d28a93d7fcfa7ba437fbbe/ccm/ccm.go 6 | package ccm 7 | 8 | import ( 9 | "crypto/cipher" 10 | "crypto/subtle" 11 | "encoding/binary" 12 | "errors" 13 | "fmt" 14 | "math" 15 | ) 16 | 17 | // ccm represents a Counter with CBC-MAC with a specific key. 18 | type ccm struct { 19 | b cipher.Block 20 | M uint8 21 | L uint8 22 | } 23 | 24 | const ccmBlockSize = 16 25 | 26 | // CCM is a block cipher in Counter with CBC-MAC mode. 27 | // Providing authenticated encryption with associated data via the cipher.AEAD interface. 28 | type CCM interface { 29 | cipher.AEAD 30 | // MaxLength returns the maxium length of plaintext in calls to Seal. 31 | // The maximum length of ciphertext in calls to Open is MaxLength()+Overhead(). 32 | // The maximum length is related to CCM's `L` parameter (15-noncesize) and 33 | // is 1<<(8*L) - 1 (but also limited by the maxium size of an int). 34 | MaxLength() int 35 | } 36 | 37 | // NewCCM returns the given 128-bit block cipher wrapped in CCM. 38 | // The tagsize must be an even integer between 4 and 16 inclusive 39 | // and is used as CCM's `M` parameter. 40 | // The noncesize must be an integer between 7 and 13 inclusive, 41 | // 15-noncesize is used as CCM's `L` parameter. 42 | func NewCCM(b cipher.Block, tagsize, noncesize int) (CCM, error) { 43 | if b.BlockSize() != ccmBlockSize { 44 | return nil, errors.New("ccm: NewCCM requires 128-bit block cipher") 45 | } 46 | if tagsize < 4 || tagsize > 16 || tagsize&1 != 0 { 47 | return nil, errors.New("ccm: tagsize must be 4, 6, 8, 10, 12, 14, or 16") 48 | } 49 | lensize := 15 - noncesize 50 | if lensize < 2 || lensize > 8 { 51 | return nil, errors.New("ccm: invalid noncesize") 52 | } 53 | c := &ccm{b: b, M: uint8(tagsize), L: uint8(lensize)} 54 | return c, nil 55 | } 56 | 57 | func (c *ccm) NonceSize() int { return 15 - int(c.L) } 58 | func (c *ccm) Overhead() int { return int(c.M) } 59 | func (c *ccm) MaxLength() int { return maxlen(c.L, c.Overhead()) } 60 | 61 | func maxlen(L uint8, tagsize int) int { 62 | max := (uint64(1) << (8 * L)) - 1 63 | if m64 := uint64(math.MaxInt64) - uint64(tagsize); L > 8 || max > m64 { 64 | max = m64 // The maximum lentgh on a 64bit arch 65 | } 66 | if max != uint64(int(max)) { 67 | return math.MaxInt32 - tagsize // We have only 32bit int's 68 | } 69 | return int(max) 70 | } 71 | 72 | // MaxNonceLength returns the maximum nonce length for a given plaintext length. 73 | // A return value <= 0 indicates that plaintext length is too large for 74 | // any nonce length. 75 | func MaxNonceLength(pdatalen int) int { 76 | const tagsize = 16 77 | for L := 2; L <= 8; L++ { 78 | if maxlen(uint8(L), tagsize) >= pdatalen { 79 | return 15 - L 80 | } 81 | } 82 | return 0 83 | } 84 | 85 | func (c *ccm) cbcRound(mac, data []byte) { 86 | for i := 0; i < ccmBlockSize; i++ { 87 | mac[i] ^= data[i] 88 | } 89 | c.b.Encrypt(mac, mac) 90 | } 91 | 92 | func (c *ccm) cbcData(mac, data []byte) { 93 | for len(data) >= ccmBlockSize { 94 | c.cbcRound(mac, data[:ccmBlockSize]) 95 | data = data[ccmBlockSize:] 96 | } 97 | if len(data) > 0 { 98 | var block [ccmBlockSize]byte 99 | copy(block[:], data) 100 | c.cbcRound(mac, block[:]) 101 | } 102 | } 103 | 104 | func (c *ccm) tag(nonce, plaintext, adata []byte) ([]byte, error) { 105 | var mac [ccmBlockSize]byte 106 | 107 | if len(adata) > 0 { 108 | mac[0] |= 1 << 6 109 | } 110 | mac[0] |= (c.M - 2) << 2 111 | mac[0] |= c.L - 1 112 | if len(nonce) != c.NonceSize() { 113 | return nil, errors.New("ccm: Invalid nonce size") 114 | } 115 | if len(plaintext) > c.MaxLength() { 116 | return nil, errors.New("ccm: plaintext too large") 117 | } 118 | binary.BigEndian.PutUint64(mac[ccmBlockSize-8:], uint64(len(plaintext))) 119 | copy(mac[1:ccmBlockSize-c.L], nonce) 120 | c.b.Encrypt(mac[:], mac[:]) 121 | 122 | var block [ccmBlockSize]byte 123 | if n := uint64(len(adata)); n > 0 { 124 | // First adata block includes adata length 125 | i := 2 126 | if n <= 0xfeff { 127 | binary.BigEndian.PutUint16(block[:i], uint16(n)) 128 | } else { 129 | block[0] = 0xfe 130 | block[1] = 0xff 131 | if n < uint64(1<<32) { 132 | i = 2 + 4 133 | binary.BigEndian.PutUint32(block[2:i], uint32(n)) 134 | } else { 135 | i = 2 + 8 136 | binary.BigEndian.PutUint64(block[2:i], uint64(n)) 137 | } 138 | } 139 | i = copy(block[i:], adata) 140 | c.cbcRound(mac[:], block[:]) 141 | c.cbcData(mac[:], adata[i:]) 142 | } 143 | 144 | if len(plaintext) > 0 { 145 | c.cbcData(mac[:], plaintext) 146 | } 147 | 148 | return mac[:c.M], nil 149 | } 150 | 151 | // sliceForAppend takes a slice and a requested number of bytes. It returns a 152 | // slice with the contents of the given slice followed by that many bytes and a 153 | // second slice that aliases into it and contains only the extra bytes. If the 154 | // original slice has sufficient capacity then no allocation is performed. 155 | // From crypto/cipher/gcm.go 156 | func sliceForAppend(in []byte, n int) (head, tail []byte) { 157 | if total := len(in) + n; cap(in) >= total { 158 | head = in[:total] 159 | } else { 160 | head = make([]byte, total) 161 | copy(head, in) 162 | } 163 | tail = head[len(in):] 164 | return 165 | } 166 | 167 | // Seal encrypts and authenticates plaintext, authenticates the 168 | // additional data and appends the result to dst, returning the updated 169 | // slice. The nonce must be NonceSize() bytes long and unique for all 170 | // time, for a given key. 171 | // The plaintext must be no longer than MaxLength() bytes long. 172 | // 173 | // The plaintext and dst may alias exactly or not at all. 174 | func (c *ccm) Seal(dst, nonce, plaintext, adata []byte) []byte { 175 | tag, err := c.tag(nonce, plaintext, adata) 176 | if err != nil { 177 | // The cipher.AEAD interface doesn't allow for an error return. 178 | panic(err) 179 | } 180 | 181 | var iv, s0 [ccmBlockSize]byte 182 | iv[0] = c.L - 1 183 | copy(iv[1:ccmBlockSize-c.L], nonce) 184 | c.b.Encrypt(s0[:], iv[:]) 185 | for i := 0; i < int(c.M); i++ { 186 | tag[i] ^= s0[i] 187 | } 188 | iv[len(iv)-1] |= 1 189 | stream := cipher.NewCTR(c.b, iv[:]) 190 | ret, out := sliceForAppend(dst, len(plaintext)+int(c.M)) 191 | stream.XORKeyStream(out, plaintext) 192 | copy(out[len(plaintext):], tag) 193 | return ret 194 | } 195 | 196 | var errOpen = errors.New("ccm: message authentication failed") 197 | 198 | func (c *ccm) Open(dst, nonce, ciphertext, adata []byte) ([]byte, error) { 199 | if len(ciphertext) < int(c.M) { 200 | return nil, errors.New("ccm: ciphertext too short") 201 | } 202 | if len(ciphertext) > c.MaxLength()+c.Overhead() { 203 | return nil, errors.New("ccm: ciphertext too long") 204 | } 205 | 206 | var tag = make([]byte, int(c.M), int(c.M)) 207 | copy(tag, ciphertext[len(ciphertext)-int(c.M):]) 208 | ciphertextWithoutTag := ciphertext[:len(ciphertext)-int(c.M)] 209 | 210 | var iv, s0 [ccmBlockSize]byte 211 | iv[0] = c.L - 1 212 | copy(iv[1:ccmBlockSize-c.L], nonce) 213 | c.b.Encrypt(s0[:], iv[:]) 214 | for i := 0; i < int(c.M); i++ { 215 | tag[i] ^= s0[i] 216 | } 217 | iv[len(iv)-1] |= 1 218 | stream := cipher.NewCTR(c.b, iv[:]) 219 | 220 | // Cannot decrypt directly to dst since we're not supposed to 221 | // reveal the plaintext to the caller if authentication fails. 222 | plaintext := make([]byte, len(ciphertextWithoutTag)) 223 | stream.XORKeyStream(plaintext, ciphertextWithoutTag) 224 | expectedTag, err := c.tag(nonce, plaintext, adata) 225 | if err != nil { 226 | return nil, err 227 | } 228 | 229 | if subtle.ConstantTimeCompare(tag, expectedTag) != 1 { 230 | return nil, errors.New(fmt.Sprintf("ccm: t[%X] != et[%X]", tag, expectedTag)) 231 | //return nil, errOpen 232 | } 233 | return append(dst, plaintext...), nil 234 | } 235 | -------------------------------------------------------------------------------- /certmanager.go: -------------------------------------------------------------------------------- 1 | package gomat 2 | 3 | import ( 4 | "crypto/ecdsa" 5 | "crypto/x509" 6 | ) 7 | 8 | // matter certificate manager interface 9 | // all generated certificates must be compatible with matter 10 | // - this means that after they are reencoded to matter format and back their signature must match 11 | type CertificateManager interface { 12 | GetCaPublicKey() ecdsa.PublicKey 13 | GetCaCertificate() *x509.Certificate 14 | 15 | // CreateUser creates keys and certificate for node with specific id 16 | // it must be possible to later retrieve node keys using GetPrivkey and certificate using GetCertificate 17 | CreateUser(node_id uint64) error 18 | 19 | // retrieve certificate of specified node (previously created by CreateUser) 20 | GetCertificate(id uint64) (*x509.Certificate, error) 21 | 22 | // retrieve key of specified node (previously created by CreateUser) 23 | GetPrivkey(id uint64) (*ecdsa.PrivateKey, error) 24 | 25 | // create and sign certificate using local CA keys 26 | SignCertificate(user_pubkey *ecdsa.PublicKey, node_id uint64) (*x509.Certificate, error) 27 | } 28 | -------------------------------------------------------------------------------- /certmanagerfile.go: -------------------------------------------------------------------------------- 1 | package gomat 2 | 3 | // requirements: 4 | // - create, manage, own CA certificate and private key 5 | // - sign device certificate (no need to store?) 6 | // - create/sign/own controller certificate and private key 7 | // - this is key of user accessing devices 8 | // - it can be admin or moreregular user 9 | // - we may want to support multiple of them 10 | 11 | import ( 12 | "crypto/ecdsa" 13 | "crypto/elliptic" 14 | "crypto/rand" 15 | "crypto/sha1" 16 | "crypto/x509" 17 | "crypto/x509/pkix" 18 | "encoding/asn1" 19 | "encoding/hex" 20 | "encoding/pem" 21 | "fmt" 22 | "log" 23 | "math/big" 24 | "os" 25 | "time" 26 | ) 27 | 28 | func certIdToName(id uint64) string { 29 | return fmt.Sprintf("%d", id) 30 | } 31 | 32 | // PEM file backed certiticate manager 33 | type FileCertManager struct { 34 | fabric uint64 35 | ca_certificate *x509.Certificate 36 | ca_private_key *ecdsa.PrivateKey 37 | } 38 | 39 | func NewFileCertManager(fabric uint64) *FileCertManager { 40 | return &FileCertManager{ 41 | fabric: fabric, 42 | } 43 | } 44 | func (cm *FileCertManager) GetCaPublicKey() ecdsa.PublicKey { 45 | return cm.ca_private_key.PublicKey 46 | } 47 | func (cm *FileCertManager) GetCaCertificate() *x509.Certificate { 48 | return cm.ca_certificate 49 | } 50 | 51 | // Load initializes CA. It loads required state from files. 52 | func (cm *FileCertManager) Load() error { 53 | _, err := os.Stat("pem/ca-private.pem") 54 | if err != nil { 55 | log.Printf("can't open CA key. continue anyway %s\n", err.Error()) 56 | return nil 57 | } 58 | anykey, err := loadPrivKey("pem/ca-private.pem") 59 | if err != nil { 60 | return err 61 | } 62 | cm.ca_private_key = anykey.(*ecdsa.PrivateKey) 63 | cm.ca_certificate, err = loadCertificate("pem/ca-cert.pem") 64 | return err 65 | } 66 | 67 | func (cm *FileCertManager) GetCertificate(id uint64) (*x509.Certificate, error) { 68 | return loadCertificate("pem/" + certIdToName(id) + "-cert.pem") 69 | } 70 | func (cm *FileCertManager) GetPrivkey(id uint64) (*ecdsa.PrivateKey, error) { 71 | pk, err := loadPrivKey("pem/" + certIdToName(id) + "-private.pem") 72 | if err != nil { 73 | return nil, err 74 | } 75 | return pk.(*ecdsa.PrivateKey), nil 76 | } 77 | 78 | func (cm *FileCertManager) CreateUser(node_id uint64) error { 79 | id := fmt.Sprintf("%d", node_id) 80 | privkey, err := generateAndStoreKeyEcdsa("pem/" + id) 81 | if err != nil { 82 | return err 83 | } 84 | cm.SignCertificate(&privkey.PublicKey, node_id) 85 | return nil 86 | } 87 | func (cm *FileCertManager) SignCertificate(user_pubkey *ecdsa.PublicKey, node_id uint64) (*x509.Certificate, error) { 88 | 89 | public_key_auth := elliptic.Marshal(elliptic.P256(), cm.ca_private_key.PublicKey.X, cm.ca_private_key.PublicKey.Y) 90 | sh := sha1.New() 91 | sh.Write(public_key_auth) 92 | sha_auth := sh.Sum(nil) 93 | 94 | public_key_subj := user_pubkey 95 | public_key_subj2 := elliptic.Marshal(elliptic.P256(), public_key_subj.X, public_key_subj.Y) 96 | shp := sha1.New() 97 | shp.Write(public_key_subj2) 98 | sha_subj := shp.Sum(nil) 99 | 100 | subj := pkix.Name{} 101 | 102 | node_id_string := fmt.Sprintf("%016X", node_id) 103 | valname, err := asn1.MarshalWithParams(node_id_string, "utf8") 104 | if err != nil { 105 | return nil, err 106 | } 107 | fabric_string := fmt.Sprintf("%016X", cm.fabric) 108 | valname_fabric, err := asn1.MarshalWithParams(fabric_string, "utf8") 109 | if err != nil { 110 | return nil, err 111 | } 112 | 113 | subj.ExtraNames = []pkix.AttributeTypeAndValue{ 114 | { 115 | Type: asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 37244, 1, 1}, 116 | Value: asn1.RawValue{FullBytes: valname}, 117 | }, 118 | { 119 | Type: asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 37244, 1, 5}, 120 | Value: asn1.RawValue{FullBytes: valname_fabric}, 121 | }, 122 | } 123 | 124 | var template x509.Certificate 125 | template.Version = 3 126 | template.SignatureAlgorithm = x509.ECDSAWithSHA256 127 | template.NotBefore = time.Now() 128 | template.NotAfter = time.Now().AddDate(1, 0, 0) 129 | template.Subject = subj 130 | template.IsCA = false 131 | template.SerialNumber = big.NewInt(10001) 132 | 133 | // order of extensions Matters! 134 | // this is why some standard parameters are in this list - to enforce right order 135 | extkeyusa, _ := hex.DecodeString("301406082B0601050507030206082B06010505070301") 136 | template.ExtraExtensions = []pkix.Extension{ 137 | { 138 | Id: asn1.ObjectIdentifier{2, 5, 29, 19}, // basic constraints 139 | Critical: true, 140 | Value: []byte{0x30, 0x03, 0x01, 0x01, 0xff}, 141 | }, 142 | { 143 | Id: asn1.ObjectIdentifier{2, 5, 29, 15}, // keyUsage 144 | Critical: true, 145 | Value: []byte{3, 2, 7, 0x80}, 146 | }, 147 | { 148 | Id: asn1.ObjectIdentifier{2, 5, 29, 37}, // ExtkeyUsage 149 | Critical: true, 150 | Value: extkeyusa, 151 | }, 152 | { 153 | Id: asn1.ObjectIdentifier{2, 5, 29, 14}, //subjectKeyId 154 | Critical: false, 155 | Value: append([]byte{0x04, 0x14}, sha_subj...), 156 | }, 157 | { 158 | Id: asn1.ObjectIdentifier{2, 5, 29, 35}, // authorityKeyId 159 | Critical: false, 160 | Value: append([]byte{0x30, 0x16, 0x80, 0x14}, sha_auth...), 161 | }, 162 | } 163 | 164 | cert_bytes, err := x509.CreateCertificate(rand.Reader, &template, cm.ca_certificate, public_key_subj, cm.ca_private_key) 165 | if err != nil { 166 | return nil, err 167 | } 168 | out_parsed, err := x509.ParseCertificate(cert_bytes) 169 | if err != nil { 170 | return nil, err 171 | } 172 | storeCertificate("pem/"+certIdToName(node_id), cert_bytes) 173 | log.Printf("Signed certificate for node 0x%x\n", node_id) 174 | return out_parsed, nil 175 | } 176 | 177 | // BootstrapCa initializes CA - creates CA keys and certificate 178 | func (cm *FileCertManager) BootstrapCa() error { 179 | _, err := os.Stat("pem/ca-private.pem") 180 | if err == nil { 181 | log.Printf("CA private key already present - skipping bootstrap\n") 182 | return nil 183 | } 184 | 185 | _, err = generateAndStoreKeyEcdsa("pem/ca") 186 | if err != nil { 187 | return err 188 | } 189 | err = cm.createCaCert() 190 | return err 191 | } 192 | 193 | func (cm *FileCertManager) createCaCert() error { 194 | pubany, err := loadPublicKey("pem/ca-public.pem") 195 | if err != nil { 196 | return err 197 | } 198 | pub := pubany.(*ecdsa.PublicKey) 199 | priv_ca, err := loadPrivKey("pem/ca-private.pem") 200 | if err != nil { 201 | return err 202 | } 203 | 204 | public_key := elliptic.Marshal(elliptic.P256(), pub.X, pub.Y) 205 | sh := sha1.New() 206 | sh.Write(public_key) 207 | sha := sh.Sum(nil) 208 | 209 | subj := pkix.Name{} 210 | 211 | valname, err := asn1.MarshalWithParams("0000000000000001", "utf8") 212 | if err != nil { 213 | return err 214 | } 215 | subj.ExtraNames = []pkix.AttributeTypeAndValue{ 216 | { 217 | Type: asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 37244, 1, 4}, 218 | Value: asn1.RawValue{FullBytes: valname}, 219 | }, 220 | } 221 | var template x509.Certificate 222 | template.Version = 3 223 | template.SignatureAlgorithm = x509.ECDSAWithSHA256 224 | template.NotBefore = time.Now() 225 | template.NotAfter = time.Now().AddDate(1, 0, 0) 226 | template.Subject = subj 227 | template.IsCA = true 228 | template.SerialNumber = big.NewInt(10000) 229 | template.Issuer = subj 230 | 231 | // extensions must be in matter correct order 232 | // for this reason they must appear in this list 233 | template.ExtraExtensions = []pkix.Extension{ 234 | { 235 | Id: asn1.ObjectIdentifier{2, 5, 29, 19}, // basic constraints 236 | Critical: true, 237 | Value: []byte{0x30, 0x03, 0x01, 0x01, 0xff}, 238 | }, 239 | { 240 | Id: asn1.ObjectIdentifier{2, 5, 29, 15}, // keyUsage 241 | Critical: true, 242 | Value: []byte{3, 2, 1, 6}, 243 | }, 244 | { 245 | Id: asn1.ObjectIdentifier{2, 5, 29, 14}, //subjectKeyId 246 | Critical: false, 247 | Value: append([]byte{0x04, 0x14}, sha...), 248 | }, 249 | { 250 | Id: asn1.ObjectIdentifier{2, 5, 29, 35}, // authorityKeyId 251 | Critical: false, 252 | Value: append([]byte{0x30, 0x16, 0x80, 0x14}, sha...), 253 | }, 254 | } 255 | 256 | cert_bytes, err := x509.CreateCertificate(rand.Reader, &template, &template, pub, priv_ca) 257 | if err != nil { 258 | return err 259 | } 260 | storeCertificate("pem/ca", cert_bytes) 261 | log.Println("CA certificate was created") 262 | return nil 263 | } 264 | 265 | func generateAndStoreKeyEcdsa(name string) (*ecdsa.PrivateKey, error) { 266 | priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) 267 | if err != nil { 268 | return nil, err 269 | } 270 | privEC, err := x509.MarshalECPrivateKey(priv) 271 | if err != nil { 272 | return nil, err 273 | } 274 | privBlock := pem.Block{ 275 | Type: "EC PRIVATE KEY", 276 | Bytes: privEC, 277 | } 278 | err = os.WriteFile(name+"-private.pem", pem.EncodeToMemory(&privBlock), 0600) 279 | if err != nil { 280 | return nil, err 281 | } 282 | 283 | pubPKIX, err := x509.MarshalPKIXPublicKey(&priv.PublicKey) 284 | if err != nil { 285 | return nil, err 286 | } 287 | pubBlock := pem.Block{ 288 | Type: "PUBLIC KEY", 289 | Bytes: pubPKIX, 290 | } 291 | err = os.WriteFile(name+"-public.pem", pem.EncodeToMemory(&pubBlock), 0600) 292 | if err != nil { 293 | return nil, err 294 | } 295 | return priv, nil 296 | } 297 | 298 | func loadPrivKey(file string) (any, error) { 299 | data, err := os.ReadFile(file) 300 | if err != nil { 301 | return nil, err 302 | } 303 | pem_block, _ := pem.Decode(data) 304 | key, err := x509.ParseECPrivateKey(pem_block.Bytes) 305 | if err != nil { 306 | return nil, err 307 | } 308 | return key, nil 309 | } 310 | func loadPublicKey(file string) (any, error) { 311 | data, err := os.ReadFile(file) 312 | if err != nil { 313 | return nil, err 314 | } 315 | pem_block, _ := pem.Decode(data) 316 | key, err := x509.ParsePKIXPublicKey(pem_block.Bytes) 317 | if err != nil { 318 | return nil, err 319 | } 320 | return key, nil 321 | } 322 | 323 | func storeCertificate(name string, cert_bytes []byte) { 324 | certBlock := pem.Block{ 325 | Type: "CERTIFICATE", 326 | Bytes: cert_bytes, 327 | } 328 | err := os.WriteFile(name+"-cert.pem", pem.EncodeToMemory(&certBlock), 0600) 329 | if err != nil { 330 | panic(err) 331 | } 332 | } 333 | 334 | func loadCertificate(file string) (*x509.Certificate, error) { 335 | data, err := os.ReadFile(file) 336 | if err != nil { 337 | return nil, err 338 | } 339 | pem_block, _ := pem.Decode(data) 340 | cert, err := x509.ParseCertificate(pem_block.Bytes) 341 | if err != nil { 342 | return nil, err 343 | } 344 | return cert, nil 345 | } 346 | -------------------------------------------------------------------------------- /demo/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "encoding/hex" 6 | "fmt" 7 | "log" 8 | "math/rand" 9 | "net" 10 | "strconv" 11 | "strings" 12 | 13 | "github.com/spf13/cobra" 14 | "github.com/tom-code/gomat" 15 | "github.com/tom-code/gomat/discover" 16 | "github.com/tom-code/gomat/mattertlv" 17 | "github.com/tom-code/gomat/onboarding_payload" 18 | "github.com/tom-code/gomat/symbols" 19 | ) 20 | 21 | func filter_devices(devices []discover.DiscoveredDevice, qr onboarding_payload.QrContent) []discover.DiscoveredDevice { 22 | out := []discover.DiscoveredDevice{} 23 | for _, device := range devices { 24 | if device.D != fmt.Sprintf("%d", qr.Discriminator) { 25 | continue 26 | } 27 | if device.VendorId != int(qr.Vendor) { 28 | continue 29 | } 30 | if device.ProductId != int(qr.Product) { 31 | continue 32 | } 33 | out = append(out, device) 34 | } 35 | return out 36 | } 37 | 38 | func command_list_fabrics(cmd *cobra.Command) { 39 | 40 | fabric := createBasicFabricFromCmd(cmd) 41 | channel, err := connectDeviceFromCmd(fabric, cmd) 42 | if err != nil { 43 | panic(err) 44 | } 45 | 46 | to_send := gomat.EncodeIMReadRequest(0, 0x3e, 1) 47 | channel.Send(to_send) 48 | 49 | resp, err := channel.Receive() 50 | if err != nil { 51 | panic(err) 52 | } 53 | if resp.ProtocolHeader.Opcode != gomat.INTERACTION_OPCODE_REPORT_DATA { 54 | panic("did not receive report data message") 55 | } 56 | 57 | fabric_array := resp.Tlv.GetItemRec([]int{1, 0, 1, 2}) 58 | if fabric_array == nil { 59 | panic("did not receive fabric list") 60 | } 61 | for _, fabr := range fabric_array.GetChild() { 62 | root_key := fabr.GetItemWithTag(1) 63 | if root_key != nil { 64 | fmt.Printf("root_key: %s\n", hex.EncodeToString(root_key.GetOctetString())) 65 | } 66 | vendor_id := fabr.GetItemWithTag(2) 67 | if vendor_id != nil { 68 | fmt.Printf("vendor_id: %d\n", vendor_id.GetInt()) 69 | } 70 | fabric_id := fabr.GetItemWithTag(3) 71 | if fabric_id != nil { 72 | fmt.Printf("fabric_id: %d\n", fabric_id.GetInt()) 73 | } 74 | node_id := fabr.GetItemWithTag(4) 75 | if node_id != nil { 76 | fmt.Printf("node_id: %d\n", node_id.GetInt()) 77 | } 78 | fmt.Println("---------------------------------") 79 | } 80 | dict := map[string]string{ 81 | ".0": "Root", 82 | ".0.1": "AttributeReports", 83 | ".0.1.0": "AttributeReportIB", 84 | ".0.1.0.1": "AttributeData", 85 | ".0.1.0.1.0": "Version", 86 | ".0.1.0.1.1": "Path", 87 | ".0.1.0.1.1.2": "Endpoint", 88 | ".0.1.0.1.1.3": "Cluster", 89 | ".0.1.0.1.1.4": "Attribute", 90 | ".0.1.0.1.2": "Data", 91 | ".0.1.0.1.2.0": "Fabrics", 92 | ".0.1.0.1.2.0.1": "RootPublicKey", 93 | ".0.1.0.1.2.0.2": "VendorId", 94 | ".0.1.0.1.2.0.3": "FabricId", 95 | ".0.1.0.1.2.0.4": "NodeId", 96 | ".0.1.0.1.2.0.5": "Label", 97 | } 98 | resp.Tlv.DumpWithDict(0, "", dict) 99 | } 100 | 101 | func command_list_device_types(cmd *cobra.Command) { 102 | 103 | fabric := createBasicFabricFromCmd(cmd) 104 | channel, err := connectDeviceFromCmd(fabric, cmd) 105 | if err != nil { 106 | panic(err) 107 | } 108 | 109 | to_send := gomat.EncodeIMReadRequest(0, symbols.CLUSTER_ID_Descriptor, symbols.ATTRIBUTE_ID_Descriptor_DeviceTypeList) 110 | channel.Send(to_send) 111 | 112 | resp, err := channel.Receive() 113 | if err != nil { 114 | panic(err) 115 | } 116 | if resp.ProtocolHeader.Opcode != gomat.INTERACTION_OPCODE_REPORT_DATA { 117 | panic("did not receive report data message") 118 | } 119 | 120 | dict := map[string]string{ 121 | ".0": "Root", 122 | ".0.1": "AttributeReports", 123 | ".0.1.0": "AttributeReportIB", 124 | ".0.1.0.1": "AttributeData", 125 | ".0.1.0.1.0": "Version", 126 | ".0.1.0.1.1": "Path", 127 | ".0.1.0.1.1.2": "Endpoint", 128 | ".0.1.0.1.1.3": "Cluster", 129 | ".0.1.0.1.1.4": "Attribute", 130 | ".0.1.0.1.2": "Data", 131 | ".0.1.0.1.2.0": "DeviceType", 132 | ".0.1.0.1.2.0.0": "DeviceType", 133 | ".0.1.0.1.2.0.1": "Revision", 134 | } 135 | resp.Tlv.DumpWithDict(0, "", dict) 136 | } 137 | 138 | func command_list_supported_clusters(cmd *cobra.Command, args []string) { 139 | 140 | fabric := createBasicFabricFromCmd(cmd) 141 | channel, err := connectDeviceFromCmd(fabric, cmd) 142 | if err != nil { 143 | panic(err) 144 | } 145 | endpoint, err := strconv.ParseInt(args[0], 0, 16) 146 | if err != nil { 147 | panic(err) 148 | } 149 | 150 | to_send := gomat.EncodeIMReadRequest(uint16(endpoint), symbols.CLUSTER_ID_Descriptor, symbols.ATTRIBUTE_ID_Descriptor_ServerList) 151 | channel.Send(to_send) 152 | 153 | resp, err := channel.Receive() 154 | if err != nil { 155 | panic(err) 156 | } 157 | if resp.ProtocolHeader.Opcode != gomat.INTERACTION_OPCODE_REPORT_DATA { 158 | panic("did not receive report data message") 159 | } 160 | 161 | dict := map[string]string{ 162 | ".0": "Root", 163 | ".0.1": "AttributeReports", 164 | ".0.1.0": "AttributeReportIB", 165 | ".0.1.0.1": "AttributeData", 166 | ".0.1.0.1.0": "Version", 167 | ".0.1.0.1.1": "Path", 168 | ".0.1.0.1.1.2": "Endpoint", 169 | ".0.1.0.1.1.3": "Cluster", 170 | ".0.1.0.1.1.4": "Attribute", 171 | ".0.1.0.1.2": "Data", 172 | ".0.1.0.1.2.0": "ClusterId", 173 | } 174 | resp.Tlv.DumpWithDict(0, "", dict) 175 | clusters := resp.Tlv.GetItemRec([]int{1, 0, 1, 2}) 176 | if clusters == nil { 177 | panic("clusters not found") 178 | } 179 | for _, c := range clusters.GetChild() { 180 | name := symbols.ClusterNameMap[c.GetInt()] 181 | fmt.Printf("0x%x %s\n", c.GetInt(), name) 182 | } 183 | } 184 | 185 | func command_list_interfaces(cmd *cobra.Command, args []string) { 186 | 187 | fabric := createBasicFabricFromCmd(cmd) 188 | channel, err := connectDeviceFromCmd(fabric, cmd) 189 | if err != nil { 190 | panic(err) 191 | } 192 | 193 | to_send := gomat.EncodeIMReadRequest(0, symbols.CLUSTER_ID_GeneralDiagnostics, symbols.ATTRIBUTE_ID_GeneralDiagnostics_NetworkInterfaces) 194 | channel.Send(to_send) 195 | 196 | resp, err := channel.Receive() 197 | if err != nil { 198 | panic(err) 199 | } 200 | if resp.ProtocolHeader.Opcode != gomat.INTERACTION_OPCODE_REPORT_DATA { 201 | panic("did not receive report data message") 202 | } 203 | dict := map[string]string{ 204 | ".0": "Root", 205 | ".0.1": "AttributeReports", 206 | ".0.1.0": "AttributeReportIB", 207 | ".0.1.0.1": "AttributeData", 208 | ".0.1.0.1.0": "Version", 209 | ".0.1.0.1.1": "Path", 210 | ".0.1.0.1.1.2": "Endpoint", 211 | ".0.1.0.1.1.3": "Cluster", 212 | ".0.1.0.1.1.4": "Attribute", 213 | ".0.1.0.1.2": "Data", 214 | ".0.1.0.1.2.0": "Interface", 215 | ".0.1.0.1.2.0.0": "Name", 216 | ".0.1.0.1.2.0.1": "IsOperational", 217 | ".0.1.0.1.2.0.4": "HWAddress", 218 | ".0.1.0.1.2.0.5": "ipv4Address", 219 | ".0.1.0.1.2.0.6": "ipv6Address", 220 | ".0.1.0.1.2.0.7": "type", 221 | } 222 | resp.Tlv.DumpWithDict(0, "", dict) 223 | } 224 | 225 | func command_get_logs(cmd *cobra.Command, args []string) { 226 | 227 | fabric := createBasicFabricFromCmd(cmd) 228 | channel, err := connectDeviceFromCmd(fabric, cmd) 229 | if err != nil { 230 | panic(err) 231 | } 232 | endpoint, err := strconv.ParseInt(args[0], 0, 16) 233 | if err != nil { 234 | panic(err) 235 | } 236 | 237 | var tlv mattertlv.TLVBuffer 238 | tlv.WriteUInt8(0, 0) // intent 239 | tlv.WriteUInt8(1, 0) 240 | 241 | to_send := gomat.EncodeIMInvokeRequest(uint16(endpoint), symbols.CLUSTER_ID_DiagnosticLogs, symbols.COMMAND_ID_DiagnosticLogs_RetrieveLogsRequest, tlv.Bytes(), false, uint16(rand.Intn(0xffff))) 242 | channel.Send(to_send) 243 | 244 | resp, err := channel.Receive() 245 | if err != nil { 246 | panic(err) 247 | } 248 | if resp.ProtocolHeader.Opcode != gomat.INTERACTION_OPCODE_INVOKE_RSP { 249 | panic("did not receive report data message") 250 | } 251 | 252 | resp.Tlv.Dump(1) 253 | } 254 | 255 | func command_open_commissioning(cmd *cobra.Command, args []string) { 256 | pin, err := strconv.ParseInt(args[0], 0, 16) 257 | if err != nil { 258 | panic(err) 259 | } 260 | 261 | fabric := createBasicFabricFromCmd(cmd) 262 | channel, err := connectDeviceFromCmd(fabric, cmd) 263 | if err != nil { 264 | panic(err) 265 | } 266 | salt := gomat.CreateRandomBytes(32) 267 | iterations := 1000 268 | sctx := gomat.NewSpaceCtx() 269 | sctx.Gen_w(int(pin), salt, iterations) 270 | sctx.Gen_random_X() 271 | sctx.Gen_random_Y() 272 | sctx.Calc_X() 273 | sctx.Calc_ZVb() 274 | data := sctx.W0 275 | data = append(data, sctx.L.As_bytes()...) 276 | 277 | var tlv mattertlv.TLVBuffer 278 | tlv.WriteUInt8(0, 240) // timeout 279 | tlv.WriteOctetString(1, data) // pake 280 | tlv.WriteUInt16(2, 1000) // discrimantor 281 | tlv.WriteUInt32(3, uint32(iterations)) // iterations 282 | tlv.WriteOctetString(4, salt) // salt 283 | 284 | var exchange uint16 = uint16(rand.Intn(0xffff)) 285 | to_send := gomat.EncodeIMTimedRequest(exchange, 6000) 286 | channel.Send(to_send) 287 | resp, err := channel.Receive() 288 | if err != nil { 289 | panic(err) 290 | } 291 | if resp.ProtocolHeader.Opcode == gomat.INTERACTION_OPCODE_STATUS_RSP { 292 | status := resp.Tlv.GetItemWithTag(0) 293 | if status != nil { 294 | if status.GetInt() != 0 { 295 | panic(fmt.Sprintf("TimedRequest failed with status: %d\n", status.GetInt())) 296 | } 297 | } else { 298 | log.Printf("TimedRequest status parse failed %v\n", resp.Payload) 299 | } 300 | } else { 301 | resp.ProtocolHeader.Dump() 302 | panic("unexpected opcode") 303 | } 304 | 305 | to_send = gomat.EncodeIMInvokeRequest(0, symbols.CLUSTER_ID_AdministratorCommissioning, symbols.COMMAND_ID_AdministratorCommissioning_OpenCommissioningWindow, tlv.Bytes(), true, exchange) 306 | channel.Send(to_send) 307 | 308 | resp, err = channel.Receive() 309 | if err != nil { 310 | panic(err) 311 | } 312 | 313 | if resp.ProtocolHeader.Opcode != gomat.INTERACTION_OPCODE_INVOKE_RSP { 314 | panic("did not receive report data message") 315 | } 316 | 317 | final_result := gomat.ParseImInvokeResponse(&resp.Tlv) 318 | switch final_result { 319 | case 0: 320 | log.Println("open commissioning success") 321 | case 2: 322 | log.Println("failed with busy (2)") 323 | case 3: 324 | log.Println("failed with pake parameter error (3)") 325 | case 4: 326 | log.Println("failed with window not open (4)") 327 | default: 328 | log.Printf("failed with unknown code 0x%x\n", final_result) 329 | } 330 | } 331 | 332 | func test_subscribe(cmd *cobra.Command, args []string) { 333 | fabric := createBasicFabricFromCmd(cmd) 334 | channel, err := connectDeviceFromCmd(fabric, cmd) 335 | if err != nil { 336 | panic(err) 337 | } 338 | 339 | endpoint, _ := strconv.ParseInt(args[0], 0, 16) 340 | cluster, _ := strconv.ParseInt(args[1], 0, 16) 341 | event, _ := strconv.ParseInt(args[2], 0, 16) 342 | to_send := gomat.EncodeIMSubscribeRequest(uint16(endpoint), uint32(cluster), uint32(event)) 343 | channel.Send(to_send) 344 | 345 | resp, err := channel.Receive() 346 | if err != nil { 347 | panic(err) 348 | } 349 | if resp.ProtocolHeader.Opcode != gomat.INTERACTION_OPCODE_REPORT_DATA { 350 | log.Println("unexpected message") 351 | resp.ProtocolHeader.Dump() 352 | } else { 353 | resp.Tlv.DumpWithDict(0, "", report_data_dictionary) 354 | } 355 | 356 | sr := gomat.EncodeIMStatusResponse(resp.ProtocolHeader.ExchangeId, 1) 357 | channel.Send(sr) 358 | for { 359 | r, err := channel.Receive() 360 | if err != nil { 361 | log.Println("it is ok to see timeout on following line") 362 | log.Println(err) 363 | continue 364 | } 365 | if r.ProtocolHeader.Opcode == gomat.INTERACTION_OPCODE_SUBSC_RSP { 366 | log.Println("subscribe response") 367 | continue 368 | } 369 | if r.ProtocolHeader.Opcode == gomat.INTERACTION_OPCODE_STATUS_RSP { 370 | log.Println("status response") 371 | continue 372 | } 373 | if r.ProtocolHeader.Opcode == gomat.INTERACTION_OPCODE_REPORT_DATA { 374 | fmt.Printf("EVENT:\n") 375 | r.Tlv.DumpWithDict(0, "", report_data_dictionary) 376 | sr = gomat.EncodeIMStatusResponse(r.ProtocolHeader.ExchangeId, 0) 377 | channel.Send(sr) 378 | } else { 379 | log.Printf("unexpected opcode %x\n", r.ProtocolHeader.Opcode) 380 | r.ProtocolHeader.Dump() 381 | } 382 | } 383 | } 384 | 385 | func createBasicFabric(id uint64) *gomat.Fabric { 386 | cert_manager := gomat.NewFileCertManager(id) 387 | err := cert_manager.Load() 388 | if err != nil { 389 | panic(err) 390 | } 391 | fabric := gomat.NewFabric(id, cert_manager) 392 | return fabric 393 | } 394 | 395 | func createBasicFabricFromCmd(cmd *cobra.Command) *gomat.Fabric { 396 | fabric_id_str, _ := cmd.Flags().GetString("fabric") 397 | id, err := strconv.ParseUint(fabric_id_str, 0, 64) 398 | if err != nil { 399 | panic(fmt.Sprintf("invalid fabric id %s", fabric_id_str)) 400 | } 401 | return createBasicFabric(id) 402 | } 403 | 404 | func connectDeviceFromCmd(fabric *gomat.Fabric, cmd *cobra.Command) (gomat.SecureChannel, error) { 405 | ip, _ := cmd.Flags().GetString("ip") 406 | device_id, _ := cmd.Flags().GetUint64("device-id") 407 | controller_id, _ := cmd.Flags().GetUint64("controller-id") 408 | 409 | secure_channel, err := gomat.StartSecureChannel(net.ParseIP(ip), 5540, 55555) 410 | if err != nil { 411 | panic(err) 412 | } 413 | secure_channel, err = gomat.SigmaExchange(fabric, controller_id, device_id, secure_channel) 414 | return secure_channel, err 415 | } 416 | 417 | var report_data_dictionary = map[string]string{ 418 | ".0": "Root", 419 | ".0.0": "SubscriptionID", 420 | ".0.1": "AttributeReports", 421 | ".0.2": "EventReports", 422 | ".0.2.0": "EventReport", 423 | ".0.2.0.0": "EventStatus", 424 | ".0.2.0.1": "EventData", 425 | ".0.2.0.1.0": "Path", 426 | ".0.2.0.1.0.0": "Node", 427 | ".0.2.0.1.0.1": "Endpoint", 428 | ".0.2.0.1.0.2": "Cluster", 429 | ".0.2.0.1.0.3": "Event", 430 | ".0.2.0.1.0.4": "Urgent", 431 | ".0.2.0.1.1": "EventNumber", 432 | ".0.2.0.1.2": "Priority", 433 | ".0.2.0.1.3": "EpochTimestamp", 434 | ".0.2.0.1.4": "SystemTimestamp", 435 | ".0.2.0.1.5": "DeltaEpochTimestamp", 436 | ".0.2.0.1.6": "DeltaTimestamp", 437 | ".0.2.0.1.7": "Data", 438 | ".0.3": "MoreChunkedMessages", 439 | ".0.4": "SuppressResponse", 440 | } 441 | 442 | func main() { 443 | var rootCmd = &cobra.Command{ 444 | Use: "gomat", 445 | Short: "matter manager", 446 | } 447 | rootCmd.PersistentFlags().StringP("fabric", "f", "0x110", "fabric identifier") 448 | 449 | var commandCmd = &cobra.Command{ 450 | Use: "cmd", 451 | } 452 | commandCmd.PersistentFlags().Uint64P("device-id", "", 2, "device id") 453 | commandCmd.PersistentFlags().Uint64P("controller-id", "", 9, "controller id") 454 | commandCmd.PersistentFlags().StringP("ip", "i", "", "ip address") 455 | 456 | commandCmd.AddCommand(&cobra.Command{ 457 | Use: "list_fabrics", 458 | Run: func(cmd *cobra.Command, args []string) { 459 | command_list_fabrics(cmd) 460 | }, 461 | }) 462 | commandCmd.AddCommand(&cobra.Command{ 463 | Use: "list_device_types", 464 | Run: func(cmd *cobra.Command, args []string) { 465 | command_list_device_types(cmd) 466 | }, 467 | }) 468 | commandCmd.AddCommand(&cobra.Command{ 469 | Use: "list_supported_clusters [endpoint]", 470 | Run: func(cmd *cobra.Command, args []string) { 471 | command_list_supported_clusters(cmd, args) 472 | }, 473 | Args: cobra.MinimumNArgs(1), 474 | }) 475 | commandCmd.AddCommand(&cobra.Command{ 476 | Use: "list_interfaces [endpoint]", 477 | Run: func(cmd *cobra.Command, args []string) { 478 | command_list_interfaces(cmd, args) 479 | }, 480 | }) 481 | commandCmd.AddCommand(&cobra.Command{ 482 | Use: "get_logs [endpoint]", 483 | Run: func(cmd *cobra.Command, args []string) { 484 | command_get_logs(cmd, args) 485 | }, 486 | Args: cobra.MinimumNArgs(1), 487 | }) 488 | commandCmd.AddCommand(&cobra.Command{ 489 | Use: "open_commissioning", 490 | Run: func(cmd *cobra.Command, args []string) { 491 | command_open_commissioning(cmd, args) 492 | }, 493 | Args: cobra.MinimumNArgs(1), 494 | }) 495 | commandCmd.AddCommand(&cobra.Command{ 496 | Use: "off", 497 | Run: func(cmd *cobra.Command, args []string) { 498 | fabric := createBasicFabricFromCmd(cmd) 499 | channel, err := connectDeviceFromCmd(fabric, cmd) 500 | if err != nil { 501 | panic(err) 502 | } 503 | to_send := gomat.EncodeIMInvokeRequest(1, symbols.CLUSTER_ID_OnOff, symbols.COMMAND_ID_OnOff_Off, []byte{}, false, uint16(rand.Intn(0xffff))) 504 | channel.Send(to_send) 505 | 506 | resp, err := channel.Receive() 507 | if err != nil { 508 | panic(err) 509 | } 510 | status, err := resp.Tlv.GetIntRec([]int{1, 0, 1, 1, 0}) 511 | if err != nil { 512 | panic(err) 513 | } 514 | fmt.Printf("result status: %d\n", status) 515 | }, 516 | }) 517 | 518 | commandCmd.AddCommand(&cobra.Command{ 519 | Use: "on", 520 | Run: func(cmd *cobra.Command, args []string) { 521 | fabric := createBasicFabricFromCmd(cmd) 522 | channel, err := connectDeviceFromCmd(fabric, cmd) 523 | if err != nil { 524 | panic(err) 525 | } 526 | to_send := gomat.EncodeIMInvokeRequest(1, symbols.CLUSTER_ID_OnOff, symbols.COMMAND_ID_OnOff_On, []byte{}, false, uint16(rand.Intn(0xffff))) 527 | channel.Send(to_send) 528 | 529 | resp, err := channel.Receive() 530 | if err != nil { 531 | panic(err) 532 | } 533 | status, err := resp.Tlv.GetIntRec([]int{1, 0, 1, 1, 0}) 534 | if err != nil { 535 | panic(err) 536 | } 537 | fmt.Printf("result status: %d\n", status) 538 | }, 539 | }) 540 | 541 | commandCmd.AddCommand(&cobra.Command{ 542 | Use: "color [hue] [saturation] [time]", 543 | Run: func(cmd *cobra.Command, args []string) { 544 | hue, err := strconv.Atoi(args[0]) 545 | if err != nil { 546 | panic(err) 547 | } 548 | saturation, err := strconv.Atoi(args[1]) 549 | if err != nil { 550 | panic(err) 551 | } 552 | time, err := strconv.Atoi(args[2]) 553 | if err != nil { 554 | panic(err) 555 | } 556 | fabric := createBasicFabricFromCmd(cmd) 557 | channel, err := connectDeviceFromCmd(fabric, cmd) 558 | if err != nil { 559 | panic(err) 560 | } 561 | var tlv mattertlv.TLVBuffer 562 | tlv.WriteUInt8(0, byte(hue)) // hue 563 | tlv.WriteUInt8(1, byte(saturation)) // saturation 564 | tlv.WriteUInt8(2, byte(time)) // time 565 | to_send := gomat.EncodeIMInvokeRequest(1, 0x300, 6, tlv.Bytes(), false, uint16(rand.Intn(0xffff))) 566 | channel.Send(to_send) 567 | 568 | resp, err := channel.Receive() 569 | if err != nil { 570 | panic(err) 571 | } 572 | status, err := resp.Tlv.GetIntRec([]int{1, 0, 1, 1, 0}) 573 | if err != nil { 574 | panic(err) 575 | } 576 | fmt.Printf("result status: %d\n", status) 577 | }, 578 | Args: cobra.MinimumNArgs(3), 579 | }) 580 | 581 | // this sends yellight proprietary command to control indidividual leds on yeelight cube 582 | commandCmd.AddCommand(&cobra.Command{ 583 | Use: "test", 584 | Run: func(cmd *cobra.Command, args []string) { 585 | 586 | fabric := createBasicFabricFromCmd(cmd) 587 | channel, err := connectDeviceFromCmd(fabric, cmd) 588 | if err != nil { 589 | panic(err) 590 | } 591 | var tlv mattertlv.TLVBuffer 592 | b := bytes.NewBuffer([]byte{}) 593 | for x:=0; x<5; x++ { 594 | for y:=0; y<5; y++ { 595 | b.WriteByte(byte(0xff)) 596 | b.WriteByte(byte(x*50+50)) 597 | b.WriteByte(byte(10)) 598 | b.WriteByte(byte(y*40)) 599 | } 600 | } 601 | tlv.WriteOctetString(0, b.Bytes()) 602 | to_send := gomat.EncodeIMInvokeRequest(2, 0x1312fc03, 0x13120007, tlv.Bytes(), false, uint16(rand.Intn(0xffff))) 603 | channel.Send(to_send) 604 | 605 | resp, err := channel.Receive() 606 | if err != nil { 607 | panic(err) 608 | } 609 | status, err := resp.Tlv.GetIntRec([]int{1, 0, 1, 1, 0}) 610 | if err != nil { 611 | panic(err) 612 | } 613 | fmt.Printf("result status: %d\n", status) 614 | }, 615 | }) 616 | 617 | commandCmd.AddCommand(&cobra.Command{ 618 | Use: "read", 619 | Run: func(cmd *cobra.Command, args []string) { 620 | fabric := createBasicFabricFromCmd(cmd) 621 | channel, err := connectDeviceFromCmd(fabric, cmd) 622 | if err != nil { 623 | panic(err) 624 | } 625 | 626 | endpoint, _ := strconv.ParseInt(args[0], 0, 16) 627 | cluster, _ := strconv.ParseInt(args[1], 0, 16) 628 | attr, _ := strconv.ParseInt(args[2], 0, 16) 629 | 630 | to_send := gomat.EncodeIMReadRequest(uint16(endpoint), uint32(cluster), uint32(attr)) 631 | channel.Send(to_send) 632 | 633 | resp, err := channel.Receive() 634 | if err != nil { 635 | panic(err) 636 | } 637 | if (resp.ProtocolHeader.ProtocolId == gomat.ProtocolIdInteraction) && 638 | (resp.ProtocolHeader.Opcode == gomat.INTERACTION_OPCODE_REPORT_DATA) { 639 | resp.Tlv.Dump(0) 640 | } 641 | channel.Close() 642 | }, 643 | Args: cobra.MinimumNArgs(3), 644 | }) 645 | 646 | commandCmd.AddCommand(&cobra.Command{ 647 | Use: "subscribe [endpoint] [cluster] [event]", 648 | Example: "subscribe 1 0x101 1", 649 | Run: test_subscribe, 650 | Args: cobra.MinimumNArgs(3), 651 | }) 652 | 653 | rootCmd.AddCommand(commandCmd) 654 | 655 | var commissionCmd = &cobra.Command{ 656 | Use: "commission", 657 | Run: func(cmd *cobra.Command, args []string) { 658 | ip, _ := cmd.Flags().GetString("ip") 659 | if len(ip) == 0 { 660 | panic("ip address is required") 661 | } 662 | pin, _ := cmd.Flags().GetString("pin") 663 | if len(pin) == 0 { 664 | panic("passcode is required") 665 | } 666 | fabric := createBasicFabricFromCmd(cmd) 667 | device_id, _ := cmd.Flags().GetUint64("device-id") 668 | controller_id, _ := cmd.Flags().GetUint64("controller-id") 669 | pinn, err := strconv.Atoi(pin) 670 | if err != nil { 671 | panic(err) 672 | } 673 | //commision(fabric, discover_with_qr(qr).addrs[1], 123456) 674 | err = gomat.Commission(fabric, net.ParseIP(ip), pinn, controller_id, device_id) 675 | if err != nil { 676 | panic(err) 677 | } 678 | 679 | cf := fabric.CompressedFabric() 680 | csf := hex.EncodeToString(cf) 681 | dids := fmt.Sprintf("%s-%016X", csf, device_id) 682 | dids = strings.ToUpper(dids) 683 | fmt.Printf("device identifier: %s\n", dids) 684 | }, 685 | } 686 | commissionCmd.Flags().StringP("ip", "i", "", "ip address") 687 | commissionCmd.Flags().StringP("pin", "p", "", "pin") 688 | commissionCmd.Flags().Uint64P("device-id", "", 2, "device id") 689 | commissionCmd.Flags().Uint64P("controller-id", "", 9, "controller id") 690 | 691 | var printInfoCmd = &cobra.Command{ 692 | Use: "fabric-info", 693 | Run: func(cmd *cobra.Command, args []string) { 694 | fabric := createBasicFabricFromCmd(cmd) 695 | cf := fabric.CompressedFabric() 696 | csf := hex.EncodeToString(cf) 697 | csf = strings.ToUpper(csf) 698 | fmt.Printf("compressed-fabric: %s", csf) 699 | }, 700 | } 701 | 702 | var cacreateuserCmd = &cobra.Command{ 703 | Use: "ca-createuser [id]", 704 | Run: func(cmd *cobra.Command, args []string) { 705 | ids := args[0] 706 | id, err := strconv.ParseUint(ids, 0, 64) 707 | if err != nil { 708 | panic(err) 709 | } 710 | //cm := NewCertManager(0x99) 711 | fabric := createBasicFabricFromCmd(cmd) 712 | err = fabric.CertificateManager.CreateUser(uint64(id)) 713 | if err != nil { 714 | panic(err) 715 | } 716 | 717 | }, 718 | Args: cobra.MinimumNArgs(1), 719 | } 720 | cacreateuserCmd.Flags().StringP("id", "i", "", "user id") 721 | var cabootCmd = &cobra.Command{ 722 | Use: "ca-bootstrap", 723 | Run: func(cmd *cobra.Command, args []string) { 724 | fabric_id_str, _ := cmd.Flags().GetString("fabric") 725 | id, err := strconv.ParseUint(fabric_id_str, 0, 64) 726 | if err != nil { 727 | panic(fmt.Sprintf("invalid fabric id %s", fabric_id_str)) 728 | } 729 | cm := gomat.NewFileCertManager(id) 730 | err = cm.BootstrapCa() 731 | if err != nil { 732 | panic(err) 733 | } 734 | }, 735 | } 736 | 737 | var discoverCmd = &cobra.Command{ 738 | Use: "discover", 739 | } 740 | discoverCmd.PersistentFlags().StringP("interface", "i", "", "network interface") 741 | discoverCmd.PersistentFlags().BoolP("disable-ipv6", "d", false, "disable ipv6") 742 | 743 | var discoverCCmd = &cobra.Command{ 744 | Use: "commissioned [device-id]", 745 | Run: func(cmd *cobra.Command, args []string) { 746 | device, _ := cmd.Flags().GetString("interface") 747 | disable_ipv6, _ := cmd.Flags().GetBool("disable-ipv6") 748 | device_filter := "" 749 | if len(args) == 1 { 750 | fabric := createBasicFabricFromCmd(cmd) 751 | dids := args[0] 752 | device_id, err := strconv.ParseInt(dids, 0, 64) 753 | if err != nil { 754 | log.Panicf("incorrect device specification %s", dids) 755 | } 756 | cf := fabric.CompressedFabric() 757 | csf := hex.EncodeToString(cf) 758 | dids = fmt.Sprintf("%s-%016X", csf, device_id) 759 | device_filter = strings.ToUpper(dids) 760 | device_filter = device_filter + "._matter._tcp.local." 761 | } 762 | devices := discover.DiscoverAllComissioned(device, disable_ipv6) 763 | for _, device := range devices { 764 | if (len(device_filter)) > 0 && device.Name != device_filter { 765 | continue 766 | } 767 | device.Dump() 768 | fmt.Println() 769 | } 770 | }, 771 | } 772 | var discoverC3Cmd = &cobra.Command{ 773 | Use: "commissioned2 [device-id]", 774 | Run: func(cmd *cobra.Command, args []string) { 775 | device, _ := cmd.Flags().GetString("interface") 776 | disable_ipv6, _ := cmd.Flags().GetBool("disable-ipv6") 777 | device_filter := "" 778 | if len(args) == 1 { 779 | fabric := createBasicFabricFromCmd(cmd) 780 | dids := args[0] 781 | device_id, err := strconv.ParseInt(dids, 0, 64) 782 | if err != nil { 783 | log.Panicf("incorrect device specification %s", dids) 784 | } 785 | cf := fabric.CompressedFabric() 786 | csf := hex.EncodeToString(cf) 787 | dids = fmt.Sprintf("%s-%016X", csf, device_id) 788 | device_filter = strings.ToUpper(dids) 789 | device_filter = device_filter + "._matter._tcp.local." 790 | } 791 | devices := discover.DiscoverComissioned(device, disable_ipv6, device_filter) 792 | for _, device := range devices { 793 | if (len(device_filter)) > 0 && device.Name != device_filter { 794 | continue 795 | } 796 | device.Dump() 797 | fmt.Println() 798 | } 799 | }, 800 | } 801 | var discoverC2Cmd = &cobra.Command{ 802 | Use: "commissionable", 803 | Run: func(cmd *cobra.Command, args []string) { 804 | device, _ := cmd.Flags().GetString("interface") 805 | disable_ipv6, _ := cmd.Flags().GetBool("disable-ipv6") 806 | qrtext, _ := cmd.Flags().GetString("qr") 807 | devices := discover.DiscoverAllComissionable(device, disable_ipv6) 808 | if len(qrtext) > 0 { 809 | qr := onboarding_payload.DecodeQrText(qrtext) 810 | devices = filter_devices(devices, qr) 811 | } 812 | for _, device := range devices { 813 | device.Dump() 814 | fmt.Println() 815 | } 816 | }, 817 | } 818 | discoverC2Cmd.Flags().StringP("qr", "q", "", "qr code") 819 | discoverCmd.AddCommand(discoverCCmd) 820 | discoverCmd.AddCommand(discoverC2Cmd) 821 | discoverCmd.AddCommand(discoverC3Cmd) 822 | var decodeQrCmd = &cobra.Command{ 823 | Use: "decode-qr", 824 | Short: "decode text representation of qr code", 825 | Run: func(cmd *cobra.Command, args []string) { 826 | qrtext := args[0] 827 | qr := onboarding_payload.DecodeQrText(qrtext) 828 | qr.Dump() 829 | }, 830 | Args: cobra.MinimumNArgs(1), 831 | } 832 | var decodeManualCmd = &cobra.Command{ 833 | Use: "decode-mc", 834 | Short: "decode manual pairing code", 835 | Run: func(cmd *cobra.Command, args []string) { 836 | text := args[0] 837 | content := onboarding_payload.DecodeManualPairingCode(text) 838 | fmt.Printf("passcode: %d\n", content.Passcode) 839 | fmt.Printf("discriminator4: %d\n", content.Discriminator4) 840 | }, 841 | Args: cobra.MinimumNArgs(1), 842 | } 843 | 844 | rootCmd.AddCommand(cacreateuserCmd) 845 | rootCmd.AddCommand(cabootCmd) 846 | rootCmd.AddCommand(commissionCmd) 847 | rootCmd.AddCommand(discoverCmd) 848 | rootCmd.AddCommand(decodeQrCmd) 849 | rootCmd.AddCommand(decodeManualCmd) 850 | rootCmd.AddCommand(printInfoCmd) 851 | rootCmd.Execute() 852 | } 853 | -------------------------------------------------------------------------------- /discover/discover.go: -------------------------------------------------------------------------------- 1 | package discover 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "maps" 7 | "net" 8 | "strconv" 9 | "strings" 10 | 11 | "github.com/hashicorp/mdns" 12 | ) 13 | 14 | type DiscoveredType int 15 | 16 | const DiscoveredTypeCommissioned = 1 17 | const DiscoveredTypeCommissionable = 2 18 | 19 | type DiscoveredDevice struct { 20 | Name string 21 | Host string 22 | Type DiscoveredType 23 | Addrs []net.IP 24 | PH string 25 | CM string 26 | VP string 27 | VendorId int 28 | ProductId int 29 | D string 30 | DN string 31 | } 32 | 33 | func (d DiscoveredDevice) Dump() { 34 | fmt.Printf("name: %s\n", d.Name) 35 | fmt.Printf("host: %s\n", d.Host) 36 | fmt.Printf("DN: %s\n", d.DN) 37 | fmt.Printf("addreses: %v\n", d.Addrs) 38 | if d.Type != DiscoveredTypeCommissioned { 39 | fmt.Printf("PH: %s\n", d.PH) 40 | fmt.Printf("CM: %s\n", d.CM) 41 | fmt.Printf("VP: %s\n", d.VP) 42 | fmt.Printf(" vendor : %d\n", d.VendorId) 43 | fmt.Printf(" product: %d\n", d.ProductId) 44 | fmt.Printf("D: %s\n", d.D) 45 | } 46 | } 47 | 48 | func parseVP(vp string) (int, int) { 49 | s := strings.Split(vp, "+") 50 | vid, err := strconv.Atoi(s[0]) 51 | if err != nil { 52 | return -1, -1 53 | } 54 | pid := -1 55 | if len(s) == 2 { 56 | pid, _ = strconv.Atoi(s[1]) 57 | } 58 | return vid, pid 59 | } 60 | 61 | func Discover(iface string) ([]DiscoveredDevice, error) { 62 | entriesCh := make(chan *mdns.ServiceEntry, 4) 63 | defer close(entriesCh) 64 | devices := []DiscoveredDevice{} 65 | go func() { 66 | for entry := range entriesCh { 67 | fmt.Printf("Got new entry: %+v\n", entry) 68 | if !strings.Contains(entry.Name, "_matterc") { 69 | continue 70 | } 71 | addrs := []net.IP{} 72 | if entry.AddrV6 != nil { 73 | addrs = append(addrs, entry.AddrV6) 74 | } 75 | if entry.AddrV6 != nil { 76 | addrs = append(addrs, entry.AddrV4) 77 | } 78 | dev := DiscoveredDevice{ 79 | Name: entry.Name, 80 | Host: entry.Host, 81 | Addrs: addrs, 82 | } 83 | for _, s := range entry.InfoFields { 84 | if strings.HasPrefix(s, "PH=") { 85 | dev.PH = s[3:] 86 | } 87 | if strings.HasPrefix(s, "CM=") { 88 | dev.CM = s[3:] 89 | } 90 | if strings.HasPrefix(s, "VP=") { 91 | dev.VP = s[3:] 92 | dev.VendorId, dev.ProductId = parseVP(dev.VP) 93 | } 94 | if strings.HasPrefix(s, "D=") { 95 | dev.D = s[2:] 96 | } 97 | if strings.HasPrefix(s, "DN=") { 98 | dev.DN = s[3:] 99 | } 100 | } 101 | devices = append(devices, dev) 102 | } 103 | }() 104 | 105 | i, err := net.InterfaceByName(iface) 106 | if err != nil { 107 | return nil, err 108 | } 109 | params := mdns.QueryParam{ 110 | Service: "_matterc._udp.", 111 | Entries: entriesCh, 112 | DisableIPv6: true, 113 | Interface: i, 114 | } 115 | err = mdns.Query(¶ms) 116 | if err != nil { 117 | panic(err) 118 | } 119 | return devices, nil 120 | } 121 | 122 | func Discover2(iface string, service string, disableipv6 bool) (map[string]DiscoveredDevice, error) { 123 | entriesCh := make(chan *mdns.ServiceEntry, 4) 124 | defer close(entriesCh) 125 | devices := map[string]DiscoveredDevice{} 126 | go func() { 127 | for entry := range entriesCh { 128 | if !strings.Contains(entry.Name, service) { 129 | continue 130 | } 131 | addrs := []net.IP{} 132 | if entry.AddrV6 != nil { 133 | addrs = append(addrs, entry.AddrV6) 134 | } 135 | if entry.AddrV6 != nil { 136 | addrs = append(addrs, entry.AddrV4) 137 | } 138 | dev := DiscoveredDevice{ 139 | Name: entry.Name, 140 | Host: entry.Host, 141 | Addrs: addrs, 142 | } 143 | for _, s := range entry.InfoFields { 144 | if strings.HasPrefix(s, "PH=") { 145 | dev.PH = s[3:] 146 | } 147 | if strings.HasPrefix(s, "CM=") { 148 | dev.CM = s[3:] 149 | } 150 | if strings.HasPrefix(s, "VP=") { 151 | dev.VP = s[3:] 152 | dev.VendorId, dev.ProductId = parseVP(dev.VP) 153 | } 154 | if strings.HasPrefix(s, "D=") { 155 | dev.D = s[2:] 156 | } 157 | if strings.HasPrefix(s, "DN=") { 158 | dev.DN = s[3:] 159 | } 160 | } 161 | devices[entry.Host] = dev 162 | } 163 | }() 164 | 165 | i, err := net.InterfaceByName(iface) 166 | if err != nil { 167 | return nil, err 168 | } 169 | params := mdns.QueryParam{ 170 | Service: service, 171 | Entries: entriesCh, 172 | DisableIPv6: disableipv6, 173 | Interface: i, 174 | } 175 | err = mdns.Query(¶ms) 176 | if err != nil { 177 | return nil, err 178 | } 179 | return devices, nil 180 | } 181 | 182 | func ListInterfaces(name string) []net.Interface { 183 | if len(name) != 0 { 184 | i, err := net.InterfaceByName(name) 185 | if err != nil { 186 | return []net.Interface{} 187 | } 188 | return []net.Interface{*i} 189 | } 190 | ifaces, err := net.Interfaces() 191 | if err != nil { 192 | return []net.Interface{} 193 | } 194 | return ifaces 195 | } 196 | 197 | func isEglible(iface net.Interface) bool { 198 | if iface.Flags&net.FlagRunning == 0 { 199 | return false 200 | } 201 | if iface.Flags&net.FlagLoopback != 0 { 202 | return false 203 | } 204 | if iface.Flags&net.FlagMulticast == 0 { 205 | return false 206 | } 207 | return true 208 | } 209 | 210 | // DiscoverAllComissioned uses mdns to discover all matter devices which are already commissioned. 211 | // Name of these devices contains compressed fabric identifier and id of device in fabric. 212 | func DiscoverAllComissioned(interfac string, disableipv6 bool) []DiscoveredDevice { 213 | ifaces := ListInterfaces(interfac) 214 | devices := map[string]DiscoveredDevice{} 215 | for _, iface := range ifaces { 216 | if !isEglible(iface) { 217 | continue 218 | } 219 | log.Printf("trying %v\n", iface) 220 | ds, _ := Discover2(iface.Name, "_matter._tcp", disableipv6) 221 | maps.Copy(devices, ds) 222 | } 223 | out := []DiscoveredDevice{} 224 | for _, d := range devices { 225 | d.Type = DiscoveredTypeCommissioned 226 | out = append(out, d) 227 | } 228 | return out 229 | } 230 | 231 | // DiscoverComissioned uses mdns to discover concrete already commissioned matter device. 232 | // It searches for device commissioned to current fabric with identifier id. 233 | func DiscoverComissioned(interfac string, disableipv6 bool, id string) []DiscoveredDevice { 234 | ifaces := ListInterfaces(interfac) 235 | devices := map[string]DiscoveredDevice{} 236 | for _, iface := range ifaces { 237 | if !isEglible(iface) { 238 | continue 239 | } 240 | log.Printf("trying %v\n", iface) 241 | ds, _ := Discover2(iface.Name, "._matter._tcp", disableipv6) 242 | maps.Copy(devices, ds) 243 | } 244 | out := []DiscoveredDevice{} 245 | for _, d := range devices { 246 | if d.Name != id { 247 | continue 248 | } 249 | d.Type = DiscoveredTypeCommissioned 250 | out = append(out, d) 251 | } 252 | return out 253 | } 254 | 255 | // DiscoverAllComissionable uses mdns to discover all matter devices with open commissioning window 256 | func DiscoverAllComissionable(interfac string, disableipv6 bool) []DiscoveredDevice { 257 | ifaces := ListInterfaces(interfac) 258 | devices := map[string]DiscoveredDevice{} 259 | for _, iface := range ifaces { 260 | if !isEglible(iface) { 261 | continue 262 | } 263 | log.Printf("trying %v\n", iface) 264 | ds, _ := Discover2(iface.Name, "_matterc._udp.", disableipv6) 265 | maps.Copy(devices, ds) 266 | } 267 | out := []DiscoveredDevice{} 268 | for _, d := range devices { 269 | d.Type = DiscoveredTypeCommissionable 270 | out = append(out, d) 271 | } 272 | return out 273 | } 274 | -------------------------------------------------------------------------------- /examples/basic/basic-main.go: -------------------------------------------------------------------------------- 1 | // this is example application which shows how to: 2 | // - create fabric (generate CA certificate) 3 | // - commission device (upload certificates to it) 4 | // - send commands to device 5 | // devica parameters (ip address and passcode) are hardcoded in main function 6 | // this example assumes that device is in state accepting new commissioning 7 | 8 | package main 9 | 10 | import ( 11 | "math/rand" 12 | "net" 13 | "os" 14 | 15 | "github.com/tom-code/gomat" 16 | "github.com/tom-code/gomat/mattertlv" 17 | "github.com/tom-code/gomat/symbols" 18 | ) 19 | 20 | func bootstrap_ca(fabric_id, admin_user uint64) { 21 | os.Mkdir("pem", 0700) 22 | cm := gomat.NewFileCertManager(fabric_id) 23 | cm.BootstrapCa() 24 | cm.Load() 25 | if err := cm.CreateUser(admin_user); err != nil { 26 | panic(err) 27 | } 28 | } 29 | 30 | func loadFabric(fabric_id uint64) *gomat.Fabric { 31 | cm := gomat.NewFileCertManager(fabric_id) 32 | cm.Load() 33 | return gomat.NewFabric(fabric_id, cm) 34 | } 35 | 36 | func commission(fabric_id, admin_user, device_id uint64, device_ip string, pin int) { 37 | fabric := loadFabric(fabric_id) 38 | if err := gomat.Commission(fabric, net.ParseIP(device_ip), pin, admin_user, device_id); err != nil { 39 | panic(err) 40 | } 41 | } 42 | 43 | func sendOnCommand(secure_channel *gomat.SecureChannel) { 44 | on_command := gomat.EncodeIMInvokeRequest( 45 | 1, // endpoint 46 | symbols.CLUSTER_ID_OnOff, // api cluster (on/off) 47 | symbols.COMMAND_ID_OnOff_On, // on command 48 | []byte{}, // no extra data 49 | false, uint16(rand.Uint32())) 50 | 51 | secure_channel.Send(on_command) 52 | 53 | // process ON command response 54 | response, err := secure_channel.Receive() 55 | if err != nil { 56 | panic(err) 57 | } 58 | if response.ProtocolHeader.Opcode != gomat.INTERACTION_OPCODE_INVOKE_RSP { 59 | panic("unexpected message") 60 | } 61 | if gomat.ParseImInvokeResponse(&response.Tlv) != 0 { 62 | response.Tlv.Dump(0) 63 | panic("response was not OK") 64 | } 65 | } 66 | 67 | func sendColorCommand(secure_channel *gomat.SecureChannel) { 68 | var tlv mattertlv.TLVBuffer 69 | tlv.WriteUInt8(0, 100) // hue 70 | tlv.WriteUInt8(1, 200) // saturation 71 | tlv.WriteUInt8(2, 10) // time 72 | color_command := gomat.EncodeIMInvokeRequest( 73 | 1, // endpoint 74 | symbols.CLUSTER_ID_ColorControl, // color control cluster 75 | symbols.COMMAND_ID_ColorControl_MoveToHueAndSaturation, 76 | tlv.Bytes(), 77 | false, uint16(rand.Uint32())) 78 | 79 | secure_channel.Send(color_command) 80 | 81 | // process command response 82 | response, err := secure_channel.Receive() 83 | if err != nil { 84 | panic(err) 85 | } 86 | if response.ProtocolHeader.Opcode != gomat.INTERACTION_OPCODE_INVOKE_RSP { 87 | panic("unexpected message") 88 | } 89 | if gomat.ParseImInvokeResponse(&response.Tlv) != 0 { 90 | response.Tlv.Dump(0) 91 | panic("response was not OK") 92 | } 93 | } 94 | 95 | func main() { 96 | var fabric_id uint64 = 0x100 97 | var admin_user uint64 = 5 98 | var device_id uint64 = 10 99 | device_ip := "192.168.5.178" 100 | pin := 123456 101 | 102 | // Generate CA keys/certificate + admin user 103 | // do this only once for your fabric 104 | bootstrap_ca(fabric_id, admin_user) 105 | 106 | // Commission device - upload certificates + set admin user 107 | // do this once for device (per fabric) 108 | commission(fabric_id, admin_user, device_id, device_ip, pin) 109 | 110 | // connect to commissioned device 111 | fabric := loadFabric(fabric_id) 112 | secure_channel, err := gomat.ConnectDevice(net.ParseIP(device_ip), 5540, fabric, device_id, admin_user) 113 | if err != nil { 114 | panic(err) 115 | } 116 | defer secure_channel.Close() 117 | 118 | // send ON command 119 | sendOnCommand(&secure_channel) 120 | 121 | // send set color command 122 | sendColorCommand(&secure_channel) 123 | } 124 | -------------------------------------------------------------------------------- /fabric.go: -------------------------------------------------------------------------------- 1 | package gomat 2 | 3 | import ( 4 | "bytes" 5 | "crypto/elliptic" 6 | "encoding/binary" 7 | "encoding/hex" 8 | "fmt" 9 | "strings" 10 | ) 11 | 12 | // Fabric structure represents matter Fabric. 13 | // Its main parameters are Id of fabric and certificate manager. 14 | type Fabric struct { 15 | id uint64 16 | CertificateManager CertificateManager 17 | ipk []byte 18 | } 19 | 20 | func (fabric Fabric) Id() uint64 { 21 | return fabric.id 22 | } 23 | 24 | // CompressedFabric returns Compressed Fabric Identifier which is used to identify fabric 25 | // in matter protocol. 26 | func (fabric Fabric) CompressedFabric() []byte { 27 | capub := fabric.CertificateManager.GetCaPublicKey() 28 | capublic_key := elliptic.Marshal(elliptic.P256(), capub.X, capub.Y) 29 | 30 | var fabric_big_endian bytes.Buffer 31 | binary.Write(&fabric_big_endian, binary.BigEndian, fabric.id) 32 | 33 | key := hkdf_sha256(capublic_key[1:], fabric_big_endian.Bytes(), []byte("CompressedFabric"), 8) 34 | return key 35 | } 36 | func (fabric Fabric) make_ipk() []byte { 37 | key := hkdf_sha256(fabric.ipk, fabric.CompressedFabric(), []byte("GroupKey v1.0"), 16) 38 | return key 39 | } 40 | 41 | func (fabric Fabric) GetOperationalDeviceId(in uint64) string { 42 | compressedFabric := hex.EncodeToString(fabric.CompressedFabric()) 43 | ids := fmt.Sprintf("%s-%016X", compressedFabric, in) 44 | return strings.ToUpper(ids) 45 | } 46 | 47 | // NewFabric constructs new Fabric object. 48 | func NewFabric(id uint64, certman CertificateManager) *Fabric { 49 | out := &Fabric{ 50 | id: id, 51 | CertificateManager: certman, 52 | ipk: []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf}, 53 | } 54 | return out 55 | } 56 | -------------------------------------------------------------------------------- /flows.go: -------------------------------------------------------------------------------- 1 | package gomat 2 | 3 | import ( 4 | "crypto/ecdh" 5 | "crypto/ecdsa" 6 | "crypto/rand" 7 | "crypto/x509" 8 | "fmt" 9 | "log" 10 | randm "math/rand" 11 | "net" 12 | 13 | "github.com/tom-code/gomat/mattertlv" 14 | ) 15 | 16 | // Spake2pExchange establishes secure session using PASE (Passcode-Authenticated Session Establishment). 17 | // This uses SPAKE2+ protocol 18 | func Spake2pExchange(pin int, udp *udpChannel) (SecureChannel, error) { 19 | exchange := uint16(randm.Intn(0xffff)) 20 | secure_channel := SecureChannel{ 21 | Udp: udp, 22 | session: 0, 23 | Counter: uint32(randm.Intn(0xffffffff)), 24 | } 25 | 26 | pbkdf_request := pBKDFParamRequest(exchange) 27 | secure_channel.Send(pbkdf_request) 28 | 29 | pbkdf_responseS, err := secure_channel.Receive() 30 | if err != nil { 31 | return SecureChannel{}, fmt.Errorf("pbkdf response not received: %s", err.Error()) 32 | } 33 | if pbkdf_responseS.ProtocolHeader.Opcode != SEC_CHAN_OPCODE_PBKDF_RESP { 34 | return SecureChannel{}, fmt.Errorf("SEC_CHAN_OPCODE_PBKDF_RESP not received") 35 | } 36 | pbkdf_response_salt := pbkdf_responseS.Tlv.GetOctetStringRec([]int{4, 2}) 37 | pbkdf_response_iterations, err := pbkdf_responseS.Tlv.GetIntRec([]int{4, 1}) 38 | if err != nil { 39 | return SecureChannel{}, fmt.Errorf("can't get pbkdf_response_iterations") 40 | } 41 | pbkdf_response_session, err := pbkdf_responseS.Tlv.GetIntRec([]int{3}) 42 | if err != nil { 43 | return SecureChannel{}, fmt.Errorf("can't get pbkdf_response_session") 44 | } 45 | 46 | sctx := NewSpaceCtx() 47 | sctx.Gen_w(pin, pbkdf_response_salt, int(pbkdf_response_iterations)) 48 | sctx.Gen_random_X() 49 | sctx.Calc_X() 50 | 51 | pake1 := pake1ParamRequest(exchange, sctx.X.As_bytes()) 52 | secure_channel.Send(pake1) 53 | 54 | pake2s, err := secure_channel.Receive() 55 | if err != nil { 56 | return SecureChannel{}, fmt.Errorf("pake2 not received: %s", err.Error()) 57 | } 58 | if pake2s.ProtocolHeader.Opcode != SEC_CHAN_OPCODE_PAKE2 { 59 | return SecureChannel{}, fmt.Errorf("SEC_CHAN_OPCODE_PAKE2 not received") 60 | } 61 | //pake2s.tlv.Dump(1) 62 | pake2_pb := pake2s.Tlv.GetOctetStringRec([]int{1}) 63 | 64 | sctx.Y.from_bytes(pake2_pb) 65 | sctx.calc_ZV() 66 | ttseed := []byte("CHIP PAKE V1 Commissioning") 67 | ttseed = append(ttseed, pbkdf_request[6:]...) // 6 is size of proto header 68 | ttseed = append(ttseed, pbkdf_responseS.Payload...) 69 | err = sctx.calc_hash(ttseed) 70 | if err != nil { 71 | return SecureChannel{}, err 72 | } 73 | 74 | pake3 := pake3ParamRequest(exchange, sctx.cA) 75 | secure_channel.Send(pake3) 76 | 77 | status_report, err := secure_channel.Receive() 78 | if err != nil { 79 | return SecureChannel{}, err 80 | } 81 | if status_report.StatusReport.ProtocolCode != 0 { 82 | return SecureChannel{}, fmt.Errorf("pake3 is not success code: %d", status_report.StatusReport.ProtocolCode) 83 | } 84 | 85 | secure_channel = SecureChannel{ 86 | Udp: udp, 87 | decrypt_key: sctx.decrypt_key, 88 | encrypt_key: sctx.encrypt_key, 89 | remote_node: []byte{0, 0, 0, 0, 0, 0, 0, 0}, 90 | local_node: []byte{0, 0, 0, 0, 0, 0, 0, 0}, 91 | session: int(pbkdf_response_session), 92 | } 93 | 94 | return secure_channel, nil 95 | } 96 | 97 | // SigmaExhange establishes secure session using CASE (Certificate Authenticated Session Establishment) 98 | func SigmaExchange(fabric *Fabric, controller_id uint64, device_id uint64, secure_channel SecureChannel) (SecureChannel, error) { 99 | 100 | controller_privkey, _ := ecdh.P256().GenerateKey(rand.Reader) 101 | sigma_context := sigmaContext{ 102 | session_privkey: controller_privkey, 103 | exchange: uint16(randm.Intn(0xffff)), 104 | } 105 | sigma_context.genSigma1(fabric, device_id) 106 | sigma1 := genSigma1Req2(sigma_context.sigma1payload, sigma_context.exchange) 107 | secure_channel.Send(sigma1) 108 | 109 | var err error 110 | sigma_context.sigma2dec, err = secure_channel.Receive() 111 | if err != nil { 112 | return SecureChannel{}, err 113 | } 114 | if (sigma_context.sigma2dec.ProtocolHeader.ProtocolId == ProtocolIdSecureChannel) && 115 | (sigma_context.sigma2dec.ProtocolHeader.Opcode == SEC_CHAN_OPCODE_STATUS_REP) { 116 | return SecureChannel{}, fmt.Errorf("sigma2 not received. status: %x %x", sigma_context.sigma2dec.StatusReport.GeneralCode, 117 | sigma_context.sigma2dec.StatusReport.ProtocolCode) 118 | } 119 | if sigma_context.sigma2dec.ProtocolHeader.Opcode != 0x31 { 120 | return SecureChannel{}, fmt.Errorf("sigma2 not received") 121 | } 122 | 123 | sigma_context.controller_key, err = fabric.CertificateManager.GetPrivkey(controller_id) 124 | if err != nil { 125 | return SecureChannel{}, err 126 | } 127 | controller_cert, err := fabric.CertificateManager.GetCertificate(controller_id) 128 | if err != nil { 129 | return SecureChannel{}, err 130 | } 131 | sigma_context.controller_matter_certificate = SerializeCertificateIntoMatter(fabric, controller_cert) 132 | 133 | to_send, err := sigma_context.sigma3(fabric) 134 | if err != nil { 135 | return SecureChannel{}, err 136 | } 137 | secure_channel.Send(to_send) 138 | 139 | sigma_result, err := secure_channel.Receive() 140 | if err != nil { 141 | return SecureChannel{}, err 142 | } 143 | if sigma_result.ProtocolHeader.Opcode != SEC_CHAN_OPCODE_STATUS_REP { 144 | return SecureChannel{}, fmt.Errorf("unexpected message (opcode:0x%x)", sigma_result.ProtocolHeader.Opcode) 145 | } 146 | if !sigma_result.StatusReport.IsOk() { 147 | return SecureChannel{}, fmt.Errorf("sigma result is not ok %d %d %d", 148 | sigma_result.StatusReport.GeneralCode, 149 | sigma_result.StatusReport.ProtocolId, sigma_result.StatusReport.ProtocolCode) 150 | } 151 | 152 | secure_channel.decrypt_key = sigma_context.r2ikey 153 | secure_channel.encrypt_key = sigma_context.i2rkey 154 | secure_channel.remote_node = id_to_bytes(device_id) 155 | secure_channel.local_node = id_to_bytes(controller_id) 156 | secure_channel.session = sigma_context.session 157 | return secure_channel, nil 158 | } 159 | 160 | // Commission performs commissioning procedure on device with device_ip ip address 161 | // - fabric is fabric object with approriate certificate authority 162 | // - pin is passcode used for device pairing 163 | // - controller_id is identifier of node whioch will be owner/admin of this device 164 | // - device_id_id is identifier of "new" device 165 | func Commission(fabric *Fabric, device_ip net.IP, pin int, controller_id, device_id uint64) error { 166 | 167 | channel, err := startUdpChannel(device_ip, 5540, 55555) 168 | if err != nil { 169 | return err 170 | } 171 | secure_channel := SecureChannel{ 172 | Udp: channel, 173 | } 174 | defer secure_channel.Close() 175 | 176 | secure_channel, err = Spake2pExchange(pin, channel) 177 | if err != nil { 178 | return err 179 | } 180 | 181 | // send csr request 182 | var tlvb mattertlv.TLVBuffer 183 | tlvb.WriteOctetString(0, CreateRandomBytes(32)) 184 | to_send := EncodeIMInvokeRequest(0, 0x3e, 4, tlvb.Bytes(), false, uint16(randm.Intn(0xffff))) 185 | secure_channel.Send(to_send) 186 | 187 | csr_resp, err := secure_channel.Receive() 188 | if err != nil { 189 | return err 190 | } 191 | 192 | nocsr := csr_resp.Tlv.GetOctetStringRec([]int{1, 0, 0, 1, 0}) 193 | if len(nocsr) == 0 { 194 | return fmt.Errorf("nocsr not received") 195 | } 196 | tlv2 := mattertlv.Decode(nocsr) 197 | csr := tlv2.GetOctetStringRec([]int{1}) 198 | csrp, err := x509.ParseCertificateRequest(csr) 199 | if err != nil { 200 | return err 201 | } 202 | 203 | //AddTrustedRootCertificate 204 | var tlv4 mattertlv.TLVBuffer 205 | tlv4.WriteOctetString(0, SerializeCertificateIntoMatter(fabric, fabric.CertificateManager.GetCaCertificate())) 206 | to_send = EncodeIMInvokeRequest(0, 0x3e, 0xb, tlv4.Bytes(), false, uint16(randm.Intn(0xffff))) 207 | secure_channel.Send(to_send) 208 | 209 | resp, err := secure_channel.Receive() 210 | if err != nil { 211 | return err 212 | } 213 | resp_status := ParseImInvokeResponse(&resp.Tlv) 214 | if resp_status != 0 { 215 | return fmt.Errorf("unexpected status to AddTrustedRootCertificate %d", resp_status) 216 | } 217 | 218 | //noc_x509 := sign_cert(csrp, 2, "user") 219 | noc_x509, err := fabric.CertificateManager.SignCertificate(csrp.PublicKey.(*ecdsa.PublicKey), device_id) 220 | if err != nil { 221 | return err 222 | } 223 | noc_matter := SerializeCertificateIntoMatter(fabric, noc_x509) 224 | //AddNOC 225 | var tlv5 mattertlv.TLVBuffer 226 | tlv5.WriteOctetString(0, noc_matter) 227 | tlv5.WriteOctetString(2, fabric.ipk) //ipk 228 | tlv5.WriteUInt64(3, controller_id) // admin subject ! 229 | tlv5.WriteUInt16(4, 101) // admin vendorid ?? 230 | to_send = EncodeIMInvokeRequest(0, 0x3e, 0x6, tlv5.Bytes(), false, uint16(randm.Intn(0xffff))) 231 | 232 | secure_channel.Send(to_send) 233 | 234 | resp, err = secure_channel.Receive() 235 | if err != nil { 236 | return err 237 | } 238 | resp_status_add_noc, err := resp.Tlv.GetIntRec([]int{1, 0, 0, 1, 0}) 239 | if err != nil { 240 | return fmt.Errorf("error during AddNOC %s", err.Error()) 241 | } 242 | if resp_status_add_noc != 0 { 243 | return fmt.Errorf("unexpected status to AddNOC %d", resp_status_add_noc) 244 | } 245 | 246 | secure_channel.decrypt_key = []byte{} 247 | secure_channel.encrypt_key = []byte{} 248 | secure_channel.session = 0 249 | 250 | secure_channel, err = SigmaExchange(fabric, controller_id, device_id, secure_channel) 251 | if err != nil { 252 | return err 253 | } 254 | 255 | //commissioning complete 256 | to_send = EncodeIMInvokeRequest(0, 0x30, 4, []byte{}, false, uint16(randm.Intn(0xffff))) 257 | secure_channel.Send(to_send) 258 | 259 | respx, err := secure_channel.Receive() 260 | if err != nil { 261 | return err 262 | } 263 | commissioning_result, err := respx.Tlv.GetIntRec([]int{1, 0, 0, 1, 0}) 264 | if err != nil { 265 | return err 266 | } 267 | if commissioning_result == 0 { 268 | log.Printf("commissioning OK\n") 269 | } else { 270 | log.Printf("commissioning error: %d\n", commissioning_result) 271 | } 272 | return nil 273 | } 274 | 275 | func ConnectDevice(device_ip net.IP, port int, fabric *Fabric, device_id, admin_id uint64) (SecureChannel, error) { 276 | var secure_channel SecureChannel 277 | var err error 278 | if secure_channel, err = StartSecureChannel(device_ip, port, 55555); err != nil { 279 | return SecureChannel{}, err 280 | } 281 | if secure_channel, err = SigmaExchange(fabric, admin_id, device_id, secure_channel); err != nil { 282 | return SecureChannel{}, err 283 | } 284 | return secure_channel, nil 285 | } 286 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/tom-code/gomat 2 | 3 | go 1.21 4 | 5 | require ( 6 | github.com/hashicorp/mdns v1.0.5 7 | github.com/spf13/cobra v1.7.0 8 | golang.org/x/crypto v0.13.0 9 | ) 10 | 11 | require ( 12 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 13 | github.com/miekg/dns v1.1.41 // indirect 14 | github.com/spf13/pflag v1.0.5 // indirect 15 | golang.org/x/net v0.10.0 // indirect 16 | golang.org/x/sys v0.12.0 // indirect 17 | ) 18 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 2 | github.com/hashicorp/mdns v1.0.5 h1:1M5hW1cunYeoXOqHwEb/GBDDHAFo0Yqb/uz/beC6LbE= 3 | github.com/hashicorp/mdns v1.0.5/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc= 4 | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= 5 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 6 | github.com/miekg/dns v1.1.41 h1:WMszZWJG0XmzbK9FEmzH2TVcqYzFesusSIB41b8KHxY= 7 | github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= 8 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 9 | github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= 10 | github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= 11 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 12 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 13 | golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck= 14 | golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= 15 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 16 | golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= 17 | golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= 18 | golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= 19 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= 20 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 21 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 22 | golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 23 | golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 24 | golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= 25 | golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 26 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 27 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 28 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 29 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 30 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 31 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 32 | -------------------------------------------------------------------------------- /matter-cert.go: -------------------------------------------------------------------------------- 1 | package gomat 2 | 3 | import ( 4 | "crypto/ecdsa" 5 | "crypto/elliptic" 6 | "crypto/sha1" 7 | "crypto/x509" 8 | "crypto/x509/pkix" 9 | "encoding/asn1" 10 | "math/big" 11 | "strconv" 12 | 13 | "github.com/tom-code/gomat/mattertlv" 14 | ) 15 | 16 | type dsaSignature struct { 17 | R, S *big.Int 18 | } 19 | 20 | func caConvertDNValue(in any) uint64 { 21 | dn_str, ok := in.(string) 22 | if !ok { 23 | return 0 24 | } 25 | 26 | dn_uint64, err := strconv.ParseUint(dn_str, 16, 64) 27 | if err != nil { 28 | return 0 29 | } 30 | return dn_uint64 31 | } 32 | 33 | func caConvertDN(in pkix.Name, out *mattertlv.TLVBuffer) { 34 | for _, extra := range in.Names { 35 | if extra.Type.Equal(asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 37244, 1, 1}) { //node-id 36 | out.WriteUInt64(17, caConvertDNValue(extra.Value)) 37 | } 38 | if extra.Type.Equal(asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 37244, 1, 4}) { //matter-rcac-id 39 | out.WriteUInt64(20, caConvertDNValue(extra.Value)) 40 | } 41 | if extra.Type.Equal(asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 37244, 1, 5}) { //matter-fabric-id 42 | out.WriteUInt64(21, caConvertDNValue(extra.Value)) 43 | } 44 | } 45 | } 46 | 47 | // SerializeCertificateIntoMatter serializes x509 certificate into matter certificate format. 48 | // Matter certificate format is way how to make matter even more weird and complicated. 49 | // Signature of matter vertificate must match signature of certificate reencoded to DER encoding. 50 | // This requires to handle very carefully order and presence of all elements in original x509. 51 | func SerializeCertificateIntoMatter(fabric *Fabric, in *x509.Certificate) []byte { 52 | pub := in.PublicKey.(*ecdsa.PublicKey) 53 | public_key := elliptic.Marshal(elliptic.P256(), pub.X, pub.Y) 54 | 55 | cacert := fabric.CertificateManager.GetCaCertificate() 56 | capub := cacert.PublicKey.(*ecdsa.PublicKey) 57 | capublic_key := elliptic.Marshal(elliptic.P256(), capub.X, capub.Y) 58 | sha1_stream := sha1.New() 59 | sha1_stream.Write(capublic_key) 60 | ca_pubkey_hash := sha1_stream.Sum(nil) 61 | 62 | var tlv mattertlv.TLVBuffer 63 | tlv.WriteAnonStruct() 64 | tlv.WriteOctetString(1, in.SerialNumber.Bytes()) // serial number 65 | tlv.WriteUInt8(2, 1) // signature algorithm 66 | 67 | tlv.WriteList(3) // issuer 68 | caConvertDN(in.Issuer, &tlv) 69 | tlv.WriteStructEnd() 70 | 71 | tlv.WriteUInt32(4, uint32(in.NotBefore.Unix()-946684800)) 72 | tlv.WriteUInt32(5, uint32(in.NotAfter.Unix())-946684800) 73 | tlv.WriteList(6) // subject 74 | caConvertDN(in.Subject, &tlv) 75 | tlv.WriteStructEnd() 76 | tlv.WriteUInt8(7, 1) 77 | tlv.WriteUInt8(8, 1) 78 | //public key: 79 | tlv.WriteOctetString(9, public_key) 80 | tlv.WriteList(10) 81 | tlv.WriteStruct(1) 82 | tlv.WriteBool(1, in.IsCA) // isCA 83 | tlv.WriteStructEnd() 84 | 85 | tlv.WriteUInt(2, mattertlv.TYPE_UINT_1, uint64(in.KeyUsage)) // key-usage 86 | 87 | if len(in.ExtKeyUsage) > 0 { 88 | tlv.WriteArray(3) 89 | tlv.WriteRaw([]byte{0x04, 0x02, 0x04, 0x01}) // extended key-usage 90 | tlv.WriteStructEnd() 91 | } 92 | 93 | tlv.WriteOctetString(4, in.SubjectKeyId) // subject-key-id 94 | tlv.WriteOctetString(5, ca_pubkey_hash) // authority-key-id 95 | tlv.WriteStructEnd() 96 | 97 | var signature dsaSignature 98 | asn1.Unmarshal(in.Signature, &signature) 99 | 100 | r := signature.R.Bytes() 101 | s := signature.S.Bytes() 102 | s4 := append(r, s...) 103 | tlv.WriteOctetString(11, s4) 104 | tlv.WriteStructEnd() 105 | return tlv.Bytes() 106 | } 107 | -------------------------------------------------------------------------------- /matter.go: -------------------------------------------------------------------------------- 1 | // Package gomat implements matter protocol to allow talking to matter enabled devices. 2 | package gomat 3 | -------------------------------------------------------------------------------- /mattertlv/tlv_test.go: -------------------------------------------------------------------------------- 1 | package mattertlv 2 | 3 | import ( 4 | "encoding/hex" 5 | "fmt" 6 | "testing" 7 | ) 8 | 9 | func TestBasicEmptyAnonStruct(t *testing.T) { 10 | var encoder TLVBuffer 11 | 12 | encoder.WriteAnonStruct() 13 | encoder.WriteStructEnd() 14 | 15 | encoded := encoder.Bytes() 16 | if hex.EncodeToString(encoded) != "1518" { 17 | t.Fatalf("incorrect %s", hex.EncodeToString(encoded)) 18 | } 19 | 20 | decoded := Decode(encoded) 21 | if len(decoded.GetChild()) != 0 { 22 | t.Fatalf("empty struct test failed") 23 | } 24 | } 25 | 26 | func TestBasic(t *testing.T) { 27 | var encoder TLVBuffer 28 | 29 | encoder.WriteAnonStruct() 30 | encoder.WriteUInt8(10, 0x12) 31 | encoder.WriteUInt16(11, 0x1234) 32 | encoder.WriteUInt32(12, 0x12345678) 33 | encoder.WriteUInt64(13, 0x123456789abcdef0) 34 | encoder.WriteUInt64(14, 0xf23456789abcdef0) 35 | encoder.WriteOctetString(15, []byte{1, 2, 3, 4, 5}) 36 | encoder.WriteBool(16, false) 37 | encoder.WriteBool(17, true) 38 | encoder.WriteStructEnd() 39 | 40 | encoded := encoder.Bytes() 41 | 42 | decoded := Decode(encoded) 43 | if len(decoded.GetChild()) != 8 { 44 | t.Fatalf("empty struct test failed") 45 | } 46 | 47 | if hex.EncodeToString(encoded) != "15240a12250b3412260c78563412270df0debc9a78563412270ef0debc9a785634f2300f0501020304052810291118" { 48 | t.Fatalf("incorrect encoding") 49 | } 50 | 51 | if decoded.GetItemWithTag(10).GetInt() != 0x12 { 52 | t.Fatalf("incorrect encoding 10") 53 | } 54 | if decoded.GetItemWithTag(11).GetInt() != 0x1234 { 55 | t.Fatalf("incorrect encoding 11") 56 | } 57 | if decoded.GetItemWithTag(12).GetInt() != 0x12345678 { 58 | t.Fatalf("incorrect encoding 12") 59 | } 60 | if decoded.GetItemWithTag(13).GetInt() != 0x123456789abcdef0 { 61 | t.Fatalf("incorrect encoding 13") 62 | } 63 | if decoded.GetItemWithTag(14).GetUint64() != 0xf23456789abcdef0 { 64 | t.Fatalf("incorrect encoding 14") 65 | } 66 | if hex.EncodeToString(decoded.GetItemWithTag(15).GetOctetString()) != "0102030405" { 67 | t.Fatalf("incorrect encoding 15") 68 | } 69 | if decoded.GetItemWithTag(16).GetBool() { 70 | t.Fatalf("incorrect encoding 16") 71 | } 72 | if !decoded.GetItemWithTag(17).GetBool() { 73 | t.Fatalf("incorrect encoding 17") 74 | } 75 | } 76 | 77 | func TestRec(t *testing.T) { 78 | var encoder TLVBuffer 79 | 80 | encoder.WriteAnonStruct() 81 | encoder.WriteOctetString(1, []byte{1, 2, 3, 4, 5}) 82 | encoder.WriteArray(2) 83 | encoder.WriteAnonStruct() 84 | encoder.WriteOctetString(2, []byte{1, 2, 3, 4, 5}) 85 | encoder.WriteUInt32(3, 33) 86 | encoder.WriteStructEnd() 87 | encoder.WriteStructEnd() 88 | encoder.WriteStructEnd() 89 | 90 | encoded := encoder.Bytes() 91 | if hex.EncodeToString(encoded) != "1530010501020304053602153002050102030405260321000000181818" { 92 | fmt.Printf(hex.EncodeToString(encoded)) 93 | t.Fatal("invalid encode") 94 | } 95 | 96 | decoded := Decode(encoded) 97 | 98 | i, err := decoded.GetIntRec([]int{2, 0, 3}) 99 | if err != nil { 100 | t.Fatalf("error %s", err.Error()) 101 | } 102 | if i != 33 { 103 | t.Fatal("incorrect value") 104 | } 105 | 106 | it := decoded.GetItemRec([]int{2, 0, 3}) 107 | if it == nil { 108 | t.Fatalf("not found") 109 | } 110 | if it.GetInt() != 33 { 111 | t.Fatal("incorrect value") 112 | } 113 | 114 | it = decoded.GetItemRec([]int{2, 0, 2}) 115 | if it == nil { 116 | t.Fatalf("not found") 117 | } 118 | if hex.EncodeToString(it.GetOctetString()) != "0102030405" { 119 | t.Fatal("incorrect value") 120 | } 121 | 122 | os := decoded.GetOctetStringRec([]int{2, 0, 2}) 123 | if hex.EncodeToString(os) != "0102030405" { 124 | t.Fatal("incorrect value") 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /mattertlv/tlvdec.go: -------------------------------------------------------------------------------- 1 | package mattertlv 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "encoding/hex" 7 | "fmt" 8 | "strings" 9 | ) 10 | 11 | type ElementType int 12 | 13 | const TypeInt ElementType = 1 14 | const TypeBool ElementType = 2 15 | const TypeUTF8String ElementType = 3 16 | const TypeOctetString ElementType = 4 17 | const TypeList ElementType = 5 18 | const TypeNull ElementType = 6 19 | 20 | // TlvItem represents one TLV entry. 21 | type TlvItem struct { 22 | Tag int 23 | Type ElementType 24 | matterType byte 25 | 26 | valueBool bool 27 | valueInt uint64 28 | valueString string 29 | valueOctetString []byte 30 | valueList []TlvItem 31 | } 32 | 33 | // GetChild returns slice of all child entries. 34 | func (i TlvItem) GetChild() []TlvItem { 35 | return i.valueList 36 | } 37 | 38 | func (i TlvItem) GetItemWithTag(tag int) *TlvItem { 39 | for n, item := range i.valueList { 40 | if item.Tag == tag { 41 | return &i.valueList[n] 42 | } 43 | } 44 | return nil 45 | } 46 | 47 | // GetChild returns value of integer entry as int. 48 | func (i TlvItem) GetInt() int { 49 | return int(i.valueInt) 50 | } 51 | 52 | // GetChild returns value of integer entry as uint64. 53 | func (i TlvItem) GetUint64() uint64 { 54 | return uint64(i.valueInt) 55 | } 56 | func (i TlvItem) GetOctetString() []byte { 57 | return i.valueOctetString 58 | } 59 | func (i TlvItem) GetString() string { 60 | return i.valueString 61 | } 62 | func (i TlvItem) GetBool() bool { 63 | return i.valueBool 64 | } 65 | func (i TlvItem) Dump(pad int) { 66 | pads := strings.Repeat("-", pad) 67 | fmt.Printf(pads) 68 | fmt.Printf("tag:%3d type:0x%02x itype:", i.Tag, i.matterType) 69 | switch i.Type { 70 | case TypeNull: 71 | fmt.Printf("null\n") 72 | case TypeInt: 73 | fmt.Printf("int val:%d\n", i.valueInt) 74 | case TypeBool: 75 | fmt.Printf("bool val:%v\n", i.valueBool) 76 | case TypeUTF8String: 77 | fmt.Printf("string val:%s\n", i.valueString) 78 | case TypeOctetString: 79 | fmt.Printf("bytes val:%s\n", hex.EncodeToString(i.valueOctetString)) 80 | case TypeList: 81 | fmt.Printf("struct:\n") 82 | for _, ii := range i.valueList { 83 | ii.Dump(pad + 2) 84 | } 85 | //fmt.Println() 86 | default: 87 | fmt.Printf("unknown %d\n", i.Type) 88 | } 89 | } 90 | 91 | func (i TlvItem) DumpToString(buf *strings.Builder, pad int) { 92 | pads := strings.Repeat(" ", pad) 93 | buf.WriteString(pads) 94 | buf.WriteString(fmt.Sprintf("%3d:", i.Tag)) 95 | switch i.Type { 96 | case TypeNull: 97 | buf.WriteString("null\n") 98 | case TypeInt: 99 | buf.WriteString(fmt.Sprintf("%d\n", i.valueInt)) 100 | case TypeBool: 101 | buf.WriteString(fmt.Sprintf("%v\n", i.valueBool)) 102 | case TypeUTF8String: 103 | buf.WriteString(fmt.Sprintf("%s\n", i.valueString)) 104 | case TypeOctetString: 105 | buf.WriteString(fmt.Sprintf("%s\n", hex.EncodeToString(i.valueOctetString))) 106 | case TypeList: 107 | buf.WriteString("struct:\n") 108 | for _, ii := range i.valueList { 109 | ii.DumpToString(buf, pad+2) 110 | } 111 | //fmt.Println() 112 | default: 113 | fmt.Printf("unknown %d\n", i.Type) 114 | } 115 | } 116 | 117 | func (i TlvItem) DumpWithDict(pad int, path string, dictionary map[string]string) { 118 | path_me := fmt.Sprintf("%s.%d", path, i.Tag) 119 | pads := strings.Repeat(" ", pad) 120 | //fmt.Printf("path %s\n", path_me) 121 | fmt.Printf(pads) 122 | name, ok := dictionary[path_me] 123 | if !ok { 124 | name = fmt.Sprintf("%d", i.Tag) 125 | } 126 | fmt.Printf("%s: ", name) 127 | switch i.Type { 128 | case TypeNull: 129 | fmt.Printf("null\n") 130 | case TypeInt: 131 | fmt.Printf("%d\n", i.valueInt) 132 | case TypeBool: 133 | fmt.Printf("%v\n", i.valueBool) 134 | case TypeUTF8String: 135 | fmt.Printf("%s\n", i.valueString) 136 | case TypeOctetString: 137 | fmt.Printf("%s\n", hex.EncodeToString(i.valueOctetString)) 138 | case TypeList: 139 | fmt.Printf("\n") 140 | for _, ii := range i.valueList { 141 | ii.DumpWithDict(pad+2, path_me, dictionary) 142 | } 143 | default: 144 | fmt.Printf("unknown %d\n", i.Type) 145 | } 146 | } 147 | 148 | func (i TlvItem) GetItemRec(tag []int) *TlvItem { 149 | if len(tag) == 0 { 150 | return &i 151 | } 152 | if i.Type == TypeList { 153 | for _, d := range i.valueList { 154 | if d.Tag == tag[0] { 155 | return d.GetItemRec(tag[1:]) 156 | } 157 | } 158 | } 159 | return nil 160 | } 161 | 162 | func (i TlvItem) GetOctetStringRec(tag []int) []byte { 163 | item := i.GetItemRec(tag) 164 | if item == nil { 165 | return []byte{} 166 | } else { 167 | return item.valueOctetString 168 | } 169 | } 170 | 171 | func (i TlvItem) GetIntRec(tag []int) (uint64, error) { 172 | item := i.GetItemRec(tag) 173 | if item == nil { 174 | return 0, fmt.Errorf("not found") 175 | } else { 176 | return item.valueInt, nil 177 | } 178 | } 179 | 180 | func readByte(buf *bytes.Buffer) int { 181 | tmp, err := buf.ReadByte() 182 | if err != nil { 183 | panic(err) 184 | } 185 | return int(tmp) 186 | } 187 | 188 | func readTag(tagctrl byte, item *TlvItem, buf *bytes.Buffer) { 189 | if tagctrl == 1 { 190 | item.Tag = readByte(buf) 191 | } 192 | } 193 | 194 | func decode(buf *bytes.Buffer, container *TlvItem) { 195 | for buf.Len() > 0 { 196 | current := TlvItem{} 197 | fb, _ := buf.ReadByte() 198 | tp := fb & 0x1f 199 | tagctrl := fb >> 5 200 | current.matterType = tp 201 | switch tp { 202 | case 0: 203 | current.Type = TypeInt 204 | readTag(tagctrl, ¤t, buf) 205 | current.valueInt = uint64(readByte(buf)) 206 | case 1: 207 | current.Type = TypeInt 208 | readTag(tagctrl, ¤t, buf) 209 | var tmp uint16 210 | binary.Read(buf, binary.LittleEndian, tmp) 211 | current.valueInt = uint64(tmp) 212 | case 2: 213 | current.Type = TypeInt 214 | readTag(tagctrl, ¤t, buf) 215 | var tmp uint32 216 | binary.Read(buf, binary.LittleEndian, tmp) 217 | current.valueInt = uint64(tmp) 218 | case 3: 219 | current.Type = TypeInt 220 | readTag(tagctrl, ¤t, buf) 221 | var tmp uint64 222 | binary.Read(buf, binary.LittleEndian, tmp) 223 | current.valueInt = uint64(tmp) 224 | case 4: 225 | current.Type = TypeInt 226 | readTag(tagctrl, ¤t, buf) 227 | current.valueInt = uint64(readByte(buf)) 228 | case 5: 229 | current.Type = TypeInt 230 | readTag(tagctrl, ¤t, buf) 231 | var tmp uint16 232 | binary.Read(buf, binary.LittleEndian, &tmp) 233 | current.valueInt = uint64(tmp) 234 | case 6: 235 | current.Type = TypeInt 236 | readTag(tagctrl, ¤t, buf) 237 | var tmp uint32 238 | binary.Read(buf, binary.LittleEndian, &tmp) 239 | current.valueInt = uint64(tmp) 240 | case 7: 241 | current.Type = TypeInt 242 | readTag(tagctrl, ¤t, buf) 243 | var tmp uint64 244 | binary.Read(buf, binary.LittleEndian, &tmp) 245 | current.valueInt = uint64(tmp) 246 | case 8: 247 | current.Type = TypeBool 248 | readTag(tagctrl, ¤t, buf) 249 | current.valueBool = false 250 | case 9: 251 | current.Type = TypeBool 252 | readTag(tagctrl, ¤t, buf) 253 | current.valueBool = true 254 | case 0xa: 255 | panic("") 256 | case 0xb: 257 | panic("") 258 | case 0xc: 259 | current.Type = TypeUTF8String 260 | readTag(tagctrl, ¤t, buf) 261 | size := readByte(buf) 262 | current.valueOctetString = make([]byte, size) 263 | buf.Read(current.valueOctetString) 264 | current.valueString = string(current.valueOctetString) 265 | case 0x10: 266 | current.Type = TypeOctetString 267 | readTag(tagctrl, ¤t, buf) 268 | size := readByte(buf) 269 | current.valueOctetString = make([]byte, size) 270 | buf.Read(current.valueOctetString) 271 | case 0x11: 272 | current.Type = TypeOctetString 273 | readTag(tagctrl, ¤t, buf) 274 | var size uint16 275 | binary.Read(buf, binary.LittleEndian, &size) 276 | current.valueOctetString = make([]byte, size) 277 | buf.Read(current.valueOctetString) 278 | case 0x14: 279 | current.Type = TypeNull 280 | readTag(tagctrl, ¤t, buf) 281 | case 0x15: 282 | current.Type = TypeList 283 | readTag(tagctrl, ¤t, buf) 284 | decode(buf, ¤t) 285 | case 0x16: 286 | current.Type = TypeList 287 | readTag(tagctrl, ¤t, buf) 288 | decode(buf, ¤t) 289 | case 0x17: 290 | current.Type = TypeList 291 | readTag(tagctrl, ¤t, buf) 292 | decode(buf, ¤t) 293 | case 0x18: 294 | return 295 | default: 296 | panic(fmt.Sprintf("unknown type %x", tp)) 297 | } 298 | container.valueList = append(container.valueList, current) 299 | } 300 | } 301 | 302 | // Decode decodes binary TLV into structure represented by TlvItem. 303 | func Decode(in []byte) TlvItem { 304 | buf := bytes.NewBuffer(in) 305 | root := &TlvItem{ 306 | Type: TypeList, 307 | } 308 | decode(buf, root) 309 | return root.valueList[0] 310 | } 311 | -------------------------------------------------------------------------------- /mattertlv/tlvenc.go: -------------------------------------------------------------------------------- 1 | package mattertlv 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | ) 7 | 8 | const TYPE_UINT_1 = 4 9 | const TYPE_UINT_2 = 5 10 | const TYPE_UINT_4 = 6 11 | const TYPE_UINT_8 = 7 12 | 13 | // TLVBuffer is buffer used to serialize matter TLV into bytes. 14 | type TLVBuffer struct { 15 | data bytes.Buffer 16 | } 17 | 18 | func (b *TLVBuffer) WriteRaw(raw []byte) { 19 | b.data.Write(raw) 20 | } 21 | 22 | func (b *TLVBuffer) Bytes() []byte { 23 | return b.data.Bytes() 24 | } 25 | 26 | func (b *TLVBuffer) writeControl(ctrl byte) { 27 | binary.Write(&b.data, binary.BigEndian, ctrl) 28 | } 29 | 30 | func (b *TLVBuffer) writeTagContentSpecific(tag byte) { 31 | binary.Write(&b.data, binary.BigEndian, tag) 32 | } 33 | 34 | func (b *TLVBuffer) WriteUInt(tag byte, typ int, val uint64) { 35 | var ctrl byte 36 | ctrl = 0x1 << 5 37 | ctrl = ctrl | byte(typ) 38 | b.data.WriteByte(ctrl) 39 | b.data.WriteByte(tag) 40 | switch typ { 41 | case TYPE_UINT_1: 42 | b.data.WriteByte(byte(val)) 43 | case TYPE_UINT_2: 44 | binary.Write(&b.data, binary.LittleEndian, uint16(val)) 45 | case TYPE_UINT_4: 46 | binary.Write(&b.data, binary.LittleEndian, uint32(val)) 47 | case TYPE_UINT_8: 48 | binary.Write(&b.data, binary.LittleEndian, uint64(val)) 49 | } 50 | } 51 | func (b *TLVBuffer) WriteUInt8(tag byte, val byte) { 52 | var ctrl byte 53 | ctrl = 0x1 << 5 54 | ctrl = ctrl | TYPE_UINT_1 55 | b.data.WriteByte(ctrl) 56 | b.data.WriteByte(tag) 57 | b.data.WriteByte(byte(val)) 58 | } 59 | 60 | func (b *TLVBuffer) WriteUInt16(tag byte, val uint16) { 61 | var ctrl byte 62 | ctrl = 0x1 << 5 63 | ctrl = ctrl | TYPE_UINT_2 64 | b.data.WriteByte(ctrl) 65 | b.data.WriteByte(tag) 66 | binary.Write(&b.data, binary.LittleEndian, val) 67 | } 68 | 69 | func (b *TLVBuffer) WriteUInt32(tag byte, val uint32) { 70 | var ctrl byte 71 | ctrl = 0x1 << 5 72 | ctrl = ctrl | TYPE_UINT_4 73 | b.data.WriteByte(ctrl) 74 | b.data.WriteByte(tag) 75 | binary.Write(&b.data, binary.LittleEndian, val) 76 | } 77 | 78 | func (b *TLVBuffer) WriteUInt64(tag byte, val uint64) { 79 | var ctrl byte 80 | ctrl = 0x1 << 5 81 | ctrl = ctrl | TYPE_UINT_8 82 | b.data.WriteByte(ctrl) 83 | b.data.WriteByte(tag) 84 | binary.Write(&b.data, binary.LittleEndian, val) 85 | } 86 | 87 | func (b *TLVBuffer) WriteOctetString(tag byte, data []byte) { 88 | var ctrl byte 89 | ctrl = 0x1 << 5 90 | if len(data) > 0xff { 91 | ctrl = ctrl | 0x11 92 | b.data.WriteByte(ctrl) 93 | b.data.WriteByte(tag) 94 | var ln uint16 95 | ln = uint16(len(data)) 96 | binary.Write(&b.data, binary.LittleEndian, ln) 97 | } else { 98 | ctrl = ctrl | 0x10 99 | b.data.WriteByte(ctrl) 100 | b.data.WriteByte(tag) 101 | b.data.WriteByte(byte(len(data))) 102 | } 103 | b.data.Write(data) 104 | } 105 | 106 | func (b *TLVBuffer) WriteBool(tag byte, val bool) { 107 | var ctrl byte 108 | ctrl = 0x1 << 5 109 | if val { 110 | ctrl = ctrl | 0x9 111 | } else { 112 | ctrl = ctrl | 0x8 113 | } 114 | b.data.WriteByte(ctrl) 115 | b.data.WriteByte(tag) 116 | } 117 | 118 | // WriteAnonStruct encodes start of structure without tag 119 | func (b *TLVBuffer) WriteAnonStruct() { 120 | b.data.WriteByte(0x15) 121 | } 122 | 123 | // WriteAnonList encodes start of list without tag 124 | func (b *TLVBuffer) WriteAnonList() { 125 | b.data.WriteByte(0x17) 126 | } 127 | 128 | // WriteAnonList encodes start of structure with specified tag 129 | func (b *TLVBuffer) WriteStruct(tag byte) { 130 | b.data.WriteByte(0x35) 131 | b.data.WriteByte(tag) 132 | } 133 | 134 | // WriteArray encodes start of array with specified tag 135 | func (b *TLVBuffer) WriteArray(tag byte) { 136 | b.data.WriteByte(0x36) 137 | b.data.WriteByte(tag) 138 | } 139 | 140 | // WriteList encodes start of list with specified tag 141 | func (b *TLVBuffer) WriteList(tag byte) { 142 | b.data.WriteByte(0x37) 143 | b.data.WriteByte(tag) 144 | } 145 | 146 | // WriteList encodes end of structure. 147 | // This is used to terminate also list and array. 148 | func (b *TLVBuffer) WriteStructEnd() { 149 | b.data.WriteByte(0x18) 150 | } 151 | -------------------------------------------------------------------------------- /messages.go: -------------------------------------------------------------------------------- 1 | package gomat 2 | 3 | import ( 4 | "bytes" 5 | "crypto/rand" 6 | "encoding/binary" 7 | "fmt" 8 | randm "math/rand" 9 | 10 | "github.com/tom-code/gomat/mattertlv" 11 | ) 12 | 13 | type ProtocolId uint16 14 | 15 | const ProtocolIdSecureChannel ProtocolId = 0 16 | const ProtocolIdInteraction ProtocolId = 1 17 | 18 | type Opcode byte 19 | 20 | const SEC_CHAN_OPCODE_ACK Opcode = 0x10 21 | const SEC_CHAN_OPCODE_PBKDF_REQ Opcode = 0x20 22 | const SEC_CHAN_OPCODE_PBKDF_RESP Opcode = 0x21 23 | const SEC_CHAN_OPCODE_PAKE1 Opcode = 0x22 24 | const SEC_CHAN_OPCODE_PAKE2 Opcode = 0x23 25 | const SEC_CHAN_OPCODE_PAKE3 Opcode = 0x24 26 | const SEC_CHAN_OPCODE_STATUS_REP Opcode = 0x40 27 | 28 | const INTERACTION_OPCODE_STATUS_RSP Opcode = 0x1 29 | const INTERACTION_OPCODE_READ_REQ Opcode = 0x2 30 | const INTERACTION_OPCODE_SUBSC_REQ Opcode = 0x3 31 | const INTERACTION_OPCODE_SUBSC_RSP Opcode = 0x4 32 | const INTERACTION_OPCODE_REPORT_DATA Opcode = 0x5 33 | const INTERACTION_OPCODE_INVOKE_REQ Opcode = 0x8 34 | const INTERACTION_OPCODE_INVOKE_RSP Opcode = 0x9 35 | const INTERACTION_OPCODE_TIMED_REQ Opcode = 0xa 36 | 37 | const exchangeFlagsInitiator = 1 38 | const exchangeFlagsAcknowledge = 2 39 | 40 | type MessageHeader struct { 41 | flags byte 42 | sessionId uint16 43 | securityFlags byte 44 | messageCounter uint32 45 | sourceNodeId []byte 46 | destinationNodeId []byte 47 | } 48 | 49 | type ProtocolMessageHeader struct { 50 | exchangeFlags byte 51 | Opcode Opcode 52 | ExchangeId uint16 53 | ProtocolId ProtocolId 54 | ackCounter uint32 55 | } 56 | 57 | func (m *ProtocolMessageHeader) Decode(data *bytes.Buffer) { 58 | m.exchangeFlags, _ = data.ReadByte() 59 | opcode, _ := data.ReadByte() 60 | m.Opcode = Opcode(opcode) 61 | binary.Read(data, binary.LittleEndian, &m.ExchangeId) 62 | binary.Read(data, binary.LittleEndian, &m.ProtocolId) 63 | if (m.exchangeFlags & 0x2) != 0 { 64 | binary.Read(data, binary.LittleEndian, &m.ackCounter) 65 | } 66 | } 67 | 68 | func (m *MessageHeader) Dump() { 69 | fmt.Printf(" flags : %d\n", m.flags) 70 | fmt.Printf(" sessionId : %d\n", m.sessionId) 71 | fmt.Printf(" secFlags : %d\n", m.securityFlags) 72 | fmt.Printf(" msgCounter : %d\n", m.messageCounter) 73 | fmt.Printf(" srcNode : %v\n", m.sourceNodeId) 74 | fmt.Printf(" dstNode : %v\n", m.destinationNodeId) 75 | } 76 | 77 | func (m *ProtocolMessageHeader) Dump() { 78 | fmt.Printf(" protocol message:\n") 79 | fmt.Printf(" exchangeFlags : %d\n", m.exchangeFlags) 80 | fmt.Printf(" opcode : 0x%x\n", m.Opcode) 81 | fmt.Printf(" exchangeId : %d\n", m.ExchangeId) 82 | fmt.Printf(" protocolId : %d\n", m.ProtocolId) 83 | fmt.Printf(" ackCounter : %d\n", m.ackCounter) 84 | } 85 | func (m *ProtocolMessageHeader) Encode(data *bytes.Buffer) { 86 | data.WriteByte(m.exchangeFlags) 87 | data.WriteByte(byte(m.Opcode)) 88 | binary.Write(data, binary.LittleEndian, uint16(m.ExchangeId)) 89 | binary.Write(data, binary.LittleEndian, uint16(m.ProtocolId)) 90 | } 91 | 92 | func (m *MessageHeader) calcMessageFlags() byte { 93 | var out byte 94 | out = 0 // version hardcoded = 0 95 | 96 | if len(m.sourceNodeId) == 8 { 97 | out = out | 4 98 | } 99 | 100 | dsiz := 0 101 | if len(m.destinationNodeId) == 2 { 102 | dsiz = 2 103 | } else if len(m.destinationNodeId) == 8 { 104 | dsiz = 1 105 | } 106 | 107 | out = out | byte(dsiz) 108 | return out 109 | } 110 | 111 | func (m *MessageHeader) Encode(data *bytes.Buffer) { 112 | data.WriteByte(m.calcMessageFlags()) 113 | binary.Write(data, binary.LittleEndian, uint16(m.sessionId)) 114 | data.WriteByte(m.securityFlags) 115 | binary.Write(data, binary.LittleEndian, uint32(m.messageCounter)) 116 | if len(m.sourceNodeId) == 8 { 117 | data.Write(m.sourceNodeId) 118 | } 119 | if len(m.destinationNodeId) > 0 { 120 | data.Write(m.destinationNodeId) 121 | } 122 | } 123 | 124 | func (m *MessageHeader) Decode(data *bytes.Buffer) error { 125 | var err error 126 | m.flags, err = data.ReadByte() 127 | if err != nil { 128 | return err 129 | } 130 | binary.Read(data, binary.LittleEndian, &m.sessionId) 131 | m.securityFlags, err = data.ReadByte() 132 | if err != nil { 133 | return err 134 | } 135 | binary.Read(data, binary.LittleEndian, &m.messageCounter) 136 | if (m.flags & 4) != 0 { 137 | m.sourceNodeId = make([]byte, 8) 138 | _, err := data.Read(m.sourceNodeId) 139 | if err != nil { 140 | return err 141 | } 142 | } 143 | if (m.flags & 3) != 0 { 144 | dsiz := 0 145 | if (m.flags & 3) == 1 { 146 | dsiz = 8 147 | } else if (m.flags & 3) == 2 { 148 | dsiz = 2 149 | } 150 | if dsiz != 0 { 151 | m.destinationNodeId = make([]byte, dsiz) 152 | _, err := data.Read(m.destinationNodeId) 153 | if err != nil { 154 | return err 155 | } 156 | } 157 | } 158 | return nil 159 | } 160 | 161 | func pBKDFParamRequest(exchange uint16) []byte { 162 | var buffer bytes.Buffer 163 | 164 | prot := ProtocolMessageHeader{ 165 | exchangeFlags: 5, 166 | Opcode: SEC_CHAN_OPCODE_PBKDF_REQ, 167 | ExchangeId: exchange, 168 | ProtocolId: ProtocolIdSecureChannel, 169 | } 170 | prot.Encode(&buffer) 171 | var tlvx mattertlv.TLVBuffer 172 | tlvx.WriteAnonStruct() 173 | initiator_random := make([]byte, 32) 174 | rand.Read(initiator_random) 175 | tlvx.WriteOctetString(0x1, initiator_random) // initiator random 176 | tlvx.WriteUInt(0x2, mattertlv.TYPE_UINT_2, 0x0001) //initator session-id 177 | tlvx.WriteUInt(0x3, mattertlv.TYPE_UINT_1, 0x00) // passcode id 178 | tlvx.WriteBool(0x4, false) // has pbkdf 179 | tlvx.WriteStructEnd() 180 | buffer.Write(tlvx.Bytes()) 181 | return buffer.Bytes() 182 | } 183 | 184 | func pake1ParamRequest(exchange uint16, key []byte) []byte { 185 | var buffer bytes.Buffer 186 | 187 | prot := ProtocolMessageHeader{ 188 | exchangeFlags: 5, 189 | Opcode: SEC_CHAN_OPCODE_PAKE1, 190 | ExchangeId: exchange, 191 | ProtocolId: ProtocolIdSecureChannel, 192 | } 193 | prot.Encode(&buffer) 194 | 195 | var tlvx mattertlv.TLVBuffer 196 | tlvx.WriteAnonStruct() 197 | tlvx.WriteOctetString(0x1, key) 198 | tlvx.WriteStructEnd() 199 | buffer.Write(tlvx.Bytes()) 200 | return buffer.Bytes() 201 | } 202 | 203 | func pake3ParamRequest(exchange uint16, key []byte) []byte { 204 | var buffer bytes.Buffer 205 | prot := ProtocolMessageHeader{ 206 | exchangeFlags: 5, 207 | Opcode: SEC_CHAN_OPCODE_PAKE3, 208 | ExchangeId: exchange, 209 | ProtocolId: ProtocolIdSecureChannel, 210 | } 211 | prot.Encode(&buffer) 212 | 213 | var tlvx mattertlv.TLVBuffer 214 | tlvx.WriteAnonStruct() 215 | tlvx.WriteOctetString(0x1, key) 216 | tlvx.WriteStructEnd() 217 | buffer.Write(tlvx.Bytes()) 218 | return buffer.Bytes() 219 | } 220 | 221 | func ackGen(p ProtocolMessageHeader, counter uint32) []byte { 222 | var buffer bytes.Buffer 223 | 224 | var eflags byte = exchangeFlagsAcknowledge 225 | if (p.exchangeFlags & exchangeFlagsInitiator) == 0 { 226 | eflags |= exchangeFlagsInitiator 227 | } 228 | prot := ProtocolMessageHeader{ 229 | exchangeFlags: eflags, 230 | Opcode: SEC_CHAN_OPCODE_ACK, 231 | ExchangeId: p.ExchangeId, 232 | ProtocolId: ProtocolIdSecureChannel, 233 | } 234 | prot.Encode(&buffer) 235 | binary.Write(&buffer, binary.LittleEndian, counter) 236 | return buffer.Bytes() 237 | } 238 | 239 | type StatusReportElements struct { 240 | GeneralCode uint16 241 | ProtocolId uint32 242 | ProtocolCode uint16 243 | } 244 | 245 | func (sr StatusReportElements) Dump() { 246 | fmt.Printf(" general code: %d\n", sr.GeneralCode) 247 | fmt.Printf(" protocol id: %d\n", sr.ProtocolId) 248 | fmt.Printf(" protocol code: %d\n", sr.ProtocolCode) 249 | } 250 | 251 | func (sr StatusReportElements) IsOk() bool { 252 | if sr.GeneralCode != 0 { 253 | return false 254 | } 255 | if sr.ProtocolId != 0 { 256 | return false 257 | } 258 | if sr.ProtocolCode != 0 { 259 | return false 260 | } 261 | return true 262 | } 263 | 264 | type DecodedGeneric struct { 265 | MessageHeader MessageHeader 266 | ProtocolHeader ProtocolMessageHeader 267 | Tlv mattertlv.TlvItem 268 | Payload []byte 269 | StatusReport StatusReportElements 270 | } 271 | 272 | func EncodeStatusReport(code StatusReportElements) []byte { 273 | var buffer bytes.Buffer 274 | buffer.WriteByte(5) // flags 275 | buffer.WriteByte(byte(SEC_CHAN_OPCODE_STATUS_REP)) // opcode 276 | var exchange_id uint16 277 | exchange_id = uint16(randm.Intn(0xffff)) 278 | binary.Write(&buffer, binary.LittleEndian, exchange_id) 279 | var protocol_id uint16 = uint16(ProtocolIdSecureChannel) 280 | binary.Write(&buffer, binary.LittleEndian, protocol_id) 281 | binary.Write(&buffer, binary.LittleEndian, code.GeneralCode) 282 | binary.Write(&buffer, binary.LittleEndian, code.ProtocolId) 283 | binary.Write(&buffer, binary.LittleEndian, code.ProtocolCode) 284 | 285 | return buffer.Bytes() 286 | } 287 | 288 | // EncodeIMInvokeRequest encodes Interaction Model Invoke Request message 289 | func EncodeIMInvokeRequest(endpoint uint16, cluster uint32, command uint32, payload []byte, timed bool, exchange uint16) []byte { 290 | var tlv mattertlv.TLVBuffer 291 | tlv.WriteAnonStruct() 292 | tlv.WriteBool(0, false) 293 | tlv.WriteBool(1, timed) 294 | tlv.WriteArray(2) 295 | tlv.WriteAnonStruct() 296 | tlv.WriteList(0) 297 | tlv.WriteUInt(0, mattertlv.TYPE_UINT_2, uint64(endpoint)) 298 | tlv.WriteUInt(1, mattertlv.TYPE_UINT_4, uint64(cluster)) 299 | tlv.WriteUInt(2, mattertlv.TYPE_UINT_4, uint64(command)) 300 | tlv.WriteStructEnd() 301 | tlv.WriteStruct(1) 302 | //tlv.writeOctetString(0, payload) 303 | tlv.WriteRaw(payload) 304 | tlv.WriteStructEnd() 305 | tlv.WriteStructEnd() 306 | tlv.WriteStructEnd() 307 | tlv.WriteUInt(0xff, mattertlv.TYPE_UINT_1, 10) 308 | tlv.WriteStructEnd() 309 | 310 | var buffer bytes.Buffer 311 | prot := ProtocolMessageHeader{ 312 | exchangeFlags: 5, 313 | Opcode: INTERACTION_OPCODE_INVOKE_REQ, 314 | ExchangeId: exchange, 315 | ProtocolId: ProtocolIdInteraction, 316 | } 317 | prot.Encode(&buffer) 318 | buffer.Write(tlv.Bytes()) 319 | 320 | return buffer.Bytes() 321 | } 322 | 323 | // EncodeIMInvokeRequest encodes Interaction Model Read Request message 324 | func EncodeIMReadRequest(endpoint uint16, cluster uint32, attr uint32) []byte { 325 | var tlv mattertlv.TLVBuffer 326 | tlv.WriteAnonStruct() 327 | tlv.WriteArray(0) 328 | tlv.WriteAnonList() 329 | tlv.WriteUInt(2, mattertlv.TYPE_UINT_2, uint64(endpoint)) 330 | tlv.WriteUInt(3, mattertlv.TYPE_UINT_4, uint64(cluster)) 331 | tlv.WriteUInt(4, mattertlv.TYPE_UINT_4, uint64(attr)) 332 | tlv.WriteStructEnd() 333 | tlv.WriteStructEnd() 334 | tlv.WriteBool(3, true) 335 | tlv.WriteUInt(0xff, mattertlv.TYPE_UINT_1, 10) 336 | tlv.WriteStructEnd() 337 | 338 | var buffer bytes.Buffer 339 | 340 | prot := ProtocolMessageHeader{ 341 | exchangeFlags: 5, 342 | Opcode: INTERACTION_OPCODE_READ_REQ, 343 | ExchangeId: 0, 344 | ProtocolId: ProtocolIdInteraction, 345 | } 346 | prot.Encode(&buffer) 347 | buffer.Write(tlv.Bytes()) 348 | 349 | return buffer.Bytes() 350 | } 351 | 352 | // EncodeIMInvokeRequest encodes Interaction Model Read Request message 353 | func EncodeIMSubscribeRequest(endpoint uint16, cluster uint32, event uint32) []byte { 354 | var tlv mattertlv.TLVBuffer 355 | tlv.WriteAnonStruct() 356 | tlv.WriteBool(0, false) // keep 357 | tlv.WriteUInt16(1, 10) // min interval 358 | tlv.WriteUInt16(2, 50) // max interval 359 | tlv.WriteArray(4) 360 | tlv.WriteAnonList() 361 | tlv.WriteUInt16(1, endpoint) 362 | tlv.WriteUInt32(2, cluster) 363 | tlv.WriteUInt32(3, event) 364 | tlv.WriteBool(4, true) // urgent 365 | tlv.WriteStructEnd() 366 | tlv.WriteStructEnd() 367 | /*tlvx.WriteArray(5) 368 | tlvx.WriteAnonStruct() 369 | tlvx.WriteUInt(0, mattertlv.TYPE_UINT_1, uint64(100)) 370 | tlvx.WriteUInt(1, mattertlv.TYPE_UINT_1, uint64(0)) 371 | tlvx.WriteAnonStructEnd() 372 | tlvx.WriteAnonStructEnd()*/ 373 | tlv.WriteBool(7, false) // fabric filtered 374 | tlv.WriteUInt(0xff, mattertlv.TYPE_UINT_1, 10) 375 | tlv.WriteStructEnd() 376 | 377 | var buffer bytes.Buffer 378 | prot := ProtocolMessageHeader{ 379 | exchangeFlags: 5, 380 | Opcode: INTERACTION_OPCODE_SUBSC_REQ, 381 | ExchangeId: 0, 382 | ProtocolId: ProtocolIdInteraction, 383 | } 384 | 385 | prot.Encode(&buffer) 386 | buffer.Write(tlv.Bytes()) 387 | 388 | return buffer.Bytes() 389 | } 390 | 391 | // EncodeIMInvokeRequest encodes Interaction Model Timed Request message 392 | func EncodeIMTimedRequest(exchange uint16, timeout uint16) []byte { 393 | var tlv mattertlv.TLVBuffer 394 | tlv.WriteAnonStruct() 395 | tlv.WriteUInt16(0, timeout) 396 | tlv.WriteUInt(0xff, mattertlv.TYPE_UINT_1, 10) 397 | tlv.WriteStructEnd() 398 | 399 | var buffer bytes.Buffer 400 | prot := ProtocolMessageHeader{ 401 | exchangeFlags: 5, 402 | Opcode: INTERACTION_OPCODE_TIMED_REQ, 403 | ExchangeId: exchange, 404 | ProtocolId: ProtocolIdInteraction, 405 | } 406 | 407 | prot.Encode(&buffer) 408 | buffer.Write(tlv.Bytes()) 409 | 410 | return buffer.Bytes() 411 | } 412 | 413 | // EncodeIMStatusResponse encodes success Interaction Model Invoke Response 414 | func EncodeIMStatusResponse(exchange_id uint16, iflag byte) []byte { 415 | var tlv mattertlv.TLVBuffer 416 | tlv.WriteAnonStruct() 417 | tlv.WriteUInt8(0, 0) 418 | tlv.WriteStructEnd() 419 | 420 | var buffer bytes.Buffer 421 | prot := ProtocolMessageHeader{ 422 | exchangeFlags: 4 | iflag, 423 | Opcode: INTERACTION_OPCODE_STATUS_RSP, 424 | ExchangeId: exchange_id, 425 | ProtocolId: ProtocolIdInteraction, 426 | } 427 | prot.Encode(&buffer) 428 | 429 | buffer.Write(tlv.Bytes()) 430 | 431 | return buffer.Bytes() 432 | } 433 | 434 | // ParseImInvokeResponse parses IM InvokeResponse TLV 435 | // - returns 0 when success 436 | // - returns -1 when parsing did fail 437 | // - returned number > 0 is ClusterStatus code 438 | func ParseImInvokeResponse(resp *mattertlv.TlvItem) int { 439 | common_status := resp.GetItemRec([]int{1, 0, 1, 1, 0}) 440 | if common_status == nil { 441 | return -1 442 | } 443 | if common_status.GetInt() == 0 { 444 | return 0 445 | } 446 | cluster_status := resp.GetItemRec([]int{1, 0, 1, 1, 1}) 447 | if cluster_status == nil { 448 | return -1 449 | } 450 | return cluster_status.GetInt() 451 | } 452 | -------------------------------------------------------------------------------- /notes.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | csr request 5 | 15 6 | 2800 suppressResponse = false 7 | 2801 timedRequest = false 8 | 3602 invoke requests array 9 | 15 struct 10 | 3700 list with tag0 - command path 11 | 240000 - endpoint-id 12 | 24013e - cluster-id 13 | 240204 - command id 14 | 18 - list/command path end 15 | 3501 - commandfields struct 16 | 3000 - octet string tag 0 - csr nonce 17 | 20 - lenghtof octet string 18 | c4f68604b151d21f2afac9e61a745ade93fde7dce1c6615de543f230bd62dd85 19 | 18 20 | 18 21 | 18 22 | 24ff0a -- InteractionModelRevision = 10 23 | 18 24 | [1695836137857] [3932:23866] [EM] >>> [E:61814r S:57888 M:181537487] (S) Msg RX from 0:FFFFFFFB00000000 [0000] --- Type 0001:08 (IM:InvokeCommandRequest) 25 | [1695836137857] [3932:23866] [EM] Handling via exchange: 61814r, Delegate: 0x1074b3e00 26 | [1695836137857] [3932:23866] [DMG] InvokeRequestMessage = 27 | [1695836137857] [3932:23866] [DMG] { 28 | [1695836137857] [3932:23866] [DMG] suppressResponse = false, 29 | [1695836137857] [3932:23866] [DMG] timedRequest = false, 30 | [1695836137857] [3932:23866] [DMG] InvokeRequests = 31 | [1695836137857] [3932:23866] [DMG] [ 32 | [1695836137857] [3932:23866] [DMG] CommandDataIB = 33 | [1695836137857] [3932:23866] [DMG] { 34 | [1695836137857] [3932:23866] [DMG] CommandPathIB = 35 | [1695836137857] [3932:23866] [DMG] { 36 | [1695836137857] [3932:23866] [DMG] EndpointId = 0x0, 37 | [1695836137857] [3932:23866] [DMG] ClusterId = 0x3e, 38 | [1695836137857] [3932:23866] [DMG] CommandId = 0x4, 39 | [1695836137857] [3932:23866] [DMG] }, 40 | [1695836137857] [3932:23866] [DMG] 41 | [1695836137857] [3932:23866] [DMG] CommandFields = 42 | [1695836137857] [3932:23866] [DMG] { 43 | [1695836137857] [3932:23866] [DMG] 0x0 = [ 44 | [1695836137857] [3932:23866] [DMG] 0xc4, 0xf6, 0x86, 0x04, 0xb1, 0x51, 0xd2, 0x1f, 0x2a, 0xfa, 0xc9, 0xe6, 0x1a, 0x74, 0x5a, 0xde, 0x93, 0xfd, 0xe7, 0xdc, 0xe1, 0xc6, 0x61, 0x5d, 0xe5, 0x43, 0xf2, 0x30, 0xbd, 0x62, 0xdd, 0x85, 45 | [1695836137857] [3932:23866] [DMG] ] (32 bytes) 46 | [1695836137857] [3932:23866] [DMG] }, 47 | [1695836137857] [3932:23866] [DMG] }, 48 | [1695836137857] [3932:23866] [DMG] 49 | [1695836137857] [3932:23866] [DMG] ], 50 | [1695836137857] [3932:23866] [DMG] 51 | [1695836137857] [3932:23866] [DMG] InteractionModelRevision = 10 52 | [1695836137857] [3932:23866] [DMG] }, 53 | [1695836137857] [3932:23866] [DMG] AccessControl: checking f=0 a=p s=0xFFFFFFFB00000000 t= c=0x0000_003E e=0 p=a 54 | [1695836137857] [3932:23866] [DMG] AccessControl: implicit admin (PASE) 55 | [1695836137857] [3932:23866] [DMG] Received command for Endpoint=0 Cluster=0x0000_003E Command=0x0000_0004 56 | [1695836137857] [3932:23866] [ZCL] OpCreds: Received a CSRRequest command 57 | [1695836137858] [3932:23866] [ZCL] OpCreds: Finding fabric with fabricIndex 0x0 58 | 59 | 60 | csrrequest decoded response: 61 | 06 flags 62 | 09 opcode - invoke response 63 | 0000 exchange id 64 | 0100 protocol id 65 | 07000000 - ack counter 66 | 15 67 | 2800 - supress response=false 68 | 3601 - invoke responses array 69 | 15 70 | 3500 71 | 3700 - command path 72 | 240000 - endpoint-id 73 | 24013e - cluster-id 74 | 240205 - command-id 75 | 18 76 | 3501 command fields 77 | 3000 f3 153001cb3081c83070020100300e310c300a060355040a0c034353523059301306072a8648ce3d020106082a8648ce3d03010703420004fd63186fce84c067f93e54f577b68a7e7ef72a9cefc547ad7c6046c22449350014b42ccdebbc97190592592ac2d9e6eb21b0a6427f908a7fdb6474ec8f6108aaa000300a06082a8648ce3d0403020348003045022067f1899819cd16ced0f0a9dc38b786741a51e5eb089b4a3e446eca7ae57567f70221008320de3db4b952c7276d797a88cc79459d789bf234e856b4fc935f97e8131942300220c4f68604b151d21f2afac9e61a745ade93fde7dce1c6615de543f230bd62dd8518 78 | 3001 40 8f4f31f304a5821b477e26888fd19491382fba3ad23ea9ce8d95e6ee11000ca7c3c59d26118ec5efe422bdbff627b77faa1e1aadfb70a6d0d4b9e2a1096f8d3a 79 | 18 80 | 18 81 | 18 82 | 18 83 | 24ff0a 84 | 18 85 | 86 | 87 | add root cert 88 | 1528002801360215370024000024013e24020b1835013000e71530010101240201370324140118260480228127260580254d3a370624140118240701240801300941046fc35861a75f0b0d9d912009cbec15676f24678aeeab3dcb189c3e021500952c199dff8680bf0d3a4ee7c9f60048135fa210f2a4d60889ed2e6ca12166dc904e370a3501290118240260300414f2462b7c9c033a9e0aecd9a11a338017dee97b69300514f2462b7c9c033a9e0aecd9a11a338017dee97b6918300b404e313fcaea8b531b24f44ff1451368eea2018c89f787f39c0a52b85b08092fd475c285b99933caaa30e106e43bd129a9c65798a1ba5c06680e42f3104dd9336e1818181824ff0a18 89 | [1695836137868] [3932:23866] [EM] >>> [E:61815r S:57888 M:181537489] (S) Msg RX from 0:FFFFFFFB00000000 [0000] --- Type 0001:08 (IM:InvokeCommandRequest) 90 | [1695836137868] [3932:23866] [EM] Handling via exchange: 61815r, Delegate: 0x1074b3e00 91 | [1695836137868] [3932:23866] [DMG] InvokeRequestMessage = 92 | [1695836137868] [3932:23866] [DMG] { 93 | [1695836137868] [3932:23866] [DMG] suppressResponse = false, 94 | [1695836137868] [3932:23866] [DMG] timedRequest = false, 95 | [1695836137868] [3932:23866] [DMG] InvokeRequests = 96 | [1695836137868] [3932:23866] [DMG] [ 97 | [1695836137868] [3932:23866] [DMG] CommandDataIB = 98 | [1695836137868] [3932:23866] [DMG] { 99 | [1695836137868] [3932:23866] [DMG] CommandPathIB = 100 | [1695836137868] [3932:23866] [DMG] { 101 | [1695836137868] [3932:23866] [DMG] EndpointId = 0x0, 102 | [1695836137868] [3932:23866] [DMG] ClusterId = 0x3e, 103 | [1695836137868] [3932:23866] [DMG] CommandId = 0xb, 104 | [1695836137868] [3932:23866] [DMG] }, 105 | [1695836137868] [3932:23866] [DMG] 106 | [1695836137868] [3932:23866] [DMG] CommandFields = 107 | [1695836137868] [3932:23866] [DMG] { 108 | [1695836137868] [3932:23866] [DMG] 0x0 = [ 109 | [1695836137868] [3932:23866] [DMG] 0x15, 0x30, 0x01, 0x01, 0x01, 0x24, 0x02, 0x01, 0x37, 0x03, 0x24, 0x14, 0x01, 0x18, 0x26, 0x04, 0x80, 0x22, 0x81, 0x27, 0x26, 0x05, 0x80, 0x25, 0x4d, 0x3a, 0x37, 0x06, 0x24, 0x14, 0x01, 0x18, 0x24, 0x07, 0x01, 0x24, 0x08, 0x01, 0x30, 0x09, 0x41, 0x04, 0x6f, 0xc3, 0x58, 0x61, 0xa7, 0x5f, 0x0b, 0x0d, 0x9d, 0x91, 0x20, 0x09, 0xcb, 0xec, 0x15, 0x67, 0x6f, 0x24, 0x67, 0x8a, 0xee, 0xab, 0x3d, 0xcb, 0x18, 0x9c, 0x3e, 0x02, 0x15, 0x00, 0x95, 0x2c, 0x19, 0x9d, 0xff, 0x86, 0x80, 0xbf, 0x0d, 0x3a, 0x4e, 0xe7, 0xc9, 0xf6, 0x00, 0x48, 0x13, 0x5f, 0xa2, 0x10, 0xf2, 0xa4, 0xd6, 0x08, 0x89, 0xed, 0x2e, 0x6c, 0xa1, 0x21, 0x66, 0xdc, 0x90, 0x4e, 0x37, 0x0a, 0x35, 0x01, 0x29, 0x01, 0x18, 0x24, 0x02, 0x60, 0x30, 0x04, 0x14, 0xf2, 0x46, 0x2b, 0x7c, 0x9c, 0x03, 0x3a, 0x9e, 0x0a, 0xec, 0xd9, 0xa1, 0x1a, 0x33, 0x80, 0x17, 0xde, 0xe9, 0x7b, 0x69, 0x30, 0x05, 0x14, 0xf2, 0x46, 0x2b, 0x7c, 0x9c, 0x03, 0x3a, 0x9e, 0x0a, 0xec, 0xd9, 0xa1, 0x1a, 0x33, 0x80, 0x17, 0xde, 0xe9, 0x7b, 0x69, 0x18, 0x30, 0x0b, 0x40, 0x4e, 0x31, 0x3f, 0xca, 0xea, 0x8b, 0x53, 0x1b, 0x24, 0xf4, 0x4f, 0xf1, 0x45, 0x13, 0x68, 0xee, 0xa2, 0x01, 0x8c, 0x89, 0xf7, 0x87, 0xf3, 0x9c, 0x0a, 0x52, 0xb8, 0x5b, 0x08, 0x09, 0x2f, 0xd4, 0x75, 0xc2, 0x85, 0xb9, 0x99, 0x33, 0xca, 0xaa, 0x30, 0xe1, 0x06, 0xe4, 0x3b, 0xd1, 0x29, 0xa9, 0xc6, 0x57, 0x98, 0xa1, 0xba, 0x5c, 0x06, 0x68, 0x0e, 0x42, 0xf3, 0x10, 0x4d, 0xd9, 0x33, 0x6e, 0x18, 110 | [1695836137868] [3932:23866] [DMG] ] (231 bytes) 111 | [1695836137868] [3932:23866] [DMG] }, 112 | [1695836137868] [3932:23866] [DMG] }, 113 | [1695836137868] [3932:23866] [DMG] 114 | [1695836137868] [3932:23866] [DMG] ], 115 | [1695836137868] [3932:23866] [DMG] 116 | [1695836137868] [3932:23866] [DMG] InteractionModelRevision = 10 117 | [1695836137868] [3932:23866] [DMG] }, 118 | [1695836137868] [3932:23866] [DMG] AccessControl: checking f=0 a=p s=0xFFFFFFFB00000000 t= c=0x0000_003E e=0 p=a 119 | [1695836137868] [3932:23866] [DMG] AccessControl: implicit admin (PASE) 120 | [1695836137868] [3932:23866] [DMG] Received command for Endpoint=0 Cluster=0x0000_003E Command=0x0000_000B 121 | [1695836137868] [3932:23866] [ZCL] OpCreds: Received an AddTrustedRootCertificate command 122 | 123 | 124 | 1528002801360215370024000024013e2402061835013000f21530010101240201370324130218260480228127260580254d3a37062415012511570418240701240801300941049d77e774c633b939d20d518520552247f900e53b1998ba5b6f26133574b2bc5c4021ad65ef9875633f575f4b1cf81ce29e64095f3aa4c709f3da0ec4523b93d6370a350128011824020136030402040118300414ed97ef3e51b5bcd774a7cf98509d3f63f98bbd1a300514cd5ad18e0342d038cbc49f83df40ab500bcd2ed818300b4024da68cb9862a39eb536fd3763a05bb78df20bd286abdbc39fc4485a5d1110b3d75951fe564e9a7bed6b160e1cb5dae1bca7b1c87bdf0ce9556a291a06fc6913183001e71530010101240201370324140118260480228127260580254d3a37062413021824070124080130094104616c8167ad163beeb1b485e6045ec13ba3f8c960b9b2957bee83f3cd4b012a5ab1919424f6533da44d75f8f706274e3d9e111e6261f06b49d9d2c4d6c3c7ad06370a3501290118240260300414cd5ad18e0342d038cbc49f83df40ab500bcd2ed8300514f2462b7c9c033a9e0aecd9a11a338017dee97b6918300b40488bc743fb4c66c564377b9bdab42f58cb51ced113b6ed59d48a23f0838816972b85fa72f7d0922a6496c966aba01d796d06ed56993fcbec4e491ac3994af57b1830021074656d706f726172792069706b203031260369b601002504f1ff18181824ff0a18 125 | [1695836137872] [3932:23671] [EM] >>> [E:61816r S:57888 M:181537491] (S) Msg RX from 0:FFFFFFFB00000000 [0000] --- Type 0001:08 (IM:InvokeCommandRequest) 126 | [1695836137872] [3932:23671] [EM] Handling via exchange: 61816r, Delegate: 0x1074b3e00 127 | [1695836137872] [3932:23671] [DMG] InvokeRequestMessage = 128 | [1695836137872] [3932:23671] [DMG] { 129 | [1695836137872] [3932:23671] [DMG] suppressResponse = false, 130 | [1695836137872] [3932:23671] [DMG] timedRequest = false, 131 | [1695836137872] [3932:23671] [DMG] InvokeRequests = 132 | [1695836137872] [3932:23671] [DMG] [ 133 | [1695836137872] [3932:23671] [DMG] CommandDataIB = 134 | [1695836137872] [3932:23671] [DMG] { 135 | [1695836137872] [3932:23671] [DMG] CommandPathIB = 136 | [1695836137872] [3932:23671] [DMG] { 137 | [1695836137872] [3932:23671] [DMG] EndpointId = 0x0, 138 | [1695836137872] [3932:23671] [DMG] ClusterId = 0x3e, 139 | [1695836137872] [3932:23671] [DMG] CommandId = 0x6, 140 | [1695836137872] [3932:23671] [DMG] }, 141 | [1695836137872] [3932:23671] [DMG] 142 | [1695836137872] [3932:23671] [DMG] CommandFields = 143 | [1695836137872] [3932:23671] [DMG] { 144 | [1695836137872] [3932:23671] [DMG] 0x0 = [ 145 | [1695836137872] [3932:23671] [DMG] 0x15, 0x30, 0x01, 0x01, 0x01, 0x24, 0x02, 0x01, 0x37, 0x03, 0x24, 0x13, 0x02, 0x18, 0x26, 0x04, 0x80, 0x22, 0x81, 0x27, 0x26, 0x05, 0x80, 0x25, 0x4d, 0x3a, 0x37, 0x06, 0x24, 0x15, 0x01, 0x25, 0x11, 0x57, 0x04, 0x18, 0x24, 0x07, 0x01, 0x24, 0x08, 0x01, 0x30, 0x09, 0x41, 0x04, 0x9d, 0x77, 0xe7, 0x74, 0xc6, 0x33, 0xb9, 0x39, 0xd2, 0x0d, 0x51, 0x85, 0x20, 0x55, 0x22, 0x47, 0xf9, 0x00, 0xe5, 0x3b, 0x19, 0x98, 0xba, 0x5b, 0x6f, 0x26, 0x13, 0x35, 0x74, 0xb2, 0xbc, 0x5c, 0x40, 0x21, 0xad, 0x65, 0xef, 0x98, 0x75, 0x63, 0x3f, 0x57, 0x5f, 0x4b, 0x1c, 0xf8, 0x1c, 0xe2, 0x9e, 0x64, 0x09, 0x5f, 0x3a, 0xa4, 0xc7, 0x09, 0xf3, 0xda, 0x0e, 0xc4, 0x52, 0x3b, 0x93, 0xd6, 0x37, 0x0a, 0x35, 0x01, 0x28, 0x01, 0x18, 0x24, 0x02, 0x01, 0x36, 0x03, 0x04, 0x02, 0x04, 0x01, 0x18, 0x30, 0x04, 0x14, 0xed, 0x97, 0xef, 0x3e, 0x51, 0xb5, 0xbc, 0xd7, 0x74, 0xa7, 0xcf, 0x98, 0x50, 0x9d, 0x3f, 0x63, 0xf9, 0x8b, 0xbd, 0x1a, 0x30, 0x05, 0x14, 0xcd, 0x5a, 0xd1, 0x8e, 0x03, 0x42, 0xd0, 0x38, 0xcb, 0xc4, 0x9f, 0x83, 0xdf, 0x40, 0xab, 0x50, 0x0b, 0xcd, 0x2e, 0xd8, 0x18, 0x30, 0x0b, 0x40, 0x24, 0xda, 0x68, 0xcb, 0x98, 0x62, 0xa3, 0x9e, 0xb5, 0x36, 0xfd, 0x37, 0x63, 0xa0, 0x5b, 0xb7, 0x8d, 0xf2, 0x0b, 0xd2, 0x86, 0xab, 0xdb, 0xc3, 0x9f, 0xc4, 0x48, 0x5a, 0x5d, 0x11, 0x10, 0xb3, 0xd7, 0x59, 0x51, 0xfe, 0x56, 0x4e, 0x9a, 0x7b, 0xed, 0x6b, 0x16, 0x0e, 0x1c, 0xb5, 0xda, 0xe1, 0xbc, 0xa7, 0xb1, 0xc8, 0x7b, 0xdf, 0x0c, 0xe9, 0x55, 0x6a, 0x29, 0x1a, 0x06, 0xfc, 0x69, 0x13, 0x18, 146 | [1695836137872] [3932:23671] [DMG] ] (242 bytes) 147 | [1695836137872] [3932:23671] [DMG] 0x1 = [ 148 | [1695836137872] [3932:23671] [DMG] 0x15, 0x30, 0x01, 0x01, 0x01, 0x24, 0x02, 0x01, 0x37, 0x03, 0x24, 0x14, 0x01, 0x18, 0x26, 0x04, 0x80, 0x22, 0x81, 0x27, 0x26, 0x05, 0x80, 0x25, 0x4d, 0x3a, 0x37, 0x06, 0x24, 0x13, 0x02, 0x18, 0x24, 0x07, 0x01, 0x24, 0x08, 0x01, 0x30, 0x09, 0x41, 0x04, 0x61, 0x6c, 0x81, 0x67, 0xad, 0x16, 0x3b, 0xee, 0xb1, 0xb4, 0x85, 0xe6, 0x04, 0x5e, 0xc1, 0x3b, 0xa3, 0xf8, 0xc9, 0x60, 0xb9, 0xb2, 0x95, 0x7b, 0xee, 0x83, 0xf3, 0xcd, 0x4b, 0x01, 0x2a, 0x5a, 0xb1, 0x91, 0x94, 0x24, 0xf6, 0x53, 0x3d, 0xa4, 0x4d, 0x75, 0xf8, 0xf7, 0x06, 0x27, 0x4e, 0x3d, 0x9e, 0x11, 0x1e, 0x62, 0x61, 0xf0, 0x6b, 0x49, 0xd9, 0xd2, 0xc4, 0xd6, 0xc3, 0xc7, 0xad, 0x06, 0x37, 0x0a, 0x35, 0x01, 0x29, 0x01, 0x18, 0x24, 0x02, 0x60, 0x30, 0x04, 0x14, 0xcd, 0x5a, 0xd1, 0x8e, 0x03, 0x42, 0xd0, 0x38, 0xcb, 0xc4, 0x9f, 0x83, 0xdf, 0x40, 0xab, 0x50, 0x0b, 0xcd, 0x2e, 0xd8, 0x30, 0x05, 0x14, 0xf2, 0x46, 0x2b, 0x7c, 0x9c, 0x03, 0x3a, 0x9e, 0x0a, 0xec, 0xd9, 0xa1, 0x1a, 0x33, 0x80, 0x17, 0xde, 0xe9, 0x7b, 0x69, 0x18, 0x30, 0x0b, 0x40, 0x48, 0x8b, 0xc7, 0x43, 0xfb, 0x4c, 0x66, 0xc5, 0x64, 0x37, 0x7b, 0x9b, 0xda, 0xb4, 0x2f, 0x58, 0xcb, 0x51, 0xce, 0xd1, 0x13, 0xb6, 0xed, 0x59, 0xd4, 0x8a, 0x23, 0xf0, 0x83, 0x88, 0x16, 0x97, 0x2b, 0x85, 0xfa, 0x72, 0xf7, 0xd0, 0x92, 0x2a, 0x64, 0x96, 0xc9, 0x66, 0xab, 0xa0, 0x1d, 0x79, 0x6d, 0x06, 0xed, 0x56, 0x99, 0x3f, 0xcb, 0xec, 0x4e, 0x49, 0x1a, 0xc3, 0x99, 0x4a, 0xf5, 0x7b, 0x18, 149 | [1695836137872] [3932:23671] [DMG] ] (231 bytes) 150 | [1695836137872] [3932:23671] [DMG] 0x2 = [ 151 | [1695836137872] [3932:23671] [DMG] 0x74, 0x65, 0x6d, 0x70, 0x6f, 0x72, 0x61, 0x72, 0x79, 0x20, 0x69, 0x70, 0x6b, 0x20, 0x30, 0x31, 152 | [1695836137872] [3932:23671] [DMG] ] (16 bytes) 153 | [1695836137873] [3932:23671] [DMG] 0x3 = 112233, 154 | [1695836137873] [3932:23671] [DMG] 0x4 = 65521, 155 | [1695836137873] [3932:23671] [DMG] }, 156 | [1695836137873] [3932:23671] [DMG] }, 157 | [1695836137873] [3932:23671] [DMG] 158 | [1695836137873] [3932:23671] [DMG] ], 159 | [1695836137873] [3932:23671] [DMG] 160 | [1695836137873] [3932:23671] [DMG] InteractionModelRevision = 10 161 | [1695836137873] [3932:23671] [DMG] }, 162 | [1695836137873] [3932:23671] [DMG] AccessControl: checking f=0 a=p s=0xFFFFFFFB00000000 t= c=0x0000_003E e=0 p=a 163 | [1695836137873] [3932:23671] [DMG] AccessControl: implicit admin (PASE) 164 | [1695836137873] [3932:23671] [DMG] Received command for Endpoint=0 Cluster=0x0000_003E Command=0x0000_0006 165 | [1695836137873] [3932:23671] [ZCL] OpCreds: Received an AddNOC command 166 | 167 | 168 | 169 | 170 | 15 171 | 30010101 172 | 240201 173 | 3703 174 | 241401 175 | 18 176 | 260480228127 177 | 260580254d3a 178 | 3706 179 | 241401 180 | 18 181 | 240701 182 | 240801300941046fc35861a75f0b0d9d912009cbec15676f24678aeeab3dcb189c3e021500952c199dff8680bf0d3a4ee7c9f60048135fa210f2a4d60889ed2e6ca12166dc904e370a3501290118240260300414f2462b7c9c033a9e0aecd9a11a338017dee97b69300514f2462b7c9c033a9e0aecd9a11a338017dee97b6918300b404e313fcaea8b531b24f44ff1451368eea2018c89f787f39c0a52b85b08092fd475c285b99933caaa30e106e43bd129a9c65798a1ba5c06680e42f3104dd9336e18 183 | 184 | 15 185 | 30010101 186 | 240201 187 | 3703 188 | 241401 189 | 18 190 | 240400240500370624140118240701240801300941046fc35861a75f0b0d9d912009cbec15676f24678aeeab3dcb189c3e021500952c199dff8680bf0d3a4ee7c9f60048135fa210f2a4d60889ed2e6ca12166dc904e370a3501290118240260300414f2462b7c9c033a9e0aecd9a11a338017dee97b69300514f2462b7c9c033a9e0aecd9a11a338017dee97b6918300b404e313fcaea8b531b24f44ff1451368eea2018c89f787f39c0a52b85b08092fd475c285b99933caaa30e106e43bd129a9c65798a1ba5c06680e42f3104dd9336e18 -------------------------------------------------------------------------------- /onboarding_payload/manual.go: -------------------------------------------------------------------------------- 1 | package onboarding_payload 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "strings" 7 | ) 8 | 9 | func DecodeManualPairingCode(in string) QrContent { 10 | in = strings.Replace(in, "-", "", -1) 11 | fmt.Printf("normalized code: %s\n", in) 12 | first_group := in[0:1] 13 | second_group := in[1:6] 14 | third_group := in[6:10] 15 | //fourth := in[10:11] 16 | first, _ := strconv.Atoi(first_group) 17 | second, _ := strconv.Atoi(second_group) 18 | third, _ := strconv.Atoi(third_group) 19 | p := second&0x3fff + third<<14 20 | d := (first & 3 << 10) + (second>>6)&0x300 21 | return QrContent{ 22 | Passcode: uint32(p), 23 | Discriminator4: uint16(d), 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /onboarding_payload/qr.go: -------------------------------------------------------------------------------- 1 | package onboarding_payload 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | const qr_alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-." 9 | 10 | func a2n(a byte) uint32 { 11 | for i := 0; i < len(qr_alphabet); i++ { 12 | if qr_alphabet[i] == a { 13 | return uint32(i) 14 | } 15 | } 16 | panic("") 17 | } 18 | 19 | type bitBuffer struct { 20 | bytes []byte 21 | current_byte int 22 | current_bit int 23 | total_bytes int 24 | } 25 | 26 | func (bb *bitBuffer) add_byte(n byte) { 27 | bb.total_bytes += 1 28 | bb.bytes = append(bb.bytes, n) 29 | } 30 | 31 | func (bb *bitBuffer) dump() { 32 | for _, b := range bb.bytes { 33 | for i := 0; i < 8; i++ { 34 | if b&1 == 1 { 35 | fmt.Printf("1") 36 | } else { 37 | fmt.Printf("0") 38 | } 39 | b = b >> 1 40 | } 41 | } 42 | fmt.Printf("\n") 43 | } 44 | 45 | func (bb *bitBuffer) get() byte { 46 | b := bb.bytes[bb.current_byte] 47 | out := (b >> bb.current_bit) & 1 48 | bb.current_bit += 1 49 | if bb.current_bit == 8 { 50 | bb.current_byte += 1 51 | bb.current_bit = 0 52 | } 53 | return out 54 | } 55 | 56 | func (bb *bitBuffer) get_number(bits int) uint64 { 57 | var out uint64 58 | var mult uint64 59 | mult = 1 60 | for i := 0; i < bits; i++ { 61 | out += uint64(bb.get()) * mult 62 | mult = mult * 2 63 | } 64 | 65 | return out 66 | } 67 | 68 | func (bb *bitBuffer) get_bit(n int) byte { 69 | b_byte := n / 8 70 | b_bit := n % 8 71 | return (bb.bytes[b_byte] >> b_bit) & 1 72 | } 73 | 74 | func (bb *bitBuffer) reset_ptr() { 75 | bb.current_bit = 0 76 | bb.current_byte = 0 77 | } 78 | 79 | func b38_decode(in string) bitBuffer { 80 | in_array := []string{} 81 | for len(in) >= 5 { 82 | in_array = append(in_array, in[:5]) 83 | in = in[5:] 84 | } 85 | if len(in) > 0 { 86 | in_array = append(in_array, in) 87 | } 88 | 89 | var bb bitBuffer 90 | for _, a := range in_array { 91 | var b24 uint32 92 | mult := 1 93 | for _, n := range a { 94 | b24 += a2n(byte(n)) * uint32(mult) 95 | mult *= 38 96 | } 97 | for i := 0; i < 3; i++ { 98 | bb.add_byte(byte(b24 & 0xff)) 99 | b24 = b24 >> 8 100 | } 101 | } 102 | return bb 103 | } 104 | 105 | type QrContent struct { 106 | Version byte 107 | Vendor uint16 108 | Product uint16 109 | Discriminator uint16 110 | Discriminator4 uint16 111 | Passcode uint32 112 | } 113 | 114 | func (qr QrContent) Dump() { 115 | fmt.Printf("version: %d\n", qr.Version) 116 | fmt.Printf("vendor: %d\n", qr.Vendor) 117 | fmt.Printf("product: %d\n", qr.Product) 118 | fmt.Printf("passcode: %d\n", qr.Passcode) 119 | fmt.Printf("discriminator: %d\n", qr.Discriminator) 120 | } 121 | 122 | func DecodeQrText(in string) QrContent { 123 | if !strings.HasPrefix(in, "MT:") { 124 | panic("wrong qr") 125 | } 126 | in = in[3:] 127 | var out QrContent 128 | bb := b38_decode(in) 129 | 130 | bb.reset_ptr() 131 | out.Version = byte(bb.get_number(3)) 132 | out.Vendor = uint16(bb.get_number(16)) 133 | out.Product = uint16(bb.get_number(16)) 134 | bb.get_number(2) // custom flow 135 | bb.get_number(8) // discovery capabilities 136 | out.Discriminator = uint16(bb.get_number(12)) 137 | out.Passcode = uint32(bb.get_number(27)) 138 | return out 139 | } 140 | -------------------------------------------------------------------------------- /securechannel.go: -------------------------------------------------------------------------------- 1 | package gomat 2 | 3 | import ( 4 | "bytes" 5 | "crypto/aes" 6 | "encoding/binary" 7 | "fmt" 8 | "math/rand" 9 | "net" 10 | "time" 11 | 12 | "github.com/tom-code/gomat/ccm" 13 | "github.com/tom-code/gomat/mattertlv" 14 | ) 15 | 16 | type udpChannel struct { 17 | Udp net.PacketConn 18 | Remote_address net.UDPAddr 19 | } 20 | 21 | func startUdpChannel(remote_ip net.IP, remote_port, local_port int) (*udpChannel, error) { 22 | var out *udpChannel = new(udpChannel) 23 | out.Remote_address = net.UDPAddr{ 24 | IP: remote_ip, 25 | Port: remote_port, 26 | } 27 | var err error 28 | out.Udp, err = net.ListenPacket("udp", fmt.Sprintf(":%d", local_port)) 29 | if err != nil { 30 | return nil, err 31 | } 32 | return out, nil 33 | } 34 | 35 | func (ch *udpChannel) send(data []byte) error { 36 | _, err := ch.Udp.WriteTo(data, &ch.Remote_address) 37 | return err 38 | } 39 | func (ch *udpChannel) receive() ([]byte, error) { 40 | buf := make([]byte, 1024*10) 41 | n, _, errx := ch.Udp.ReadFrom(buf) 42 | if errx != nil { 43 | return []byte{}, errx 44 | } 45 | return buf[:n], nil 46 | } 47 | 48 | func make_nonce3(counter uint32, node []byte) []byte { 49 | var n bytes.Buffer 50 | n.WriteByte(0) 51 | binary.Write(&n, binary.LittleEndian, counter) 52 | n.Write(node) 53 | return n.Bytes() 54 | } 55 | 56 | type SecureChannel struct { 57 | Udp *udpChannel 58 | encrypt_key []byte 59 | decrypt_key []byte 60 | remote_node []byte 61 | local_node []byte 62 | Counter uint32 63 | session int 64 | } 65 | 66 | // StartSecureChannel initializes secure channel for plain unencrypted communication. 67 | // It initializes UDP interface and blocks local udp port. 68 | // Secure channel becomes encrypted after encryption keys are supplied. 69 | func StartSecureChannel(remote_ip net.IP, remote_port, local_port int) (SecureChannel, error) { 70 | udp, err := startUdpChannel(remote_ip, remote_port, local_port) 71 | if err != nil { 72 | return SecureChannel{}, err 73 | } 74 | return SecureChannel{ 75 | Udp: udp, 76 | Counter: uint32(rand.Intn(0xffffffff)), 77 | }, nil 78 | } 79 | 80 | func (sc *SecureChannel) Receive() (DecodedGeneric, error) { 81 | sc.Udp.Udp.SetReadDeadline(time.Now().Add(time.Second * 3)) 82 | data, err := sc.Udp.receive() 83 | if err != nil { 84 | return DecodedGeneric{}, err 85 | } 86 | decode_buffer := bytes.NewBuffer(data) 87 | var out DecodedGeneric 88 | out.MessageHeader.Decode(decode_buffer) 89 | add := data[:len(data)-decode_buffer.Len()] 90 | proto := decode_buffer.Bytes() 91 | 92 | if len(sc.decrypt_key) > 0 { 93 | nonce := make_nonce3(out.MessageHeader.messageCounter, sc.remote_node) 94 | c, err := aes.NewCipher(sc.decrypt_key) 95 | if err != nil { 96 | return DecodedGeneric{}, err 97 | } 98 | ccm, err := ccm.NewCCM(c, 16, len(nonce)) 99 | if err != nil { 100 | return DecodedGeneric{}, err 101 | } 102 | ciphertext := proto 103 | decbuf := []byte{} 104 | outx, err := ccm.Open(decbuf, nonce, ciphertext, add) 105 | if err != nil { 106 | return DecodedGeneric{}, err 107 | } 108 | 109 | decoder := bytes.NewBuffer(outx) 110 | 111 | out.ProtocolHeader.Decode(decoder) 112 | if len(decoder.Bytes()) > 0 { 113 | tlvdata := make([]byte, decoder.Len()) 114 | n, _ := decoder.Read(tlvdata) 115 | out.Payload = tlvdata[:n] 116 | } 117 | } else { 118 | out.ProtocolHeader.Decode(decode_buffer) 119 | if len(decode_buffer.Bytes()) > 0 { 120 | tlvdata := make([]byte, decode_buffer.Len()) 121 | n, _ := decode_buffer.Read(tlvdata) 122 | out.Payload = tlvdata[:n] 123 | } 124 | } 125 | 126 | if out.ProtocolHeader.ProtocolId == 0 { 127 | if out.ProtocolHeader.Opcode == SEC_CHAN_OPCODE_ACK { // standalone ack 128 | return sc.Receive() 129 | } 130 | } 131 | 132 | ack := ackGen(out.ProtocolHeader, out.MessageHeader.messageCounter) 133 | sc.Send(ack) 134 | 135 | if out.ProtocolHeader.ProtocolId == 0 { 136 | if out.ProtocolHeader.Opcode == SEC_CHAN_OPCODE_STATUS_REP { // status report 137 | buf := bytes.NewBuffer(out.Payload) 138 | binary.Read(buf, binary.LittleEndian, &out.StatusReport.GeneralCode) 139 | binary.Read(buf, binary.LittleEndian, &out.StatusReport.ProtocolId) 140 | binary.Read(buf, binary.LittleEndian, &out.StatusReport.ProtocolCode) 141 | return out, nil 142 | } 143 | } 144 | if len(out.Payload) > 0 { 145 | out.Tlv = mattertlv.Decode(out.Payload) 146 | } 147 | return out, nil 148 | } 149 | 150 | // Send sends Protocol Message via secure channel. It creates Matter Message by adding Message Header. 151 | // Protocol Message is aes-ccm encrypted when channel does have encryption keys. 152 | // When encryption keys are empty plain Message is sent. 153 | func (sc *SecureChannel) Send(data []byte) error { 154 | 155 | sc.Counter = sc.Counter + 1 156 | var buffer bytes.Buffer 157 | msg := MessageHeader{ 158 | sessionId: uint16(sc.session), 159 | securityFlags: 0, 160 | messageCounter: sc.Counter, 161 | sourceNodeId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, 162 | } 163 | msg.Encode(&buffer) 164 | if len(sc.encrypt_key) == 0 { 165 | buffer.Write(data) 166 | } else { 167 | header_slice := buffer.Bytes() 168 | add2 := make([]byte, len(header_slice)) 169 | copy(add2, header_slice) 170 | 171 | nonce := make_nonce3(sc.Counter, sc.local_node) 172 | 173 | c, err := aes.NewCipher(sc.encrypt_key) 174 | if err != nil { 175 | return err 176 | } 177 | ccm, err := ccm.NewCCM(c, 16, len(nonce)) 178 | if err != nil { 179 | return err 180 | } 181 | CipherText := ccm.Seal(nil, nonce, data, add2) 182 | buffer.Write(CipherText) 183 | } 184 | 185 | err := sc.Udp.send(buffer.Bytes()) 186 | return err 187 | } 188 | 189 | // Close secure channel. Send close session message to remote end and relase UDP port. 190 | func (sc *SecureChannel) Close() { 191 | if sc.Udp == nil || sc.Udp.Udp == nil { 192 | return 193 | } 194 | sr := EncodeStatusReport(StatusReportElements{ 195 | GeneralCode: 0, 196 | ProtocolId: 0, 197 | ProtocolCode: 3, //close session 198 | }) 199 | sc.Send(sr) 200 | sc.Udp.Udp.Close() 201 | } 202 | -------------------------------------------------------------------------------- /sigma.go: -------------------------------------------------------------------------------- 1 | package gomat 2 | 3 | import ( 4 | "bytes" 5 | "crypto/aes" 6 | "crypto/ecdh" 7 | "crypto/ecdsa" 8 | "crypto/elliptic" 9 | "crypto/rand" 10 | "encoding/binary" 11 | 12 | "github.com/tom-code/gomat/ccm" 13 | "github.com/tom-code/gomat/mattertlv" 14 | ) 15 | 16 | type sigmaContext struct { 17 | session_privkey *ecdh.PrivateKey 18 | session int 19 | controller_key *ecdsa.PrivateKey 20 | controller_matter_certificate []byte 21 | 22 | i2rkey []byte 23 | r2ikey []byte 24 | 25 | sigma2dec DecodedGeneric 26 | sigma1payload []byte 27 | exchange uint16 28 | } 29 | 30 | func (sc *sigmaContext) genSigma1(fabric *Fabric, device_id uint64) { 31 | var tlvx mattertlv.TLVBuffer 32 | tlvx.WriteAnonStruct() 33 | 34 | initiatorRandom := make([]byte, 32) 35 | rand.Read(initiatorRandom) 36 | tlvx.WriteOctetString(1, initiatorRandom) 37 | 38 | sessionId := 222 39 | tlvx.WriteUInt(2, mattertlv.TYPE_UINT_2, uint64(sessionId)) 40 | 41 | var destination_message bytes.Buffer 42 | destination_message.Write(initiatorRandom) 43 | //cacert := ca.LoadCert("ca-cert.pem") 44 | cacert := fabric.CertificateManager.GetCaCertificate() 45 | capub := cacert.PublicKey.(*ecdsa.PublicKey) 46 | capublic_key := elliptic.Marshal(elliptic.P256(), capub.X, capub.Y) 47 | 48 | destination_message.Write(capublic_key) 49 | 50 | var fabric_id uint64 51 | fabric_id = fabric.id 52 | binary.Write(&destination_message, binary.LittleEndian, fabric_id) 53 | 54 | var node uint64 55 | node = device_id 56 | binary.Write(&destination_message, binary.LittleEndian, node) 57 | 58 | key := fabric.make_ipk() 59 | 60 | destinationIdentifier := hmac_sha256_enc(destination_message.Bytes(), key) 61 | 62 | tlvx.WriteOctetString(3, destinationIdentifier) 63 | 64 | tlvx.WriteOctetString(4, sc.session_privkey.PublicKey().Bytes()) 65 | tlvx.WriteStructEnd() 66 | sc.sigma1payload = tlvx.Bytes() 67 | } 68 | 69 | func genSigma1Req2(payload []byte, exchange uint16) []byte { 70 | var buffer bytes.Buffer 71 | prot := ProtocolMessageHeader{ 72 | exchangeFlags: 5, 73 | Opcode: 0x30, //sigma1 74 | ExchangeId: exchange, 75 | ProtocolId: 0x00, 76 | } 77 | prot.Encode(&buffer) 78 | 79 | buffer.Write(payload) 80 | return buffer.Bytes() 81 | } 82 | 83 | func genSigma3Req2(payload []byte, exchange uint16) []byte { 84 | var buffer bytes.Buffer 85 | prot := ProtocolMessageHeader{ 86 | exchangeFlags: 5, 87 | Opcode: 0x32, //sigma1 88 | ExchangeId: exchange, 89 | ProtocolId: 0x00} 90 | 91 | prot.Encode(&buffer) 92 | 93 | buffer.Write(payload) 94 | return buffer.Bytes() 95 | } 96 | 97 | func (sc *sigmaContext) sigma3(fabric *Fabric) ([]byte, error) { 98 | var tlv_s3tbs mattertlv.TLVBuffer 99 | tlv_s3tbs.WriteAnonStruct() 100 | tlv_s3tbs.WriteOctetString(1, sc.controller_matter_certificate) 101 | tlv_s3tbs.WriteOctetString(3, sc.session_privkey.PublicKey().Bytes()) 102 | responder_public := sc.sigma2dec.Tlv.GetOctetStringRec([]int{3}) 103 | sigma2responder_session, err := sc.sigma2dec.Tlv.GetIntRec([]int{2}) 104 | if err != nil { 105 | return []byte{}, err 106 | } 107 | tlv_s3tbs.WriteOctetString(4, responder_public) 108 | tlv_s3tbs.WriteStructEnd() 109 | //log.Printf("responder public %s\n", hex.EncodeToString(responder_public)) 110 | 111 | tlv_s3tbs_hash := sha256_enc(tlv_s3tbs.Bytes()) 112 | sr, ss, err := ecdsa.Sign(rand.Reader, sc.controller_key, tlv_s3tbs_hash) 113 | if err != nil { 114 | return []byte{}, err 115 | } 116 | tlv_s3tbs_out := append(sr.Bytes(), ss.Bytes()...) 117 | 118 | var tlv_s3tbe mattertlv.TLVBuffer 119 | tlv_s3tbe.WriteAnonStruct() 120 | tlv_s3tbe.WriteOctetString(1, sc.controller_matter_certificate) 121 | tlv_s3tbe.WriteOctetString(3, tlv_s3tbs_out) 122 | tlv_s3tbe.WriteStructEnd() 123 | 124 | pub, err := ecdh.P256().NewPublicKey(responder_public) 125 | if err != nil { 126 | return []byte{}, err 127 | } 128 | shared_secret, err := sc.session_privkey.ECDH(pub) 129 | if err != nil { 130 | return []byte{}, err 131 | } 132 | s3k_th := sc.sigma1payload 133 | s3k_th = append(s3k_th, sc.sigma2dec.Payload...) 134 | 135 | transcript_hash := sha256_enc(s3k_th) 136 | s3_salt := fabric.make_ipk() 137 | s3_salt = append(s3_salt, transcript_hash...) 138 | 139 | s3k := hkdf_sha256(shared_secret, s3_salt, []byte("Sigma3"), 16) 140 | 141 | c, err := aes.NewCipher(s3k) 142 | if err != nil { 143 | return []byte{}, err 144 | } 145 | nonce := []byte("NCASE_Sigma3N") 146 | ccm, err := ccm.NewCCM(c, 16, len(nonce)) 147 | if err != nil { 148 | return []byte{}, err 149 | } 150 | CipherText := ccm.Seal(nil, nonce, tlv_s3tbe.Bytes(), []byte{}) 151 | 152 | var tlv_s3 mattertlv.TLVBuffer 153 | tlv_s3.WriteAnonStruct() 154 | tlv_s3.WriteOctetString(1, CipherText) 155 | tlv_s3.WriteStructEnd() 156 | 157 | to_send := genSigma3Req2(tlv_s3.Bytes(), sc.exchange) 158 | 159 | // prepare session keys 160 | ses_key_transcript := s3k_th 161 | ses_key_transcript = append(ses_key_transcript, tlv_s3.Bytes()...) 162 | transcript_hash = sha256_enc(ses_key_transcript) 163 | salt := fabric.make_ipk() 164 | salt = append(salt, transcript_hash...) 165 | 166 | keypack := hkdf_sha256(shared_secret, salt, []byte("SessionKeys"), 16*3) 167 | sc.session = int(sigma2responder_session) 168 | 169 | sc.i2rkey = keypack[:16] 170 | sc.r2ikey = keypack[16:32] 171 | 172 | return to_send, nil 173 | } 174 | -------------------------------------------------------------------------------- /spake2p.go: -------------------------------------------------------------------------------- 1 | package gomat 2 | 3 | import ( 4 | "bytes" 5 | "crypto/elliptic" 6 | "crypto/sha256" 7 | "encoding/binary" 8 | "encoding/hex" 9 | "fmt" 10 | "math/big" 11 | 12 | "golang.org/x/crypto/pbkdf2" 13 | ) 14 | 15 | func pinToPasscode(pin uint32) []byte { 16 | var buf bytes.Buffer 17 | binary.Write(&buf, binary.LittleEndian, pin) 18 | return buf.Bytes() 19 | } 20 | 21 | type point struct { 22 | x *big.Int 23 | y *big.Int 24 | } 25 | 26 | func (p point) dump() { 27 | fmt.Printf(" x: %v\n", p.x) 28 | fmt.Printf(" y: %v\n", p.y) 29 | } 30 | func (p point) As_bytes() []byte { 31 | o1 := elliptic.Marshal(elliptic.P256(), p.x, p.y) 32 | return o1 33 | } 34 | func (p *point) from_bytes(in []byte) { 35 | p.x, p.y = elliptic.Unmarshal(elliptic.P256(), in) 36 | } 37 | 38 | var spake_seed_M point 39 | var spake_seed_N point 40 | 41 | func init() { 42 | mhex := "02886e2f97ace46e55ba9dd7242579f2993b64e16ef3dcab95afd497333d8fa12f" 43 | mbin, _ := hex.DecodeString(mhex) 44 | spake_seed_M.x, spake_seed_M.y = elliptic.UnmarshalCompressed(elliptic.P256(), mbin) 45 | 46 | nhex := "03d8bbd6c639c62937b04d997f38c3770719c629d7014d49a24b4f98baa1292b49" 47 | nbin, _ := hex.DecodeString(nhex) 48 | spake_seed_N.x, spake_seed_N.y = elliptic.UnmarshalCompressed(elliptic.P256(), nbin) 49 | } 50 | 51 | func serializeBytes(buf *bytes.Buffer, p []byte) { 52 | ln := uint64(len(p)) 53 | binary.Write(buf, binary.LittleEndian, ln) 54 | buf.Write(p) 55 | } 56 | 57 | func createTT(context []byte, a, b string, m, n, x, y, z, v []byte, w0 []byte) []byte { 58 | var buf bytes.Buffer 59 | serializeBytes(&buf, context) 60 | serializeBytes(&buf, []byte(a)) 61 | serializeBytes(&buf, []byte(b)) 62 | 63 | serializeBytes(&buf, m) 64 | serializeBytes(&buf, n) 65 | serializeBytes(&buf, x) 66 | serializeBytes(&buf, y) 67 | serializeBytes(&buf, z) 68 | serializeBytes(&buf, v) 69 | serializeBytes(&buf, w0) 70 | return buf.Bytes() 71 | } 72 | 73 | type SpakeCtx struct { 74 | curve elliptic.Curve 75 | W0 []byte 76 | W1 []byte 77 | x_random big.Int 78 | y_random big.Int 79 | X point 80 | Y point 81 | Z point 82 | V point 83 | L point 84 | cA []byte 85 | cB []byte 86 | Ke []byte 87 | Ka []byte 88 | encrypt_key []byte 89 | decrypt_key []byte 90 | } 91 | 92 | func (ctx *SpakeCtx) Gen_w(passcode int, salt []byte, iterations int) { 93 | pwd := pinToPasscode(uint32(passcode)) 94 | ws := pbkdf2.Key(pwd, salt, iterations, 80, sha256.New) 95 | w0 := ws[:40] 96 | w1 := ws[40:80] 97 | 98 | curve := elliptic.P256() 99 | w0b := new(big.Int) 100 | w0b.SetBytes(w0) 101 | ctx.W0 = w0b.Mod(w0b, curve.Params().N).Bytes() 102 | 103 | w1b := new(big.Int) 104 | w1b.SetBytes(w1) 105 | ctx.W1 = w1b.Mod(w1b, curve.Params().N).Bytes() 106 | 107 | } 108 | 109 | func (ctx *SpakeCtx) Gen_random_X() { 110 | ctx.x_random.SetBytes(CreateRandomBytes(32)) 111 | } 112 | func (ctx *SpakeCtx) Gen_random_Y() { 113 | ctx.y_random.SetBytes(CreateRandomBytes(32)) 114 | } 115 | func (ctx *SpakeCtx) Calc_X() { 116 | // X=x*P+w0*M 117 | tx, ty := ctx.curve.ScalarBaseMult(ctx.x_random.Bytes()) 118 | px, py := ctx.curve.ScalarMult(spake_seed_M.x, spake_seed_M.y, ctx.W0) 119 | ctx.X.x, ctx.X.y = ctx.curve.Add(tx, ty, px, py) 120 | } 121 | func (ctx *SpakeCtx) calc_Y() { 122 | //Y=y*P, pB=w*N+Y 123 | ypx, ypy := ctx.curve.ScalarMult(spake_seed_N.x, spake_seed_N.y, ctx.W0) 124 | ytx, yty := ctx.curve.ScalarBaseMult(ctx.y_random.Bytes()) 125 | ctx.Y.x, ctx.Y.y = ctx.curve.Add(ytx, yty, ypx, ypy) 126 | } 127 | 128 | func (ctx *SpakeCtx) calc_ZV() { 129 | //A computes Z as h*x*(Y-w0*N), and V as h*w1*(Y-w0*N). 130 | wnx, wny := ctx.curve.ScalarMult(spake_seed_N.x, spake_seed_N.y, ctx.W0) 131 | wny = wny.Neg(wny) 132 | wny = wny.Mod(wny, ctx.curve.Params().P) 133 | znx, zny := ctx.curve.Add(ctx.Y.x, ctx.Y.y, wnx, wny) 134 | ctx.Z.x, ctx.Z.y = ctx.curve.ScalarMult(znx, zny, ctx.x_random.Bytes()) 135 | 136 | ctx.V.x, ctx.V.y = ctx.curve.ScalarMult(znx, zny, ctx.W1) 137 | 138 | } 139 | 140 | func (ctx *SpakeCtx) Calc_ZVb() { 141 | //B computes Z as y(X-w0*M) and V as yL 142 | unx, uny := ctx.curve.ScalarMult(spake_seed_M.x, spake_seed_M.y, ctx.W0) 143 | uny = uny.Neg(uny) 144 | uny = uny.Mod(uny, ctx.curve.Params().P) 145 | zznx, zzny := ctx.curve.Add(ctx.X.x, ctx.X.y, unx, uny) 146 | ctx.Z.x, ctx.Z.y = ctx.curve.ScalarMult(zznx, zzny, ctx.y_random.Bytes()) 147 | ctx.L.x, ctx.L.y = ctx.curve.ScalarBaseMult(ctx.W1) 148 | ctx.V.x, ctx.V.y = ctx.curve.ScalarMult(ctx.L.x, ctx.L.y, ctx.y_random.Bytes()) 149 | } 150 | 151 | func (ctx *SpakeCtx) calc_hash(seed []byte) error { 152 | 153 | sh0sum := sha256_enc(seed) 154 | mbin := elliptic.Marshal(elliptic.P256(), spake_seed_M.x, spake_seed_M.y) 155 | nbin := elliptic.Marshal(elliptic.P256(), spake_seed_N.x, spake_seed_N.y) 156 | tt := createTT(sh0sum, "", "", mbin, nbin, ctx.X.As_bytes(), ctx.Y.As_bytes(), ctx.Z.As_bytes(), ctx.V.As_bytes(), ctx.W0) 157 | 158 | sh1sum := sha256_enc(tt) 159 | 160 | ctx.Ka = sh1sum[:16] 161 | ctx.Ke = sh1sum[16:32] 162 | 163 | key := hkdf_sha256(ctx.Ka, nil, []byte("ConfirmationKeys"), 32) 164 | 165 | ctx.cA = hmac_sha256_enc(ctx.Y.As_bytes(), key[:16]) 166 | ctx.cB = hmac_sha256_enc(ctx.X.As_bytes(), key[16:]) 167 | 168 | Xcryptkey := hkdf_sha256(ctx.Ke, nil, []byte("SessionKeys"), 16*3) 169 | ctx.decrypt_key = Xcryptkey[16:32] 170 | ctx.encrypt_key = Xcryptkey[:16] 171 | return nil 172 | } 173 | 174 | func NewSpaceCtx() SpakeCtx { 175 | return SpakeCtx{ 176 | curve: elliptic.P256(), 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /symbols/README.md: -------------------------------------------------------------------------------- 1 | 2 | This directory contains .go and .json files with identifiers of clusters, commands and attributes. 3 | These are generated using tools in gen directory. 4 | 5 | As source data xml from following directory is used: https://github.com/project-chip/connectedhomeip/tree/master/data_model/clusters 6 | -------------------------------------------------------------------------------- /symbols/gen/process.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "encoding/xml" 6 | "fmt" 7 | "log" 8 | "os" 9 | "path/filepath" 10 | "strconv" 11 | "strings" 12 | ) 13 | 14 | type MatterInfo struct { 15 | Clusters map[int]ClusterInfo 16 | } 17 | 18 | type ClusterInfo struct { 19 | Name string 20 | Id int 21 | Commands []CommandInfo 22 | Attributes []AttributeInfo 23 | } 24 | 25 | type CommandInfo struct { 26 | Name string 27 | Id int 28 | } 29 | type AttributeInfo struct { 30 | Name string 31 | Id int 32 | } 33 | 34 | type CommandXmlDef struct { 35 | Name string `xml:"name,attr"` 36 | Id string `xml:"id,attr"` 37 | } 38 | 39 | type CommandListXmlDef struct { 40 | Command []CommandXmlDef `xml:"command"` 41 | } 42 | 43 | type AttributeXmlDef struct { 44 | Name string `xml:"name,attr"` 45 | Id string `xml:"id,attr"` 46 | } 47 | 48 | type AttributeListXmlDef struct { 49 | Attribute []AttributeXmlDef `xml:"attribute"` 50 | } 51 | 52 | type ClusterXmlDef struct { 53 | XMLName xml.Name `xml:"cluster"` 54 | Name string `xml:"name,attr"` 55 | Id string `xml:"id,attr"` 56 | Commands CommandListXmlDef `xml:"commands"` 57 | Attributes AttributeListXmlDef `xml:"attributes"` 58 | } 59 | 60 | func symbolize(in string) string { 61 | s := strings.ReplaceAll(in, " ", "") 62 | s = strings.ReplaceAll(s, "-", "") 63 | s = strings.ReplaceAll(s, "/", "") 64 | return s 65 | } 66 | 67 | func process_file(fname string) (ClusterInfo, error) { 68 | xml_content, err := os.ReadFile(fname) 69 | var out ClusterInfo 70 | if err != nil { 71 | return out, err 72 | } 73 | 74 | var parsed_xml ClusterXmlDef 75 | err = xml.Unmarshal(xml_content, &parsed_xml) 76 | if err != nil { 77 | return out, err 78 | } 79 | log.Printf("%+v\n", parsed_xml) 80 | out.Name = symbolize(parsed_xml.Name) 81 | id, err := strconv.ParseUint(parsed_xml.Id, 0, 32) 82 | if err != nil { 83 | return out, err 84 | } 85 | out.Id = int(id) 86 | 87 | for _, command := range parsed_xml.Commands.Command { 88 | id, err := strconv.ParseUint(command.Id, 0, 32) 89 | if err != nil { 90 | continue 91 | } 92 | cmd := CommandInfo{ 93 | Name: symbolize(command.Name), 94 | Id: int(id), 95 | } 96 | out.Commands = append(out.Commands, cmd) 97 | } 98 | deduplicate := map[string]bool{} 99 | for _, attribute := range parsed_xml.Attributes.Attribute { 100 | _, duplicit := deduplicate[attribute.Name] 101 | if duplicit { 102 | continue 103 | } else { 104 | deduplicate[attribute.Name] = true 105 | } 106 | id, err := strconv.ParseUint(attribute.Id, 0, 32) 107 | if err != nil { 108 | continue 109 | } 110 | attr := AttributeInfo{ 111 | Name: symbolize(attribute.Name), 112 | Id: int(id), 113 | } 114 | out.Attributes = append(out.Attributes, attr) 115 | } 116 | return out, nil 117 | } 118 | 119 | func writeGoInfo(mi MatterInfo) error { 120 | f, err := os.Create("../info.go") 121 | if err != nil { 122 | return err 123 | } 124 | defer f.Close() 125 | f.WriteString("package symbols\n\n") 126 | 127 | for _, cluster := range mi.Clusters { 128 | f.WriteString(fmt.Sprintf("const CLUSTER_ID_%s = 0x%x\n", cluster.Name, cluster.Id)) 129 | for _, command := range cluster.Commands { 130 | f.WriteString(fmt.Sprintf("const COMMAND_ID_%s_%s = %d\n", cluster.Name, command.Name, command.Id)) 131 | } 132 | for _, attribute := range cluster.Attributes { 133 | f.WriteString(fmt.Sprintf("const ATTRIBUTE_ID_%s_%s = %d\n", cluster.Name, attribute.Name, attribute.Id)) 134 | } 135 | } 136 | 137 | f.WriteString("var ClusterNameMap = map[int]string {\n") 138 | for _, cluster := range mi.Clusters { 139 | f.WriteString(fmt.Sprintf(" CLUSTER_ID_%s: \"%s\",\n", cluster.Name, cluster.Name)) 140 | } 141 | f.WriteString("}") 142 | 143 | return nil 144 | } 145 | 146 | func process_all() (MatterInfo, error) { 147 | var mi MatterInfo 148 | mi.Clusters = map[int]ClusterInfo{} 149 | files, err := os.ReadDir(xmlPath) 150 | if err != nil { 151 | return mi, err 152 | } 153 | for _, e := range files { 154 | fname := filepath.Join(xmlPath, e.Name()) 155 | log.Println(fname) 156 | c, err := process_file(fname) 157 | if err != nil { 158 | log.Println(err) 159 | } 160 | mi.Clusters[c.Id] = c 161 | log.Println(c) 162 | } 163 | return mi, nil 164 | } 165 | 166 | const xmlPath = "../xml" 167 | 168 | func main() { 169 | mi, err := process_all() 170 | if err != nil { 171 | panic(err) 172 | } 173 | writeGoInfo(mi) 174 | jsondata, err := json.MarshalIndent(&mi, "", " ") 175 | if err != nil { 176 | panic(err) 177 | } 178 | os.WriteFile("../info.json", jsondata, 0666) 179 | 180 | } 181 | -------------------------------------------------------------------------------- /symbols/info.go: -------------------------------------------------------------------------------- 1 | package symbols 2 | 3 | const CLUSTER_ID_PressureMeasurement = 0x403 4 | const ATTRIBUTE_ID_PressureMeasurement_MeasuredValue = 0 5 | const ATTRIBUTE_ID_PressureMeasurement_MinMeasuredValue = 1 6 | const ATTRIBUTE_ID_PressureMeasurement_MaxMeasuredValue = 2 7 | const ATTRIBUTE_ID_PressureMeasurement_Tolerance = 3 8 | const ATTRIBUTE_ID_PressureMeasurement_ScaledValue = 16 9 | const ATTRIBUTE_ID_PressureMeasurement_MinScaledValue = 17 10 | const ATTRIBUTE_ID_PressureMeasurement_MaxScaledValue = 18 11 | const ATTRIBUTE_ID_PressureMeasurement_ScaledTolerance = 19 12 | const ATTRIBUTE_ID_PressureMeasurement_Scale = 20 13 | const CLUSTER_ID_TargetNavigator = 0x505 14 | const COMMAND_ID_TargetNavigator_NavigateTarget = 0 15 | const COMMAND_ID_TargetNavigator_NavigateTargetResponse = 1 16 | const ATTRIBUTE_ID_TargetNavigator_TargetList = 0 17 | const ATTRIBUTE_ID_TargetNavigator_CurrentTarget = 1 18 | const CLUSTER_ID_AccessControl = 0x1f 19 | const ATTRIBUTE_ID_AccessControl_ACL = 0 20 | const ATTRIBUTE_ID_AccessControl_Extension = 1 21 | const ATTRIBUTE_ID_AccessControl_SubjectsPerAccessControlEntry = 2 22 | const ATTRIBUTE_ID_AccessControl_TargetsPerAccessControlEntry = 3 23 | const ATTRIBUTE_ID_AccessControl_AccessControlEntriesPerFabric = 4 24 | const CLUSTER_ID_BallastConfiguration = 0x301 25 | const ATTRIBUTE_ID_BallastConfiguration_PhysicalMinLevel = 0 26 | const ATTRIBUTE_ID_BallastConfiguration_PhysicalMaxLevel = 1 27 | const ATTRIBUTE_ID_BallastConfiguration_BallastStatus = 2 28 | const ATTRIBUTE_ID_BallastConfiguration_MinLevel = 16 29 | const ATTRIBUTE_ID_BallastConfiguration_MaxLevel = 17 30 | const ATTRIBUTE_ID_BallastConfiguration_PowerOnLevel = 18 31 | const ATTRIBUTE_ID_BallastConfiguration_PowerOnFadeTime = 19 32 | const ATTRIBUTE_ID_BallastConfiguration_IntrinsicBallastFactor = 20 33 | const ATTRIBUTE_ID_BallastConfiguration_BallastFactorAdjustment = 21 34 | const ATTRIBUTE_ID_BallastConfiguration_LampQuantity = 32 35 | const ATTRIBUTE_ID_BallastConfiguration_LampType = 48 36 | const ATTRIBUTE_ID_BallastConfiguration_LampManufacturer = 49 37 | const ATTRIBUTE_ID_BallastConfiguration_LampRatedHours = 50 38 | const ATTRIBUTE_ID_BallastConfiguration_LampBurnHours = 51 39 | const ATTRIBUTE_ID_BallastConfiguration_LampAlarmMode = 52 40 | const ATTRIBUTE_ID_BallastConfiguration_LampBurnHoursTripPoint = 53 41 | const CLUSTER_ID_MediaPlayback = 0x506 42 | const COMMAND_ID_MediaPlayback_Play = 0 43 | const COMMAND_ID_MediaPlayback_Pause = 1 44 | const COMMAND_ID_MediaPlayback_Stop = 2 45 | const COMMAND_ID_MediaPlayback_StartOver = 3 46 | const COMMAND_ID_MediaPlayback_Previous = 4 47 | const COMMAND_ID_MediaPlayback_Next = 5 48 | const COMMAND_ID_MediaPlayback_Rewind = 6 49 | const COMMAND_ID_MediaPlayback_FastForward = 7 50 | const COMMAND_ID_MediaPlayback_SkipForward = 8 51 | const COMMAND_ID_MediaPlayback_SkipBackward = 9 52 | const COMMAND_ID_MediaPlayback_PlaybackResponse = 10 53 | const COMMAND_ID_MediaPlayback_Seek = 11 54 | const ATTRIBUTE_ID_MediaPlayback_CurrentState = 0 55 | const ATTRIBUTE_ID_MediaPlayback_StartTime = 1 56 | const ATTRIBUTE_ID_MediaPlayback_Duration = 2 57 | const ATTRIBUTE_ID_MediaPlayback_SampledPosition = 3 58 | const ATTRIBUTE_ID_MediaPlayback_PlaybackSpeed = 4 59 | const ATTRIBUTE_ID_MediaPlayback_SeekRangeEnd = 5 60 | const ATTRIBUTE_ID_MediaPlayback_SeekRangeStart = 6 61 | const CLUSTER_ID_MicrowaveOvenMode = 0x5e 62 | const COMMAND_ID_MicrowaveOvenMode_ChangeToMode = 0 63 | const COMMAND_ID_MicrowaveOvenMode_ChangeToModeResponse = 1 64 | const ATTRIBUTE_ID_MicrowaveOvenMode_SupportedModes = 0 65 | const ATTRIBUTE_ID_MicrowaveOvenMode_CurrentMode = 1 66 | const ATTRIBUTE_ID_MicrowaveOvenMode_StartUpMode = 2 67 | const ATTRIBUTE_ID_MicrowaveOvenMode_OnMode = 3 68 | const CLUSTER_ID_OperationalCredentials = 0x3e 69 | const COMMAND_ID_OperationalCredentials_AttestationRequest = 0 70 | const COMMAND_ID_OperationalCredentials_AttestationResponse = 1 71 | const COMMAND_ID_OperationalCredentials_CertificateChainRequest = 2 72 | const COMMAND_ID_OperationalCredentials_CertificateChainResponse = 3 73 | const COMMAND_ID_OperationalCredentials_CSRRequest = 4 74 | const COMMAND_ID_OperationalCredentials_CSRResponse = 5 75 | const COMMAND_ID_OperationalCredentials_AddNOC = 6 76 | const COMMAND_ID_OperationalCredentials_UpdateNOC = 7 77 | const COMMAND_ID_OperationalCredentials_NOCResponse = 8 78 | const COMMAND_ID_OperationalCredentials_UpdateFabricLabel = 9 79 | const COMMAND_ID_OperationalCredentials_RemoveFabric = 10 80 | const COMMAND_ID_OperationalCredentials_AddTrustedRootCertificate = 11 81 | const ATTRIBUTE_ID_OperationalCredentials_NOCs = 0 82 | const ATTRIBUTE_ID_OperationalCredentials_Fabrics = 1 83 | const ATTRIBUTE_ID_OperationalCredentials_SupportedFabrics = 2 84 | const ATTRIBUTE_ID_OperationalCredentials_CommissionedFabrics = 3 85 | const ATTRIBUTE_ID_OperationalCredentials_TrustedRootCertificates = 4 86 | const ATTRIBUTE_ID_OperationalCredentials_CurrentFabricIndex = 5 87 | const CLUSTER_ID_OperationalState = 0x60 88 | const COMMAND_ID_OperationalState_Pause = 0 89 | const COMMAND_ID_OperationalState_Stop = 1 90 | const COMMAND_ID_OperationalState_Start = 2 91 | const COMMAND_ID_OperationalState_Resume = 3 92 | const COMMAND_ID_OperationalState_OperationalCommandResponse = 4 93 | const ATTRIBUTE_ID_OperationalState_PhaseList = 0 94 | const ATTRIBUTE_ID_OperationalState_CurrentPhase = 1 95 | const ATTRIBUTE_ID_OperationalState_CountdownTime = 2 96 | const ATTRIBUTE_ID_OperationalState_OperationalStateList = 3 97 | const ATTRIBUTE_ID_OperationalState_OperationalState = 4 98 | const ATTRIBUTE_ID_OperationalState_OperationalError = 5 99 | const CLUSTER_ID_WindowCovering = 0x102 100 | const COMMAND_ID_WindowCovering_UpOrOpen = 0 101 | const COMMAND_ID_WindowCovering_DownOrClose = 1 102 | const COMMAND_ID_WindowCovering_StopMotion = 2 103 | const COMMAND_ID_WindowCovering_GoToLiftValue = 4 104 | const COMMAND_ID_WindowCovering_GoToLiftPercentage = 5 105 | const COMMAND_ID_WindowCovering_GoToTiltValue = 7 106 | const COMMAND_ID_WindowCovering_GoToTiltPercentage = 8 107 | const ATTRIBUTE_ID_WindowCovering_Type = 0 108 | const ATTRIBUTE_ID_WindowCovering_PhysicalClosedLimitLift = 1 109 | const ATTRIBUTE_ID_WindowCovering_PhysicalClosedLimitTilt = 2 110 | const ATTRIBUTE_ID_WindowCovering_CurrentPositionLift = 3 111 | const ATTRIBUTE_ID_WindowCovering_CurrentPositionTilt = 4 112 | const ATTRIBUTE_ID_WindowCovering_NumberOfActuationsLift = 5 113 | const ATTRIBUTE_ID_WindowCovering_NumberOfActuationsTilt = 6 114 | const ATTRIBUTE_ID_WindowCovering_ConfigStatus = 7 115 | const ATTRIBUTE_ID_WindowCovering_CurrentPositionLiftPercentage = 8 116 | const ATTRIBUTE_ID_WindowCovering_CurrentPositionTiltPercentage = 9 117 | const ATTRIBUTE_ID_WindowCovering_OperationalStatus = 10 118 | const ATTRIBUTE_ID_WindowCovering_TargetPositionLiftPercent100ths = 11 119 | const ATTRIBUTE_ID_WindowCovering_TargetPositionTiltPercent100ths = 12 120 | const ATTRIBUTE_ID_WindowCovering_EndProductType = 13 121 | const ATTRIBUTE_ID_WindowCovering_CurrentPositionLiftPercent100ths = 14 122 | const ATTRIBUTE_ID_WindowCovering_CurrentPositionTiltPercent100ths = 15 123 | const ATTRIBUTE_ID_WindowCovering_InstalledOpenLimitLift = 16 124 | const ATTRIBUTE_ID_WindowCovering_InstalledClosedLimitLift = 17 125 | const ATTRIBUTE_ID_WindowCovering_InstalledOpenLimitTilt = 18 126 | const ATTRIBUTE_ID_WindowCovering_InstalledClosedLimitTilt = 19 127 | const ATTRIBUTE_ID_WindowCovering_VelocityLift = 20 128 | const ATTRIBUTE_ID_WindowCovering_AccelerationTimeLift = 21 129 | const ATTRIBUTE_ID_WindowCovering_DecelerationTimeLift = 22 130 | const ATTRIBUTE_ID_WindowCovering_Mode = 23 131 | const ATTRIBUTE_ID_WindowCovering_IntermediateSetpointsLift = 24 132 | const ATTRIBUTE_ID_WindowCovering_IntermediateSetpointsTilt = 25 133 | const ATTRIBUTE_ID_WindowCovering_SafetyStatus = 26 134 | const CLUSTER_ID_Descriptor = 0x1d 135 | const ATTRIBUTE_ID_Descriptor_DeviceTypeList = 0 136 | const ATTRIBUTE_ID_Descriptor_ServerList = 1 137 | const ATTRIBUTE_ID_Descriptor_ClientList = 2 138 | const ATTRIBUTE_ID_Descriptor_PartsList = 3 139 | const ATTRIBUTE_ID_Descriptor_TagList = 4 140 | const CLUSTER_ID_ThreadNetworkDiagnostics = 0x35 141 | const COMMAND_ID_ThreadNetworkDiagnostics_ResetCounts = 0 142 | const ATTRIBUTE_ID_ThreadNetworkDiagnostics_Channel = 0 143 | const ATTRIBUTE_ID_ThreadNetworkDiagnostics_RoutingRole = 1 144 | const ATTRIBUTE_ID_ThreadNetworkDiagnostics_NetworkName = 2 145 | const ATTRIBUTE_ID_ThreadNetworkDiagnostics_PanId = 3 146 | const ATTRIBUTE_ID_ThreadNetworkDiagnostics_ExtendedPanId = 4 147 | const ATTRIBUTE_ID_ThreadNetworkDiagnostics_MeshLocalPrefix = 5 148 | const ATTRIBUTE_ID_ThreadNetworkDiagnostics_OverrunCount = 6 149 | const ATTRIBUTE_ID_ThreadNetworkDiagnostics_NeighborTable = 7 150 | const ATTRIBUTE_ID_ThreadNetworkDiagnostics_RouteTable = 8 151 | const ATTRIBUTE_ID_ThreadNetworkDiagnostics_PartitionId = 9 152 | const ATTRIBUTE_ID_ThreadNetworkDiagnostics_Weighting = 10 153 | const ATTRIBUTE_ID_ThreadNetworkDiagnostics_DataVersion = 11 154 | const ATTRIBUTE_ID_ThreadNetworkDiagnostics_StableDataVersion = 12 155 | const ATTRIBUTE_ID_ThreadNetworkDiagnostics_LeaderRouterId = 13 156 | const ATTRIBUTE_ID_ThreadNetworkDiagnostics_DetachedRoleCount = 14 157 | const ATTRIBUTE_ID_ThreadNetworkDiagnostics_ChildRoleCount = 15 158 | const ATTRIBUTE_ID_ThreadNetworkDiagnostics_RouterRoleCount = 16 159 | const ATTRIBUTE_ID_ThreadNetworkDiagnostics_LeaderRoleCount = 17 160 | const ATTRIBUTE_ID_ThreadNetworkDiagnostics_AttachAttemptCount = 18 161 | const ATTRIBUTE_ID_ThreadNetworkDiagnostics_PartitionIdChangeCount = 19 162 | const ATTRIBUTE_ID_ThreadNetworkDiagnostics_BetterPartitionAttachAttemptCount = 20 163 | const ATTRIBUTE_ID_ThreadNetworkDiagnostics_ParentChangeCount = 21 164 | const ATTRIBUTE_ID_ThreadNetworkDiagnostics_TxTotalCount = 22 165 | const ATTRIBUTE_ID_ThreadNetworkDiagnostics_TxUnicastCount = 23 166 | const ATTRIBUTE_ID_ThreadNetworkDiagnostics_TxBroadcastCount = 24 167 | const ATTRIBUTE_ID_ThreadNetworkDiagnostics_TxAckRequestedCount = 25 168 | const ATTRIBUTE_ID_ThreadNetworkDiagnostics_TxAckedCount = 26 169 | const ATTRIBUTE_ID_ThreadNetworkDiagnostics_TxNoAckRequestedCount = 27 170 | const ATTRIBUTE_ID_ThreadNetworkDiagnostics_TxDataCount = 28 171 | const ATTRIBUTE_ID_ThreadNetworkDiagnostics_TxDataPollCount = 29 172 | const ATTRIBUTE_ID_ThreadNetworkDiagnostics_TxBeaconCount = 30 173 | const ATTRIBUTE_ID_ThreadNetworkDiagnostics_TxBeaconRequestCount = 31 174 | const ATTRIBUTE_ID_ThreadNetworkDiagnostics_TxOtherCount = 32 175 | const ATTRIBUTE_ID_ThreadNetworkDiagnostics_TxRetryCount = 33 176 | const ATTRIBUTE_ID_ThreadNetworkDiagnostics_TxDirectMaxRetryExpiryCount = 34 177 | const ATTRIBUTE_ID_ThreadNetworkDiagnostics_TxIndirectMaxRetryExpiryCount = 35 178 | const ATTRIBUTE_ID_ThreadNetworkDiagnostics_TxErrCcaCount = 36 179 | const ATTRIBUTE_ID_ThreadNetworkDiagnostics_TxErrAbortCount = 37 180 | const ATTRIBUTE_ID_ThreadNetworkDiagnostics_TxErrBusyChannelCount = 38 181 | const ATTRIBUTE_ID_ThreadNetworkDiagnostics_RxTotalCount = 39 182 | const ATTRIBUTE_ID_ThreadNetworkDiagnostics_RxUnicastCount = 40 183 | const ATTRIBUTE_ID_ThreadNetworkDiagnostics_RxBroadcastCount = 41 184 | const ATTRIBUTE_ID_ThreadNetworkDiagnostics_RxDataCount = 42 185 | const ATTRIBUTE_ID_ThreadNetworkDiagnostics_RxDataPollCount = 43 186 | const ATTRIBUTE_ID_ThreadNetworkDiagnostics_RxBeaconCount = 44 187 | const ATTRIBUTE_ID_ThreadNetworkDiagnostics_RxBeaconRequestCount = 45 188 | const ATTRIBUTE_ID_ThreadNetworkDiagnostics_RxOtherCount = 46 189 | const ATTRIBUTE_ID_ThreadNetworkDiagnostics_RxAddressFilteredCount = 47 190 | const ATTRIBUTE_ID_ThreadNetworkDiagnostics_RxDestAddrFilteredCount = 48 191 | const ATTRIBUTE_ID_ThreadNetworkDiagnostics_RxDuplicatedCount = 49 192 | const ATTRIBUTE_ID_ThreadNetworkDiagnostics_RxErrNoFrameCount = 50 193 | const ATTRIBUTE_ID_ThreadNetworkDiagnostics_RxErrUnknownNeighborCount = 51 194 | const ATTRIBUTE_ID_ThreadNetworkDiagnostics_RxErrInvalidSrcAddrCount = 52 195 | const ATTRIBUTE_ID_ThreadNetworkDiagnostics_RxErrSecCount = 53 196 | const ATTRIBUTE_ID_ThreadNetworkDiagnostics_RxErrFcsCount = 54 197 | const ATTRIBUTE_ID_ThreadNetworkDiagnostics_RxErrOtherCount = 55 198 | const ATTRIBUTE_ID_ThreadNetworkDiagnostics_ActiveTimestamp = 56 199 | const ATTRIBUTE_ID_ThreadNetworkDiagnostics_PendingTimestamp = 57 200 | const ATTRIBUTE_ID_ThreadNetworkDiagnostics_Delay = 58 201 | const ATTRIBUTE_ID_ThreadNetworkDiagnostics_SecurityPolicy = 59 202 | const ATTRIBUTE_ID_ThreadNetworkDiagnostics_ChannelPage0Mask = 60 203 | const ATTRIBUTE_ID_ThreadNetworkDiagnostics_OperationalDatasetComponents = 61 204 | const ATTRIBUTE_ID_ThreadNetworkDiagnostics_ActiveNetworkFaults = 62 205 | const CLUSTER_ID_FlowMeasurement = 0x404 206 | const ATTRIBUTE_ID_FlowMeasurement_MeasuredValue = 0 207 | const ATTRIBUTE_ID_FlowMeasurement_MinMeasuredValue = 1 208 | const ATTRIBUTE_ID_FlowMeasurement_MaxMeasuredValue = 2 209 | const ATTRIBUTE_ID_FlowMeasurement_Tolerance = 3 210 | const CLUSTER_ID_LevelControl = 0x8 211 | const COMMAND_ID_LevelControl_MoveToLevel = 0 212 | const COMMAND_ID_LevelControl_Move = 1 213 | const COMMAND_ID_LevelControl_Step = 2 214 | const COMMAND_ID_LevelControl_Stop = 3 215 | const COMMAND_ID_LevelControl_MoveToLevelWithOnOff = 4 216 | const COMMAND_ID_LevelControl_MoveWithOnOff = 5 217 | const COMMAND_ID_LevelControl_StepWithOnOff = 6 218 | const COMMAND_ID_LevelControl_StopWithOnOff = 7 219 | const COMMAND_ID_LevelControl_MoveToClosestFrequency = 8 220 | const ATTRIBUTE_ID_LevelControl_CurrentLevel = 0 221 | const ATTRIBUTE_ID_LevelControl_RemainingTime = 1 222 | const ATTRIBUTE_ID_LevelControl_MinLevel = 2 223 | const ATTRIBUTE_ID_LevelControl_MaxLevel = 3 224 | const ATTRIBUTE_ID_LevelControl_CurrentFrequency = 4 225 | const ATTRIBUTE_ID_LevelControl_MinFrequency = 5 226 | const ATTRIBUTE_ID_LevelControl_MaxFrequency = 6 227 | const ATTRIBUTE_ID_LevelControl_Options = 15 228 | const ATTRIBUTE_ID_LevelControl_OnOffTransitionTime = 16 229 | const ATTRIBUTE_ID_LevelControl_OnLevel = 17 230 | const ATTRIBUTE_ID_LevelControl_OnTransitionTime = 18 231 | const ATTRIBUTE_ID_LevelControl_OffTransitionTime = 19 232 | const ATTRIBUTE_ID_LevelControl_DefaultMoveRate = 20 233 | const ATTRIBUTE_ID_LevelControl_StartUpCurrentLevel = 16384 234 | const CLUSTER_ID_Switch = 0x3b 235 | const ATTRIBUTE_ID_Switch_NumberOfPositions = 0 236 | const ATTRIBUTE_ID_Switch_CurrentPosition = 1 237 | const ATTRIBUTE_ID_Switch_MultiPressMax = 2 238 | const CLUSTER_ID_Thermostat = 0x201 239 | const COMMAND_ID_Thermostat_SetpointRaiseLower = 0 240 | const COMMAND_ID_Thermostat_GetWeeklyScheduleResponse = 0 241 | const COMMAND_ID_Thermostat_SetWeeklySchedule = 1 242 | const COMMAND_ID_Thermostat_GetRelayStatusLogResponse = 1 243 | const COMMAND_ID_Thermostat_GetWeeklySchedule = 2 244 | const COMMAND_ID_Thermostat_ClearWeeklySchedule = 3 245 | const COMMAND_ID_Thermostat_GetRelayStatusLog = 4 246 | const ATTRIBUTE_ID_Thermostat_LocalTemperature = 0 247 | const ATTRIBUTE_ID_Thermostat_OutdoorTemperature = 1 248 | const ATTRIBUTE_ID_Thermostat_Occupancy = 2 249 | const ATTRIBUTE_ID_Thermostat_AbsMinHeatSetpointLimit = 3 250 | const ATTRIBUTE_ID_Thermostat_AbsMaxHeatSetpointLimit = 4 251 | const ATTRIBUTE_ID_Thermostat_AbsMinCoolSetpointLimit = 5 252 | const ATTRIBUTE_ID_Thermostat_AbsMaxCoolSetpointLimit = 6 253 | const ATTRIBUTE_ID_Thermostat_PICoolingDemand = 7 254 | const ATTRIBUTE_ID_Thermostat_PIHeatingDemand = 8 255 | const ATTRIBUTE_ID_Thermostat_HVACSystemTypeConfiguration = 9 256 | const ATTRIBUTE_ID_Thermostat_LocalTemperatureCalibration = 16 257 | const ATTRIBUTE_ID_Thermostat_OccupiedCoolingSetpoint = 17 258 | const ATTRIBUTE_ID_Thermostat_OccupiedHeatingSetpoint = 18 259 | const ATTRIBUTE_ID_Thermostat_UnoccupiedCoolingSetpoint = 19 260 | const ATTRIBUTE_ID_Thermostat_UnoccupiedHeatingSetpoint = 20 261 | const ATTRIBUTE_ID_Thermostat_MinHeatSetpointLimit = 21 262 | const ATTRIBUTE_ID_Thermostat_MaxHeatSetpointLimit = 22 263 | const ATTRIBUTE_ID_Thermostat_MinCoolSetpointLimit = 23 264 | const ATTRIBUTE_ID_Thermostat_MaxCoolSetpointLimit = 24 265 | const ATTRIBUTE_ID_Thermostat_MinSetpointDeadBand = 25 266 | const ATTRIBUTE_ID_Thermostat_RemoteSensing = 26 267 | const ATTRIBUTE_ID_Thermostat_ControlSequenceOfOperation = 27 268 | const ATTRIBUTE_ID_Thermostat_SystemMode = 28 269 | const ATTRIBUTE_ID_Thermostat_AlarmMask = 29 270 | const ATTRIBUTE_ID_Thermostat_ThermostatRunningMode = 30 271 | const ATTRIBUTE_ID_Thermostat_StartOfWeek = 32 272 | const ATTRIBUTE_ID_Thermostat_NumberOfWeeklyTransitions = 33 273 | const ATTRIBUTE_ID_Thermostat_NumberOfDailyTransitions = 34 274 | const ATTRIBUTE_ID_Thermostat_TemperatureSetpointHold = 35 275 | const ATTRIBUTE_ID_Thermostat_TemperatureSetpointHoldDuration = 36 276 | const ATTRIBUTE_ID_Thermostat_ThermostatProgrammingOperationMode = 37 277 | const ATTRIBUTE_ID_Thermostat_ThermostatRunningState = 41 278 | const ATTRIBUTE_ID_Thermostat_SetpointChangeSource = 48 279 | const ATTRIBUTE_ID_Thermostat_SetpointChangeAmount = 49 280 | const ATTRIBUTE_ID_Thermostat_SetpointChangeSourceTimestamp = 50 281 | const ATTRIBUTE_ID_Thermostat_OccupiedSetback = 52 282 | const ATTRIBUTE_ID_Thermostat_OccupiedSetbackMin = 53 283 | const ATTRIBUTE_ID_Thermostat_OccupiedSetbackMax = 54 284 | const ATTRIBUTE_ID_Thermostat_UnoccupiedSetback = 55 285 | const ATTRIBUTE_ID_Thermostat_UnoccupiedSetbackMin = 56 286 | const ATTRIBUTE_ID_Thermostat_UnoccupiedSetbackMax = 57 287 | const ATTRIBUTE_ID_Thermostat_EmergencyHeatDelta = 58 288 | const ATTRIBUTE_ID_Thermostat_ACType = 64 289 | const ATTRIBUTE_ID_Thermostat_ACCapacity = 65 290 | const ATTRIBUTE_ID_Thermostat_ACRefrigerantType = 66 291 | const ATTRIBUTE_ID_Thermostat_ACCompressorType = 67 292 | const ATTRIBUTE_ID_Thermostat_ACErrorCode = 68 293 | const ATTRIBUTE_ID_Thermostat_ACLouverPosition = 69 294 | const ATTRIBUTE_ID_Thermostat_ACCoilTemperature = 70 295 | const ATTRIBUTE_ID_Thermostat_ACCapacityFormat = 71 296 | const CLUSTER_ID_RefrigeratorAlarm = 0x57 297 | const COMMAND_ID_RefrigeratorAlarm_ModifyEnabledAlarms = 1 298 | const CLUSTER_ID_ElectricalEnergyMeasurement = 0x91 299 | const ATTRIBUTE_ID_ElectricalEnergyMeasurement_Measured = 0 300 | const ATTRIBUTE_ID_ElectricalEnergyMeasurement_CumulativeEnergyImportedTime = 1 301 | const ATTRIBUTE_ID_ElectricalEnergyMeasurement_CumulativeEnergyImported = 2 302 | const ATTRIBUTE_ID_ElectricalEnergyMeasurement_CumulativeEnergyExportedTime = 3 303 | const ATTRIBUTE_ID_ElectricalEnergyMeasurement_CumulativeEnergyExported = 4 304 | const ATTRIBUTE_ID_ElectricalEnergyMeasurement_PeriodicEnergyImportedStartTime = 5 305 | const ATTRIBUTE_ID_ElectricalEnergyMeasurement_PeriodicEnergyImportedEndTime = 6 306 | const ATTRIBUTE_ID_ElectricalEnergyMeasurement_PeriodicEnergyImported = 7 307 | const ATTRIBUTE_ID_ElectricalEnergyMeasurement_PeriodicEnergyExportedStartTime = 8 308 | const ATTRIBUTE_ID_ElectricalEnergyMeasurement_PeriodicEnergyExportedEndTime = 9 309 | const ATTRIBUTE_ID_ElectricalEnergyMeasurement_PeriodicEnergyExported = 10 310 | const ATTRIBUTE_ID_ElectricalEnergyMeasurement_EphemeralEnergyImported = 11 311 | const CLUSTER_ID_IlluminanceMeasurement = 0x400 312 | const ATTRIBUTE_ID_IlluminanceMeasurement_MeasuredValue = 0 313 | const ATTRIBUTE_ID_IlluminanceMeasurement_MinMeasuredValue = 1 314 | const ATTRIBUTE_ID_IlluminanceMeasurement_MaxMeasuredValue = 2 315 | const ATTRIBUTE_ID_IlluminanceMeasurement_Tolerance = 3 316 | const ATTRIBUTE_ID_IlluminanceMeasurement_LightSensorType = 4 317 | const CLUSTER_ID_FixedLabel = 0x40 318 | const ATTRIBUTE_ID_FixedLabel_LabelList = 0 319 | const CLUSTER_ID_LaundryWasherMode = 0x51 320 | const ATTRIBUTE_ID_LaundryWasherMode_SupportedModes = 0 321 | const ATTRIBUTE_ID_LaundryWasherMode_CurrentMode = 1 322 | const ATTRIBUTE_ID_LaundryWasherMode_StartUpMode = 2 323 | const ATTRIBUTE_ID_LaundryWasherMode_OnMode = 3 324 | const CLUSTER_ID_NetworkCommissioning = 0x31 325 | const COMMAND_ID_NetworkCommissioning_ScanNetworks = 0 326 | const COMMAND_ID_NetworkCommissioning_ScanNetworksResponse = 1 327 | const COMMAND_ID_NetworkCommissioning_AddOrUpdateWiFiNetwork = 2 328 | const COMMAND_ID_NetworkCommissioning_AddOrUpdateThreadNetwork = 3 329 | const COMMAND_ID_NetworkCommissioning_RemoveNetwork = 4 330 | const COMMAND_ID_NetworkCommissioning_NetworkConfigResponse = 5 331 | const COMMAND_ID_NetworkCommissioning_ConnectNetwork = 6 332 | const COMMAND_ID_NetworkCommissioning_ConnectNetworkResponse = 7 333 | const COMMAND_ID_NetworkCommissioning_ReorderNetwork = 8 334 | const ATTRIBUTE_ID_NetworkCommissioning_MaxNetworks = 0 335 | const ATTRIBUTE_ID_NetworkCommissioning_Networks = 1 336 | const ATTRIBUTE_ID_NetworkCommissioning_ScanMaxTimeSeconds = 2 337 | const ATTRIBUTE_ID_NetworkCommissioning_ConnectMaxTimeSeconds = 3 338 | const ATTRIBUTE_ID_NetworkCommissioning_InterfaceEnabled = 4 339 | const ATTRIBUTE_ID_NetworkCommissioning_LastNetworkingStatus = 5 340 | const ATTRIBUTE_ID_NetworkCommissioning_LastNetworkID = 6 341 | const ATTRIBUTE_ID_NetworkCommissioning_LastConnectErrorValue = 7 342 | const ATTRIBUTE_ID_NetworkCommissioning_SupportedWiFiBands = 8 343 | const ATTRIBUTE_ID_NetworkCommissioning_SupportedThreadFeatures = 9 344 | const ATTRIBUTE_ID_NetworkCommissioning_ThreadVersion = 10 345 | const CLUSTER_ID_OvenCavityOperationalState = 0x48 346 | const CLUSTER_ID_AirQuality = 0x5b 347 | const ATTRIBUTE_ID_AirQuality_AirQuality = 0 348 | const CLUSTER_ID_ApplicationLauncher = 0x50c 349 | const COMMAND_ID_ApplicationLauncher_LaunchApp = 0 350 | const COMMAND_ID_ApplicationLauncher_StopApp = 1 351 | const COMMAND_ID_ApplicationLauncher_HideApp = 2 352 | const COMMAND_ID_ApplicationLauncher_LauncherResponse = 3 353 | const ATTRIBUTE_ID_ApplicationLauncher_CatalogList = 0 354 | const ATTRIBUTE_ID_ApplicationLauncher_CurrentApp = 1 355 | const CLUSTER_ID_BasicInformation = 0x28 356 | const ATTRIBUTE_ID_BasicInformation_DataModelRevision = 0 357 | const ATTRIBUTE_ID_BasicInformation_VendorName = 1 358 | const ATTRIBUTE_ID_BasicInformation_VendorID = 2 359 | const ATTRIBUTE_ID_BasicInformation_ProductName = 3 360 | const ATTRIBUTE_ID_BasicInformation_ProductID = 4 361 | const ATTRIBUTE_ID_BasicInformation_NodeLabel = 5 362 | const ATTRIBUTE_ID_BasicInformation_Location = 6 363 | const ATTRIBUTE_ID_BasicInformation_HardwareVersion = 7 364 | const ATTRIBUTE_ID_BasicInformation_HardwareVersionString = 8 365 | const ATTRIBUTE_ID_BasicInformation_SoftwareVersion = 9 366 | const ATTRIBUTE_ID_BasicInformation_SoftwareVersionString = 10 367 | const ATTRIBUTE_ID_BasicInformation_ManufacturingDate = 11 368 | const ATTRIBUTE_ID_BasicInformation_PartNumber = 12 369 | const ATTRIBUTE_ID_BasicInformation_ProductURL = 13 370 | const ATTRIBUTE_ID_BasicInformation_ProductLabel = 14 371 | const ATTRIBUTE_ID_BasicInformation_SerialNumber = 15 372 | const ATTRIBUTE_ID_BasicInformation_LocalConfigDisabled = 16 373 | const ATTRIBUTE_ID_BasicInformation_Reachable = 17 374 | const ATTRIBUTE_ID_BasicInformation_UniqueID = 18 375 | const ATTRIBUTE_ID_BasicInformation_CapabilityMinima = 19 376 | const ATTRIBUTE_ID_BasicInformation_ProductAppearance = 20 377 | const ATTRIBUTE_ID_BasicInformation_SpecificationVersion = 21 378 | const ATTRIBUTE_ID_BasicInformation_MaxPathsPerInvoke = 22 379 | const CLUSTER_ID_EnergyPrice = 0x95 380 | const COMMAND_ID_EnergyPrice_GetDetailedPriceRequest = 0 381 | const COMMAND_ID_EnergyPrice_GetDetailedPriceResponse = 1 382 | const COMMAND_ID_EnergyPrice_GetDetailedForecastRequest = 2 383 | const COMMAND_ID_EnergyPrice_GetDetailedForecastResponse = 3 384 | const ATTRIBUTE_ID_EnergyPrice_UnitOfMeasure = 0 385 | const ATTRIBUTE_ID_EnergyPrice_CurrentPrice = 1 386 | const ATTRIBUTE_ID_EnergyPrice_PriceForecast = 2 387 | const CLUSTER_ID_MediaInput = 0x507 388 | const COMMAND_ID_MediaInput_SelectInput = 0 389 | const COMMAND_ID_MediaInput_ShowInputStatus = 1 390 | const COMMAND_ID_MediaInput_HideInputStatus = 2 391 | const COMMAND_ID_MediaInput_RenameInput = 3 392 | const ATTRIBUTE_ID_MediaInput_InputList = 0 393 | const ATTRIBUTE_ID_MediaInput_CurrentInput = 1 394 | const CLUSTER_ID_TemperatureControl = 0x56 395 | const COMMAND_ID_TemperatureControl_SetTemperature = 0 396 | const CLUSTER_ID_ContentAppObserver = 0x510 397 | const COMMAND_ID_ContentAppObserver_ContentAppMessage = 0 398 | const COMMAND_ID_ContentAppObserver_ContentAppMessageResponse = 1 399 | const CLUSTER_ID_ElectricalPowerMeasurement = 0x90 400 | const ATTRIBUTE_ID_ElectricalPowerMeasurement_PowerMode = 0 401 | const ATTRIBUTE_ID_ElectricalPowerMeasurement_Accuracy = 1 402 | const ATTRIBUTE_ID_ElectricalPowerMeasurement_Ranges = 2 403 | const ATTRIBUTE_ID_ElectricalPowerMeasurement_Voltage = 3 404 | const ATTRIBUTE_ID_ElectricalPowerMeasurement_Current = 4 405 | const ATTRIBUTE_ID_ElectricalPowerMeasurement_ActivePower = 5 406 | const ATTRIBUTE_ID_ElectricalPowerMeasurement_RMSCurrent = 6 407 | const ATTRIBUTE_ID_ElectricalPowerMeasurement_RMSPower = 7 408 | const ATTRIBUTE_ID_ElectricalPowerMeasurement_ApparentPower = 8 409 | const ATTRIBUTE_ID_ElectricalPowerMeasurement_ReactivePower = 9 410 | const ATTRIBUTE_ID_ElectricalPowerMeasurement_Frequency = 10 411 | const ATTRIBUTE_ID_ElectricalPowerMeasurement_HarmonicCurrents = 11 412 | const ATTRIBUTE_ID_ElectricalPowerMeasurement_HarmonicPhases = 12 413 | const ATTRIBUTE_ID_ElectricalPowerMeasurement_PowerFactor = 13 414 | const ATTRIBUTE_ID_ElectricalPowerMeasurement_LineCurrent = 14 415 | const ATTRIBUTE_ID_ElectricalPowerMeasurement_NeutralCurrent = 15 416 | const CLUSTER_ID_WaterHeaterManagement = 0x94 417 | const COMMAND_ID_WaterHeaterManagement_Boost = 1 418 | const COMMAND_ID_WaterHeaterManagement_CancelBoost = 1 419 | const ATTRIBUTE_ID_WaterHeaterManagement_HeaterTypes = 0 420 | const ATTRIBUTE_ID_WaterHeaterManagement_HeatDemand = 1 421 | const ATTRIBUTE_ID_WaterHeaterManagement_TankVolume = 2 422 | const ATTRIBUTE_ID_WaterHeaterManagement_EstimatedHeatRequired = 3 423 | const ATTRIBUTE_ID_WaterHeaterManagement_TankPercentage = 4 424 | const CLUSTER_ID_Actions = 0x25 425 | const COMMAND_ID_Actions_InstantAction = 0 426 | const COMMAND_ID_Actions_InstantActionWithTransition = 1 427 | const COMMAND_ID_Actions_StartAction = 2 428 | const COMMAND_ID_Actions_StartActionWithDuration = 3 429 | const COMMAND_ID_Actions_StopAction = 4 430 | const COMMAND_ID_Actions_PauseAction = 5 431 | const COMMAND_ID_Actions_PauseActionWithDuration = 6 432 | const COMMAND_ID_Actions_ResumeAction = 7 433 | const COMMAND_ID_Actions_EnableAction = 8 434 | const COMMAND_ID_Actions_EnableActionWithDuration = 9 435 | const COMMAND_ID_Actions_DisableAction = 10 436 | const COMMAND_ID_Actions_DisableActionWithDuration = 11 437 | const ATTRIBUTE_ID_Actions_ActionList = 0 438 | const ATTRIBUTE_ID_Actions_EndpointLists = 1 439 | const ATTRIBUTE_ID_Actions_SetupURL = 2 440 | const CLUSTER_ID_BridgedDeviceBasicInformation = 0x39 441 | const ATTRIBUTE_ID_BridgedDeviceBasicInformation_DataModelRevision = 0 442 | const ATTRIBUTE_ID_BridgedDeviceBasicInformation_VendorName = 1 443 | const ATTRIBUTE_ID_BridgedDeviceBasicInformation_VendorID = 2 444 | const ATTRIBUTE_ID_BridgedDeviceBasicInformation_ProductName = 3 445 | const ATTRIBUTE_ID_BridgedDeviceBasicInformation_ProductID = 4 446 | const ATTRIBUTE_ID_BridgedDeviceBasicInformation_NodeLabel = 5 447 | const ATTRIBUTE_ID_BridgedDeviceBasicInformation_Location = 6 448 | const ATTRIBUTE_ID_BridgedDeviceBasicInformation_HardwareVersion = 7 449 | const ATTRIBUTE_ID_BridgedDeviceBasicInformation_HardwareVersionString = 8 450 | const ATTRIBUTE_ID_BridgedDeviceBasicInformation_SoftwareVersion = 9 451 | const ATTRIBUTE_ID_BridgedDeviceBasicInformation_SoftwareVersionString = 10 452 | const ATTRIBUTE_ID_BridgedDeviceBasicInformation_ManufacturingDate = 11 453 | const ATTRIBUTE_ID_BridgedDeviceBasicInformation_PartNumber = 12 454 | const ATTRIBUTE_ID_BridgedDeviceBasicInformation_ProductURL = 13 455 | const ATTRIBUTE_ID_BridgedDeviceBasicInformation_ProductLabel = 14 456 | const ATTRIBUTE_ID_BridgedDeviceBasicInformation_SerialNumber = 15 457 | const ATTRIBUTE_ID_BridgedDeviceBasicInformation_LocalConfigDisabled = 16 458 | const ATTRIBUTE_ID_BridgedDeviceBasicInformation_Reachable = 17 459 | const ATTRIBUTE_ID_BridgedDeviceBasicInformation_UniqueID = 18 460 | const ATTRIBUTE_ID_BridgedDeviceBasicInformation_CapabilityMinima = 19 461 | const ATTRIBUTE_ID_BridgedDeviceBasicInformation_ProductAppearance = 20 462 | const ATTRIBUTE_ID_BridgedDeviceBasicInformation_SpecificationVersion = 21 463 | const ATTRIBUTE_ID_BridgedDeviceBasicInformation_MaxPathsPerInvoke = 22 464 | const CLUSTER_ID_EthernetNetworkDiagnostics = 0x37 465 | const COMMAND_ID_EthernetNetworkDiagnostics_ResetCounts = 0 466 | const ATTRIBUTE_ID_EthernetNetworkDiagnostics_PHYRate = 0 467 | const ATTRIBUTE_ID_EthernetNetworkDiagnostics_FullDuplex = 1 468 | const ATTRIBUTE_ID_EthernetNetworkDiagnostics_PacketRxCount = 2 469 | const ATTRIBUTE_ID_EthernetNetworkDiagnostics_PacketTxCount = 3 470 | const ATTRIBUTE_ID_EthernetNetworkDiagnostics_TxErrCount = 4 471 | const ATTRIBUTE_ID_EthernetNetworkDiagnostics_CollisionCount = 5 472 | const ATTRIBUTE_ID_EthernetNetworkDiagnostics_OverrunCount = 6 473 | const ATTRIBUTE_ID_EthernetNetworkDiagnostics_CarrierDetect = 7 474 | const ATTRIBUTE_ID_EthernetNetworkDiagnostics_TimeSinceReset = 8 475 | const CLUSTER_ID_WiFiNetworkDiagnostics = 0x36 476 | const COMMAND_ID_WiFiNetworkDiagnostics_ResetCounts = 0 477 | const ATTRIBUTE_ID_WiFiNetworkDiagnostics_BSSID = 0 478 | const ATTRIBUTE_ID_WiFiNetworkDiagnostics_SecurityType = 1 479 | const ATTRIBUTE_ID_WiFiNetworkDiagnostics_WiFiVersion = 2 480 | const ATTRIBUTE_ID_WiFiNetworkDiagnostics_ChannelNumber = 3 481 | const ATTRIBUTE_ID_WiFiNetworkDiagnostics_RSSI = 4 482 | const ATTRIBUTE_ID_WiFiNetworkDiagnostics_BeaconLostCount = 5 483 | const ATTRIBUTE_ID_WiFiNetworkDiagnostics_BeaconRxCount = 6 484 | const ATTRIBUTE_ID_WiFiNetworkDiagnostics_PacketMulticastRxCount = 7 485 | const ATTRIBUTE_ID_WiFiNetworkDiagnostics_PacketMulticastTxCount = 8 486 | const ATTRIBUTE_ID_WiFiNetworkDiagnostics_PacketUnicastRxCount = 9 487 | const ATTRIBUTE_ID_WiFiNetworkDiagnostics_PacketUnicastTxCount = 10 488 | const ATTRIBUTE_ID_WiFiNetworkDiagnostics_CurrentMaxRate = 11 489 | const ATTRIBUTE_ID_WiFiNetworkDiagnostics_OverrunCount = 12 490 | const CLUSTER_ID_DishwasherAlarm = 0x5d 491 | const CLUSTER_ID_GroupKeyManagement = 0x3f 492 | const COMMAND_ID_GroupKeyManagement_KeySetWriteCommand = 0 493 | const COMMAND_ID_GroupKeyManagement_KeySetReadCommand = 1 494 | const COMMAND_ID_GroupKeyManagement_KeySetReadResponseCommand = 2 495 | const COMMAND_ID_GroupKeyManagement_KeySetRemoveCommand = 3 496 | const COMMAND_ID_GroupKeyManagement_KeySetReadAllIndicesCommand = 4 497 | const COMMAND_ID_GroupKeyManagement_KeySetReadAllIndicesResponseCommand = 5 498 | const ATTRIBUTE_ID_GroupKeyManagement_GroupKeyMap = 0 499 | const ATTRIBUTE_ID_GroupKeyManagement_GroupTable = 1 500 | const ATTRIBUTE_ID_GroupKeyManagement_MaxGroupsPerFabric = 2 501 | const ATTRIBUTE_ID_GroupKeyManagement_MaxGroupKeysPerFabric = 3 502 | const CLUSTER_ID_UnitLocalization = 0x2d 503 | const ATTRIBUTE_ID_UnitLocalization_TemperatureUnit = 0 504 | const CLUSTER_ID_ThermostatUserInterfaceConfiguration = 0x204 505 | const ATTRIBUTE_ID_ThermostatUserInterfaceConfiguration_TemperatureDisplayMode = 0 506 | const ATTRIBUTE_ID_ThermostatUserInterfaceConfiguration_KeypadLockout = 1 507 | const ATTRIBUTE_ID_ThermostatUserInterfaceConfiguration_ScheduleProgrammingVisibility = 2 508 | const CLUSTER_ID_AdministratorCommissioning = 0x3c 509 | const COMMAND_ID_AdministratorCommissioning_OpenCommissioningWindow = 0 510 | const COMMAND_ID_AdministratorCommissioning_OpenBasicCommissioningWindow = 1 511 | const COMMAND_ID_AdministratorCommissioning_RevokeCommissioning = 2 512 | const ATTRIBUTE_ID_AdministratorCommissioning_WindowStatus = 0 513 | const ATTRIBUTE_ID_AdministratorCommissioning_AdminFabricIndex = 1 514 | const ATTRIBUTE_ID_AdministratorCommissioning_AdminVendorId = 2 515 | const CLUSTER_ID_BooleanState = 0x45 516 | const ATTRIBUTE_ID_BooleanState_StateValue = 0 517 | const CLUSTER_ID_TimeFormatLocalization = 0x2c 518 | const ATTRIBUTE_ID_TimeFormatLocalization_HourFormat = 0 519 | const ATTRIBUTE_ID_TimeFormatLocalization_ActiveCalendarType = 1 520 | const ATTRIBUTE_ID_TimeFormatLocalization_SupportedCalendarTypes = 2 521 | const CLUSTER_ID_Messages = 0x97 522 | const COMMAND_ID_Messages_PresentMessagesRequest = 0 523 | const COMMAND_ID_Messages_CancelMessagesRequest = 1 524 | const ATTRIBUTE_ID_Messages_Messages = 0 525 | const ATTRIBUTE_ID_Messages_ActiveMessageIDs = 1 526 | const CLUSTER_ID_RVCRunMode = 0x54 527 | const CLUSTER_ID_PowerSource = 0x2f 528 | const COMMAND_ID_PowerSource_DisableBattery = 1 529 | const COMMAND_ID_PowerSource_EnableBatteryCharging = 2 530 | const COMMAND_ID_PowerSource_EnableBatteryDischarging = 3 531 | const COMMAND_ID_PowerSource_StartBatteryDiagnostics = 4 532 | const ATTRIBUTE_ID_PowerSource_Status = 0 533 | const ATTRIBUTE_ID_PowerSource_Order = 1 534 | const ATTRIBUTE_ID_PowerSource_Description = 2 535 | const ATTRIBUTE_ID_PowerSource_WiredAssessedInputVoltage = 3 536 | const ATTRIBUTE_ID_PowerSource_WiredAssessedInputFrequency = 4 537 | const ATTRIBUTE_ID_PowerSource_WiredCurrentType = 5 538 | const ATTRIBUTE_ID_PowerSource_WiredAssessedCurrent = 6 539 | const ATTRIBUTE_ID_PowerSource_WiredNominalVoltage = 7 540 | const ATTRIBUTE_ID_PowerSource_WiredMaximumCurrent = 8 541 | const ATTRIBUTE_ID_PowerSource_WiredPresent = 9 542 | const ATTRIBUTE_ID_PowerSource_ActiveWiredFaults = 10 543 | const ATTRIBUTE_ID_PowerSource_BatVoltage = 11 544 | const ATTRIBUTE_ID_PowerSource_BatPercentRemaining = 12 545 | const ATTRIBUTE_ID_PowerSource_BatTimeRemaining = 13 546 | const ATTRIBUTE_ID_PowerSource_BatChargeLevel = 14 547 | const ATTRIBUTE_ID_PowerSource_BatReplacementNeeded = 15 548 | const ATTRIBUTE_ID_PowerSource_BatReplaceability = 16 549 | const ATTRIBUTE_ID_PowerSource_BatPresent = 17 550 | const ATTRIBUTE_ID_PowerSource_ActiveBatFaults = 18 551 | const ATTRIBUTE_ID_PowerSource_BatReplacementDescription = 19 552 | const ATTRIBUTE_ID_PowerSource_BatCommonDesignation = 20 553 | const ATTRIBUTE_ID_PowerSource_BatANSIDesignation = 21 554 | const ATTRIBUTE_ID_PowerSource_BatIECDesignation = 22 555 | const ATTRIBUTE_ID_PowerSource_BatApprovedChemistry = 23 556 | const ATTRIBUTE_ID_PowerSource_BatCapacity = 24 557 | const ATTRIBUTE_ID_PowerSource_BatQuantity = 25 558 | const ATTRIBUTE_ID_PowerSource_BatChargeState = 26 559 | const ATTRIBUTE_ID_PowerSource_BatTimeToFullCharge = 27 560 | const ATTRIBUTE_ID_PowerSource_BatFunctionalWhileCharging = 28 561 | const ATTRIBUTE_ID_PowerSource_BatChargingCurrent = 29 562 | const ATTRIBUTE_ID_PowerSource_ActiveBatChargeFaults = 30 563 | const ATTRIBUTE_ID_PowerSource_EndpointList = 31 564 | const ATTRIBUTE_ID_PowerSource_BatAbsMaxChargeCurrent = 32 565 | const ATTRIBUTE_ID_PowerSource_BatAbsMinChargeCurrent = 33 566 | const ATTRIBUTE_ID_PowerSource_BatAbsMaxDischargeCurrent = 34 567 | const ATTRIBUTE_ID_PowerSource_BatMaxChargeCurrent = 35 568 | const ATTRIBUTE_ID_PowerSource_BatMinChargeCurrent = 36 569 | const ATTRIBUTE_ID_PowerSource_BatMaxDischargeCurrent = 37 570 | const ATTRIBUTE_ID_PowerSource_BatChargePermissions = 38 571 | const ATTRIBUTE_ID_PowerSource_BatEnableChargeTime = 39 572 | const ATTRIBUTE_ID_PowerSource_BatEnableDischargeTime = 40 573 | const ATTRIBUTE_ID_PowerSource_WiredMaxAmperage = 41 574 | const ATTRIBUTE_ID_PowerSource_WiredAbsMaxAmperage = 42 575 | const CLUSTER_ID_AccountLogin = 0x50e 576 | const COMMAND_ID_AccountLogin_GetSetupPIN = 0 577 | const COMMAND_ID_AccountLogin_GetSetupPINResponse = 1 578 | const COMMAND_ID_AccountLogin_Login = 2 579 | const COMMAND_ID_AccountLogin_Logout = 3 580 | const CLUSTER_ID_ContentLauncher = 0x50a 581 | const COMMAND_ID_ContentLauncher_LaunchContent = 0 582 | const COMMAND_ID_ContentLauncher_LaunchURL = 1 583 | const COMMAND_ID_ContentLauncher_LauncherResponse = 2 584 | const ATTRIBUTE_ID_ContentLauncher_AcceptHeader = 0 585 | const ATTRIBUTE_ID_ContentLauncher_SupportedStreamingProtocols = 1 586 | const CLUSTER_ID_SoftwareDiagnostics = 0x34 587 | const COMMAND_ID_SoftwareDiagnostics_ResetWatermarks = 0 588 | const ATTRIBUTE_ID_SoftwareDiagnostics_ThreadMetrics = 0 589 | const ATTRIBUTE_ID_SoftwareDiagnostics_CurrentHeapFree = 1 590 | const ATTRIBUTE_ID_SoftwareDiagnostics_CurrentHeapUsed = 2 591 | const ATTRIBUTE_ID_SoftwareDiagnostics_CurrentHeapHighWatermark = 3 592 | const CLUSTER_ID_DoorLock = 0x101 593 | const COMMAND_ID_DoorLock_LockDoor = 0 594 | const COMMAND_ID_DoorLock_UnlockDoor = 1 595 | const COMMAND_ID_DoorLock_Toggle = 2 596 | const COMMAND_ID_DoorLock_UnlockwithTimeout = 3 597 | const COMMAND_ID_DoorLock_GetLogRecord = 4 598 | const COMMAND_ID_DoorLock_GetLogRecordResponse = 4 599 | const COMMAND_ID_DoorLock_SetPINCode = 5 600 | const COMMAND_ID_DoorLock_GetPINCode = 6 601 | const COMMAND_ID_DoorLock_GetPINCodeResponse = 6 602 | const COMMAND_ID_DoorLock_ClearPINCode = 7 603 | const COMMAND_ID_DoorLock_ClearAllPINCodes = 8 604 | const COMMAND_ID_DoorLock_SetUserStatus = 9 605 | const COMMAND_ID_DoorLock_GetUserStatus = 10 606 | const COMMAND_ID_DoorLock_GetUserStatusResponse = 10 607 | const COMMAND_ID_DoorLock_SetWeekDaySchedule = 11 608 | const COMMAND_ID_DoorLock_GetWeekDaySchedule = 12 609 | const COMMAND_ID_DoorLock_GetWeekDayScheduleResponse = 12 610 | const COMMAND_ID_DoorLock_ClearWeekDaySchedule = 13 611 | const COMMAND_ID_DoorLock_SetYearDaySchedule = 14 612 | const COMMAND_ID_DoorLock_GetYearDaySchedule = 15 613 | const COMMAND_ID_DoorLock_GetYearDayScheduleResponse = 15 614 | const COMMAND_ID_DoorLock_ClearYearDaySchedule = 16 615 | const COMMAND_ID_DoorLock_SetHolidaySchedule = 17 616 | const COMMAND_ID_DoorLock_GetHolidaySchedule = 18 617 | const COMMAND_ID_DoorLock_GetHolidayScheduleResponse = 18 618 | const COMMAND_ID_DoorLock_ClearHolidaySchedule = 19 619 | const COMMAND_ID_DoorLock_SetUserType = 20 620 | const COMMAND_ID_DoorLock_GetUserType = 21 621 | const COMMAND_ID_DoorLock_GetUserTypeResponse = 21 622 | const COMMAND_ID_DoorLock_SetRFIDCode = 22 623 | const COMMAND_ID_DoorLock_GetRFIDCode = 23 624 | const COMMAND_ID_DoorLock_GetRFIDCodeResponse = 23 625 | const COMMAND_ID_DoorLock_ClearRFIDCode = 24 626 | const COMMAND_ID_DoorLock_ClearAllRFIDCodes = 25 627 | const COMMAND_ID_DoorLock_SetUser = 26 628 | const COMMAND_ID_DoorLock_GetUser = 27 629 | const COMMAND_ID_DoorLock_GetUserResponse = 28 630 | const COMMAND_ID_DoorLock_ClearUser = 29 631 | const COMMAND_ID_DoorLock_OperatingEventNotification = 32 632 | const COMMAND_ID_DoorLock_ProgrammingEventNotification = 33 633 | const COMMAND_ID_DoorLock_SetCredential = 34 634 | const COMMAND_ID_DoorLock_SetCredentialResponse = 35 635 | const COMMAND_ID_DoorLock_GetCredentialStatus = 36 636 | const COMMAND_ID_DoorLock_GetCredentialStatusResponse = 37 637 | const COMMAND_ID_DoorLock_ClearCredential = 38 638 | const COMMAND_ID_DoorLock_UnboltDoor = 39 639 | const ATTRIBUTE_ID_DoorLock_LockState = 0 640 | const ATTRIBUTE_ID_DoorLock_LockType = 1 641 | const ATTRIBUTE_ID_DoorLock_ActuatorEnabled = 2 642 | const ATTRIBUTE_ID_DoorLock_DoorState = 3 643 | const ATTRIBUTE_ID_DoorLock_DoorOpenEvents = 4 644 | const ATTRIBUTE_ID_DoorLock_DoorClosedEvents = 5 645 | const ATTRIBUTE_ID_DoorLock_OpenPeriod = 6 646 | const ATTRIBUTE_ID_DoorLock_NumberOfLogRecordsSupported = 16 647 | const ATTRIBUTE_ID_DoorLock_NumberOfTotalUsersSupported = 17 648 | const ATTRIBUTE_ID_DoorLock_NumberOfPINUsersSupported = 18 649 | const ATTRIBUTE_ID_DoorLock_NumberOfRFIDUsersSupported = 19 650 | const ATTRIBUTE_ID_DoorLock_NumberOfWeekDaySchedulesSupportedPerUser = 20 651 | const ATTRIBUTE_ID_DoorLock_NumberOfYearDaySchedulesSupportedPerUser = 21 652 | const ATTRIBUTE_ID_DoorLock_NumberOfHolidaySchedulesSupported = 22 653 | const ATTRIBUTE_ID_DoorLock_MaxPINCodeLength = 23 654 | const ATTRIBUTE_ID_DoorLock_MinPINCodeLength = 24 655 | const ATTRIBUTE_ID_DoorLock_MaxRFIDCodeLength = 25 656 | const ATTRIBUTE_ID_DoorLock_MinRFIDCodeLength = 26 657 | const ATTRIBUTE_ID_DoorLock_CredentialRulesSupport = 27 658 | const ATTRIBUTE_ID_DoorLock_NumberOfCredentialsSupportedPerUser = 28 659 | const ATTRIBUTE_ID_DoorLock_EnableLogging = 32 660 | const ATTRIBUTE_ID_DoorLock_Language = 33 661 | const ATTRIBUTE_ID_DoorLock_LEDSettings = 34 662 | const ATTRIBUTE_ID_DoorLock_AutoRelockTime = 35 663 | const ATTRIBUTE_ID_DoorLock_SoundVolume = 36 664 | const ATTRIBUTE_ID_DoorLock_OperatingMode = 37 665 | const ATTRIBUTE_ID_DoorLock_SupportedOperatingModes = 38 666 | const ATTRIBUTE_ID_DoorLock_DefaultConfigurationRegister = 39 667 | const ATTRIBUTE_ID_DoorLock_EnableLocalProgramming = 40 668 | const ATTRIBUTE_ID_DoorLock_EnableOneTouchLocking = 41 669 | const ATTRIBUTE_ID_DoorLock_EnableInsideStatusLED = 42 670 | const ATTRIBUTE_ID_DoorLock_EnablePrivacyModeButton = 43 671 | const ATTRIBUTE_ID_DoorLock_LocalProgrammingFeatures = 44 672 | const ATTRIBUTE_ID_DoorLock_WrongCodeEntryLimit = 48 673 | const ATTRIBUTE_ID_DoorLock_UserCodeTemporaryDisableTime = 49 674 | const ATTRIBUTE_ID_DoorLock_SendPINOverTheAir = 50 675 | const ATTRIBUTE_ID_DoorLock_RequirePINforRemoteOperation = 51 676 | const ATTRIBUTE_ID_DoorLock_SecurityLevel = 52 677 | const ATTRIBUTE_ID_DoorLock_ExpiringUserTimeout = 53 678 | const ATTRIBUTE_ID_DoorLock_AlarmMask = 64 679 | const ATTRIBUTE_ID_DoorLock_KeypadOperationEventMask = 65 680 | const ATTRIBUTE_ID_DoorLock_RemoteOperationEventMask = 66 681 | const ATTRIBUTE_ID_DoorLock_ManualOperationEventMask = 67 682 | const ATTRIBUTE_ID_DoorLock_RFIDOperationEventMask = 68 683 | const ATTRIBUTE_ID_DoorLock_KeypadProgrammingEventMask = 69 684 | const ATTRIBUTE_ID_DoorLock_RemoteProgrammingEventMask = 70 685 | const ATTRIBUTE_ID_DoorLock_RFIDProgrammingEventMask = 71 686 | const CLUSTER_ID_Groups = 0x4 687 | const COMMAND_ID_Groups_AddGroup = 0 688 | const COMMAND_ID_Groups_AddGroupResponse = 0 689 | const COMMAND_ID_Groups_ViewGroup = 1 690 | const COMMAND_ID_Groups_ViewGroupResponse = 1 691 | const COMMAND_ID_Groups_GetGroupMembership = 2 692 | const COMMAND_ID_Groups_GetGroupMembershipResponse = 2 693 | const COMMAND_ID_Groups_RemoveGroup = 3 694 | const COMMAND_ID_Groups_RemoveGroupResponse = 3 695 | const COMMAND_ID_Groups_RemoveAllGroups = 4 696 | const COMMAND_ID_Groups_AddGroupIfIdentifying = 5 697 | const ATTRIBUTE_ID_Groups_NameSupport = 0 698 | const CLUSTER_ID_Identify = 0x3 699 | const COMMAND_ID_Identify_Identify = 0 700 | const COMMAND_ID_Identify_IdentifyQueryResponse = 0 701 | const COMMAND_ID_Identify_IdentifyQuery = 1 702 | const COMMAND_ID_Identify_TriggerEffect = 64 703 | const ATTRIBUTE_ID_Identify_IdentifyTime = 0 704 | const ATTRIBUTE_ID_Identify_IdentifyType = 1 705 | const CLUSTER_ID_LocalizationConfiguration = 0x2b 706 | const ATTRIBUTE_ID_LocalizationConfiguration_ActiveLocale = 0 707 | const ATTRIBUTE_ID_LocalizationConfiguration_SupportedLocales = 1 708 | const CLUSTER_ID_OvenMode = 0x49 709 | const CLUSTER_ID_TemperatureMeasurement = 0x402 710 | const ATTRIBUTE_ID_TemperatureMeasurement_MeasuredValue = 0 711 | const ATTRIBUTE_ID_TemperatureMeasurement_MinMeasuredValue = 1 712 | const ATTRIBUTE_ID_TemperatureMeasurement_MaxMeasuredValue = 2 713 | const ATTRIBUTE_ID_TemperatureMeasurement_Tolerance = 3 714 | const CLUSTER_ID_ValveConfigurationandControl = 0x81 715 | const COMMAND_ID_ValveConfigurationandControl_Open = 0 716 | const COMMAND_ID_ValveConfigurationandControl_Close = 1 717 | const COMMAND_ID_ValveConfigurationandControl_SetLevel = 2 718 | const ATTRIBUTE_ID_ValveConfigurationandControl_OpenDuration = 0 719 | const ATTRIBUTE_ID_ValveConfigurationandControl_AutoCloseTime = 1 720 | const ATTRIBUTE_ID_ValveConfigurationandControl_RemainingDuration = 2 721 | const ATTRIBUTE_ID_ValveConfigurationandControl_CurrentState = 3 722 | const ATTRIBUTE_ID_ValveConfigurationandControl_TargetState = 4 723 | const ATTRIBUTE_ID_ValveConfigurationandControl_StartUpState = 5 724 | const ATTRIBUTE_ID_ValveConfigurationandControl_CurrentLevel = 6 725 | const ATTRIBUTE_ID_ValveConfigurationandControl_TargetLevel = 7 726 | const ATTRIBUTE_ID_ValveConfigurationandControl_OpenLevel = 8 727 | const ATTRIBUTE_ID_ValveConfigurationandControl_ValveFault = 9 728 | const CLUSTER_ID_NetworkInfrastructure = 0x0 729 | const CLUSTER_ID_Channel = 0x504 730 | const COMMAND_ID_Channel_ChangeChannel = 0 731 | const COMMAND_ID_Channel_ChangeChannelResponse = 1 732 | const COMMAND_ID_Channel_ChangeChannelByNumber = 2 733 | const COMMAND_ID_Channel_SkipChannel = 3 734 | const ATTRIBUTE_ID_Channel_ChannelList = 0 735 | const ATTRIBUTE_ID_Channel_Lineup = 1 736 | const ATTRIBUTE_ID_Channel_CurrentChannel = 2 737 | const CLUSTER_ID_UserLabel = 0x41 738 | const ATTRIBUTE_ID_UserLabel_LabelList = 0 739 | const CLUSTER_ID_LaundryWasherControls = 0x53 740 | const ATTRIBUTE_ID_LaundryWasherControls_SpinSpeeds = 0 741 | const ATTRIBUTE_ID_LaundryWasherControls_SpinSpeedCurrent = 1 742 | const ATTRIBUTE_ID_LaundryWasherControls_NumberOfRinses = 2 743 | const ATTRIBUTE_ID_LaundryWasherControls_SupportedRinses = 3 744 | const CLUSTER_ID_PowerSourceConfiguration = 0x2e 745 | const ATTRIBUTE_ID_PowerSourceConfiguration_Sources = 0 746 | const CLUSTER_ID_SmokeCOAlarm = 0x5c 747 | const COMMAND_ID_SmokeCOAlarm_SelfTestRequest = 0 748 | const ATTRIBUTE_ID_SmokeCOAlarm_ExpressedState = 0 749 | const ATTRIBUTE_ID_SmokeCOAlarm_SmokeState = 1 750 | const ATTRIBUTE_ID_SmokeCOAlarm_COState = 2 751 | const ATTRIBUTE_ID_SmokeCOAlarm_BatteryAlert = 3 752 | const ATTRIBUTE_ID_SmokeCOAlarm_DeviceMuted = 4 753 | const ATTRIBUTE_ID_SmokeCOAlarm_TestInProgress = 5 754 | const ATTRIBUTE_ID_SmokeCOAlarm_HardwareFaultAlert = 6 755 | const ATTRIBUTE_ID_SmokeCOAlarm_EndOfServiceAlert = 7 756 | const ATTRIBUTE_ID_SmokeCOAlarm_InterconnectSmokeAlarm = 8 757 | const ATTRIBUTE_ID_SmokeCOAlarm_InterconnectCOAlarm = 9 758 | const ATTRIBUTE_ID_SmokeCOAlarm_ContaminationState = 10 759 | const ATTRIBUTE_ID_SmokeCOAlarm_SmokeSensitivityLevel = 11 760 | const ATTRIBUTE_ID_SmokeCOAlarm_ExpiryDate = 12 761 | const CLUSTER_ID_ApplicationBasic = 0x50d 762 | const ATTRIBUTE_ID_ApplicationBasic_VendorName = 0 763 | const ATTRIBUTE_ID_ApplicationBasic_VendorID = 1 764 | const ATTRIBUTE_ID_ApplicationBasic_ApplicationName = 2 765 | const ATTRIBUTE_ID_ApplicationBasic_ProductID = 3 766 | const ATTRIBUTE_ID_ApplicationBasic_Application = 4 767 | const ATTRIBUTE_ID_ApplicationBasic_Status = 5 768 | const ATTRIBUTE_ID_ApplicationBasic_ApplicationVersion = 6 769 | const ATTRIBUTE_ID_ApplicationBasic_AllowedVendorList = 7 770 | const CLUSTER_ID_FanControl = 0x202 771 | const COMMAND_ID_FanControl_Step = 0 772 | const ATTRIBUTE_ID_FanControl_FanMode = 0 773 | const ATTRIBUTE_ID_FanControl_FanModeSequence = 1 774 | const ATTRIBUTE_ID_FanControl_PercentSetting = 2 775 | const ATTRIBUTE_ID_FanControl_PercentCurrent = 3 776 | const ATTRIBUTE_ID_FanControl_SpeedMax = 4 777 | const ATTRIBUTE_ID_FanControl_SpeedSetting = 5 778 | const ATTRIBUTE_ID_FanControl_SpeedCurrent = 6 779 | const ATTRIBUTE_ID_FanControl_RockSupport = 7 780 | const ATTRIBUTE_ID_FanControl_RockSetting = 8 781 | const ATTRIBUTE_ID_FanControl_WindSupport = 9 782 | const ATTRIBUTE_ID_FanControl_WindSetting = 10 783 | const ATTRIBUTE_ID_FanControl_AirflowDirection = 11 784 | const CLUSTER_ID_KeypadInput = 0x509 785 | const COMMAND_ID_KeypadInput_SendKey = 0 786 | const COMMAND_ID_KeypadInput_SendKeyResponse = 1 787 | const CLUSTER_ID_LaundryDryerControls = 0x4a 788 | const ATTRIBUTE_ID_LaundryDryerControls_SupportedDrynessLevels = 0 789 | const ATTRIBUTE_ID_LaundryDryerControls_SelectedDrynessLevel = 1 790 | const CLUSTER_ID_OnOff = 0x6 791 | const COMMAND_ID_OnOff_Off = 0 792 | const COMMAND_ID_OnOff_On = 1 793 | const COMMAND_ID_OnOff_Toggle = 2 794 | const COMMAND_ID_OnOff_OffWithEffect = 64 795 | const COMMAND_ID_OnOff_OnWithRecallGlobalScene = 65 796 | const COMMAND_ID_OnOff_OnWithTimedOff = 66 797 | const ATTRIBUTE_ID_OnOff_OnOff = 0 798 | const ATTRIBUTE_ID_OnOff_GlobalSceneControl = 16384 799 | const ATTRIBUTE_ID_OnOff_OnTime = 16385 800 | const ATTRIBUTE_ID_OnOff_OffWaitTime = 16386 801 | const ATTRIBUTE_ID_OnOff_StartUpOnOff = 16387 802 | const CLUSTER_ID_ColorControl = 0x300 803 | const COMMAND_ID_ColorControl_MoveToHue = 0 804 | const COMMAND_ID_ColorControl_MoveHue = 1 805 | const COMMAND_ID_ColorControl_StepHue = 2 806 | const COMMAND_ID_ColorControl_MoveToSaturation = 3 807 | const COMMAND_ID_ColorControl_MoveSaturation = 4 808 | const COMMAND_ID_ColorControl_StepSaturation = 5 809 | const COMMAND_ID_ColorControl_MoveToHueAndSaturation = 6 810 | const COMMAND_ID_ColorControl_MoveToColor = 7 811 | const COMMAND_ID_ColorControl_MoveColor = 8 812 | const COMMAND_ID_ColorControl_StepColor = 9 813 | const COMMAND_ID_ColorControl_MoveToColorTemperature = 10 814 | const COMMAND_ID_ColorControl_EnhancedMoveToHue = 64 815 | const COMMAND_ID_ColorControl_EnhancedMoveHue = 65 816 | const COMMAND_ID_ColorControl_EnhancedStepHue = 66 817 | const COMMAND_ID_ColorControl_EnhancedMoveToHueAndSaturation = 67 818 | const COMMAND_ID_ColorControl_ColorLoopSet = 68 819 | const COMMAND_ID_ColorControl_StopMoveStep = 71 820 | const COMMAND_ID_ColorControl_MoveColorTemperature = 75 821 | const COMMAND_ID_ColorControl_StepColorTemperature = 76 822 | const ATTRIBUTE_ID_ColorControl_CurrentHue = 0 823 | const ATTRIBUTE_ID_ColorControl_CurrentSaturation = 1 824 | const ATTRIBUTE_ID_ColorControl_RemainingTime = 2 825 | const ATTRIBUTE_ID_ColorControl_CurrentX = 3 826 | const ATTRIBUTE_ID_ColorControl_CurrentY = 4 827 | const ATTRIBUTE_ID_ColorControl_DriftCompensation = 5 828 | const ATTRIBUTE_ID_ColorControl_CompensationText = 6 829 | const ATTRIBUTE_ID_ColorControl_ColorTemperatureMireds = 7 830 | const ATTRIBUTE_ID_ColorControl_ColorMode = 8 831 | const ATTRIBUTE_ID_ColorControl_Options = 15 832 | const ATTRIBUTE_ID_ColorControl_NumberOfPrimaries = 16 833 | const ATTRIBUTE_ID_ColorControl_Primary1X = 17 834 | const ATTRIBUTE_ID_ColorControl_Primary1Y = 18 835 | const ATTRIBUTE_ID_ColorControl_Primary1Intensity = 19 836 | const ATTRIBUTE_ID_ColorControl_Primary2X = 21 837 | const ATTRIBUTE_ID_ColorControl_Primary2Y = 22 838 | const ATTRIBUTE_ID_ColorControl_Primary2Intensity = 23 839 | const ATTRIBUTE_ID_ColorControl_Primary3X = 25 840 | const ATTRIBUTE_ID_ColorControl_Primary3Y = 26 841 | const ATTRIBUTE_ID_ColorControl_Primary3Intensity = 27 842 | const ATTRIBUTE_ID_ColorControl_Primary4X = 32 843 | const ATTRIBUTE_ID_ColorControl_Primary4Y = 33 844 | const ATTRIBUTE_ID_ColorControl_Primary4Intensity = 34 845 | const ATTRIBUTE_ID_ColorControl_Primary5X = 36 846 | const ATTRIBUTE_ID_ColorControl_Primary5Y = 37 847 | const ATTRIBUTE_ID_ColorControl_Primary5Intensity = 38 848 | const ATTRIBUTE_ID_ColorControl_Primary6X = 40 849 | const ATTRIBUTE_ID_ColorControl_Primary6Y = 41 850 | const ATTRIBUTE_ID_ColorControl_Primary6Intensity = 42 851 | const ATTRIBUTE_ID_ColorControl_WhitePointX = 48 852 | const ATTRIBUTE_ID_ColorControl_WhitePointY = 49 853 | const ATTRIBUTE_ID_ColorControl_ColorPointRX = 50 854 | const ATTRIBUTE_ID_ColorControl_ColorPointRY = 51 855 | const ATTRIBUTE_ID_ColorControl_ColorPointRIntensity = 52 856 | const ATTRIBUTE_ID_ColorControl_ColorPointGX = 54 857 | const ATTRIBUTE_ID_ColorControl_ColorPointGY = 55 858 | const ATTRIBUTE_ID_ColorControl_ColorPointGIntensity = 56 859 | const ATTRIBUTE_ID_ColorControl_ColorPointBX = 58 860 | const ATTRIBUTE_ID_ColorControl_ColorPointBY = 59 861 | const ATTRIBUTE_ID_ColorControl_ColorPointBIntensity = 60 862 | const ATTRIBUTE_ID_ColorControl_EnhancedCurrentHue = 16384 863 | const ATTRIBUTE_ID_ColorControl_EnhancedColorMode = 16385 864 | const ATTRIBUTE_ID_ColorControl_ColorLoopActive = 16386 865 | const ATTRIBUTE_ID_ColorControl_ColorLoopDirection = 16387 866 | const ATTRIBUTE_ID_ColorControl_ColorLoopTime = 16388 867 | const ATTRIBUTE_ID_ColorControl_ColorLoopStartEnhancedHue = 16389 868 | const ATTRIBUTE_ID_ColorControl_ColorLoopStoredEnhancedHue = 16390 869 | const ATTRIBUTE_ID_ColorControl_ColorCapabilities = 16394 870 | const ATTRIBUTE_ID_ColorControl_ColorTempPhysicalMinMireds = 16395 871 | const ATTRIBUTE_ID_ColorControl_ColorTempPhysicalMaxMireds = 16396 872 | const ATTRIBUTE_ID_ColorControl_CoupleColorTempToLevelMinMireds = 16397 873 | const ATTRIBUTE_ID_ColorControl_StartUpColorTemperatureMireds = 16400 874 | const CLUSTER_ID_EnergyCalendar = 0x9a 875 | const ATTRIBUTE_ID_EnergyCalendar_CalendarID = 0 876 | const ATTRIBUTE_ID_EnergyCalendar_Name = 1 877 | const ATTRIBUTE_ID_EnergyCalendar_ProviderID = 2 878 | const ATTRIBUTE_ID_EnergyCalendar_EventID = 3 879 | const ATTRIBUTE_ID_EnergyCalendar_StartDate = 4 880 | const ATTRIBUTE_ID_EnergyCalendar_TimeReference = 5 881 | const ATTRIBUTE_ID_EnergyCalendar_CalendarPeriods = 6 882 | const ATTRIBUTE_ID_EnergyCalendar_SpecialDays = 7 883 | const ATTRIBUTE_ID_EnergyCalendar_CurrentDay = 10 884 | const ATTRIBUTE_ID_EnergyCalendar_NextDay = 13 885 | const ATTRIBUTE_ID_EnergyCalendar_CurrentTransition = 13 886 | const ATTRIBUTE_ID_EnergyCalendar_PeakPeriodStatus = 15 887 | const ATTRIBUTE_ID_EnergyCalendar_PeakPeriodStartTime = 16 888 | const ATTRIBUTE_ID_EnergyCalendar_PeakPeriodEndTime = 17 889 | const CLUSTER_ID_ModeSelect = 0x50 890 | const COMMAND_ID_ModeSelect_ChangeToMode = 0 891 | const ATTRIBUTE_ID_ModeSelect_Description = 0 892 | const ATTRIBUTE_ID_ModeSelect_StandardNamespace = 1 893 | const ATTRIBUTE_ID_ModeSelect_SupportedModes = 2 894 | const ATTRIBUTE_ID_ModeSelect_CurrentMode = 3 895 | const ATTRIBUTE_ID_ModeSelect_StartUpMode = 4 896 | const ATTRIBUTE_ID_ModeSelect_OnMode = 5 897 | const CLUSTER_ID_DishwasherMode = 0x59 898 | const ATTRIBUTE_ID_DishwasherMode_SupportedModes = 0 899 | const ATTRIBUTE_ID_DishwasherMode_CurrentMode = 1 900 | const ATTRIBUTE_ID_DishwasherMode_StartUpMode = 2 901 | const ATTRIBUTE_ID_DishwasherMode_OnMode = 3 902 | const CLUSTER_ID_RVCOperationalState = 0x61 903 | const CLUSTER_ID_TimeSync = 0x38 904 | const CLUSTER_ID_DemandResponseandLoadControl = 0x96 905 | const COMMAND_ID_DemandResponseandLoadControl_RegisterLoadControlProgramRequest = 0 906 | const COMMAND_ID_DemandResponseandLoadControl_UnregisterLoadControlProgramRequest = 1 907 | const COMMAND_ID_DemandResponseandLoadControl_AddLoadControlEventRequest = 2 908 | const COMMAND_ID_DemandResponseandLoadControl_RemoveLoadControlEventRequest = 3 909 | const COMMAND_ID_DemandResponseandLoadControl_ClearLoadControlEventsRequest = 4 910 | const ATTRIBUTE_ID_DemandResponseandLoadControl_DeviceClass = 0 911 | const ATTRIBUTE_ID_DemandResponseandLoadControl_LoadControlPrograms = 1 912 | const ATTRIBUTE_ID_DemandResponseandLoadControl_NumberOfLoadControlPrograms = 2 913 | const ATTRIBUTE_ID_DemandResponseandLoadControl_Events = 3 914 | const ATTRIBUTE_ID_DemandResponseandLoadControl_NumberOfEventsPerProgram = 4 915 | const ATTRIBUTE_ID_DemandResponseandLoadControl_NumberOfTransitions = 5 916 | const ATTRIBUTE_ID_DemandResponseandLoadControl_DefaultRandomStart = 6 917 | const ATTRIBUTE_ID_DemandResponseandLoadControl_DefaultRandomDuration = 7 918 | const CLUSTER_ID_DeviceEnergyManagement = 0x98 919 | const COMMAND_ID_DeviceEnergyManagement_PowerAdjustRequest = 0 920 | const COMMAND_ID_DeviceEnergyManagement_CancelPowerAdjustRequest = 1 921 | const COMMAND_ID_DeviceEnergyManagement_StartTimeAdjustRequest = 2 922 | const COMMAND_ID_DeviceEnergyManagement_PauseRequest = 3 923 | const COMMAND_ID_DeviceEnergyManagement_ResumeRequest = 4 924 | const COMMAND_ID_DeviceEnergyManagement_ModifyPowerForecastRequest = 5 925 | const COMMAND_ID_DeviceEnergyManagement_RequestLimitBasedPowerForecast = 6 926 | const ATTRIBUTE_ID_DeviceEnergyManagement_EsaType = 0 927 | const ATTRIBUTE_ID_DeviceEnergyManagement_EsaIsGenerator = 1 928 | const ATTRIBUTE_ID_DeviceEnergyManagement_EsaState = 2 929 | const ATTRIBUTE_ID_DeviceEnergyManagement_AbsMinPower = 3 930 | const ATTRIBUTE_ID_DeviceEnergyManagement_AbsMaxPower = 4 931 | const ATTRIBUTE_ID_DeviceEnergyManagement_PowerAdjustmentCapability = 5 932 | const ATTRIBUTE_ID_DeviceEnergyManagement_PowerForecast = 6 933 | const CLUSTER_ID_GeneralDiagnostics = 0x33 934 | const COMMAND_ID_GeneralDiagnostics_TestEventTrigger = 0 935 | const COMMAND_ID_GeneralDiagnostics_TimeSnapshot = 1 936 | const COMMAND_ID_GeneralDiagnostics_TimeSnapshotResponse = 2 937 | const ATTRIBUTE_ID_GeneralDiagnostics_NetworkInterfaces = 0 938 | const ATTRIBUTE_ID_GeneralDiagnostics_RebootCount = 1 939 | const ATTRIBUTE_ID_GeneralDiagnostics_UpTime = 2 940 | const ATTRIBUTE_ID_GeneralDiagnostics_TotalOperationalHours = 3 941 | const ATTRIBUTE_ID_GeneralDiagnostics_BootReason = 4 942 | const ATTRIBUTE_ID_GeneralDiagnostics_ActiveHardwareFaults = 5 943 | const ATTRIBUTE_ID_GeneralDiagnostics_ActiveRadioFaults = 6 944 | const ATTRIBUTE_ID_GeneralDiagnostics_ActiveNetworkFaults = 7 945 | const ATTRIBUTE_ID_GeneralDiagnostics_TestEventTriggersEnabled = 8 946 | const CLUSTER_ID_EnergyPreference = 0x9b 947 | const ATTRIBUTE_ID_EnergyPreference_EnergyBalances = 0 948 | const ATTRIBUTE_ID_EnergyPreference_CurrentEnergyBalance = 1 949 | const ATTRIBUTE_ID_EnergyPreference_CurrentLowPowerModeSensitivity = 1 950 | const ATTRIBUTE_ID_EnergyPreference_EnergyPriorities = 2 951 | const ATTRIBUTE_ID_EnergyPreference_LowPowerModeSensitivities = 3 952 | const CLUSTER_ID_RVCCleanMode = 0x55 953 | const CLUSTER_ID_RefrigeratorAndTemperatureControlledCabinetMode = 0x52 954 | const ATTRIBUTE_ID_RefrigeratorAndTemperatureControlledCabinetMode_SupportedModes = 0 955 | const ATTRIBUTE_ID_RefrigeratorAndTemperatureControlledCabinetMode_CurrentMode = 1 956 | const ATTRIBUTE_ID_RefrigeratorAndTemperatureControlledCabinetMode_StartUpMode = 2 957 | const ATTRIBUTE_ID_RefrigeratorAndTemperatureControlledCabinetMode_OnMode = 3 958 | const CLUSTER_ID_AudioOutput = 0x50b 959 | const COMMAND_ID_AudioOutput_SelectOutput = 0 960 | const COMMAND_ID_AudioOutput_RenameOutput = 1 961 | const ATTRIBUTE_ID_AudioOutput_OutputList = 0 962 | const ATTRIBUTE_ID_AudioOutput_CurrentOutput = 1 963 | const CLUSTER_ID_MicrowaveOvenControl = 0x50f 964 | const COMMAND_ID_MicrowaveOvenControl_SetCookingParameters = 0 965 | const COMMAND_ID_MicrowaveOvenControl_AddMoreTime = 1 966 | const ATTRIBUTE_ID_MicrowaveOvenControl_CookTime = 1 967 | const ATTRIBUTE_ID_MicrowaveOvenControl_PowerSetting = 2 968 | const ATTRIBUTE_ID_MicrowaveOvenControl_MinPower = 3 969 | const ATTRIBUTE_ID_MicrowaveOvenControl_MaxPower = 4 970 | const ATTRIBUTE_ID_MicrowaveOvenControl_PowerStep = 5 971 | const CLUSTER_ID_OccupancySensing = 0x406 972 | const ATTRIBUTE_ID_OccupancySensing_Occupancy = 0 973 | const ATTRIBUTE_ID_OccupancySensing_OccupancySensorType = 1 974 | const ATTRIBUTE_ID_OccupancySensing_OccupancySensorTypeBitmap = 2 975 | const ATTRIBUTE_ID_OccupancySensing_PIROccupiedToUnoccupiedDelay = 16 976 | const ATTRIBUTE_ID_OccupancySensing_PIRUnoccupiedToOccupiedDelay = 17 977 | const ATTRIBUTE_ID_OccupancySensing_PIRUnoccupiedToOccupiedThreshold = 18 978 | const ATTRIBUTE_ID_OccupancySensing_UltrasonicOccupiedToUnoccupiedDelay = 32 979 | const ATTRIBUTE_ID_OccupancySensing_UltrasonicUnoccupiedToOccupiedDelay = 33 980 | const ATTRIBUTE_ID_OccupancySensing_UltrasonicUnoccupiedToOccupiedThreshold = 34 981 | const ATTRIBUTE_ID_OccupancySensing_PhysicalContactOccupiedToUnoccupiedDelay = 48 982 | const ATTRIBUTE_ID_OccupancySensing_PhysicalContactUnoccupiedToOccupiedDelay = 49 983 | const ATTRIBUTE_ID_OccupancySensing_PhysicalContactUnoccupiedToOccupiedThreshold = 50 984 | const CLUSTER_ID_WakeonLAN = 0x503 985 | const ATTRIBUTE_ID_WakeonLAN_MACAddress = 0 986 | const ATTRIBUTE_ID_WakeonLAN_LinkLocalAddress = 1 987 | const CLUSTER_ID_Binding = 0x1e 988 | const ATTRIBUTE_ID_Binding_Binding = 0 989 | const CLUSTER_ID_LowPower = 0x508 990 | const COMMAND_ID_LowPower_Sleep = 0 991 | const CLUSTER_ID_Timer = 0x47 992 | const COMMAND_ID_Timer_SetTimer = 0 993 | const COMMAND_ID_Timer_ResetTimer = 1 994 | const COMMAND_ID_Timer_AddTime = 2 995 | const COMMAND_ID_Timer_ReduceTime = 3 996 | const ATTRIBUTE_ID_Timer_SetTime = 0 997 | const ATTRIBUTE_ID_Timer_TimeRemaining = 1 998 | const ATTRIBUTE_ID_Timer_TimerState = 2 999 | const CLUSTER_ID_BooleanSensorConfiguration = 0x80 1000 | const COMMAND_ID_BooleanSensorConfiguration_SuppressRequest = 0 1001 | const ATTRIBUTE_ID_BooleanSensorConfiguration_SensitivityLevel = 0 1002 | const ATTRIBUTE_ID_BooleanSensorConfiguration_AlarmsActive = 1 1003 | const ATTRIBUTE_ID_BooleanSensorConfiguration_AlarmsSuppressed = 2 1004 | const ATTRIBUTE_ID_BooleanSensorConfiguration_AlarmsEnabled = 3 1005 | const CLUSTER_ID_DiagnosticLogs = 0x32 1006 | const COMMAND_ID_DiagnosticLogs_RetrieveLogsRequest = 0 1007 | const COMMAND_ID_DiagnosticLogs_RetrieveLogsResponse = 1 1008 | const CLUSTER_ID_GeneralCommissioning = 0x30 1009 | const ATTRIBUTE_ID_GeneralCommissioning_Breadcrumb = 0 1010 | const ATTRIBUTE_ID_GeneralCommissioning_BasicCommissioningInfo = 1 1011 | const ATTRIBUTE_ID_GeneralCommissioning_RegulatoryConfig = 2 1012 | const ATTRIBUTE_ID_GeneralCommissioning_LocationCapability = 3 1013 | const ATTRIBUTE_ID_GeneralCommissioning_SupportsConcurrentConnection = 4 1014 | const CLUSTER_ID_PumpConfigurationandControl = 0x200 1015 | const CLUSTER_ID_Scenes = 0x5 1016 | const COMMAND_ID_Scenes_AddScene = 0 1017 | const COMMAND_ID_Scenes_AddSceneResponse = 0 1018 | const COMMAND_ID_Scenes_ViewScene = 1 1019 | const COMMAND_ID_Scenes_ViewSceneResponse = 1 1020 | const COMMAND_ID_Scenes_RemoveScene = 2 1021 | const COMMAND_ID_Scenes_RemoveSceneResponse = 2 1022 | const COMMAND_ID_Scenes_RemoveAllScenes = 3 1023 | const COMMAND_ID_Scenes_RemoveAllScenesResponse = 3 1024 | const COMMAND_ID_Scenes_StoreScene = 4 1025 | const COMMAND_ID_Scenes_StoreSceneResponse = 4 1026 | const COMMAND_ID_Scenes_RecallScene = 5 1027 | const COMMAND_ID_Scenes_GetSceneMembership = 6 1028 | const COMMAND_ID_Scenes_GetSceneMembershipResponse = 6 1029 | const COMMAND_ID_Scenes_EnhancedAddScene = 64 1030 | const COMMAND_ID_Scenes_EnhancedAddSceneResponse = 64 1031 | const COMMAND_ID_Scenes_EnhancedViewScene = 65 1032 | const COMMAND_ID_Scenes_EnhancedViewSceneResponse = 65 1033 | const COMMAND_ID_Scenes_CopyScene = 66 1034 | const COMMAND_ID_Scenes_CopySceneResponse = 66 1035 | const ATTRIBUTE_ID_Scenes_SceneCount = 0 1036 | const ATTRIBUTE_ID_Scenes_CurrentScene = 1 1037 | const ATTRIBUTE_ID_Scenes_CurrentGroup = 2 1038 | const ATTRIBUTE_ID_Scenes_SceneValid = 3 1039 | const ATTRIBUTE_ID_Scenes_NameSupport = 4 1040 | const ATTRIBUTE_ID_Scenes_LastConfiguredBy = 5 1041 | const ATTRIBUTE_ID_Scenes_SceneTableSize = 6 1042 | const ATTRIBUTE_ID_Scenes_FabricSceneInfo = 7 1043 | 1044 | var ClusterNameMap = map[int]string{ 1045 | CLUSTER_ID_SoftwareDiagnostics: "SoftwareDiagnostics", 1046 | CLUSTER_ID_DoorLock: "DoorLock", 1047 | CLUSTER_ID_Groups: "Groups", 1048 | CLUSTER_ID_Identify: "Identify", 1049 | CLUSTER_ID_LocalizationConfiguration: "LocalizationConfiguration", 1050 | CLUSTER_ID_OvenMode: "OvenMode", 1051 | CLUSTER_ID_ValveConfigurationandControl: "ValveConfigurationandControl", 1052 | CLUSTER_ID_NetworkInfrastructure: "NetworkInfrastructure", 1053 | CLUSTER_ID_Channel: "Channel", 1054 | CLUSTER_ID_UserLabel: "UserLabel", 1055 | CLUSTER_ID_LaundryWasherControls: "LaundryWasherControls", 1056 | CLUSTER_ID_PowerSourceConfiguration: "PowerSourceConfiguration", 1057 | CLUSTER_ID_SmokeCOAlarm: "SmokeCOAlarm", 1058 | CLUSTER_ID_TemperatureMeasurement: "TemperatureMeasurement", 1059 | CLUSTER_ID_ApplicationBasic: "ApplicationBasic", 1060 | CLUSTER_ID_FanControl: "FanControl", 1061 | CLUSTER_ID_KeypadInput: "KeypadInput", 1062 | CLUSTER_ID_LaundryDryerControls: "LaundryDryerControls", 1063 | CLUSTER_ID_OnOff: "OnOff", 1064 | CLUSTER_ID_ColorControl: "ColorControl", 1065 | CLUSTER_ID_EnergyCalendar: "EnergyCalendar", 1066 | CLUSTER_ID_ModeSelect: "ModeSelect", 1067 | CLUSTER_ID_DishwasherMode: "DishwasherMode", 1068 | CLUSTER_ID_RVCOperationalState: "RVCOperationalState", 1069 | CLUSTER_ID_TimeSync: "TimeSync", 1070 | CLUSTER_ID_DemandResponseandLoadControl: "DemandResponseandLoadControl", 1071 | CLUSTER_ID_DeviceEnergyManagement: "DeviceEnergyManagement", 1072 | CLUSTER_ID_GeneralDiagnostics: "GeneralDiagnostics", 1073 | CLUSTER_ID_EnergyPreference: "EnergyPreference", 1074 | CLUSTER_ID_RVCCleanMode: "RVCCleanMode", 1075 | CLUSTER_ID_RefrigeratorAndTemperatureControlledCabinetMode: "RefrigeratorAndTemperatureControlledCabinetMode", 1076 | CLUSTER_ID_AudioOutput: "AudioOutput", 1077 | CLUSTER_ID_MicrowaveOvenControl: "MicrowaveOvenControl", 1078 | CLUSTER_ID_OccupancySensing: "OccupancySensing", 1079 | CLUSTER_ID_WakeonLAN: "WakeonLAN", 1080 | CLUSTER_ID_Binding: "Binding", 1081 | CLUSTER_ID_LowPower: "LowPower", 1082 | CLUSTER_ID_Timer: "Timer", 1083 | CLUSTER_ID_BooleanSensorConfiguration: "BooleanSensorConfiguration", 1084 | CLUSTER_ID_DiagnosticLogs: "DiagnosticLogs", 1085 | CLUSTER_ID_GeneralCommissioning: "GeneralCommissioning", 1086 | CLUSTER_ID_PumpConfigurationandControl: "PumpConfigurationandControl", 1087 | CLUSTER_ID_Scenes: "Scenes", 1088 | CLUSTER_ID_TargetNavigator: "TargetNavigator", 1089 | CLUSTER_ID_AccessControl: "AccessControl", 1090 | CLUSTER_ID_BallastConfiguration: "BallastConfiguration", 1091 | CLUSTER_ID_MediaPlayback: "MediaPlayback", 1092 | CLUSTER_ID_MicrowaveOvenMode: "MicrowaveOvenMode", 1093 | CLUSTER_ID_OperationalCredentials: "OperationalCredentials", 1094 | CLUSTER_ID_OperationalState: "OperationalState", 1095 | CLUSTER_ID_PressureMeasurement: "PressureMeasurement", 1096 | CLUSTER_ID_WindowCovering: "WindowCovering", 1097 | CLUSTER_ID_Descriptor: "Descriptor", 1098 | CLUSTER_ID_ThreadNetworkDiagnostics: "ThreadNetworkDiagnostics", 1099 | CLUSTER_ID_FlowMeasurement: "FlowMeasurement", 1100 | CLUSTER_ID_LevelControl: "LevelControl", 1101 | CLUSTER_ID_Switch: "Switch", 1102 | CLUSTER_ID_Thermostat: "Thermostat", 1103 | CLUSTER_ID_ElectricalEnergyMeasurement: "ElectricalEnergyMeasurement", 1104 | CLUSTER_ID_IlluminanceMeasurement: "IlluminanceMeasurement", 1105 | CLUSTER_ID_FixedLabel: "FixedLabel", 1106 | CLUSTER_ID_LaundryWasherMode: "LaundryWasherMode", 1107 | CLUSTER_ID_NetworkCommissioning: "NetworkCommissioning", 1108 | CLUSTER_ID_OvenCavityOperationalState: "OvenCavityOperationalState", 1109 | CLUSTER_ID_RefrigeratorAlarm: "RefrigeratorAlarm", 1110 | CLUSTER_ID_AirQuality: "AirQuality", 1111 | CLUSTER_ID_ApplicationLauncher: "ApplicationLauncher", 1112 | CLUSTER_ID_BasicInformation: "BasicInformation", 1113 | CLUSTER_ID_EnergyPrice: "EnergyPrice", 1114 | CLUSTER_ID_MediaInput: "MediaInput", 1115 | CLUSTER_ID_TemperatureControl: "TemperatureControl", 1116 | CLUSTER_ID_ContentAppObserver: "ContentAppObserver", 1117 | CLUSTER_ID_ElectricalPowerMeasurement: "ElectricalPowerMeasurement", 1118 | CLUSTER_ID_WaterHeaterManagement: "WaterHeaterManagement", 1119 | CLUSTER_ID_Actions: "Actions", 1120 | CLUSTER_ID_BridgedDeviceBasicInformation: "BridgedDeviceBasicInformation", 1121 | CLUSTER_ID_EthernetNetworkDiagnostics: "EthernetNetworkDiagnostics", 1122 | CLUSTER_ID_WiFiNetworkDiagnostics: "WiFiNetworkDiagnostics", 1123 | CLUSTER_ID_DishwasherAlarm: "DishwasherAlarm", 1124 | CLUSTER_ID_GroupKeyManagement: "GroupKeyManagement", 1125 | CLUSTER_ID_UnitLocalization: "UnitLocalization", 1126 | CLUSTER_ID_AdministratorCommissioning: "AdministratorCommissioning", 1127 | CLUSTER_ID_BooleanState: "BooleanState", 1128 | CLUSTER_ID_TimeFormatLocalization: "TimeFormatLocalization", 1129 | CLUSTER_ID_Messages: "Messages", 1130 | CLUSTER_ID_RVCRunMode: "RVCRunMode", 1131 | CLUSTER_ID_PowerSource: "PowerSource", 1132 | CLUSTER_ID_ThermostatUserInterfaceConfiguration: "ThermostatUserInterfaceConfiguration", 1133 | CLUSTER_ID_AccountLogin: "AccountLogin", 1134 | CLUSTER_ID_ContentLauncher: "ContentLauncher", 1135 | } 1136 | -------------------------------------------------------------------------------- /util.go: -------------------------------------------------------------------------------- 1 | package gomat 2 | 3 | import ( 4 | "bytes" 5 | "crypto/hmac" 6 | "crypto/rand" 7 | "crypto/sha256" 8 | "encoding/binary" 9 | "io" 10 | 11 | "golang.org/x/crypto/hkdf" 12 | ) 13 | 14 | func CreateRandomBytes(n int) []byte { 15 | out := make([]byte, n) 16 | rand.Read(out) 17 | return out 18 | } 19 | 20 | func id_to_bytes(id uint64) []byte { 21 | var b bytes.Buffer 22 | binary.Write(&b, binary.LittleEndian, id) 23 | return b.Bytes() 24 | } 25 | 26 | func hmac_sha256_enc(in []byte, key []byte) []byte { 27 | mac := hmac.New(sha256.New, key) 28 | mac.Write(in) 29 | return mac.Sum(nil) 30 | } 31 | 32 | func sha256_enc(in []byte) []byte { 33 | s := sha256.New() 34 | s.Write(in) 35 | return s.Sum(nil) 36 | } 37 | 38 | func hkdf_sha256(secret, salt, info []byte, size int) []byte { 39 | engine := hkdf.New(sha256.New, secret, salt, info) 40 | key := make([]byte, size) 41 | if _, err := io.ReadFull(engine, key); err != nil { 42 | return []byte{} 43 | } 44 | return key 45 | } 46 | --------------------------------------------------------------------------------