commit 4ee1259d51b92e09410814ba903df1b1e9c0b976 Author: dima <1.e4.kc6@gmail.com> Date: Thu Oct 16 19:38:09 2025 +0000 first commit diff --git a/README b/README new file mode 100644 index 0000000..4ace9a8 --- /dev/null +++ b/README @@ -0,0 +1,19 @@ +tcp-protobuf-app/ +├── proto/ +│ └── service/ +│ └── v1/ +│ └── service.proto # Схема Protobuf +├── buf.yaml # Конфігурація Buf +├── buf.gen.yaml # Налаштування генерації +├── server/ +│ └── main.go +└── client/ + └── main.go + + + go install github.com/bufbuild/buf/cmd/buf@latest + + go install google.golang.org/protobuf/cmd/protoc-gen-go@latest + + + go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest \ No newline at end of file diff --git a/bin/bin.go b/bin/bin.go new file mode 100644 index 0000000..b184bac --- /dev/null +++ b/bin/bin.go @@ -0,0 +1,46 @@ +package bin + +import ( + "bytes" + "fmt" + "io" + "net" + + "google.golang.org/protobuf/proto" +) + +func WriteMessage(conn net.Conn, m proto.Message) error { + data, err := proto.Marshal(m) + if err != nil { + return fmt.Errorf("proto.Marshal: %w", err) + } + var ( + length = len(data) + w = bytes.NewBuffer(nil) + ) + w.Write([]byte{ + byte(length), + byte(length >> 8), + }) + w.Write(data) + if _, err := conn.Write(w.Bytes()); err != nil { + return fmt.Errorf("conn.Write: %w", err) + } + return nil +} + +func ReadMessage(conn net.Conn, m proto.Message) error { + buf := make([]byte, 2) + if _, err := io.ReadFull(conn, buf); err != nil { + return fmt.Errorf("io.ReadFull: %w", err) + } + length := int(buf[0]) | (int(buf[1]) << 8) + buf = make([]byte, length) + if _, err := io.ReadFull(conn, buf); err != nil { + return fmt.Errorf("io.ReadFull: %w", err) + } + if err := proto.Unmarshal(buf, m); err != nil { + return fmt.Errorf("proto.Unmarshal: %w", err) + } + return nil +} diff --git a/buf.gen.yaml b/buf.gen.yaml new file mode 100644 index 0000000..45435c9 --- /dev/null +++ b/buf.gen.yaml @@ -0,0 +1,5 @@ +version: v1 +plugins: + - name: go + out: gen + opt: paths=source_relative \ No newline at end of file diff --git a/buf.yaml b/buf.yaml new file mode 100644 index 0000000..61a4e3b --- /dev/null +++ b/buf.yaml @@ -0,0 +1,8 @@ +version: v1 +name: buf.build/dima/tcp-service +lint: + use: + - DEFAULT +breaking: + use: + - FILE \ No newline at end of file diff --git a/client/main.go b/client/main.go new file mode 100644 index 0000000..65f2203 --- /dev/null +++ b/client/main.go @@ -0,0 +1,82 @@ +package main + +import ( + "log" + "net" + "time" + + pb "gordenko.dev/dima/protolab/gen/service/v1" + + "gordenko.dev/dima/protolab/bin" +) + +const ( + ServerAddr = "localhost:8080" +) + +func main() { + conn, err := net.Dial("tcp", ServerAddr) + if err != nil { + log.Fatalf("net.Dial: %v\n", err) + } + defer conn.Close() + + log.Println("connected to server") + + for { + // PING + pingReq := &pb.RequestWrapper{ + Payload: &pb.RequestWrapper_Ping{ + Ping: &pb.PingRequest{ + Timestamp: time.Now().Unix(), + }, + }, + } + + err = bin.WriteMessage(conn, pingReq) + if err != nil { + log.Fatalf("send ping: %v", err) + } else { + log.Println("ping sent") + } + + var pingResp pb.ResponseWrapper + err = bin.ReadMessage(conn, &pingResp) + if err != nil { + log.Fatalf("read ping response: %v", err) + } + if resp := pingResp.GetPingResp(); resp != nil { + log.Printf("pong received, server time: %s\n", + time.Unix(resp.GetServerTimestamp(), 0).Format(time.RFC3339)) + } + + time.Sleep(3 * time.Second) + // TELEMETRY + telemetryReq := &pb.RequestWrapper{ + Payload: &pb.RequestWrapper_SendTelemetry{ + SendTelemetry: &pb.TelemetryData{ + DeviceId: "device-123", + Timestamp: time.Now().Unix(), + Temperature: 25, + BatteryLevel: 70, + }, + }, + } + err = bin.WriteMessage(conn, telemetryReq) + if err != nil { + log.Fatalf("send telemetry: %v", err) + } else { + log.Println("telemetry sent") + } + + var telemetryResp pb.ResponseWrapper + err = bin.ReadMessage(conn, &telemetryResp) + if err != nil { + log.Fatalf("bin.ReadMessage: %v", err) + } + if resp := telemetryResp.GetTelemetryResp(); resp != nil { + log.Printf("server response on TelemetryData: %s\n", resp.GetResultCode().String()) + } + time.Sleep(3 * time.Second) + } +} diff --git a/gen/service/v1/service.pb.go b/gen/service/v1/service.pb.go new file mode 100644 index 0000000..984adf3 --- /dev/null +++ b/gen/service/v1/service.pb.go @@ -0,0 +1,536 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.10 +// protoc (unknown) +// source: service/v1/service.proto + +package servicev1 + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type TelemetryResponse_ResultCode int32 + +const ( + TelemetryResponse_RESULT_CODE_UNSPECIFIED TelemetryResponse_ResultCode = 0 + TelemetryResponse_RESULT_CODE_SUCCEEDED TelemetryResponse_ResultCode = 1 + TelemetryResponse_RESULT_CODE_FAILED TelemetryResponse_ResultCode = 2 +) + +// Enum value maps for TelemetryResponse_ResultCode. +var ( + TelemetryResponse_ResultCode_name = map[int32]string{ + 0: "RESULT_CODE_UNSPECIFIED", + 1: "RESULT_CODE_SUCCEEDED", + 2: "RESULT_CODE_FAILED", + } + TelemetryResponse_ResultCode_value = map[string]int32{ + "RESULT_CODE_UNSPECIFIED": 0, + "RESULT_CODE_SUCCEEDED": 1, + "RESULT_CODE_FAILED": 2, + } +) + +func (x TelemetryResponse_ResultCode) Enum() *TelemetryResponse_ResultCode { + p := new(TelemetryResponse_ResultCode) + *p = x + return p +} + +func (x TelemetryResponse_ResultCode) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (TelemetryResponse_ResultCode) Descriptor() protoreflect.EnumDescriptor { + return file_service_v1_service_proto_enumTypes[0].Descriptor() +} + +func (TelemetryResponse_ResultCode) Type() protoreflect.EnumType { + return &file_service_v1_service_proto_enumTypes[0] +} + +func (x TelemetryResponse_ResultCode) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use TelemetryResponse_ResultCode.Descriptor instead. +func (TelemetryResponse_ResultCode) EnumDescriptor() ([]byte, []int) { + return file_service_v1_service_proto_rawDescGZIP(), []int{3, 0} +} + +type TelemetryData struct { + state protoimpl.MessageState `protogen:"open.v1"` + DeviceId string `protobuf:"bytes,1,opt,name=device_id,json=deviceId,proto3" json:"device_id,omitempty"` + Timestamp int64 `protobuf:"varint,2,opt,name=timestamp,proto3" json:"timestamp,omitempty"` + Temperature float64 `protobuf:"fixed64,3,opt,name=temperature,proto3" json:"temperature,omitempty"` + BatteryLevel int32 `protobuf:"varint,4,opt,name=battery_level,json=batteryLevel,proto3" json:"battery_level,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TelemetryData) Reset() { + *x = TelemetryData{} + mi := &file_service_v1_service_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TelemetryData) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TelemetryData) ProtoMessage() {} + +func (x *TelemetryData) ProtoReflect() protoreflect.Message { + mi := &file_service_v1_service_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TelemetryData.ProtoReflect.Descriptor instead. +func (*TelemetryData) Descriptor() ([]byte, []int) { + return file_service_v1_service_proto_rawDescGZIP(), []int{0} +} + +func (x *TelemetryData) GetDeviceId() string { + if x != nil { + return x.DeviceId + } + return "" +} + +func (x *TelemetryData) GetTimestamp() int64 { + if x != nil { + return x.Timestamp + } + return 0 +} + +func (x *TelemetryData) GetTemperature() float64 { + if x != nil { + return x.Temperature + } + return 0 +} + +func (x *TelemetryData) GetBatteryLevel() int32 { + if x != nil { + return x.BatteryLevel + } + return 0 +} + +type PingRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Timestamp int64 `protobuf:"varint,1,opt,name=timestamp,proto3" json:"timestamp,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PingRequest) Reset() { + *x = PingRequest{} + mi := &file_service_v1_service_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PingRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PingRequest) ProtoMessage() {} + +func (x *PingRequest) ProtoReflect() protoreflect.Message { + mi := &file_service_v1_service_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PingRequest.ProtoReflect.Descriptor instead. +func (*PingRequest) Descriptor() ([]byte, []int) { + return file_service_v1_service_proto_rawDescGZIP(), []int{1} +} + +func (x *PingRequest) GetTimestamp() int64 { + if x != nil { + return x.Timestamp + } + return 0 +} + +type RequestWrapper struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Types that are valid to be assigned to Payload: + // + // *RequestWrapper_SendTelemetry + // *RequestWrapper_Ping + Payload isRequestWrapper_Payload `protobuf_oneof:"payload"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RequestWrapper) Reset() { + *x = RequestWrapper{} + mi := &file_service_v1_service_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RequestWrapper) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RequestWrapper) ProtoMessage() {} + +func (x *RequestWrapper) ProtoReflect() protoreflect.Message { + mi := &file_service_v1_service_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RequestWrapper.ProtoReflect.Descriptor instead. +func (*RequestWrapper) Descriptor() ([]byte, []int) { + return file_service_v1_service_proto_rawDescGZIP(), []int{2} +} + +func (x *RequestWrapper) GetPayload() isRequestWrapper_Payload { + if x != nil { + return x.Payload + } + return nil +} + +func (x *RequestWrapper) GetSendTelemetry() *TelemetryData { + if x != nil { + if x, ok := x.Payload.(*RequestWrapper_SendTelemetry); ok { + return x.SendTelemetry + } + } + return nil +} + +func (x *RequestWrapper) GetPing() *PingRequest { + if x != nil { + if x, ok := x.Payload.(*RequestWrapper_Ping); ok { + return x.Ping + } + } + return nil +} + +type isRequestWrapper_Payload interface { + isRequestWrapper_Payload() +} + +type RequestWrapper_SendTelemetry struct { + SendTelemetry *TelemetryData `protobuf:"bytes,1,opt,name=send_telemetry,json=sendTelemetry,proto3,oneof"` +} + +type RequestWrapper_Ping struct { + Ping *PingRequest `protobuf:"bytes,2,opt,name=ping,proto3,oneof"` +} + +func (*RequestWrapper_SendTelemetry) isRequestWrapper_Payload() {} + +func (*RequestWrapper_Ping) isRequestWrapper_Payload() {} + +type TelemetryResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + ResultCode TelemetryResponse_ResultCode `protobuf:"varint,1,opt,name=result_code,json=resultCode,proto3,enum=service.v1.TelemetryResponse_ResultCode" json:"result_code,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TelemetryResponse) Reset() { + *x = TelemetryResponse{} + mi := &file_service_v1_service_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TelemetryResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TelemetryResponse) ProtoMessage() {} + +func (x *TelemetryResponse) ProtoReflect() protoreflect.Message { + mi := &file_service_v1_service_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TelemetryResponse.ProtoReflect.Descriptor instead. +func (*TelemetryResponse) Descriptor() ([]byte, []int) { + return file_service_v1_service_proto_rawDescGZIP(), []int{3} +} + +func (x *TelemetryResponse) GetResultCode() TelemetryResponse_ResultCode { + if x != nil { + return x.ResultCode + } + return TelemetryResponse_RESULT_CODE_UNSPECIFIED +} + +type PingResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + ServerTimestamp int64 `protobuf:"varint,1,opt,name=server_timestamp,json=serverTimestamp,proto3" json:"server_timestamp,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PingResponse) Reset() { + *x = PingResponse{} + mi := &file_service_v1_service_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PingResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PingResponse) ProtoMessage() {} + +func (x *PingResponse) ProtoReflect() protoreflect.Message { + mi := &file_service_v1_service_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PingResponse.ProtoReflect.Descriptor instead. +func (*PingResponse) Descriptor() ([]byte, []int) { + return file_service_v1_service_proto_rawDescGZIP(), []int{4} +} + +func (x *PingResponse) GetServerTimestamp() int64 { + if x != nil { + return x.ServerTimestamp + } + return 0 +} + +type ResponseWrapper struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Types that are valid to be assigned to Payload: + // + // *ResponseWrapper_TelemetryResp + // *ResponseWrapper_PingResp + Payload isResponseWrapper_Payload `protobuf_oneof:"payload"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ResponseWrapper) Reset() { + *x = ResponseWrapper{} + mi := &file_service_v1_service_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ResponseWrapper) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ResponseWrapper) ProtoMessage() {} + +func (x *ResponseWrapper) ProtoReflect() protoreflect.Message { + mi := &file_service_v1_service_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ResponseWrapper.ProtoReflect.Descriptor instead. +func (*ResponseWrapper) Descriptor() ([]byte, []int) { + return file_service_v1_service_proto_rawDescGZIP(), []int{5} +} + +func (x *ResponseWrapper) GetPayload() isResponseWrapper_Payload { + if x != nil { + return x.Payload + } + return nil +} + +func (x *ResponseWrapper) GetTelemetryResp() *TelemetryResponse { + if x != nil { + if x, ok := x.Payload.(*ResponseWrapper_TelemetryResp); ok { + return x.TelemetryResp + } + } + return nil +} + +func (x *ResponseWrapper) GetPingResp() *PingResponse { + if x != nil { + if x, ok := x.Payload.(*ResponseWrapper_PingResp); ok { + return x.PingResp + } + } + return nil +} + +type isResponseWrapper_Payload interface { + isResponseWrapper_Payload() +} + +type ResponseWrapper_TelemetryResp struct { + TelemetryResp *TelemetryResponse `protobuf:"bytes,1,opt,name=telemetry_resp,json=telemetryResp,proto3,oneof"` +} + +type ResponseWrapper_PingResp struct { + PingResp *PingResponse `protobuf:"bytes,2,opt,name=ping_resp,json=pingResp,proto3,oneof"` +} + +func (*ResponseWrapper_TelemetryResp) isResponseWrapper_Payload() {} + +func (*ResponseWrapper_PingResp) isResponseWrapper_Payload() {} + +var File_service_v1_service_proto protoreflect.FileDescriptor + +const file_service_v1_service_proto_rawDesc = "" + + "\n" + + "\x18service/v1/service.proto\x12\n" + + "service.v1\"\x91\x01\n" + + "\rTelemetryData\x12\x1b\n" + + "\tdevice_id\x18\x01 \x01(\tR\bdeviceId\x12\x1c\n" + + "\ttimestamp\x18\x02 \x01(\x03R\ttimestamp\x12 \n" + + "\vtemperature\x18\x03 \x01(\x01R\vtemperature\x12#\n" + + "\rbattery_level\x18\x04 \x01(\x05R\fbatteryLevel\"+\n" + + "\vPingRequest\x12\x1c\n" + + "\ttimestamp\x18\x01 \x01(\x03R\ttimestamp\"\x8e\x01\n" + + "\x0eRequestWrapper\x12B\n" + + "\x0esend_telemetry\x18\x01 \x01(\v2\x19.service.v1.TelemetryDataH\x00R\rsendTelemetry\x12-\n" + + "\x04ping\x18\x02 \x01(\v2\x17.service.v1.PingRequestH\x00R\x04pingB\t\n" + + "\apayload\"\xbc\x01\n" + + "\x11TelemetryResponse\x12I\n" + + "\vresult_code\x18\x01 \x01(\x0e2(.service.v1.TelemetryResponse.ResultCodeR\n" + + "resultCode\"\\\n" + + "\n" + + "ResultCode\x12\x1b\n" + + "\x17RESULT_CODE_UNSPECIFIED\x10\x00\x12\x19\n" + + "\x15RESULT_CODE_SUCCEEDED\x10\x01\x12\x16\n" + + "\x12RESULT_CODE_FAILED\x10\x02\"9\n" + + "\fPingResponse\x12)\n" + + "\x10server_timestamp\x18\x01 \x01(\x03R\x0fserverTimestamp\"\x9d\x01\n" + + "\x0fResponseWrapper\x12F\n" + + "\x0etelemetry_resp\x18\x01 \x01(\v2\x1d.service.v1.TelemetryResponseH\x00R\rtelemetryResp\x127\n" + + "\tping_resp\x18\x02 \x01(\v2\x18.service.v1.PingResponseH\x00R\bpingRespB\t\n" + + "\apayloadB#Z!protolab/gen/service/v1;servicev1b\x06proto3" + +var ( + file_service_v1_service_proto_rawDescOnce sync.Once + file_service_v1_service_proto_rawDescData []byte +) + +func file_service_v1_service_proto_rawDescGZIP() []byte { + file_service_v1_service_proto_rawDescOnce.Do(func() { + file_service_v1_service_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_service_v1_service_proto_rawDesc), len(file_service_v1_service_proto_rawDesc))) + }) + return file_service_v1_service_proto_rawDescData +} + +var file_service_v1_service_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_service_v1_service_proto_msgTypes = make([]protoimpl.MessageInfo, 6) +var file_service_v1_service_proto_goTypes = []any{ + (TelemetryResponse_ResultCode)(0), // 0: service.v1.TelemetryResponse.ResultCode + (*TelemetryData)(nil), // 1: service.v1.TelemetryData + (*PingRequest)(nil), // 2: service.v1.PingRequest + (*RequestWrapper)(nil), // 3: service.v1.RequestWrapper + (*TelemetryResponse)(nil), // 4: service.v1.TelemetryResponse + (*PingResponse)(nil), // 5: service.v1.PingResponse + (*ResponseWrapper)(nil), // 6: service.v1.ResponseWrapper +} +var file_service_v1_service_proto_depIdxs = []int32{ + 1, // 0: service.v1.RequestWrapper.send_telemetry:type_name -> service.v1.TelemetryData + 2, // 1: service.v1.RequestWrapper.ping:type_name -> service.v1.PingRequest + 0, // 2: service.v1.TelemetryResponse.result_code:type_name -> service.v1.TelemetryResponse.ResultCode + 4, // 3: service.v1.ResponseWrapper.telemetry_resp:type_name -> service.v1.TelemetryResponse + 5, // 4: service.v1.ResponseWrapper.ping_resp:type_name -> service.v1.PingResponse + 5, // [5:5] is the sub-list for method output_type + 5, // [5:5] is the sub-list for method input_type + 5, // [5:5] is the sub-list for extension type_name + 5, // [5:5] is the sub-list for extension extendee + 0, // [0:5] is the sub-list for field type_name +} + +func init() { file_service_v1_service_proto_init() } +func file_service_v1_service_proto_init() { + if File_service_v1_service_proto != nil { + return + } + file_service_v1_service_proto_msgTypes[2].OneofWrappers = []any{ + (*RequestWrapper_SendTelemetry)(nil), + (*RequestWrapper_Ping)(nil), + } + file_service_v1_service_proto_msgTypes[5].OneofWrappers = []any{ + (*ResponseWrapper_TelemetryResp)(nil), + (*ResponseWrapper_PingResp)(nil), + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_service_v1_service_proto_rawDesc), len(file_service_v1_service_proto_rawDesc)), + NumEnums: 1, + NumMessages: 6, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_service_v1_service_proto_goTypes, + DependencyIndexes: file_service_v1_service_proto_depIdxs, + EnumInfos: file_service_v1_service_proto_enumTypes, + MessageInfos: file_service_v1_service_proto_msgTypes, + }.Build() + File_service_v1_service_proto = out.File + file_service_v1_service_proto_goTypes = nil + file_service_v1_service_proto_depIdxs = nil +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..f40cca0 --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module gordenko.dev/dima/protolab + +go 1.24.2 + +require google.golang.org/protobuf v1.36.10 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..8fe3089 --- /dev/null +++ b/go.sum @@ -0,0 +1,4 @@ +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= +google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= diff --git a/server/main.go b/server/main.go new file mode 100644 index 0000000..d035880 --- /dev/null +++ b/server/main.go @@ -0,0 +1,90 @@ +package main + +import ( + "fmt" + "log" + "net" + "time" + + pb "gordenko.dev/dima/protolab/gen/service/v1" + + "gordenko.dev/dima/protolab/bin" +) + +const ( + Port = 8080 +) + +func handleConnection(conn net.Conn) { + defer conn.Close() + log.Printf("client %s connected\n", conn.RemoteAddr().String()) + + for { + var ( + reqWrapper pb.RequestWrapper + respWrapper *pb.ResponseWrapper + ) + + err := bin.ReadMessage(conn, &reqWrapper) + if err != nil { + log.Fatalf("read request: %v\n", err) + } + + switch p := reqWrapper.Payload.(type) { + case *pb.RequestWrapper_SendTelemetry: + data := p.SendTelemetry + log.Printf("telemetry received from %s: Temp %.2f°C, BatteryLevel %v%%\n", + data.GetDeviceId(), data.GetTemperature(), data.GetBatteryLevel()) + + respWrapper = &pb.ResponseWrapper{ + Payload: &pb.ResponseWrapper_TelemetryResp{ + TelemetryResp: &pb.TelemetryResponse{ + ResultCode: pb.TelemetryResponse_RESULT_CODE_SUCCEEDED, + }, + }, + } + + case *pb.RequestWrapper_Ping: + data := p.Ping + log.Printf("ping received, client time %s\n", + time.Unix(data.GetTimestamp(), 0).Format(time.RFC3339)) + + respWrapper = &pb.ResponseWrapper{ + Payload: &pb.ResponseWrapper_PingResp{ + PingResp: &pb.PingResponse{ + ServerTimestamp: time.Now().Unix(), + }, + }, + } + + default: + log.Printf("unknown request received: %T\n", p) + } + + err = bin.WriteMessage(conn, respWrapper) + if err != nil { + log.Fatalf("send response: %v\n", err) + } else { + log.Println("response sent") + } + } +} + +func main() { + listener, err := net.Listen("tcp", fmt.Sprintf(":%d", Port)) + if err != nil { + log.Fatalf("net.Listen: %v\n", err) + } + defer listener.Close() + + log.Printf("server started on port %d", Port) + + for { + conn, err := listener.Accept() + if err != nil { + log.Printf("listener.Accept: %v", err) + continue + } + go handleConnection(conn) + } +} diff --git a/service/v1/service.proto b/service/v1/service.proto new file mode 100644 index 0000000..40a8425 --- /dev/null +++ b/service/v1/service.proto @@ -0,0 +1,43 @@ +syntax = "proto3"; + +package service.v1; + +option go_package = "protolab/gen/service/v1;servicev1"; + +message TelemetryData { + string device_id = 1; + int64 timestamp = 2; + double temperature = 3; + int32 battery_level = 4; +} + +message PingRequest { + int64 timestamp = 1; +} + +message RequestWrapper { + oneof payload { + TelemetryData send_telemetry = 1; + PingRequest ping = 2; + } +} + +message TelemetryResponse { + enum ResultCode { + RESULT_CODE_UNSPECIFIED = 0; + RESULT_CODE_SUCCEEDED = 1; + RESULT_CODE_FAILED = 2; + } + ResultCode result_code = 1; +} + +message PingResponse { + int64 server_timestamp = 1; +} + +message ResponseWrapper { + oneof payload { + TelemetryResponse telemetry_resp = 1; + PingResponse ping_resp = 2; + } +}