209 lines
6.0 KiB
Go
209 lines
6.0 KiB
Go
package mesh
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"encoding/binary"
|
|
"fmt"
|
|
"strings"
|
|
"unicode/utf8"
|
|
|
|
"google.golang.org/protobuf/encoding/protowire"
|
|
)
|
|
|
|
// BuildOptions 是构建 MeshPacket 的公共选项。
|
|
type BuildOptions struct {
|
|
FromNodeNum uint32
|
|
ToNodeNum uint32
|
|
PacketID uint32
|
|
ChannelID string
|
|
GatewayID string
|
|
PSK []byte
|
|
Encrypt bool
|
|
ViaMQTT bool
|
|
}
|
|
|
|
// TextBuildOptions 是构建文本消息的选项。
|
|
type TextBuildOptions struct {
|
|
BuildOptions
|
|
Text string
|
|
}
|
|
|
|
// NodeInfoBuildOptions 是构建节点信息广播的选项。
|
|
type NodeInfoBuildOptions struct {
|
|
BuildOptions
|
|
NodeID string
|
|
LongName string
|
|
ShortName string
|
|
HWModel uint32
|
|
Role uint32
|
|
IsLicensed bool
|
|
PublicKey []byte
|
|
}
|
|
|
|
// RandomPacketID 生成一个非零随机 packet id。
|
|
func RandomPacketID() uint32 {
|
|
var buf [4]byte
|
|
if _, err := rand.Read(buf[:]); err != nil {
|
|
return 1
|
|
}
|
|
id := binary.LittleEndian.Uint32(buf[:])
|
|
if id == 0 {
|
|
id = 1
|
|
}
|
|
return id
|
|
}
|
|
|
|
// BuildTextServiceEnvelope 构建一条加密的文本消息 ServiceEnvelope。
|
|
func BuildTextServiceEnvelope(opts TextBuildOptions) ([]byte, error) {
|
|
if opts.FromNodeNum == 0 {
|
|
return nil, fmt.Errorf("from node number is required")
|
|
}
|
|
if opts.PacketID == 0 {
|
|
return nil, fmt.Errorf("packet id is required")
|
|
}
|
|
if opts.ChannelID == "" {
|
|
return nil, fmt.Errorf("channel id is required")
|
|
}
|
|
if opts.GatewayID == "" {
|
|
opts.GatewayID = NodeNumToID(opts.FromNodeNum)
|
|
}
|
|
if opts.Text == "" {
|
|
return nil, fmt.Errorf("text is required")
|
|
}
|
|
if !utf8.ValidString(opts.Text) {
|
|
return nil, fmt.Errorf("text must be valid utf-8")
|
|
}
|
|
data := buildData(PortNumTextMessage, []byte(opts.Text))
|
|
packet, err := buildMeshPacket(opts.BuildOptions, data)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return buildServiceEnvelope(packet, opts.ChannelID, opts.GatewayID), nil
|
|
}
|
|
|
|
// BuildNodeInfoServiceEnvelope 构建一条节点信息广播 ServiceEnvelope。
|
|
func BuildNodeInfoServiceEnvelope(opts NodeInfoBuildOptions) ([]byte, error) {
|
|
if opts.FromNodeNum == 0 {
|
|
return nil, fmt.Errorf("from node number is required")
|
|
}
|
|
if opts.NodeID == "" {
|
|
opts.NodeID = NodeNumToID(opts.FromNodeNum)
|
|
}
|
|
if opts.GatewayID == "" {
|
|
opts.GatewayID = NodeNumToID(opts.FromNodeNum)
|
|
}
|
|
if opts.ChannelID == "" {
|
|
return nil, fmt.Errorf("channel id is required")
|
|
}
|
|
if opts.LongName == "" {
|
|
opts.LongName = NodeNumToID(opts.FromNodeNum)
|
|
}
|
|
if opts.ShortName == "" {
|
|
opts.ShortName = strings.ToUpper(opts.LongName)
|
|
if len(opts.ShortName) > 4 {
|
|
opts.ShortName = opts.ShortName[:4]
|
|
}
|
|
}
|
|
user := buildUser(opts)
|
|
data := buildData(PortNumNodeInfo, user)
|
|
packet, err := buildMeshPacket(opts.BuildOptions, data)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return buildServiceEnvelope(packet, opts.ChannelID, opts.GatewayID), nil
|
|
}
|
|
|
|
func buildData(portnum uint32, payload []byte) []byte {
|
|
var out []byte
|
|
out = protowire.AppendTag(out, 1, protowire.VarintType)
|
|
out = protowire.AppendVarint(out, uint64(portnum))
|
|
out = protowire.AppendTag(out, 2, protowire.BytesType)
|
|
out = protowire.AppendBytes(out, payload)
|
|
return out
|
|
}
|
|
|
|
func buildUser(opts NodeInfoBuildOptions) []byte {
|
|
var out []byte
|
|
out = protowire.AppendTag(out, 1, protowire.BytesType)
|
|
out = protowire.AppendBytes(out, []byte(opts.NodeID))
|
|
out = protowire.AppendTag(out, 2, protowire.BytesType)
|
|
out = protowire.AppendBytes(out, []byte(opts.LongName))
|
|
out = protowire.AppendTag(out, 3, protowire.BytesType)
|
|
out = protowire.AppendBytes(out, []byte(opts.ShortName))
|
|
if opts.HWModel != 0 {
|
|
out = protowire.AppendTag(out, 5, protowire.VarintType)
|
|
out = protowire.AppendVarint(out, uint64(opts.HWModel))
|
|
}
|
|
out = protowire.AppendTag(out, 6, protowire.VarintType)
|
|
if opts.IsLicensed {
|
|
out = protowire.AppendVarint(out, 1)
|
|
} else {
|
|
out = protowire.AppendVarint(out, 0)
|
|
}
|
|
out = protowire.AppendTag(out, 7, protowire.VarintType)
|
|
out = protowire.AppendVarint(out, uint64(opts.Role))
|
|
if len(opts.PublicKey) > 0 {
|
|
out = protowire.AppendTag(out, 8, protowire.BytesType)
|
|
out = protowire.AppendBytes(out, opts.PublicKey)
|
|
}
|
|
return out
|
|
}
|
|
|
|
func buildMeshPacket(opts BuildOptions, data []byte) ([]byte, error) {
|
|
if opts.FromNodeNum == 0 {
|
|
return nil, fmt.Errorf("from node number is required")
|
|
}
|
|
if opts.PacketID == 0 {
|
|
return nil, fmt.Errorf("packet id is required")
|
|
}
|
|
if opts.ChannelID == "" {
|
|
return nil, fmt.Errorf("channel id is required")
|
|
}
|
|
var out []byte
|
|
out = protowire.AppendTag(out, 1, protowire.Fixed32Type)
|
|
out = protowire.AppendFixed32(out, opts.FromNodeNum)
|
|
out = protowire.AppendTag(out, 2, protowire.Fixed32Type)
|
|
out = protowire.AppendFixed32(out, opts.ToNodeNum)
|
|
|
|
if opts.Encrypt {
|
|
if len(opts.PSK) == 0 {
|
|
return nil, fmt.Errorf("psk is required for encrypted packet")
|
|
}
|
|
ciphertext, err := cryptAESCTR(opts.PSK, opts.FromNodeNum, opts.PacketID, data)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
out = protowire.AppendTag(out, 3, protowire.VarintType)
|
|
out = protowire.AppendVarint(out, uint64(channelHash(opts.ChannelID, opts.PSK)))
|
|
out = protowire.AppendTag(out, 5, protowire.BytesType)
|
|
out = protowire.AppendBytes(out, ciphertext)
|
|
} else {
|
|
out = protowire.AppendTag(out, 4, protowire.BytesType)
|
|
out = protowire.AppendBytes(out, data)
|
|
}
|
|
|
|
out = protowire.AppendTag(out, 6, protowire.Fixed32Type)
|
|
out = protowire.AppendFixed32(out, opts.PacketID)
|
|
if opts.ViaMQTT {
|
|
out = protowire.AppendTag(out, 14, protowire.VarintType)
|
|
out = protowire.AppendVarint(out, 1)
|
|
}
|
|
// hop_limit = 7(默认)
|
|
out = protowire.AppendTag(out, 9, protowire.VarintType)
|
|
out = protowire.AppendVarint(out, 7)
|
|
out = protowire.AppendTag(out, 15, protowire.VarintType)
|
|
out = protowire.AppendVarint(out, 7)
|
|
return out, nil
|
|
}
|
|
|
|
func buildServiceEnvelope(packet []byte, channelID string, gatewayID string) []byte {
|
|
var out []byte
|
|
out = protowire.AppendTag(out, 1, protowire.BytesType)
|
|
out = protowire.AppendBytes(out, packet)
|
|
out = protowire.AppendTag(out, 2, protowire.BytesType)
|
|
out = protowire.AppendBytes(out, []byte(channelID))
|
|
out = protowire.AppendTag(out, 3, protowire.BytesType)
|
|
out = protowire.AppendBytes(out, []byte(gatewayID))
|
|
return out
|
|
}
|