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 }