mirror of
https://github.com/dpup/meshstream.git
synced 2026-08-06 16:53:09 +02:00
Beginning of message decryption
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
package decoder
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// XOR encrypts or decrypts text with the specified key. It requires the packetID and sending node ID for the AES IV
|
||||
func XOR(text []byte, key []byte, packetID, fromNode uint32) ([]byte, error) {
|
||||
if len(key) != 16 && len(key) != 24 && len(key) != 32 {
|
||||
return nil, fmt.Errorf("key length must be 16, 24, or 32 bytes")
|
||||
}
|
||||
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// The IV needs to be unique, but not secure. It's common to include it at
|
||||
// the beginning of the text. In CTR mode, the IV size is equal to the block size.
|
||||
//if len(text) < aes.BlockSize {
|
||||
// return nil, fmt.Errorf("text too short")
|
||||
//}
|
||||
iv, err := CreateNonce(packetID, fromNode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
//text = text[aes.BlockSize:]
|
||||
|
||||
// CTR mode is the same for both encryption and decryption, so we use
|
||||
// the NewCTR function rather than NewCBCDecrypter.
|
||||
stream := cipher.NewCTR(block, iv)
|
||||
|
||||
// XORKeyStream can work in-place if the two arguments are the same.
|
||||
plaintext := make([]byte, len(text))
|
||||
stream.XORKeyStream(plaintext, text)
|
||||
|
||||
return plaintext, nil
|
||||
}
|
||||
|
||||
// CreateNonce creates a 128-bit nonce.
|
||||
// It takes a uint32 packetId, converts it to a uint64, and a uint32 fromNode.
|
||||
// The nonce is concatenated as [64-bit packetId][32-bit fromNode][32-bit block counter].
|
||||
func CreateNonce(packetId uint32, fromNode uint32) ([]byte, error) {
|
||||
// Expand packetId to 64 bits
|
||||
packetId64 := uint64(packetId)
|
||||
|
||||
// Initialize block counter (32-bit, starts at zero)
|
||||
blockCounter := uint32(0)
|
||||
|
||||
// Create a buffer for the nonce
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
// Write packetId, fromNode, and block counter to the buffer
|
||||
err := binary.Write(buf, binary.LittleEndian, packetId64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = binary.Write(buf, binary.LittleEndian, fromNode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = binary.Write(buf, binary.LittleEndian, blockCounter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
@@ -15,15 +15,15 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
mqttBroker = "mqtt.bayme.sh"
|
||||
mqttUsername = "meshdev"
|
||||
mqttPassword = "large4cats"
|
||||
mqttBroker = "mqtt.bayme.sh"
|
||||
mqttUsername = "meshdev"
|
||||
mqttPassword = "large4cats"
|
||||
mqttTopicPrefix = "msh/US/CA/Motherlode"
|
||||
)
|
||||
|
||||
var messagePubHandler mqtt.MessageHandler = func(client mqtt.Client, msg mqtt.Message) {
|
||||
fmt.Printf("Received message from topic: %s\n", msg.Topic())
|
||||
|
||||
|
||||
// Parse the topic structure
|
||||
topicInfo, err := decoder.ParseTopic(msg.Topic())
|
||||
if err != nil {
|
||||
@@ -35,7 +35,7 @@ var messagePubHandler mqtt.MessageHandler = func(client mqtt.Client, msg mqtt.Me
|
||||
formattedOutput := decoder.FormatMessage(topicInfo, msg.Payload())
|
||||
fmt.Println(formattedOutput)
|
||||
}
|
||||
|
||||
|
||||
fmt.Println(strings.Repeat("-", 80))
|
||||
}
|
||||
|
||||
@@ -50,13 +50,17 @@ var connectLostHandler mqtt.ConnectionLostHandler = func(client mqtt.Client, err
|
||||
func main() {
|
||||
// Set up logging
|
||||
log.SetOutput(os.Stdout)
|
||||
|
||||
|
||||
// Initialize default channel key
|
||||
err := decoder.AddChannelKey("LongFast", decoder.DefaultPrivateKey)
|
||||
if err != nil {
|
||||
log.Printf("Failed to initialize default channel key: %v", err)
|
||||
}
|
||||
|
||||
|
||||
if err := decoder.AddChannelKey("ERSN", "VIuMtC5uDDJtC/ojdH314HLkDIHanX4LdbK5yViV9jA="); err != nil {
|
||||
log.Printf("Failed to initialize ERSN channel key: %v", err)
|
||||
}
|
||||
|
||||
// Create MQTT client options
|
||||
opts := mqtt.NewClientOptions()
|
||||
opts.AddBroker(fmt.Sprintf("tcp://%s:1883", mqttBroker))
|
||||
@@ -93,4 +97,4 @@ func main() {
|
||||
token = client.Unsubscribe(topic)
|
||||
token.Wait()
|
||||
client.Disconnect(250)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -711,7 +711,7 @@ var File_meshtastic_deviceonly_proto protoreflect.FileDescriptor
|
||||
const file_meshtastic_deviceonly_proto_rawDesc = "" +
|
||||
"\n" +
|
||||
"\x1bmeshtastic/deviceonly.proto\x12\n" +
|
||||
"meshtastic\x1a\x18meshtastic/channel.proto\x1a\x15meshtastic/mesh.proto\x1a\x1ameshtastic/telemetry.proto\x1a\x17meshtastic/config.proto\x1a\x1ameshtastic/localonly.proto\x1a\fnanopb.proto\"\xc7\x01\n" +
|
||||
"meshtastic\x1a\x18meshtastic/channel.proto\x1a\x15meshtastic/mesh.proto\x1a\x1ameshtastic/telemetry.proto\x1a\x17meshtastic/config.proto\x1a\x1ameshtastic/localonly.proto\x1a\x17meshtastic/nanopb.proto\"\xc7\x01\n" +
|
||||
"\fPositionLite\x12\x1d\n" +
|
||||
"\n" +
|
||||
"latitude_i\x18\x01 \x01(\x0fR\tlatitudeI\x12\x1f\n" +
|
||||
@@ -846,7 +846,7 @@ func file_meshtastic_deviceonly_proto_init() {
|
||||
file_meshtastic_telemetry_proto_init()
|
||||
file_meshtastic_config_proto_init()
|
||||
file_meshtastic_localonly_proto_init()
|
||||
file_nanopb_proto_init()
|
||||
file_meshtastic_nanopb_proto_init()
|
||||
file_meshtastic_deviceonly_proto_msgTypes[2].OneofWrappers = []any{}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.6
|
||||
// protoc v5.29.3
|
||||
// source: nanopb.proto
|
||||
// source: meshtastic/nanopb.proto
|
||||
|
||||
package generated
|
||||
|
||||
@@ -71,11 +71,11 @@ func (x FieldType) String() string {
|
||||
}
|
||||
|
||||
func (FieldType) Descriptor() protoreflect.EnumDescriptor {
|
||||
return file_nanopb_proto_enumTypes[0].Descriptor()
|
||||
return file_meshtastic_nanopb_proto_enumTypes[0].Descriptor()
|
||||
}
|
||||
|
||||
func (FieldType) Type() protoreflect.EnumType {
|
||||
return &file_nanopb_proto_enumTypes[0]
|
||||
return &file_meshtastic_nanopb_proto_enumTypes[0]
|
||||
}
|
||||
|
||||
func (x FieldType) Number() protoreflect.EnumNumber {
|
||||
@@ -94,7 +94,7 @@ func (x *FieldType) UnmarshalJSON(b []byte) error {
|
||||
|
||||
// Deprecated: Use FieldType.Descriptor instead.
|
||||
func (FieldType) EnumDescriptor() ([]byte, []int) {
|
||||
return file_nanopb_proto_rawDescGZIP(), []int{0}
|
||||
return file_meshtastic_nanopb_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
type IntSize int32
|
||||
@@ -136,11 +136,11 @@ func (x IntSize) String() string {
|
||||
}
|
||||
|
||||
func (IntSize) Descriptor() protoreflect.EnumDescriptor {
|
||||
return file_nanopb_proto_enumTypes[1].Descriptor()
|
||||
return file_meshtastic_nanopb_proto_enumTypes[1].Descriptor()
|
||||
}
|
||||
|
||||
func (IntSize) Type() protoreflect.EnumType {
|
||||
return &file_nanopb_proto_enumTypes[1]
|
||||
return &file_meshtastic_nanopb_proto_enumTypes[1]
|
||||
}
|
||||
|
||||
func (x IntSize) Number() protoreflect.EnumNumber {
|
||||
@@ -159,7 +159,7 @@ func (x *IntSize) UnmarshalJSON(b []byte) error {
|
||||
|
||||
// Deprecated: Use IntSize.Descriptor instead.
|
||||
func (IntSize) EnumDescriptor() ([]byte, []int) {
|
||||
return file_nanopb_proto_rawDescGZIP(), []int{1}
|
||||
return file_meshtastic_nanopb_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
type TypenameMangling int32
|
||||
@@ -198,11 +198,11 @@ func (x TypenameMangling) String() string {
|
||||
}
|
||||
|
||||
func (TypenameMangling) Descriptor() protoreflect.EnumDescriptor {
|
||||
return file_nanopb_proto_enumTypes[2].Descriptor()
|
||||
return file_meshtastic_nanopb_proto_enumTypes[2].Descriptor()
|
||||
}
|
||||
|
||||
func (TypenameMangling) Type() protoreflect.EnumType {
|
||||
return &file_nanopb_proto_enumTypes[2]
|
||||
return &file_meshtastic_nanopb_proto_enumTypes[2]
|
||||
}
|
||||
|
||||
func (x TypenameMangling) Number() protoreflect.EnumNumber {
|
||||
@@ -221,7 +221,7 @@ func (x *TypenameMangling) UnmarshalJSON(b []byte) error {
|
||||
|
||||
// Deprecated: Use TypenameMangling.Descriptor instead.
|
||||
func (TypenameMangling) EnumDescriptor() ([]byte, []int) {
|
||||
return file_nanopb_proto_rawDescGZIP(), []int{2}
|
||||
return file_meshtastic_nanopb_proto_rawDescGZIP(), []int{2}
|
||||
}
|
||||
|
||||
type DescriptorSize int32
|
||||
@@ -263,11 +263,11 @@ func (x DescriptorSize) String() string {
|
||||
}
|
||||
|
||||
func (DescriptorSize) Descriptor() protoreflect.EnumDescriptor {
|
||||
return file_nanopb_proto_enumTypes[3].Descriptor()
|
||||
return file_meshtastic_nanopb_proto_enumTypes[3].Descriptor()
|
||||
}
|
||||
|
||||
func (DescriptorSize) Type() protoreflect.EnumType {
|
||||
return &file_nanopb_proto_enumTypes[3]
|
||||
return &file_meshtastic_nanopb_proto_enumTypes[3]
|
||||
}
|
||||
|
||||
func (x DescriptorSize) Number() protoreflect.EnumNumber {
|
||||
@@ -286,7 +286,7 @@ func (x *DescriptorSize) UnmarshalJSON(b []byte) error {
|
||||
|
||||
// Deprecated: Use DescriptorSize.Descriptor instead.
|
||||
func (DescriptorSize) EnumDescriptor() ([]byte, []int) {
|
||||
return file_nanopb_proto_rawDescGZIP(), []int{3}
|
||||
return file_meshtastic_nanopb_proto_rawDescGZIP(), []int{3}
|
||||
}
|
||||
|
||||
// This is the inner options message, which basically defines options for
|
||||
@@ -404,7 +404,7 @@ const (
|
||||
|
||||
func (x *NanoPBOptions) Reset() {
|
||||
*x = NanoPBOptions{}
|
||||
mi := &file_nanopb_proto_msgTypes[0]
|
||||
mi := &file_meshtastic_nanopb_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -416,7 +416,7 @@ func (x *NanoPBOptions) String() string {
|
||||
func (*NanoPBOptions) ProtoMessage() {}
|
||||
|
||||
func (x *NanoPBOptions) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_nanopb_proto_msgTypes[0]
|
||||
mi := &file_meshtastic_nanopb_proto_msgTypes[0]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -429,7 +429,7 @@ func (x *NanoPBOptions) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use NanoPBOptions.ProtoReflect.Descriptor instead.
|
||||
func (*NanoPBOptions) Descriptor() ([]byte, []int) {
|
||||
return file_nanopb_proto_rawDescGZIP(), []int{0}
|
||||
return file_meshtastic_nanopb_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *NanoPBOptions) GetMaxSize() int32 {
|
||||
@@ -635,14 +635,14 @@ func (x *NanoPBOptions) GetFallbackType() FieldType {
|
||||
return Default_NanoPBOptions_FallbackType
|
||||
}
|
||||
|
||||
var file_nanopb_proto_extTypes = []protoimpl.ExtensionInfo{
|
||||
var file_meshtastic_nanopb_proto_extTypes = []protoimpl.ExtensionInfo{
|
||||
{
|
||||
ExtendedType: (*descriptorpb.FileOptions)(nil),
|
||||
ExtensionType: (*NanoPBOptions)(nil),
|
||||
Field: 1010,
|
||||
Name: "nanopb_fileopt",
|
||||
Tag: "bytes,1010,opt,name=nanopb_fileopt",
|
||||
Filename: "nanopb.proto",
|
||||
Filename: "meshtastic/nanopb.proto",
|
||||
},
|
||||
{
|
||||
ExtendedType: (*descriptorpb.MessageOptions)(nil),
|
||||
@@ -650,7 +650,7 @@ var file_nanopb_proto_extTypes = []protoimpl.ExtensionInfo{
|
||||
Field: 1010,
|
||||
Name: "nanopb_msgopt",
|
||||
Tag: "bytes,1010,opt,name=nanopb_msgopt",
|
||||
Filename: "nanopb.proto",
|
||||
Filename: "meshtastic/nanopb.proto",
|
||||
},
|
||||
{
|
||||
ExtendedType: (*descriptorpb.EnumOptions)(nil),
|
||||
@@ -658,7 +658,7 @@ var file_nanopb_proto_extTypes = []protoimpl.ExtensionInfo{
|
||||
Field: 1010,
|
||||
Name: "nanopb_enumopt",
|
||||
Tag: "bytes,1010,opt,name=nanopb_enumopt",
|
||||
Filename: "nanopb.proto",
|
||||
Filename: "meshtastic/nanopb.proto",
|
||||
},
|
||||
{
|
||||
ExtendedType: (*descriptorpb.FieldOptions)(nil),
|
||||
@@ -666,39 +666,39 @@ var file_nanopb_proto_extTypes = []protoimpl.ExtensionInfo{
|
||||
Field: 1010,
|
||||
Name: "nanopb",
|
||||
Tag: "bytes,1010,opt,name=nanopb",
|
||||
Filename: "nanopb.proto",
|
||||
Filename: "meshtastic/nanopb.proto",
|
||||
},
|
||||
}
|
||||
|
||||
// Extension fields to descriptorpb.FileOptions.
|
||||
var (
|
||||
// optional NanoPBOptions nanopb_fileopt = 1010;
|
||||
E_NanopbFileopt = &file_nanopb_proto_extTypes[0]
|
||||
E_NanopbFileopt = &file_meshtastic_nanopb_proto_extTypes[0]
|
||||
)
|
||||
|
||||
// Extension fields to descriptorpb.MessageOptions.
|
||||
var (
|
||||
// optional NanoPBOptions nanopb_msgopt = 1010;
|
||||
E_NanopbMsgopt = &file_nanopb_proto_extTypes[1]
|
||||
E_NanopbMsgopt = &file_meshtastic_nanopb_proto_extTypes[1]
|
||||
)
|
||||
|
||||
// Extension fields to descriptorpb.EnumOptions.
|
||||
var (
|
||||
// optional NanoPBOptions nanopb_enumopt = 1010;
|
||||
E_NanopbEnumopt = &file_nanopb_proto_extTypes[2]
|
||||
E_NanopbEnumopt = &file_meshtastic_nanopb_proto_extTypes[2]
|
||||
)
|
||||
|
||||
// Extension fields to descriptorpb.FieldOptions.
|
||||
var (
|
||||
// optional NanoPBOptions nanopb = 1010;
|
||||
E_Nanopb = &file_nanopb_proto_extTypes[3]
|
||||
E_Nanopb = &file_meshtastic_nanopb_proto_extTypes[3]
|
||||
)
|
||||
|
||||
var File_nanopb_proto protoreflect.FileDescriptor
|
||||
var File_meshtastic_nanopb_proto protoreflect.FileDescriptor
|
||||
|
||||
const file_nanopb_proto_rawDesc = "" +
|
||||
const file_meshtastic_nanopb_proto_rawDesc = "" +
|
||||
"\n" +
|
||||
"\fnanopb.proto\x1a google/protobuf/descriptor.proto\"\x86\n" +
|
||||
"\x17meshtastic/nanopb.proto\x1a google/protobuf/descriptor.proto\"\x86\n" +
|
||||
"\n" +
|
||||
"\rNanoPBOptions\x12\x19\n" +
|
||||
"\bmax_size\x18\x01 \x01(\x05R\amaxSize\x12\x1d\n" +
|
||||
@@ -775,20 +775,20 @@ const file_nanopb_proto_rawDesc = "" +
|
||||
"\x18fi.kapsi.koti.jpa.nanopbZ\"github.com/meshtastic/go/generated"
|
||||
|
||||
var (
|
||||
file_nanopb_proto_rawDescOnce sync.Once
|
||||
file_nanopb_proto_rawDescData []byte
|
||||
file_meshtastic_nanopb_proto_rawDescOnce sync.Once
|
||||
file_meshtastic_nanopb_proto_rawDescData []byte
|
||||
)
|
||||
|
||||
func file_nanopb_proto_rawDescGZIP() []byte {
|
||||
file_nanopb_proto_rawDescOnce.Do(func() {
|
||||
file_nanopb_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_nanopb_proto_rawDesc), len(file_nanopb_proto_rawDesc)))
|
||||
func file_meshtastic_nanopb_proto_rawDescGZIP() []byte {
|
||||
file_meshtastic_nanopb_proto_rawDescOnce.Do(func() {
|
||||
file_meshtastic_nanopb_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_meshtastic_nanopb_proto_rawDesc), len(file_meshtastic_nanopb_proto_rawDesc)))
|
||||
})
|
||||
return file_nanopb_proto_rawDescData
|
||||
return file_meshtastic_nanopb_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_nanopb_proto_enumTypes = make([]protoimpl.EnumInfo, 4)
|
||||
var file_nanopb_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
|
||||
var file_nanopb_proto_goTypes = []any{
|
||||
var file_meshtastic_nanopb_proto_enumTypes = make([]protoimpl.EnumInfo, 4)
|
||||
var file_meshtastic_nanopb_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
|
||||
var file_meshtastic_nanopb_proto_goTypes = []any{
|
||||
(FieldType)(0), // 0: FieldType
|
||||
(IntSize)(0), // 1: IntSize
|
||||
(TypenameMangling)(0), // 2: TypenameMangling
|
||||
@@ -800,7 +800,7 @@ var file_nanopb_proto_goTypes = []any{
|
||||
(*descriptorpb.EnumOptions)(nil), // 8: google.protobuf.EnumOptions
|
||||
(*descriptorpb.FieldOptions)(nil), // 9: google.protobuf.FieldOptions
|
||||
}
|
||||
var file_nanopb_proto_depIdxs = []int32{
|
||||
var file_meshtastic_nanopb_proto_depIdxs = []int32{
|
||||
1, // 0: NanoPBOptions.int_size:type_name -> IntSize
|
||||
0, // 1: NanoPBOptions.type:type_name -> FieldType
|
||||
2, // 2: NanoPBOptions.mangle_names:type_name -> TypenameMangling
|
||||
@@ -822,28 +822,28 @@ var file_nanopb_proto_depIdxs = []int32{
|
||||
0, // [0:6] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_nanopb_proto_init() }
|
||||
func file_nanopb_proto_init() {
|
||||
if File_nanopb_proto != nil {
|
||||
func init() { file_meshtastic_nanopb_proto_init() }
|
||||
func file_meshtastic_nanopb_proto_init() {
|
||||
if File_meshtastic_nanopb_proto != nil {
|
||||
return
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_nanopb_proto_rawDesc), len(file_nanopb_proto_rawDesc)),
|
||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_meshtastic_nanopb_proto_rawDesc), len(file_meshtastic_nanopb_proto_rawDesc)),
|
||||
NumEnums: 4,
|
||||
NumMessages: 1,
|
||||
NumExtensions: 4,
|
||||
NumServices: 0,
|
||||
},
|
||||
GoTypes: file_nanopb_proto_goTypes,
|
||||
DependencyIndexes: file_nanopb_proto_depIdxs,
|
||||
EnumInfos: file_nanopb_proto_enumTypes,
|
||||
MessageInfos: file_nanopb_proto_msgTypes,
|
||||
ExtensionInfos: file_nanopb_proto_extTypes,
|
||||
GoTypes: file_meshtastic_nanopb_proto_goTypes,
|
||||
DependencyIndexes: file_meshtastic_nanopb_proto_depIdxs,
|
||||
EnumInfos: file_meshtastic_nanopb_proto_enumTypes,
|
||||
MessageInfos: file_meshtastic_nanopb_proto_msgTypes,
|
||||
ExtensionInfos: file_meshtastic_nanopb_proto_extTypes,
|
||||
}.Build()
|
||||
File_nanopb_proto = out.File
|
||||
file_nanopb_proto_goTypes = nil
|
||||
file_nanopb_proto_depIdxs = nil
|
||||
File_meshtastic_nanopb_proto = out.File
|
||||
file_meshtastic_nanopb_proto_goTypes = nil
|
||||
file_meshtastic_nanopb_proto_depIdxs = nil
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import "meshtastic/mesh.proto";
|
||||
import "meshtastic/telemetry.proto";
|
||||
import "meshtastic/config.proto";
|
||||
import "meshtastic/localonly.proto";
|
||||
import "nanopb.proto";
|
||||
import "meshtastic/nanopb.proto";
|
||||
|
||||
option csharp_namespace = "Meshtastic.Protobufs";
|
||||
option go_package = "github.com/meshtastic/go/generated";
|
||||
|
||||
Reference in New Issue
Block a user