diff --git a/mocks/GNMIClient.go b/mocks/GNMIClient.go new file mode 100644 index 0000000000000000000000000000000000000000..255d4a40d68513d1b1125101f8e9cdc12f139dc2 --- /dev/null +++ b/mocks/GNMIClient.go @@ -0,0 +1,137 @@ +// Code generated by mockery v2.6.0. DO NOT EDIT. + +package mocks + +import ( + context "context" + + gnmi "github.com/openconfig/gnmi/proto/gnmi" + grpc "google.golang.org/grpc" + + mock "github.com/stretchr/testify/mock" +) + +// GNMIClient is an autogenerated mock type for the GNMIClient type +type GNMIClient struct { + mock.Mock +} + +// Capabilities provides a mock function with given fields: ctx, in, opts +func (_m *GNMIClient) Capabilities(ctx context.Context, in *gnmi.CapabilityRequest, opts ...grpc.CallOption) (*gnmi.CapabilityResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *gnmi.CapabilityResponse + if rf, ok := ret.Get(0).(func(context.Context, *gnmi.CapabilityRequest, ...grpc.CallOption) *gnmi.CapabilityResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*gnmi.CapabilityResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *gnmi.CapabilityRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Get provides a mock function with given fields: ctx, in, opts +func (_m *GNMIClient) Get(ctx context.Context, in *gnmi.GetRequest, opts ...grpc.CallOption) (*gnmi.GetResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *gnmi.GetResponse + if rf, ok := ret.Get(0).(func(context.Context, *gnmi.GetRequest, ...grpc.CallOption) *gnmi.GetResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*gnmi.GetResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *gnmi.GetRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Set provides a mock function with given fields: ctx, in, opts +func (_m *GNMIClient) Set(ctx context.Context, in *gnmi.SetRequest, opts ...grpc.CallOption) (*gnmi.SetResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *gnmi.SetResponse + if rf, ok := ret.Get(0).(func(context.Context, *gnmi.SetRequest, ...grpc.CallOption) *gnmi.SetResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*gnmi.SetResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *gnmi.SetRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Subscribe provides a mock function with given fields: ctx, opts +func (_m *GNMIClient) Subscribe(ctx context.Context, opts ...grpc.CallOption) (gnmi.GNMI_SubscribeClient, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 gnmi.GNMI_SubscribeClient + if rf, ok := ret.Get(0).(func(context.Context, ...grpc.CallOption) gnmi.GNMI_SubscribeClient); ok { + r0 = rf(ctx, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(gnmi.GNMI_SubscribeClient) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, ...grpc.CallOption) error); ok { + r1 = rf(ctx, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} diff --git a/mocks/GNMIServer.go b/mocks/GNMIServer.go new file mode 100644 index 0000000000000000000000000000000000000000..0628c5b31fe2c971810f5a2c9f7be3a8326ee40c --- /dev/null +++ b/mocks/GNMIServer.go @@ -0,0 +1,98 @@ +// Code generated by mockery v2.6.0. DO NOT EDIT. + +package mocks + +import ( + context "context" + + gnmi "github.com/openconfig/gnmi/proto/gnmi" + mock "github.com/stretchr/testify/mock" +) + +// GNMIServer is an autogenerated mock type for the GNMIServer type +type GNMIServer struct { + mock.Mock +} + +// Capabilities provides a mock function with given fields: _a0, _a1 +func (_m *GNMIServer) Capabilities(_a0 context.Context, _a1 *gnmi.CapabilityRequest) (*gnmi.CapabilityResponse, error) { + ret := _m.Called(_a0, _a1) + + var r0 *gnmi.CapabilityResponse + if rf, ok := ret.Get(0).(func(context.Context, *gnmi.CapabilityRequest) *gnmi.CapabilityResponse); ok { + r0 = rf(_a0, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*gnmi.CapabilityResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *gnmi.CapabilityRequest) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Get provides a mock function with given fields: _a0, _a1 +func (_m *GNMIServer) Get(_a0 context.Context, _a1 *gnmi.GetRequest) (*gnmi.GetResponse, error) { + ret := _m.Called(_a0, _a1) + + var r0 *gnmi.GetResponse + if rf, ok := ret.Get(0).(func(context.Context, *gnmi.GetRequest) *gnmi.GetResponse); ok { + r0 = rf(_a0, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*gnmi.GetResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *gnmi.GetRequest) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Set provides a mock function with given fields: _a0, _a1 +func (_m *GNMIServer) Set(_a0 context.Context, _a1 *gnmi.SetRequest) (*gnmi.SetResponse, error) { + ret := _m.Called(_a0, _a1) + + var r0 *gnmi.SetResponse + if rf, ok := ret.Get(0).(func(context.Context, *gnmi.SetRequest) *gnmi.SetResponse); ok { + r0 = rf(_a0, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*gnmi.SetResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *gnmi.SetRequest) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Subscribe provides a mock function with given fields: _a0 +func (_m *GNMIServer) Subscribe(_a0 gnmi.GNMI_SubscribeServer) error { + ret := _m.Called(_a0) + + var r0 error + if rf, ok := ret.Get(0).(func(gnmi.GNMI_SubscribeServer) error); ok { + r0 = rf(_a0) + } else { + r0 = ret.Error(0) + } + + return r0 +} diff --git a/mocks/GNMI_SubscribeClient.go b/mocks/GNMI_SubscribeClient.go new file mode 100644 index 0000000000000000000000000000000000000000..456be898e5d92171a26add0d03e863f08f302d24 --- /dev/null +++ b/mocks/GNMI_SubscribeClient.go @@ -0,0 +1,151 @@ +// Code generated by mockery v2.6.0. DO NOT EDIT. + +package mocks + +import ( + context "context" + + gnmi "github.com/openconfig/gnmi/proto/gnmi" + metadata "google.golang.org/grpc/metadata" + + mock "github.com/stretchr/testify/mock" +) + +// GNMI_SubscribeClient is an autogenerated mock type for the GNMI_SubscribeClient type +type GNMI_SubscribeClient struct { + mock.Mock +} + +// CloseSend provides a mock function with given fields: +func (_m *GNMI_SubscribeClient) CloseSend() error { + ret := _m.Called() + + var r0 error + if rf, ok := ret.Get(0).(func() error); ok { + r0 = rf() + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Context provides a mock function with given fields: +func (_m *GNMI_SubscribeClient) Context() context.Context { + ret := _m.Called() + + var r0 context.Context + if rf, ok := ret.Get(0).(func() context.Context); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(context.Context) + } + } + + return r0 +} + +// Header provides a mock function with given fields: +func (_m *GNMI_SubscribeClient) Header() (metadata.MD, error) { + ret := _m.Called() + + var r0 metadata.MD + if rf, ok := ret.Get(0).(func() metadata.MD); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(metadata.MD) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Recv provides a mock function with given fields: +func (_m *GNMI_SubscribeClient) Recv() (*gnmi.SubscribeResponse, error) { + ret := _m.Called() + + var r0 *gnmi.SubscribeResponse + if rf, ok := ret.Get(0).(func() *gnmi.SubscribeResponse); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*gnmi.SubscribeResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RecvMsg provides a mock function with given fields: m +func (_m *GNMI_SubscribeClient) RecvMsg(m interface{}) error { + ret := _m.Called(m) + + var r0 error + if rf, ok := ret.Get(0).(func(interface{}) error); ok { + r0 = rf(m) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Send provides a mock function with given fields: _a0 +func (_m *GNMI_SubscribeClient) Send(_a0 *gnmi.SubscribeRequest) error { + ret := _m.Called(_a0) + + var r0 error + if rf, ok := ret.Get(0).(func(*gnmi.SubscribeRequest) error); ok { + r0 = rf(_a0) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// SendMsg provides a mock function with given fields: m +func (_m *GNMI_SubscribeClient) SendMsg(m interface{}) error { + ret := _m.Called(m) + + var r0 error + if rf, ok := ret.Get(0).(func(interface{}) error); ok { + r0 = rf(m) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Trailer provides a mock function with given fields: +func (_m *GNMI_SubscribeClient) Trailer() metadata.MD { + ret := _m.Called() + + var r0 metadata.MD + if rf, ok := ret.Get(0).(func() metadata.MD); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(metadata.MD) + } + } + + return r0 +} diff --git a/mocks/GNMI_SubscribeServer.go b/mocks/GNMI_SubscribeServer.go new file mode 100644 index 0000000000000000000000000000000000000000..e19a9379ffc57345b30141b757dbd5d57327a82a --- /dev/null +++ b/mocks/GNMI_SubscribeServer.go @@ -0,0 +1,131 @@ +// Code generated by mockery v2.6.0. DO NOT EDIT. + +package mocks + +import ( + context "context" + + gnmi "github.com/openconfig/gnmi/proto/gnmi" + metadata "google.golang.org/grpc/metadata" + + mock "github.com/stretchr/testify/mock" +) + +// GNMI_SubscribeServer is an autogenerated mock type for the GNMI_SubscribeServer type +type GNMI_SubscribeServer struct { + mock.Mock +} + +// Context provides a mock function with given fields: +func (_m *GNMI_SubscribeServer) Context() context.Context { + ret := _m.Called() + + var r0 context.Context + if rf, ok := ret.Get(0).(func() context.Context); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(context.Context) + } + } + + return r0 +} + +// Recv provides a mock function with given fields: +func (_m *GNMI_SubscribeServer) Recv() (*gnmi.SubscribeRequest, error) { + ret := _m.Called() + + var r0 *gnmi.SubscribeRequest + if rf, ok := ret.Get(0).(func() *gnmi.SubscribeRequest); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*gnmi.SubscribeRequest) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RecvMsg provides a mock function with given fields: m +func (_m *GNMI_SubscribeServer) RecvMsg(m interface{}) error { + ret := _m.Called(m) + + var r0 error + if rf, ok := ret.Get(0).(func(interface{}) error); ok { + r0 = rf(m) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Send provides a mock function with given fields: _a0 +func (_m *GNMI_SubscribeServer) Send(_a0 *gnmi.SubscribeResponse) error { + ret := _m.Called(_a0) + + var r0 error + if rf, ok := ret.Get(0).(func(*gnmi.SubscribeResponse) error); ok { + r0 = rf(_a0) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// SendHeader provides a mock function with given fields: _a0 +func (_m *GNMI_SubscribeServer) SendHeader(_a0 metadata.MD) error { + ret := _m.Called(_a0) + + var r0 error + if rf, ok := ret.Get(0).(func(metadata.MD) error); ok { + r0 = rf(_a0) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// SendMsg provides a mock function with given fields: m +func (_m *GNMI_SubscribeServer) SendMsg(m interface{}) error { + ret := _m.Called(m) + + var r0 error + if rf, ok := ret.Get(0).(func(interface{}) error); ok { + r0 = rf(m) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// SetHeader provides a mock function with given fields: _a0 +func (_m *GNMI_SubscribeServer) SetHeader(_a0 metadata.MD) error { + ret := _m.Called(_a0) + + var r0 error + if rf, ok := ret.Get(0).(func(metadata.MD) error); ok { + r0 = rf(_a0) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// SetTrailer provides a mock function with given fields: _a0 +func (_m *GNMI_SubscribeServer) SetTrailer(_a0 metadata.MD) { + _m.Called(_a0) +} diff --git a/mocks/isSubscribeRequest_Request.go b/mocks/isSubscribeRequest_Request.go new file mode 100644 index 0000000000000000000000000000000000000000..73f763981ed7bd4586787e88694c844885b2b843 --- /dev/null +++ b/mocks/isSubscribeRequest_Request.go @@ -0,0 +1,15 @@ +// Code generated by mockery v2.6.0. DO NOT EDIT. + +package mocks + +import mock "github.com/stretchr/testify/mock" + +// isSubscribeRequest_Request is an autogenerated mock type for the isSubscribeRequest_Request type +type isSubscribeRequest_Request struct { + mock.Mock +} + +// isSubscribeRequest_Request provides a mock function with given fields: +func (_m *isSubscribeRequest_Request) isSubscribeRequest_Request() { + _m.Called() +} diff --git a/mocks/isSubscribeResponse_Response.go b/mocks/isSubscribeResponse_Response.go new file mode 100644 index 0000000000000000000000000000000000000000..ba10966e2703113f5fc03233883022efb7843b4b --- /dev/null +++ b/mocks/isSubscribeResponse_Response.go @@ -0,0 +1,15 @@ +// Code generated by mockery v2.6.0. DO NOT EDIT. + +package mocks + +import mock "github.com/stretchr/testify/mock" + +// isSubscribeResponse_Response is an autogenerated mock type for the isSubscribeResponse_Response type +type isSubscribeResponse_Response struct { + mock.Mock +} + +// isSubscribeResponse_Response provides a mock function with given fields: +func (_m *isSubscribeResponse_Response) isSubscribeResponse_Response() { + _m.Called() +} diff --git a/mocks/isTypedValue_Value.go b/mocks/isTypedValue_Value.go new file mode 100644 index 0000000000000000000000000000000000000000..a49fc787f9dbe7f24023258d180371a5bcd42c7b --- /dev/null +++ b/mocks/isTypedValue_Value.go @@ -0,0 +1,15 @@ +// Code generated by mockery v2.6.0. DO NOT EDIT. + +package mocks + +import mock "github.com/stretchr/testify/mock" + +// isTypedValue_Value is an autogenerated mock type for the isTypedValue_Value type +type isTypedValue_Value struct { + mock.Mock +} + +// isTypedValue_Value provides a mock function with given fields: +func (_m *isTypedValue_Value) isTypedValue_Value() { + _m.Called() +} diff --git a/nucleus/errors.go b/nucleus/errors.go new file mode 100644 index 0000000000000000000000000000000000000000..32fdb47733ddc1e44cf8c458c8bf8c67133e94f3 --- /dev/null +++ b/nucleus/errors.go @@ -0,0 +1,11 @@ +package nucleus + +import "fmt" + +type ErrNilClient struct { + +} + +func (e *ErrNilClient)Error() string { + return fmt.Sprintf("client cannot be nil") +} \ No newline at end of file diff --git a/nucleus/gnmi_transport.go b/nucleus/gnmi_transport.go index 964c22d89ccced928bc7fef7c0e923e4fb255f4a..edb7662a032033a05fa1c8f7237689fb461f2e0a 100644 --- a/nucleus/gnmi_transport.go +++ b/nucleus/gnmi_transport.go @@ -2,6 +2,7 @@ package nucleus import ( "code.fbi.h-da.de/cocsn/gosdn/forks/goarista/gnmi" + "code.fbi.h-da.de/cocsn/gosdn/nucleus/util" "context" "errors" gpb "github.com/openconfig/gnmi/proto/gnmi" @@ -10,12 +11,33 @@ import ( "github.com/openconfig/ygot/ytypes" log "github.com/sirupsen/logrus" "strings" + "time" ) +func init() { + // tapProto taps get responses and requests to binary file + // CAUTION only set true if you know what you do + tapProto = true +} + +func NewGnmiTransport(config *gnmi.Config) (*Gnmi, error) { + c, err := gnmi.Dial(config) + if err != nil { + return nil, err + } + return &Gnmi{ + SetNode: nil, + RespChan: nil, + config: config, + client: c, + }, nil +} + type Gnmi struct { SetNode func(schema *yang.Entry, root interface{}, path *gpb.Path, val interface{}, opts ...ytypes.SetNodeOpt) error RespChan chan *gpb.SubscribeResponse config *gnmi.Config + client gpb.GNMIClient } func (g *Gnmi) SetConfig(config *gnmi.Config) { @@ -29,11 +51,23 @@ func (g *Gnmi) GetConfig() *gnmi.Config { // interface satisfaction for now // TODO: Convert to meaningfiul calls func (g *Gnmi) Get(ctx context.Context, params ...string) (interface{}, error) { + if g.client == nil { + return nil, &ErrNilClient{} + } paths := gnmi.SplitPaths(params) return g.get(ctx, paths, "") } -func (g *Gnmi) Set(ctx context.Context, params ...string) (interface{}, error) { return nil, nil } +func (g *Gnmi) Set(ctx context.Context, params ...string) (interface{}, error) { + if g.client == nil { + return nil, &ErrNilClient{} + } + return nil, nil +} + func (g *Gnmi) Subscribe(ctx context.Context, params ...string) error { + if g.client == nil { + return &ErrNilClient{} + } return g.subscribe(ctx) } @@ -65,7 +99,7 @@ func (g *Gnmi) ProcessResponse(resp interface{}, root interface{}, s *ytypes.Sch schema := models["Device"] if err := g.SetNode(schema, root, update.Path, update.Val, opts...); err != nil { - log.Error(err) + return err } } } @@ -102,11 +136,7 @@ func gnmiFullPath(prefix, path *gpb.Path) (*gpb.Path, error) { // Capabilities calls GNMI capabilities func (g *Gnmi) Capabilities(ctx context.Context) (interface{}, error) { - client, err := gnmi.Dial(g.config) - if err != nil { - return nil, err - } - resp, err := client.Capabilities(ctx, &gpb.CapabilityRequest{}) + resp, err := g.client.Capabilities(ctx, &gpb.CapabilityRequest{}) if err != nil { return nil, err } @@ -127,11 +157,15 @@ func (g *Gnmi) get(ctx context.Context, paths [][]string, origin string) (interf // getWithRequest takes a fully formed GetRequest, performs the Get, // and returns any response. func (g *Gnmi) getWithRequest(ctx context.Context, req *gpb.GetRequest) (interface{}, error) { - client, err := gnmi.Dial(g.config) - if err != nil { - return nil, err + resp, err := g.client.Get(ctx, req) + if tapProto { + if err := util.Write(req, "req-" + time.Now().String()); err != nil { + log.Errorf("error while writing request: %v", err) + } + if err := util.Write(resp, "resp-" + time.Now().String()); err != nil { + log.Errorf("error while writing request: %v", err) + } } - resp, err := client.Get(ctx, req) if err != nil { return nil, err } @@ -142,20 +176,12 @@ func (g *Gnmi) getWithRequest(ctx context.Context, req *gpb.GetRequest) (interfa func (g *Gnmi) set(ctx context.Context, setOps []*gnmi.Operation, exts ...*gnmi_ext.Extension) error { ctx = gnmi.NewContext(ctx, g.config) - client, err := gnmi.Dial(g.config) - if err != nil { - return err - } - return gnmi.Set(ctx, client, setOps, exts...) + return gnmi.Set(ctx, g.client, setOps, exts...) } // Subscribe calls GNMI subscribe func (g *Gnmi) subscribe(ctx context.Context) error { ctx = gnmi.NewContext(ctx, g.config) - client, err := gnmi.Dial(g.config) - if err != nil { - return err - } opts := ctx.Value("opts").(*gnmi.SubscribeOptions) go func() { for { @@ -165,7 +191,7 @@ func (g *Gnmi) subscribe(ctx context.Context) error { } } }() - return gnmi.SubscribeErr(ctx, client, opts, g.RespChan) + return gnmi.SubscribeErr(ctx, g.client, opts, g.RespChan) } // Close calls GNMI close diff --git a/nucleus/util/message_tools.go b/nucleus/util/message_tools.go new file mode 100644 index 0000000000000000000000000000000000000000..c8633e5a323fab0518aa1dd7f7dbb37bfd357599 --- /dev/null +++ b/nucleus/util/message_tools.go @@ -0,0 +1,41 @@ +package util + +import ( + "fmt" + "github.com/golang/protobuf/proto" + "io/ioutil" +) + +/* +Copycat source: https://dev.to/techschoolguru/go-generate-serialize-protobuf-message-4m7a + */ + +// Write writes protocol buffer message to binary file +func Write(message proto.Message, filename string) error { + data, err := proto.Marshal(message) + if err != nil { + return fmt.Errorf("cannot marshal proto message to binary: %w", err) + } + + err = ioutil.WriteFile(filename, data, 0644) + if err != nil { + return fmt.Errorf("cannot write binary data to file: %w", err) + } + + return nil +} + + +func Read(filename string, message proto.Message) error { + data, err := ioutil.ReadFile(filename) + if err != nil { + return fmt.Errorf("cannot read binary data from file: %w", err) + } + + err = proto.Unmarshal(data, message) + if err != nil { + return fmt.Errorf("cannot unmarshal binary to proto message: %w", err) + } + + return nil +} \ No newline at end of file