Files

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
}