first commit

main
dima 5 days ago
commit 4ee1259d51
  1. 19
      README
  2. 46
      bin/bin.go
  3. 5
      buf.gen.yaml
  4. 8
      buf.yaml
  5. 82
      client/main.go
  6. 536
      gen/service/v1/service.pb.go
  7. 5
      go.mod
  8. 4
      go.sum
  9. 90
      server/main.go
  10. 43
      service/v1/service.proto

@ -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

@ -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
}

@ -0,0 +1,5 @@
version: v1
plugins:
- name: go
out: gen
opt: paths=source_relative

@ -0,0 +1,8 @@
version: v1
name: buf.build/dima/tcp-service
lint:
use:
- DEFAULT
breaking:
use:
- FILE

@ -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)
}
}

@ -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
}

@ -0,0 +1,5 @@
module gordenko.dev/dima/protolab
go 1.24.2
require google.golang.org/protobuf v1.36.10

@ -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=

@ -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)
}
}

@ -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;
}
}
Loading…
Cancel
Save