From 1031f3122ee1c02e30526445f485d2970e3b1121 Mon Sep 17 00:00:00 2001
From: Leonard Lyubich <leonard@nspcc.ru>
Date: Fri, 12 Mar 2021 15:57:23 +0300
Subject: [PATCH] [#263] v2: Support new rpc library

Implement `message.Message` interface on all structures and use new methods
for conversion instead of functions. make `Unmarshal` and JSON methods to
use encoding functions from `message` library. Remove all per-service
clients and implement `rpc` library of the functions which execute NeoFS API
RPC through new RPC client. Remove no longer used gRPC per-service clients.

Signed-off-by: Leonard Lyubich <leonard@nspcc.ru>
---
 v2/accounting/accounting.go       |   68 +-
 v2/accounting/client.go           |  156 --
 v2/accounting/convert.go          |  207 +-
 v2/accounting/grpc/client.go      |   62 -
 v2/accounting/json.go             |   18 +-
 v2/accounting/json_test.go        |   20 -
 v2/accounting/marshal.go          |   19 +-
 v2/accounting/marshal_test.go     |   81 -
 v2/accounting/message_test.go     |   19 +
 v2/accounting/service.go          |   27 -
 v2/accounting/test/client_test.go |  163 --
 v2/accounting/test/generate.go    |   54 +
 v2/acl/convert.go                 |  504 +++--
 v2/acl/json.go                    |  114 +-
 v2/acl/json_test.go               |   81 -
 v2/acl/marshal.go                 |   65 +-
 v2/acl/marshal_test.go            |  237 --
 v2/acl/message_test.go            |   21 +
 v2/acl/test/generate.go           |  124 ++
 v2/audit/convert.go               |  142 +-
 v2/audit/json.go                  |   18 +-
 v2/audit/json_test.go             |   20 -
 v2/audit/marshal.go               |   11 +-
 v2/audit/marshal_test.go          |   63 -
 v2/audit/message_test.go          |   15 +
 v2/audit/test/generate.go         |   30 +
 v2/client/options.go              |  115 -
 v2/client/proto.go                |   23 -
 v2/container/client.go            |  355 ---
 v2/container/convert.go           | 1494 +++++++------
 v2/container/grpc/client.go       |   86 -
 v2/container/json.go              |   34 +-
 v2/container/json_test.go         |   32 -
 v2/container/marshal.go           |   85 +-
 v2/container/marshal_test.go      |  492 -----
 v2/container/message_test.go      |   43 +
 v2/container/service.go           |  113 -
 v2/container/test/client_test.go  |  958 --------
 v2/container/test/generate.go     |  318 +++
 v2/container/types.go             |  471 +---
 v2/netmap/client.go               |  190 --
 v2/netmap/convert.go              |  768 ++++---
 v2/netmap/grpc/client.go          |   66 -
 v2/netmap/json.go                 |  114 +-
 v2/netmap/json_test.go            |   80 -
 v2/netmap/marshal.go              |   81 +-
 v2/netmap/marshal_test.go         |  248 ---
 v2/netmap/message_test.go         |   25 +
 v2/netmap/service.go              |   46 -
 v2/netmap/test/generate.go        |  219 ++
 v2/netmap/types.go                |  130 +-
 v2/object/client.go               |  408 ----
 v2/object/client_stream.go        |   60 -
 v2/object/convert.go              | 3390 ++++++++++++++++-------------
 v2/object/json.go                 |  126 +-
 v2/object/json_test.go            |   92 -
 v2/object/marshal.go              |  400 ++--
 v2/object/marshal_test.go         |  776 -------
 v2/object/message_test.go         |   54 +
 v2/object/service.go              |  129 --
 v2/object/test/client_test.go     |  471 ----
 v2/object/test/generate.go        |  512 +++++
 v2/object/types.go                |  476 +---
 v2/refs/convert.go                |  345 +--
 v2/refs/json.go                   |  114 +-
 v2/refs/json_test.go              |   97 -
 v2/refs/marshal.go                |   65 +-
 v2/refs/marshal_test.go           |  146 --
 v2/refs/message_test.go           |   21 +
 v2/refs/test/generate.go          |  101 +
 v2/rpc/accounting.go              |   29 +
 v2/rpc/common.go                  |    3 +
 v2/rpc/container.go               |  131 ++
 v2/rpc/netmap.go                  |   46 +
 v2/rpc/object.go                  |  190 ++
 v2/rpc/session.go                 |   28 +
 v2/session/client.go              |  156 --
 v2/session/convert.go             | 1021 +++++----
 v2/session/json.go                |  103 +-
 v2/session/json_test.go           |  116 -
 v2/session/marshal.go             |   45 +-
 v2/session/marshal_test.go        |  385 ----
 v2/session/message_test.go        |   26 +
 v2/session/service.go             |   25 -
 v2/session/test/client_test.go    |  159 --
 v2/session/test/generate.go       |  204 ++
 v2/session/types.go               |   12 +
 v2/session/util.go                |   90 +
 v2/storagegroup/convert.go        |   65 +-
 v2/storagegroup/json.go           |   18 +-
 v2/storagegroup/json_test.go      |   20 -
 v2/storagegroup/marshal.go        |   11 +-
 v2/storagegroup/marshal_test.go   |   48 -
 v2/storagegroup/message_test.go   |   15 +
 v2/storagegroup/test/generate.go  |   20 +
 v2/tombstone/convert.go           |   56 +-
 v2/tombstone/json.go              |   18 +-
 v2/tombstone/json_test.go         |   20 -
 v2/tombstone/marshal.go           |   11 +-
 v2/tombstone/marshal_test.go      |   39 -
 v2/tombstone/message_test.go      |   15 +
 v2/tombstone/test/generate.go     |   19 +
 102 files changed, 7554 insertions(+), 12298 deletions(-)
 delete mode 100644 v2/accounting/client.go
 delete mode 100644 v2/accounting/grpc/client.go
 delete mode 100644 v2/accounting/json_test.go
 delete mode 100644 v2/accounting/marshal_test.go
 create mode 100644 v2/accounting/message_test.go
 delete mode 100644 v2/accounting/service.go
 delete mode 100644 v2/accounting/test/client_test.go
 create mode 100644 v2/accounting/test/generate.go
 delete mode 100644 v2/acl/json_test.go
 delete mode 100644 v2/acl/marshal_test.go
 create mode 100644 v2/acl/message_test.go
 create mode 100644 v2/acl/test/generate.go
 delete mode 100644 v2/audit/json_test.go
 delete mode 100644 v2/audit/marshal_test.go
 create mode 100644 v2/audit/message_test.go
 create mode 100644 v2/audit/test/generate.go
 delete mode 100644 v2/client/options.go
 delete mode 100644 v2/client/proto.go
 delete mode 100644 v2/container/client.go
 delete mode 100644 v2/container/grpc/client.go
 delete mode 100644 v2/container/json_test.go
 delete mode 100644 v2/container/marshal_test.go
 create mode 100644 v2/container/message_test.go
 delete mode 100644 v2/container/service.go
 delete mode 100644 v2/container/test/client_test.go
 create mode 100644 v2/container/test/generate.go
 delete mode 100644 v2/netmap/client.go
 delete mode 100644 v2/netmap/grpc/client.go
 delete mode 100644 v2/netmap/json_test.go
 delete mode 100644 v2/netmap/marshal_test.go
 create mode 100644 v2/netmap/message_test.go
 delete mode 100644 v2/netmap/service.go
 create mode 100644 v2/netmap/test/generate.go
 delete mode 100644 v2/object/client.go
 delete mode 100644 v2/object/client_stream.go
 delete mode 100644 v2/object/json_test.go
 delete mode 100644 v2/object/marshal_test.go
 create mode 100644 v2/object/message_test.go
 delete mode 100644 v2/object/service.go
 delete mode 100644 v2/object/test/client_test.go
 create mode 100644 v2/object/test/generate.go
 delete mode 100644 v2/refs/json_test.go
 delete mode 100644 v2/refs/marshal_test.go
 create mode 100644 v2/refs/message_test.go
 create mode 100644 v2/refs/test/generate.go
 create mode 100644 v2/rpc/accounting.go
 create mode 100644 v2/rpc/common.go
 create mode 100644 v2/rpc/container.go
 create mode 100644 v2/rpc/netmap.go
 create mode 100644 v2/rpc/object.go
 create mode 100644 v2/rpc/session.go
 delete mode 100644 v2/session/client.go
 delete mode 100644 v2/session/json_test.go
 delete mode 100644 v2/session/marshal_test.go
 create mode 100644 v2/session/message_test.go
 delete mode 100644 v2/session/service.go
 delete mode 100644 v2/session/test/client_test.go
 create mode 100644 v2/session/test/generate.go
 delete mode 100644 v2/storagegroup/json_test.go
 delete mode 100644 v2/storagegroup/marshal_test.go
 create mode 100644 v2/storagegroup/message_test.go
 create mode 100644 v2/storagegroup/test/generate.go
 delete mode 100644 v2/tombstone/json_test.go
 delete mode 100644 v2/tombstone/marshal_test.go
 create mode 100644 v2/tombstone/message_test.go
 create mode 100644 v2/tombstone/test/generate.go

diff --git a/v2/accounting/accounting.go b/v2/accounting/accounting.go
index 619f220..d2d287f 100644
--- a/v2/accounting/accounting.go
+++ b/v2/accounting/accounting.go
@@ -19,6 +19,18 @@ type Decimal struct {
 	prec uint32
 }
 
+type BalanceRequest struct {
+	body *BalanceRequestBody
+
+	session.RequestHeaders
+}
+
+type BalanceResponse struct {
+	body *BalanceResponseBody
+
+	session.ResponseHeaders
+}
+
 func (b *BalanceRequestBody) GetOwnerID() *refs.OwnerID {
 	if b != nil {
 		return b.ownerID
@@ -47,34 +59,6 @@ func (b *BalanceRequest) SetBody(v *BalanceRequestBody) {
 	}
 }
 
-func (b *BalanceRequest) GetMetaHeader() *session.RequestMetaHeader {
-	if b != nil {
-		return b.metaHeader
-	}
-
-	return nil
-}
-
-func (b *BalanceRequest) SetMetaHeader(v *session.RequestMetaHeader) {
-	if b != nil {
-		b.metaHeader = v
-	}
-}
-
-func (b *BalanceRequest) GetVerificationHeader() *session.RequestVerificationHeader {
-	if b != nil {
-		return b.verifyHeader
-	}
-
-	return nil
-}
-
-func (b *BalanceRequest) SetVerificationHeader(v *session.RequestVerificationHeader) {
-	if b != nil {
-		b.verifyHeader = v
-	}
-}
-
 func (d *Decimal) GetValue() int64 {
 	if d != nil {
 		return d.val
@@ -130,31 +114,3 @@ func (br *BalanceResponse) SetBody(v *BalanceResponseBody) {
 		br.body = v
 	}
 }
-
-func (br *BalanceResponse) GetMetaHeader() *session.ResponseMetaHeader {
-	if br != nil {
-		return br.metaHeader
-	}
-
-	return nil
-}
-
-func (br *BalanceResponse) SetMetaHeader(v *session.ResponseMetaHeader) {
-	if br != nil {
-		br.metaHeader = v
-	}
-}
-
-func (br *BalanceResponse) GetVerificationHeader() *session.ResponseVerificationHeader {
-	if br != nil {
-		return br.verifyHeader
-	}
-
-	return nil
-}
-
-func (br *BalanceResponse) SetVerificationHeader(v *session.ResponseVerificationHeader) {
-	if br != nil {
-		br.verifyHeader = v
-	}
-}
diff --git a/v2/accounting/client.go b/v2/accounting/client.go
deleted file mode 100644
index 3d9e51d..0000000
--- a/v2/accounting/client.go
+++ /dev/null
@@ -1,156 +0,0 @@
-package accounting
-
-import (
-	"context"
-
-	accounting "github.com/nspcc-dev/neofs-api-go/v2/accounting/grpc"
-	"github.com/nspcc-dev/neofs-api-go/v2/client"
-	"github.com/pkg/errors"
-	"google.golang.org/grpc"
-)
-
-// Client represents universal accounting
-// transport client.
-type Client struct {
-	client *getBalanceClient
-}
-
-// Option represents Client option.
-type Option func(*cfg)
-
-type cfg struct {
-	proto client.Protocol
-
-	globalOpts []client.Option
-
-	gRPC cfgGRPC
-}
-
-type cfgGRPC struct {
-	serviceClient accounting.AccountingServiceClient
-
-	grpcCallOpts []grpc.CallOption
-
-	callOpts []accounting.Option
-
-	client *accounting.Client
-}
-
-type getBalanceClient struct {
-	requestConverter func(*BalanceRequest) interface{}
-
-	caller func(context.Context, interface{}) (interface{}, error)
-
-	responseConverter func(interface{}) *BalanceResponse
-}
-
-// Balance sends BalanceRequest over the network and returns BalanceResponse.
-//
-// It returns any error encountered during the call.
-func (c *Client) Balance(ctx context.Context, req *BalanceRequest) (*BalanceResponse, error) {
-	resp, err := c.client.caller(ctx, c.client.requestConverter(req))
-	if err != nil {
-		return nil, errors.Wrap(err, "could not send balance request")
-	}
-
-	return c.client.responseConverter(resp), nil
-}
-
-func defaultCfg() *cfg {
-	return &cfg{
-		proto: client.ProtoGRPC,
-	}
-}
-
-func NewClient(opts ...Option) (*Client, error) {
-	cfg := defaultCfg()
-
-	for i := range opts {
-		opts[i](cfg)
-	}
-
-	var err error
-
-	switch cfg.proto {
-	case client.ProtoGRPC:
-		var c *accounting.Client
-		if c, err = newGRPCClient(cfg); err != nil {
-			break
-		}
-
-		return &Client{
-			client: &getBalanceClient{
-				requestConverter: func(req *BalanceRequest) interface{} {
-					return BalanceRequestToGRPCMessage(req)
-				},
-				caller: func(ctx context.Context, req interface{}) (interface{}, error) {
-					return c.Balance(ctx, req.(*accounting.BalanceRequest))
-				},
-				responseConverter: func(resp interface{}) *BalanceResponse {
-					return BalanceResponseFromGRPCMessage(resp.(*accounting.BalanceResponse))
-				},
-			},
-		}, nil
-	default:
-		err = client.ErrProtoUnsupported
-	}
-
-	return nil, errors.Wrapf(err, "could not create %s Accounting client", cfg.proto)
-}
-
-func newGRPCClient(cfg *cfg) (*accounting.Client, error) {
-	var err error
-
-	if cfg.gRPC.client == nil {
-		if cfg.gRPC.serviceClient == nil {
-			conn, err := client.NewGRPCClientConn(cfg.globalOpts...)
-			if err != nil {
-				return nil, errors.Wrap(err, "could not open gRPC client connection")
-			}
-
-			cfg.gRPC.serviceClient = accounting.NewAccountingServiceClient(conn)
-		}
-
-		cfg.gRPC.client, err = accounting.NewClient(
-			cfg.gRPC.serviceClient,
-			append(
-				cfg.gRPC.callOpts,
-				accounting.WithCallOptions(cfg.gRPC.grpcCallOpts),
-			)...,
-		)
-	}
-
-	return cfg.gRPC.client, err
-}
-
-func WithGlobalOpts(v ...client.Option) Option {
-	return func(c *cfg) {
-		if len(v) > 0 {
-			c.globalOpts = v
-		}
-	}
-}
-
-func WithGRPCServiceClient(v accounting.AccountingServiceClient) Option {
-	return func(c *cfg) {
-		c.gRPC.serviceClient = v
-	}
-}
-
-func WithGRPCCallOpts(v []grpc.CallOption) Option {
-	return func(c *cfg) {
-		c.gRPC.grpcCallOpts = v
-	}
-}
-
-func WithGRPCClientOpts(v []accounting.Option) Option {
-	return func(c *cfg) {
-		c.gRPC.callOpts = v
-	}
-}
-
-func WithGRPCClient(v *accounting.Client) Option {
-	return func(c *cfg) {
-		c.gRPC.client = v
-	}
-}
diff --git a/v2/accounting/convert.go b/v2/accounting/convert.go
index d7fe95c..2a842f8 100644
--- a/v2/accounting/convert.go
+++ b/v2/accounting/convert.go
@@ -1,153 +1,178 @@
 package accounting
 
 import (
+	"github.com/nspcc-dev/neofs-api-go/rpc/grpc"
+	"github.com/nspcc-dev/neofs-api-go/rpc/message"
 	accounting "github.com/nspcc-dev/neofs-api-go/v2/accounting/grpc"
 	"github.com/nspcc-dev/neofs-api-go/v2/refs"
-	"github.com/nspcc-dev/neofs-api-go/v2/session"
+	refsGRPC "github.com/nspcc-dev/neofs-api-go/v2/refs/grpc"
 )
 
-func BalanceRequestBodyToGRPCMessage(b *BalanceRequestBody) *accounting.BalanceRequest_Body {
-	if b == nil {
-		return nil
+func (b *BalanceRequestBody) ToGRPCMessage() grpc.Message {
+	var m *accounting.BalanceRequest_Body
+
+	if b != nil {
+		m = new(accounting.BalanceRequest_Body)
+
+		m.SetOwnerId(b.ownerID.ToGRPCMessage().(*refsGRPC.OwnerID))
 	}
 
-	m := new(accounting.BalanceRequest_Body)
-
-	m.SetOwnerId(
-		refs.OwnerIDToGRPCMessage(b.GetOwnerID()),
-	)
-
 	return m
 }
 
-func BalanceRequestBodyFromGRPCMessage(m *accounting.BalanceRequest_Body) *BalanceRequestBody {
-	if m == nil {
-		return nil
+func (b *BalanceRequestBody) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*accounting.BalanceRequest_Body)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
 	}
 
-	b := new(BalanceRequestBody)
+	var err error
 
-	b.SetOwnerID(
-		refs.OwnerIDFromGRPCMessage(m.GetOwnerId()),
-	)
+	ownerID := v.GetOwnerId()
+	if ownerID == nil {
+		b.ownerID = nil
+	} else {
+		if b.ownerID == nil {
+			b.ownerID = new(refs.OwnerID)
+		}
 
-	return b
+		err = b.ownerID.FromGRPCMessage(ownerID)
+	}
+
+	return err
 }
 
-func BalanceRequestToGRPCMessage(b *BalanceRequest) *accounting.BalanceRequest {
-	if b == nil {
-		return nil
+func (b *BalanceRequest) ToGRPCMessage() grpc.Message {
+	var m *accounting.BalanceRequest
+
+	if b != nil {
+		m = new(accounting.BalanceRequest)
+
+		m.SetBody(b.body.ToGRPCMessage().(*accounting.BalanceRequest_Body))
+		b.RequestHeaders.ToMessage(m)
 	}
 
-	m := new(accounting.BalanceRequest)
-
-	m.SetBody(
-		BalanceRequestBodyToGRPCMessage(b.GetBody()),
-	)
-
-	session.RequestHeadersToGRPC(b, m)
-
 	return m
 }
 
-func BalanceRequestFromGRPCMessage(m *accounting.BalanceRequest) *BalanceRequest {
-	if m == nil {
-		return nil
+func (b *BalanceRequest) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*accounting.BalanceRequest)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
 	}
 
-	b := new(BalanceRequest)
+	var err error
 
-	b.SetBody(
-		BalanceRequestBodyFromGRPCMessage(m.GetBody()),
-	)
+	body := v.GetBody()
+	if body == nil {
+		b.body = nil
+	} else {
+		if b.body == nil {
+			b.body = new(BalanceRequestBody)
+		}
 
-	session.RequestHeadersFromGRPC(m, b)
+		err = b.body.FromGRPCMessage(body)
+		if err != nil {
+			return err
+		}
+	}
 
-	return b
+	return b.RequestHeaders.FromMessage(v)
 }
 
-func DecimalToGRPCMessage(d *Decimal) *accounting.Decimal {
-	if d == nil {
-		return nil
+func (d *Decimal) ToGRPCMessage() grpc.Message {
+	var m *accounting.Decimal
+
+	if d != nil {
+		m = new(accounting.Decimal)
+
+		m.SetValue(d.val)
+		m.SetPrecision(d.prec)
 	}
 
-	m := new(accounting.Decimal)
-
-	m.SetValue(d.GetValue())
-	m.SetPrecision(d.GetPrecision())
-
 	return m
 }
 
-func DecimalFromGRPCMessage(m *accounting.Decimal) *Decimal {
-	if m == nil {
-		return nil
+func (d *Decimal) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*accounting.Decimal)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
 	}
 
-	d := new(Decimal)
+	d.val = v.GetValue()
+	d.prec = v.GetPrecision()
 
-	d.SetValue(m.GetValue())
-	d.SetPrecision(m.GetPrecision())
-
-	return d
+	return nil
 }
 
-func BalanceResponseBodyToGRPCMessage(br *BalanceResponseBody) *accounting.BalanceResponse_Body {
-	if br == nil {
-		return nil
+func (br *BalanceResponseBody) ToGRPCMessage() grpc.Message {
+	var m *accounting.BalanceResponse_Body
+
+	if br != nil {
+		m = new(accounting.BalanceResponse_Body)
+
+		m.SetBalance(br.bal.ToGRPCMessage().(*accounting.Decimal))
 	}
 
-	m := new(accounting.BalanceResponse_Body)
-
-	m.SetBalance(
-		DecimalToGRPCMessage(br.GetBalance()),
-	)
-
 	return m
 }
 
-func BalanceResponseBodyFromGRPCMessage(m *accounting.BalanceResponse_Body) *BalanceResponseBody {
-	if m == nil {
-		return nil
+func (br *BalanceResponseBody) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*accounting.BalanceResponse_Body)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
 	}
 
-	br := new(BalanceResponseBody)
+	var err error
 
-	br.SetBalance(
-		DecimalFromGRPCMessage(m.GetBalance()),
-	)
+	bal := v.GetBalance()
+	if bal == nil {
+		br.bal = nil
+	} else {
+		if br.bal == nil {
+			br.bal = new(Decimal)
+		}
 
-	return br
+		err = br.bal.FromGRPCMessage(bal)
+	}
+
+	return err
 }
 
-func BalanceResponseToGRPCMessage(br *BalanceResponse) *accounting.BalanceResponse {
-	if br == nil {
-		return nil
+func (br *BalanceResponse) ToGRPCMessage() grpc.Message {
+	var m *accounting.BalanceResponse
+
+	if br != nil {
+		m = new(accounting.BalanceResponse)
+
+		m.SetBody(br.body.ToGRPCMessage().(*accounting.BalanceResponse_Body))
+		br.ResponseHeaders.ToMessage(m)
 	}
 
-	m := new(accounting.BalanceResponse)
-
-	m.SetBody(
-		BalanceResponseBodyToGRPCMessage(br.GetBody()),
-	)
-
-	session.ResponseHeadersToGRPC(br, m)
-
 	return m
 }
 
-func BalanceResponseFromGRPCMessage(m *accounting.BalanceResponse) *BalanceResponse {
-	if m == nil {
-		return nil
+func (br *BalanceResponse) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*accounting.BalanceResponse)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
 	}
 
-	br := new(BalanceResponse)
+	var err error
 
-	br.SetBody(
-		BalanceResponseBodyFromGRPCMessage(m.GetBody()),
-	)
+	body := v.GetBody()
+	if body == nil {
+		br.body = nil
+	} else {
+		if br.body == nil {
+			br.body = new(BalanceResponseBody)
+		}
 
-	session.ResponseHeadersFromGRPC(m, br)
+		err = br.body.FromGRPCMessage(body)
+		if err != nil {
+			return err
+		}
+	}
 
-	return br
+	return br.ResponseHeaders.FromMessage(v)
 }
diff --git a/v2/accounting/grpc/client.go b/v2/accounting/grpc/client.go
deleted file mode 100644
index 6a626b1..0000000
--- a/v2/accounting/grpc/client.go
+++ /dev/null
@@ -1,62 +0,0 @@
-package accounting
-
-import (
-	"context"
-
-	"github.com/pkg/errors"
-	"google.golang.org/grpc"
-)
-
-// Client wraps AccountingServiceClient
-// with pre-defined configurations.
-type Client struct {
-	*cfg
-
-	client AccountingServiceClient
-}
-
-// Option represents Client option.
-type Option func(*cfg)
-
-type cfg struct {
-	callOpts []grpc.CallOption
-}
-
-// ErrNilAccountingServiceClient is returned by functions that expect
-// a non-nil AccountingServiceClient, but received nil.
-var ErrNilAccountingServiceClient = errors.New("accounting gRPC client is nil")
-
-func defaultCfg() *cfg {
-	return new(cfg)
-}
-
-// NewClient creates, initializes and returns a new Client instance.
-//
-// Options are applied one by one in order.
-func NewClient(c AccountingServiceClient, opts ...Option) (*Client, error) {
-	if c == nil {
-		return nil, ErrNilAccountingServiceClient
-	}
-
-	cfg := defaultCfg()
-	for i := range opts {
-		opts[i](cfg)
-	}
-
-	return &Client{
-		cfg:    cfg,
-		client: c,
-	}, nil
-}
-
-func (c *Client) Balance(ctx context.Context, req *BalanceRequest) (*BalanceResponse, error) {
-	return c.client.Balance(ctx, req, c.callOpts...)
-}
-
-// WithCallOptions returns Option that configures
-// Client to attach call options to each rpc call.
-func WithCallOptions(opts []grpc.CallOption) Option {
-	return func(c *cfg) {
-		c.callOpts = opts
-	}
-}
diff --git a/v2/accounting/json.go b/v2/accounting/json.go
index 322ca5c..6229885 100644
--- a/v2/accounting/json.go
+++ b/v2/accounting/json.go
@@ -1,26 +1,14 @@
 package accounting
 
 import (
+	"github.com/nspcc-dev/neofs-api-go/rpc/message"
 	accounting "github.com/nspcc-dev/neofs-api-go/v2/accounting/grpc"
-	"google.golang.org/protobuf/encoding/protojson"
 )
 
 func (d *Decimal) MarshalJSON() ([]byte, error) {
-	return protojson.MarshalOptions{
-		EmitUnpopulated: true,
-	}.Marshal(
-		DecimalToGRPCMessage(d),
-	)
+	return message.MarshalJSON(d)
 }
 
 func (d *Decimal) UnmarshalJSON(data []byte) error {
-	msg := new(accounting.Decimal)
-
-	if err := protojson.Unmarshal(data, msg); err != nil {
-		return err
-	}
-
-	*d = *DecimalFromGRPCMessage(msg)
-
-	return nil
+	return message.UnmarshalJSON(d, data, new(accounting.Decimal))
 }
diff --git a/v2/accounting/json_test.go b/v2/accounting/json_test.go
deleted file mode 100644
index 1145e05..0000000
--- a/v2/accounting/json_test.go
+++ /dev/null
@@ -1,20 +0,0 @@
-package accounting_test
-
-import (
-	"testing"
-
-	"github.com/nspcc-dev/neofs-api-go/v2/accounting"
-	"github.com/stretchr/testify/require"
-)
-
-func TestDecimalJSON(t *testing.T) {
-	i := generateDecimal(10)
-
-	data, err := i.MarshalJSON()
-	require.NoError(t, err)
-
-	i2 := new(accounting.Decimal)
-	require.NoError(t, i2.UnmarshalJSON(data))
-
-	require.Equal(t, i, i2)
-}
diff --git a/v2/accounting/marshal.go b/v2/accounting/marshal.go
index e25ab90..11ae796 100644
--- a/v2/accounting/marshal.go
+++ b/v2/accounting/marshal.go
@@ -1,9 +1,9 @@
 package accounting
 
 import (
+	"github.com/nspcc-dev/neofs-api-go/rpc/message"
 	protoutil "github.com/nspcc-dev/neofs-api-go/util/proto"
 	accounting "github.com/nspcc-dev/neofs-api-go/v2/accounting/grpc"
-	"google.golang.org/protobuf/proto"
 )
 
 const (
@@ -56,14 +56,7 @@ func (d *Decimal) StableSize() (size int) {
 }
 
 func (d *Decimal) Unmarshal(data []byte) error {
-	m := new(accounting.Decimal)
-	if err := proto.Unmarshal(data, m); err != nil {
-		return err
-	}
-
-	*d = *DecimalFromGRPCMessage(m)
-
-	return nil
+	return message.Unmarshal(d, data, new(accounting.Decimal))
 }
 
 func (b *BalanceRequestBody) StableMarshal(buf []byte) ([]byte, error) {
@@ -93,6 +86,10 @@ func (b *BalanceRequestBody) StableSize() (size int) {
 	return size
 }
 
+func (b *BalanceRequestBody) Unmarshal(data []byte) error {
+	return message.Unmarshal(b, data, new(accounting.BalanceRequest_Body))
+}
+
 func (br *BalanceResponseBody) StableMarshal(buf []byte) ([]byte, error) {
 	if br == nil {
 		return []byte{}, nil
@@ -119,3 +116,7 @@ func (br *BalanceResponseBody) StableSize() (size int) {
 
 	return size
 }
+
+func (br *BalanceResponseBody) Unmarshal(data []byte) error {
+	return message.Unmarshal(br, data, new(accounting.BalanceResponse_Body))
+}
diff --git a/v2/accounting/marshal_test.go b/v2/accounting/marshal_test.go
deleted file mode 100644
index 6ace77f..0000000
--- a/v2/accounting/marshal_test.go
+++ /dev/null
@@ -1,81 +0,0 @@
-package accounting_test
-
-import (
-	"testing"
-
-	"github.com/nspcc-dev/neofs-api-go/v2/accounting"
-	grpc "github.com/nspcc-dev/neofs-api-go/v2/accounting/grpc"
-	"github.com/nspcc-dev/neofs-api-go/v2/refs"
-	"github.com/stretchr/testify/require"
-	goproto "google.golang.org/protobuf/proto"
-)
-
-func TestBalanceRequestBody_StableMarshal(t *testing.T) {
-	requestBodyFrom := generateBalanceRequestBody("Owner ID")
-	transport := new(grpc.BalanceRequest_Body)
-
-	t.Run("non empty", func(t *testing.T) {
-		wire, err := requestBodyFrom.StableMarshal(nil)
-		require.NoError(t, err)
-
-		err = goproto.Unmarshal(wire, transport)
-		require.NoError(t, err)
-
-		requestBodyTo := accounting.BalanceRequestBodyFromGRPCMessage(transport)
-		require.Equal(t, requestBodyFrom, requestBodyTo)
-	})
-}
-
-func TestBalanceResponseBody_StableMarshal(t *testing.T) {
-	responseBodyFrom := generateBalanceResponseBody(444)
-	transport := new(grpc.BalanceResponse_Body)
-
-	t.Run("non empty", func(t *testing.T) {
-		wire, err := responseBodyFrom.StableMarshal(nil)
-		require.NoError(t, err)
-
-		err = goproto.Unmarshal(wire, transport)
-		require.NoError(t, err)
-
-		responseBodyTo := accounting.BalanceResponseBodyFromGRPCMessage(transport)
-		require.Equal(t, responseBodyFrom, responseBodyTo)
-	})
-}
-
-func generateDecimal(val int64) *accounting.Decimal {
-	decimal := new(accounting.Decimal)
-	decimal.SetValue(val)
-	decimal.SetPrecision(1000)
-
-	return decimal
-}
-
-func generateBalanceRequestBody(id string) *accounting.BalanceRequestBody {
-	owner := new(refs.OwnerID)
-	owner.SetValue([]byte(id))
-
-	request := new(accounting.BalanceRequestBody)
-	request.SetOwnerID(owner)
-
-	return request
-}
-
-func generateBalanceResponseBody(val int64) *accounting.BalanceResponseBody {
-	response := new(accounting.BalanceResponseBody)
-	response.SetBalance(generateDecimal(val))
-
-	return response
-}
-
-func TestDecimalMarshal(t *testing.T) {
-	d := generateDecimal(3)
-
-	data, err := d.StableMarshal(nil)
-	require.NoError(t, err)
-
-	d2 := new(accounting.Decimal)
-
-	require.NoError(t, d2.Unmarshal(data))
-
-	require.Equal(t, d, d2)
-}
diff --git a/v2/accounting/message_test.go b/v2/accounting/message_test.go
new file mode 100644
index 0000000..baf71fa
--- /dev/null
+++ b/v2/accounting/message_test.go
@@ -0,0 +1,19 @@
+package accounting_test
+
+import (
+	"testing"
+
+	"github.com/nspcc-dev/neofs-api-go/rpc/message"
+	messagetest "github.com/nspcc-dev/neofs-api-go/rpc/message/test"
+	accountingtest "github.com/nspcc-dev/neofs-api-go/v2/accounting/test"
+)
+
+func TestMessage(t *testing.T) {
+	messagetest.TestRPCMessage(t,
+		func(empty bool) message.Message { return accountingtest.GenerateDecimal(empty) },
+		func(empty bool) message.Message { return accountingtest.GenerateBalanceRequestBody(empty) },
+		func(empty bool) message.Message { return accountingtest.GenerateBalanceRequest(empty) },
+		func(empty bool) message.Message { return accountingtest.GenerateBalanceResponseBody(empty) },
+		func(empty bool) message.Message { return accountingtest.GenerateBalanceResponse(empty) },
+	)
+}
diff --git a/v2/accounting/service.go b/v2/accounting/service.go
deleted file mode 100644
index 8443c66..0000000
--- a/v2/accounting/service.go
+++ /dev/null
@@ -1,27 +0,0 @@
-package accounting
-
-import (
-	"context"
-
-	"github.com/nspcc-dev/neofs-api-go/v2/session"
-)
-
-type Service interface {
-	Balance(context.Context, *BalanceRequest) (*BalanceResponse, error)
-}
-
-type BalanceRequest struct {
-	body *BalanceRequestBody
-
-	metaHeader *session.RequestMetaHeader
-
-	verifyHeader *session.RequestVerificationHeader
-}
-
-type BalanceResponse struct {
-	body *BalanceResponseBody
-
-	metaHeader *session.ResponseMetaHeader
-
-	verifyHeader *session.ResponseVerificationHeader
-}
diff --git a/v2/accounting/test/client_test.go b/v2/accounting/test/client_test.go
deleted file mode 100644
index 7c63dcf..0000000
--- a/v2/accounting/test/client_test.go
+++ /dev/null
@@ -1,163 +0,0 @@
-package main
-
-import (
-	"context"
-	"crypto/ecdsa"
-	"errors"
-	"testing"
-
-	"github.com/nspcc-dev/neofs-api-go/v2/accounting"
-	accountingGRPC "github.com/nspcc-dev/neofs-api-go/v2/accounting/grpc"
-	"github.com/nspcc-dev/neofs-api-go/v2/refs"
-	"github.com/nspcc-dev/neofs-api-go/v2/session"
-	"github.com/nspcc-dev/neofs-api-go/v2/signature"
-	"github.com/nspcc-dev/neofs-crypto/test"
-	"github.com/stretchr/testify/require"
-	"google.golang.org/grpc"
-)
-
-type testGRPCClient struct {
-	server *testGRPCServer
-}
-
-type testGRPCServer struct {
-	key  *ecdsa.PrivateKey
-	resp *accounting.BalanceResponse
-	err  error
-}
-
-func (s *testGRPCClient) Balance(ctx context.Context, in *accountingGRPC.BalanceRequest, opts ...grpc.CallOption) (*accountingGRPC.BalanceResponse, error) {
-	return s.server.Balance(ctx, in)
-}
-
-func (s *testGRPCServer) Balance(_ context.Context, req *accountingGRPC.BalanceRequest) (*accountingGRPC.BalanceResponse, error) {
-	if s.err != nil {
-		return nil, s.err
-	}
-
-	// verify request structure
-	if err := signature.VerifyServiceMessage(
-		accounting.BalanceRequestFromGRPCMessage(req),
-	); err != nil {
-		return nil, err
-	}
-
-	// sign response structure
-	if err := signature.SignServiceMessage(s.key, s.resp); err != nil {
-		return nil, err
-	}
-
-	return accounting.BalanceResponseToGRPCMessage(s.resp), nil
-}
-
-func testRequest() *accounting.BalanceRequest {
-	ownerID := new(refs.OwnerID)
-	ownerID.SetValue([]byte{1, 2, 3})
-
-	body := new(accounting.BalanceRequestBody)
-	body.SetOwnerID(ownerID)
-
-	meta := new(session.RequestMetaHeader)
-	meta.SetTTL(1)
-
-	req := new(accounting.BalanceRequest)
-	req.SetBody(body)
-	req.SetMetaHeader(meta)
-
-	return req
-}
-
-func testResponse() *accounting.BalanceResponse {
-	dec := new(accounting.Decimal)
-	dec.SetValue(10)
-
-	body := new(accounting.BalanceResponseBody)
-	body.SetBalance(dec)
-
-	meta := new(session.ResponseMetaHeader)
-	meta.SetTTL(1)
-
-	resp := new(accounting.BalanceResponse)
-	resp.SetBody(body)
-	resp.SetMetaHeader(meta)
-
-	return resp
-}
-
-func TestGRPCClient(t *testing.T) {
-	ctx := context.TODO()
-
-	cliKey := test.DecodeKey(0)
-	srvKey := test.DecodeKey(1)
-
-	t.Run("gRPC server error", func(t *testing.T) {
-		srvErr := errors.New("test server error")
-
-		srv := &testGRPCServer{
-			err: srvErr,
-		}
-
-		cli := &testGRPCClient{
-			server: srv,
-		}
-
-		c, err := accounting.NewClient(accounting.WithGRPCServiceClient(cli))
-		require.NoError(t, err)
-
-		resp, err := c.Balance(ctx, new(accounting.BalanceRequest))
-		require.True(t, errors.Is(err, srvErr))
-		require.Nil(t, resp)
-	})
-
-	t.Run("invalid request structure", func(t *testing.T) {
-		req := testRequest()
-
-		require.Error(t, signature.VerifyServiceMessage(req))
-
-		c, err := accounting.NewClient(
-			accounting.WithGRPCServiceClient(
-				&testGRPCClient{
-					server: new(testGRPCServer),
-				},
-			),
-		)
-		require.NoError(t, err)
-
-		resp, err := c.Balance(ctx, req)
-		require.Error(t, err)
-		require.Nil(t, resp)
-	})
-
-	t.Run("correct response", func(t *testing.T) {
-		req := testRequest()
-
-		require.NoError(t, signature.SignServiceMessage(cliKey, req))
-
-		resp := testResponse()
-
-		{ // w/o this require.Equal fails due to nil and []T{} difference
-			meta := new(session.ResponseMetaHeader)
-			meta.SetXHeaders([]*session.XHeader{})
-			resp.SetMetaHeader(meta)
-		}
-
-		c, err := accounting.NewClient(
-			accounting.WithGRPCServiceClient(
-				&testGRPCClient{
-					server: &testGRPCServer{
-						key:  srvKey,
-						resp: resp,
-					},
-				},
-			),
-		)
-		require.NoError(t, err)
-
-		r, err := c.Balance(ctx, req)
-		require.NoError(t, err)
-
-		require.NoError(t, signature.VerifyServiceMessage(r))
-		require.Equal(t, resp.GetBody(), r.GetBody())
-		require.Equal(t, resp.GetMetaHeader(), r.GetMetaHeader())
-	})
-}
diff --git a/v2/accounting/test/generate.go b/v2/accounting/test/generate.go
new file mode 100644
index 0000000..b2c0d95
--- /dev/null
+++ b/v2/accounting/test/generate.go
@@ -0,0 +1,54 @@
+package accountingtest
+
+import (
+	"github.com/nspcc-dev/neofs-api-go/v2/accounting"
+	accountingtest "github.com/nspcc-dev/neofs-api-go/v2/refs/test"
+	sessiontest "github.com/nspcc-dev/neofs-api-go/v2/session/test"
+)
+
+func GenerateBalanceRequest(empty bool) *accounting.BalanceRequest {
+	m := new(accounting.BalanceRequest)
+
+	m.SetBody(GenerateBalanceRequestBody(empty))
+	m.SetMetaHeader(sessiontest.GenerateRequestMetaHeader(empty))
+	m.SetVerificationHeader(sessiontest.GenerateRequestVerificationHeader(empty))
+
+	return m
+}
+
+func GenerateBalanceRequestBody(empty bool) *accounting.BalanceRequestBody {
+	m := new(accounting.BalanceRequestBody)
+
+	m.SetOwnerID(accountingtest.GenerateOwnerID(empty))
+
+	return m
+}
+
+func GenerateBalanceResponse(empty bool) *accounting.BalanceResponse {
+	m := new(accounting.BalanceResponse)
+
+	m.SetBody(GenerateBalanceResponseBody(empty))
+	m.SetMetaHeader(sessiontest.GenerateResponseMetaHeader(empty))
+	m.SetVerificationHeader(sessiontest.GenerateResponseVerificationHeader(empty))
+
+	return m
+}
+
+func GenerateBalanceResponseBody(empty bool) *accounting.BalanceResponseBody {
+	m := new(accounting.BalanceResponseBody)
+
+	m.SetBalance(GenerateDecimal(empty))
+
+	return m
+}
+
+func GenerateDecimal(empty bool) *accounting.Decimal {
+	m := new(accounting.Decimal)
+
+	if !empty {
+		m.SetValue(1)
+		m.SetPrecision(2)
+	}
+
+	return m
+}
diff --git a/v2/acl/convert.go b/v2/acl/convert.go
index 462baa7..d4a52b8 100644
--- a/v2/acl/convert.go
+++ b/v2/acl/convert.go
@@ -1,8 +1,11 @@
 package acl
 
 import (
+	"github.com/nspcc-dev/neofs-api-go/rpc/grpc"
+	"github.com/nspcc-dev/neofs-api-go/rpc/message"
 	acl "github.com/nspcc-dev/neofs-api-go/v2/acl/grpc"
 	"github.com/nspcc-dev/neofs-api-go/v2/refs"
+	refsGRPC "github.com/nspcc-dev/neofs-api-go/v2/refs/grpc"
 )
 
 // RoleToGRPCField converts unified role enum into grpc enum.
@@ -149,316 +152,395 @@ func MatchTypeFromGRPCField(t acl.MatchType) MatchType {
 	}
 }
 
-// HeaderFilterToGRPCMessage converts unified header filter struct into grpc struct.
-func HeaderFilterToGRPCMessage(f *HeaderFilter) *acl.EACLRecord_Filter {
-	if f == nil {
-		return nil
+func (f *HeaderFilter) ToGRPCMessage() grpc.Message {
+	var m *acl.EACLRecord_Filter
+
+	if f != nil {
+		m = new(acl.EACLRecord_Filter)
+
+		m.SetKey(f.key)
+		m.SetValue(f.value)
+		m.SetHeader(HeaderTypeToGRPCField(f.hdrType))
+		m.SetMatchType(MatchTypeToGRPCField(f.matchType))
 	}
 
-	m := new(acl.EACLRecord_Filter)
-
-	m.SetHeader(
-		HeaderTypeToGRPCField(f.GetHeaderType()),
-	)
-
-	m.SetMatchType(
-		MatchTypeToGRPCField(f.GetMatchType()),
-	)
-
-	m.SetKey(f.GetKey())
-	m.SetValue(f.GetValue())
-
 	return m
 }
 
-// HeaderFilterFromGRPCMessage converts grpc struct into unified header filter struct.
-func HeaderFilterFromGRPCMessage(m *acl.EACLRecord_Filter) *HeaderFilter {
-	if m == nil {
-		return nil
+func (f *HeaderFilter) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*acl.EACLRecord_Filter)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
 	}
 
-	f := new(HeaderFilter)
+	f.key = v.GetKey()
+	f.value = v.GetValue()
+	f.hdrType = HeaderTypeFromGRPCField(v.GetHeaderType())
+	f.matchType = MatchTypeFromGRPCField(v.GetMatchType())
 
-	f.SetHeaderType(
-		HeaderTypeFromGRPCField(m.GetHeaderType()),
-	)
-
-	f.SetMatchType(
-		MatchTypeFromGRPCField(m.GetMatchType()),
-	)
-
-	f.SetKey(m.GetKey())
-	f.SetValue(m.GetValue())
-
-	return f
+	return nil
 }
 
-// TargetToGRPCMessage converts unified role info struct into grpc struct.
-func TargetToGRPCMessage(t *Target) *acl.EACLRecord_Target {
-	if t == nil {
-		return nil
+func HeaderFiltersToGRPC(fs []*HeaderFilter) (res []*acl.EACLRecord_Filter) {
+	if fs != nil {
+		res = make([]*acl.EACLRecord_Filter, 0, len(fs))
+
+		for i := range fs {
+			res = append(res, fs[i].ToGRPCMessage().(*acl.EACLRecord_Filter))
+		}
 	}
 
-	m := new(acl.EACLRecord_Target)
+	return
+}
 
-	m.SetRole(
-		RoleToGRPCField(t.GetRole()),
-	)
+func HeaderFiltersFromGRPC(fs []*acl.EACLRecord_Filter) (res []*HeaderFilter, err error) {
+	if fs != nil {
+		res = make([]*HeaderFilter, 0, len(fs))
 
-	m.SetKeys(t.GetKeys())
+		for i := range fs {
+			var x *HeaderFilter
+
+			if fs[i] != nil {
+				x = new(HeaderFilter)
+
+				err = x.FromGRPCMessage(fs[i])
+				if err != nil {
+					return
+				}
+			}
+
+			res = append(res, x)
+		}
+	}
+
+	return
+}
+
+func (t *Target) ToGRPCMessage() grpc.Message {
+	var m *acl.EACLRecord_Target
+
+	if t != nil {
+		m = new(acl.EACLRecord_Target)
+
+		m.SetRole(RoleToGRPCField(t.role))
+		m.SetKeys(t.keys)
+	}
 
 	return m
 }
 
-// TargetInfoFromGRPCMessage converts grpc struct into unified role info struct.
-func TargetInfoFromGRPCMessage(m *acl.EACLRecord_Target) *Target {
-	if m == nil {
-		return nil
+func (t *Target) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*acl.EACLRecord_Target)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
 	}
 
-	t := new(Target)
+	t.role = RoleFromGRPCField(v.GetRole())
+	t.keys = v.GetKeys()
 
-	t.SetRole(
-		RoleFromGRPCField(m.GetRole()),
-	)
-
-	t.SetKeys(m.GetKeys())
-
-	return t
+	return nil
 }
 
-// RecordToGRPCMessage converts unified acl record struct into grpc struct.
-func RecordToGRPCMessage(r *Record) *acl.EACLRecord {
-	if r == nil {
-		return nil
+func TargetsToGRPC(ts []*Target) (res []*acl.EACLRecord_Target) {
+	if ts != nil {
+		res = make([]*acl.EACLRecord_Target, 0, len(ts))
+
+		for i := range ts {
+			res = append(res, ts[i].ToGRPCMessage().(*acl.EACLRecord_Target))
+		}
 	}
 
-	m := new(acl.EACLRecord)
+	return
+}
 
-	m.SetOperation(
-		OperationToGRPCField(r.GetOperation()),
-	)
+func TargetsFromGRPC(fs []*acl.EACLRecord_Target) (res []*Target, err error) {
+	if fs != nil {
+		res = make([]*Target, 0, len(fs))
 
-	m.SetAction(
-		ActionToGRPCField(r.GetAction()),
-	)
+		for i := range fs {
+			var x *Target
 
-	filters := r.GetFilters()
-	filterMsg := make([]*acl.EACLRecord_Filter, 0, len(filters))
+			if fs[i] != nil {
+				x = new(Target)
 
-	for i := range filters {
-		filterMsg = append(filterMsg, HeaderFilterToGRPCMessage(filters[i]))
+				err = x.FromGRPCMessage(fs[i])
+				if err != nil {
+					return
+				}
+			}
+
+			res = append(res, x)
+		}
 	}
 
-	m.SetFilters(filterMsg)
+	return
+}
 
-	targets := r.GetTargets()
-	targetMsg := make([]*acl.EACLRecord_Target, 0, len(targets))
+func (r *Record) ToGRPCMessage() grpc.Message {
+	var m *acl.EACLRecord
 
-	for i := range targets {
-		targetMsg = append(targetMsg, TargetToGRPCMessage(targets[i]))
+	if r != nil {
+		m = new(acl.EACLRecord)
+
+		m.SetOperation(OperationToGRPCField(r.op))
+		m.SetAction(ActionToGRPCField(r.action))
+		m.SetFilters(HeaderFiltersToGRPC(r.filters))
+		m.SetTargets(TargetsToGRPC(r.targets))
 	}
 
-	m.SetTargets(targetMsg)
-
 	return m
 }
 
-// RecordFromGRPCMessage converts grpc struct into unified acl record struct.
-func RecordFromGRPCMessage(m *acl.EACLRecord) *Record {
-	if m == nil {
-		return nil
+func (r *Record) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*acl.EACLRecord)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
 	}
 
-	r := new(Record)
+	var err error
 
-	r.SetOperation(
-		OperationFromGRPCField(m.GetOperation()),
-	)
-
-	r.SetAction(
-		ActionFromGRPCField(m.GetAction()),
-	)
-
-	filterMsg := m.GetFilters()
-	filters := make([]*HeaderFilter, 0, len(filterMsg))
-
-	for i := range filterMsg {
-		filters = append(filters, HeaderFilterFromGRPCMessage(filterMsg[i]))
+	r.filters, err = HeaderFiltersFromGRPC(v.GetFilters())
+	if err != nil {
+		return err
 	}
 
-	r.SetFilters(filters)
-
-	targetMsg := m.GetTargets()
-	targets := make([]*Target, 0, len(targetMsg))
-
-	for i := range targetMsg {
-		targets = append(targets, TargetInfoFromGRPCMessage(targetMsg[i]))
+	r.targets, err = TargetsFromGRPC(v.GetTargets())
+	if err != nil {
+		return err
 	}
 
-	r.SetTargets(targets)
+	r.op = OperationFromGRPCField(v.GetOperation())
+	r.action = ActionFromGRPCField(v.GetAction())
 
-	return r
+	return nil
 }
 
-// TableToGRPCMessage converts unified acl table struct into grpc struct.
-func TableToGRPCMessage(t *Table) *acl.EACLTable {
-	if t == nil {
-		return nil
+func RecordsToGRPC(ts []*Record) (res []*acl.EACLRecord) {
+	if ts != nil {
+		res = make([]*acl.EACLRecord, 0, len(ts))
+
+		for i := range ts {
+			res = append(res, ts[i].ToGRPCMessage().(*acl.EACLRecord))
+		}
 	}
 
-	m := new(acl.EACLTable)
+	return
+}
 
-	m.SetVersion(
-		refs.VersionToGRPCMessage(t.GetVersion()),
-	)
-	m.SetContainerId(
-		refs.ContainerIDToGRPCMessage(t.GetContainerID()),
-	)
+func RecordsFromGRPC(fs []*acl.EACLRecord) (res []*Record, err error) {
+	if fs != nil {
+		res = make([]*Record, 0, len(fs))
 
-	records := t.GetRecords()
-	recordMsg := make([]*acl.EACLRecord, 0, len(records))
+		for i := range fs {
+			var x *Record
 
-	for i := range records {
-		recordMsg = append(recordMsg, RecordToGRPCMessage(records[i]))
+			if fs[i] != nil {
+				x = new(Record)
+
+				err = x.FromGRPCMessage(fs[i])
+				if err != nil {
+					return
+				}
+			}
+
+			res = append(res, x)
+		}
 	}
 
-	m.SetRecords(recordMsg)
+	return
+}
+
+func (t *Table) ToGRPCMessage() grpc.Message {
+	var m *acl.EACLTable
+
+	if t != nil {
+		m = new(acl.EACLTable)
+
+		m.SetVersion(t.version.ToGRPCMessage().(*refsGRPC.Version))
+		m.SetContainerId(t.cid.ToGRPCMessage().(*refsGRPC.ContainerID))
+		m.SetRecords(RecordsToGRPC(t.records))
+	}
 
 	return m
 }
 
-// TableFromGRPCMessage converts grpc struct into unified acl table struct.
-func TableFromGRPCMessage(m *acl.EACLTable) *Table {
-	if m == nil {
-		return nil
+func (t *Table) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*acl.EACLTable)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
 	}
 
-	t := new(Table)
+	var err error
 
-	t.SetVersion(
-		refs.VersionFromGRPCMessage(m.GetVersion()),
-	)
-	t.SetContainerID(
-		refs.ContainerIDFromGRPCMessage(m.GetContainerId()),
-	)
+	cid := v.GetContainerId()
+	if cid == nil {
+		t.cid = nil
+	} else {
+		if t.cid == nil {
+			t.cid = new(refs.ContainerID)
+		}
 
-	recordMsg := m.GetRecords()
-	records := make([]*Record, 0, len(recordMsg))
-
-	for i := range recordMsg {
-		records = append(records, RecordFromGRPCMessage(recordMsg[i]))
+		err = t.cid.FromGRPCMessage(cid)
+		if err != nil {
+			return err
+		}
 	}
 
-	t.SetRecords(records)
+	version := v.GetVersion()
+	if version == nil {
+		t.version = nil
+	} else {
+		if t.version == nil {
+			t.version = new(refs.Version)
+		}
 
-	return t
+		err = t.version.FromGRPCMessage(version)
+		if err != nil {
+			return err
+		}
+	}
+
+	t.records, err = RecordsFromGRPC(v.GetRecords())
+
+	return err
 }
 
-func TokenLifetimeToGRPCMessage(tl *TokenLifetime) *acl.BearerToken_Body_TokenLifetime {
-	if tl == nil {
-		return nil
+func (l *TokenLifetime) ToGRPCMessage() grpc.Message {
+	var m *acl.BearerToken_Body_TokenLifetime
+
+	if l != nil {
+		m = new(acl.BearerToken_Body_TokenLifetime)
+
+		m.SetExp(l.exp)
+		m.SetIat(l.iat)
+		m.SetNbf(l.nbf)
 	}
 
-	m := new(acl.BearerToken_Body_TokenLifetime)
-
-	m.SetExp(tl.GetExp())
-	m.SetNbf(tl.GetNbf())
-	m.SetIat(tl.GetIat())
-
 	return m
 }
 
-func TokenLifetimeFromGRPCMessage(m *acl.BearerToken_Body_TokenLifetime) *TokenLifetime {
-	if m == nil {
-		return nil
+func (l *TokenLifetime) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*acl.BearerToken_Body_TokenLifetime)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
 	}
 
-	tl := new(TokenLifetime)
+	l.exp = v.GetExp()
+	l.iat = v.GetIat()
+	l.nbf = v.GetNbf()
 
-	tl.SetExp(m.GetExp())
-	tl.SetNbf(m.GetNbf())
-	tl.SetIat(m.GetIat())
-
-	return tl
+	return nil
 }
 
-func BearerTokenBodyToGRPCMessage(v *BearerTokenBody) *acl.BearerToken_Body {
-	if v == nil {
-		return nil
+func (bt *BearerTokenBody) ToGRPCMessage() grpc.Message {
+	var m *acl.BearerToken_Body
+
+	if bt != nil {
+		m = new(acl.BearerToken_Body)
+
+		m.SetOwnerId(bt.ownerID.ToGRPCMessage().(*refsGRPC.OwnerID))
+		m.SetLifetime(bt.lifetime.ToGRPCMessage().(*acl.BearerToken_Body_TokenLifetime))
+		m.SetEaclTable(bt.eacl.ToGRPCMessage().(*acl.EACLTable))
 	}
 
-	m := new(acl.BearerToken_Body)
-
-	m.SetEaclTable(
-		TableToGRPCMessage(v.GetEACL()),
-	)
-
-	m.SetOwnerId(
-		refs.OwnerIDToGRPCMessage(v.GetOwnerID()),
-	)
-
-	m.SetLifetime(
-		TokenLifetimeToGRPCMessage(v.GetLifetime()),
-	)
-
 	return m
 }
 
-func BearerTokenBodyFromGRPCMessage(m *acl.BearerToken_Body) *BearerTokenBody {
-	if m == nil {
-		return nil
+func (bt *BearerTokenBody) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*acl.BearerToken_Body)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
 	}
 
-	bt := new(BearerTokenBody)
+	var err error
 
-	bt.SetEACL(
-		TableFromGRPCMessage(m.GetEaclTable()),
-	)
+	ownerID := v.GetOwnerId()
+	if ownerID == nil {
+		bt.ownerID = nil
+	} else {
+		if bt.ownerID == nil {
+			bt.ownerID = new(refs.OwnerID)
+		}
 
-	bt.SetOwnerID(
-		refs.OwnerIDFromGRPCMessage(m.GetOwnerId()),
-	)
+		err = bt.ownerID.FromGRPCMessage(ownerID)
+		if err != nil {
+			return err
+		}
+	}
 
-	bt.SetLifetime(
-		TokenLifetimeFromGRPCMessage(m.GetLifetime()),
-	)
+	lifetime := v.GetLifetime()
+	if lifetime == nil {
+		bt.lifetime = nil
+	} else {
+		if bt.lifetime == nil {
+			bt.lifetime = new(TokenLifetime)
+		}
 
-	return bt
+		err = bt.lifetime.FromGRPCMessage(lifetime)
+		if err != nil {
+			return err
+		}
+	}
+
+	eacl := v.GetEaclTable()
+	if eacl == nil {
+		bt.eacl = nil
+	} else {
+		if bt.eacl == nil {
+			bt.eacl = new(Table)
+		}
+
+		err = bt.eacl.FromGRPCMessage(eacl)
+	}
+
+	return err
 }
 
-func BearerTokenToGRPCMessage(t *BearerToken) *acl.BearerToken {
-	if t == nil {
-		return nil
+func (bt *BearerToken) ToGRPCMessage() grpc.Message {
+	var m *acl.BearerToken
+
+	if bt != nil {
+		m = new(acl.BearerToken)
+
+		m.SetBody(bt.body.ToGRPCMessage().(*acl.BearerToken_Body))
+		m.SetSignature(bt.sig.ToGRPCMessage().(*refsGRPC.Signature))
 	}
 
-	m := new(acl.BearerToken)
-
-	m.SetBody(
-		BearerTokenBodyToGRPCMessage(t.GetBody()),
-	)
-
-	m.SetSignature(
-		refs.SignatureToGRPCMessage(t.GetSignature()),
-	)
-
 	return m
 }
 
-func BearerTokenFromGRPCMessage(m *acl.BearerToken) *BearerToken {
-	if m == nil {
-		return nil
+func (bt *BearerToken) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*acl.BearerToken)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
 	}
 
-	bt := new(BearerToken)
+	var err error
 
-	bt.SetBody(
-		BearerTokenBodyFromGRPCMessage(m.GetBody()),
-	)
+	body := v.GetBody()
+	if body == nil {
+		bt.body = nil
+	} else {
+		if bt.body == nil {
+			bt.body = new(BearerTokenBody)
+		}
 
-	bt.SetSignature(
-		refs.SignatureFromGRPCMessage(m.GetSignature()),
-	)
+		err = bt.body.FromGRPCMessage(body)
+		if err != nil {
+			return err
+		}
+	}
 
-	return bt
+	sig := v.GetSignature()
+	if sig == nil {
+		bt.sig = nil
+	} else {
+		if bt.sig == nil {
+			bt.sig = new(refs.Signature)
+		}
+
+		err = bt.sig.FromGRPCMessage(sig)
+	}
+
+	return err
 }
diff --git a/v2/acl/json.go b/v2/acl/json.go
index 91f2d32..1a3cacd 100644
--- a/v2/acl/json.go
+++ b/v2/acl/json.go
@@ -1,146 +1,62 @@
 package acl
 
 import (
+	"github.com/nspcc-dev/neofs-api-go/rpc/message"
 	acl "github.com/nspcc-dev/neofs-api-go/v2/acl/grpc"
-	"google.golang.org/protobuf/encoding/protojson"
 )
 
 func (f *HeaderFilter) MarshalJSON() ([]byte, error) {
-	return protojson.MarshalOptions{
-		EmitUnpopulated: true,
-	}.Marshal(
-		HeaderFilterToGRPCMessage(f),
-	)
+	return message.MarshalJSON(f)
 }
 
 func (f *HeaderFilter) UnmarshalJSON(data []byte) error {
-	msg := new(acl.EACLRecord_Filter)
-
-	if err := protojson.Unmarshal(data, msg); err != nil {
-		return err
-	}
-
-	*f = *HeaderFilterFromGRPCMessage(msg)
-
-	return nil
+	return message.UnmarshalJSON(f, data, new(acl.EACLRecord_Filter))
 }
 
 func (t *Target) MarshalJSON() ([]byte, error) {
-	return protojson.MarshalOptions{
-		EmitUnpopulated: true,
-	}.Marshal(
-		TargetToGRPCMessage(t),
-	)
+	return message.MarshalJSON(t)
 }
 
 func (t *Target) UnmarshalJSON(data []byte) error {
-	msg := new(acl.EACLRecord_Target)
-
-	if err := protojson.Unmarshal(data, msg); err != nil {
-		return err
-	}
-
-	*t = *TargetInfoFromGRPCMessage(msg)
-
-	return nil
+	return message.UnmarshalJSON(t, data, new(acl.EACLRecord_Target))
 }
 
 func (r *Record) MarshalJSON() ([]byte, error) {
-	return protojson.MarshalOptions{
-		EmitUnpopulated: true,
-	}.Marshal(
-		RecordToGRPCMessage(r),
-	)
+	return message.MarshalJSON(r)
 }
 
 func (r *Record) UnmarshalJSON(data []byte) error {
-	msg := new(acl.EACLRecord)
-
-	if err := protojson.Unmarshal(data, msg); err != nil {
-		return err
-	}
-
-	*r = *RecordFromGRPCMessage(msg)
-
-	return nil
+	return message.UnmarshalJSON(r, data, new(acl.EACLRecord))
 }
 
 func (t *Table) MarshalJSON() ([]byte, error) {
-	return protojson.MarshalOptions{
-		EmitUnpopulated: true,
-	}.Marshal(
-		TableToGRPCMessage(t),
-	)
+	return message.MarshalJSON(t)
 }
 
 func (t *Table) UnmarshalJSON(data []byte) error {
-	msg := new(acl.EACLTable)
-
-	if err := protojson.Unmarshal(data, msg); err != nil {
-		return err
-	}
-
-	*t = *TableFromGRPCMessage(msg)
-
-	return nil
+	return message.UnmarshalJSON(t, data, new(acl.EACLTable))
 }
 
 func (l *TokenLifetime) MarshalJSON() ([]byte, error) {
-	return protojson.MarshalOptions{
-		EmitUnpopulated: true,
-	}.Marshal(
-		TokenLifetimeToGRPCMessage(l),
-	)
+	return message.MarshalJSON(l)
 }
 
 func (l *TokenLifetime) UnmarshalJSON(data []byte) error {
-	msg := new(acl.BearerToken_Body_TokenLifetime)
-
-	if err := protojson.Unmarshal(data, msg); err != nil {
-		return err
-	}
-
-	*l = *TokenLifetimeFromGRPCMessage(msg)
-
-	return nil
+	return message.UnmarshalJSON(l, data, new(acl.BearerToken_Body_TokenLifetime))
 }
 
 func (bt *BearerTokenBody) MarshalJSON() ([]byte, error) {
-	return protojson.MarshalOptions{
-		EmitUnpopulated: true,
-	}.Marshal(
-		BearerTokenBodyToGRPCMessage(bt),
-	)
+	return message.MarshalJSON(bt)
 }
 
 func (bt *BearerTokenBody) UnmarshalJSON(data []byte) error {
-	msg := new(acl.BearerToken_Body)
-
-	if err := protojson.Unmarshal(data, msg); err != nil {
-		return err
-	}
-
-	*bt = *BearerTokenBodyFromGRPCMessage(msg)
-
-	return nil
+	return message.UnmarshalJSON(bt, data, new(acl.BearerToken_Body))
 }
 
 func (bt *BearerToken) MarshalJSON() ([]byte, error) {
-	return protojson.MarshalOptions{
-		EmitUnpopulated: true,
-	}.Marshal(
-		BearerTokenToGRPCMessage(bt),
-	)
+	return message.MarshalJSON(bt)
 }
 
 func (bt *BearerToken) UnmarshalJSON(data []byte) error {
-	msg := new(acl.BearerToken)
-
-	if err := protojson.Unmarshal(data, msg); err != nil {
-		return err
-	}
-
-	*bt = *BearerTokenFromGRPCMessage(msg)
-
-	return nil
+	return message.UnmarshalJSON(bt, data, new(acl.BearerToken))
 }
diff --git a/v2/acl/json_test.go b/v2/acl/json_test.go
deleted file mode 100644
index 6d95755..0000000
--- a/v2/acl/json_test.go
+++ /dev/null
@@ -1,81 +0,0 @@
-package acl_test
-
-import (
-	"testing"
-
-	"github.com/nspcc-dev/neofs-api-go/v2/acl"
-	"github.com/stretchr/testify/require"
-)
-
-func TestBearerTokenJSON(t *testing.T) {
-	b := generateBearerToken("id")
-
-	data, err := b.MarshalJSON()
-	require.NoError(t, err)
-
-	b2 := new(acl.BearerToken)
-	require.NoError(t, b2.UnmarshalJSON(data))
-
-	require.Equal(t, b, b2)
-}
-
-func TestFilterJSON(t *testing.T) {
-	f := generateFilter(acl.HeaderTypeObject, "key", "value")
-
-	data, err := f.MarshalJSON()
-	require.NoError(t, err)
-
-	f2 := new(acl.HeaderFilter)
-	require.NoError(t, f2.UnmarshalJSON(data))
-
-	require.Equal(t, f, f2)
-}
-
-func TestTargetJSON(t *testing.T) {
-	tar := generateTarget(acl.RoleSystem, 3)
-
-	data, err := tar.MarshalJSON()
-	require.NoError(t, err)
-
-	tar2 := new(acl.Target)
-	require.NoError(t, tar2.UnmarshalJSON(data))
-
-	require.Equal(t, tar, tar2)
-}
-
-func TestTable_MarshalJSON(t *testing.T) {
-	tab := new(acl.Table)
-	tab.SetRecords([]*acl.Record{generateRecord(false)})
-
-	data, err := tab.MarshalJSON()
-	require.NoError(t, err)
-
-	tab2 := new(acl.Table)
-	require.NoError(t, tab2.UnmarshalJSON(data))
-
-	require.Equal(t, tab, tab2)
-}
-
-func TestTokenLifetimeJSON(t *testing.T) {
-	l := generateLifetime(1, 2, 3)
-
-	data, err := l.MarshalJSON()
-	require.NoError(t, err)
-
-	l2 := new(acl.TokenLifetime)
-	require.NoError(t, l2.UnmarshalJSON(data))
-
-	require.Equal(t, l, l2)
-}
-
-func TestBearerTokenBodyJSON(t *testing.T) {
-	b := generateBearerTokenBody("id")
-
-	data, err := b.MarshalJSON()
-	require.NoError(t, err)
-
-	b2 := new(acl.BearerTokenBody)
-	require.NoError(t, b2.UnmarshalJSON(data))
-
-	require.Equal(t, b, b2)
-}
diff --git a/v2/acl/marshal.go b/v2/acl/marshal.go
index f477848..15a317d 100644
--- a/v2/acl/marshal.go
+++ b/v2/acl/marshal.go
@@ -1,9 +1,9 @@
 package acl
 
 import (
+	"github.com/nspcc-dev/neofs-api-go/rpc/message"
 	protoutil "github.com/nspcc-dev/neofs-api-go/util/proto"
 	acl "github.com/nspcc-dev/neofs-api-go/v2/acl/grpc"
-	"google.golang.org/protobuf/proto"
 )
 
 const (
@@ -95,14 +95,7 @@ func (t *Table) StableSize() (size int) {
 }
 
 func (t *Table) Unmarshal(data []byte) error {
-	m := new(acl.EACLTable)
-	if err := proto.Unmarshal(data, m); err != nil {
-		return err
-	}
-
-	*t = *TableFromGRPCMessage(m)
-
-	return nil
+	return message.Unmarshal(t, data, new(acl.EACLTable))
 }
 
 // StableMarshal marshals unified acl record structure in a protobuf
@@ -177,14 +170,7 @@ func (r *Record) StableSize() (size int) {
 }
 
 func (r *Record) Unmarshal(data []byte) error {
-	m := new(acl.EACLRecord)
-	if err := proto.Unmarshal(data, m); err != nil {
-		return err
-	}
-
-	*r = *RecordFromGRPCMessage(m)
-
-	return nil
+	return message.Unmarshal(r, data, new(acl.EACLRecord))
 }
 
 // StableMarshal marshals unified header filter structure in a protobuf
@@ -247,14 +233,7 @@ func (f *HeaderFilter) StableSize() (size int) {
 }
 
 func (f *HeaderFilter) Unmarshal(data []byte) error {
-	m := new(acl.EACLRecord_Filter)
-	if err := proto.Unmarshal(data, m); err != nil {
-		return err
-	}
-
-	*f = *HeaderFilterFromGRPCMessage(m)
-
-	return nil
+	return message.Unmarshal(f, data, new(acl.EACLRecord_Filter))
 }
 
 // StableMarshal marshals unified role info structure in a protobuf
@@ -301,14 +280,7 @@ func (t *Target) StableSize() (size int) {
 }
 
 func (t *Target) Unmarshal(data []byte) error {
-	m := new(acl.EACLRecord_Target)
-	if err := proto.Unmarshal(data, m); err != nil {
-		return err
-	}
-
-	*t = *TargetInfoFromGRPCMessage(m)
-
-	return nil
+	return message.Unmarshal(t, data, new(acl.EACLRecord_Target))
 }
 
 func (l *TokenLifetime) StableMarshal(buf []byte) ([]byte, error) {
@@ -360,14 +332,7 @@ func (l *TokenLifetime) StableSize() (size int) {
 }
 
 func (l *TokenLifetime) Unmarshal(data []byte) error {
-	m := new(acl.BearerToken_Body_TokenLifetime)
-	if err := proto.Unmarshal(data, m); err != nil {
-		return err
-	}
-
-	*l = *TokenLifetimeFromGRPCMessage(m)
-
-	return nil
+	return message.Unmarshal(l, data, new(acl.BearerToken_Body_TokenLifetime))
 }
 
 func (bt *BearerTokenBody) StableMarshal(buf []byte) ([]byte, error) {
@@ -419,14 +384,7 @@ func (bt *BearerTokenBody) StableSize() (size int) {
 }
 
 func (bt *BearerTokenBody) Unmarshal(data []byte) error {
-	m := new(acl.BearerToken_Body)
-	if err := proto.Unmarshal(data, m); err != nil {
-		return err
-	}
-
-	*bt = *BearerTokenBodyFromGRPCMessage(m)
-
-	return nil
+	return message.Unmarshal(bt, data, new(acl.BearerToken_Body))
 }
 
 func (bt *BearerToken) StableMarshal(buf []byte) ([]byte, error) {
@@ -470,12 +428,5 @@ func (bt *BearerToken) StableSize() (size int) {
 }
 
 func (bt *BearerToken) Unmarshal(data []byte) error {
-	m := new(acl.BearerToken)
-	if err := proto.Unmarshal(data, m); err != nil {
-		return err
-	}
-
-	*bt = *BearerTokenFromGRPCMessage(m)
-
-	return nil
+	return message.Unmarshal(bt, data, new(acl.BearerToken))
 }
diff --git a/v2/acl/marshal_test.go b/v2/acl/marshal_test.go
deleted file mode 100644
index 8b85411..0000000
--- a/v2/acl/marshal_test.go
+++ /dev/null
@@ -1,237 +0,0 @@
-package acl_test
-
-import (
-	"fmt"
-	"testing"
-
-	"github.com/nspcc-dev/neofs-api-go/v2/acl"
-	"github.com/nspcc-dev/neofs-api-go/v2/refs"
-	"github.com/stretchr/testify/require"
-)
-
-func generateTarget(u acl.Role, k int) *acl.Target {
-	target := new(acl.Target)
-	target.SetRole(u)
-
-	keys := make([][]byte, k)
-
-	for i := 0; i < k; i++ {
-		s := fmt.Sprintf("Public Key %d", i+1)
-		keys[i] = []byte(s)
-	}
-
-	return target
-}
-
-func generateFilter(t acl.HeaderType, k, v string) *acl.HeaderFilter {
-	filter := new(acl.HeaderFilter)
-	filter.SetHeaderType(t)
-	filter.SetMatchType(acl.MatchTypeStringEqual)
-	filter.SetKey(k)
-	filter.SetValue(v)
-
-	return filter
-}
-
-func generateRecord(another bool) *acl.Record {
-	record := new(acl.Record)
-
-	switch another {
-	case true:
-		t1 := generateTarget(acl.RoleUser, 2)
-		f1 := generateFilter(acl.HeaderTypeObject, "OID", "ObjectID Value")
-
-		record.SetOperation(acl.OperationHead)
-		record.SetAction(acl.ActionDeny)
-		record.SetTargets([]*acl.Target{t1})
-		record.SetFilters([]*acl.HeaderFilter{f1})
-	default:
-		t1 := generateTarget(acl.RoleUser, 2)
-		t2 := generateTarget(acl.RoleSystem, 0)
-		f1 := generateFilter(acl.HeaderTypeObject, "CID", "Container ID Value")
-		f2 := generateFilter(acl.HeaderTypeRequest, "X-Header-Key", "X-Header-Value")
-
-		record.SetOperation(acl.OperationPut)
-		record.SetAction(acl.ActionAllow)
-		record.SetTargets([]*acl.Target{t1, t2})
-		record.SetFilters([]*acl.HeaderFilter{f1, f2})
-	}
-
-	return record
-}
-
-func generateEACL() *acl.Table {
-	cid := new(refs.ContainerID)
-	cid.SetValue([]byte("Container ID"))
-
-	ver := new(refs.Version)
-	ver.SetMajor(2)
-	ver.SetMinor(3)
-
-	table := new(acl.Table)
-	table.SetVersion(ver)
-	table.SetContainerID(cid)
-	table.SetRecords([]*acl.Record{generateRecord(true)})
-
-	return table
-}
-
-func generateSignature(k, v string) *refs.Signature {
-	sig := new(refs.Signature)
-	sig.SetKey([]byte(k))
-	sig.SetSign([]byte(v))
-
-	return sig
-}
-
-func generateLifetime(exp, nbf, iat uint64) *acl.TokenLifetime {
-	lifetime := new(acl.TokenLifetime)
-	lifetime.SetExp(exp)
-	lifetime.SetNbf(nbf)
-	lifetime.SetIat(iat)
-
-	return lifetime
-}
-
-func generateBearerTokenBody(id string) *acl.BearerTokenBody {
-	owner := new(refs.OwnerID)
-	owner.SetValue([]byte(id))
-
-	tokenBody := new(acl.BearerTokenBody)
-	tokenBody.SetOwnerID(owner)
-	tokenBody.SetLifetime(generateLifetime(1, 2, 3))
-	tokenBody.SetEACL(generateEACL())
-
-	return tokenBody
-}
-
-func generateBearerToken(id string) *acl.BearerToken {
-	bearerToken := new(acl.BearerToken)
-	bearerToken.SetBody(generateBearerTokenBody(id))
-	bearerToken.SetSignature(generateSignature("id", id))
-
-	return bearerToken
-}
-
-func TestHeaderFilter_StableMarshal(t *testing.T) {
-	filterFrom := generateFilter(acl.HeaderTypeObject, "CID", "Container ID Value")
-
-	t.Run("non empty", func(t *testing.T) {
-		filterFrom.SetHeaderType(acl.HeaderTypeObject)
-		filterFrom.SetMatchType(acl.MatchTypeStringEqual)
-		filterFrom.SetKey("Hello")
-		filterFrom.SetValue("World")
-
-		wire, err := filterFrom.StableMarshal(nil)
-		require.NoError(t, err)
-
-		filterTo := new(acl.HeaderFilter)
-		require.NoError(t, filterTo.Unmarshal(wire))
-
-		require.Equal(t, filterFrom, filterTo)
-	})
-}
-
-func TestTargetInfo_StableMarshal(t *testing.T) {
-	targetFrom := generateTarget(acl.RoleUser, 2)
-
-	t.Run("non empty", func(t *testing.T) {
-		targetFrom.SetRole(acl.RoleUser)
-		targetFrom.SetKeys([][]byte{
-			[]byte("Public Key 1"),
-			[]byte("Public Key 2"),
-		})
-
-		wire, err := targetFrom.StableMarshal(nil)
-		require.NoError(t, err)
-
-		targetTo := new(acl.Target)
-		require.NoError(t, targetTo.Unmarshal(wire))
-
-		require.Equal(t, targetFrom, targetTo)
-	})
-}
-
-func TestRecord_StableMarshal(t *testing.T) {
-	recordFrom := generateRecord(false)
-
-	t.Run("non empty", func(t *testing.T) {
-		wire, err := recordFrom.StableMarshal(nil)
-		require.NoError(t, err)
-
-		to := new(acl.Record)
-		require.NoError(t, to.Unmarshal(wire))
-
-		require.Equal(t, recordFrom, to)
-	})
-}
-
-func TestTable_StableMarshal(t *testing.T) {
-	tableFrom := new(acl.Table)
-
-	t.Run("non empty", func(t *testing.T) {
-		cid := new(refs.ContainerID)
-		cid.SetValue([]byte("Container ID"))
-
-		ver := new(refs.Version)
-		ver.SetMajor(2)
-		ver.SetMinor(3)
-
-		r1 := generateRecord(false)
-		r2 := generateRecord(true)
-
-		tableFrom.SetVersion(ver)
-		tableFrom.SetContainerID(cid)
-		tableFrom.SetRecords([]*acl.Record{r1, r2})
-
-		wire, err := tableFrom.StableMarshal(nil)
-		require.NoError(t, err)
-
-		tableTo := new(acl.Table)
-		require.NoError(t, tableTo.Unmarshal(wire))
-
-		require.Equal(t, tableFrom, tableTo)
-	})
-}
-
-func TestTokenLifetime_StableMarshal(t *testing.T) {
-	lifetimeFrom := generateLifetime(10, 20, 30)
-
-	t.Run("non empty", func(t *testing.T) {
-		wire, err := lifetimeFrom.StableMarshal(nil)
-		require.NoError(t, err)
-
-		lifetimeTo := new(acl.TokenLifetime)
-		require.NoError(t, lifetimeTo.Unmarshal(wire))
-
-		require.Equal(t, lifetimeFrom, lifetimeTo)
-	})
-}
-
-func TestBearerTokenBody_StableMarshal(t *testing.T) {
-	bearerTokenBodyFrom := generateBearerTokenBody("Bearer Token Body")
-
-	t.Run("non empty", func(t *testing.T) {
-		wire, err := bearerTokenBodyFrom.StableMarshal(nil)
-		require.NoError(t, err)
-
-		bearerTokenBodyTo := new(acl.BearerTokenBody)
-		require.NoError(t, bearerTokenBodyTo.Unmarshal(wire))
-
-		require.Equal(t, bearerTokenBodyFrom, bearerTokenBodyTo)
-	})
-}
-
-func TestBearerToken_StableMarshal(t *testing.T) {
-	bearerTokenFrom := generateBearerToken("Bearer Token")
-
-	t.Run("non empty", func(t *testing.T) {
-		wire, err := bearerTokenFrom.StableMarshal(nil)
-		require.NoError(t, err)
-
-		bearerTokenTo := new(acl.BearerToken)
-		require.NoError(t, bearerTokenTo.Unmarshal(wire))
-
-		require.Equal(t, bearerTokenFrom, bearerTokenTo)
-	})
-}
diff --git a/v2/acl/message_test.go b/v2/acl/message_test.go
new file mode 100644
index 0000000..bb6d991
--- /dev/null
+++ b/v2/acl/message_test.go
@@ -0,0 +1,21 @@
+package acl_test
+
+import (
+	"testing"
+
+	"github.com/nspcc-dev/neofs-api-go/rpc/message"
+	messagetest "github.com/nspcc-dev/neofs-api-go/rpc/message/test"
+	acltest "github.com/nspcc-dev/neofs-api-go/v2/acl/test"
+)
+
+func TestMessageConvert(t *testing.T) {
+	messagetest.TestRPCMessage(t,
+		func(empty bool) message.Message { return acltest.GenerateFilter(empty) },
+		func(empty bool) message.Message { return acltest.GenerateTarget(empty) },
+		func(empty bool) message.Message { return acltest.GenerateRecord(empty) },
+		func(empty bool) message.Message { return acltest.GenerateTable(empty) },
+		func(empty bool) message.Message { return acltest.GenerateTokenLifetime(empty) },
+		func(empty bool) message.Message { return acltest.GenerateBearerTokenBody(empty) },
+		func(empty bool) message.Message { return acltest.GenerateBearerToken(empty) },
+	)
+}
diff --git a/v2/acl/test/generate.go b/v2/acl/test/generate.go
new file mode 100644
index 0000000..3b1e1a7
--- /dev/null
+++ b/v2/acl/test/generate.go
@@ -0,0 +1,124 @@
+package acltest
+
+import (
+	"github.com/nspcc-dev/neofs-api-go/v2/acl"
+	accountingtest "github.com/nspcc-dev/neofs-api-go/v2/refs/test"
+)
+
+func GenerateBearerToken(empty bool) *acl.BearerToken {
+	m := new(acl.BearerToken)
+
+	m.SetBody(GenerateBearerTokenBody(empty))
+	m.SetSignature(accountingtest.GenerateSignature(empty))
+
+	return m
+}
+
+func GenerateBearerTokenBody(empty bool) *acl.BearerTokenBody {
+	m := new(acl.BearerTokenBody)
+
+	m.SetOwnerID(accountingtest.GenerateOwnerID(empty))
+	m.SetEACL(GenerateTable(empty))
+	m.SetLifetime(GenerateTokenLifetime(empty))
+
+	return m
+}
+
+func GenerateTable(empty bool) *acl.Table {
+	m := new(acl.Table)
+
+	m.SetRecords(GenerateRecords(empty))
+	m.SetContainerID(accountingtest.GenerateContainerID(empty))
+	m.SetVersion(accountingtest.GenerateVersion(empty))
+
+	return m
+}
+
+func GenerateRecords(empty bool) []*acl.Record {
+	rs := make([]*acl.Record, 0)
+
+	if !empty {
+		rs = append(rs,
+			GenerateRecord(false),
+			GenerateRecord(false),
+		)
+	}
+
+	return rs
+}
+
+func GenerateRecord(empty bool) *acl.Record {
+	m := new(acl.Record)
+
+	if !empty {
+		m.SetAction(acl.ActionAllow)
+		m.SetOperation(acl.OperationGet)
+	}
+
+	m.SetFilters(GenerateFilters(empty))
+	m.SetTargets(GenerateTargets(empty))
+
+	return m
+}
+
+func GenerateFilters(empty bool) []*acl.HeaderFilter {
+	fs := make([]*acl.HeaderFilter, 0)
+
+	if !empty {
+		fs = append(fs,
+			GenerateFilter(false),
+			GenerateFilter(false),
+		)
+	}
+
+	return fs
+}
+
+func GenerateFilter(empty bool) *acl.HeaderFilter {
+	m := new(acl.HeaderFilter)
+
+	if !empty {
+		m.SetKey("key")
+		m.SetValue("val")
+		m.SetHeaderType(acl.HeaderTypeRequest)
+		m.SetMatchType(acl.MatchTypeStringEqual)
+	}
+
+	return m
+}
+
+func GenerateTargets(empty bool) []*acl.Target {
+	ts := make([]*acl.Target, 0)
+
+	if !empty {
+		ts = append(ts,
+			GenerateTarget(false),
+			GenerateTarget(false),
+		)
+	}
+
+	return ts
+}
+
+func GenerateTarget(empty bool) *acl.Target {
+	m := new(acl.Target)
+
+	if !empty {
+		m.SetRole(acl.RoleSystem)
+		m.SetKeys([][]byte{{1}, {2}})
+	}
+
+	return m
+}
+
+func GenerateTokenLifetime(empty bool) *acl.TokenLifetime {
+	m := new(acl.TokenLifetime)
+
+	if !empty {
+		m.SetExp(1)
+		m.SetIat(2)
+		m.SetExp(3)
+	}
+
+	return m
+}
diff --git a/v2/audit/convert.go b/v2/audit/convert.go
index 6402041..1b2a998 100644
--- a/v2/audit/convert.go
+++ b/v2/audit/convert.go
@@ -1,94 +1,94 @@
 package audit
 
 import (
+	"github.com/nspcc-dev/neofs-api-go/rpc/grpc"
+	"github.com/nspcc-dev/neofs-api-go/rpc/message"
 	audit "github.com/nspcc-dev/neofs-api-go/v2/audit/grpc"
 	"github.com/nspcc-dev/neofs-api-go/v2/refs"
+	refsGRPC "github.com/nspcc-dev/neofs-api-go/v2/refs/grpc"
 )
 
-// DataAuditResultToGRPCMessage converts unified DataAuditResult structure
-// into gRPC DataAuditResult message.
-func DataAuditResultToGRPCMessage(a *DataAuditResult) *audit.DataAuditResult {
-	if a == nil {
-		return nil
+func (a *DataAuditResult) ToGRPCMessage() grpc.Message {
+	var m *audit.DataAuditResult
+
+	if a != nil {
+		m = new(audit.DataAuditResult)
+
+		m.SetAuditEpoch(a.auditEpoch)
+		m.SetPublicKey(a.pubKey)
+		m.SetContainerId(a.cid.ToGRPCMessage().(*refsGRPC.ContainerID))
+		m.SetComplete(a.complete)
+		m.SetVersion(a.version.ToGRPCMessage().(*refsGRPC.Version))
+		m.SetPassNodes(a.passNodes)
+		m.SetFailNodes(a.failNodes)
+		m.SetRetries(a.retries)
+		m.SetRequests(a.requests)
+		m.SetHit(a.hit)
+		m.SetMiss(a.miss)
+		m.SetFail(a.fail)
+		m.SetPassSg(refs.ObjectIDListToGRPCMessage(a.passSG))
+		m.SetFailSg(refs.ObjectIDListToGRPCMessage(a.failSG))
 	}
 
-	m := new(audit.DataAuditResult)
-
-	m.SetVersion(
-		refs.VersionToGRPCMessage(a.GetVersion()),
-	)
-
-	m.SetAuditEpoch(a.GetAuditEpoch())
-
-	m.SetContainerId(
-		refs.ContainerIDToGRPCMessage(a.GetContainerID()),
-	)
-
-	m.SetPublicKey(a.GetPublicKey())
-
-	m.SetComplete(a.GetComplete())
-
-	m.SetPassSg(
-		refs.ObjectIDListToGRPCMessage(a.GetPassSG()),
-	)
-
-	m.SetFailSg(
-		refs.ObjectIDListToGRPCMessage(a.GetFailSG()),
-	)
-
-	m.SetRequests(a.GetRequests())
-	m.SetRetries(a.GetRetries())
-
-	m.SetHit(a.GetHit())
-	m.SetMiss(a.GetMiss())
-	m.SetFail(a.GetFail())
-
-	m.SetPassNodes(a.GetPassNodes())
-	m.SetFailNodes(a.GetFailNodes())
-
 	return m
 }
 
-// DataAuditResultFromGRPCMessage converts gRPC message DataAuditResult
-// into unified DataAuditResult structure.
-func DataAuditResultFromGRPCMessage(m *audit.DataAuditResult) *DataAuditResult {
-	if m == nil {
-		return nil
+func (a *DataAuditResult) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*audit.DataAuditResult)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
 	}
 
-	a := new(DataAuditResult)
+	var err error
 
-	a.SetVersion(
-		refs.VersionFromGRPCMessage(m.GetVersion()),
-	)
+	cid := v.GetContainerId()
+	if cid == nil {
+		a.cid = nil
+	} else {
+		if a.cid == nil {
+			a.cid = new(refs.ContainerID)
+		}
 
-	a.SetAuditEpoch(m.GetAuditEpoch())
+		err = a.cid.FromGRPCMessage(cid)
+		if err != nil {
+			return err
+		}
+	}
 
-	a.SetContainerID(
-		refs.ContainerIDFromGRPCMessage(m.GetContainerId()),
-	)
+	version := v.GetVersion()
+	if version == nil {
+		a.version = nil
+	} else {
+		if a.version == nil {
+			a.version = new(refs.Version)
+		}
 
-	a.SetPublicKey(m.GetPublicKey())
+		err = a.version.FromGRPCMessage(version)
+		if err != nil {
+			return err
+		}
+	}
 
-	a.SetComplete(m.GetComplete())
+	a.passSG, err = refs.ObjectIDListFromGRPCMessage(v.GetPassSg())
+	if err != nil {
+		return err
+	}
 
-	a.SetPassSG(
-		refs.ObjectIDListFromGRPCMessage(m.GetPassSg()),
-	)
+	a.failSG, err = refs.ObjectIDListFromGRPCMessage(v.GetFailSg())
+	if err != nil {
+		return err
+	}
 
-	a.SetFailSG(
-		refs.ObjectIDListFromGRPCMessage(m.GetFailSg()),
-	)
+	a.auditEpoch = v.GetAuditEpoch()
+	a.pubKey = v.GetPublicKey()
+	a.complete = v.GetComplete()
+	a.passNodes = v.GetPassNodes()
+	a.failNodes = v.GetFailNodes()
+	a.retries = v.GetRetries()
+	a.requests = v.GetRequests()
+	a.hit = v.GetHit()
+	a.miss = v.GetMiss()
+	a.fail = v.GetFail()
 
-	a.SetRequests(m.GetRequests())
-	a.SetRetries(m.GetRetries())
-
-	a.SetHit(m.GetHit())
-	a.SetMiss(m.GetMiss())
-	a.SetFail(m.GetFail())
-
-	a.SetPassNodes(m.GetPassNodes())
-	a.SetFailNodes(m.GetFailNodes())
-
-	return a
+	return err
 }
diff --git a/v2/audit/json.go b/v2/audit/json.go
index 80cb33a..b119aa1 100644
--- a/v2/audit/json.go
+++ b/v2/audit/json.go
@@ -1,26 +1,14 @@
 package audit
 
 import (
+	"github.com/nspcc-dev/neofs-api-go/rpc/message"
 	audit "github.com/nspcc-dev/neofs-api-go/v2/audit/grpc"
-	"google.golang.org/protobuf/encoding/protojson"
 )
 
 func (a *DataAuditResult) MarshalJSON() ([]byte, error) {
-	return protojson.MarshalOptions{
-		EmitUnpopulated: true,
-	}.Marshal(
-		DataAuditResultToGRPCMessage(a),
-	)
+	return message.MarshalJSON(a)
 }
 
 func (a *DataAuditResult) UnmarshalJSON(data []byte) error {
-	msg := new(audit.DataAuditResult)
-
-	if err := protojson.Unmarshal(data, msg); err != nil {
-		return err
-	}
-
-	*a = *DataAuditResultFromGRPCMessage(msg)
-
-	return nil
+	return message.UnmarshalJSON(a, data, new(audit.DataAuditResult))
 }
diff --git a/v2/audit/json_test.go b/v2/audit/json_test.go
deleted file mode 100644
index f1cae45..0000000
--- a/v2/audit/json_test.go
+++ /dev/null
@@ -1,20 +0,0 @@
-package audit_test
-
-import (
-	"testing"
-
-	"github.com/nspcc-dev/neofs-api-go/v2/audit"
-	"github.com/stretchr/testify/require"
-)
-
-func TestDataAuditResultJSON(t *testing.T) {
-	a := generateDataAuditResult()
-
-	data, err := a.MarshalJSON()
-	require.NoError(t, err)
-
-	a2 := new(audit.DataAuditResult)
-	require.NoError(t, a2.UnmarshalJSON(data))
-
-	require.Equal(t, a, a2)
-}
diff --git a/v2/audit/marshal.go b/v2/audit/marshal.go
index 5ea8ef5..e1c1b53 100644
--- a/v2/audit/marshal.go
+++ b/v2/audit/marshal.go
@@ -1,10 +1,10 @@
 package audit
 
 import (
+	"github.com/nspcc-dev/neofs-api-go/rpc/message"
 	"github.com/nspcc-dev/neofs-api-go/util/proto"
 	audit "github.com/nspcc-dev/neofs-api-go/v2/audit/grpc"
 	"github.com/nspcc-dev/neofs-api-go/v2/refs"
-	goproto "google.golang.org/protobuf/proto"
 )
 
 const (
@@ -168,12 +168,5 @@ func (a *DataAuditResult) StableSize() (size int) {
 // Unmarshal unmarshals DataAuditResult structure from its protobuf
 // binary representation.
 func (a *DataAuditResult) Unmarshal(data []byte) error {
-	m := new(audit.DataAuditResult)
-	if err := goproto.Unmarshal(data, m); err != nil {
-		return err
-	}
-
-	*a = *DataAuditResultFromGRPCMessage(m)
-
-	return nil
+	return message.Unmarshal(a, data, new(audit.DataAuditResult))
 }
diff --git a/v2/audit/marshal_test.go b/v2/audit/marshal_test.go
deleted file mode 100644
index bac2dbe..0000000
--- a/v2/audit/marshal_test.go
+++ /dev/null
@@ -1,63 +0,0 @@
-package audit_test
-
-import (
-	"testing"
-
-	"github.com/nspcc-dev/neofs-api-go/v2/audit"
-	"github.com/nspcc-dev/neofs-api-go/v2/refs"
-	"github.com/stretchr/testify/require"
-)
-
-func TestDataAuditResult_StableMarshal(t *testing.T) {
-	from := generateDataAuditResult()
-
-	t.Run("non empty", func(t *testing.T) {
-		wire, err := from.StableMarshal(nil)
-		require.NoError(t, err)
-
-		to := new(audit.DataAuditResult)
-		require.NoError(t, to.Unmarshal(wire))
-
-		require.Equal(t, from, to)
-	})
-}
-
-func generateDataAuditResult() *audit.DataAuditResult {
-	a := new(audit.DataAuditResult)
-
-	v := new(refs.Version)
-	v.SetMajor(2)
-	v.SetMinor(1)
-
-	oid1 := new(refs.ObjectID)
-	oid1.SetValue([]byte("Object ID 1"))
-
-	oid2 := new(refs.ObjectID)
-	oid2.SetValue([]byte("Object ID 2"))
-
-	cid := new(refs.ContainerID)
-	cid.SetValue([]byte("Container ID"))
-
-	a.SetVersion(v)
-	a.SetAuditEpoch(13)
-	a.SetContainerID(cid)
-	a.SetPublicKey([]byte("Public key"))
-	a.SetComplete(true)
-	a.SetRequests(10)
-	a.SetRetries(9)
-	a.SetPassSG([]*refs.ObjectID{oid1, oid2})
-	a.SetFailSG([]*refs.ObjectID{oid2, oid1})
-	a.SetHit(1)
-	a.SetMiss(2)
-	a.SetFail(3)
-	a.SetPassNodes([][]byte{
-		{1, 2},
-		{3, 4},
-	})
-	a.SetFailNodes([][]byte{
-		{5, 6},
-		{7, 8},
-	})
-
-	return a
-}
diff --git a/v2/audit/message_test.go b/v2/audit/message_test.go
new file mode 100644
index 0000000..dbfc7b7
--- /dev/null
+++ b/v2/audit/message_test.go
@@ -0,0 +1,15 @@
+package audit_test
+
+import (
+	"testing"
+
+	"github.com/nspcc-dev/neofs-api-go/rpc/message"
+	messagetest "github.com/nspcc-dev/neofs-api-go/rpc/message/test"
+	audittest "github.com/nspcc-dev/neofs-api-go/v2/audit/test"
+)
+
+func TestMessageConvert(t *testing.T) {
+	messagetest.TestRPCMessage(t,
+		func(empty bool) message.Message { return audittest.GenerateDataAuditResult(empty) },
+	)
+}
diff --git a/v2/audit/test/generate.go b/v2/audit/test/generate.go
new file mode 100644
index 0000000..76d85a9
--- /dev/null
+++ b/v2/audit/test/generate.go
@@ -0,0 +1,30 @@
+package audittest
+
+import (
+	"github.com/nspcc-dev/neofs-api-go/v2/audit"
+	refstest "github.com/nspcc-dev/neofs-api-go/v2/refs/test"
+)
+
+func GenerateDataAuditResult(empty bool) *audit.DataAuditResult {
+	m := new(audit.DataAuditResult)
+
+	if !empty {
+		m.SetPublicKey([]byte{1, 2, 3})
+		m.SetAuditEpoch(13)
+		m.SetHit(100)
+		m.SetMiss(200)
+		m.SetFail(300)
+		m.SetComplete(true)
+		m.SetPassNodes([][]byte{{1}, {2}})
+		m.SetFailNodes([][]byte{{3}, {4}})
+		m.SetRequests(666)
+		m.SetRetries(777)
+	}
+
+	m.SetVersion(refstest.GenerateVersion(empty))
+	m.SetContainerID(refstest.GenerateContainerID(empty))
+	m.SetPassSG(refstest.GenerateObjectIDs(empty))
+	m.SetFailSG(refstest.GenerateObjectIDs(empty))
+
+	return m
+}
diff --git a/v2/client/options.go b/v2/client/options.go
deleted file mode 100644
index 1c9e60a..0000000
--- a/v2/client/options.go
+++ /dev/null
@@ -1,115 +0,0 @@
-package client
-
-import (
-	"context"
-	"net"
-	"time"
-
-	"google.golang.org/grpc"
-)
-
-type Option func(*cfg)
-
-type cfg struct {
-	addr string
-
-	conn net.Conn
-
-	gRPC cfgGRPC
-
-	dialTimeout time.Duration
-}
-
-type cfgGRPC struct {
-	dialOpts []grpc.DialOption
-
-	conn *grpc.ClientConn
-}
-
-const defaultDialTimeout = 5 * time.Second
-
-func defaultCfg() *cfg {
-	return &cfg{
-		gRPC: cfgGRPC{
-			dialOpts: []grpc.DialOption{
-				grpc.WithInsecure(),
-			},
-		},
-		dialTimeout: defaultDialTimeout,
-	}
-}
-
-func NewGRPCClientConn(opts ...Option) (*grpc.ClientConn, error) {
-	cfg := defaultCfg()
-
-	for i := range opts {
-		opts[i](cfg)
-	}
-
-	var err error
-
-	if cfg.gRPC.conn == nil {
-		if cfg.conn != nil {
-			if cfg.addr == "" {
-				cfg.addr = cfg.conn.RemoteAddr().String()
-			}
-
-			cfg.gRPC.dialOpts = append(cfg.gRPC.dialOpts,
-				grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) {
-					return cfg.conn, nil
-				}),
-			)
-		}
-
-		dialCtx, cancel := context.WithTimeout(context.Background(), cfg.dialTimeout)
-
-		cfg.gRPC.conn, err = grpc.DialContext(dialCtx, cfg.addr, cfg.gRPC.dialOpts...)
-
-		cancel()
-		if err != nil {
-			return nil, err
-		}
-	}
-
-	return cfg.gRPC.conn, err
-}
-
-func WithNetworkAddress(v string) Option {
-	return func(c *cfg) {
-		if v != "" {
-			c.addr = v
-		}
-	}
-}
-
-func WithNetConn(v net.Conn) Option {
-	return func(c *cfg) {
-		if v != nil {
-			c.conn = v
-		}
-	}
-}
-
-func WithDialTimeout(v time.Duration) Option {
-	return func(c *cfg) {
-		if v > 0 {
-			c.dialTimeout = v
-		}
-	}
-}
-
-func WithGRPCDialOpts(v []grpc.DialOption) Option {
-	return func(c *cfg) {
-		if len(v) > 0 {
-			c.gRPC.dialOpts = v
-		}
-	}
-}
-
-func WithGRPCConn(v *grpc.ClientConn) Option {
-	return func(c *cfg) {
-		if v != nil {
-			c.gRPC.conn = v
-		}
-	}
-}
diff --git a/v2/client/proto.go b/v2/client/proto.go
deleted file mode 100644
index f6a6aa6..0000000
--- a/v2/client/proto.go
+++ /dev/null
@@ -1,23 +0,0 @@
-package client
-
-import (
-	"github.com/pkg/errors"
-)
-
-type Protocol uint32
-
-const (
-	_ Protocol = iota
-	ProtoGRPC
-)
-
-var ErrProtoUnsupported = errors.New("unsupported protocol")
-
-func (p Protocol) String() string {
-	switch p {
-	case ProtoGRPC:
-		return "GRPC"
-	default:
-		return "UNKNOWN"
-	}
-}
diff --git a/v2/container/client.go b/v2/container/client.go
deleted file mode 100644
index c0921b6..0000000
--- a/v2/container/client.go
+++ /dev/null
@@ -1,355 +0,0 @@
-package container
-
-import (
-	"context"
-
-	"github.com/nspcc-dev/neofs-api-go/v2/client"
-	container "github.com/nspcc-dev/neofs-api-go/v2/container/grpc"
-	"github.com/pkg/errors"
-	"google.golang.org/grpc"
-)
-
-// Client represents universal container
-// transport client.
-type Client struct {
-	cPut *putClient
-
-	cGet *getClient
-
-	cDel *delClient
-
-	cList *listClient
-
-	cSetEACL *setEACLClient
-
-	cGetEACL *getEACLClient
-
-	cAnnounce *announceUsedSpaceClient
-}
-
-// Option represents Client option.
-type Option func(*cfg)
-
-type cfg struct {
-	proto client.Protocol
-
-	globalOpts []client.Option
-
-	gRPC cfgGRPC
-}
-
-type cfgGRPC struct {
-	serviceClient container.ContainerServiceClient
-
-	grpcCallOpts []grpc.CallOption
-
-	callOpts []container.Option
-
-	client *container.Client
-}
-
-type putClient struct {
-	requestConverter func(*PutRequest) interface{}
-
-	caller func(context.Context, interface{}) (interface{}, error)
-
-	responseConverter func(interface{}) *PutResponse
-}
-
-type getClient struct {
-	requestConverter func(*GetRequest) interface{}
-
-	caller func(context.Context, interface{}) (interface{}, error)
-
-	responseConverter func(interface{}) *GetResponse
-}
-
-type delClient struct {
-	requestConverter func(*DeleteRequest) interface{}
-
-	caller func(context.Context, interface{}) (interface{}, error)
-
-	responseConverter func(interface{}) *DeleteResponse
-}
-
-type listClient struct {
-	requestConverter func(*ListRequest) interface{}
-
-	caller func(context.Context, interface{}) (interface{}, error)
-
-	responseConverter func(interface{}) *ListResponse
-}
-
-type setEACLClient struct {
-	requestConverter func(*SetExtendedACLRequest) interface{}
-
-	caller func(context.Context, interface{}) (interface{}, error)
-
-	responseConverter func(interface{}) *SetExtendedACLResponse
-}
-
-type getEACLClient struct {
-	requestConverter func(*GetExtendedACLRequest) interface{}
-
-	caller func(context.Context, interface{}) (interface{}, error)
-
-	responseConverter func(interface{}) *GetExtendedACLResponse
-}
-
-type announceUsedSpaceClient struct {
-	requestConverter func(request *AnnounceUsedSpaceRequest) interface{}
-
-	caller func(context.Context, interface{}) (interface{}, error)
-
-	responseConverter func(interface{}) *AnnounceUsedSpaceResponse
-}
-
-// Put sends PutRequest over the network and returns PutResponse.
-//
-// It returns any error encountered during the call.
-func (c *Client) Put(ctx context.Context, req *PutRequest) (*PutResponse, error) {
-	resp, err := c.cPut.caller(ctx, c.cPut.requestConverter(req))
-	if err != nil {
-		return nil, errors.Wrap(err, "could not send container put request")
-	}
-
-	return c.cPut.responseConverter(resp), nil
-}
-
-// Get sends GetRequest over the network and returns GetResponse.
-//
-// It returns any error encountered during the call.
-func (c *Client) Get(ctx context.Context, req *GetRequest) (*GetResponse, error) {
-	resp, err := c.cGet.caller(ctx, c.cGet.requestConverter(req))
-	if err != nil {
-		return nil, errors.Wrap(err, "could not send container get request")
-	}
-
-	return c.cGet.responseConverter(resp), nil
-}
-
-// Delete sends GetRequest over the network and returns GetResponse.
-//
-// It returns any error encountered during the call.
-func (c *Client) Delete(ctx context.Context, req *DeleteRequest) (*DeleteResponse, error) {
-	resp, err := c.cDel.caller(ctx, c.cDel.requestConverter(req))
-	if err != nil {
-		return nil, errors.Wrap(err, "could not send container delete request")
-	}
-
-	return c.cDel.responseConverter(resp), nil
-}
-
-// List sends ListRequest over the network and returns ListResponse.
-//
-// It returns any error encountered during the call.
-func (c *Client) List(ctx context.Context, req *ListRequest) (*ListResponse, error) {
-	resp, err := c.cList.caller(ctx, c.cList.requestConverter(req))
-	if err != nil {
-		return nil, errors.Wrap(err, "could not send container list request")
-	}
-
-	return c.cList.responseConverter(resp), nil
-}
-
-// SetExtendedACL sends SetExtendedACLRequest over the network and returns SetExtendedACLResponse.
-//
-// It returns any error encountered during the call.
-func (c *Client) SetExtendedACL(ctx context.Context, req *SetExtendedACLRequest) (*SetExtendedACLResponse, error) {
-	resp, err := c.cSetEACL.caller(ctx, c.cSetEACL.requestConverter(req))
-	if err != nil {
-		return nil, errors.Wrap(err, "could not send container set EACL request")
-	}
-
-	return c.cSetEACL.responseConverter(resp), nil
-}
-
-// GetExtendedACL sends GetExtendedACLRequest over the network and returns GetExtendedACLResponse.
-//
-// It returns any error encountered during the call.
-func (c *Client) GetExtendedACL(ctx context.Context, req *GetExtendedACLRequest) (*GetExtendedACLResponse, error) {
-	resp, err := c.cGetEACL.caller(ctx, c.cGetEACL.requestConverter(req))
-	if err != nil {
-		return nil, errors.Wrap(err, "could not send container get EACL request")
-	}
-
-	return c.cGetEACL.responseConverter(resp), nil
-}
-
-// AnnounceUsedSpace sends AnnounceUsedSpaceRequest over the network and returns
-// AnnounceUsedSpaceResponse.
-//
-// It returns any error encountered during the call.
-func (c *Client) AnnounceUsedSpace(ctx context.Context, req *AnnounceUsedSpaceRequest) (*AnnounceUsedSpaceResponse, error) {
-	resp, err := c.cAnnounce.caller(ctx, c.cAnnounce.requestConverter(req))
-	if err != nil {
-		return nil, errors.Wrap(err, "could not send announce used space request")
-	}
-
-	return c.cAnnounce.responseConverter(resp), nil
-}
-
-func defaultCfg() *cfg {
-	return &cfg{
-		proto: client.ProtoGRPC,
-	}
-}
-
-func NewClient(opts ...Option) (*Client, error) {
-	cfg := defaultCfg()
-
-	for i := range opts {
-		opts[i](cfg)
-	}
-
-	var err error
-
-	switch cfg.proto {
-	case client.ProtoGRPC:
-		var c *container.Client
-		if c, err = newGRPCClient(cfg); err != nil {
-			break
-		}
-
-		return &Client{
-			cPut: &putClient{
-				requestConverter: func(req *PutRequest) interface{} {
-					return PutRequestToGRPCMessage(req)
-				},
-				caller: func(ctx context.Context, req interface{}) (interface{}, error) {
-					return c.Put(ctx, req.(*container.PutRequest))
-				},
-				responseConverter: func(resp interface{}) *PutResponse {
-					return PutResponseFromGRPCMessage(resp.(*container.PutResponse))
-				},
-			},
-			cGet: &getClient{
-				requestConverter: func(req *GetRequest) interface{} {
-					return GetRequestToGRPCMessage(req)
-				},
-				caller: func(ctx context.Context, req interface{}) (interface{}, error) {
-					return c.Get(ctx, req.(*container.GetRequest))
-				},
-				responseConverter: func(resp interface{}) *GetResponse {
-					return GetResponseFromGRPCMessage(resp.(*container.GetResponse))
-				},
-			},
-			cDel: &delClient{
-				requestConverter: func(req *DeleteRequest) interface{} {
-					return DeleteRequestToGRPCMessage(req)
-				},
-				caller: func(ctx context.Context, req interface{}) (interface{}, error) {
-					return c.Delete(ctx, req.(*container.DeleteRequest))
-				},
-				responseConverter: func(resp interface{}) *DeleteResponse {
-					return DeleteResponseFromGRPCMessage(resp.(*container.DeleteResponse))
-				},
-			},
-			cList: &listClient{
-				requestConverter: func(req *ListRequest) interface{} {
-					return ListRequestToGRPCMessage(req)
-				},
-				caller: func(ctx context.Context, req interface{}) (interface{}, error) {
-					return c.List(ctx, req.(*container.ListRequest))
-				},
-				responseConverter: func(resp interface{}) *ListResponse {
-					return ListResponseFromGRPCMessage(resp.(*container.ListResponse))
-				},
-			},
-			cSetEACL: &setEACLClient{
-				requestConverter: func(req *SetExtendedACLRequest) interface{} {
-					return SetExtendedACLRequestToGRPCMessage(req)
-				},
-				caller: func(ctx context.Context, req interface{}) (interface{}, error) {
-					return c.SetExtendedACL(ctx, req.(*container.SetExtendedACLRequest))
-				},
-				responseConverter: func(resp interface{}) *SetExtendedACLResponse {
-					return SetExtendedACLResponseFromGRPCMessage(resp.(*container.SetExtendedACLResponse))
-				},
-			},
-			cGetEACL: &getEACLClient{
-				requestConverter: func(req *GetExtendedACLRequest) interface{} {
-					return GetExtendedACLRequestToGRPCMessage(req)
-				},
-				caller: func(ctx context.Context, req interface{}) (interface{}, error) {
-					return c.GetExtendedACL(ctx, req.(*container.GetExtendedACLRequest))
-				},
-				responseConverter: func(resp interface{}) *GetExtendedACLResponse {
-					return GetExtendedACLResponseFromGRPCMessage(resp.(*container.GetExtendedACLResponse))
-				},
-			},
-			cAnnounce: &announceUsedSpaceClient{
-				requestConverter: func(req *AnnounceUsedSpaceRequest) interface{} {
-					return AnnounceUsedSpaceRequestToGRPCMessage(req)
-				},
-				caller: func(ctx context.Context, req interface{}) (interface{}, error) {
-					return c.AnnounceUsedSpace(ctx, req.(*container.AnnounceUsedSpaceRequest))
-				},
-				responseConverter: func(resp interface{}) *AnnounceUsedSpaceResponse {
-					return AnnounceUsedSpaceResponseFromGRPCMessage(resp.(*container.AnnounceUsedSpaceResponse))
-				},
-			},
-		}, nil
-	default:
-		err = client.ErrProtoUnsupported
-	}
-
-	return nil, errors.Wrapf(err, "could not create %s Session client", cfg.proto)
-}
-
-func newGRPCClient(cfg *cfg) (*container.Client, error) {
-	var err error
-
-	if cfg.gRPC.client == nil {
-		if cfg.gRPC.serviceClient == nil {
-			conn, err := client.NewGRPCClientConn(cfg.globalOpts...)
-			if err != nil {
-				return nil, errors.Wrap(err, "could not open gRPC client connection")
-			}
-
-			cfg.gRPC.serviceClient = container.NewContainerServiceClient(conn)
-		}
-
-		cfg.gRPC.client, err = container.NewClient(
-			cfg.gRPC.serviceClient,
-			append(
-				cfg.gRPC.callOpts,
-				container.WithCallOptions(cfg.gRPC.grpcCallOpts),
-			)...,
-		)
-	}
-
-	return cfg.gRPC.client, err
-}
-
-func WithGlobalOpts(v ...client.Option) Option {
-	return func(c *cfg) {
-		if len(v) > 0 {
-			c.globalOpts = v
-		}
-	}
-}
-
-func WithGRPCServiceClient(v container.ContainerServiceClient) Option {
-	return func(c *cfg) {
-		c.gRPC.serviceClient = v
-	}
-}
-
-func WithGRPCCallOpts(v []grpc.CallOption) Option {
-	return func(c *cfg) {
-		c.gRPC.grpcCallOpts = v
-	}
-}
-
-func WithGRPCClientOpts(v []container.Option) Option {
-	return func(c *cfg) {
-		c.gRPC.callOpts = v
-	}
-}
-
-func WithGRPCClient(v *container.Client) Option {
-	return func(c *cfg) {
-		c.gRPC.client = v
-	}
-}
diff --git a/v2/container/convert.go b/v2/container/convert.go
index 386eb8e..5ef3fed 100644
--- a/v2/container/convert.go
+++ b/v2/container/convert.go
@@ -1,1015 +1,1237 @@
 package container
 
 import (
+	"github.com/nspcc-dev/neofs-api-go/rpc/grpc"
+	"github.com/nspcc-dev/neofs-api-go/rpc/message"
 	"github.com/nspcc-dev/neofs-api-go/v2/acl"
+	aclGRPC "github.com/nspcc-dev/neofs-api-go/v2/acl/grpc"
 	container "github.com/nspcc-dev/neofs-api-go/v2/container/grpc"
 	"github.com/nspcc-dev/neofs-api-go/v2/netmap"
+	netmapGRPC "github.com/nspcc-dev/neofs-api-go/v2/netmap/grpc"
 	"github.com/nspcc-dev/neofs-api-go/v2/refs"
 	refsGRPC "github.com/nspcc-dev/neofs-api-go/v2/refs/grpc"
-	"github.com/nspcc-dev/neofs-api-go/v2/session"
 )
 
-func AttributeToGRPCMessage(a *Attribute) *container.Container_Attribute {
-	if a == nil {
-		return nil
+func (a *Attribute) ToGRPCMessage() grpc.Message {
+	var m *container.Container_Attribute
+
+	if a != nil {
+		m = new(container.Container_Attribute)
+
+		m.SetKey(a.key)
+		m.SetValue(a.val)
 	}
 
-	m := new(container.Container_Attribute)
-
-	m.SetKey(a.GetKey())
-	m.SetValue(a.GetValue())
-
 	return m
 }
 
-func AttributeFromGRPCMessage(m *container.Container_Attribute) *Attribute {
-	if m == nil {
-		return nil
+func (a *Attribute) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*container.Container_Attribute)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
 	}
 
-	a := new(Attribute)
+	a.key = v.GetKey()
+	a.val = v.GetValue()
 
-	a.SetKey(m.GetKey())
-	a.SetValue(m.GetValue())
-
-	return a
+	return nil
 }
 
-func ContainerToGRPCMessage(c *Container) *container.Container {
-	if c == nil {
-		return nil
+func AttributesToGRPC(xs []*Attribute) (res []*container.Container_Attribute) {
+	if xs != nil {
+		res = make([]*container.Container_Attribute, 0, len(xs))
+
+		for i := range xs {
+			res = append(res, xs[i].ToGRPCMessage().(*container.Container_Attribute))
+		}
 	}
 
-	m := new(container.Container)
+	return
+}
 
-	m.SetVersion(
-		refs.VersionToGRPCMessage(c.GetVersion()),
-	)
+func AttributesFromGRPC(xs []*container.Container_Attribute) (res []*Attribute, err error) {
+	if xs != nil {
+		res = make([]*Attribute, 0, len(xs))
 
-	m.SetOwnerId(
-		refs.OwnerIDToGRPCMessage(c.GetOwnerID()),
-	)
+		for i := range xs {
+			var x *Attribute
 
-	m.SetNonce(c.GetNonce())
+			if xs[i] != nil {
+				x = new(Attribute)
 
-	m.SetBasicAcl(c.GetBasicACL())
+				err = x.FromGRPCMessage(xs[i])
+				if err != nil {
+					return
+				}
+			}
 
-	m.SetPlacementPolicy(
-		netmap.PlacementPolicyToGRPCMessage(c.GetPlacementPolicy()),
-	)
-
-	attr := c.GetAttributes()
-	attrMsg := make([]*container.Container_Attribute, 0, len(attr))
-
-	for i := range attr {
-		attrMsg = append(attrMsg, AttributeToGRPCMessage(attr[i]))
+			res = append(res, x)
+		}
 	}
 
-	m.SetAttributes(attrMsg)
+	return
+}
+
+func (c *Container) ToGRPCMessage() grpc.Message {
+	var m *container.Container
+
+	if c != nil {
+		m = new(container.Container)
+
+		m.SetVersion(c.version.ToGRPCMessage().(*refsGRPC.Version))
+		m.SetOwnerId(c.ownerID.ToGRPCMessage().(*refsGRPC.OwnerID))
+		m.SetPlacementPolicy(c.policy.ToGRPCMessage().(*netmapGRPC.PlacementPolicy))
+		m.SetAttributes(AttributesToGRPC(c.attr))
+		m.SetBasicAcl(c.basicACL)
+		m.SetNonce(c.nonce)
+	}
 
 	return m
 }
 
-func ContainerFromGRPCMessage(m *container.Container) *Container {
-	if m == nil {
-		return nil
+func (c *Container) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*container.Container)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
 	}
 
-	c := new(Container)
+	var err error
 
-	c.SetVersion(
-		refs.VersionFromGRPCMessage(m.GetVersion()),
-	)
+	version := v.GetVersion()
+	if version == nil {
+		c.version = nil
+	} else {
+		if c.version == nil {
+			c.version = new(refs.Version)
+		}
 
-	c.SetOwnerID(
-		refs.OwnerIDFromGRPCMessage(m.GetOwnerId()),
-	)
-
-	c.SetNonce(m.GetNonce())
-
-	c.SetBasicACL(m.GetBasicAcl())
-
-	c.SetPlacementPolicy(
-		netmap.PlacementPolicyFromGRPCMessage(m.GetPlacementPolicy()),
-	)
-
-	attrMsg := m.GetAttributes()
-	attr := make([]*Attribute, 0, len(attrMsg))
-
-	for i := range attrMsg {
-		attr = append(attr, AttributeFromGRPCMessage(attrMsg[i]))
+		err = c.version.FromGRPCMessage(version)
+		if err != nil {
+			return err
+		}
 	}
 
-	c.SetAttributes(attr)
+	ownerID := v.GetOwnerId()
+	if ownerID == nil {
+		c.ownerID = nil
+	} else {
+		if c.ownerID == nil {
+			c.ownerID = new(refs.OwnerID)
+		}
 
-	return c
+		err = c.ownerID.FromGRPCMessage(ownerID)
+		if err != nil {
+			return err
+		}
+	}
+
+	policy := v.GetPlacementPolicy()
+	if policy == nil {
+		c.policy = nil
+	} else {
+		if c.policy == nil {
+			c.policy = new(netmap.PlacementPolicy)
+		}
+
+		err = c.policy.FromGRPCMessage(policy)
+		if err != nil {
+			return err
+		}
+	}
+
+	c.attr, err = AttributesFromGRPC(v.GetAttributes())
+	if err != nil {
+		return err
+	}
+
+	c.basicACL = v.GetBasicAcl()
+	c.nonce = v.GetNonce()
+
+	return nil
 }
 
-func PutRequestBodyToGRPCMessage(r *PutRequestBody) *container.PutRequest_Body {
-	if r == nil {
-		return nil
+func (r *PutRequestBody) ToGRPCMessage() grpc.Message {
+	var m *container.PutRequest_Body
+
+	if r != nil {
+		m = new(container.PutRequest_Body)
+
+		m.SetContainer(r.cnr.ToGRPCMessage().(*container.Container))
+		m.SetSignature(r.sig.ToGRPCMessage().(*refsGRPC.Signature))
 	}
 
-	m := new(container.PutRequest_Body)
-
-	m.SetContainer(
-		ContainerToGRPCMessage(r.GetContainer()),
-	)
-
-	m.SetSignature(
-		refs.SignatureToGRPCMessage(r.GetSignature()),
-	)
-
 	return m
 }
 
-func PutRequestBodyFromGRPCMessage(m *container.PutRequest_Body) *PutRequestBody {
-	if m == nil {
-		return nil
+func (r *PutRequestBody) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*container.PutRequest_Body)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
 	}
 
-	r := new(PutRequestBody)
+	var err error
 
-	r.SetContainer(
-		ContainerFromGRPCMessage(m.GetContainer()),
-	)
+	cnr := v.GetContainer()
+	if cnr == nil {
+		r.cnr = nil
+	} else {
+		if r.cnr == nil {
+			r.cnr = new(Container)
+		}
 
-	r.SetSignature(
-		refs.SignatureFromGRPCMessage(m.GetSignature()),
-	)
+		err = r.cnr.FromGRPCMessage(cnr)
+		if err != nil {
+			return err
+		}
+	}
 
-	return r
+	sig := v.GetSignature()
+	if sig == nil {
+		r.sig = nil
+	} else {
+		if r.sig == nil {
+			r.sig = new(refs.Signature)
+		}
+
+		err = r.sig.FromGRPCMessage(sig)
+	}
+
+	return err
 }
 
-func PutRequestToGRPCMessage(r *PutRequest) *container.PutRequest {
-	if r == nil {
-		return nil
+func (r *PutRequest) ToGRPCMessage() grpc.Message {
+	var m *container.PutRequest
+
+	if r != nil {
+		m = new(container.PutRequest)
+
+		m.SetBody(r.body.ToGRPCMessage().(*container.PutRequest_Body))
+		r.RequestHeaders.ToMessage(m)
 	}
 
-	m := new(container.PutRequest)
-
-	m.SetBody(
-		PutRequestBodyToGRPCMessage(r.GetBody()),
-	)
-
-	session.RequestHeadersToGRPC(r, m)
-
 	return m
 }
 
-func PutRequestFromGRPCMessage(m *container.PutRequest) *PutRequest {
-	if m == nil {
-		return nil
+func (r *PutRequest) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*container.PutRequest)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
 	}
 
-	r := new(PutRequest)
+	var err error
 
-	r.SetBody(
-		PutRequestBodyFromGRPCMessage(m.GetBody()),
-	)
+	body := v.GetBody()
+	if body == nil {
+		r.body = nil
+	} else {
+		if r.body == nil {
+			r.body = new(PutRequestBody)
+		}
 
-	session.RequestHeadersFromGRPC(m, r)
+		err = r.body.FromGRPCMessage(body)
+		if err != nil {
+			return err
+		}
+	}
 
-	return r
+	return r.RequestHeaders.FromMessage(v)
 }
 
-func PutResponseBodyToGRPCMessage(r *PutResponseBody) *container.PutResponse_Body {
-	if r == nil {
-		return nil
+func (r *PutResponseBody) ToGRPCMessage() grpc.Message {
+	var m *container.PutResponse_Body
+
+	if r != nil {
+		m = new(container.PutResponse_Body)
+
+		m.SetContainerId(r.cid.ToGRPCMessage().(*refsGRPC.ContainerID))
 	}
 
-	m := new(container.PutResponse_Body)
-
-	m.SetContainerId(
-		refs.ContainerIDToGRPCMessage(r.GetContainerID()),
-	)
-
 	return m
 }
 
-func PutResponseBodyFromGRPCMessage(m *container.PutResponse_Body) *PutResponseBody {
-	if m == nil {
-		return nil
+func (r *PutResponseBody) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*container.PutResponse_Body)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
 	}
 
-	r := new(PutResponseBody)
+	var err error
 
-	r.SetContainerID(
-		refs.ContainerIDFromGRPCMessage(m.GetContainerId()),
-	)
+	cid := v.GetContainerId()
+	if cid == nil {
+		r.cid = nil
+	} else {
+		if r.cid == nil {
+			r.cid = new(refs.ContainerID)
+		}
 
-	return r
+		err = r.cid.FromGRPCMessage(cid)
+	}
+
+	return err
 }
 
-func PutResponseToGRPCMessage(r *PutResponse) *container.PutResponse {
-	if r == nil {
-		return nil
+func (r *PutResponse) ToGRPCMessage() grpc.Message {
+	var m *container.PutResponse
+
+	if r != nil {
+		m = new(container.PutResponse)
+
+		m.SetBody(r.body.ToGRPCMessage().(*container.PutResponse_Body))
+		r.ResponseHeaders.ToMessage(m)
 	}
 
-	m := new(container.PutResponse)
-
-	m.SetBody(
-		PutResponseBodyToGRPCMessage(r.GetBody()),
-	)
-
-	session.ResponseHeadersToGRPC(r, m)
-
 	return m
 }
 
-func PutResponseFromGRPCMessage(m *container.PutResponse) *PutResponse {
-	if m == nil {
-		return nil
+func (r *PutResponse) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*container.PutResponse)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
 	}
 
-	r := new(PutResponse)
+	var err error
 
-	r.SetBody(
-		PutResponseBodyFromGRPCMessage(m.GetBody()),
-	)
+	body := v.GetBody()
+	if body == nil {
+		r.body = nil
+	} else {
+		if r.body == nil {
+			r.body = new(PutResponseBody)
+		}
 
-	session.ResponseHeadersFromGRPC(m, r)
+		err = r.body.FromGRPCMessage(body)
+		if err != nil {
+			return err
+		}
+	}
 
-	return r
+	return r.ResponseHeaders.FromMessage(v)
 }
 
-func GetRequestBodyToGRPCMessage(r *GetRequestBody) *container.GetRequest_Body {
-	if r == nil {
-		return nil
+func (r *GetRequestBody) ToGRPCMessage() grpc.Message {
+	var m *container.GetRequest_Body
+
+	if r != nil {
+		m = new(container.GetRequest_Body)
+
+		m.SetContainerId(r.cid.ToGRPCMessage().(*refsGRPC.ContainerID))
 	}
 
-	m := new(container.GetRequest_Body)
-
-	m.SetContainerId(
-		refs.ContainerIDToGRPCMessage(r.GetContainerID()),
-	)
-
 	return m
 }
 
-func GetRequestBodyFromGRPCMessage(m *container.GetRequest_Body) *GetRequestBody {
-	if m == nil {
-		return nil
+func (r *GetRequestBody) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*container.GetRequest_Body)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
 	}
 
-	r := new(GetRequestBody)
+	var err error
 
-	r.SetContainerID(
-		refs.ContainerIDFromGRPCMessage(m.GetContainerId()),
-	)
+	cid := v.GetContainerId()
+	if cid == nil {
+		r.cid = nil
+	} else {
+		if r.cid == nil {
+			r.cid = new(refs.ContainerID)
+		}
 
-	return r
+		err = r.cid.FromGRPCMessage(cid)
+	}
+
+	return err
 }
 
-func GetRequestToGRPCMessage(r *GetRequest) *container.GetRequest {
-	if r == nil {
-		return nil
+func (r *GetRequest) ToGRPCMessage() grpc.Message {
+	var m *container.GetRequest
+
+	if r != nil {
+		m = new(container.GetRequest)
+
+		m.SetBody(r.body.ToGRPCMessage().(*container.GetRequest_Body))
+		r.RequestHeaders.ToMessage(m)
 	}
 
-	m := new(container.GetRequest)
-
-	m.SetBody(
-		GetRequestBodyToGRPCMessage(r.GetBody()),
-	)
-
-	session.RequestHeadersToGRPC(r, m)
-
 	return m
 }
 
-func GetRequestFromGRPCMessage(m *container.GetRequest) *GetRequest {
-	if m == nil {
-		return nil
+func (r *GetRequest) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*container.GetRequest)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
 	}
 
-	r := new(GetRequest)
+	var err error
 
-	r.SetBody(
-		GetRequestBodyFromGRPCMessage(m.GetBody()),
-	)
+	body := v.GetBody()
+	if body == nil {
+		r.body = nil
+	} else {
+		if r.body == nil {
+			r.body = new(GetRequestBody)
+		}
 
-	session.RequestHeadersFromGRPC(m, r)
+		err = r.body.FromGRPCMessage(body)
+		if err != nil {
+			return err
+		}
+	}
 
-	return r
+	return r.RequestHeaders.FromMessage(v)
 }
 
-func GetResponseBodyToGRPCMessage(r *GetResponseBody) *container.GetResponse_Body {
-	if r == nil {
-		return nil
+func (r *GetResponseBody) ToGRPCMessage() grpc.Message {
+	var m *container.GetResponse_Body
+
+	if r != nil {
+		m = new(container.GetResponse_Body)
+
+		m.SetContainer(r.cnr.ToGRPCMessage().(*container.Container))
 	}
 
-	m := new(container.GetResponse_Body)
-
-	m.SetContainer(
-		ContainerToGRPCMessage(r.GetContainer()),
-	)
-
 	return m
 }
 
-func GetResponseBodyFromGRPCMessage(m *container.GetResponse_Body) *GetResponseBody {
-	if m == nil {
-		return nil
+func (r *GetResponseBody) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*container.GetResponse_Body)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
 	}
 
-	r := new(GetResponseBody)
+	var err error
 
-	r.SetContainer(
-		ContainerFromGRPCMessage(m.GetContainer()),
-	)
+	cnr := v.GetContainer()
+	if cnr == nil {
+		r.cnr = nil
+	} else {
+		if r.cnr == nil {
+			r.cnr = new(Container)
+		}
 
-	return r
+		err = r.cnr.FromGRPCMessage(cnr)
+	}
+
+	return err
 }
 
-func GetResponseToGRPCMessage(r *GetResponse) *container.GetResponse {
-	if r == nil {
-		return nil
+func (r *GetResponse) ToGRPCMessage() grpc.Message {
+	var m *container.GetResponse
+
+	if r != nil {
+		m = new(container.GetResponse)
+
+		m.SetBody(r.body.ToGRPCMessage().(*container.GetResponse_Body))
+		r.ResponseHeaders.ToMessage(m)
 	}
 
-	m := new(container.GetResponse)
-
-	m.SetBody(
-		GetResponseBodyToGRPCMessage(r.GetBody()),
-	)
-
-	session.ResponseHeadersToGRPC(r, m)
-
 	return m
 }
 
-func GetResponseFromGRPCMessage(m *container.GetResponse) *GetResponse {
-	if m == nil {
-		return nil
+func (r *GetResponse) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*container.GetResponse)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
 	}
 
-	r := new(GetResponse)
+	var err error
 
-	r.SetBody(
-		GetResponseBodyFromGRPCMessage(m.GetBody()),
-	)
+	body := v.GetBody()
+	if body == nil {
+		r.body = nil
+	} else {
+		if r.body == nil {
+			r.body = new(GetResponseBody)
+		}
 
-	session.ResponseHeadersFromGRPC(m, r)
+		err = r.body.FromGRPCMessage(body)
+		if err != nil {
+			return err
+		}
+	}
 
-	return r
+	return r.ResponseHeaders.FromMessage(v)
 }
 
-func DeleteRequestBodyToGRPCMessage(r *DeleteRequestBody) *container.DeleteRequest_Body {
-	if r == nil {
-		return nil
+func (r *DeleteRequestBody) ToGRPCMessage() grpc.Message {
+	var m *container.DeleteRequest_Body
+
+	if r != nil {
+		m = new(container.DeleteRequest_Body)
+
+		m.SetContainerId(r.cid.ToGRPCMessage().(*refsGRPC.ContainerID))
+		m.SetSignature(r.sig.ToGRPCMessage().(*refsGRPC.Signature))
 	}
 
-	m := new(container.DeleteRequest_Body)
-
-	m.SetContainerId(
-		refs.ContainerIDToGRPCMessage(r.GetContainerID()),
-	)
-
-	m.SetSignature(
-		refs.SignatureToGRPCMessage(r.GetSignature()),
-	)
-
 	return m
 }
 
-func DeleteRequestBodyFromGRPCMessage(m *container.DeleteRequest_Body) *DeleteRequestBody {
-	if m == nil {
-		return nil
+func (r *DeleteRequestBody) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*container.DeleteRequest_Body)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
 	}
 
-	r := new(DeleteRequestBody)
+	var err error
 
-	r.SetContainerID(
-		refs.ContainerIDFromGRPCMessage(m.GetContainerId()),
-	)
+	cid := v.GetContainerId()
+	if cid == nil {
+		r.cid = nil
+	} else {
+		if r.cid == nil {
+			r.cid = new(refs.ContainerID)
+		}
 
-	r.SetSignature(
-		refs.SignatureFromGRPCMessage(m.GetSignature()),
-	)
+		err = r.cid.FromGRPCMessage(cid)
+		if err != nil {
+			return err
+		}
+	}
 
-	return r
+	sig := v.GetSignature()
+	if sig == nil {
+		r.sig = nil
+	} else {
+		if r.sig == nil {
+			r.sig = new(refs.Signature)
+		}
+
+		err = r.sig.FromGRPCMessage(sig)
+	}
+
+	return err
 }
 
-func DeleteRequestToGRPCMessage(r *DeleteRequest) *container.DeleteRequest {
-	if r == nil {
-		return nil
+func (r *DeleteRequest) ToGRPCMessage() grpc.Message {
+	var m *container.DeleteRequest
+
+	if r != nil {
+		m = new(container.DeleteRequest)
+
+		m.SetBody(r.body.ToGRPCMessage().(*container.DeleteRequest_Body))
+		r.RequestHeaders.ToMessage(m)
 	}
 
-	m := new(container.DeleteRequest)
-
-	m.SetBody(
-		DeleteRequestBodyToGRPCMessage(r.GetBody()),
-	)
-
-	session.RequestHeadersToGRPC(r, m)
-
 	return m
 }
 
-func DeleteRequestFromGRPCMessage(m *container.DeleteRequest) *DeleteRequest {
-	if m == nil {
-		return nil
+func (r *DeleteRequest) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*container.DeleteRequest)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
 	}
 
-	r := new(DeleteRequest)
+	var err error
 
-	r.SetBody(
-		DeleteRequestBodyFromGRPCMessage(m.GetBody()),
-	)
+	body := v.GetBody()
+	if body == nil {
+		r.body = nil
+	} else {
+		if r.body == nil {
+			r.body = new(DeleteRequestBody)
+		}
 
-	session.RequestHeadersFromGRPC(m, r)
+		err = r.body.FromGRPCMessage(body)
+		if err != nil {
+			return err
+		}
+	}
 
-	return r
+	return r.RequestHeaders.FromMessage(v)
 }
 
-func DeleteResponseBodyToGRPCMessage(r *DeleteResponseBody) *container.DeleteResponse_Body {
-	if r == nil {
-		return nil
-	}
+func (r *DeleteResponseBody) ToGRPCMessage() grpc.Message {
+	var m *container.DeleteResponse_Body
 
-	m := new(container.DeleteResponse_Body)
+	if r != nil {
+		m = new(container.DeleteResponse_Body)
+	}
 
 	return m
 }
 
-func DeleteResponseBodyFromGRPCMessage(m *container.DeleteResponse_Body) *DeleteResponseBody {
-	if m == nil {
-		return nil
+func (r *DeleteResponseBody) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*container.DeleteResponse_Body)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
 	}
 
-	r := new(DeleteResponseBody)
-
-	return r
+	return nil
 }
 
-func DeleteResponseToGRPCMessage(r *DeleteResponse) *container.DeleteResponse {
-	if r == nil {
-		return nil
+func (r *DeleteResponse) ToGRPCMessage() grpc.Message {
+	var m *container.DeleteResponse
+
+	if r != nil {
+		m = new(container.DeleteResponse)
+
+		m.SetBody(r.body.ToGRPCMessage().(*container.DeleteResponse_Body))
+		r.ResponseHeaders.ToMessage(m)
 	}
 
-	m := new(container.DeleteResponse)
-
-	m.SetBody(
-		DeleteResponseBodyToGRPCMessage(r.GetBody()),
-	)
-
-	session.ResponseHeadersToGRPC(r, m)
-
 	return m
 }
 
-func DeleteResponseFromGRPCMessage(m *container.DeleteResponse) *DeleteResponse {
-	if m == nil {
-		return nil
+func (r *DeleteResponse) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*container.DeleteResponse)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
 	}
 
-	r := new(DeleteResponse)
+	var err error
 
-	r.SetBody(
-		DeleteResponseBodyFromGRPCMessage(m.GetBody()),
-	)
+	body := v.GetBody()
+	if body == nil {
+		r.body = nil
+	} else {
+		if r.body == nil {
+			r.body = new(DeleteResponseBody)
+		}
 
-	session.ResponseHeadersFromGRPC(m, r)
+		err = r.body.FromGRPCMessage(body)
+		if err != nil {
+			return err
+		}
+	}
 
-	return r
+	return r.ResponseHeaders.FromMessage(v)
 }
 
-func ListRequestBodyToGRPCMessage(r *ListRequestBody) *container.ListRequest_Body {
-	if r == nil {
-		return nil
+func (r *ListRequestBody) ToGRPCMessage() grpc.Message {
+	var m *container.ListRequest_Body
+
+	if r != nil {
+		m = new(container.ListRequest_Body)
+
+		m.SetOwnerId(r.ownerID.ToGRPCMessage().(*refsGRPC.OwnerID))
 	}
 
-	m := new(container.ListRequest_Body)
-
-	m.SetOwnerId(
-		refs.OwnerIDToGRPCMessage(r.GetOwnerID()),
-	)
-
 	return m
 }
 
-func ListRequestBodyFromGRPCMessage(m *container.ListRequest_Body) *ListRequestBody {
-	if m == nil {
-		return nil
+func (r *ListRequestBody) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*container.ListRequest_Body)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
 	}
 
-	r := new(ListRequestBody)
+	var err error
 
-	r.SetOwnerID(
-		refs.OwnerIDFromGRPCMessage(m.GetOwnerId()),
-	)
+	ownerID := v.GetOwnerId()
+	if ownerID == nil {
+		r.ownerID = nil
+	} else {
+		if r.ownerID == nil {
+			r.ownerID = new(refs.OwnerID)
+		}
 
-	return r
+		err = r.ownerID.FromGRPCMessage(ownerID)
+	}
+
+	return err
 }
 
-func ListRequestToGRPCMessage(r *ListRequest) *container.ListRequest {
-	if r == nil {
-		return nil
+func (r *ListRequest) ToGRPCMessage() grpc.Message {
+	var m *container.ListRequest
+
+	if r != nil {
+		m = new(container.ListRequest)
+
+		m.SetBody(r.body.ToGRPCMessage().(*container.ListRequest_Body))
+		r.RequestHeaders.ToMessage(m)
 	}
 
-	m := new(container.ListRequest)
-
-	m.SetBody(
-		ListRequestBodyToGRPCMessage(r.GetBody()),
-	)
-
-	session.RequestHeadersToGRPC(r, m)
-
 	return m
 }
 
-func ListRequestFromGRPCMessage(m *container.ListRequest) *ListRequest {
-	if m == nil {
-		return nil
+func (r *ListRequest) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*container.ListRequest)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
 	}
 
-	r := new(ListRequest)
+	var err error
 
-	r.SetBody(
-		ListRequestBodyFromGRPCMessage(m.GetBody()),
-	)
+	body := v.GetBody()
+	if body == nil {
+		r.body = nil
+	} else {
+		if r.body == nil {
+			r.body = new(ListRequestBody)
+		}
 
-	session.RequestHeadersFromGRPC(m, r)
+		err = r.body.FromGRPCMessage(body)
+		if err != nil {
+			return err
+		}
+	}
 
-	return r
+	return r.RequestHeaders.FromMessage(v)
 }
 
-func ListResponseBodyToGRPCMessage(r *ListResponseBody) *container.ListResponse_Body {
-	if r == nil {
-		return nil
+func (r *ListResponseBody) ToGRPCMessage() grpc.Message {
+	var m *container.ListResponse_Body
+
+	if r != nil {
+		m = new(container.ListResponse_Body)
+
+		m.SetContainerIds(refs.ContainerIDsToGRPCMessage(r.cidList))
 	}
 
-	m := new(container.ListResponse_Body)
-
-	cids := r.GetContainerIDs()
-	cidMsg := make([]*refsGRPC.ContainerID, 0, len(cids))
-
-	for i := range cids {
-		cidMsg = append(cidMsg, refs.ContainerIDToGRPCMessage(cids[i]))
-	}
-
-	m.SetContainerIds(cidMsg)
-
 	return m
 }
 
-func ListResponseBodyFromGRPCMessage(m *container.ListResponse_Body) *ListResponseBody {
-	if m == nil {
-		return nil
+func (r *ListResponseBody) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*container.ListResponse_Body)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
 	}
 
-	r := new(ListResponseBody)
+	var err error
 
-	cidMsg := m.GetContainerIds()
-	cids := make([]*refs.ContainerID, 0, len(cidMsg))
+	r.cidList, err = refs.ContainerIDsFromGRPCMessage(v.GetContainerIds())
 
-	for i := range cidMsg {
-		cids = append(cids, refs.ContainerIDFromGRPCMessage(cidMsg[i]))
-	}
-
-	r.SetContainerIDs(cids)
-
-	return r
+	return err
 }
 
-func ListResponseToGRPCMessage(r *ListResponse) *container.ListResponse {
-	if r == nil {
-		return nil
+func (r *ListResponse) ToGRPCMessage() grpc.Message {
+	var m *container.ListResponse
+
+	if r != nil {
+		m = new(container.ListResponse)
+
+		m.SetBody(r.body.ToGRPCMessage().(*container.ListResponse_Body))
+		r.ResponseHeaders.ToMessage(m)
 	}
 
-	m := new(container.ListResponse)
-
-	m.SetBody(
-		ListResponseBodyToGRPCMessage(r.GetBody()),
-	)
-
-	session.ResponseHeadersToGRPC(r, m)
-
 	return m
 }
 
-func ListResponseFromGRPCMessage(m *container.ListResponse) *ListResponse {
-	if m == nil {
-		return nil
+func (r *ListResponse) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*container.ListResponse)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
 	}
 
-	r := new(ListResponse)
+	var err error
 
-	r.SetBody(
-		ListResponseBodyFromGRPCMessage(m.GetBody()),
-	)
+	body := v.GetBody()
+	if body == nil {
+		r.body = nil
+	} else {
+		if r.body == nil {
+			r.body = new(ListResponseBody)
+		}
 
-	session.ResponseHeadersFromGRPC(m, r)
+		err = r.body.FromGRPCMessage(body)
+		if err != nil {
+			return err
+		}
+	}
 
-	return r
+	return r.ResponseHeaders.FromMessage(v)
 }
 
-func SetExtendedACLRequestBodyToGRPCMessage(r *SetExtendedACLRequestBody) *container.SetExtendedACLRequest_Body {
-	if r == nil {
-		return nil
+func (r *SetExtendedACLRequestBody) ToGRPCMessage() grpc.Message {
+	var m *container.SetExtendedACLRequest_Body
+
+	if r != nil {
+		m = new(container.SetExtendedACLRequest_Body)
+
+		m.SetEacl(r.eacl.ToGRPCMessage().(*aclGRPC.EACLTable))
+		m.SetSignature(r.sig.ToGRPCMessage().(*refsGRPC.Signature))
 	}
 
-	m := new(container.SetExtendedACLRequest_Body)
-
-	m.SetEacl(
-		acl.TableToGRPCMessage(r.GetEACL()),
-	)
-
-	m.SetSignature(
-		refs.SignatureToGRPCMessage(r.GetSignature()))
-
 	return m
 }
 
-func SetExtendedACLRequestBodyFromGRPCMessage(m *container.SetExtendedACLRequest_Body) *SetExtendedACLRequestBody {
-	if m == nil {
-		return nil
+func (r *SetExtendedACLRequestBody) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*container.SetExtendedACLRequest_Body)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
 	}
 
-	r := new(SetExtendedACLRequestBody)
+	var err error
 
-	r.SetEACL(
-		acl.TableFromGRPCMessage(m.GetEacl()),
-	)
+	eacl := v.GetEacl()
+	if eacl == nil {
+		r.eacl = nil
+	} else {
+		if r.eacl == nil {
+			r.eacl = new(acl.Table)
+		}
 
-	r.SetSignature(
-		refs.SignatureFromGRPCMessage(m.GetSignature()),
-	)
+		err = r.eacl.FromGRPCMessage(eacl)
+		if err != nil {
+			return err
+		}
+	}
 
-	return r
+	sig := v.GetSignature()
+	if sig == nil {
+		r.sig = nil
+	} else {
+		if r.sig == nil {
+			r.sig = new(refs.Signature)
+		}
+
+		err = r.sig.FromGRPCMessage(sig)
+	}
+
+	return err
 }
 
-func SetExtendedACLRequestToGRPCMessage(r *SetExtendedACLRequest) *container.SetExtendedACLRequest {
-	if r == nil {
-		return nil
+func (r *SetExtendedACLRequest) ToGRPCMessage() grpc.Message {
+	var m *container.SetExtendedACLRequest
+
+	if r != nil {
+		m = new(container.SetExtendedACLRequest)
+
+		m.SetBody(r.body.ToGRPCMessage().(*container.SetExtendedACLRequest_Body))
+		r.RequestHeaders.ToMessage(m)
 	}
 
-	m := new(container.SetExtendedACLRequest)
-
-	m.SetBody(
-		SetExtendedACLRequestBodyToGRPCMessage(r.GetBody()),
-	)
-
-	session.RequestHeadersToGRPC(r, m)
-
 	return m
 }
 
-func SetExtendedACLRequestFromGRPCMessage(m *container.SetExtendedACLRequest) *SetExtendedACLRequest {
-	if m == nil {
-		return nil
+func (r *SetExtendedACLRequest) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*container.SetExtendedACLRequest)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
 	}
 
-	r := new(SetExtendedACLRequest)
+	var err error
 
-	r.SetBody(
-		SetExtendedACLRequestBodyFromGRPCMessage(m.GetBody()),
-	)
+	body := v.GetBody()
+	if body == nil {
+		r.body = nil
+	} else {
+		if r.body == nil {
+			r.body = new(SetExtendedACLRequestBody)
+		}
 
-	session.RequestHeadersFromGRPC(m, r)
+		err = r.body.FromGRPCMessage(body)
+		if err != nil {
+			return err
+		}
+	}
 
-	return r
+	return r.RequestHeaders.FromMessage(v)
 }
 
-func SetExtendedACLResponseBodyToGRPCMessage(r *SetExtendedACLResponseBody) *container.SetExtendedACLResponse_Body {
-	if r == nil {
-		return nil
-	}
+func (r *SetExtendedACLResponseBody) ToGRPCMessage() grpc.Message {
+	var m *container.SetExtendedACLResponse_Body
 
-	m := new(container.SetExtendedACLResponse_Body)
+	if r != nil {
+		m = new(container.SetExtendedACLResponse_Body)
+	}
 
 	return m
 }
 
-func SetExtendedACLResponseBodyFromGRPCMessage(m *container.SetExtendedACLResponse_Body) *SetExtendedACLResponseBody {
-	if m == nil {
-		return nil
+func (r *SetExtendedACLResponseBody) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*container.SetExtendedACLResponse_Body)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
 	}
 
-	r := new(SetExtendedACLResponseBody)
-
-	return r
+	return nil
 }
 
-func SetExtendedACLResponseToGRPCMessage(r *SetExtendedACLResponse) *container.SetExtendedACLResponse {
-	if r == nil {
-		return nil
+func (r *SetExtendedACLResponse) ToGRPCMessage() grpc.Message {
+	var m *container.SetExtendedACLResponse
+
+	if r != nil {
+		m = new(container.SetExtendedACLResponse)
+
+		m.SetBody(r.body.ToGRPCMessage().(*container.SetExtendedACLResponse_Body))
+		r.ResponseHeaders.ToMessage(m)
 	}
 
-	m := new(container.SetExtendedACLResponse)
-
-	m.SetBody(
-		SetExtendedACLResponseBodyToGRPCMessage(r.GetBody()),
-	)
-
-	session.ResponseHeadersToGRPC(r, m)
-
 	return m
 }
 
-func SetExtendedACLResponseFromGRPCMessage(m *container.SetExtendedACLResponse) *SetExtendedACLResponse {
-	if m == nil {
-		return nil
+func (r *SetExtendedACLResponse) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*container.SetExtendedACLResponse)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
 	}
 
-	r := new(SetExtendedACLResponse)
+	var err error
 
-	r.SetBody(
-		SetExtendedACLResponseBodyFromGRPCMessage(m.GetBody()),
-	)
+	body := v.GetBody()
+	if body == nil {
+		r.body = nil
+	} else {
+		if r.body == nil {
+			r.body = new(SetExtendedACLResponseBody)
+		}
 
-	session.ResponseHeadersFromGRPC(m, r)
+		err = r.body.FromGRPCMessage(body)
+		if err != nil {
+			return err
+		}
+	}
 
-	return r
+	return r.ResponseHeaders.FromMessage(v)
 }
 
-func GetExtendedACLRequestBodyToGRPCMessage(r *GetExtendedACLRequestBody) *container.GetExtendedACLRequest_Body {
-	if r == nil {
-		return nil
+func (r *GetExtendedACLRequestBody) ToGRPCMessage() grpc.Message {
+	var m *container.GetExtendedACLRequest_Body
+
+	if r != nil {
+		m = new(container.GetExtendedACLRequest_Body)
+
+		m.SetContainerId(r.cid.ToGRPCMessage().(*refsGRPC.ContainerID))
 	}
 
-	m := new(container.GetExtendedACLRequest_Body)
-
-	m.SetContainerId(
-		refs.ContainerIDToGRPCMessage(r.GetContainerID()),
-	)
-
 	return m
 }
 
-func GetExtendedACLRequestBodyFromGRPCMessage(m *container.GetExtendedACLRequest_Body) *GetExtendedACLRequestBody {
-	if m == nil {
-		return nil
+func (r *GetExtendedACLRequestBody) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*container.GetExtendedACLRequest_Body)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
 	}
 
-	r := new(GetExtendedACLRequestBody)
+	var err error
 
-	r.SetContainerID(
-		refs.ContainerIDFromGRPCMessage(m.GetContainerId()),
-	)
+	cid := v.GetContainerId()
+	if cid == nil {
+		r.cid = nil
+	} else {
+		if r.cid == nil {
+			r.cid = new(refs.ContainerID)
+		}
 
-	return r
+		err = r.cid.FromGRPCMessage(cid)
+	}
+
+	return err
 }
 
-func GetExtendedACLRequestToGRPCMessage(r *GetExtendedACLRequest) *container.GetExtendedACLRequest {
-	if r == nil {
-		return nil
+func (r *GetExtendedACLRequest) ToGRPCMessage() grpc.Message {
+	var m *container.GetExtendedACLRequest
+
+	if r != nil {
+		m = new(container.GetExtendedACLRequest)
+
+		m.SetBody(r.body.ToGRPCMessage().(*container.GetExtendedACLRequest_Body))
+		r.RequestHeaders.ToMessage(m)
 	}
 
-	m := new(container.GetExtendedACLRequest)
-
-	m.SetBody(
-		GetExtendedACLRequestBodyToGRPCMessage(r.GetBody()),
-	)
-
-	session.RequestHeadersToGRPC(r, m)
-
 	return m
 }
 
-func GetExtendedACLRequestFromGRPCMessage(m *container.GetExtendedACLRequest) *GetExtendedACLRequest {
-	if m == nil {
-		return nil
+func (r *GetExtendedACLRequest) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*container.GetExtendedACLRequest)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
 	}
 
-	r := new(GetExtendedACLRequest)
+	var err error
 
-	r.SetBody(
-		GetExtendedACLRequestBodyFromGRPCMessage(m.GetBody()),
-	)
+	body := v.GetBody()
+	if body == nil {
+		r.body = nil
+	} else {
+		if r.body == nil {
+			r.body = new(GetExtendedACLRequestBody)
+		}
 
-	session.RequestHeadersFromGRPC(m, r)
+		err = r.body.FromGRPCMessage(body)
+		if err != nil {
+			return err
+		}
+	}
 
-	return r
+	return r.RequestHeaders.FromMessage(v)
 }
 
-func GetExtendedACLResponseBodyToGRPCMessage(r *GetExtendedACLResponseBody) *container.GetExtendedACLResponse_Body {
-	if r == nil {
-		return nil
+func (r *GetExtendedACLResponseBody) ToGRPCMessage() grpc.Message {
+	var m *container.GetExtendedACLResponse_Body
+
+	if r != nil {
+		m = new(container.GetExtendedACLResponse_Body)
+
+		m.SetEacl(r.eacl.ToGRPCMessage().(*aclGRPC.EACLTable))
+		m.SetSignature(r.sig.ToGRPCMessage().(*refsGRPC.Signature))
 	}
 
-	m := new(container.GetExtendedACLResponse_Body)
-
-	m.SetEacl(
-		acl.TableToGRPCMessage(r.GetEACL()),
-	)
-
-	m.SetSignature(
-		refs.SignatureToGRPCMessage(r.GetSignature()),
-	)
-
 	return m
 }
 
-func GetExtendedACLResponseBodyFromGRPCMessage(m *container.GetExtendedACLResponse_Body) *GetExtendedACLResponseBody {
-	if m == nil {
-		return nil
+func (r *GetExtendedACLResponseBody) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*container.GetExtendedACLResponse_Body)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
 	}
 
-	r := new(GetExtendedACLResponseBody)
+	var err error
 
-	r.SetEACL(
-		acl.TableFromGRPCMessage(m.GetEacl()),
-	)
+	eacl := v.GetEacl()
+	if eacl == nil {
+		r.eacl = nil
+	} else {
+		if r.eacl == nil {
+			r.eacl = new(acl.Table)
+		}
 
-	r.SetSignature(
-		refs.SignatureFromGRPCMessage(m.GetSignature()),
-	)
+		err = r.eacl.FromGRPCMessage(eacl)
+		if err != nil {
+			return err
+		}
+	}
 
-	return r
+	sig := v.GetSignature()
+	if sig == nil {
+		r.sig = nil
+	} else {
+		if r.sig == nil {
+			r.sig = new(refs.Signature)
+		}
+
+		err = r.sig.FromGRPCMessage(sig)
+	}
+
+	return err
 }
 
-func GetExtendedACLResponseToGRPCMessage(r *GetExtendedACLResponse) *container.GetExtendedACLResponse {
-	if r == nil {
-		return nil
+func (r *GetExtendedACLResponse) ToGRPCMessage() grpc.Message {
+	var m *container.GetExtendedACLResponse
+
+	if r != nil {
+		m = new(container.GetExtendedACLResponse)
+
+		m.SetBody(r.body.ToGRPCMessage().(*container.GetExtendedACLResponse_Body))
+		r.ResponseHeaders.ToMessage(m)
 	}
 
-	m := new(container.GetExtendedACLResponse)
-
-	m.SetBody(
-		GetExtendedACLResponseBodyToGRPCMessage(r.GetBody()),
-	)
-
-	session.ResponseHeadersToGRPC(r, m)
-
 	return m
 }
 
-func GetExtendedACLResponseFromGRPCMessage(m *container.GetExtendedACLResponse) *GetExtendedACLResponse {
-	if m == nil {
-		return nil
+func (r *GetExtendedACLResponse) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*container.GetExtendedACLResponse)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
 	}
 
-	r := new(GetExtendedACLResponse)
+	var err error
 
-	r.SetBody(
-		GetExtendedACLResponseBodyFromGRPCMessage(m.GetBody()),
-	)
+	body := v.GetBody()
+	if body == nil {
+		r.body = nil
+	} else {
+		if r.body == nil {
+			r.body = new(GetExtendedACLResponseBody)
+		}
 
-	session.ResponseHeadersFromGRPC(m, r)
+		err = r.body.FromGRPCMessage(body)
+		if err != nil {
+			return err
+		}
+	}
 
-	return r
+	return r.ResponseHeaders.FromMessage(v)
 }
 
-func UsedSpaceAnnouncementToGRPCMessage(a *UsedSpaceAnnouncement) *container.AnnounceUsedSpaceRequest_Body_Announcement {
-	if a == nil {
-		return nil
+func (a *UsedSpaceAnnouncement) ToGRPCMessage() grpc.Message {
+	var m *container.AnnounceUsedSpaceRequest_Body_Announcement
+
+	if a != nil {
+		m = new(container.AnnounceUsedSpaceRequest_Body_Announcement)
+
+		m.SetContainerId(a.cid.ToGRPCMessage().(*refsGRPC.ContainerID))
+		m.SetEpoch(a.epoch)
+		m.SetUsedSpace(a.usedSpace)
 	}
 
-	m := new(container.AnnounceUsedSpaceRequest_Body_Announcement)
-
-	m.SetEpoch(a.GetEpoch())
-
-	m.SetContainerId(
-		refs.ContainerIDToGRPCMessage(a.GetContainerID()),
-	)
-
-	m.SetUsedSpace(a.GetUsedSpace())
-
 	return m
 }
 
-func UsedSpaceAnnouncementFromGRPCMessage(m *container.AnnounceUsedSpaceRequest_Body_Announcement) *UsedSpaceAnnouncement {
-	if m == nil {
-		return nil
+func (a *UsedSpaceAnnouncement) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*container.AnnounceUsedSpaceRequest_Body_Announcement)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
 	}
 
-	a := new(UsedSpaceAnnouncement)
+	var err error
 
-	a.SetEpoch(m.GetEpoch())
+	cid := v.GetContainerId()
+	if cid == nil {
+		a.cid = nil
+	} else {
+		if a.cid == nil {
+			a.cid = new(refs.ContainerID)
+		}
 
-	a.SetContainerID(
-		refs.ContainerIDFromGRPCMessage(m.GetContainerId()),
-	)
+		err = a.cid.FromGRPCMessage(cid)
+		if err != nil {
+			return err
+		}
+	}
 
-	a.SetUsedSpace(m.GetUsedSpace())
+	a.epoch = v.GetEpoch()
+	a.usedSpace = v.GetUsedSpace()
 
-	return a
+	return nil
 }
 
-func AnnounceUsedSpaceRequestBodyToGRPCMessage(r *AnnounceUsedSpaceRequestBody) *container.AnnounceUsedSpaceRequest_Body {
-	if r == nil {
-		return nil
+func UsedSpaceAnnouncementsToGRPCMessage(
+	ids []*UsedSpaceAnnouncement,
+) (res []*container.AnnounceUsedSpaceRequest_Body_Announcement) {
+	if ids != nil {
+		res = make([]*container.AnnounceUsedSpaceRequest_Body_Announcement, 0, len(ids))
+
+		for i := range ids {
+			res = append(res, ids[i].ToGRPCMessage().(*container.AnnounceUsedSpaceRequest_Body_Announcement))
+		}
 	}
 
-	m := new(container.AnnounceUsedSpaceRequest_Body)
+	return
+}
 
-	announcements := r.GetAnnouncements()
-	msgAnnouncements := make([]*container.AnnounceUsedSpaceRequest_Body_Announcement, 0, len(announcements))
+func UsedSpaceAnnouncementssFromGRPCMessage(
+	asV2 []*container.AnnounceUsedSpaceRequest_Body_Announcement,
+) (res []*UsedSpaceAnnouncement, err error) {
+	if asV2 != nil {
+		res = make([]*UsedSpaceAnnouncement, 0, len(asV2))
 
-	for i := range announcements {
-		msgAnnouncements = append(
-			msgAnnouncements,
-			UsedSpaceAnnouncementToGRPCMessage(announcements[i]),
-		)
+		for i := range asV2 {
+			var a *UsedSpaceAnnouncement
+
+			if asV2[i] != nil {
+				a = new(UsedSpaceAnnouncement)
+
+				err = a.FromGRPCMessage(asV2[i])
+				if err != nil {
+					return
+				}
+			}
+
+			res = append(res, a)
+		}
 	}
 
-	m.SetAnnouncements(msgAnnouncements)
+	return
+}
+
+func (r *AnnounceUsedSpaceRequestBody) ToGRPCMessage() grpc.Message {
+	var m *container.AnnounceUsedSpaceRequest_Body
+
+	if r != nil {
+		m = new(container.AnnounceUsedSpaceRequest_Body)
+
+		m.SetAnnouncements(UsedSpaceAnnouncementsToGRPCMessage(r.announcements))
+	}
 
 	return m
 }
 
-func AnnounceUsedSpaceRequestBodyFromGRPCMessage(m *container.AnnounceUsedSpaceRequest_Body) *AnnounceUsedSpaceRequestBody {
-	if m == nil {
-		return nil
+func (r *AnnounceUsedSpaceRequestBody) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*container.AnnounceUsedSpaceRequest_Body)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
 	}
 
-	r := new(AnnounceUsedSpaceRequestBody)
+	var err error
 
-	msgAnnouncements := m.GetAnnouncements()
-	announcements := make([]*UsedSpaceAnnouncement, 0, len(msgAnnouncements))
+	r.announcements, err = UsedSpaceAnnouncementssFromGRPCMessage(v.GetAnnouncements())
 
-	for i := range msgAnnouncements {
-		announcements = append(
-			announcements,
-			UsedSpaceAnnouncementFromGRPCMessage(msgAnnouncements[i]),
-		)
-	}
-
-	r.SetAnnouncements(announcements)
-
-	return r
+	return err
 }
 
-func AnnounceUsedSpaceRequestToGRPCMessage(r *AnnounceUsedSpaceRequest) *container.AnnounceUsedSpaceRequest {
-	if r == nil {
-		return nil
+func (r *AnnounceUsedSpaceRequest) ToGRPCMessage() grpc.Message {
+	var m *container.AnnounceUsedSpaceRequest
+
+	if r != nil {
+		m = new(container.AnnounceUsedSpaceRequest)
+
+		m.SetBody(r.body.ToGRPCMessage().(*container.AnnounceUsedSpaceRequest_Body))
+		r.RequestHeaders.ToMessage(m)
 	}
 
-	m := new(container.AnnounceUsedSpaceRequest)
-
-	m.SetBody(
-		AnnounceUsedSpaceRequestBodyToGRPCMessage(r.GetBody()),
-	)
-
-	session.RequestHeadersToGRPC(r, m)
-
 	return m
 }
 
-func AnnounceUsedSpaceRequestFromGRPCMessage(m *container.AnnounceUsedSpaceRequest) *AnnounceUsedSpaceRequest {
-	if m == nil {
-		return nil
+func (r *AnnounceUsedSpaceRequest) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*container.AnnounceUsedSpaceRequest)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
 	}
 
-	r := new(AnnounceUsedSpaceRequest)
+	var err error
 
-	r.SetBody(
-		AnnounceUsedSpaceRequestBodyFromGRPCMessage(m.GetBody()),
-	)
+	body := v.GetBody()
+	if body == nil {
+		r.body = nil
+	} else {
+		if r.body == nil {
+			r.body = new(AnnounceUsedSpaceRequestBody)
+		}
 
-	session.RequestHeadersFromGRPC(m, r)
+		err = r.body.FromGRPCMessage(body)
+		if err != nil {
+			return err
+		}
+	}
 
-	return r
+	return r.RequestHeaders.FromMessage(v)
 }
 
-func AnnounceUsedSpaceResponseBodyToGRPCMessage(r *AnnounceUsedSpaceResponseBody) *container.AnnounceUsedSpaceResponse_Body {
-	if r == nil {
-		return nil
-	}
+func (r *AnnounceUsedSpaceResponseBody) ToGRPCMessage() grpc.Message {
+	var m *container.AnnounceUsedSpaceResponse_Body
 
-	m := new(container.AnnounceUsedSpaceResponse_Body)
+	if r != nil {
+		m = new(container.AnnounceUsedSpaceResponse_Body)
+	}
 
 	return m
 }
 
-func AnnounceUsedSpaceResponseBodyFromGRPCMessage(m *container.AnnounceUsedSpaceResponse_Body) *AnnounceUsedSpaceResponseBody {
-	if m == nil {
-		return nil
+func (r *AnnounceUsedSpaceResponseBody) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*container.AnnounceUsedSpaceResponse_Body)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
 	}
 
-	r := new(AnnounceUsedSpaceResponseBody)
-
-	return r
+	return nil
 }
 
-func AnnounceUsedSpaceResponseToGRPCMessage(r *AnnounceUsedSpaceResponse) *container.AnnounceUsedSpaceResponse {
-	if r == nil {
-		return nil
+func (r *AnnounceUsedSpaceResponse) ToGRPCMessage() grpc.Message {
+	var m *container.AnnounceUsedSpaceResponse
+
+	if r != nil {
+		m = new(container.AnnounceUsedSpaceResponse)
+
+		m.SetBody(r.body.ToGRPCMessage().(*container.AnnounceUsedSpaceResponse_Body))
+		r.ResponseHeaders.ToMessage(m)
 	}
 
-	m := new(container.AnnounceUsedSpaceResponse)
-
-	m.SetBody(
-		AnnounceUsedSpaceResponseBodyToGRPCMessage(r.GetBody()),
-	)
-
-	session.ResponseHeadersToGRPC(r, m)
-
 	return m
 }
 
-func AnnounceUsedSpaceResponseFromGRPCMessage(m *container.AnnounceUsedSpaceResponse) *AnnounceUsedSpaceResponse {
-	if m == nil {
-		return nil
+func (r *AnnounceUsedSpaceResponse) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*container.AnnounceUsedSpaceResponse)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
 	}
 
-	r := new(AnnounceUsedSpaceResponse)
+	var err error
 
-	r.SetBody(
-		AnnounceUsedSpaceResponseBodyFromGRPCMessage(m.GetBody()),
-	)
+	body := v.GetBody()
+	if body == nil {
+		r.body = nil
+	} else {
+		if r.body == nil {
+			r.body = new(AnnounceUsedSpaceResponseBody)
+		}
 
-	session.ResponseHeadersFromGRPC(m, r)
+		err = r.body.FromGRPCMessage(body)
+		if err != nil {
+			return err
+		}
+	}
 
-	return r
+	return r.ResponseHeaders.FromMessage(v)
 }
diff --git a/v2/container/grpc/client.go b/v2/container/grpc/client.go
deleted file mode 100644
index 7e126d2..0000000
--- a/v2/container/grpc/client.go
+++ /dev/null
@@ -1,86 +0,0 @@
-package container
-
-import (
-	"context"
-
-	"github.com/pkg/errors"
-	"google.golang.org/grpc"
-)
-
-// Client wraps ContainerServiceClient
-// with pre-defined configurations.
-type Client struct {
-	*cfg
-
-	client ContainerServiceClient
-}
-
-// Option represents Client option.
-type Option func(*cfg)
-
-type cfg struct {
-	callOpts []grpc.CallOption
-}
-
-// ErrNilContainerServiceClient is returned by functions that expect
-// a non-nil ContainerServiceClient, but received nil.
-var ErrNilContainerServiceClient = errors.New("container gRPC client is nil")
-
-func defaultCfg() *cfg {
-	return new(cfg)
-}
-
-// NewClient creates, initializes and returns a new Client instance.
-//
-// Options are applied one by one in order.
-func NewClient(c ContainerServiceClient, opts ...Option) (*Client, error) {
-	if c == nil {
-		return nil, ErrNilContainerServiceClient
-	}
-
-	cfg := defaultCfg()
-	for i := range opts {
-		opts[i](cfg)
-	}
-
-	return &Client{
-		cfg:    cfg,
-		client: c,
-	}, nil
-}
-
-func (c *Client) Put(ctx context.Context, req *PutRequest) (*PutResponse, error) {
-	return c.client.Put(ctx, req, c.callOpts...)
-}
-
-func (c *Client) Get(ctx context.Context, req *GetRequest) (*GetResponse, error) {
-	return c.client.Get(ctx, req, c.callOpts...)
-}
-
-func (c *Client) Delete(ctx context.Context, req *DeleteRequest) (*DeleteResponse, error) {
-	return c.client.Delete(ctx, req, c.callOpts...)
-}
-
-func (c *Client) List(ctx context.Context, req *ListRequest) (*ListResponse, error) {
-	return c.client.List(ctx, req, c.callOpts...)
-}
-
-func (c *Client) SetExtendedACL(ctx context.Context, req *SetExtendedACLRequest) (*SetExtendedACLResponse, error) {
-	return c.client.SetExtendedACL(ctx, req, c.callOpts...)
-}
-
-func (c *Client) GetExtendedACL(ctx context.Context, req *GetExtendedACLRequest) (*GetExtendedACLResponse, error) {
-	return c.client.GetExtendedACL(ctx, req, c.callOpts...)
-}
-
-func (c *Client) AnnounceUsedSpace(ctx context.Context, req *AnnounceUsedSpaceRequest) (*AnnounceUsedSpaceResponse, error) {
-	return c.client.AnnounceUsedSpace(ctx, req, c.callOpts...)
-}
-
-// WithCallOptions returns Option that configures
-// Client to attach call options to each rpc call.
-func WithCallOptions(opts []grpc.CallOption) Option {
-	return func(c *cfg) {
-		c.callOpts = opts
-	}
-}
diff --git a/v2/container/json.go b/v2/container/json.go
index 7ce52da..c681540 100644
--- a/v2/container/json.go
+++ b/v2/container/json.go
@@ -1,46 +1,22 @@
 package container
 
 import (
+	"github.com/nspcc-dev/neofs-api-go/rpc/message"
 	container "github.com/nspcc-dev/neofs-api-go/v2/container/grpc"
-	"google.golang.org/protobuf/encoding/protojson"
 )
 
 func (a *Attribute) MarshalJSON() ([]byte, error) {
-	return protojson.MarshalOptions{
-		EmitUnpopulated: true,
-	}.Marshal(
-		AttributeToGRPCMessage(a),
-	)
+	return message.MarshalJSON(a)
 }
 
 func (a *Attribute) UnmarshalJSON(data []byte) error {
-	msg := new(container.Container_Attribute)
-
-	if err := protojson.Unmarshal(data, msg); err != nil {
-		return err
-	}
-
-	*a = *AttributeFromGRPCMessage(msg)
-
-	return nil
+	return message.UnmarshalJSON(a, data, new(container.Container_Attribute))
 }
 
 func (c *Container) MarshalJSON() ([]byte, error) {
-	return protojson.MarshalOptions{
-		EmitUnpopulated: true,
-	}.Marshal(
-		ContainerToGRPCMessage(c),
-	)
+	return message.MarshalJSON(c)
 }
 
 func (c *Container) UnmarshalJSON(data []byte) error {
-	msg := new(container.Container)
-
-	if err := protojson.Unmarshal(data, msg); err != nil {
-		return err
-	}
-
-	*c = *ContainerFromGRPCMessage(msg)
-
-	return nil
+	return message.UnmarshalJSON(c, data, new(container.Container))
 }
diff --git a/v2/container/json_test.go b/v2/container/json_test.go
deleted file mode 100644
index 40228a2..0000000
--- a/v2/container/json_test.go
+++ /dev/null
@@ -1,32 +0,0 @@
-package container_test
-
-import (
-	"testing"
-
-	"github.com/nspcc-dev/neofs-api-go/v2/container"
-	"github.com/stretchr/testify/require"
-)
-
-func TestContainerJSON(t *testing.T) {
-	c := generateContainer("nonce")
-
-	data, err := c.MarshalJSON()
-	require.NoError(t, err)
-
-	c2 := new(container.Container)
-	require.NoError(t, c2.UnmarshalJSON(data))
-
-	require.Equal(t, c, c2)
-}
-
-func TestAttributeJSON(t *testing.T) {
-	b := generateAttribute("key", "value")
-
-	data, err := b.MarshalJSON()
-	require.NoError(t, err)
-
-	b2 := new(container.Attribute)
-	require.NoError(t, b2.UnmarshalJSON(data))
-
-	require.Equal(t, b, b2)
-}
diff --git a/v2/container/marshal.go b/v2/container/marshal.go
index cf16e8a..3c0af3e 100644
--- a/v2/container/marshal.go
+++ b/v2/container/marshal.go
@@ -1,9 +1,9 @@
 package container
 
 import (
+	"github.com/nspcc-dev/neofs-api-go/rpc/message"
 	protoutil "github.com/nspcc-dev/neofs-api-go/util/proto"
 	container "github.com/nspcc-dev/neofs-api-go/v2/container/grpc"
-	"google.golang.org/protobuf/proto"
 )
 
 const (
@@ -89,14 +89,7 @@ func (a *Attribute) StableSize() (size int) {
 }
 
 func (a *Attribute) Unmarshal(data []byte) error {
-	m := new(container.Container_Attribute)
-	if err := proto.Unmarshal(data, m); err != nil {
-		return err
-	}
-
-	*a = *AttributeFromGRPCMessage(m)
-
-	return nil
+	return message.Unmarshal(a, data, new(container.Container_Attribute))
 }
 
 func (c *Container) StableMarshal(buf []byte) ([]byte, error) {
@@ -178,14 +171,7 @@ func (c *Container) StableSize() (size int) {
 }
 
 func (c *Container) Unmarshal(data []byte) error {
-	m := new(container.Container)
-	if err := proto.Unmarshal(data, m); err != nil {
-		return err
-	}
-
-	*c = *ContainerFromGRPCMessage(m)
-
-	return nil
+	return message.Unmarshal(c, data, new(container.Container))
 }
 
 func (r *PutRequestBody) StableMarshal(buf []byte) ([]byte, error) {
@@ -228,6 +214,10 @@ func (r *PutRequestBody) StableSize() (size int) {
 	return size
 }
 
+func (r *PutRequestBody) Unmarshal(data []byte) error {
+	return message.Unmarshal(r, data, new(container.PutRequest_Body))
+}
+
 func (r *PutResponseBody) StableMarshal(buf []byte) ([]byte, error) {
 	if r == nil {
 		return []byte{}, nil
@@ -259,6 +249,10 @@ func (r *PutResponseBody) StableSize() (size int) {
 	return size
 }
 
+func (r *PutResponseBody) Unmarshal(data []byte) error {
+	return message.Unmarshal(r, data, new(container.PutResponse_Body))
+}
+
 func (r *DeleteRequestBody) StableMarshal(buf []byte) ([]byte, error) {
 	if r == nil {
 		return []byte{}, nil
@@ -299,6 +293,10 @@ func (r *DeleteRequestBody) StableSize() (size int) {
 	return size
 }
 
+func (r *DeleteRequestBody) Unmarshal(data []byte) error {
+	return message.Unmarshal(r, data, new(container.DeleteRequest_Body))
+}
+
 func (r *DeleteResponseBody) StableMarshal(buf []byte) ([]byte, error) {
 	return nil, nil
 }
@@ -307,6 +305,10 @@ func (r *DeleteResponseBody) StableSize() (size int) {
 	return 0
 }
 
+func (r *DeleteResponseBody) Unmarshal([]byte) error {
+	return nil
+}
+
 func (r *GetRequestBody) StableMarshal(buf []byte) ([]byte, error) {
 	if r == nil {
 		return []byte{}, nil
@@ -334,6 +336,10 @@ func (r *GetRequestBody) StableSize() (size int) {
 	return size
 }
 
+func (r *GetRequestBody) Unmarshal(data []byte) error {
+	return message.Unmarshal(r, data, new(container.GetRequest_Body))
+}
+
 func (r *GetResponseBody) StableMarshal(buf []byte) ([]byte, error) {
 	if r == nil {
 		return []byte{}, nil
@@ -361,6 +367,10 @@ func (r *GetResponseBody) StableSize() (size int) {
 	return size
 }
 
+func (r *GetResponseBody) Unmarshal(data []byte) error {
+	return message.Unmarshal(r, data, new(container.GetResponse_Body))
+}
+
 func (r *ListRequestBody) StableMarshal(buf []byte) ([]byte, error) {
 	if r == nil {
 		return []byte{}, nil
@@ -388,6 +398,10 @@ func (r *ListRequestBody) StableSize() (size int) {
 	return size
 }
 
+func (r *ListRequestBody) Unmarshal(data []byte) error {
+	return message.Unmarshal(r, data, new(container.ListRequest_Body))
+}
+
 func (r *ListResponseBody) StableMarshal(buf []byte) ([]byte, error) {
 	if r == nil {
 		return []byte{}, nil
@@ -426,6 +440,10 @@ func (r *ListResponseBody) StableSize() (size int) {
 	return size
 }
 
+func (r *ListResponseBody) Unmarshal(data []byte) error {
+	return message.Unmarshal(r, data, new(container.ListResponse_Body))
+}
+
 func (r *SetExtendedACLRequestBody) StableMarshal(buf []byte) ([]byte, error) {
 	if r == nil {
 		return []byte{}, nil
@@ -466,6 +484,10 @@ func (r *SetExtendedACLRequestBody) StableSize() (size int) {
 	return size
 }
 
+func (r *SetExtendedACLRequestBody) Unmarshal(data []byte) error {
+	return message.Unmarshal(r, data, new(container.SetExtendedACLRequest_Body))
+}
+
 func (r *SetExtendedACLResponseBody) StableMarshal(buf []byte) ([]byte, error) {
 	return nil, nil
 }
@@ -474,6 +496,10 @@ func (r *SetExtendedACLResponseBody) StableSize() (size int) {
 	return 0
 }
 
+func (r *SetExtendedACLResponseBody) Unmarshal([]byte) error {
+	return nil
+}
+
 func (r *GetExtendedACLRequestBody) StableMarshal(buf []byte) ([]byte, error) {
 	if r == nil {
 		return []byte{}, nil
@@ -501,6 +527,10 @@ func (r *GetExtendedACLRequestBody) StableSize() (size int) {
 	return size
 }
 
+func (r *GetExtendedACLRequestBody) Unmarshal(data []byte) error {
+	return message.Unmarshal(r, data, new(container.GetExtendedACLRequest_Body))
+}
+
 func (r *GetExtendedACLResponseBody) StableMarshal(buf []byte) ([]byte, error) {
 	if r == nil {
 		return []byte{}, nil
@@ -541,6 +571,10 @@ func (r *GetExtendedACLResponseBody) StableSize() (size int) {
 	return size
 }
 
+func (r *GetExtendedACLResponseBody) Unmarshal(data []byte) error {
+	return message.Unmarshal(r, data, new(container.GetExtendedACLResponse_Body))
+}
+
 func (a *UsedSpaceAnnouncement) StableMarshal(buf []byte) ([]byte, error) {
 	if a == nil {
 		return []byte{}, nil
@@ -590,14 +624,7 @@ func (a *UsedSpaceAnnouncement) StableSize() (size int) {
 }
 
 func (a *UsedSpaceAnnouncement) Unmarshal(data []byte) error {
-	m := new(container.AnnounceUsedSpaceRequest_Body_Announcement)
-	if err := proto.Unmarshal(data, m); err != nil {
-		return err
-	}
-
-	*a = *UsedSpaceAnnouncementFromGRPCMessage(m)
-
-	return nil
+	return message.Unmarshal(a, data, new(container.AnnounceUsedSpaceRequest_Body_Announcement))
 }
 
 func (r *AnnounceUsedSpaceRequestBody) StableMarshal(buf []byte) ([]byte, error) {
@@ -638,6 +665,10 @@ func (r *AnnounceUsedSpaceRequestBody) StableSize() (size int) {
 	return size
 }
 
+func (r *AnnounceUsedSpaceRequestBody) Unmarshal(data []byte) error {
+	return message.Unmarshal(r, data, new(container.AnnounceUsedSpaceRequest_Body))
+}
+
 func (r *AnnounceUsedSpaceResponseBody) StableMarshal(buf []byte) ([]byte, error) {
 	return nil, nil
 }
@@ -645,3 +676,7 @@ func (r *AnnounceUsedSpaceResponseBody) StableMarshal(buf []byte) ([]byte, error
 func (r *AnnounceUsedSpaceResponseBody) StableSize() (size int) {
 	return 0
 }
+
+func (r *AnnounceUsedSpaceResponseBody) Unmarshal([]byte) error {
+	return nil
+}
diff --git a/v2/container/marshal_test.go b/v2/container/marshal_test.go
deleted file mode 100644
index f1fc189..0000000
--- a/v2/container/marshal_test.go
+++ /dev/null
@@ -1,492 +0,0 @@
-package container_test
-
-import (
-	"crypto/sha256"
-	"fmt"
-	"math/rand"
-	"testing"
-
-	"github.com/nspcc-dev/neofs-api-go/v2/acl"
-	"github.com/nspcc-dev/neofs-api-go/v2/container"
-	grpc "github.com/nspcc-dev/neofs-api-go/v2/container/grpc"
-	"github.com/nspcc-dev/neofs-api-go/v2/refs"
-	"github.com/stretchr/testify/require"
-	goproto "google.golang.org/protobuf/proto"
-)
-
-func TestAttribute_StableMarshal(t *testing.T) {
-	attributeFrom := generateAttribute("key", "value")
-
-	t.Run("non empty", func(t *testing.T) {
-		wire, err := attributeFrom.StableMarshal(nil)
-		require.NoError(t, err)
-
-		attributeTo := new(container.Attribute)
-		require.NoError(t, attributeTo.Unmarshal(wire))
-
-		require.Equal(t, attributeFrom, attributeTo)
-	})
-}
-
-func TestContainer_StableMarshal(t *testing.T) {
-	cnrFrom := generateContainer("nonce")
-
-	t.Run("non empty", func(t *testing.T) {
-		wire, err := cnrFrom.StableMarshal(nil)
-		require.NoError(t, err)
-
-		cnrTo := new(container.Container)
-		require.NoError(t, cnrTo.Unmarshal(wire))
-
-		require.Equal(t, cnrFrom, cnrTo)
-	})
-}
-
-func TestPutRequestBody_StableMarshal(t *testing.T) {
-	requestFrom := generatePutRequestBody("nonce")
-	transport := new(grpc.PutRequest_Body)
-
-	t.Run("non empty", func(t *testing.T) {
-		wire, err := requestFrom.StableMarshal(nil)
-		require.NoError(t, err)
-
-		err = goproto.Unmarshal(wire, transport)
-		require.NoError(t, err)
-
-		requestTo := container.PutRequestBodyFromGRPCMessage(transport)
-		require.Equal(t, requestFrom, requestTo)
-	})
-}
-
-func TestPutResponseBody_StableMarshal(t *testing.T) {
-	responseFrom := generatePutResponseBody("Container ID")
-	transport := new(grpc.PutResponse_Body)
-
-	t.Run("non empty", func(t *testing.T) {
-		wire, err := responseFrom.StableMarshal(nil)
-		require.NoError(t, err)
-
-		err = goproto.Unmarshal(wire, transport)
-		require.NoError(t, err)
-
-		responseTo := container.PutResponseBodyFromGRPCMessage(transport)
-		require.Equal(t, responseFrom, responseTo)
-	})
-}
-
-func TestDeleteRequestBody_StableMarshal(t *testing.T) {
-	requestFrom := generateDeleteRequestBody("Container ID")
-	transport := new(grpc.DeleteRequest_Body)
-
-	t.Run("non empty", func(t *testing.T) {
-		wire, err := requestFrom.StableMarshal(nil)
-		require.NoError(t, err)
-
-		err = goproto.Unmarshal(wire, transport)
-		require.NoError(t, err)
-
-		requestTo := container.DeleteRequestBodyFromGRPCMessage(transport)
-		require.Equal(t, requestFrom, requestTo)
-	})
-}
-
-func TestDeleteResponseBody_StableMarshal(t *testing.T) {
-	responseFrom := generateDeleteResponseBody()
-	transport := new(grpc.DeleteResponse_Body)
-
-	t.Run("non empty", func(t *testing.T) {
-		wire, err := responseFrom.StableMarshal(nil)
-		require.NoError(t, err)
-
-		err = goproto.Unmarshal(wire, transport)
-		require.NoError(t, err)
-
-		responseTo := container.DeleteResponseBodyFromGRPCMessage(transport)
-		require.Equal(t, responseFrom, responseTo)
-	})
-}
-
-func TestGetRequestBody_StableMarshal(t *testing.T) {
-	requestFrom := generateGetRequestBody("Container ID")
-	transport := new(grpc.GetRequest_Body)
-
-	t.Run("non empty", func(t *testing.T) {
-		wire, err := requestFrom.StableMarshal(nil)
-		require.NoError(t, err)
-
-		err = goproto.Unmarshal(wire, transport)
-		require.NoError(t, err)
-
-		requestTo := container.GetRequestBodyFromGRPCMessage(transport)
-		require.Equal(t, requestFrom, requestTo)
-	})
-}
-
-func TestGetResponseBody_StableMarshal(t *testing.T) {
-	responseFrom := generateGetResponseBody("nonce")
-	transport := new(grpc.GetResponse_Body)
-
-	t.Run("non empty", func(t *testing.T) {
-		wire, err := responseFrom.StableMarshal(nil)
-		require.NoError(t, err)
-
-		err = goproto.Unmarshal(wire, transport)
-		require.NoError(t, err)
-
-		responseTo := container.GetResponseBodyFromGRPCMessage(transport)
-		require.Equal(t, responseFrom, responseTo)
-	})
-}
-
-func TestListRequestBody_StableMarshal(t *testing.T) {
-	requestFrom := generateListRequestBody("Owner ID")
-	transport := new(grpc.ListRequest_Body)
-
-	t.Run("non empty", func(t *testing.T) {
-		wire, err := requestFrom.StableMarshal(nil)
-		require.NoError(t, err)
-
-		err = goproto.Unmarshal(wire, transport)
-		require.NoError(t, err)
-
-		requestTo := container.ListRequestBodyFromGRPCMessage(transport)
-		require.Equal(t, requestFrom, requestTo)
-	})
-}
-
-func TestListResponseBody_StableMarshal(t *testing.T) {
-	responseFrom := generateListResponseBody(3)
-	transport := new(grpc.ListResponse_Body)
-
-	t.Run("non empty", func(t *testing.T) {
-		wire, err := responseFrom.StableMarshal(nil)
-		require.NoError(t, err)
-
-		err = goproto.Unmarshal(wire, transport)
-		require.NoError(t, err)
-
-		responseTo := container.ListResponseBodyFromGRPCMessage(transport)
-		require.Equal(t, responseFrom, responseTo)
-	})
-}
-
-func TestSetEACLRequestBody_StableMarshal(t *testing.T) {
-	requestFrom := generateSetEACLRequestBody(4, "Filter Key", "Filter Value")
-	transport := new(grpc.SetExtendedACLRequest_Body)
-
-	t.Run("non empty", func(t *testing.T) {
-		wire, err := requestFrom.StableMarshal(nil)
-		require.NoError(t, err)
-
-		err = goproto.Unmarshal(wire, transport)
-		require.NoError(t, err)
-
-		requestTo := container.SetExtendedACLRequestBodyFromGRPCMessage(transport)
-		require.Equal(t, requestFrom, requestTo)
-	})
-}
-
-func TestSetEACLResponseBody_StableMarshal(t *testing.T) {
-	responseFrom := generateSetEACLResponseBody()
-	transport := new(grpc.SetExtendedACLResponse_Body)
-
-	t.Run("non empty", func(t *testing.T) {
-		wire, err := responseFrom.StableMarshal(nil)
-		require.NoError(t, err)
-
-		err = goproto.Unmarshal(wire, transport)
-		require.NoError(t, err)
-
-		responseTo := container.SetExtendedACLResponseBodyFromGRPCMessage(transport)
-		require.Equal(t, responseFrom, responseTo)
-	})
-}
-
-func TestGetEACLRequestBody_StableMarshal(t *testing.T) {
-	requestFrom := generateGetEACLRequestBody("Container ID")
-	transport := new(grpc.GetExtendedACLRequest_Body)
-
-	t.Run("non empty", func(t *testing.T) {
-		wire, err := requestFrom.StableMarshal(nil)
-		require.NoError(t, err)
-
-		err = goproto.Unmarshal(wire, transport)
-		require.NoError(t, err)
-
-		requestTo := container.GetExtendedACLRequestBodyFromGRPCMessage(transport)
-		require.Equal(t, requestFrom, requestTo)
-	})
-}
-
-func TestGetEACLResponseBody_StableMarshal(t *testing.T) {
-	responseFrom := generateGetEACLResponseBody(3, "Filter Key", "Filter Value")
-	transport := new(grpc.GetExtendedACLResponse_Body)
-
-	t.Run("non empty", func(t *testing.T) {
-		wire, err := responseFrom.StableMarshal(nil)
-		require.NoError(t, err)
-
-		err = goproto.Unmarshal(wire, transport)
-		require.NoError(t, err)
-
-		responseTo := container.GetExtendedACLResponseBodyFromGRPCMessage(transport)
-		require.Equal(t, responseFrom, responseTo)
-	})
-}
-
-func TestUsedSpaceAnnouncement_StableMarshal(t *testing.T) {
-	from := generateAnnouncement()
-	transport := new(grpc.AnnounceUsedSpaceRequest_Body_Announcement)
-
-	t.Run("non empty", func(t *testing.T) {
-		wire, err := from.StableMarshal(nil)
-		require.NoError(t, err)
-
-		err = goproto.Unmarshal(wire, transport)
-		require.NoError(t, err)
-
-		to := container.UsedSpaceAnnouncementFromGRPCMessage(transport)
-		require.Equal(t, from, to)
-	})
-}
-
-func TestAnnounceUsedSpaceRequestBody_StableMarshal(t *testing.T) {
-	requestFrom := generateAnnounceRequestBody(10)
-	transport := new(grpc.AnnounceUsedSpaceRequest_Body)
-
-	t.Run("non empty", func(t *testing.T) {
-		wire, err := requestFrom.StableMarshal(nil)
-		require.NoError(t, err)
-
-		err = goproto.Unmarshal(wire, transport)
-		require.NoError(t, err)
-
-		requestTo := container.AnnounceUsedSpaceRequestBodyFromGRPCMessage(transport)
-		require.Equal(t, requestFrom, requestTo)
-	})
-}
-
-func TestAnnounceUsedSpaceResponseBody_StableMarshal(t *testing.T) {
-	responseFrom := generateAnnounceResponseBody()
-	transport := new(grpc.AnnounceUsedSpaceResponse_Body)
-
-	t.Run("non empty", func(t *testing.T) {
-		wire, err := responseFrom.StableMarshal(nil)
-		require.NoError(t, err)
-
-		err = goproto.Unmarshal(wire, transport)
-		require.NoError(t, err)
-
-		responseTo := container.AnnounceUsedSpaceResponseBodyFromGRPCMessage(transport)
-		require.Equal(t, responseFrom, responseTo)
-	})
-}
-
-func generateAttribute(k, v string) *container.Attribute {
-	attr := new(container.Attribute)
-	attr.SetKey(k)
-	attr.SetValue(v)
-
-	return attr
-}
-
-func generateContainer(n string) *container.Container {
-	owner := new(refs.OwnerID)
-	owner.SetValue([]byte("Owner ID"))
-
-	version := new(refs.Version)
-	version.SetMajor(2)
-	version.SetMinor(0)
-
-	// todo: add placement rule
-
-	cnr := new(container.Container)
-	cnr.SetOwnerID(owner)
-	cnr.SetVersion(version)
-	cnr.SetAttributes([]*container.Attribute{
-		generateAttribute("one", "two"),
-		generateAttribute("three", "four"),
-	})
-	cnr.SetBasicACL(100)
-	cnr.SetNonce([]byte(n))
-
-	return cnr
-}
-
-func generateSignature(k, v string) *refs.Signature {
-	sig := new(refs.Signature)
-	sig.SetKey([]byte(k))
-	sig.SetSign([]byte(v))
-
-	return sig
-}
-
-func generatePutRequestBody(n string) *container.PutRequestBody {
-	req := new(container.PutRequestBody)
-	req.SetContainer(generateContainer(n))
-	req.SetSignature(generateSignature("public key", "signature"))
-
-	return req
-}
-
-func generatePutResponseBody(id string) *container.PutResponseBody {
-	cid := new(refs.ContainerID)
-	cid.SetValue([]byte(id))
-
-	resp := new(container.PutResponseBody)
-	resp.SetContainerID(cid)
-
-	return resp
-}
-
-func generateDeleteRequestBody(id string) *container.DeleteRequestBody {
-	cid := new(refs.ContainerID)
-	cid.SetValue([]byte(id))
-
-	req := new(container.DeleteRequestBody)
-	req.SetContainerID(cid)
-	req.SetSignature(generateSignature("public key", "signature"))
-
-	return req
-}
-
-func generateDeleteResponseBody() *container.DeleteResponseBody {
-	return new(container.DeleteResponseBody)
-}
-
-func generateGetRequestBody(id string) *container.GetRequestBody {
-	cid := new(refs.ContainerID)
-	cid.SetValue([]byte(id))
-
-	req := new(container.GetRequestBody)
-	req.SetContainerID(cid)
-
-	return req
-}
-
-func generateGetResponseBody(n string) *container.GetResponseBody {
-	resp := new(container.GetResponseBody)
-	resp.SetContainer(generateContainer(n))
-
-	return resp
-}
-
-func generateListRequestBody(id string) *container.ListRequestBody {
-	owner := new(refs.OwnerID)
-	owner.SetValue([]byte(id))
-
-	req := new(container.ListRequestBody)
-	req.SetOwnerID(owner)
-
-	return req
-}
-
-func generateListResponseBody(n int) *container.ListResponseBody {
-	resp := new(container.ListResponseBody)
-
-	ids := make([]*refs.ContainerID, n)
-	for i := 0; i < n; i++ {
-		cid := new(refs.ContainerID)
-		cid.SetValue([]byte(fmt.Sprintf("Container ID %d", n+1)))
-		ids[i] = cid
-	}
-
-	resp.SetContainerIDs(ids)
-
-	return resp
-}
-
-func generateEACL(n int, k, v string) *acl.Table {
-	target := new(acl.Target)
-	target.SetRole(acl.RoleUser)
-
-	keys := make([][]byte, n)
-
-	for i := 0; i < n; i++ {
-		s := fmt.Sprintf("Public Key %d", i+1)
-		keys[i] = []byte(s)
-	}
-
-	filter := new(acl.HeaderFilter)
-	filter.SetHeaderType(acl.HeaderTypeObject)
-	filter.SetMatchType(acl.MatchTypeStringEqual)
-	filter.SetKey(k)
-	filter.SetValue(v)
-
-	record := new(acl.Record)
-	record.SetOperation(acl.OperationHead)
-	record.SetAction(acl.ActionDeny)
-	record.SetTargets([]*acl.Target{target})
-	record.SetFilters([]*acl.HeaderFilter{filter})
-
-	table := new(acl.Table)
-	cid := new(refs.ContainerID)
-	cid.SetValue([]byte("Container ID"))
-
-	table.SetContainerID(cid)
-	table.SetRecords([]*acl.Record{record})
-
-	return table
-}
-
-func generateSetEACLRequestBody(n int, k, v string) *container.SetExtendedACLRequestBody {
-	req := new(container.SetExtendedACLRequestBody)
-	req.SetEACL(generateEACL(n, k, v))
-	req.SetSignature(generateSignature("public key", "signature"))
-
-	return req
-}
-
-func generateSetEACLResponseBody() *container.SetExtendedACLResponseBody {
-	return new(container.SetExtendedACLResponseBody)
-}
-
-func generateGetEACLRequestBody(id string) *container.GetExtendedACLRequestBody {
-	cid := new(refs.ContainerID)
-	cid.SetValue([]byte(id))
-
-	req := new(container.GetExtendedACLRequestBody)
-	req.SetContainerID(cid)
-
-	return req
-}
-
-func generateGetEACLResponseBody(n int, k, v string) *container.GetExtendedACLResponseBody {
-	resp := new(container.GetExtendedACLResponseBody)
-	resp.SetEACL(generateEACL(n, k, v))
-	resp.SetSignature(generateSignature("public key", "signature"))
-
-	return resp
-}
-
-func generateAnnouncement() *container.UsedSpaceAnnouncement {
-	buf := make([]byte, sha256.Size)
-	rand.Read(buf)
-
-	cid := new(refs.ContainerID)
-	cid.SetValue(buf)
-
-	a := new(container.UsedSpaceAnnouncement)
-	a.SetEpoch(rand.Uint64())
-	a.SetContainerID(cid)
-	a.SetUsedSpace(rand.Uint64())
-
-	return a
-}
-
-func generateAnnounceRequestBody(n int) *container.AnnounceUsedSpaceRequestBody {
-	resp := new(container.AnnounceUsedSpaceRequestBody)
-
-	announcements := make([]*container.UsedSpaceAnnouncement, 0, n)
-	for i := 0; i < n; i++ {
-		announcements = append(announcements, generateAnnouncement())
-	}
-
-	resp.SetAnnouncements(announcements)
-
-	return resp
-}
-
-func generateAnnounceResponseBody() *container.AnnounceUsedSpaceResponseBody {
-	return new(container.AnnounceUsedSpaceResponseBody)
-}
diff --git a/v2/container/message_test.go b/v2/container/message_test.go
new file mode 100644
index 0000000..592fcbe
--- /dev/null
+++ b/v2/container/message_test.go
@@ -0,0 +1,43 @@
+package container_test
+
+import (
+	"testing"
+
+	"github.com/nspcc-dev/neofs-api-go/rpc/message"
+	messagetest "github.com/nspcc-dev/neofs-api-go/rpc/message/test"
+	containertest "github.com/nspcc-dev/neofs-api-go/v2/container/test"
+)
+
+func TestMessageConvert(t *testing.T) {
+	messagetest.TestRPCMessage(t,
+		func(empty bool) message.Message { return containertest.GenerateAttribute(empty) },
+		func(empty bool) message.Message { return containertest.GenerateContainer(empty) },
+		func(empty bool) message.Message { return containertest.GeneratePutRequestBody(empty) },
+		func(empty bool) message.Message { return containertest.GeneratePutRequest(empty) },
+		func(empty bool) message.Message { return containertest.GeneratePutResponseBody(empty) },
+		func(empty bool) message.Message { return containertest.GeneratePutResponse(empty) },
+		func(empty bool) message.Message { return containertest.GenerateGetRequestBody(empty) },
+		func(empty bool) message.Message { return containertest.GenerateGetRequest(empty) },
+		func(empty bool) message.Message { return containertest.GenerateGetResponseBody(empty) },
+		func(empty bool) message.Message { return containertest.GenerateGetResponse(empty) },
+		func(empty bool) message.Message { return containertest.GenerateDeleteRequestBody(empty) },
+		func(empty bool) message.Message { return containertest.GenerateDeleteRequest(empty) },
+		func(empty bool) message.Message { return containertest.GenerateDeleteResponseBody(empty) },
+		func(empty bool) message.Message { return containertest.GenerateDeleteResponse(empty) },
+		func(empty bool) message.Message { return containertest.GenerateListRequestBody(empty) },
+		func(empty bool) message.Message { return containertest.GenerateListRequest(empty) },
+		func(empty bool) message.Message { return containertest.GenerateListResponseBody(empty) },
+		func(empty bool) message.Message { return containertest.GenerateListResponse(empty) },
+		func(empty bool) message.Message { return containertest.GenerateSetExtendedACLRequestBody(empty) },
+		func(empty bool) message.Message { return containertest.GenerateSetExtendedACLRequest(empty) },
+		func(empty bool) message.Message { return containertest.GenerateGetRequestBody(empty) },
+		func(empty bool) message.Message { return containertest.GenerateGetRequest(empty) },
+		func(empty bool) message.Message { return containertest.GenerateGetResponseBody(empty) },
+		func(empty bool) message.Message { return containertest.GenerateGetResponse(empty) },
+		func(empty bool) message.Message { return containertest.GenerateUsedSpaceAnnouncement(empty) },
+		func(empty bool) message.Message { return containertest.GenerateAnnounceUsedSpaceRequestBody(empty) },
+		func(empty bool) message.Message { return containertest.GenerateAnnounceUsedSpaceRequest(empty) },
+		func(empty bool) message.Message { return containertest.GenerateAnnounceUsedSpaceResponseBody(empty) },
+		func(empty bool) message.Message { return containertest.GenerateAnnounceUsedSpaceResponse(empty) },
+	)
+}
diff --git a/v2/container/service.go b/v2/container/service.go
deleted file mode 100644
index 7c369b2..0000000
--- a/v2/container/service.go
+++ /dev/null
@@ -1,113 +0,0 @@
-package container
-
-import (
-	"context"
-
-	"github.com/nspcc-dev/neofs-api-go/v2/session"
-)
-
-type Service interface {
-	Put(context.Context, *PutRequest) (*PutResponse, error)
-	Delete(context.Context, *DeleteRequest) (*DeleteResponse, error)
-	Get(context.Context, *GetRequest) (*GetResponse, error)
-	List(context.Context, *ListRequest) (*ListResponse, error)
-	SetExtendedACL(context.Context, *SetExtendedACLRequest) (*SetExtendedACLResponse, error)
-	GetExtendedACL(context.Context, *GetExtendedACLRequest) (*GetExtendedACLResponse, error)
-	AnnounceUsedSpace(context.Context, *AnnounceUsedSpaceRequest) (*AnnounceUsedSpaceResponse, error)
-}
-
-type PutRequest struct {
-	body *PutRequestBody
-
-	metaHeader *session.RequestMetaHeader
-
-	verifyHeader *session.RequestVerificationHeader
-}
-
-type PutResponse struct {
-	body *PutResponseBody
-
-	metaHeader *session.ResponseMetaHeader
-
-	verifyHeader *session.ResponseVerificationHeader
-}
-
-type DeleteRequest struct {
-	body *DeleteRequestBody
-
-	metaHeader *session.RequestMetaHeader
-
-	verifyHeader *session.RequestVerificationHeader
-}
-
-type DeleteResponse struct {
-	body *DeleteResponseBody
-
-	metaHeader *session.ResponseMetaHeader
-
-	verifyHeader *session.ResponseVerificationHeader
-}
-
-type ListRequest struct {
-	body *ListRequestBody
-
-	metaHeader *session.RequestMetaHeader
-
-	verifyHeader *session.RequestVerificationHeader
-}
-
-type ListResponse struct {
-	body *ListResponseBody
-
-	metaHeader *session.ResponseMetaHeader
-
-	verifyHeader *session.ResponseVerificationHeader
-}
-
-type SetExtendedACLRequest struct {
-	body *SetExtendedACLRequestBody
-
-	metaHeader *session.RequestMetaHeader
-
-	verifyHeader *session.RequestVerificationHeader
-}
-
-type SetExtendedACLResponse struct {
-	body *SetExtendedACLResponseBody
-
-	metaHeader *session.ResponseMetaHeader
-
-	verifyHeader *session.ResponseVerificationHeader
-}
-
-type GetExtendedACLRequest struct {
-	body *GetExtendedACLRequestBody
-
-	metaHeader *session.RequestMetaHeader
-
-	verifyHeader *session.RequestVerificationHeader
-}
-
-type GetExtendedACLResponse struct {
-	body *GetExtendedACLResponseBody
-
-	metaHeader *session.ResponseMetaHeader
-
-	verifyHeader *session.ResponseVerificationHeader
-}
-
-type AnnounceUsedSpaceRequest struct {
-	body *AnnounceUsedSpaceRequestBody
-
-	metaHeader *session.RequestMetaHeader
-
-	verifyHeader *session.RequestVerificationHeader
-}
-
-type AnnounceUsedSpaceResponse struct {
-	body *AnnounceUsedSpaceResponseBody
-
-	metaHeader *session.ResponseMetaHeader
-
-	verifyHeader *session.ResponseVerificationHeader
-}
diff --git a/v2/container/test/client_test.go b/v2/container/test/client_test.go
deleted file mode 100644
index 177b9c9..0000000
--- a/v2/container/test/client_test.go
+++ /dev/null
@@ -1,958 +0,0 @@
-package main
-
-import (
-	"context"
-	"crypto/ecdsa"
-	"errors"
-	"testing"
-
-	"github.com/nspcc-dev/neofs-api-go/v2/acl"
-	"github.com/nspcc-dev/neofs-api-go/v2/container"
-	containerGRPC "github.com/nspcc-dev/neofs-api-go/v2/container/grpc"
-	"github.com/nspcc-dev/neofs-api-go/v2/refs"
-	"github.com/nspcc-dev/neofs-api-go/v2/session"
-	"github.com/nspcc-dev/neofs-api-go/v2/signature"
-	"github.com/nspcc-dev/neofs-crypto/test"
-	"github.com/stretchr/testify/require"
-	"google.golang.org/grpc"
-)
-
-type testGRPCClient struct {
-	server containerGRPC.ContainerServiceServer
-}
-
-type testGRPCServer struct {
-	key          *ecdsa.PrivateKey
-	putResp      *container.PutResponse
-	getResp      *container.GetResponse
-	delResp      *container.DeleteResponse
-	listResp     *container.ListResponse
-	sEaclResp    *container.SetExtendedACLResponse
-	gEaclResp    *container.GetExtendedACLResponse
-	announceResp *container.AnnounceUsedSpaceResponse
-	err          error
-}
-
-func (s *testGRPCClient) Put(ctx context.Context, in *containerGRPC.PutRequest, opts ...grpc.CallOption) (*containerGRPC.PutResponse, error) {
-	return s.server.Put(ctx, in)
-}
-
-func (s *testGRPCClient) Delete(ctx context.Context, in *containerGRPC.DeleteRequest, opts ...grpc.CallOption) (*containerGRPC.DeleteResponse, error) {
-	return s.server.Delete(ctx, in)
-}
-
-func (s *testGRPCClient) Get(ctx context.Context, in *containerGRPC.GetRequest, opts ...grpc.CallOption) (*containerGRPC.GetResponse, error) {
-	return s.server.Get(ctx, in)
-}
-
-func (s *testGRPCClient) List(ctx context.Context, in *containerGRPC.ListRequest, opts ...grpc.CallOption) (*containerGRPC.ListResponse, error) {
-	return s.server.List(ctx, in)
-}
-
-func (s *testGRPCClient) SetExtendedACL(ctx context.Context, in *containerGRPC.SetExtendedACLRequest, opts ...grpc.CallOption) (*containerGRPC.SetExtendedACLResponse, error) {
-	return s.server.SetExtendedACL(ctx, in)
-}
-
-func (s *testGRPCClient) GetExtendedACL(ctx context.Context, in *containerGRPC.GetExtendedACLRequest, opts ...grpc.CallOption) (*containerGRPC.GetExtendedACLResponse, error) {
-	return s.server.GetExtendedACL(ctx, in)
-}
-
-func (s *testGRPCClient) AnnounceUsedSpace(ctx context.Context, in *containerGRPC.AnnounceUsedSpaceRequest, opts ...grpc.CallOption) (*containerGRPC.AnnounceUsedSpaceResponse, error) {
-	return s.server.AnnounceUsedSpace(ctx, in)
-}
-
-func (s *testGRPCServer) Put(_ context.Context, req *containerGRPC.PutRequest) (*containerGRPC.PutResponse, error) {
-	if s.err != nil {
-		return nil, s.err
-	}
-
-	// verify request structure
-	if err := signature.VerifyServiceMessage(
-		container.PutRequestFromGRPCMessage(req),
-	); err != nil {
-		return nil, err
-	}
-
-	// sign response structure
-	if err := signature.SignServiceMessage(s.key, s.putResp); err != nil {
-		return nil, err
-	}
-
-	return container.PutResponseToGRPCMessage(s.putResp), nil
-}
-
-func (s *testGRPCServer) Delete(_ context.Context, req *containerGRPC.DeleteRequest) (*containerGRPC.DeleteResponse, error) {
-	if s.err != nil {
-		return nil, s.err
-	}
-
-	// verify request structure
-	if err := signature.VerifyServiceMessage(
-		container.DeleteRequestFromGRPCMessage(req),
-	); err != nil {
-		return nil, err
-	}
-
-	// sign response structure
-	if err := signature.SignServiceMessage(s.key, s.delResp); err != nil {
-		return nil, err
-	}
-
-	return container.DeleteResponseToGRPCMessage(s.delResp), nil
-}
-
-func (s *testGRPCServer) Get(_ context.Context, req *containerGRPC.GetRequest) (*containerGRPC.GetResponse, error) {
-	if s.err != nil {
-		return nil, s.err
-	}
-
-	// verify request structure
-	if err := signature.VerifyServiceMessage(
-		container.GetRequestFromGRPCMessage(req),
-	); err != nil {
-		return nil, err
-	}
-
-	// sign response structure
-	if err := signature.SignServiceMessage(s.key, s.getResp); err != nil {
-		return nil, err
-	}
-
-	return container.GetResponseToGRPCMessage(s.getResp), nil
-}
-
-func (s *testGRPCServer) List(_ context.Context, req *containerGRPC.ListRequest) (*containerGRPC.ListResponse, error) {
-	if s.err != nil {
-		return nil, s.err
-	}
-
-	// verify request structure
-	if err := signature.VerifyServiceMessage(
-		container.ListRequestFromGRPCMessage(req),
-	); err != nil {
-		return nil, err
-	}
-
-	// sign response structure
-	if err := signature.SignServiceMessage(s.key, s.listResp); err != nil {
-		return nil, err
-	}
-
-	return container.ListResponseToGRPCMessage(s.listResp), nil
-}
-
-func (s *testGRPCServer) SetExtendedACL(_ context.Context, req *containerGRPC.SetExtendedACLRequest) (*containerGRPC.SetExtendedACLResponse, error) {
-	if s.err != nil {
-		return nil, s.err
-	}
-
-	// verify request structure
-	if err := signature.VerifyServiceMessage(
-		container.SetExtendedACLRequestFromGRPCMessage(req),
-	); err != nil {
-		return nil, err
-	}
-
-	// sign response structure
-	if err := signature.SignServiceMessage(s.key, s.sEaclResp); err != nil {
-		return nil, err
-	}
-
-	return container.SetExtendedACLResponseToGRPCMessage(s.sEaclResp), nil
-}
-
-func (s *testGRPCServer) GetExtendedACL(_ context.Context, req *containerGRPC.GetExtendedACLRequest) (*containerGRPC.GetExtendedACLResponse, error) {
-	if s.err != nil {
-		return nil, s.err
-	}
-
-	// verify request structure
-	if err := signature.VerifyServiceMessage(
-		container.GetExtendedACLRequestFromGRPCMessage(req),
-	); err != nil {
-		return nil, err
-	}
-
-	// sign response structure
-	if err := signature.SignServiceMessage(s.key, s.gEaclResp); err != nil {
-		return nil, err
-	}
-
-	return container.GetExtendedACLResponseToGRPCMessage(s.gEaclResp), nil
-}
-
-func (s *testGRPCServer) AnnounceUsedSpace(_ context.Context, req *containerGRPC.AnnounceUsedSpaceRequest) (*containerGRPC.AnnounceUsedSpaceResponse, error) {
-	if s.err != nil {
-		return nil, s.err
-	}
-
-	// verify request structure
-	if err := signature.VerifyServiceMessage(
-		container.AnnounceUsedSpaceRequestFromGRPCMessage(req),
-	); err != nil {
-		return nil, err
-	}
-
-	// sign response structure
-	if err := signature.SignServiceMessage(s.key, s.announceResp); err != nil {
-		return nil, err
-	}
-
-	return container.AnnounceUsedSpaceResponseToGRPCMessage(s.announceResp), nil
-}
-
-func testPutRequest() *container.PutRequest {
-	cnr := new(container.Container)
-	cnr.SetBasicACL(1)
-
-	body := new(container.PutRequestBody)
-	body.SetContainer(cnr)
-
-	meta := new(session.RequestMetaHeader)
-	meta.SetTTL(1)
-
-	req := new(container.PutRequest)
-	req.SetBody(body)
-	req.SetMetaHeader(meta)
-
-	return req
-}
-
-func testPutResponse() *container.PutResponse {
-	cid := new(refs.ContainerID)
-	cid.SetValue([]byte{1, 2, 3})
-
-	body := new(container.PutResponseBody)
-	body.SetContainerID(cid)
-
-	meta := new(session.ResponseMetaHeader)
-	meta.SetTTL(1)
-	meta.SetXHeaders([]*session.XHeader{}) // w/o this require.Equal fails due to nil and []T{} difference
-
-	resp := new(container.PutResponse)
-	resp.SetBody(body)
-	resp.SetMetaHeader(meta)
-
-	return resp
-}
-
-func testGetRequest() *container.GetRequest {
-	cid := new(refs.ContainerID)
-	cid.SetValue([]byte{1, 2, 3})
-
-	body := new(container.GetRequestBody)
-	body.SetContainerID(cid)
-
-	meta := new(session.RequestMetaHeader)
-	meta.SetTTL(1)
-
-	req := new(container.GetRequest)
-	req.SetBody(body)
-	req.SetMetaHeader(meta)
-
-	return req
-}
-
-func testGetResponse() *container.GetResponse {
-	cnr := new(container.Container)
-	cnr.SetAttributes([]*container.Attribute{}) // w/o this require.Equal fails due to nil and []T{} difference
-
-	body := new(container.GetResponseBody)
-	body.SetContainer(cnr)
-
-	meta := new(session.ResponseMetaHeader)
-	meta.SetTTL(1)
-	meta.SetXHeaders([]*session.XHeader{}) // w/o this require.Equal fails due to nil and []T{} difference
-
-	resp := new(container.GetResponse)
-	resp.SetBody(body)
-	resp.SetMetaHeader(meta)
-
-	return resp
-}
-
-func testDelRequest() *container.DeleteRequest {
-	cid := new(refs.ContainerID)
-	cid.SetValue([]byte{1, 2, 3})
-
-	body := new(container.DeleteRequestBody)
-	body.SetContainerID(cid)
-
-	meta := new(session.RequestMetaHeader)
-	meta.SetTTL(1)
-
-	req := new(container.DeleteRequest)
-	req.SetBody(body)
-	req.SetMetaHeader(meta)
-
-	return req
-}
-
-func testDelResponse() *container.DeleteResponse {
-	body := new(container.DeleteResponseBody)
-
-	meta := new(session.ResponseMetaHeader)
-	meta.SetTTL(1)
-	meta.SetXHeaders([]*session.XHeader{}) // w/o this require.Equal fails due to nil and []T{} difference
-
-	resp := new(container.DeleteResponse)
-	resp.SetBody(body)
-	resp.SetMetaHeader(meta)
-
-	return resp
-}
-
-func testListRequest() *container.ListRequest {
-	ownerID := new(refs.OwnerID)
-	ownerID.SetValue([]byte{1, 2, 3})
-
-	body := new(container.ListRequestBody)
-	body.SetOwnerID(ownerID)
-
-	meta := new(session.RequestMetaHeader)
-	meta.SetTTL(1)
-
-	req := new(container.ListRequest)
-	req.SetBody(body)
-	req.SetMetaHeader(meta)
-
-	return req
-}
-
-func testListResponse() *container.ListResponse {
-	cid := new(refs.ContainerID)
-	cid.SetValue([]byte{1, 2, 3})
-
-	body := new(container.ListResponseBody)
-	body.SetContainerIDs([]*refs.ContainerID{cid})
-
-	meta := new(session.ResponseMetaHeader)
-	meta.SetTTL(1)
-	meta.SetXHeaders([]*session.XHeader{}) // w/o this require.Equal fails due to nil and []T{} difference
-
-	resp := new(container.ListResponse)
-	resp.SetBody(body)
-	resp.SetMetaHeader(meta)
-
-	return resp
-}
-
-func testSetEACLRequest() *container.SetExtendedACLRequest {
-	cid := new(refs.ContainerID)
-	cid.SetValue([]byte{1, 2, 3})
-
-	eacl := new(acl.Table)
-	eacl.SetContainerID(cid)
-
-	body := new(container.SetExtendedACLRequestBody)
-	body.SetEACL(eacl)
-
-	meta := new(session.RequestMetaHeader)
-	meta.SetTTL(1)
-
-	req := new(container.SetExtendedACLRequest)
-	req.SetBody(body)
-	req.SetMetaHeader(meta)
-
-	return req
-}
-
-func testSetEACLResponse() *container.SetExtendedACLResponse {
-	body := new(container.SetExtendedACLResponseBody)
-
-	meta := new(session.ResponseMetaHeader)
-	meta.SetTTL(1)
-	meta.SetXHeaders([]*session.XHeader{}) // w/o this require.Equal fails due to nil and []T{} difference
-
-	resp := new(container.SetExtendedACLResponse)
-	resp.SetBody(body)
-	resp.SetMetaHeader(meta)
-
-	return resp
-}
-
-func testGetEACLRequest() *container.GetExtendedACLRequest {
-	cid := new(refs.ContainerID)
-	cid.SetValue([]byte{1, 2, 3})
-
-	body := new(container.GetExtendedACLRequestBody)
-	body.SetContainerID(cid)
-
-	meta := new(session.RequestMetaHeader)
-	meta.SetTTL(1)
-
-	req := new(container.GetExtendedACLRequest)
-	req.SetBody(body)
-	req.SetMetaHeader(meta)
-
-	return req
-}
-
-func testGetEACLResponse() *container.GetExtendedACLResponse {
-	cid := new(refs.ContainerID)
-	cid.SetValue([]byte{1, 2, 3})
-
-	eacl := new(acl.Table)
-	eacl.SetContainerID(cid)
-	eacl.SetRecords([]*acl.Record{}) // w/o this require.Equal fails due to nil and []T{} difference
-
-	body := new(container.GetExtendedACLResponseBody)
-	body.SetEACL(eacl)
-
-	meta := new(session.ResponseMetaHeader)
-	meta.SetTTL(1)
-	meta.SetXHeaders([]*session.XHeader{}) // w/o this require.Equal fails due to nil and []T{} difference
-
-	resp := new(container.GetExtendedACLResponse)
-	resp.SetBody(body)
-	resp.SetMetaHeader(meta)
-
-	return resp
-}
-
-func testAnnounceRequest() *container.AnnounceUsedSpaceRequest {
-	cid1 := new(refs.ContainerID)
-	cid1.SetValue([]byte{1, 2, 3})
-
-	cid2 := new(refs.ContainerID)
-	cid2.SetValue([]byte{4, 5, 6})
-
-	a1 := new(container.UsedSpaceAnnouncement)
-	a1.SetEpoch(20)
-	a1.SetUsedSpace(10)
-	a1.SetContainerID(cid1)
-
-	a2 := new(container.UsedSpaceAnnouncement)
-	a2.SetEpoch(20)
-	a2.SetUsedSpace(20)
-	a2.SetContainerID(cid2)
-
-	announcements := []*container.UsedSpaceAnnouncement{a1, a2}
-
-	body := new(container.AnnounceUsedSpaceRequestBody)
-	body.SetAnnouncements(announcements)
-
-	meta := new(session.RequestMetaHeader)
-	meta.SetTTL(1)
-
-	req := new(container.AnnounceUsedSpaceRequest)
-	req.SetBody(body)
-	req.SetMetaHeader(meta)
-
-	return req
-}
-
-func testAnnounceResponse() *container.AnnounceUsedSpaceResponse {
-	body := new(container.AnnounceUsedSpaceResponseBody)
-
-	meta := new(session.ResponseMetaHeader)
-	meta.SetTTL(1)
-	meta.SetXHeaders([]*session.XHeader{}) // w/o this require.Equal fails due to nil and []T{} difference
-
-	resp := new(container.AnnounceUsedSpaceResponse)
-	resp.SetBody(body)
-	resp.SetMetaHeader(meta)
-
-	return resp
-}
-
-func TestGRPCClient_Put(t *testing.T) {
-	ctx := context.TODO()
-
-	cliKey := test.DecodeKey(0)
-	srvKey := test.DecodeKey(1)
-
-	t.Run("gRPC server error", func(t *testing.T) {
-		srvErr := errors.New("test server error")
-
-		srv := &testGRPCServer{
-			err: srvErr,
-		}
-
-		cli := &testGRPCClient{
-			server: srv,
-		}
-
-		c, err := container.NewClient(container.WithGRPCServiceClient(cli))
-		require.NoError(t, err)
-
-		resp, err := c.Put(ctx, new(container.PutRequest))
-		require.True(t, errors.Is(err, srvErr))
-		require.Nil(t, resp)
-	})
-
-	t.Run("invalid request structure", func(t *testing.T) {
-		req := testPutRequest()
-
-		require.Error(t, signature.VerifyServiceMessage(req))
-
-		c, err := container.NewClient(
-			container.WithGRPCServiceClient(
-				&testGRPCClient{
-					server: new(testGRPCServer),
-				},
-			),
-		)
-		require.NoError(t, err)
-
-		resp, err := c.Put(ctx, req)
-		require.Error(t, err)
-		require.Nil(t, resp)
-	})
-
-	t.Run("correct response", func(t *testing.T) {
-		req := testPutRequest()
-
-		require.NoError(t, signature.SignServiceMessage(cliKey, req))
-
-		resp := testPutResponse()
-
-		c, err := container.NewClient(
-			container.WithGRPCServiceClient(
-				&testGRPCClient{
-					server: &testGRPCServer{
-						key:     srvKey,
-						putResp: resp,
-					},
-				},
-			),
-		)
-		require.NoError(t, err)
-
-		r, err := c.Put(ctx, req)
-		require.NoError(t, err)
-
-		require.NoError(t, signature.VerifyServiceMessage(r))
-		require.Equal(t, resp.GetBody(), r.GetBody())
-		require.Equal(t, resp.GetMetaHeader(), r.GetMetaHeader())
-	})
-}
-
-func TestGRPCClient_Get(t *testing.T) {
-	ctx := context.TODO()
-
-	cliKey := test.DecodeKey(0)
-	srvKey := test.DecodeKey(1)
-
-	t.Run("gRPC server error", func(t *testing.T) {
-		srvErr := errors.New("test server error")
-
-		srv := &testGRPCServer{
-			err: srvErr,
-		}
-
-		cli := &testGRPCClient{
-			server: srv,
-		}
-
-		c, err := container.NewClient(container.WithGRPCServiceClient(cli))
-		require.NoError(t, err)
-
-		resp, err := c.Get(ctx, new(container.GetRequest))
-		require.True(t, errors.Is(err, srvErr))
-		require.Nil(t, resp)
-	})
-
-	t.Run("invalid request structure", func(t *testing.T) {
-		req := testGetRequest()
-
-		require.Error(t, signature.VerifyServiceMessage(req))
-
-		c, err := container.NewClient(
-			container.WithGRPCServiceClient(
-				&testGRPCClient{
-					server: new(testGRPCServer),
-				},
-			),
-		)
-		require.NoError(t, err)
-
-		resp, err := c.Get(ctx, req)
-		require.Error(t, err)
-		require.Nil(t, resp)
-	})
-
-	t.Run("correct response", func(t *testing.T) {
-		req := testGetRequest()
-
-		require.NoError(t, signature.SignServiceMessage(cliKey, req))
-
-		resp := testGetResponse()
-
-		c, err := container.NewClient(
-			container.WithGRPCServiceClient(
-				&testGRPCClient{
-					server: &testGRPCServer{
-						key:     srvKey,
-						getResp: resp,
-					},
-				},
-			),
-		)
-		require.NoError(t, err)
-
-		r, err := c.Get(ctx, req)
-		require.NoError(t, err)
-
-		require.NoError(t, signature.VerifyServiceMessage(r))
-		require.Equal(t, resp.GetBody(), r.GetBody())
-		require.Equal(t, resp.GetMetaHeader(), r.GetMetaHeader())
-	})
-}
-
-func TestGRPCClient_Delete(t *testing.T) {
-	ctx := context.TODO()
-
-	cliKey := test.DecodeKey(0)
-	srvKey := test.DecodeKey(1)
-
-	t.Run("gRPC server error", func(t *testing.T) {
-		srvErr := errors.New("test server error")
-
-		srv := &testGRPCServer{
-			err: srvErr,
-		}
-
-		cli := &testGRPCClient{
-			server: srv,
-		}
-
-		c, err := container.NewClient(container.WithGRPCServiceClient(cli))
-		require.NoError(t, err)
-
-		resp, err := c.Delete(ctx, new(container.DeleteRequest))
-		require.True(t, errors.Is(err, srvErr))
-		require.Nil(t, resp)
-	})
-
-	t.Run("invalid request structure", func(t *testing.T) {
-		req := testDelRequest()
-
-		require.Error(t, signature.VerifyServiceMessage(req))
-
-		c, err := container.NewClient(
-			container.WithGRPCServiceClient(
-				&testGRPCClient{
-					server: new(testGRPCServer),
-				},
-			),
-		)
-		require.NoError(t, err)
-
-		resp, err := c.Delete(ctx, req)
-		require.Error(t, err)
-		require.Nil(t, resp)
-	})
-
-	t.Run("correct response", func(t *testing.T) {
-		req := testDelRequest()
-
-		require.NoError(t, signature.SignServiceMessage(cliKey, req))
-
-		resp := testDelResponse()
-
-		c, err := container.NewClient(
-			container.WithGRPCServiceClient(
-				&testGRPCClient{
-					server: &testGRPCServer{
-						key:     srvKey,
-						delResp: resp,
-					},
-				},
-			),
-		)
-		require.NoError(t, err)
-
-		r, err := c.Delete(ctx, req)
-		require.NoError(t, err)
-
-		require.NoError(t, signature.VerifyServiceMessage(r))
-		require.Equal(t, resp.GetBody(), r.GetBody())
-		require.Equal(t, resp.GetMetaHeader(), r.GetMetaHeader())
-	})
-}
-
-func TestGRPCClient_List(t *testing.T) {
-	ctx := context.TODO()
-
-	cliKey := test.DecodeKey(0)
-	srvKey := test.DecodeKey(1)
-
-	t.Run("gRPC server error", func(t *testing.T) {
-		srvErr := errors.New("test server error")
-
-		srv := &testGRPCServer{
-			err: srvErr,
-		}
-
-		cli := &testGRPCClient{
-			server: srv,
-		}
-
-		c, err := container.NewClient(container.WithGRPCServiceClient(cli))
-		require.NoError(t, err)
-
-		resp, err := c.List(ctx, new(container.ListRequest))
-		require.True(t, errors.Is(err, srvErr))
-		require.Nil(t, resp)
-	})
-
-	t.Run("invalid request structure", func(t *testing.T) {
-		req := testListRequest()
-
-		require.Error(t, signature.VerifyServiceMessage(req))
-
-		c, err := container.NewClient(
-			container.WithGRPCServiceClient(
-				&testGRPCClient{
-					server: new(testGRPCServer),
-				},
-			),
-		)
-		require.NoError(t, err)
-
-		resp, err := c.List(ctx, req)
-		require.Error(t, err)
-		require.Nil(t, resp)
-	})
-
-	t.Run("correct response", func(t *testing.T) {
-		req := testListRequest()
-
-		require.NoError(t, signature.SignServiceMessage(cliKey, req))
-
-		resp := testListResponse()
-
-		c, err := container.NewClient(
-			container.WithGRPCServiceClient(
-				&testGRPCClient{
-					server: &testGRPCServer{
-						key:      srvKey,
-						listResp: resp,
-					},
-				},
-			),
-		)
-		require.NoError(t, err)
-
-		r, err := c.List(ctx, req)
-		require.NoError(t, err)
-
-		require.NoError(t, signature.VerifyServiceMessage(r))
-		require.Equal(t, resp.GetBody(), r.GetBody())
-		require.Equal(t, resp.GetMetaHeader(), r.GetMetaHeader())
-	})
-}
-
-func TestGRPCClient_SetEACL(t *testing.T) {
-	ctx := context.TODO()
-
-	cliKey := test.DecodeKey(0)
-	srvKey := test.DecodeKey(1)
-
-	t.Run("gRPC server error", func(t *testing.T) {
-		srvErr := errors.New("test server error")
-
-		srv := &testGRPCServer{
-			err: srvErr,
-		}
-
-		cli := &testGRPCClient{
-			server: srv,
-		}
-
-		c, err := container.NewClient(container.WithGRPCServiceClient(cli))
-		require.NoError(t, err)
-
-		resp, err := c.SetExtendedACL(ctx, new(container.SetExtendedACLRequest))
-		require.True(t, errors.Is(err, srvErr))
-		require.Nil(t, resp)
-	})
-	t.Run("invalid request structure", func(t *testing.T) {
-		req := testSetEACLRequest()
-
-		require.Error(t, signature.VerifyServiceMessage(req))
-
-		c, err := container.NewClient(
-			container.WithGRPCServiceClient(
-				&testGRPCClient{
-					server: new(testGRPCServer),
-				},
-			),
-		)
-		require.NoError(t, err)
-
-		resp, err := c.SetExtendedACL(ctx, req)
-		require.Error(t, err)
-		require.Nil(t, resp)
-	})
-
-	t.Run("correct response", func(t *testing.T) {
-		req := testSetEACLRequest()
-
-		require.NoError(t, signature.SignServiceMessage(cliKey, req))
-
-		resp := testSetEACLResponse()
-
-		c, err := container.NewClient(
-			container.WithGRPCServiceClient(
-				&testGRPCClient{
-					server: &testGRPCServer{
-						key:       srvKey,
-						sEaclResp: resp,
-					},
-				},
-			),
-		)
-		require.NoError(t, err)
-
-		r, err := c.SetExtendedACL(ctx, req)
-		require.NoError(t, err)
-
-		require.NoError(t, signature.VerifyServiceMessage(r))
-		require.Equal(t, resp.GetBody(), r.GetBody())
-		require.Equal(t, resp.GetMetaHeader(), r.GetMetaHeader())
-	})
-}
-
-func TestGRPCClient_GetEACL(t *testing.T) {
-	ctx := context.TODO()
-
-	cliKey := test.DecodeKey(0)
-	srvKey := test.DecodeKey(1)
-
-	t.Run("gRPC server error", func(t *testing.T) {
-		srvErr := errors.New("test server error")
-
-		srv := &testGRPCServer{
-			err: srvErr,
-		}
-
-		cli := &testGRPCClient{
-			server: srv,
-		}
-
-		c, err := container.NewClient(container.WithGRPCServiceClient(cli))
-		require.NoError(t, err)
-
-		resp, err := c.GetExtendedACL(ctx, new(container.GetExtendedACLRequest))
-		require.True(t, errors.Is(err, srvErr))
-		require.Nil(t, resp)
-	})
-	t.Run("invalid request structure", func(t *testing.T) {
-		req := testGetEACLRequest()
-
-		require.Error(t, signature.VerifyServiceMessage(req))
-
-		c, err := container.NewClient(
-			container.WithGRPCServiceClient(
-				&testGRPCClient{
-					server: new(testGRPCServer),
-				},
-			),
-		)
-		require.NoError(t, err)
-
-		resp, err := c.GetExtendedACL(ctx, req)
-		require.Error(t, err)
-		require.Nil(t, resp)
-	})
-
-	t.Run("correct response", func(t *testing.T) {
-		req := testGetEACLRequest()
-
-		require.NoError(t, signature.SignServiceMessage(cliKey, req))
-
-		resp := testGetEACLResponse()
-
-		c, err := container.NewClient(
-			container.WithGRPCServiceClient(
-				&testGRPCClient{
-					server: &testGRPCServer{
-						key:       srvKey,
-						gEaclResp: resp,
-					},
-				},
-			),
-		)
-		require.NoError(t, err)
-
-		r, err := c.GetExtendedACL(ctx, req)
-		require.NoError(t, err)
-
-		require.NoError(t, signature.VerifyServiceMessage(r))
-		require.Equal(t, resp.GetBody(), r.GetBody())
-		require.Equal(t, resp.GetMetaHeader(), r.GetMetaHeader())
-	})
-}
-
-func TestGRPCClient_AnnounceUsedSpace(t *testing.T) {
-	ctx := context.TODO()
-
-	cliKey := test.DecodeKey(0)
-	srvKey := test.DecodeKey(1)
-
-	t.Run("gRPC server error", func(t *testing.T) {
-		srvErr := errors.New("test server error")
-
-		srv := &testGRPCServer{
-			err: srvErr,
-		}
-
-		cli := &testGRPCClient{
-			server: srv,
-		}
-
-		c, err := container.NewClient(container.WithGRPCServiceClient(cli))
-		require.NoError(t, err)
-
-		resp, err := c.AnnounceUsedSpace(ctx, new(container.AnnounceUsedSpaceRequest))
-		require.True(t, errors.Is(err, srvErr))
-		require.Nil(t, resp)
-	})
-	t.Run("invalid request structure", func(t *testing.T) {
-		req := testAnnounceRequest()
-
-		require.Error(t, signature.VerifyServiceMessage(req))
-
-		c, err := container.NewClient(
-			container.WithGRPCServiceClient(
-				&testGRPCClient{
-					server: new(testGRPCServer),
-				},
-			),
-		)
-		require.NoError(t, err)
-
-		resp, err := c.AnnounceUsedSpace(ctx, req)
-		require.Error(t, err)
-		require.Nil(t, resp)
-	})
-
-	t.Run("correct response", func(t *testing.T) {
-		req := testAnnounceRequest()
-
-		require.NoError(t, signature.SignServiceMessage(cliKey, req))
-
-		resp := testAnnounceResponse()
-
-		c, err := container.NewClient(
-			container.WithGRPCServiceClient(
-				&testGRPCClient{
-					server: &testGRPCServer{
-						key:          srvKey,
-						announceResp: resp,
-					},
-				},
-			),
-		)
-		require.NoError(t, err)
-
-		r, err := c.AnnounceUsedSpace(ctx, req)
-		require.NoError(t, err)
-
-		require.NoError(t, signature.VerifyServiceMessage(r))
-		require.Equal(t, resp.GetBody(), r.GetBody())
-		require.Equal(t, resp.GetMetaHeader(), r.GetMetaHeader())
-	})
-}
diff --git a/v2/container/test/generate.go b/v2/container/test/generate.go
new file mode 100644
index 0000000..032bde7
--- /dev/null
+++ b/v2/container/test/generate.go
@@ -0,0 +1,318 @@
+package containertest
+
+import (
+	acltest "github.com/nspcc-dev/neofs-api-go/v2/acl/test"
+	"github.com/nspcc-dev/neofs-api-go/v2/container"
+	netmaptest "github.com/nspcc-dev/neofs-api-go/v2/netmap/test"
+	refstest "github.com/nspcc-dev/neofs-api-go/v2/refs/test"
+	sessiontest "github.com/nspcc-dev/neofs-api-go/v2/session/test"
+)
+
+func GenerateAttribute(empty bool) *container.Attribute {
+	m := new(container.Attribute)
+
+	if !empty {
+		m.SetKey("key")
+		m.SetValue("val")
+	}
+
+	return m
+}
+
+func GenerateAttributes(empty bool) (res []*container.Attribute) {
+	if !empty {
+		res = append(res,
+			GenerateAttribute(false),
+			GenerateAttribute(false),
+		)
+	}
+
+	return
+}
+
+func GenerateContainer(empty bool) *container.Container {
+	m := new(container.Container)
+
+	if !empty {
+		m.SetBasicACL(12)
+		m.SetNonce([]byte{1, 2, 3})
+	}
+
+	m.SetOwnerID(refstest.GenerateOwnerID(empty))
+	m.SetVersion(refstest.GenerateVersion(empty))
+	m.SetAttributes(GenerateAttributes(empty))
+	m.SetPlacementPolicy(netmaptest.GeneratePlacementPolicy(empty))
+
+	return m
+}
+
+func GeneratePutRequestBody(empty bool) *container.PutRequestBody {
+	m := new(container.PutRequestBody)
+
+	m.SetContainer(GenerateContainer(empty))
+	m.SetSignature(refstest.GenerateSignature(empty))
+
+	return m
+}
+
+func GeneratePutRequest(empty bool) *container.PutRequest {
+	m := new(container.PutRequest)
+
+	m.SetBody(GeneratePutRequestBody(empty))
+	m.SetMetaHeader(sessiontest.GenerateRequestMetaHeader(empty))
+	m.SetVerificationHeader(sessiontest.GenerateRequestVerificationHeader(empty))
+
+	return m
+}
+
+func GeneratePutResponseBody(empty bool) *container.PutResponseBody {
+	m := new(container.PutResponseBody)
+
+	m.SetContainerID(refstest.GenerateContainerID(empty))
+
+	return m
+}
+
+func GeneratePutResponse(empty bool) *container.PutResponse {
+	m := new(container.PutResponse)
+
+	m.SetBody(GeneratePutResponseBody(empty))
+	m.SetMetaHeader(sessiontest.GenerateResponseMetaHeader(empty))
+	m.SetVerificationHeader(sessiontest.GenerateResponseVerificationHeader(empty))
+
+	return m
+}
+
+func GenerateGetRequestBody(empty bool) *container.GetRequestBody {
+	m := new(container.GetRequestBody)
+
+	m.SetContainerID(refstest.GenerateContainerID(empty))
+
+	return m
+}
+
+func GenerateGetRequest(empty bool) *container.GetRequest {
+	m := new(container.GetRequest)
+
+	m.SetBody(GenerateGetRequestBody(empty))
+	m.SetMetaHeader(sessiontest.GenerateRequestMetaHeader(empty))
+	m.SetVerificationHeader(sessiontest.GenerateRequestVerificationHeader(empty))
+
+	return m
+}
+
+func GenerateGetResponseBody(empty bool) *container.GetResponseBody {
+	m := new(container.GetResponseBody)
+
+	m.SetContainer(GenerateContainer(empty))
+
+	return m
+}
+
+func GenerateGetResponse(empty bool) *container.GetResponse {
+	m := new(container.GetResponse)
+
+	m.SetBody(GenerateGetResponseBody(empty))
+	m.SetMetaHeader(sessiontest.GenerateResponseMetaHeader(empty))
+	m.SetVerificationHeader(sessiontest.GenerateResponseVerificationHeader(empty))
+
+	return m
+}
+
+func GenerateDeleteRequestBody(empty bool) *container.DeleteRequestBody {
+	m := new(container.DeleteRequestBody)
+
+	m.SetContainerID(refstest.GenerateContainerID(empty))
+	m.SetSignature(refstest.GenerateSignature(empty))
+
+	return m
+}
+
+func GenerateDeleteRequest(empty bool) *container.DeleteRequest {
+	m := new(container.DeleteRequest)
+
+	m.SetBody(GenerateDeleteRequestBody(empty))
+	m.SetMetaHeader(sessiontest.GenerateRequestMetaHeader(empty))
+	m.SetVerificationHeader(sessiontest.GenerateRequestVerificationHeader(empty))
+
+	return m
+}
+
+func GenerateDeleteResponseBody(empty bool) *container.DeleteResponseBody {
+	m := new(container.DeleteResponseBody)
+
+	return m
+}
+
+func GenerateDeleteResponse(empty bool) *container.DeleteResponse {
+	m := new(container.DeleteResponse)
+
+	m.SetBody(GenerateDeleteResponseBody(empty))
+	m.SetMetaHeader(sessiontest.GenerateResponseMetaHeader(empty))
+	m.SetVerificationHeader(sessiontest.GenerateResponseVerificationHeader(empty))
+
+	return m
+}
+
+func GenerateListRequestBody(empty bool) *container.ListRequestBody {
+	m := new(container.ListRequestBody)
+
+	m.SetOwnerID(refstest.GenerateOwnerID(empty))
+
+	return m
+}
+
+func GenerateListRequest(empty bool) *container.ListRequest {
+	m := new(container.ListRequest)
+
+	m.SetBody(GenerateListRequestBody(empty))
+	m.SetMetaHeader(sessiontest.GenerateRequestMetaHeader(empty))
+	m.SetVerificationHeader(sessiontest.GenerateRequestVerificationHeader(empty))
+
+	return m
+}
+
+func GenerateListResponseBody(empty bool) *container.ListResponseBody {
+	m := new(container.ListResponseBody)
+
+	m.SetContainerIDs(refstest.GenerateContainerIDs(empty))
+
+	return m
+}
+
+func GenerateListResponse(empty bool) *container.ListResponse {
+	m := new(container.ListResponse)
+
+	m.SetBody(GenerateListResponseBody(empty))
+	m.SetMetaHeader(sessiontest.GenerateResponseMetaHeader(empty))
+	m.SetVerificationHeader(sessiontest.GenerateResponseVerificationHeader(empty))
+
+	return m
+}
+
+func GenerateSetExtendedACLRequestBody(empty bool) *container.SetExtendedACLRequestBody {
+	m := new(container.SetExtendedACLRequestBody)
+
+	m.SetEACL(acltest.GenerateTable(empty))
+	m.SetSignature(refstest.GenerateSignature(empty))
+
+	return m
+}
+
+func GenerateSetExtendedACLRequest(empty bool) *container.SetExtendedACLRequest {
+	m := new(container.SetExtendedACLRequest)
+
+	m.SetBody(GenerateSetExtendedACLRequestBody(empty))
+	m.SetMetaHeader(sessiontest.GenerateRequestMetaHeader(empty))
+	m.SetVerificationHeader(sessiontest.GenerateRequestVerificationHeader(empty))
+
+	return m
+}
+
+func GenerateSetExtendedACLResponseBody(empty bool) *container.SetExtendedACLResponseBody {
+	m := new(container.SetExtendedACLResponseBody)
+
+	return m
+}
+
+func GenerateSetExtendedACLResponse(empty bool) *container.SetExtendedACLResponse {
+	m := new(container.SetExtendedACLResponse)
+
+	m.SetBody(GenerateSetExtendedACLResponseBody(empty))
+	m.SetMetaHeader(sessiontest.GenerateResponseMetaHeader(empty))
+	m.SetVerificationHeader(sessiontest.GenerateResponseVerificationHeader(empty))
+
+	return m
+}
+
+func GenerateGetExtendedACLRequestBody(empty bool) *container.GetExtendedACLRequestBody {
+	m := new(container.GetExtendedACLRequestBody)
+
+	m.SetContainerID(refstest.GenerateContainerID(empty))
+
+	return m
+}
+
+func GenerateGetExtendedACLRequest(empty bool) *container.GetExtendedACLRequest {
+	m := new(container.GetExtendedACLRequest)
+
+	m.SetBody(GenerateGetExtendedACLRequestBody(empty))
+	m.SetMetaHeader(sessiontest.GenerateRequestMetaHeader(empty))
+	m.SetVerificationHeader(sessiontest.GenerateRequestVerificationHeader(empty))
+
+	return m
+}
+
+func GenerateGetExtendedACLResponseBody(empty bool) *container.GetExtendedACLResponseBody {
+	m := new(container.GetExtendedACLResponseBody)
+
+	m.SetEACL(acltest.GenerateTable(empty))
+	m.SetSignature(refstest.GenerateSignature(empty))
+
+	return m
+}
+
+func GenerateGetExtendedACLResponse(empty bool) *container.GetExtendedACLResponse {
+	m := new(container.GetExtendedACLResponse)
+
+	m.SetBody(GenerateGetExtendedACLResponseBody(empty))
+	m.SetMetaHeader(sessiontest.GenerateResponseMetaHeader(empty))
+	m.SetVerificationHeader(sessiontest.GenerateResponseVerificationHeader(empty))
+
+	return m
+}
+
+func GenerateUsedSpaceAnnouncement(empty bool) *container.UsedSpaceAnnouncement {
+	m := new(container.UsedSpaceAnnouncement)
+
+	m.SetContainerID(refstest.GenerateContainerID(empty))
+	m.SetEpoch(1)
+	m.SetUsedSpace(2)
+
+	return m
+}
+
+func GenerateUsedSpaceAnnouncements(empty bool) (res []*container.UsedSpaceAnnouncement) {
+	if !empty {
+		res = append(res,
+			GenerateUsedSpaceAnnouncement(false),
+			GenerateUsedSpaceAnnouncement(false),
+		)
+	}
+
+	return
+}
+
+func GenerateAnnounceUsedSpaceRequestBody(empty bool) *container.AnnounceUsedSpaceRequestBody {
+	m := new(container.AnnounceUsedSpaceRequestBody)
+
+	m.SetAnnouncements(GenerateUsedSpaceAnnouncements(empty))
+
+	return m
+}
+
+func GenerateAnnounceUsedSpaceRequest(empty bool) *container.AnnounceUsedSpaceRequest {
+	m := new(container.AnnounceUsedSpaceRequest)
+
+	m.SetBody(GenerateAnnounceUsedSpaceRequestBody(empty))
+	m.SetMetaHeader(sessiontest.GenerateRequestMetaHeader(empty))
+	m.SetVerificationHeader(sessiontest.GenerateRequestVerificationHeader(empty))
+
+	return m
+}
+
+func GenerateAnnounceUsedSpaceResponseBody(empty bool) *container.AnnounceUsedSpaceResponseBody {
+	m := new(container.AnnounceUsedSpaceResponseBody)
+
+	return m
+}
+
+func GenerateAnnounceUsedSpaceResponse(empty bool) *container.AnnounceUsedSpaceResponse {
+	m := new(container.AnnounceUsedSpaceResponse)
+
+	m.SetBody(GenerateAnnounceUsedSpaceResponseBody(empty))
+	m.SetMetaHeader(sessiontest.GenerateResponseMetaHeader(empty))
+	m.SetVerificationHeader(sessiontest.GenerateResponseVerificationHeader(empty))
+
+	return m
+}
diff --git a/v2/container/types.go b/v2/container/types.go
index 004ddd7..a790f03 100644
--- a/v2/container/types.go
+++ b/v2/container/types.go
@@ -30,11 +30,22 @@ type PutRequestBody struct {
 
 	sig *refs.Signature
 }
+type PutRequest struct {
+	body *PutRequestBody
+
+	session.RequestHeaders
+}
 
 type PutResponseBody struct {
 	cid *refs.ContainerID
 }
 
+type PutResponse struct {
+	body *PutResponseBody
+
+	session.ResponseHeaders
+}
+
 type GetRequestBody struct {
 	cid *refs.ContainerID
 }
@@ -42,9 +53,7 @@ type GetRequestBody struct {
 type GetRequest struct {
 	body *GetRequestBody
 
-	metaHeader *session.RequestMetaHeader
-
-	verifyHeader *session.RequestVerificationHeader
+	session.RequestHeaders
 }
 
 type GetResponseBody struct {
@@ -54,9 +63,7 @@ type GetResponseBody struct {
 type GetResponse struct {
 	body *GetResponseBody
 
-	metaHeader *session.ResponseMetaHeader
-
-	verifyHeader *session.ResponseVerificationHeader
+	session.ResponseHeaders
 }
 
 type DeleteRequestBody struct {
@@ -65,34 +72,82 @@ type DeleteRequestBody struct {
 	sig *refs.Signature
 }
 
+type DeleteRequest struct {
+	body *DeleteRequestBody
+
+	session.RequestHeaders
+}
+
 type DeleteResponseBody struct{}
 
+type DeleteResponse struct {
+	body *DeleteResponseBody
+
+	session.ResponseHeaders
+}
+
 type ListRequestBody struct {
 	ownerID *refs.OwnerID
 }
 
+type ListRequest struct {
+	body *ListRequestBody
+
+	session.RequestHeaders
+}
+
 type ListResponseBody struct {
 	cidList []*refs.ContainerID
 }
 
+type ListResponse struct {
+	body *ListResponseBody
+
+	session.ResponseHeaders
+}
+
 type SetExtendedACLRequestBody struct {
 	eacl *acl.Table
 
 	sig *refs.Signature
 }
 
+type SetExtendedACLRequest struct {
+	body *SetExtendedACLRequestBody
+
+	session.RequestHeaders
+}
+
 type SetExtendedACLResponseBody struct{}
 
+type SetExtendedACLResponse struct {
+	body *SetExtendedACLResponseBody
+
+	session.ResponseHeaders
+}
+
 type GetExtendedACLRequestBody struct {
 	cid *refs.ContainerID
 }
 
+type GetExtendedACLRequest struct {
+	body *GetExtendedACLRequestBody
+
+	session.RequestHeaders
+}
+
 type GetExtendedACLResponseBody struct {
 	eacl *acl.Table
 
 	sig *refs.Signature
 }
 
+type GetExtendedACLResponse struct {
+	body *GetExtendedACLResponseBody
+
+	session.ResponseHeaders
+}
+
 type UsedSpaceAnnouncement struct {
 	epoch uint64
 
@@ -105,8 +160,20 @@ type AnnounceUsedSpaceRequestBody struct {
 	announcements []*UsedSpaceAnnouncement
 }
 
+type AnnounceUsedSpaceRequest struct {
+	body *AnnounceUsedSpaceRequestBody
+
+	session.RequestHeaders
+}
+
 type AnnounceUsedSpaceResponseBody struct{}
 
+type AnnounceUsedSpaceResponse struct {
+	body *AnnounceUsedSpaceResponseBody
+
+	session.ResponseHeaders
+}
+
 func (a *Attribute) GetKey() string {
 	if a != nil {
 		return a.key
@@ -261,34 +328,6 @@ func (r *PutRequest) SetBody(v *PutRequestBody) {
 	}
 }
 
-func (r *PutRequest) GetMetaHeader() *session.RequestMetaHeader {
-	if r != nil {
-		return r.metaHeader
-	}
-
-	return nil
-}
-
-func (r *PutRequest) SetMetaHeader(v *session.RequestMetaHeader) {
-	if r != nil {
-		r.metaHeader = v
-	}
-}
-
-func (r *PutRequest) GetVerificationHeader() *session.RequestVerificationHeader {
-	if r != nil {
-		return r.verifyHeader
-	}
-
-	return nil
-}
-
-func (r *PutRequest) SetVerificationHeader(v *session.RequestVerificationHeader) {
-	if r != nil {
-		r.verifyHeader = v
-	}
-}
-
 func (r *PutResponseBody) GetContainerID() *refs.ContainerID {
 	if r != nil {
 		return r.cid
@@ -317,34 +356,6 @@ func (r *PutResponse) SetBody(v *PutResponseBody) {
 	}
 }
 
-func (r *PutResponse) GetMetaHeader() *session.ResponseMetaHeader {
-	if r != nil {
-		return r.metaHeader
-	}
-
-	return nil
-}
-
-func (r *PutResponse) SetMetaHeader(v *session.ResponseMetaHeader) {
-	if r != nil {
-		r.metaHeader = v
-	}
-}
-
-func (r *PutResponse) GetVerificationHeader() *session.ResponseVerificationHeader {
-	if r != nil {
-		return r.verifyHeader
-	}
-
-	return nil
-}
-
-func (r *PutResponse) SetVerificationHeader(v *session.ResponseVerificationHeader) {
-	if r != nil {
-		r.verifyHeader = v
-	}
-}
-
 func (r *GetRequestBody) GetContainerID() *refs.ContainerID {
 	if r != nil {
 		return r.cid
@@ -373,34 +384,6 @@ func (r *GetRequest) SetBody(v *GetRequestBody) {
 	}
 }
 
-func (r *GetRequest) GetMetaHeader() *session.RequestMetaHeader {
-	if r != nil {
-		return r.metaHeader
-	}
-
-	return nil
-}
-
-func (r *GetRequest) SetMetaHeader(v *session.RequestMetaHeader) {
-	if r != nil {
-		r.metaHeader = v
-	}
-}
-
-func (r *GetRequest) GetVerificationHeader() *session.RequestVerificationHeader {
-	if r != nil {
-		return r.verifyHeader
-	}
-
-	return nil
-}
-
-func (r *GetRequest) SetVerificationHeader(v *session.RequestVerificationHeader) {
-	if r != nil {
-		r.verifyHeader = v
-	}
-}
-
 func (r *GetResponseBody) GetContainer() *Container {
 	if r != nil {
 		return r.cnr
@@ -429,34 +412,6 @@ func (r *GetResponse) SetBody(v *GetResponseBody) {
 	}
 }
 
-func (r *GetResponse) GetMetaHeader() *session.ResponseMetaHeader {
-	if r != nil {
-		return r.metaHeader
-	}
-
-	return nil
-}
-
-func (r *GetResponse) SetMetaHeader(v *session.ResponseMetaHeader) {
-	if r != nil {
-		r.metaHeader = v
-	}
-}
-
-func (r *GetResponse) GetVerificationHeader() *session.ResponseVerificationHeader {
-	if r != nil {
-		return r.verifyHeader
-	}
-
-	return nil
-}
-
-func (r *GetResponse) SetVerificationHeader(v *session.ResponseVerificationHeader) {
-	if r != nil {
-		r.verifyHeader = v
-	}
-}
-
 func (r *DeleteRequestBody) GetContainerID() *refs.ContainerID {
 	if r != nil {
 		return r.cid
@@ -499,34 +454,6 @@ func (r *DeleteRequest) SetBody(v *DeleteRequestBody) {
 	}
 }
 
-func (r *DeleteRequest) GetMetaHeader() *session.RequestMetaHeader {
-	if r != nil {
-		return r.metaHeader
-	}
-
-	return nil
-}
-
-func (r *DeleteRequest) SetMetaHeader(v *session.RequestMetaHeader) {
-	if r != nil {
-		r.metaHeader = v
-	}
-}
-
-func (r *DeleteRequest) GetVerificationHeader() *session.RequestVerificationHeader {
-	if r != nil {
-		return r.verifyHeader
-	}
-
-	return nil
-}
-
-func (r *DeleteRequest) SetVerificationHeader(v *session.RequestVerificationHeader) {
-	if r != nil {
-		r.verifyHeader = v
-	}
-}
-
 func (r *DeleteResponse) GetBody() *DeleteResponseBody {
 	if r != nil {
 		return r.body
@@ -541,34 +468,6 @@ func (r *DeleteResponse) SetBody(v *DeleteResponseBody) {
 	}
 }
 
-func (r *DeleteResponse) GetMetaHeader() *session.ResponseMetaHeader {
-	if r != nil {
-		return r.metaHeader
-	}
-
-	return nil
-}
-
-func (r *DeleteResponse) SetMetaHeader(v *session.ResponseMetaHeader) {
-	if r != nil {
-		r.metaHeader = v
-	}
-}
-
-func (r *DeleteResponse) GetVerificationHeader() *session.ResponseVerificationHeader {
-	if r != nil {
-		return r.verifyHeader
-	}
-
-	return nil
-}
-
-func (r *DeleteResponse) SetVerificationHeader(v *session.ResponseVerificationHeader) {
-	if r != nil {
-		r.verifyHeader = v
-	}
-}
-
 func (r *ListRequestBody) GetOwnerID() *refs.OwnerID {
 	if r != nil {
 		return r.ownerID
@@ -597,34 +496,6 @@ func (r *ListRequest) SetBody(v *ListRequestBody) {
 	}
 }
 
-func (r *ListRequest) GetMetaHeader() *session.RequestMetaHeader {
-	if r != nil {
-		return r.metaHeader
-	}
-
-	return nil
-}
-
-func (r *ListRequest) SetMetaHeader(v *session.RequestMetaHeader) {
-	if r != nil {
-		r.metaHeader = v
-	}
-}
-
-func (r *ListRequest) GetVerificationHeader() *session.RequestVerificationHeader {
-	if r != nil {
-		return r.verifyHeader
-	}
-
-	return nil
-}
-
-func (r *ListRequest) SetVerificationHeader(v *session.RequestVerificationHeader) {
-	if r != nil {
-		r.verifyHeader = v
-	}
-}
-
 func (r *ListResponseBody) GetContainerIDs() []*refs.ContainerID {
 	if r != nil {
 		return r.cidList
@@ -653,34 +524,6 @@ func (r *ListResponse) SetBody(v *ListResponseBody) {
 	}
 }
 
-func (r *ListResponse) GetMetaHeader() *session.ResponseMetaHeader {
-	if r != nil {
-		return r.metaHeader
-	}
-
-	return nil
-}
-
-func (r *ListResponse) SetMetaHeader(v *session.ResponseMetaHeader) {
-	if r != nil {
-		r.metaHeader = v
-	}
-}
-
-func (r *ListResponse) GetVerificationHeader() *session.ResponseVerificationHeader {
-	if r != nil {
-		return r.verifyHeader
-	}
-
-	return nil
-}
-
-func (r *ListResponse) SetVerificationHeader(v *session.ResponseVerificationHeader) {
-	if r != nil {
-		r.verifyHeader = v
-	}
-}
-
 func (r *SetExtendedACLRequestBody) GetEACL() *acl.Table {
 	if r != nil {
 		return r.eacl
@@ -723,34 +566,6 @@ func (r *SetExtendedACLRequest) SetBody(v *SetExtendedACLRequestBody) {
 	}
 }
 
-func (r *SetExtendedACLRequest) GetMetaHeader() *session.RequestMetaHeader {
-	if r != nil {
-		return r.metaHeader
-	}
-
-	return nil
-}
-
-func (r *SetExtendedACLRequest) SetMetaHeader(v *session.RequestMetaHeader) {
-	if r != nil {
-		r.metaHeader = v
-	}
-}
-
-func (r *SetExtendedACLRequest) GetVerificationHeader() *session.RequestVerificationHeader {
-	if r != nil {
-		return r.verifyHeader
-	}
-
-	return nil
-}
-
-func (r *SetExtendedACLRequest) SetVerificationHeader(v *session.RequestVerificationHeader) {
-	if r != nil {
-		r.verifyHeader = v
-	}
-}
-
 func (r *SetExtendedACLResponse) GetBody() *SetExtendedACLResponseBody {
 	if r != nil {
 		return r.body
@@ -765,34 +580,6 @@ func (r *SetExtendedACLResponse) SetBody(v *SetExtendedACLResponseBody) {
 	}
 }
 
-func (r *SetExtendedACLResponse) GetMetaHeader() *session.ResponseMetaHeader {
-	if r != nil {
-		return r.metaHeader
-	}
-
-	return nil
-}
-
-func (r *SetExtendedACLResponse) SetMetaHeader(v *session.ResponseMetaHeader) {
-	if r != nil {
-		r.metaHeader = v
-	}
-}
-
-func (r *SetExtendedACLResponse) GetVerificationHeader() *session.ResponseVerificationHeader {
-	if r != nil {
-		return r.verifyHeader
-	}
-
-	return nil
-}
-
-func (r *SetExtendedACLResponse) SetVerificationHeader(v *session.ResponseVerificationHeader) {
-	if r != nil {
-		r.verifyHeader = v
-	}
-}
-
 func (r *GetExtendedACLRequestBody) GetContainerID() *refs.ContainerID {
 	if r != nil {
 		return r.cid
@@ -821,34 +608,6 @@ func (r *GetExtendedACLRequest) SetBody(v *GetExtendedACLRequestBody) {
 	}
 }
 
-func (r *GetExtendedACLRequest) GetMetaHeader() *session.RequestMetaHeader {
-	if r != nil {
-		return r.metaHeader
-	}
-
-	return nil
-}
-
-func (r *GetExtendedACLRequest) SetMetaHeader(v *session.RequestMetaHeader) {
-	if r != nil {
-		r.metaHeader = v
-	}
-}
-
-func (r *GetExtendedACLRequest) GetVerificationHeader() *session.RequestVerificationHeader {
-	if r != nil {
-		return r.verifyHeader
-	}
-
-	return nil
-}
-
-func (r *GetExtendedACLRequest) SetVerificationHeader(v *session.RequestVerificationHeader) {
-	if r != nil {
-		r.verifyHeader = v
-	}
-}
-
 func (r *GetExtendedACLResponseBody) GetEACL() *acl.Table {
 	if r != nil {
 		return r.eacl
@@ -891,34 +650,6 @@ func (r *GetExtendedACLResponse) SetBody(v *GetExtendedACLResponseBody) {
 	}
 }
 
-func (r *GetExtendedACLResponse) GetMetaHeader() *session.ResponseMetaHeader {
-	if r != nil {
-		return r.metaHeader
-	}
-
-	return nil
-}
-
-func (r *GetExtendedACLResponse) SetMetaHeader(v *session.ResponseMetaHeader) {
-	if r != nil {
-		r.metaHeader = v
-	}
-}
-
-func (r *GetExtendedACLResponse) GetVerificationHeader() *session.ResponseVerificationHeader {
-	if r != nil {
-		return r.verifyHeader
-	}
-
-	return nil
-}
-
-func (r *GetExtendedACLResponse) SetVerificationHeader(v *session.ResponseVerificationHeader) {
-	if r != nil {
-		r.verifyHeader = v
-	}
-}
-
 func (a *UsedSpaceAnnouncement) GetEpoch() uint64 {
 	if a != nil {
 		return a.epoch
@@ -989,34 +720,6 @@ func (r *AnnounceUsedSpaceRequest) SetBody(v *AnnounceUsedSpaceRequestBody) {
 	}
 }
 
-func (r *AnnounceUsedSpaceRequest) GetMetaHeader() *session.RequestMetaHeader {
-	if r != nil {
-		return r.metaHeader
-	}
-
-	return nil
-}
-
-func (r *AnnounceUsedSpaceRequest) SetMetaHeader(v *session.RequestMetaHeader) {
-	if r != nil {
-		r.metaHeader = v
-	}
-}
-
-func (r *AnnounceUsedSpaceRequest) GetVerificationHeader() *session.RequestVerificationHeader {
-	if r != nil {
-		return r.verifyHeader
-	}
-
-	return nil
-}
-
-func (r *AnnounceUsedSpaceRequest) SetVerificationHeader(v *session.RequestVerificationHeader) {
-	if r != nil {
-		r.verifyHeader = v
-	}
-}
-
 func (r *AnnounceUsedSpaceResponse) GetBody() *AnnounceUsedSpaceResponseBody {
 	if r != nil {
 		return r.body
@@ -1030,31 +733,3 @@ func (r *AnnounceUsedSpaceResponse) SetBody(v *AnnounceUsedSpaceResponseBody) {
 		r.body = v
 	}
 }
-
-func (r *AnnounceUsedSpaceResponse) GetMetaHeader() *session.ResponseMetaHeader {
-	if r != nil {
-		return r.metaHeader
-	}
-
-	return nil
-}
-
-func (r *AnnounceUsedSpaceResponse) SetMetaHeader(v *session.ResponseMetaHeader) {
-	if r != nil {
-		r.metaHeader = v
-	}
-}
-
-func (r *AnnounceUsedSpaceResponse) GetVerificationHeader() *session.ResponseVerificationHeader {
-	if r != nil {
-		return r.verifyHeader
-	}
-
-	return nil
-}
-
-func (r *AnnounceUsedSpaceResponse) SetVerificationHeader(v *session.ResponseVerificationHeader) {
-	if r != nil {
-		r.verifyHeader = v
-	}
-}
diff --git a/v2/netmap/client.go b/v2/netmap/client.go
deleted file mode 100644
index e00f2db..0000000
--- a/v2/netmap/client.go
+++ /dev/null
@@ -1,190 +0,0 @@
-package netmap
-
-import (
-	"context"
-
-	"github.com/nspcc-dev/neofs-api-go/v2/client"
-	netmap "github.com/nspcc-dev/neofs-api-go/v2/netmap/grpc"
-	"github.com/pkg/errors"
-	"google.golang.org/grpc"
-)
-
-// Client represents universal netmap transport client.
-type Client struct {
-	cLocalNodeInfo *localNodeInfoClient
-
-	cNetworkInfo *networkInfoClient
-}
-
-// Option represents Client option.
-type Option func(*cfg)
-
-type cfg struct {
-	proto client.Protocol
-
-	globalOpts []client.Option
-
-	gRPC cfgGRPC
-}
-
-type cfgGRPC struct {
-	serviceClient netmap.NetmapServiceClient
-
-	grpcCallOpts []grpc.CallOption
-
-	callOpts []netmap.Option
-
-	client *netmap.Client
-}
-
-type localNodeInfoClient struct {
-	requestConverter func(*LocalNodeInfoRequest) interface{}
-
-	caller func(context.Context, interface{}) (interface{}, error)
-
-	responseConverter func(interface{}) *LocalNodeInfoResponse
-}
-
-type networkInfoClient struct {
-	requestConverter func(*NetworkInfoRequest) interface{}
-
-	caller func(context.Context, interface{}) (interface{}, error)
-
-	responseConverter func(interface{}) *NetworkInfoResponse
-}
-
-// LocalNodeInfo sends LocalNodeInfoRequest over the network.
-func (c *Client) LocalNodeInfo(ctx context.Context, req *LocalNodeInfoRequest) (*LocalNodeInfoResponse, error) {
-	resp, err := c.cLocalNodeInfo.caller(ctx, c.cLocalNodeInfo.requestConverter(req))
-	if err != nil {
-		return nil, errors.Wrap(err, "could not send local node info request")
-	}
-
-	return c.cLocalNodeInfo.responseConverter(resp), nil
-}
-
-// NetworkInfo sends NetworkInfoRequest over the network.
-func (c *Client) NetworkInfo(ctx context.Context, req *NetworkInfoRequest) (*NetworkInfoResponse, error) {
-	resp, err := c.cNetworkInfo.caller(ctx, c.cNetworkInfo.requestConverter(req))
-	if err != nil {
-		return nil, errors.Wrap(err, "could not send network info request")
-	}
-
-	return c.cNetworkInfo.responseConverter(resp), nil
-}
-
-func defaultCfg() *cfg {
-	return &cfg{
-		proto: client.ProtoGRPC,
-	}
-}
-
-// NewClient is a constructor for netmap transport client.
-func NewClient(opts ...Option) (*Client, error) {
-	cfg := defaultCfg()
-
-	for i := range opts {
-		opts[i](cfg)
-	}
-
-	var err error
-
-	switch cfg.proto {
-	case client.ProtoGRPC:
-		var c *netmap.Client
-		if c, err = newGRPCClient(cfg); err != nil {
-			break
-		}
-
-		return &Client{
-			cLocalNodeInfo: &localNodeInfoClient{
-				requestConverter: func(req *LocalNodeInfoRequest) interface{} {
-					return LocalNodeInfoRequestToGRPCMessage(req)
-				},
-				caller: func(ctx context.Context, req interface{}) (interface{}, error) {
-					return c.LocalNodeInfo(ctx, req.(*netmap.LocalNodeInfoRequest))
-				},
-				responseConverter: func(resp interface{}) *LocalNodeInfoResponse {
-					return LocalNodeInfoResponseFromGRPCMessage(resp.(*netmap.LocalNodeInfoResponse))
-				},
-			},
-			cNetworkInfo: &networkInfoClient{
-				requestConverter: func(req *NetworkInfoRequest) interface{} {
-					return NetworkInfoRequestToGRPCMessage(req)
-				},
-				caller: func(ctx context.Context, req interface{}) (interface{}, error) {
-					return c.NetworkInfo(ctx, req.(*netmap.NetworkInfoRequest))
-				},
-				responseConverter: func(resp interface{}) *NetworkInfoResponse {
-					return NetworkInfoResponseFromGRPCMessage(resp.(*netmap.NetworkInfoResponse))
-				},
-			},
-		}, nil
-	default:
-		err = client.ErrProtoUnsupported
-	}
-
-	return nil, errors.Wrapf(err, "could not create %s Netmap client", cfg.proto)
-}
-
-func newGRPCClient(cfg *cfg) (*netmap.Client, error) {
-	var err error
-
-	if cfg.gRPC.client == nil {
-		if cfg.gRPC.serviceClient == nil {
-			conn, err := client.NewGRPCClientConn(cfg.globalOpts...)
-			if err != nil {
-				return nil, errors.Wrap(err, "could not open gRPC client connection")
-			}
-
-			cfg.gRPC.serviceClient = netmap.NewNetmapServiceClient(conn)
-		}
-
-		cfg.gRPC.client, err = netmap.NewClient(
-			cfg.gRPC.serviceClient,
-			append(
-				cfg.gRPC.callOpts,
-				netmap.WithCallOptions(cfg.gRPC.grpcCallOpts),
-			)...,
-		)
-	}
-
-	return cfg.gRPC.client, err
-}
-
-// WithGlobalOpts sets global client options to client.
-func WithGlobalOpts(v ...client.Option) Option {
-	return func(c *cfg) {
-		if len(v) > 0 {
-			c.globalOpts = v
-		}
-	}
-}
-
-// WithGRPCServiceClient sets existing service client.
-func WithGRPCServiceClient(v netmap.NetmapServiceClient) Option {
-	return func(c *cfg) {
-		c.gRPC.serviceClient = v
-	}
-}
-
-// WithGRPCServiceClient sets GRPC specific call options.
-func WithGRPCCallOpts(v []grpc.CallOption) Option {
-	return func(c *cfg) {
-		c.gRPC.grpcCallOpts = v
-	}
-}
-
-// WithGRPCServiceClient sets GRPC specific client options.
-func WithGRPCClientOpts(v []netmap.Option) Option {
-	return func(c *cfg) {
-		c.gRPC.callOpts = v
-	}
-}
-
-// WithGRPCServiceClient sets existing GRPC client.
-func WithGRPCClient(v *netmap.Client) Option {
-	return func(c *cfg) {
-		c.gRPC.client = v
-	}
-}
diff --git a/v2/netmap/convert.go b/v2/netmap/convert.go
index caf30bd..3c64495 100644
--- a/v2/netmap/convert.go
+++ b/v2/netmap/convert.go
@@ -1,167 +1,252 @@
 package netmap
 
 import (
+	"github.com/nspcc-dev/neofs-api-go/rpc/grpc"
+	"github.com/nspcc-dev/neofs-api-go/rpc/message"
 	netmap "github.com/nspcc-dev/neofs-api-go/v2/netmap/grpc"
 	"github.com/nspcc-dev/neofs-api-go/v2/refs"
-	"github.com/nspcc-dev/neofs-api-go/v2/session"
+	refsGRPC "github.com/nspcc-dev/neofs-api-go/v2/refs/grpc"
 )
 
-func FilterToGRPCMessage(f *Filter) *netmap.Filter {
-	if f == nil {
-		return nil
+func (f *Filter) ToGRPCMessage() grpc.Message {
+	var m *netmap.Filter
+
+	if f != nil {
+		m = new(netmap.Filter)
+
+		m.SetKey(f.key)
+		m.SetValue(f.value)
+		m.SetName(f.name)
+		m.SetOp(OperationToGRPCMessage(f.op))
+		m.SetFilters(FiltersToGRPC(f.filters))
 	}
 
-	m := new(netmap.Filter)
-
-	m.SetName(f.GetName())
-	m.SetKey(f.GetKey())
-	m.SetValue(f.GetValue())
-	m.SetOp(OperationToGRPCMessage(f.GetOp()))
-
-	filters := make([]*netmap.Filter, 0, len(f.GetFilters()))
-	for _, filter := range f.GetFilters() {
-		filters = append(filters, FilterToGRPCMessage(filter))
-	}
-	m.SetFilters(filters)
-
 	return m
 }
 
-func FilterFromGRPCMessage(m *netmap.Filter) *Filter {
-	if m == nil {
-		return nil
+func (f *Filter) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*netmap.Filter)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
 	}
 
-	f := new(Filter)
-	f.SetName(m.GetName())
-	f.SetKey(m.GetKey())
-	f.SetValue(m.GetValue())
-	f.SetOp(OperationFromGRPCMessage(m.GetOp()))
+	var err error
 
-	filters := make([]*Filter, 0, len(f.GetFilters()))
-	for _, filter := range m.GetFilters() {
-		filters = append(filters, FilterFromGRPCMessage(filter))
+	f.filters, err = FiltersFromGRPC(v.GetFilters())
+	if err != nil {
+		return err
 	}
-	f.SetFilters(filters)
 
-	return f
+	f.key = v.GetKey()
+	f.value = v.GetValue()
+	f.name = v.GetName()
+	f.op = OperationFromGRPCMessage(v.GetOp())
+
+	return nil
 }
 
-func SelectorToGRPCMessage(s *Selector) *netmap.Selector {
-	if s == nil {
-		return nil
+func FiltersToGRPC(fs []*Filter) (res []*netmap.Filter) {
+	if fs != nil {
+		res = make([]*netmap.Filter, 0, len(fs))
+
+		for i := range fs {
+			res = append(res, fs[i].ToGRPCMessage().(*netmap.Filter))
+		}
 	}
 
-	m := new(netmap.Selector)
+	return
+}
 
-	m.SetName(s.GetName())
-	m.SetCount(s.GetCount())
-	m.SetClause(ClauseToGRPCMessage(s.GetClause()))
-	m.SetFilter(s.GetFilter())
-	m.SetAttribute(s.GetAttribute())
+func FiltersFromGRPC(fs []*netmap.Filter) (res []*Filter, err error) {
+	if fs != nil {
+		res = make([]*Filter, 0, len(fs))
+
+		for i := range fs {
+			var f *Filter
+
+			if fs[i] != nil {
+				f = new(Filter)
+
+				err = f.FromGRPCMessage(fs[i])
+				if err != nil {
+					return
+				}
+			}
+
+			res = append(res, f)
+		}
+	}
+
+	return
+}
+
+func (s *Selector) ToGRPCMessage() grpc.Message {
+	var m *netmap.Selector
+
+	if s != nil {
+		m = new(netmap.Selector)
+
+		m.SetName(s.name)
+		m.SetAttribute(s.attribute)
+		m.SetFilter(s.filter)
+		m.SetCount(s.count)
+		m.SetClause(ClauseToGRPCMessage(s.clause))
+	}
 
 	return m
 }
 
-func SelectorFromGRPCMessage(m *netmap.Selector) *Selector {
-	if m == nil {
-		return nil
+func (s *Selector) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*netmap.Selector)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
 	}
 
-	s := new(Selector)
+	s.name = v.GetName()
+	s.attribute = v.GetAttribute()
+	s.filter = v.GetFilter()
+	s.count = v.GetCount()
+	s.clause = ClauseFromGRPCMessage(v.GetClause())
 
-	s.SetName(m.GetName())
-	s.SetCount(m.GetCount())
-	s.SetClause(ClauseFromGRPCMessage(m.GetClause()))
-	s.SetFilter(m.GetFilter())
-	s.SetAttribute(m.GetAttribute())
-
-	return s
+	return nil
 }
 
-func ReplicaToGRPCMessage(r *Replica) *netmap.Replica {
-	if r == nil {
-		return nil
+func SelectorsToGRPC(ss []*Selector) (res []*netmap.Selector) {
+	if ss != nil {
+		res = make([]*netmap.Selector, 0, len(ss))
+
+		for i := range ss {
+			res = append(res, ss[i].ToGRPCMessage().(*netmap.Selector))
+		}
 	}
 
-	m := new(netmap.Replica)
+	return
+}
 
-	m.SetCount(r.GetCount())
-	m.SetSelector(r.GetSelector())
+func SelectorsFromGRPC(ss []*netmap.Selector) (res []*Selector, err error) {
+	if ss != nil {
+		res = make([]*Selector, 0, len(ss))
+
+		for i := range ss {
+			var s *Selector
+
+			if ss[i] != nil {
+				s = new(Selector)
+
+				err = s.FromGRPCMessage(ss[i])
+				if err != nil {
+					return
+				}
+			}
+
+			res = append(res, s)
+		}
+	}
+
+	return
+}
+
+func (r *Replica) ToGRPCMessage() grpc.Message {
+	var m *netmap.Replica
+
+	if r != nil {
+		m = new(netmap.Replica)
+
+		m.SetSelector(r.selector)
+		m.SetCount(r.count)
+	}
 
 	return m
 }
 
-func ReplicaFromGRPCMessage(m *netmap.Replica) *Replica {
-	if m == nil {
-		return nil
+func (r *Replica) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*netmap.Replica)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
 	}
 
-	r := new(Replica)
-	r.SetSelector(m.GetSelector())
-	r.SetCount(m.GetCount())
+	r.selector = v.GetSelector()
+	r.count = v.GetCount()
 
-	return r
+	return nil
 }
 
-func PlacementPolicyToGRPCMessage(p *PlacementPolicy) *netmap.PlacementPolicy {
-	if p == nil {
-		return nil
+func ReplicasToGRPC(rs []*Replica) (res []*netmap.Replica) {
+	if rs != nil {
+		res = make([]*netmap.Replica, 0, len(rs))
+
+		for i := range rs {
+			res = append(res, rs[i].ToGRPCMessage().(*netmap.Replica))
+		}
 	}
 
-	filters := make([]*netmap.Filter, 0, len(p.GetFilters()))
-	for _, filter := range p.GetFilters() {
-		filters = append(filters, FilterToGRPCMessage(filter))
+	return
+}
+
+func ReplicasFromGRPC(rs []*netmap.Replica) (res []*Replica, err error) {
+	if rs != nil {
+		res = make([]*Replica, 0, len(rs))
+
+		for i := range rs {
+			var r *Replica
+
+			if rs[i] != nil {
+				r = new(Replica)
+
+				err = r.FromGRPCMessage(rs[i])
+				if err != nil {
+					return
+				}
+			}
+
+			res = append(res, r)
+		}
 	}
 
-	selectors := make([]*netmap.Selector, 0, len(p.GetSelectors()))
-	for _, selector := range p.GetSelectors() {
-		selectors = append(selectors, SelectorToGRPCMessage(selector))
+	return
+}
+
+func (p *PlacementPolicy) ToGRPCMessage() grpc.Message {
+	var m *netmap.PlacementPolicy
+
+	if p != nil {
+		m = new(netmap.PlacementPolicy)
+
+		m.SetFilters(FiltersToGRPC(p.filters))
+		m.SetSelectors(SelectorsToGRPC(p.selectors))
+		m.SetReplicas(ReplicasToGRPC(p.replicas))
+		m.SetContainerBackupFactor(p.backupFactor)
 	}
 
-	replicas := make([]*netmap.Replica, 0, len(p.GetReplicas()))
-	for _, replica := range p.GetReplicas() {
-		replicas = append(replicas, ReplicaToGRPCMessage(replica))
-	}
-
-	m := new(netmap.PlacementPolicy)
-
-	m.SetContainerBackupFactor(p.GetContainerBackupFactor())
-	m.SetFilters(filters)
-	m.SetSelectors(selectors)
-	m.SetReplicas(replicas)
-
 	return m
 }
 
-func PlacementPolicyFromGRPCMessage(m *netmap.PlacementPolicy) *PlacementPolicy {
-	if m == nil {
-		return nil
+func (p *PlacementPolicy) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*netmap.PlacementPolicy)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
 	}
 
-	filters := make([]*Filter, 0, len(m.GetFilters()))
-	for _, filter := range m.GetFilters() {
-		filters = append(filters, FilterFromGRPCMessage(filter))
+	var err error
+
+	p.filters, err = FiltersFromGRPC(v.GetFilters())
+	if err != nil {
+		return err
 	}
 
-	selectors := make([]*Selector, 0, len(m.GetSelectors()))
-	for _, selector := range m.GetSelectors() {
-		selectors = append(selectors, SelectorFromGRPCMessage(selector))
+	p.selectors, err = SelectorsFromGRPC(v.GetSelectors())
+	if err != nil {
+		return err
 	}
 
-	replicas := make([]*Replica, 0, len(m.GetReplicas()))
-	for _, replica := range m.GetReplicas() {
-		replicas = append(replicas, ReplicaFromGRPCMessage(replica))
+	p.replicas, err = ReplicasFromGRPC(v.GetReplicas())
+	if err != nil {
+		return err
 	}
 
-	p := new(PlacementPolicy)
+	p.backupFactor = v.GetContainerBackupFactor()
 
-	p.SetContainerBackupFactor(m.GetContainerBackupFactor())
-	p.SetFilters(filters)
-	p.SetSelectors(selectors)
-	p.SetReplicas(replicas)
-
-	return p
+	return nil
 }
 
 func ClauseToGRPCMessage(n Clause) netmap.Clause {
@@ -188,286 +273,397 @@ func NodeStateFromRPCMessage(n netmap.NodeInfo_State) NodeState {
 	return NodeState(n)
 }
 
-func AttributeToGRPCMessage(a *Attribute) *netmap.NodeInfo_Attribute {
-	if a == nil {
-		return nil
+func (a *Attribute) ToGRPCMessage() grpc.Message {
+	var m *netmap.NodeInfo_Attribute
+
+	if a != nil {
+		m = new(netmap.NodeInfo_Attribute)
+
+		m.SetKey(a.key)
+		m.SetValue(a.value)
+		m.SetParents(a.parents)
 	}
 
-	m := new(netmap.NodeInfo_Attribute)
-
-	m.SetKey(a.GetKey())
-	m.SetValue(a.GetValue())
-	m.SetParents(a.GetParents())
-
 	return m
 }
 
-func AttributeFromGRPCMessage(m *netmap.NodeInfo_Attribute) *Attribute {
-	if m == nil {
-		return nil
+func (a *Attribute) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*netmap.NodeInfo_Attribute)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
 	}
 
-	a := new(Attribute)
+	a.key = v.GetKey()
+	a.value = v.GetValue()
+	a.parents = v.GetParents()
 
-	a.SetKey(m.GetKey())
-	a.SetValue(m.GetValue())
-	a.SetParents(m.GetParents())
-
-	return a
+	return nil
 }
 
-func NodeInfoToGRPCMessage(n *NodeInfo) *netmap.NodeInfo {
-	if n == nil {
-		return nil
+func AttributesToGRPC(as []*Attribute) (res []*netmap.NodeInfo_Attribute) {
+	if as != nil {
+		res = make([]*netmap.NodeInfo_Attribute, 0, len(as))
+
+		for i := range as {
+			res = append(res, as[i].ToGRPCMessage().(*netmap.NodeInfo_Attribute))
+		}
 	}
 
-	m := new(netmap.NodeInfo)
+	return
+}
 
-	m.SetPublicKey(n.GetPublicKey())
-	m.SetAddress(n.GetAddress())
-	m.SetState(NodeStateToGRPCMessage(n.GetState()))
+func AttributesFromGRPC(as []*netmap.NodeInfo_Attribute) (res []*Attribute, err error) {
+	if as != nil {
+		res = make([]*Attribute, 0, len(as))
 
-	attr := n.GetAttributes()
-	attrMsg := make([]*netmap.NodeInfo_Attribute, 0, len(attr))
+		for i := range as {
+			var a *Attribute
 
-	for i := range attr {
-		attrMsg = append(attrMsg, AttributeToGRPCMessage(attr[i]))
+			if as[i] != nil {
+				a = new(Attribute)
+
+				err = a.FromGRPCMessage(as[i])
+				if err != nil {
+					return
+				}
+			}
+
+			res = append(res, a)
+		}
 	}
 
-	m.SetAttributes(attrMsg)
+	return
+}
+
+func (ni *NodeInfo) ToGRPCMessage() grpc.Message {
+	var m *netmap.NodeInfo
+
+	if ni != nil {
+		m = new(netmap.NodeInfo)
+
+		m.SetPublicKey(ni.publicKey)
+		m.SetAddress(ni.address)
+		m.SetState(NodeStateToGRPCMessage(ni.state))
+		m.SetAttributes(AttributesToGRPC(ni.attributes))
+	}
 
 	return m
 }
 
-func NodeInfoFromGRPCMessage(m *netmap.NodeInfo) *NodeInfo {
-	if m == nil {
-		return nil
+func (ni *NodeInfo) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*netmap.NodeInfo)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
 	}
 
-	a := new(NodeInfo)
+	var err error
 
-	a.SetPublicKey(m.GetPublicKey())
-	a.SetAddress(m.GetAddress())
-	a.SetState(NodeStateFromRPCMessage(m.GetState()))
-
-	attrMsg := m.GetAttributes()
-	attr := make([]*Attribute, 0, len(attrMsg))
-
-	for i := range attrMsg {
-		attr = append(attr, AttributeFromGRPCMessage(attrMsg[i]))
+	ni.attributes, err = AttributesFromGRPC(v.GetAttributes())
+	if err != nil {
+		return err
 	}
 
-	a.SetAttributes(attr)
+	ni.publicKey = v.GetPublicKey()
+	ni.address = v.GetAddress()
+	ni.state = NodeStateFromRPCMessage(v.GetState())
 
-	return a
+	return nil
 }
 
-func LocalNodeInfoRequestBodyToGRPCMessage(r *LocalNodeInfoRequestBody) *netmap.LocalNodeInfoRequest_Body {
-	if r == nil {
-		return nil
+func (l *LocalNodeInfoRequestBody) ToGRPCMessage() grpc.Message {
+	var m *netmap.LocalNodeInfoRequest_Body
+
+	if l != nil {
+		m = new(netmap.LocalNodeInfoRequest_Body)
 	}
 
-	return new(netmap.LocalNodeInfoRequest_Body)
-}
-
-func LocalNodeInfoRequestBodyFromGRPCMessage(m *netmap.LocalNodeInfoRequest_Body) *LocalNodeInfoRequestBody {
-	if m == nil {
-		return nil
-	}
-
-	return new(LocalNodeInfoRequestBody)
-}
-
-func LocalNodeInfoResponseBodyToGRPCMessage(r *LocalNodeInfoResponseBody) *netmap.LocalNodeInfoResponse_Body {
-	if r == nil {
-		return nil
-	}
-
-	m := new(netmap.LocalNodeInfoResponse_Body)
-
-	m.SetVersion(refs.VersionToGRPCMessage(r.GetVersion()))
-	m.SetNodeInfo(NodeInfoToGRPCMessage(r.GetNodeInfo()))
-
 	return m
 }
 
-func LocalNodeInfoResponseBodyFromGRPCMessage(m *netmap.LocalNodeInfoResponse_Body) *LocalNodeInfoResponseBody {
-	if m == nil {
-		return nil
+func (l *LocalNodeInfoRequestBody) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*netmap.LocalNodeInfoRequest_Body)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
 	}
 
-	r := new(LocalNodeInfoResponseBody)
-	r.SetVersion(refs.VersionFromGRPCMessage(m.GetVersion()))
-	r.SetNodeInfo(NodeInfoFromGRPCMessage(m.GetNodeInfo()))
-
-	return r
+	return nil
 }
 
-func LocalNodeInfoRequestToGRPCMessage(r *LocalNodeInfoRequest) *netmap.LocalNodeInfoRequest {
-	if r == nil {
-		return nil
+func (l *LocalNodeInfoRequest) ToGRPCMessage() grpc.Message {
+	var m *netmap.LocalNodeInfoRequest
+
+	if l != nil {
+		m = new(netmap.LocalNodeInfoRequest)
+
+		m.SetBody(l.body.ToGRPCMessage().(*netmap.LocalNodeInfoRequest_Body))
+		l.RequestHeaders.ToMessage(m)
 	}
 
-	m := new(netmap.LocalNodeInfoRequest)
-	m.SetBody(LocalNodeInfoRequestBodyToGRPCMessage(r.GetBody()))
-
-	session.RequestHeadersToGRPC(r, m)
-
 	return m
 }
 
-func LocalNodeInfoRequestFromGRPCMessage(m *netmap.LocalNodeInfoRequest) *LocalNodeInfoRequest {
-	if m == nil {
-		return nil
+func (l *LocalNodeInfoRequest) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*netmap.LocalNodeInfoRequest)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
 	}
 
-	r := new(LocalNodeInfoRequest)
-	r.SetBody(LocalNodeInfoRequestBodyFromGRPCMessage(m.GetBody()))
+	var err error
 
-	session.RequestHeadersFromGRPC(m, r)
+	body := v.GetBody()
+	if body == nil {
+		l.body = nil
+	} else {
+		if l.body == nil {
+			l.body = new(LocalNodeInfoRequestBody)
+		}
 
-	return r
+		err = l.body.FromGRPCMessage(body)
+		if err != nil {
+			return err
+		}
+	}
+
+	return l.RequestHeaders.FromMessage(v)
 }
 
-func LocalNodeInfoResponseToGRPCMessage(r *LocalNodeInfoResponse) *netmap.LocalNodeInfoResponse {
-	if r == nil {
-		return nil
+func (l *LocalNodeInfoResponseBody) ToGRPCMessage() grpc.Message {
+	var m *netmap.LocalNodeInfoResponse_Body
+
+	if l != nil {
+		m = new(netmap.LocalNodeInfoResponse_Body)
+
+		m.SetVersion(l.version.ToGRPCMessage().(*refsGRPC.Version))
+		m.SetNodeInfo(l.nodeInfo.ToGRPCMessage().(*netmap.NodeInfo))
 	}
 
-	m := new(netmap.LocalNodeInfoResponse)
-	m.SetBody(LocalNodeInfoResponseBodyToGRPCMessage(r.GetBody()))
-
-	session.ResponseHeadersToGRPC(r, m)
-
 	return m
 }
 
-func LocalNodeInfoResponseFromGRPCMessage(m *netmap.LocalNodeInfoResponse) *LocalNodeInfoResponse {
-	if m == nil {
-		return nil
+func (l *LocalNodeInfoResponseBody) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*netmap.LocalNodeInfoResponse_Body)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
 	}
 
-	r := new(LocalNodeInfoResponse)
-	r.SetBody(LocalNodeInfoResponseBodyFromGRPCMessage(m.GetBody()))
+	var err error
 
-	session.ResponseHeadersFromGRPC(m, r)
+	version := v.GetVersion()
+	if version == nil {
+		l.version = nil
+	} else {
+		if l.version == nil {
+			l.version = new(refs.Version)
+		}
 
-	return r
+		err = l.version.FromGRPCMessage(version)
+		if err != nil {
+			return err
+		}
+	}
+
+	nodeInfo := v.GetNodeInfo()
+	if nodeInfo == nil {
+		l.nodeInfo = nil
+	} else {
+		if l.nodeInfo == nil {
+			l.nodeInfo = new(NodeInfo)
+		}
+
+		err = l.nodeInfo.FromGRPCMessage(nodeInfo)
+	}
+
+	return err
 }
 
-func NetworkInfoToGRPCMessage(n *NetworkInfo) *netmap.NetworkInfo {
-	if n == nil {
-		return nil
+func (l *LocalNodeInfoResponse) ToGRPCMessage() grpc.Message {
+	var m *netmap.LocalNodeInfoResponse
+
+	if l != nil {
+		m = new(netmap.LocalNodeInfoResponse)
+
+		m.SetBody(l.body.ToGRPCMessage().(*netmap.LocalNodeInfoResponse_Body))
+		l.ResponseHeaders.ToMessage(m)
 	}
 
-	m := new(netmap.NetworkInfo)
-
-	m.SetCurrentEpoch(n.GetCurrentEpoch())
-	m.SetMagicNumber(n.GetMagicNumber())
-
 	return m
 }
 
-func NetworkInfoFromGRPCMessage(m *netmap.NetworkInfo) *NetworkInfo {
-	if m == nil {
-		return nil
+func (l *LocalNodeInfoResponse) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*netmap.LocalNodeInfoResponse)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
 	}
 
-	n := new(NetworkInfo)
+	var err error
 
-	n.SetCurrentEpoch(m.GetCurrentEpoch())
-	n.SetMagicNumber(m.GetMagicNumber())
+	body := v.GetBody()
+	if body == nil {
+		l.body = nil
+	} else {
+		if l.body == nil {
+			l.body = new(LocalNodeInfoResponseBody)
+		}
 
-	return n
+		err = l.body.FromGRPCMessage(body)
+		if err != nil {
+			return err
+		}
+	}
+
+	return l.ResponseHeaders.FromMessage(v)
 }
 
-func NetworkInfoRequestBodyToGRPCMessage(r *NetworkInfoRequestBody) *netmap.NetworkInfoRequest_Body {
-	if r == nil {
-		return nil
+func (i *NetworkInfo) ToGRPCMessage() grpc.Message {
+	var m *netmap.NetworkInfo
+
+	if i != nil {
+		m = new(netmap.NetworkInfo)
+
+		m.SetMagicNumber(i.magicNum)
+		m.SetCurrentEpoch(i.curEpoch)
 	}
 
-	return new(netmap.NetworkInfoRequest_Body)
-}
-
-func NetworkInfoRequestBodyFromGRPCMessage(m *netmap.NetworkInfoRequest_Body) *NetworkInfoRequestBody {
-	if m == nil {
-		return nil
-	}
-
-	return new(NetworkInfoRequestBody)
-}
-
-func NetworkInfoResponseBodyToGRPCMessage(r *NetworkInfoResponseBody) *netmap.NetworkInfoResponse_Body {
-	if r == nil {
-		return nil
-	}
-
-	m := new(netmap.NetworkInfoResponse_Body)
-
-	m.SetNetworkInfo(NetworkInfoToGRPCMessage(r.GetNetworkInfo()))
-
 	return m
 }
 
-func NetworkInfoResponseBodyFromGRPCMessage(m *netmap.NetworkInfoResponse_Body) *NetworkInfoResponseBody {
-	if m == nil {
-		return nil
+func (i *NetworkInfo) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*netmap.NetworkInfo)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
 	}
 
-	r := new(NetworkInfoResponseBody)
-	r.SetNetworkInfo(NetworkInfoFromGRPCMessage(m.GetNetworkInfo()))
+	i.magicNum = v.GetMagicNumber()
+	i.curEpoch = v.GetCurrentEpoch()
 
-	return r
+	return nil
 }
 
-func NetworkInfoRequestToGRPCMessage(r *NetworkInfoRequest) *netmap.NetworkInfoRequest {
-	if r == nil {
-		return nil
+func (l *NetworkInfoRequestBody) ToGRPCMessage() grpc.Message {
+	var m *netmap.NetworkInfoRequest_Body
+
+	if l != nil {
+		m = new(netmap.NetworkInfoRequest_Body)
 	}
 
-	m := new(netmap.NetworkInfoRequest)
-	m.SetBody(NetworkInfoRequestBodyToGRPCMessage(r.GetBody()))
-
-	session.RequestHeadersToGRPC(r, m)
-
 	return m
 }
 
-func NetworkInfoRequestFromGRPCMessage(m *netmap.NetworkInfoRequest) *NetworkInfoRequest {
-	if m == nil {
-		return nil
+func (l *NetworkInfoRequestBody) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*netmap.NetworkInfoRequest_Body)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
 	}
 
-	r := new(NetworkInfoRequest)
-	r.SetBody(NetworkInfoRequestBodyFromGRPCMessage(m.GetBody()))
-
-	session.RequestHeadersFromGRPC(m, r)
-
-	return r
+	return nil
 }
 
-func NetworkInfoResponseToGRPCMessage(r *NetworkInfoResponse) *netmap.NetworkInfoResponse {
-	if r == nil {
-		return nil
+func (l *NetworkInfoRequest) ToGRPCMessage() grpc.Message {
+	var m *netmap.NetworkInfoRequest
+
+	if l != nil {
+		m = new(netmap.NetworkInfoRequest)
+
+		m.SetBody(l.body.ToGRPCMessage().(*netmap.NetworkInfoRequest_Body))
+		l.RequestHeaders.ToMessage(m)
 	}
 
-	m := new(netmap.NetworkInfoResponse)
-	m.SetBody(NetworkInfoResponseBodyToGRPCMessage(r.GetBody()))
-
-	session.ResponseHeadersToGRPC(r, m)
-
 	return m
 }
 
-func NetworkInfoResponseFromGRPCMessage(m *netmap.NetworkInfoResponse) *NetworkInfoResponse {
-	if m == nil {
-		return nil
+func (l *NetworkInfoRequest) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*netmap.NetworkInfoRequest)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
 	}
 
-	r := new(NetworkInfoResponse)
-	r.SetBody(NetworkInfoResponseBodyFromGRPCMessage(m.GetBody()))
+	var err error
 
-	session.ResponseHeadersFromGRPC(m, r)
+	body := v.GetBody()
+	if body == nil {
+		l.body = nil
+	} else {
+		if l.body == nil {
+			l.body = new(NetworkInfoRequestBody)
+		}
 
-	return r
+		err = l.body.FromGRPCMessage(body)
+		if err != nil {
+			return err
+		}
+	}
+
+	return l.RequestHeaders.FromMessage(v)
+}
+
+func (i *NetworkInfoResponseBody) ToGRPCMessage() grpc.Message {
+	var m *netmap.NetworkInfoResponse_Body
+
+	if i != nil {
+		m = new(netmap.NetworkInfoResponse_Body)
+
+		m.SetNetworkInfo(i.netInfo.ToGRPCMessage().(*netmap.NetworkInfo))
+	}
+
+	return m
+}
+
+func (i *NetworkInfoResponseBody) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*netmap.NetworkInfoResponse_Body)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
+	}
+
+	var err error
+
+	netInfo := v.GetNetworkInfo()
+	if netInfo == nil {
+		i.netInfo = nil
+	} else {
+		if i.netInfo == nil {
+			i.netInfo = new(NetworkInfo)
+		}
+
+		err = i.netInfo.FromGRPCMessage(netInfo)
+	}
+
+	return err
+}
+
+func (l *NetworkInfoResponse) ToGRPCMessage() grpc.Message {
+	var m *netmap.NetworkInfoResponse
+
+	if l != nil {
+		m = new(netmap.NetworkInfoResponse)
+
+		m.SetBody(l.body.ToGRPCMessage().(*netmap.NetworkInfoResponse_Body))
+		l.ResponseHeaders.ToMessage(m)
+	}
+
+	return m
+}
+
+func (l *NetworkInfoResponse) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*netmap.NetworkInfoResponse)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
+	}
+
+	var err error
+
+	body := v.GetBody()
+	if body == nil {
+		l.body = nil
+	} else {
+		if l.body == nil {
+			l.body = new(NetworkInfoResponseBody)
+		}
+
+		err = l.body.FromGRPCMessage(body)
+		if err != nil {
+			return err
+		}
+	}
+
+	return l.ResponseHeaders.FromMessage(v)
 }
diff --git a/v2/netmap/grpc/client.go b/v2/netmap/grpc/client.go
deleted file mode 100644
index 5ed5e5b..0000000
--- a/v2/netmap/grpc/client.go
+++ /dev/null
@@ -1,66 +0,0 @@
-package netmap
-
-import (
-	"context"
-
-	"github.com/pkg/errors"
-	"google.golang.org/grpc"
-)
-
-// Client wraps NetmapServiceClient
-// with pre-defined configurations.
-type Client struct {
-	*cfg
-
-	client NetmapServiceClient
-}
-
-// Option represents Client option.
-type Option func(*cfg)
-
-type cfg struct {
-	callOpts []grpc.CallOption
-}
-
-// ErrNilNetmapServiceClient is returned by functions that expect
-// a non-nil ContainerServiceClient, but received nil.
-var ErrNilNetmapServiceClient = errors.New("netmap gRPC client is nil")
-
-func defaultCfg() *cfg {
-	return new(cfg)
-}
-
-// NewClient creates, initializes and returns a new Client instance.
-//
-// Options are applied one by one in order.
-func NewClient(c NetmapServiceClient, opts ...Option) (*Client, error) {
-	if c == nil {
-		return nil, ErrNilNetmapServiceClient
-	}
-
-	cfg := defaultCfg()
-	for i := range opts {
-		opts[i](cfg)
-	}
-
-	return &Client{
-		cfg:    cfg,
-		client: c,
-	}, nil
-}
-
-func (c *Client) LocalNodeInfo(ctx context.Context, req *LocalNodeInfoRequest) (*LocalNodeInfoResponse, error) {
-	return c.client.LocalNodeInfo(ctx, req, c.callOpts...)
-}
-
-func (c *Client) NetworkInfo(ctx context.Context, req *NetworkInfoRequest) (*NetworkInfoResponse, error) {
-	return c.client.NetworkInfo(ctx, req, c.callOpts...)
-}
-
-// WithCallOptions returns Option that configures
-// Client to attach call options to each rpc call.
-func WithCallOptions(opts []grpc.CallOption) Option {
-	return func(c *cfg) {
-		c.callOpts = opts
-	}
-}
diff --git a/v2/netmap/json.go b/v2/netmap/json.go
index 3445f05..64f2f6d 100644
--- a/v2/netmap/json.go
+++ b/v2/netmap/json.go
@@ -1,146 +1,62 @@
 package netmap
 
 import (
+	"github.com/nspcc-dev/neofs-api-go/rpc/message"
 	netmap "github.com/nspcc-dev/neofs-api-go/v2/netmap/grpc"
-	"google.golang.org/protobuf/encoding/protojson"
 )
 
 func (p *PlacementPolicy) MarshalJSON() ([]byte, error) {
-	return protojson.MarshalOptions{
-		EmitUnpopulated: true,
-	}.Marshal(
-		PlacementPolicyToGRPCMessage(p),
-	)
+	return message.MarshalJSON(p)
 }
 
 func (p *PlacementPolicy) UnmarshalJSON(data []byte) error {
-	msg := new(netmap.PlacementPolicy)
-
-	if err := protojson.Unmarshal(data, msg); err != nil {
-		return err
-	}
-
-	*p = *PlacementPolicyFromGRPCMessage(msg)
-
-	return nil
+	return message.UnmarshalJSON(p, data, new(netmap.PlacementPolicy))
 }
 
 func (f *Filter) MarshalJSON() ([]byte, error) {
-	return protojson.MarshalOptions{
-		EmitUnpopulated: true,
-	}.Marshal(
-		FilterToGRPCMessage(f),
-	)
+	return message.MarshalJSON(f)
 }
 
 func (f *Filter) UnmarshalJSON(data []byte) error {
-	msg := new(netmap.Filter)
-
-	if err := protojson.Unmarshal(data, msg); err != nil {
-		return err
-	}
-
-	*f = *FilterFromGRPCMessage(msg)
-
-	return nil
+	return message.UnmarshalJSON(f, data, new(netmap.Filter))
 }
 
 func (s *Selector) MarshalJSON() ([]byte, error) {
-	return protojson.MarshalOptions{
-		EmitUnpopulated: true,
-	}.Marshal(
-		SelectorToGRPCMessage(s),
-	)
+	return message.MarshalJSON(s)
 }
 
 func (s *Selector) UnmarshalJSON(data []byte) error {
-	msg := new(netmap.Selector)
-
-	if err := protojson.Unmarshal(data, msg); err != nil {
-		return err
-	}
-
-	*s = *SelectorFromGRPCMessage(msg)
-
-	return nil
+	return message.UnmarshalJSON(s, data, new(netmap.Selector))
 }
 
 func (r *Replica) MarshalJSON() ([]byte, error) {
-	return protojson.MarshalOptions{
-		EmitUnpopulated: true,
-	}.Marshal(
-		ReplicaToGRPCMessage(r),
-	)
+	return message.MarshalJSON(r)
 }
 
 func (r *Replica) UnmarshalJSON(data []byte) error {
-	msg := new(netmap.Replica)
-
-	if err := protojson.Unmarshal(data, msg); err != nil {
-		return err
-	}
-
-	*r = *ReplicaFromGRPCMessage(msg)
-
-	return nil
+	return message.UnmarshalJSON(r, data, new(netmap.Replica))
 }
 
 func (a *Attribute) MarshalJSON() ([]byte, error) {
-	return protojson.MarshalOptions{
-		EmitUnpopulated: true,
-	}.Marshal(
-		AttributeToGRPCMessage(a),
-	)
+	return message.MarshalJSON(a)
 }
 
 func (a *Attribute) UnmarshalJSON(data []byte) error {
-	msg := new(netmap.NodeInfo_Attribute)
-
-	if err := protojson.Unmarshal(data, msg); err != nil {
-		return err
-	}
-
-	*a = *AttributeFromGRPCMessage(msg)
-
-	return nil
+	return message.UnmarshalJSON(a, data, new(netmap.NodeInfo_Attribute))
 }
 
 func (ni *NodeInfo) MarshalJSON() ([]byte, error) {
-	return protojson.MarshalOptions{
-		EmitUnpopulated: true,
-	}.Marshal(
-		NodeInfoToGRPCMessage(ni),
-	)
+	return message.MarshalJSON(ni)
 }
 
 func (ni *NodeInfo) UnmarshalJSON(data []byte) error {
-	msg := new(netmap.NodeInfo)
-
-	if err := protojson.Unmarshal(data, msg); err != nil {
-		return err
-	}
-
-	*ni = *NodeInfoFromGRPCMessage(msg)
-
-	return nil
+	return message.UnmarshalJSON(ni, data, new(netmap.NodeInfo))
 }
 
 func (i *NetworkInfo) MarshalJSON() ([]byte, error) {
-	return protojson.MarshalOptions{
-		EmitUnpopulated: true,
-	}.Marshal(
-		NetworkInfoToGRPCMessage(i),
-	)
+	return message.MarshalJSON(i)
 }
 
 func (i *NetworkInfo) UnmarshalJSON(data []byte) error {
-	msg := new(netmap.NetworkInfo)
-
-	if err := protojson.Unmarshal(data, msg); err != nil {
-		return err
-	}
-
-	*i = *NetworkInfoFromGRPCMessage(msg)
-
-	return nil
+	return message.UnmarshalJSON(i, data, new(netmap.NetworkInfo))
 }
diff --git a/v2/netmap/json_test.go b/v2/netmap/json_test.go
deleted file mode 100644
index 3f01b03..0000000
--- a/v2/netmap/json_test.go
+++ /dev/null
@@ -1,80 +0,0 @@
-package netmap_test
-
-import (
-	"testing"
-
-	"github.com/nspcc-dev/neofs-api-go/v2/netmap"
-	"github.com/stretchr/testify/require"
-)
-
-func TestFilterJSON(t *testing.T) {
-	f := generateFilter("key", "value", false)
-
-	d, err := f.MarshalJSON()
-	require.NoError(t, err)
-
-	f2 := new(netmap.Filter)
-	require.NoError(t, f2.UnmarshalJSON(d))
-
-	require.Equal(t, f, f2)
-}
-
-func TestSelectorJSON(t *testing.T) {
-	s := generateSelector("name")
-
-	data, err := s.MarshalJSON()
-	require.NoError(t, err)
-
-	s2 := new(netmap.Selector)
-	require.NoError(t, s2.UnmarshalJSON(data))
-
-	require.Equal(t, s, s2)
-}
-
-func TestReplicaJSON(t *testing.T) {
-	s := generateReplica("selector")
-
-	data, err := s.MarshalJSON()
-	require.NoError(t, err)
-
-	s2 := new(netmap.Replica)
-	require.NoError(t, s2.UnmarshalJSON(data))
-
-	require.Equal(t, s, s2)
-}
-
-func TestAttributeJSON(t *testing.T) {
-	a := generateAttribute("key", "value")
-
-	data, err := a.MarshalJSON()
-	require.NoError(t, err)
-
-	a2 := new(netmap.Attribute)
-	require.NoError(t, a2.UnmarshalJSON(data))
-
-	require.Equal(t, a, a2)
-}
-
-func TestNodeInfoJSON(t *testing.T) {
-	i := generateNodeInfo("key", "value", 3)
-
-	data, err := i.MarshalJSON()
-	require.NoError(t, err)
-
-	i2 := new(netmap.NodeInfo)
-	require.NoError(t, i2.UnmarshalJSON(data))
-
-	require.Equal(t, i, i2)
-}
-
-func TestNetworkInfoJSON(t *testing.T) {
-	i := generateNetworkInfo()
-
-	data, err := i.MarshalJSON()
-	require.NoError(t, err)
-
-	i2 := new(netmap.NetworkInfo)
-	require.NoError(t, i2.UnmarshalJSON(data))
-
-	require.Equal(t, i, i2)
-}
diff --git a/v2/netmap/marshal.go b/v2/netmap/marshal.go
index 3fa3388..79da27a 100644
--- a/v2/netmap/marshal.go
+++ b/v2/netmap/marshal.go
@@ -1,9 +1,9 @@
 package netmap
 
 import (
+	"github.com/nspcc-dev/neofs-api-go/rpc/message"
 	protoutil "github.com/nspcc-dev/neofs-api-go/util/proto"
 	netmap "github.com/nspcc-dev/neofs-api-go/v2/netmap/grpc"
-	"google.golang.org/protobuf/proto"
 )
 
 const (
@@ -107,14 +107,7 @@ func (f *Filter) StableSize() (size int) {
 }
 
 func (f *Filter) Unmarshal(data []byte) error {
-	m := new(netmap.Filter)
-	if err := proto.Unmarshal(data, m); err != nil {
-		return err
-	}
-
-	*f = *FilterFromGRPCMessage(m)
-
-	return nil
+	return message.Unmarshal(f, data, new(netmap.Filter))
 }
 
 func (s *Selector) StableMarshal(buf []byte) ([]byte, error) {
@@ -178,14 +171,7 @@ func (s *Selector) StableSize() (size int) {
 }
 
 func (s *Selector) Unmarshal(data []byte) error {
-	m := new(netmap.Selector)
-	if err := proto.Unmarshal(data, m); err != nil {
-		return err
-	}
-
-	*s = *SelectorFromGRPCMessage(m)
-
-	return nil
+	return message.Unmarshal(s, data, new(netmap.Selector))
 }
 
 func (r *Replica) StableMarshal(buf []byte) ([]byte, error) {
@@ -225,14 +211,7 @@ func (r *Replica) StableSize() (size int) {
 }
 
 func (r *Replica) Unmarshal(data []byte) error {
-	m := new(netmap.Replica)
-	if err := proto.Unmarshal(data, m); err != nil {
-		return err
-	}
-
-	*r = *ReplicaFromGRPCMessage(m)
-
-	return nil
+	return message.Unmarshal(r, data, new(netmap.Replica))
 }
 
 func (p *PlacementPolicy) StableMarshal(buf []byte) ([]byte, error) {
@@ -305,14 +284,7 @@ func (p *PlacementPolicy) StableSize() (size int) {
 }
 
 func (p *PlacementPolicy) Unmarshal(data []byte) error {
-	m := new(netmap.PlacementPolicy)
-	if err := proto.Unmarshal(data, m); err != nil {
-		return err
-	}
-
-	*p = *PlacementPolicyFromGRPCMessage(m)
-
-	return nil
+	return message.Unmarshal(p, data, new(netmap.PlacementPolicy))
 }
 
 func (a *Attribute) StableMarshal(buf []byte) ([]byte, error) {
@@ -371,14 +343,7 @@ func (a *Attribute) StableSize() (size int) {
 }
 
 func (a *Attribute) Unmarshal(data []byte) error {
-	m := new(netmap.NodeInfo_Attribute)
-	if err := proto.Unmarshal(data, m); err != nil {
-		return err
-	}
-
-	*a = *AttributeFromGRPCMessage(m)
-
-	return nil
+	return message.Unmarshal(a, data, new(netmap.NodeInfo_Attribute))
 }
 
 func (ni *NodeInfo) StableMarshal(buf []byte) ([]byte, error) {
@@ -443,14 +408,7 @@ func (ni *NodeInfo) StableSize() (size int) {
 }
 
 func (ni *NodeInfo) Unmarshal(data []byte) error {
-	m := new(netmap.NodeInfo)
-	if err := proto.Unmarshal(data, m); err != nil {
-		return err
-	}
-
-	*ni = *NodeInfoFromGRPCMessage(m)
-
-	return nil
+	return message.Unmarshal(ni, data, new(netmap.NodeInfo))
 }
 
 func (l *LocalNodeInfoRequestBody) StableMarshal(buf []byte) ([]byte, error) {
@@ -461,6 +419,10 @@ func (l *LocalNodeInfoRequestBody) StableSize() (size int) {
 	return 0
 }
 
+func (l *LocalNodeInfoRequestBody) Unmarshal([]byte) error {
+	return nil
+}
+
 func (l *LocalNodeInfoResponseBody) StableMarshal(buf []byte) ([]byte, error) {
 	if l == nil {
 		return []byte{}, nil
@@ -501,6 +463,10 @@ func (l *LocalNodeInfoResponseBody) StableSize() (size int) {
 	return size
 }
 
+func (l *LocalNodeInfoResponseBody) Unmarshal(data []byte) error {
+	return message.Unmarshal(l, data, new(netmap.LocalNodeInfoResponse_Body))
+}
+
 const (
 	_ = iota
 	netInfoCurEpochFNum
@@ -548,14 +514,7 @@ func (i *NetworkInfo) StableSize() (size int) {
 }
 
 func (i *NetworkInfo) Unmarshal(data []byte) error {
-	m := new(netmap.NetworkInfo)
-	if err := proto.Unmarshal(data, m); err != nil {
-		return err
-	}
-
-	*i = *NetworkInfoFromGRPCMessage(m)
-
-	return nil
+	return message.Unmarshal(i, data, new(netmap.NetworkInfo))
 }
 
 func (l *NetworkInfoRequestBody) StableMarshal(buf []byte) ([]byte, error) {
@@ -566,6 +525,10 @@ func (l *NetworkInfoRequestBody) StableSize() (size int) {
 	return 0
 }
 
+func (l *NetworkInfoRequestBody) Unmarshal(data []byte) error {
+	return message.Unmarshal(l, data, new(netmap.NetworkInfoRequest_Body))
+}
+
 const (
 	_ = iota
 	netInfoRespBodyNetInfoFNum
@@ -597,3 +560,7 @@ func (i *NetworkInfoResponseBody) StableSize() (size int) {
 
 	return size
 }
+
+func (i *NetworkInfoResponseBody) Unmarshal(data []byte) error {
+	return message.Unmarshal(i, data, new(netmap.NetworkInfoResponse_Body))
+}
diff --git a/v2/netmap/marshal_test.go b/v2/netmap/marshal_test.go
deleted file mode 100644
index 0acbd4b..0000000
--- a/v2/netmap/marshal_test.go
+++ /dev/null
@@ -1,248 +0,0 @@
-package netmap_test
-
-import (
-	"strconv"
-	"testing"
-
-	"github.com/nspcc-dev/neofs-api-go/v2/netmap"
-	grpc "github.com/nspcc-dev/neofs-api-go/v2/netmap/grpc"
-	"github.com/nspcc-dev/neofs-api-go/v2/refs"
-	"github.com/stretchr/testify/require"
-	goproto "google.golang.org/protobuf/proto"
-)
-
-func TestAttribute_StableMarshal(t *testing.T) {
-	from := generateAttribute("key", "value")
-
-	t.Run("non empty", func(t *testing.T) {
-		wire, err := from.StableMarshal(nil)
-		require.NoError(t, err)
-
-		to := new(netmap.Attribute)
-		require.NoError(t, to.Unmarshal(wire))
-
-		require.Equal(t, from, to)
-	})
-}
-
-func TestNodeInfo_StableMarshal(t *testing.T) {
-	from := generateNodeInfo("publicKey", "/multi/addr", 10)
-
-	t.Run("non empty", func(t *testing.T) {
-		wire, err := from.StableMarshal(nil)
-		require.NoError(t, err)
-
-		to := new(netmap.NodeInfo)
-		require.NoError(t, to.Unmarshal(wire))
-
-		require.Equal(t, from, to)
-	})
-}
-
-func TestFilter_StableMarshal(t *testing.T) {
-	from := generateFilter("key", "value", false)
-
-	t.Run("non empty", func(t *testing.T) {
-		wire, err := from.StableMarshal(nil)
-		require.NoError(t, err)
-
-		to := new(netmap.Filter)
-		require.NoError(t, to.Unmarshal(wire))
-
-		require.Equal(t, from, to)
-	})
-}
-
-func TestSelector_StableMarshal(t *testing.T) {
-	from := generateSelector("name")
-
-	t.Run("non empty", func(t *testing.T) {
-		wire, err := from.StableMarshal(nil)
-		require.NoError(t, err)
-
-		to := new(netmap.Selector)
-		require.NoError(t, to.Unmarshal(wire))
-
-		require.Equal(t, from, to)
-	})
-}
-
-func TestReplica_StableMarshal(t *testing.T) {
-	from := generateReplica("selector")
-
-	t.Run("non empty", func(t *testing.T) {
-		wire, err := from.StableMarshal(nil)
-		require.NoError(t, err)
-
-		to := new(netmap.Replica)
-		require.NoError(t, to.Unmarshal(wire))
-
-		require.Equal(t, from, to)
-	})
-}
-
-func TestPlacementPolicy_StableMarshal(t *testing.T) {
-	from := generatePolicy(3)
-
-	t.Run("non empty", func(t *testing.T) {
-		wire, err := from.StableMarshal(nil)
-		require.NoError(t, err)
-
-		to := new(netmap.PlacementPolicy)
-		require.NoError(t, to.Unmarshal(wire))
-
-		require.Equal(t, from, to)
-	})
-}
-
-func TestLocalNodeInfoResponseBody_StableMarshal(t *testing.T) {
-	from := generateNodeInfoResponseBody()
-	transport := new(grpc.LocalNodeInfoResponse_Body)
-
-	t.Run("non empty", func(t *testing.T) {
-		wire, err := from.StableMarshal(nil)
-		require.NoError(t, err)
-
-		err = goproto.Unmarshal(wire, transport)
-		require.NoError(t, err)
-
-		to := netmap.LocalNodeInfoResponseBodyFromGRPCMessage(transport)
-		require.Equal(t, from, to)
-	})
-}
-
-func TestNetworkInfoResponseBody_StableMarshal(t *testing.T) {
-	from := generateNetworkInfoResponseBody()
-	transport := new(grpc.NetworkInfoResponse_Body)
-
-	t.Run("non empty", func(t *testing.T) {
-		wire, err := from.StableMarshal(nil)
-		require.NoError(t, err)
-
-		err = goproto.Unmarshal(wire, transport)
-		require.NoError(t, err)
-
-		to := netmap.NetworkInfoResponseBodyFromGRPCMessage(transport)
-		require.Equal(t, from, to)
-	})
-}
-
-func generateAttribute(k, v string) *netmap.Attribute {
-	attr := new(netmap.Attribute)
-	attr.SetKey(k)
-	attr.SetValue(v)
-	attr.SetParents([]string{k, v})
-
-	return attr
-}
-
-func generateNodeInfo(key, addr string, n int) *netmap.NodeInfo {
-	nodeInfo := new(netmap.NodeInfo)
-	nodeInfo.SetPublicKey([]byte(key))
-	nodeInfo.SetAddress(addr)
-	nodeInfo.SetState(netmap.Online)
-
-	attrs := make([]*netmap.Attribute, n)
-	for i := 0; i < n; i++ {
-		j := strconv.Itoa(n)
-		attrs[i] = generateAttribute("key"+j, "value"+j)
-	}
-
-	nodeInfo.SetAttributes(attrs)
-
-	return nodeInfo
-}
-
-func generateFilter(key, value string, fin bool) *netmap.Filter {
-	f := new(netmap.Filter)
-	f.SetKey(key)
-	f.SetValue(value)
-	f.SetName("name")
-	f.SetOp(netmap.AND)
-	if !fin {
-		ff := generateFilter(key+"fin", value+"fin", true)
-		f.SetFilters([]*netmap.Filter{ff})
-	} else {
-		f.SetFilters([]*netmap.Filter{})
-	}
-
-	return f
-}
-
-func generateSelector(name string) *netmap.Selector {
-	s := new(netmap.Selector)
-	s.SetName(name)
-	s.SetAttribute("attribute")
-	s.SetClause(netmap.Distinct)
-	s.SetCount(10)
-	s.SetFilter("filter")
-
-	return s
-}
-
-func generateReplica(selector string) *netmap.Replica {
-	r := new(netmap.Replica)
-	r.SetCount(10)
-	r.SetSelector(selector)
-
-	return r
-}
-
-func generatePolicy(n int) *netmap.PlacementPolicy {
-	var (
-		p = new(netmap.PlacementPolicy)
-		f = make([]*netmap.Filter, 0, n)
-		s = make([]*netmap.Selector, 0, n)
-		r = make([]*netmap.Replica, 0, n)
-	)
-
-	for i := 0; i < n; i++ {
-		ind := strconv.Itoa(i)
-
-		f = append(f, generateFilter("key"+ind, "val"+ind, false))
-		s = append(s, generateSelector("name"+ind))
-		r = append(r, generateReplica("selector"+ind))
-	}
-
-	p.SetFilters(f)
-	p.SetSelectors(s)
-	p.SetReplicas(r)
-	p.SetContainerBackupFactor(10)
-
-	return p
-}
-
-func generateNodeInfoResponseBody() *netmap.LocalNodeInfoResponseBody {
-	ni := generateNodeInfo("key", "/multi/addr", 2)
-
-	r := new(netmap.LocalNodeInfoResponseBody)
-	r.SetVersion(generateVersion(2, 1))
-	r.SetNodeInfo(ni)
-
-	return r
-}
-
-func generateVersion(maj, min uint32) *refs.Version {
-	version := new(refs.Version)
-	version.SetMajor(maj)
-	version.SetMinor(min)
-
-	return version
-}
-
-func generateNetworkInfo() *netmap.NetworkInfo {
-	ni := new(netmap.NetworkInfo)
-	ni.SetCurrentEpoch(13)
-	ni.SetMagicNumber(666)
-
-	return ni
-}
-
-func generateNetworkInfoResponseBody() *netmap.NetworkInfoResponseBody {
-	ni := generateNetworkInfo()
-
-	r := new(netmap.NetworkInfoResponseBody)
-	r.SetNetworkInfo(ni)
-
-	return r
-}
diff --git a/v2/netmap/message_test.go b/v2/netmap/message_test.go
new file mode 100644
index 0000000..4a653bf
--- /dev/null
+++ b/v2/netmap/message_test.go
@@ -0,0 +1,25 @@
+package netmap_test
+
+import (
+	"testing"
+
+	"github.com/nspcc-dev/neofs-api-go/rpc/message"
+	messagetest "github.com/nspcc-dev/neofs-api-go/rpc/message/test"
+	netmaptest "github.com/nspcc-dev/neofs-api-go/v2/netmap/test"
+)
+
+func TestMessageConvert(t *testing.T) {
+	messagetest.TestRPCMessage(t,
+		func(empty bool) message.Message { return netmaptest.GenerateFilter(empty) },
+		func(empty bool) message.Message { return netmaptest.GenerateSelector(empty) },
+		func(empty bool) message.Message { return netmaptest.GenerateReplica(empty) },
+		func(empty bool) message.Message { return netmaptest.GeneratePlacementPolicy(empty) },
+		func(empty bool) message.Message { return netmaptest.GenerateAttribute(empty) },
+		func(empty bool) message.Message { return netmaptest.GenerateNodeInfo(empty) },
+		func(empty bool) message.Message { return netmaptest.GenerateLocalNodeInfoRequest(empty) },
+		func(empty bool) message.Message { return netmaptest.GenerateLocalNodeInfoResponseBody(empty) },
+		func(empty bool) message.Message { return netmaptest.GenerateNetworkInfo(empty) },
+		func(empty bool) message.Message { return netmaptest.GenerateNetworkInfoRequest(empty) },
+		func(empty bool) message.Message { return netmaptest.GenerateNetworkInfoResponseBody(empty) },
+	)
+}
diff --git a/v2/netmap/service.go b/v2/netmap/service.go
deleted file mode 100644
index a57819b..0000000
--- a/v2/netmap/service.go
+++ /dev/null
@@ -1,46 +0,0 @@
-package netmap
-
-import (
-	"context"
-
-	"github.com/nspcc-dev/neofs-api-go/v2/session"
-)
-
-type Service interface {
-	LocalNodeInfo(ctx context.Context, request *LocalNodeInfoRequest) (*LocalNodeInfoResponse, error)
-	NetworkInfo(ctx context.Context, request *NetworkInfoRequest) (*NetworkInfoResponse, error)
-}
-
-type LocalNodeInfoRequest struct {
-	body *LocalNodeInfoRequestBody
-
-	metaHeader *session.RequestMetaHeader
-
-	verifyHeader *session.RequestVerificationHeader
-}
-
-type LocalNodeInfoResponse struct {
-	body *LocalNodeInfoResponseBody
-
-	metaHeader *session.ResponseMetaHeader
-
-	verifyHeader *session.ResponseVerificationHeader
-}
-
-// NetworkInfoRequest is a structure of NetworkInfo request.
-type NetworkInfoRequest struct {
-	body *NetworkInfoRequestBody
-
-	metaHeader *session.RequestMetaHeader
-
-	verifyHeader *session.RequestVerificationHeader
-}
-
-// NetworkInfoResponse is a structure of NetworkInfo response.
-type NetworkInfoResponse struct {
-	body *NetworkInfoResponseBody
-
-	metaHeader *session.ResponseMetaHeader
-
-	verifyHeader *session.ResponseVerificationHeader
-}
diff --git a/v2/netmap/test/generate.go b/v2/netmap/test/generate.go
new file mode 100644
index 0000000..3818596
--- /dev/null
+++ b/v2/netmap/test/generate.go
@@ -0,0 +1,219 @@
+package netmaptest
+
+import (
+	"github.com/nspcc-dev/neofs-api-go/v2/netmap"
+	refstest "github.com/nspcc-dev/neofs-api-go/v2/refs/test"
+	sessiontest "github.com/nspcc-dev/neofs-api-go/v2/session/test"
+)
+
+func GenerateFilter(empty bool) *netmap.Filter {
+	return generateFilter(empty, true)
+}
+
+func generateFilter(empty, withSub bool) *netmap.Filter {
+	m := new(netmap.Filter)
+
+	if !empty {
+		m.SetKey("filter key")
+		m.SetValue("filter value")
+		m.SetName("filter name")
+		m.SetOp(1)
+
+		if withSub {
+			m.SetFilters([]*netmap.Filter{
+				generateFilter(empty, false),
+				generateFilter(empty, false),
+			})
+		}
+	}
+
+	return m
+}
+
+func GenerateFilters(empty bool) (res []*netmap.Filter) {
+	if !empty {
+		res = append(res,
+			GenerateFilter(false),
+			GenerateFilter(false),
+		)
+	}
+
+	return
+}
+
+func GenerateSelector(empty bool) *netmap.Selector {
+	m := new(netmap.Selector)
+
+	if !empty {
+		m.SetCount(66)
+		m.SetAttribute("selector attribute")
+		m.SetFilter("select filter")
+		m.SetName("select name")
+		m.SetClause(1)
+	}
+
+	return m
+}
+
+func GenerateSelectors(empty bool) (res []*netmap.Selector) {
+	if !empty {
+		res = append(res,
+			GenerateSelector(false),
+			GenerateSelector(false),
+		)
+	}
+
+	return
+}
+
+func GenerateReplica(empty bool) *netmap.Replica {
+	m := new(netmap.Replica)
+
+	if !empty {
+		m.SetCount(42)
+		m.SetSelector("replica selector")
+	}
+
+	return m
+}
+
+func GenerateReplicas(empty bool) (res []*netmap.Replica) {
+	if !empty {
+		res = append(res,
+			GenerateReplica(false),
+			GenerateReplica(false),
+		)
+	}
+
+	return
+}
+
+func GeneratePlacementPolicy(empty bool) *netmap.PlacementPolicy {
+	m := new(netmap.PlacementPolicy)
+
+	if !empty {
+		m.SetContainerBackupFactor(322)
+	}
+
+	m.SetFilters(GenerateFilters(empty))
+	m.SetSelectors(GenerateSelectors(empty))
+	m.SetReplicas(GenerateReplicas(empty))
+
+	return m
+}
+
+func GenerateAttribute(empty bool) *netmap.Attribute {
+	m := new(netmap.Attribute)
+
+	if !empty {
+		m.SetKey("attribute key")
+		m.SetValue("attribute val")
+	}
+
+	return m
+}
+
+func GenerateAttributes(empty bool) (res []*netmap.Attribute) {
+	if !empty {
+		res = append(res,
+			GenerateAttribute(false),
+			GenerateAttribute(false),
+		)
+	}
+
+	return
+}
+
+func GenerateNodeInfo(empty bool) *netmap.NodeInfo {
+	m := new(netmap.NodeInfo)
+
+	if !empty {
+		m.SetAddress("node address")
+		m.SetPublicKey([]byte{1, 2, 3})
+		m.SetState(33)
+	}
+
+	m.SetAttributes(GenerateAttributes(empty))
+
+	return m
+}
+
+func GenerateLocalNodeInfoRequestBody(empty bool) *netmap.LocalNodeInfoRequestBody {
+	m := new(netmap.LocalNodeInfoRequestBody)
+
+	return m
+}
+
+func GenerateLocalNodeInfoRequest(empty bool) *netmap.LocalNodeInfoRequest {
+	m := new(netmap.LocalNodeInfoRequest)
+
+	m.SetBody(GenerateLocalNodeInfoRequestBody(empty))
+	m.SetMetaHeader(sessiontest.GenerateRequestMetaHeader(empty))
+	m.SetVerificationHeader(sessiontest.GenerateRequestVerificationHeader(empty))
+
+	return m
+}
+
+func GenerateLocalNodeInfoResponseBody(empty bool) *netmap.LocalNodeInfoResponseBody {
+	m := new(netmap.LocalNodeInfoResponseBody)
+
+	m.SetVersion(refstest.GenerateVersion(empty))
+	m.SetNodeInfo(GenerateNodeInfo(empty))
+
+	return m
+}
+
+func GenerateLocalNodeInfoResponse(empty bool) *netmap.LocalNodeInfoResponse {
+	m := new(netmap.LocalNodeInfoResponse)
+
+	m.SetBody(GenerateLocalNodeInfoResponseBody(empty))
+	m.SetMetaHeader(sessiontest.GenerateResponseMetaHeader(empty))
+	m.SetVerificationHeader(sessiontest.GenerateResponseVerificationHeader(empty))
+
+	return m
+}
+
+func GenerateNetworkInfo(empty bool) *netmap.NetworkInfo {
+	m := new(netmap.NetworkInfo)
+
+	if !empty {
+		m.SetMagicNumber(228)
+		m.SetCurrentEpoch(666)
+	}
+
+	return m
+}
+
+func GenerateNetworkInfoRequestBody(empty bool) *netmap.NetworkInfoRequestBody {
+	m := new(netmap.NetworkInfoRequestBody)
+
+	return m
+}
+
+func GenerateNetworkInfoRequest(empty bool) *netmap.NetworkInfoRequest {
+	m := new(netmap.NetworkInfoRequest)
+
+	m.SetBody(GenerateNetworkInfoRequestBody(empty))
+	m.SetMetaHeader(sessiontest.GenerateRequestMetaHeader(empty))
+	m.SetVerificationHeader(sessiontest.GenerateRequestVerificationHeader(empty))
+
+	return m
+}
+
+func GenerateNetworkInfoResponseBody(empty bool) *netmap.NetworkInfoResponseBody {
+	m := new(netmap.NetworkInfoResponseBody)
+
+	m.SetNetworkInfo(GenerateNetworkInfo(empty))
+
+	return m
+}
+
+func GenerateNetworkInfoResponse(empty bool) *netmap.NetworkInfoResponse {
+	m := new(netmap.NetworkInfoResponse)
+
+	m.SetBody(GenerateNetworkInfoResponseBody(empty))
+	m.SetMetaHeader(sessiontest.GenerateResponseMetaHeader(empty))
+	m.SetVerificationHeader(sessiontest.GenerateResponseVerificationHeader(empty))
+
+	return m
+}
diff --git a/v2/netmap/types.go b/v2/netmap/types.go
index 320384e..e67e0a6 100644
--- a/v2/netmap/types.go
+++ b/v2/netmap/types.go
@@ -5,6 +5,32 @@ import (
 	"github.com/nspcc-dev/neofs-api-go/v2/session"
 )
 
+type LocalNodeInfoRequest struct {
+	body *LocalNodeInfoRequestBody
+
+	session.RequestHeaders
+}
+
+type LocalNodeInfoResponse struct {
+	body *LocalNodeInfoResponseBody
+
+	session.ResponseHeaders
+}
+
+// NetworkInfoRequest is a structure of NetworkInfo request.
+type NetworkInfoRequest struct {
+	body *NetworkInfoRequestBody
+
+	session.RequestHeaders
+}
+
+// NetworkInfoResponse is a structure of NetworkInfo response.
+type NetworkInfoResponse struct {
+	body *NetworkInfoResponseBody
+
+	session.ResponseHeaders
+}
+
 type Filter struct {
 	name    string
 	key     string
@@ -443,32 +469,6 @@ func (l *LocalNodeInfoRequest) SetBody(body *LocalNodeInfoRequestBody) {
 	}
 }
 
-func (l *LocalNodeInfoRequest) GetMetaHeader() *session.RequestMetaHeader {
-	if l != nil {
-		return l.metaHeader
-	}
-	return nil
-}
-
-func (l *LocalNodeInfoRequest) SetMetaHeader(metaHeader *session.RequestMetaHeader) {
-	if l != nil {
-		l.metaHeader = metaHeader
-	}
-}
-
-func (l *LocalNodeInfoRequest) GetVerificationHeader() *session.RequestVerificationHeader {
-	if l != nil {
-		return l.verifyHeader
-	}
-	return nil
-}
-
-func (l *LocalNodeInfoRequest) SetVerificationHeader(verifyHeader *session.RequestVerificationHeader) {
-	if l != nil {
-		l.verifyHeader = verifyHeader
-	}
-}
-
 func (l *LocalNodeInfoResponse) GetBody() *LocalNodeInfoResponseBody {
 	if l != nil {
 		return l.body
@@ -482,32 +482,6 @@ func (l *LocalNodeInfoResponse) SetBody(body *LocalNodeInfoResponseBody) {
 	}
 }
 
-func (l *LocalNodeInfoResponse) GetMetaHeader() *session.ResponseMetaHeader {
-	if l != nil {
-		return l.metaHeader
-	}
-	return nil
-}
-
-func (l *LocalNodeInfoResponse) SetMetaHeader(metaHeader *session.ResponseMetaHeader) {
-	if l != nil {
-		l.metaHeader = metaHeader
-	}
-}
-
-func (l *LocalNodeInfoResponse) GetVerificationHeader() *session.ResponseVerificationHeader {
-	if l != nil {
-		return l.verifyHeader
-	}
-	return nil
-}
-
-func (l *LocalNodeInfoResponse) SetVerificationHeader(verifyHeader *session.ResponseVerificationHeader) {
-	if l != nil {
-		l.verifyHeader = verifyHeader
-	}
-}
-
 // NetworkInfo groups information about
 // NeoFS network.
 type NetworkInfo struct {
@@ -583,32 +557,6 @@ func (l *NetworkInfoRequest) SetBody(body *NetworkInfoRequestBody) {
 	}
 }
 
-func (l *NetworkInfoRequest) GetMetaHeader() *session.RequestMetaHeader {
-	if l != nil {
-		return l.metaHeader
-	}
-	return nil
-}
-
-func (l *NetworkInfoRequest) SetMetaHeader(metaHeader *session.RequestMetaHeader) {
-	if l != nil {
-		l.metaHeader = metaHeader
-	}
-}
-
-func (l *NetworkInfoRequest) GetVerificationHeader() *session.RequestVerificationHeader {
-	if l != nil {
-		return l.verifyHeader
-	}
-	return nil
-}
-
-func (l *NetworkInfoRequest) SetVerificationHeader(verifyHeader *session.RequestVerificationHeader) {
-	if l != nil {
-		l.verifyHeader = verifyHeader
-	}
-}
-
 func (l *NetworkInfoResponse) GetBody() *NetworkInfoResponseBody {
 	if l != nil {
 		return l.body
@@ -621,29 +569,3 @@ func (l *NetworkInfoResponse) SetBody(body *NetworkInfoResponseBody) {
 		l.body = body
 	}
 }
-
-func (l *NetworkInfoResponse) GetMetaHeader() *session.ResponseMetaHeader {
-	if l != nil {
-		return l.metaHeader
-	}
-	return nil
-}
-
-func (l *NetworkInfoResponse) SetMetaHeader(metaHeader *session.ResponseMetaHeader) {
-	if l != nil {
-		l.metaHeader = metaHeader
-	}
-}
-
-func (l *NetworkInfoResponse) GetVerificationHeader() *session.ResponseVerificationHeader {
-	if l != nil {
-		return l.verifyHeader
-	}
-	return nil
-}
-
-func (l *NetworkInfoResponse) SetVerificationHeader(verifyHeader *session.ResponseVerificationHeader) {
-	if l != nil {
-		l.verifyHeader = verifyHeader
-	}
-}
diff --git a/v2/object/client.go b/v2/object/client.go
deleted file mode 100644
index 0287090..0000000
--- a/v2/object/client.go
+++ /dev/null
@@ -1,408 +0,0 @@
-package object
-
-import (
-	"context"
-
-	"github.com/nspcc-dev/neofs-api-go/v2/client"
-	object "github.com/nspcc-dev/neofs-api-go/v2/object/grpc"
-	"github.com/pkg/errors"
-	"google.golang.org/grpc"
-)
-
-// Client represents universal object
-// transport client.
-type Client struct {
-	getClient *getObjectClient
-
-	putClient *putObjectClient
-
-	headClient *headObjectClient
-
-	searchClient *searchObjectClient
-
-	deleteClient *deleteObjectClient
-
-	getRangeClient *getRangeObjectClient
-
-	getRangeHashClient *getRangeHashObjectClient
-}
-
-// Option represents Client option.
-type Option func(*cfg)
-
-type cfg struct {
-	proto client.Protocol
-
-	globalOpts []client.Option
-
-	gRPC cfgGRPC
-}
-
-type cfgGRPC struct {
-	serviceClient object.ObjectServiceClient
-
-	grpcCallOpts []grpc.CallOption
-
-	callOpts []object.Option
-
-	client *object.Client
-}
-
-// types of upper level sub-clients, accessed directly from object.Client
-type (
-	getObjectClient struct {
-		streamClientGetter func(context.Context, *GetRequest) (interface{}, error)
-
-		streamerConstructor func(interface{}) (GetObjectStreamer, error)
-	}
-
-	putObjectClient struct {
-		streamClientGetter func(context.Context) (interface{}, error)
-
-		streamerConstructor func(interface{}) (PutObjectStreamer, error)
-	}
-
-	headObjectClient struct {
-		requestConverter func(request *HeadRequest) interface{}
-
-		caller func(context.Context, interface{}) (interface{}, error)
-
-		responseConverter func(interface{}) *HeadResponse
-	}
-
-	deleteObjectClient struct {
-		requestConverter func(request *DeleteRequest) interface{}
-
-		caller func(context.Context, interface{}) (interface{}, error)
-
-		responseConverter func(interface{}) *DeleteResponse
-	}
-
-	searchObjectClient struct {
-		streamClientGetter func(context.Context, *SearchRequest) (interface{}, error)
-
-		streamerConstructor func(interface{}) (SearchObjectStreamer, error)
-	}
-
-	getRangeObjectClient struct {
-		streamClientGetter func(context.Context, *GetRangeRequest) (interface{}, error)
-
-		streamerConstructor func(interface{}) (GetRangeObjectStreamer, error)
-	}
-
-	getRangeHashObjectClient struct {
-		requestConverter func(request *GetRangeHashRequest) interface{}
-
-		caller func(context.Context, interface{}) (interface{}, error)
-
-		responseConverter func(interface{}) *GetRangeHashResponse
-	}
-)
-
-func (c *Client) Get(ctx context.Context, req *GetRequest) (GetObjectStreamer, error) {
-	cli, err := c.getClient.streamClientGetter(ctx, req)
-	if err != nil {
-		return nil, errors.Wrap(err, "could not send get object request")
-	}
-
-	return c.getClient.streamerConstructor(cli)
-}
-
-func (c *Client) Put(ctx context.Context) (PutObjectStreamer, error) {
-	cli, err := c.putClient.streamClientGetter(ctx)
-	if err != nil {
-		return nil, errors.Wrap(err, "could not prepare put object streamer")
-	}
-
-	return c.putClient.streamerConstructor(cli)
-}
-
-func (c *Client) Head(ctx context.Context, req *HeadRequest) (*HeadResponse, error) {
-	resp, err := c.headClient.caller(ctx, c.headClient.requestConverter(req))
-	if err != nil {
-		return nil, errors.Wrap(err, "could not send head object request")
-	}
-
-	return c.headClient.responseConverter(resp), nil
-}
-
-func (c *Client) Search(ctx context.Context, req *SearchRequest) (SearchObjectStreamer, error) {
-	cli, err := c.searchClient.streamClientGetter(ctx, req)
-	if err != nil {
-		return nil, err
-	}
-
-	return c.searchClient.streamerConstructor(cli)
-}
-
-func (c *Client) Delete(ctx context.Context, req *DeleteRequest) (*DeleteResponse, error) {
-	resp, err := c.deleteClient.caller(ctx, c.deleteClient.requestConverter(req))
-	if err != nil {
-		return nil, errors.Wrap(err, "could not send delete object request")
-	}
-
-	return c.deleteClient.responseConverter(resp), nil
-}
-
-func (c *Client) GetRange(ctx context.Context, req *GetRangeRequest) (GetRangeObjectStreamer, error) {
-	cli, err := c.getRangeClient.streamClientGetter(ctx, req)
-	if err != nil {
-		return nil, errors.Wrap(err, "could not send get object range request")
-	}
-
-	return c.getRangeClient.streamerConstructor(cli)
-}
-
-func (c *Client) GetRangeHash(ctx context.Context, req *GetRangeHashRequest) (*GetRangeHashResponse, error) {
-	resp, err := c.getRangeHashClient.caller(ctx, c.getRangeHashClient.requestConverter(req))
-	if err != nil {
-		return nil, errors.Wrap(err, "could not send get object range hash request")
-	}
-
-	return c.getRangeHashClient.responseConverter(resp), nil
-}
-
-func defaultCfg() *cfg {
-	return &cfg{
-		proto: client.ProtoGRPC,
-	}
-}
-
-func NewClient(opts ...Option) (*Client, error) {
-	cfg := defaultCfg()
-
-	for i := range opts {
-		opts[i](cfg)
-	}
-
-	var err error
-
-	switch cfg.proto {
-	case client.ProtoGRPC:
-		var c *object.Client
-		if c, err = newGRPCClient(cfg); err != nil {
-			break
-		}
-
-		return &Client{
-			getClient:          newGRPCGetClient(c),
-			putClient:          newGRPCPutClient(c),
-			headClient:         newGRPCHeadClient(c),
-			searchClient:       newGRPCSearchClient(c),
-			deleteClient:       newGRPCDeleteClient(c),
-			getRangeClient:     newGRPCGetRangeClient(c),
-			getRangeHashClient: newGRPCGetRangeHashClient(c),
-		}, nil
-	default:
-		err = client.ErrProtoUnsupported
-	}
-
-	return nil, errors.Wrapf(err, "could not create %s object client", cfg.proto)
-}
-
-func newGRPCClient(cfg *cfg) (*object.Client, error) {
-	var err error
-
-	if cfg.gRPC.client == nil {
-		if cfg.gRPC.serviceClient == nil {
-			conn, err := client.NewGRPCClientConn(cfg.globalOpts...)
-			if err != nil {
-				return nil, errors.Wrap(err, "could not open gRPC getClient connection")
-			}
-
-			cfg.gRPC.serviceClient = object.NewObjectServiceClient(conn)
-		}
-
-		cfg.gRPC.client, err = object.NewClient(
-			cfg.gRPC.serviceClient,
-			append(
-				cfg.gRPC.callOpts,
-				object.WithCallOptions(cfg.gRPC.grpcCallOpts),
-			)...,
-		)
-	}
-
-	return cfg.gRPC.client, err
-}
-
-func newGRPCGetClient(c *object.Client) *getObjectClient {
-	cli := &getObjectClient{
-		streamClientGetter: func(ctx context.Context, request *GetRequest) (interface{}, error) {
-			return c.Get(ctx, GetRequestToGRPCMessage(request))
-		},
-		streamerConstructor: func(i interface{}) (GetObjectStreamer, error) {
-			cli, ok := i.(object.ObjectService_GetClient)
-			if !ok {
-				return nil, errors.New("can't convert interface to grpc get getClient")
-			}
-			return &getObjectGRPCStream{
-				recv: func() (*GetResponse, error) {
-					resp, err := cli.Recv()
-					if err != nil {
-						return nil, err
-					}
-
-					return GetResponseFromGRPCMessage(resp), nil
-				},
-			}, nil
-		},
-	}
-
-	return cli
-}
-
-func newGRPCPutClient(c *object.Client) *putObjectClient {
-	cli := &putObjectClient{
-		streamClientGetter: func(ctx context.Context) (interface{}, error) {
-			return c.Put(ctx)
-		},
-		streamerConstructor: func(i interface{}) (PutObjectStreamer, error) {
-			cli, ok := i.(object.ObjectService_PutClient)
-			if !ok {
-				return nil, errors.New("can't convert interface to grpc get getClient")
-			}
-
-			return &putObjectGRPCStream{
-				send: func(request *PutRequest) error {
-					return cli.Send(PutRequestToGRPCMessage(request))
-				},
-				closeAndRecv: func() (*PutResponse, error) {
-					resp, err := cli.CloseAndRecv()
-					if err != nil {
-						return nil, err
-					}
-
-					return PutResponseFromGRPCMessage(resp), nil
-				},
-			}, nil
-		},
-	}
-
-	return cli
-}
-
-func newGRPCHeadClient(c *object.Client) *headObjectClient {
-	return &headObjectClient{
-		requestConverter: func(req *HeadRequest) interface{} {
-			return HeadRequestToGRPCMessage(req)
-		},
-		caller: func(ctx context.Context, req interface{}) (interface{}, error) {
-			return c.Head(ctx, req.(*object.HeadRequest))
-		},
-		responseConverter: func(resp interface{}) *HeadResponse {
-			return HeadResponseFromGRPCMessage(resp.(*object.HeadResponse))
-		},
-	}
-}
-
-func newGRPCSearchClient(c *object.Client) *searchObjectClient {
-	cli := &searchObjectClient{
-		streamClientGetter: func(ctx context.Context, request *SearchRequest) (interface{}, error) {
-			return c.Search(ctx, SearchRequestToGRPCMessage(request))
-		},
-		streamerConstructor: func(i interface{}) (SearchObjectStreamer, error) {
-			cli, ok := i.(object.ObjectService_SearchClient)
-			if !ok {
-				return nil, errors.New("can't convert interface to grpc get getClient")
-			}
-			return &searchObjectGRPCStream{
-				recv: func() (*SearchResponse, error) {
-					resp, err := cli.Recv()
-					if err != nil {
-						return nil, err
-					}
-
-					return SearchResponseFromGRPCMessage(resp), nil
-				},
-			}, nil
-		},
-	}
-
-	return cli
-}
-
-func newGRPCDeleteClient(c *object.Client) *deleteObjectClient {
-	return &deleteObjectClient{
-		requestConverter: func(req *DeleteRequest) interface{} {
-			return DeleteRequestToGRPCMessage(req)
-		},
-		caller: func(ctx context.Context, req interface{}) (interface{}, error) {
-			return c.Delete(ctx, req.(*object.DeleteRequest))
-		},
-		responseConverter: func(resp interface{}) *DeleteResponse {
-			return DeleteResponseFromGRPCMessage(resp.(*object.DeleteResponse))
-		},
-	}
-}
-
-func newGRPCGetRangeClient(c *object.Client) *getRangeObjectClient {
-	cli := &getRangeObjectClient{
-		streamClientGetter: func(ctx context.Context, request *GetRangeRequest) (interface{}, error) {
-			return c.GetRange(ctx, GetRangeRequestToGRPCMessage(request))
-		},
-		streamerConstructor: func(i interface{}) (GetRangeObjectStreamer, error) {
-			cli, ok := i.(object.ObjectService_GetRangeClient)
-			if !ok {
-				return nil, errors.New("can't convert interface to grpc get getClient")
-			}
-			return &getRangeObjectGRPCStream{
-				recv: func() (*GetRangeResponse, error) {
-					resp, err := cli.Recv()
-					if err != nil {
-						return nil, err
-					}
-
-					return GetRangeResponseFromGRPCMessage(resp), nil
-				},
-			}, nil
-		},
-	}
-
-	return cli
-}
-
-func newGRPCGetRangeHashClient(c *object.Client) *getRangeHashObjectClient {
-	return &getRangeHashObjectClient{
-		requestConverter: func(req *GetRangeHashRequest) interface{} {
-			return GetRangeHashRequestToGRPCMessage(req)
-		},
-		caller: func(ctx context.Context, req interface{}) (interface{}, error) {
-			return c.GetRangeHash(ctx, req.(*object.GetRangeHashRequest))
-		},
-		responseConverter: func(resp interface{}) *GetRangeHashResponse {
-			return GetRangeHashResponseFromGRPCMessage(resp.(*object.GetRangeHashResponse))
-		},
-	}
-}
-
-func WithGlobalOpts(v ...client.Option) Option {
-	return func(c *cfg) {
-		if len(v) > 0 {
-			c.globalOpts = v
-		}
-	}
-}
-
-func WithGRPCServiceClient(v object.ObjectServiceClient) Option {
-	return func(c *cfg) {
-		c.gRPC.serviceClient = v
-	}
-}
-
-func WithGRPCCallOpts(v []grpc.CallOption) Option {
-	return func(c *cfg) {
-		c.gRPC.grpcCallOpts = v
-	}
-}
-
-func WithGRPCClientOpts(v []object.Option) Option {
-	return func(c *cfg) {
-		c.gRPC.callOpts = v
-	}
-}
-
-func WithGRPCClient(v *object.Client) Option {
-	return func(c *cfg) {
-		c.gRPC.client = v
-	}
-}
diff --git a/v2/object/client_stream.go b/v2/object/client_stream.go
deleted file mode 100644
index ea9dc98..0000000
--- a/v2/object/client_stream.go
+++ /dev/null
@@ -1,60 +0,0 @@
-package object
-
-type (
-	GetObjectStreamer interface {
-		Recv() (*GetResponse, error)
-	}
-
-	PutObjectStreamer interface {
-		Send(*PutRequest) error
-		CloseAndRecv() (*PutResponse, error)
-	}
-
-	SearchObjectStreamer interface {
-		Recv() (*SearchResponse, error)
-	}
-
-	GetRangeObjectStreamer interface {
-		Recv() (*GetRangeResponse, error)
-	}
-)
-
-type (
-	getObjectGRPCStream struct {
-		recv func() (*GetResponse, error)
-	}
-
-	putObjectGRPCStream struct {
-		send func(*PutRequest) error
-
-		closeAndRecv func() (*PutResponse, error)
-	}
-
-	searchObjectGRPCStream struct {
-		recv func() (*SearchResponse, error)
-	}
-
-	getRangeObjectGRPCStream struct {
-		recv func() (*GetRangeResponse, error)
-	}
-)
-
-func (s *getObjectGRPCStream) Recv() (*GetResponse, error) {
-	return s.recv()
-}
-
-func (p *putObjectGRPCStream) Send(request *PutRequest) error {
-	return p.send(request)
-}
-
-func (p *putObjectGRPCStream) CloseAndRecv() (*PutResponse, error) {
-	return p.closeAndRecv()
-}
-
-func (s *searchObjectGRPCStream) Recv() (*SearchResponse, error) {
-	return s.recv()
-}
-
-func (r *getRangeObjectGRPCStream) Recv() (*GetRangeResponse, error) {
-	return r.recv()
-}
diff --git a/v2/object/convert.go b/v2/object/convert.go
index 88b0e0e..96b1b5b 100644
--- a/v2/object/convert.go
+++ b/v2/object/convert.go
@@ -3,10 +3,14 @@ package object
 import (
 	"fmt"
 
+	"github.com/nspcc-dev/neofs-api-go/rpc/grpc"
+	"github.com/nspcc-dev/neofs-api-go/rpc/message"
 	object "github.com/nspcc-dev/neofs-api-go/v2/object/grpc"
 	"github.com/nspcc-dev/neofs-api-go/v2/refs"
 	refsGRPC "github.com/nspcc-dev/neofs-api-go/v2/refs/grpc"
 	"github.com/nspcc-dev/neofs-api-go/v2/session"
+	sessionGRPC "github.com/nspcc-dev/neofs-api-go/v2/session/grpc"
+	"github.com/pkg/errors"
 )
 
 func TypeToGRPCField(t Type) object.ObjectType {
@@ -25,1636 +29,2020 @@ func MatchTypeFromGRPCField(t object.MatchType) MatchType {
 	return MatchType(t)
 }
 
-func ShortHeaderToGRPCMessage(h *ShortHeader) *object.ShortHeader {
-	if h == nil {
-		return nil
-	}
-
-	m := new(object.ShortHeader)
-
-	m.SetVersion(
-		refs.VersionToGRPCMessage(h.GetVersion()),
-	)
-
-	m.SetCreationEpoch(h.GetCreationEpoch())
-
-	m.SetOwnerId(
-		refs.OwnerIDToGRPCMessage(h.GetOwnerID()),
-	)
-
-	m.SetObjectType(
-		TypeToGRPCField(h.GetObjectType()),
-	)
-
-	m.SetPayloadLength(h.GetPayloadLength())
-
-	m.SetPayloadHash(
-		refs.ChecksumToGRPCMessage(h.GetPayloadHash()),
-	)
-
-	m.SetHomomorphicHash(
-		refs.ChecksumToGRPCMessage(h.GetHomomorphicHash()),
-	)
-
-	return m
-}
-
-func ShortHeaderFromGRPCMessage(m *object.ShortHeader) *ShortHeader {
-	if m == nil {
-		return nil
-	}
-
-	h := new(ShortHeader)
-
-	h.SetVersion(
-		refs.VersionFromGRPCMessage(m.GetVersion()),
-	)
-
-	h.SetCreationEpoch(m.GetCreationEpoch())
-
-	h.SetOwnerID(
-		refs.OwnerIDFromGRPCMessage(m.GetOwnerId()),
-	)
-
-	h.SetObjectType(
-		TypeFromGRPCField(m.GetObjectType()),
-	)
-
-	h.SetPayloadLength(m.GetPayloadLength())
-
-	h.SetPayloadHash(
-		refs.ChecksumFromGRPCMessage(m.GetPayloadHash()),
-	)
-
-	h.SetHomomorphicHash(
-		refs.ChecksumFromGRPCMessage(m.GetHomomorphicHash()),
-	)
-
-	return h
-}
-
-func AttributeToGRPCMessage(a *Attribute) *object.Header_Attribute {
-	if a == nil {
-		return nil
-	}
-
-	m := new(object.Header_Attribute)
-
-	m.SetKey(a.GetKey())
-	m.SetValue(a.GetValue())
-
-	return m
-}
-
-func AttributeFromGRPCMessage(m *object.Header_Attribute) *Attribute {
-	if m == nil {
-		return nil
-	}
-
-	h := new(Attribute)
-
-	h.SetKey(m.GetKey())
-	h.SetValue(m.GetValue())
-
-	return h
-}
-
-func SplitHeaderToGRPCMessage(h *SplitHeader) *object.Header_Split {
-	if h == nil {
-		return nil
-	}
-
-	m := new(object.Header_Split)
-
-	m.SetParent(
-		refs.ObjectIDToGRPCMessage(h.GetParent()),
-	)
-
-	m.SetPrevious(
-		refs.ObjectIDToGRPCMessage(h.GetPrevious()),
-	)
-
-	m.SetParentSignature(
-		refs.SignatureToGRPCMessage(h.GetParentSignature()),
-	)
-
-	m.SetParentHeader(
-		HeaderToGRPCMessage(h.GetParentHeader()),
-	)
-
-	children := h.GetChildren()
-	childMsg := make([]*refsGRPC.ObjectID, 0, len(children))
-
-	for i := range children {
-		childMsg = append(childMsg, refs.ObjectIDToGRPCMessage(children[i]))
-	}
-
-	m.SetChildren(childMsg)
-
-	m.SetSplitId(h.GetSplitID())
-
-	return m
-}
-
-func SplitHeaderFromGRPCMessage(m *object.Header_Split) *SplitHeader {
-	if m == nil {
-		return nil
-	}
-
-	h := new(SplitHeader)
-
-	h.SetParent(
-		refs.ObjectIDFromGRPCMessage(m.GetParent()),
-	)
-
-	h.SetPrevious(
-		refs.ObjectIDFromGRPCMessage(m.GetPrevious()),
-	)
-
-	h.SetParentSignature(
-		refs.SignatureFromGRPCMessage(m.GetParentSignature()),
-	)
-
-	h.SetParentHeader(
-		HeaderFromGRPCMessage(m.GetParentHeader()),
-	)
-
-	childMsg := m.GetChildren()
-	children := make([]*refs.ObjectID, 0, len(childMsg))
-
-	for i := range childMsg {
-		children = append(children, refs.ObjectIDFromGRPCMessage(childMsg[i]))
-	}
-
-	h.SetChildren(children)
-
-	h.SetSplitID(m.GetSplitId())
-
-	return h
-}
-
-func HeaderToGRPCMessage(h *Header) *object.Header {
-	if h == nil {
-		return nil
-	}
-
-	m := new(object.Header)
-
-	m.SetVersion(
-		refs.VersionToGRPCMessage(h.GetVersion()),
-	)
-
-	m.SetContainerId(
-		refs.ContainerIDToGRPCMessage(h.GetContainerID()),
-	)
-
-	m.SetOwnerId(
-		refs.OwnerIDToGRPCMessage(h.GetOwnerID()),
-	)
-
-	m.SetCreationEpoch(h.GetCreationEpoch())
-
-	m.SetPayloadLength(h.GetPayloadLength())
-
-	m.SetPayloadHash(
-		refs.ChecksumToGRPCMessage(h.GetPayloadHash()),
-	)
-
-	m.SetHomomorphicHash(
-		refs.ChecksumToGRPCMessage(h.GetHomomorphicHash()),
-	)
-
-	m.SetObjectType(
-		TypeToGRPCField(h.GetObjectType()),
-	)
-
-	m.SetSessionToken(
-		session.SessionTokenToGRPCMessage(h.GetSessionToken()),
-	)
-
-	attr := h.GetAttributes()
-	attrMsg := make([]*object.Header_Attribute, 0, len(attr))
-
-	for i := range attr {
-		attrMsg = append(attrMsg, AttributeToGRPCMessage(attr[i]))
-	}
-
-	m.SetAttributes(attrMsg)
-
-	m.SetSplit(
-		SplitHeaderToGRPCMessage(h.GetSplit()),
-	)
-
-	return m
-}
-
-func HeaderFromGRPCMessage(m *object.Header) *Header {
-	if m == nil {
-		return nil
-	}
-
-	h := new(Header)
-
-	h.SetVersion(
-		refs.VersionFromGRPCMessage(m.GetVersion()),
-	)
-
-	h.SetContainerID(
-		refs.ContainerIDFromGRPCMessage(m.GetContainerId()),
-	)
-
-	h.SetOwnerID(
-		refs.OwnerIDFromGRPCMessage(m.GetOwnerId()),
-	)
-
-	h.SetCreationEpoch(m.GetCreationEpoch())
-
-	h.SetPayloadLength(m.GetPayloadLength())
-
-	h.SetPayloadHash(
-		refs.ChecksumFromGRPCMessage(m.GetPayloadHash()),
-	)
-
-	h.SetHomomorphicHash(
-		refs.ChecksumFromGRPCMessage(m.GetHomomorphicHash()),
-	)
-
-	h.SetObjectType(
-		TypeFromGRPCField(m.GetObjectType()),
-	)
-
-	h.SetSessionToken(
-		session.SessionTokenFromGRPCMessage(m.GetSessionToken()),
-	)
-
-	attrMsg := m.GetAttributes()
-	attr := make([]*Attribute, 0, len(attrMsg))
-
-	for i := range attrMsg {
-		attr = append(attr, AttributeFromGRPCMessage(attrMsg[i]))
-	}
-
-	h.SetAttributes(attr)
-
-	h.SetSplit(
-		SplitHeaderFromGRPCMessage(m.GetSplit()),
-	)
-
-	return h
-}
-
-func HeaderWithSignatureToGRPCMessage(h *HeaderWithSignature) *object.HeaderWithSignature {
-	if h == nil {
-		return nil
-	}
-
-	m := new(object.HeaderWithSignature)
-
-	m.SetHeader(
-		HeaderToGRPCMessage(h.GetHeader()),
-	)
-
-	m.SetSignature(
-		refs.SignatureToGRPCMessage(h.GetSignature()),
-	)
-
-	return m
-}
-
-func HeaderWithSignatureFromGRPCMessage(m *object.HeaderWithSignature) *HeaderWithSignature {
-	if m == nil {
-		return nil
-	}
-
-	h := new(HeaderWithSignature)
-
-	h.SetHeader(
-		HeaderFromGRPCMessage(m.GetHeader()),
-	)
-
-	h.SetSignature(
-		refs.SignatureFromGRPCMessage(m.GetSignature()),
-	)
-
-	return h
-}
-
-func ObjectToGRPCMessage(o *Object) *object.Object {
-	if o == nil {
-		return nil
-	}
-
-	m := new(object.Object)
-
-	m.SetObjectId(
-		refs.ObjectIDToGRPCMessage(o.GetObjectID()),
-	)
-
-	m.SetSignature(
-		refs.SignatureToGRPCMessage(o.GetSignature()),
-	)
-
-	m.SetHeader(
-		HeaderToGRPCMessage(o.GetHeader()),
-	)
-
-	m.SetPayload(o.GetPayload())
-
-	return m
-}
-
-func ObjectFromGRPCMessage(m *object.Object) *Object {
-	if m == nil {
-		return nil
-	}
-
-	o := new(Object)
-
-	o.SetObjectID(
-		refs.ObjectIDFromGRPCMessage(m.GetObjectId()),
-	)
-
-	o.SetSignature(
-		refs.SignatureFromGRPCMessage(m.GetSignature()),
-	)
-
-	o.SetHeader(
-		HeaderFromGRPCMessage(m.GetHeader()),
-	)
-
-	o.SetPayload(m.GetPayload())
-
-	return o
-}
-
-func SplitInfoToGRPCMessage(s *SplitInfo) *object.SplitInfo {
-	if s == nil {
-		return nil
-	}
-
-	m := new(object.SplitInfo)
-
-	m.SetSplitId(s.GetSplitID())
-
-	m.SetLastPart(
-		refs.ObjectIDToGRPCMessage(s.GetLastPart()),
-	)
-
-	m.SetLink(
-		refs.ObjectIDToGRPCMessage(s.GetLink()),
-	)
-
-	return m
-}
-
-func SplitInfoFromGRPCMessage(m *object.SplitInfo) *SplitInfo {
-	if m == nil {
-		return nil
-	}
-
-	r := new(SplitInfo)
-
-	r.SetSplitID(m.GetSplitId())
-
-	r.SetLastPart(
-		refs.ObjectIDFromGRPCMessage(m.GetLastPart()),
-	)
-
-	r.SetLink(
-		refs.ObjectIDFromGRPCMessage(m.GetLink()),
-	)
-
-	return r
-}
-
-func GetRequestBodyToGRPCMessage(r *GetRequestBody) *object.GetRequest_Body {
-	if r == nil {
-		return nil
-	}
-
-	m := new(object.GetRequest_Body)
-
-	m.SetAddress(
-		refs.AddressToGRPCMessage(r.GetAddress()),
-	)
-
-	m.SetRaw(r.GetRaw())
-
-	return m
-}
-
-func GetRequestBodyFromGRPCMessage(m *object.GetRequest_Body) *GetRequestBody {
-	if m == nil {
-		return nil
-	}
-
-	r := new(GetRequestBody)
-
-	r.SetAddress(
-		refs.AddressFromGRPCMessage(m.GetAddress()),
-	)
-
-	r.SetRaw(m.GetRaw())
-
-	return r
-}
-
-func GetRequestToGRPCMessage(r *GetRequest) *object.GetRequest {
-	if r == nil {
-		return nil
-	}
-
-	m := new(object.GetRequest)
-
-	m.SetBody(
-		GetRequestBodyToGRPCMessage(r.GetBody()),
-	)
-
-	session.RequestHeadersToGRPC(r, m)
-
-	return m
-}
-
-func GetRequestFromGRPCMessage(m *object.GetRequest) *GetRequest {
-	if m == nil {
-		return nil
-	}
-
-	r := new(GetRequest)
-
-	r.SetBody(
-		GetRequestBodyFromGRPCMessage(m.GetBody()),
-	)
-
-	session.RequestHeadersFromGRPC(m, r)
-
-	return r
-}
-
-func GetObjectPartInitToGRPCMessage(r *GetObjectPartInit) *object.GetResponse_Body_Init {
-	if r == nil {
-		return nil
-	}
-
-	m := new(object.GetResponse_Body_Init)
-
-	m.SetObjectId(
-		refs.ObjectIDToGRPCMessage(r.GetObjectID()),
-	)
-
-	m.SetSignature(
-		refs.SignatureToGRPCMessage(r.GetSignature()),
-	)
-
-	m.SetHeader(
-		HeaderToGRPCMessage(r.GetHeader()),
-	)
-
-	return m
-}
-
-func GetObjectPartInitFromGRPCMessage(m *object.GetResponse_Body_Init) *GetObjectPartInit {
-	if m == nil {
-		return nil
-	}
-
-	r := new(GetObjectPartInit)
-
-	r.SetObjectID(
-		refs.ObjectIDFromGRPCMessage(m.GetObjectId()),
-	)
-
-	r.SetSignature(
-		refs.SignatureFromGRPCMessage(m.GetSignature()),
-	)
-
-	r.SetHeader(
-		HeaderFromGRPCMessage(m.GetHeader()),
-	)
-
-	return r
-}
-
-func GetObjectPartChunkToGRPCMessage(r *GetObjectPartChunk) *object.GetResponse_Body_Chunk {
-	if r == nil {
-		return nil
-	}
-
-	m := new(object.GetResponse_Body_Chunk)
-
-	m.SetChunk(r.GetChunk())
-
-	return m
-}
-
-func GetObjectPartChunkFromGRPCMessage(m *object.GetResponse_Body_Chunk) *GetObjectPartChunk {
-	if m == nil {
-		return nil
-	}
-
-	r := new(GetObjectPartChunk)
-
-	r.SetChunk(m.GetChunk())
-
-	return r
-}
-
-func GetResponseBodyToGRPCMessage(r *GetResponseBody) *object.GetResponse_Body {
-	if r == nil {
-		return nil
-	}
-
-	m := new(object.GetResponse_Body)
-
-	switch v := r.GetObjectPart(); t := v.(type) {
-	case nil:
-	case *GetObjectPartInit:
-		m.SetInit(
-			GetObjectPartInitToGRPCMessage(t),
-		)
-	case *GetObjectPartChunk:
-		m.SetChunk(
-			GetObjectPartChunkToGRPCMessage(t),
-		)
-	case *SplitInfo:
-		m.SetSplitInfo(
-			SplitInfoToGRPCMessage(t),
-		)
-	default:
-		panic(fmt.Sprintf("unknown object part %T", t))
+func (h *ShortHeader) ToGRPCMessage() grpc.Message {
+	var m *object.ShortHeader
+
+	if h != nil {
+		m = new(object.ShortHeader)
+
+		m.SetVersion(h.version.ToGRPCMessage().(*refsGRPC.Version))
+		m.SetOwnerId(h.ownerID.ToGRPCMessage().(*refsGRPC.OwnerID))
+		m.SetHomomorphicHash(h.homoHash.ToGRPCMessage().(*refsGRPC.Checksum))
+		m.SetPayloadHash(h.payloadHash.ToGRPCMessage().(*refsGRPC.Checksum))
+		m.SetObjectType(TypeToGRPCField(h.typ))
+		m.SetCreationEpoch(h.creatEpoch)
+		m.SetPayloadLength(h.payloadLen)
 	}
 
 	return m
 }
 
-func GetResponseBodyFromGRPCMessage(m *object.GetResponse_Body) *GetResponseBody {
-	if m == nil {
-		return nil
+func (h *ShortHeader) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*object.ShortHeader)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
 	}
 
-	r := new(GetResponseBody)
+	var err error
 
-	switch v := m.GetObjectPart().(type) {
+	version := v.GetVersion()
+	if version == nil {
+		h.version = nil
+	} else {
+		if h.version == nil {
+			h.version = new(refs.Version)
+		}
+
+		err = h.version.FromGRPCMessage(version)
+		if err != nil {
+			return err
+		}
+	}
+
+	ownerID := v.GetOwnerId()
+	if ownerID == nil {
+		h.ownerID = nil
+	} else {
+		if h.ownerID == nil {
+			h.ownerID = new(refs.OwnerID)
+		}
+
+		err = h.ownerID.FromGRPCMessage(ownerID)
+		if err != nil {
+			return err
+		}
+	}
+
+	homoHash := v.GetHomomorphicHash()
+	if homoHash == nil {
+		h.homoHash = nil
+	} else {
+		if h.homoHash == nil {
+			h.homoHash = new(refs.Checksum)
+		}
+
+		err = h.homoHash.FromGRPCMessage(homoHash)
+		if err != nil {
+			return err
+		}
+	}
+
+	payloadHash := v.GetPayloadHash()
+	if payloadHash == nil {
+		h.payloadHash = nil
+	} else {
+		if h.payloadHash == nil {
+			h.payloadHash = new(refs.Checksum)
+		}
+
+		err = h.payloadHash.FromGRPCMessage(payloadHash)
+		if err != nil {
+			return err
+		}
+	}
+
+	h.typ = TypeFromGRPCField(v.GetObjectType())
+	h.creatEpoch = v.GetCreationEpoch()
+	h.payloadLen = v.GetPayloadLength()
+
+	return nil
+}
+
+func (a *Attribute) ToGRPCMessage() grpc.Message {
+	var m *object.Header_Attribute
+
+	if a != nil {
+		m = new(object.Header_Attribute)
+
+		m.SetKey(a.key)
+		m.SetValue(a.val)
+	}
+
+	return m
+}
+
+func (a *Attribute) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*object.Header_Attribute)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
+	}
+
+	a.key = v.GetKey()
+	a.val = v.GetValue()
+
+	return nil
+}
+
+func AttributesToGRPC(xs []*Attribute) (res []*object.Header_Attribute) {
+	if xs != nil {
+		res = make([]*object.Header_Attribute, 0, len(xs))
+
+		for i := range xs {
+			res = append(res, xs[i].ToGRPCMessage().(*object.Header_Attribute))
+		}
+	}
+
+	return
+}
+
+func AttributesFromGRPC(xs []*object.Header_Attribute) (res []*Attribute, err error) {
+	if xs != nil {
+		res = make([]*Attribute, 0, len(xs))
+
+		for i := range xs {
+			var x *Attribute
+
+			if xs[i] != nil {
+				x = new(Attribute)
+
+				err = x.FromGRPCMessage(xs[i])
+				if err != nil {
+					return
+				}
+			}
+
+			res = append(res, x)
+		}
+	}
+
+	return
+}
+
+func (h *SplitHeader) ToGRPCMessage() grpc.Message {
+	var m *object.Header_Split
+
+	if h != nil {
+		m = new(object.Header_Split)
+
+		m.SetParent(h.par.ToGRPCMessage().(*refsGRPC.ObjectID))
+		m.SetPrevious(h.prev.ToGRPCMessage().(*refsGRPC.ObjectID))
+		m.SetParentHeader(h.parHdr.ToGRPCMessage().(*object.Header))
+		m.SetParentSignature(h.parSig.ToGRPCMessage().(*refsGRPC.Signature))
+		m.SetChildren(refs.ObjectIDListToGRPCMessage(h.children))
+		m.SetSplitId(h.splitID)
+	}
+
+	return m
+}
+
+func (h *SplitHeader) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*object.Header_Split)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
+	}
+
+	var err error
+
+	par := v.GetParent()
+	if par == nil {
+		h.par = nil
+	} else {
+		if h.par == nil {
+			h.par = new(refs.ObjectID)
+		}
+
+		err = h.par.FromGRPCMessage(par)
+		if err != nil {
+			return err
+		}
+	}
+
+	prev := v.GetPrevious()
+	if prev == nil {
+		h.prev = nil
+	} else {
+		if h.prev == nil {
+			h.prev = new(refs.ObjectID)
+		}
+
+		err = h.prev.FromGRPCMessage(prev)
+		if err != nil {
+			return err
+		}
+	}
+
+	parHdr := v.GetParentHeader()
+	if parHdr == nil {
+		h.parHdr = nil
+	} else {
+		if h.parHdr == nil {
+			h.parHdr = new(Header)
+		}
+
+		err = h.parHdr.FromGRPCMessage(parHdr)
+		if err != nil {
+			return err
+		}
+	}
+
+	parSig := v.GetParentSignature()
+	if parSig == nil {
+		h.parSig = nil
+	} else {
+		if h.parSig == nil {
+			h.parSig = new(refs.Signature)
+		}
+
+		err = h.parSig.FromGRPCMessage(parSig)
+		if err != nil {
+			return err
+		}
+	}
+
+	h.children, err = refs.ObjectIDListFromGRPCMessage(v.GetChildren())
+	if err != nil {
+		return err
+	}
+
+	h.splitID = v.GetSplitId()
+
+	return nil
+}
+
+func (h *Header) ToGRPCMessage() grpc.Message {
+	var m *object.Header
+
+	if h != nil {
+		m = new(object.Header)
+
+		m.SetVersion(h.version.ToGRPCMessage().(*refsGRPC.Version))
+		m.SetPayloadHash(h.payloadHash.ToGRPCMessage().(*refsGRPC.Checksum))
+		m.SetOwnerId(h.ownerID.ToGRPCMessage().(*refsGRPC.OwnerID))
+		m.SetHomomorphicHash(h.homoHash.ToGRPCMessage().(*refsGRPC.Checksum))
+		m.SetContainerId(h.cid.ToGRPCMessage().(*refsGRPC.ContainerID))
+		m.SetSessionToken(h.sessionToken.ToGRPCMessage().(*sessionGRPC.SessionToken))
+		m.SetSplit(h.split.ToGRPCMessage().(*object.Header_Split))
+		m.SetAttributes(AttributesToGRPC(h.attr))
+		m.SetPayloadLength(h.payloadLen)
+		m.SetCreationEpoch(h.creatEpoch)
+		m.SetObjectType(TypeToGRPCField(h.typ))
+	}
+
+	return m
+}
+
+func (h *Header) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*object.Header)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
+	}
+
+	var err error
+
+	version := v.GetVersion()
+	if version == nil {
+		h.version = nil
+	} else {
+		if h.version == nil {
+			h.version = new(refs.Version)
+		}
+
+		err = h.version.FromGRPCMessage(version)
+		if err != nil {
+			return err
+		}
+	}
+
+	payloadHash := v.GetPayloadHash()
+	if payloadHash == nil {
+		h.payloadHash = nil
+	} else {
+		if h.payloadHash == nil {
+			h.payloadHash = new(refs.Checksum)
+		}
+
+		err = h.payloadHash.FromGRPCMessage(payloadHash)
+		if err != nil {
+			return err
+		}
+	}
+
+	ownerID := v.GetOwnerId()
+	if ownerID == nil {
+		h.ownerID = nil
+	} else {
+		if h.ownerID == nil {
+			h.ownerID = new(refs.OwnerID)
+		}
+
+		err = h.ownerID.FromGRPCMessage(ownerID)
+		if err != nil {
+			return err
+		}
+	}
+
+	homoHash := v.GetHomomorphicHash()
+	if homoHash == nil {
+		h.homoHash = nil
+	} else {
+		if h.homoHash == nil {
+			h.homoHash = new(refs.Checksum)
+		}
+
+		err = h.homoHash.FromGRPCMessage(homoHash)
+		if err != nil {
+			return err
+		}
+	}
+
+	cid := v.GetContainerId()
+	if cid == nil {
+		h.cid = nil
+	} else {
+		if h.cid == nil {
+			h.cid = new(refs.ContainerID)
+		}
+
+		err = h.cid.FromGRPCMessage(cid)
+		if err != nil {
+			return err
+		}
+	}
+
+	sessionToken := v.GetSessionToken()
+	if sessionToken == nil {
+		h.sessionToken = nil
+	} else {
+		if h.sessionToken == nil {
+			h.sessionToken = new(session.SessionToken)
+		}
+
+		err = h.sessionToken.FromGRPCMessage(sessionToken)
+		if err != nil {
+			return err
+		}
+	}
+
+	split := v.GetSplit()
+	if split == nil {
+		h.split = nil
+	} else {
+		if h.split == nil {
+			h.split = new(SplitHeader)
+		}
+
+		err = h.split.FromGRPCMessage(split)
+		if err != nil {
+			return err
+		}
+	}
+
+	h.attr, err = AttributesFromGRPC(v.GetAttributes())
+	if err != nil {
+		return err
+	}
+
+	h.payloadLen = v.GetPayloadLength()
+	h.creatEpoch = v.GetCreationEpoch()
+	h.typ = TypeFromGRPCField(v.GetObjectType())
+
+	return nil
+}
+
+func (h *HeaderWithSignature) ToGRPCMessage() grpc.Message {
+	var m *object.HeaderWithSignature
+
+	if h != nil {
+		m = new(object.HeaderWithSignature)
+
+		m.SetSignature(h.signature.ToGRPCMessage().(*refsGRPC.Signature))
+		m.SetHeader(h.header.ToGRPCMessage().(*object.Header))
+	}
+
+	return m
+}
+
+func (h *HeaderWithSignature) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*object.HeaderWithSignature)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
+	}
+
+	var err error
+
+	signature := v.GetSignature()
+	if signature == nil {
+		h.signature = nil
+	} else {
+		if h.signature == nil {
+			h.signature = new(refs.Signature)
+		}
+
+		err = h.signature.FromGRPCMessage(signature)
+		if err != nil {
+			return err
+		}
+	}
+
+	header := v.GetHeader()
+	if header == nil {
+		h.header = nil
+	} else {
+		if h.header == nil {
+			h.header = new(Header)
+		}
+
+		err = h.header.FromGRPCMessage(header)
+	}
+
+	return err
+}
+
+func (o *Object) ToGRPCMessage() grpc.Message {
+	var m *object.Object
+
+	if o != nil {
+		m = new(object.Object)
+
+		m.SetObjectId(o.objectID.ToGRPCMessage().(*refsGRPC.ObjectID))
+		m.SetSignature(o.idSig.ToGRPCMessage().(*refsGRPC.Signature))
+		m.SetHeader(o.header.ToGRPCMessage().(*object.Header))
+		m.SetPayload(o.payload)
+	}
+
+	return m
+}
+
+func (o *Object) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*object.Object)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
+	}
+
+	var err error
+
+	objectID := v.GetObjectId()
+	if objectID == nil {
+		o.objectID = nil
+	} else {
+		if o.objectID == nil {
+			o.objectID = new(refs.ObjectID)
+		}
+
+		err = o.objectID.FromGRPCMessage(objectID)
+		if err != nil {
+			return err
+		}
+	}
+
+	idSig := v.GetSignature()
+	if idSig == nil {
+		o.idSig = nil
+	} else {
+		if o.idSig == nil {
+			o.idSig = new(refs.Signature)
+		}
+
+		err = o.idSig.FromGRPCMessage(idSig)
+		if err != nil {
+			return err
+		}
+	}
+
+	header := v.GetHeader()
+	if header == nil {
+		o.header = nil
+	} else {
+		if o.header == nil {
+			o.header = new(Header)
+		}
+
+		err = o.header.FromGRPCMessage(header)
+		if err != nil {
+			return err
+		}
+	}
+
+	o.payload = v.GetPayload()
+
+	return nil
+}
+
+func (s *SplitInfo) ToGRPCMessage() grpc.Message {
+	var m *object.SplitInfo
+
+	if s != nil {
+		m = new(object.SplitInfo)
+
+		m.SetLastPart(s.lastPart.ToGRPCMessage().(*refsGRPC.ObjectID))
+		m.SetLink(s.link.ToGRPCMessage().(*refsGRPC.ObjectID))
+		m.SetSplitId(s.splitID)
+	}
+
+	return m
+}
+
+func (s *SplitInfo) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*object.SplitInfo)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
+	}
+
+	var err error
+
+	lastPart := v.GetLastPart()
+	if lastPart == nil {
+		s.lastPart = nil
+	} else {
+		if s.lastPart == nil {
+			s.lastPart = new(refs.ObjectID)
+		}
+
+		err = s.lastPart.FromGRPCMessage(lastPart)
+		if err != nil {
+			return err
+		}
+	}
+
+	link := v.GetLink()
+	if link == nil {
+		s.link = nil
+	} else {
+		if s.link == nil {
+			s.link = new(refs.ObjectID)
+		}
+
+		err = s.link.FromGRPCMessage(link)
+		if err != nil {
+			return err
+		}
+	}
+
+	s.splitID = v.GetSplitId()
+
+	return nil
+}
+
+func (r *GetRequestBody) ToGRPCMessage() grpc.Message {
+	var m *object.GetRequest_Body
+
+	if r != nil {
+		m = new(object.GetRequest_Body)
+
+		m.SetAddress(r.addr.ToGRPCMessage().(*refsGRPC.Address))
+		m.SetRaw(r.raw)
+	}
+
+	return m
+}
+
+func (r *GetRequestBody) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*object.GetRequest_Body)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
+	}
+
+	var err error
+
+	addr := v.GetAddress()
+	if addr == nil {
+		r.addr = nil
+	} else {
+		if r.addr == nil {
+			r.addr = new(refs.Address)
+		}
+
+		err = r.addr.FromGRPCMessage(addr)
+		if err != nil {
+			return err
+		}
+	}
+
+	r.raw = v.GetRaw()
+
+	return nil
+}
+
+func (r *GetRequest) ToGRPCMessage() grpc.Message {
+	var m *object.GetRequest
+
+	if r != nil {
+		m = new(object.GetRequest)
+
+		m.SetBody(r.body.ToGRPCMessage().(*object.GetRequest_Body))
+		r.RequestHeaders.ToMessage(m)
+	}
+
+	return m
+}
+
+func (r *GetRequest) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*object.GetRequest)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
+	}
+
+	var err error
+
+	body := v.GetBody()
+	if body == nil {
+		r.body = nil
+	} else {
+		if r.body == nil {
+			r.body = new(GetRequestBody)
+		}
+
+		err = r.body.FromGRPCMessage(body)
+		if err != nil {
+			return err
+		}
+	}
+
+	return r.RequestHeaders.FromMessage(v)
+}
+
+func (r *GetObjectPartInit) ToGRPCMessage() grpc.Message {
+	var m *object.GetResponse_Body_Init
+
+	if r != nil {
+		m = new(object.GetResponse_Body_Init)
+
+		m.SetObjectId(r.id.ToGRPCMessage().(*refsGRPC.ObjectID))
+		m.SetSignature(r.sig.ToGRPCMessage().(*refsGRPC.Signature))
+		m.SetHeader(r.hdr.ToGRPCMessage().(*object.Header))
+	}
+
+	return m
+}
+
+func (r *GetObjectPartInit) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*object.GetResponse_Body_Init)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
+	}
+
+	var err error
+
+	id := v.GetObjectId()
+	if id == nil {
+		r.id = nil
+	} else {
+		if r.id == nil {
+			r.id = new(refs.ObjectID)
+		}
+
+		err = r.id.FromGRPCMessage(id)
+		if err != nil {
+			return err
+		}
+	}
+
+	sig := v.GetSignature()
+	if sig == nil {
+		r.sig = nil
+	} else {
+		if r.sig == nil {
+			r.sig = new(refs.Signature)
+		}
+
+		err = r.sig.FromGRPCMessage(sig)
+		if err != nil {
+			return err
+		}
+	}
+
+	hdr := v.GetHeader()
+	if hdr == nil {
+		r.hdr = nil
+	} else {
+		if r.hdr == nil {
+			r.hdr = new(Header)
+		}
+
+		err = r.hdr.FromGRPCMessage(hdr)
+	}
+
+	return err
+}
+
+func (r *GetObjectPartChunk) ToGRPCMessage() grpc.Message {
+	var m *object.GetResponse_Body_Chunk
+
+	if r != nil {
+		m = new(object.GetResponse_Body_Chunk)
+
+		m.SetChunk(r.chunk)
+	}
+
+	return m
+}
+
+func (r *GetObjectPartChunk) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*object.GetResponse_Body_Chunk)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
+	}
+
+	r.chunk = v.GetChunk()
+
+	return nil
+}
+
+func (r *GetResponseBody) ToGRPCMessage() grpc.Message {
+	var m *object.GetResponse_Body
+
+	if r != nil {
+		m = new(object.GetResponse_Body)
+
+		switch v := r.GetObjectPart(); t := v.(type) {
+		case nil:
+			m.ObjectPart = nil
+		case *GetObjectPartInit:
+			m.SetInit(t.ToGRPCMessage().(*object.GetResponse_Body_Init))
+		case *GetObjectPartChunk:
+			m.SetChunk(t.ToGRPCMessage().(*object.GetResponse_Body_Chunk))
+		case *SplitInfo:
+			m.SetSplitInfo(t.ToGRPCMessage().(*object.SplitInfo))
+		default:
+			panic(fmt.Sprintf("unknown get object part %T", t))
+		}
+	}
+
+	return m
+}
+
+func (r *GetResponseBody) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*object.GetResponse_Body)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
+	}
+
+	var err error
+
+	r.objPart = nil
+
+	switch pt := v.GetObjectPart().(type) {
 	case nil:
 	case *object.GetResponse_Body_Init_:
-		r.SetObjectPart(
-			GetObjectPartInitFromGRPCMessage(v.Init),
-		)
+		if pt != nil {
+			partInit := new(GetObjectPartInit)
+			r.objPart = partInit
+			err = partInit.FromGRPCMessage(pt.Init)
+		}
 	case *object.GetResponse_Body_Chunk:
-		r.SetObjectPart(
-			GetObjectPartChunkFromGRPCMessage(v),
-		)
+		if pt != nil {
+			partChunk := new(GetObjectPartChunk)
+			r.objPart = partChunk
+			err = partChunk.FromGRPCMessage(pt)
+		}
 	case *object.GetResponse_Body_SplitInfo:
-		r.SetObjectPart(
-			SplitInfoFromGRPCMessage(v.SplitInfo),
-		)
+		if pt != nil {
+			partSplit := new(SplitInfo)
+			r.objPart = partSplit
+			err = partSplit.FromGRPCMessage(pt.SplitInfo)
+		}
 	default:
-		panic(fmt.Sprintf("unknown object part %T", v))
+		err = errors.Errorf("unknown get object part %T", pt)
 	}
 
-	return r
+	return err
 }
 
-func GetResponseToGRPCMessage(r *GetResponse) *object.GetResponse {
-	if r == nil {
-		return nil
-	}
+func (r *GetResponse) ToGRPCMessage() grpc.Message {
+	var m *object.GetResponse
 
-	m := new(object.GetResponse)
+	if r != nil {
+		m = new(object.GetResponse)
 
-	m.SetBody(
-		GetResponseBodyToGRPCMessage(r.GetBody()),
-	)
-
-	session.ResponseHeadersToGRPC(r, m)
-
-	return m
-}
-
-func GetResponseFromGRPCMessage(m *object.GetResponse) *GetResponse {
-	if m == nil {
-		return nil
-	}
-
-	r := new(GetResponse)
-
-	r.SetBody(
-		GetResponseBodyFromGRPCMessage(m.GetBody()),
-	)
-
-	session.ResponseHeadersFromGRPC(m, r)
-
-	return r
-}
-
-func PutObjectPartInitToGRPCMessage(r *PutObjectPartInit) *object.PutRequest_Body_Init {
-	if r == nil {
-		return nil
-	}
-
-	m := new(object.PutRequest_Body_Init)
-
-	m.SetObjectId(
-		refs.ObjectIDToGRPCMessage(r.GetObjectID()),
-	)
-
-	m.SetSignature(
-		refs.SignatureToGRPCMessage(r.GetSignature()),
-	)
-
-	m.SetHeader(
-		HeaderToGRPCMessage(r.GetHeader()),
-	)
-
-	m.SetCopiesNumber(r.GetCopiesNumber())
-
-	return m
-}
-
-func PutObjectPartInitFromGRPCMessage(m *object.PutRequest_Body_Init) *PutObjectPartInit {
-	if m == nil {
-		return nil
-	}
-
-	r := new(PutObjectPartInit)
-
-	r.SetObjectID(
-		refs.ObjectIDFromGRPCMessage(m.GetObjectId()),
-	)
-
-	r.SetSignature(
-		refs.SignatureFromGRPCMessage(m.GetSignature()),
-	)
-
-	r.SetHeader(
-		HeaderFromGRPCMessage(m.GetHeader()),
-	)
-
-	r.SetCopiesNumber(m.GetCopiesNumber())
-
-	return r
-}
-
-func PutObjectPartChunkToGRPCMessage(r *PutObjectPartChunk) *object.PutRequest_Body_Chunk {
-	if r == nil {
-		return nil
-	}
-
-	m := new(object.PutRequest_Body_Chunk)
-
-	m.SetChunk(r.GetChunk())
-
-	return m
-}
-
-func PutObjectPartChunkFromGRPCMessage(m *object.PutRequest_Body_Chunk) *PutObjectPartChunk {
-	if m == nil {
-		return nil
-	}
-
-	r := new(PutObjectPartChunk)
-
-	r.SetChunk(m.GetChunk())
-
-	return r
-}
-
-func PutRequestBodyToGRPCMessage(r *PutRequestBody) *object.PutRequest_Body {
-	if r == nil {
-		return nil
-	}
-
-	m := new(object.PutRequest_Body)
-
-	switch v := r.GetObjectPart(); t := v.(type) {
-	case nil:
-	case *PutObjectPartInit:
-		m.SetInit(
-			PutObjectPartInitToGRPCMessage(t),
-		)
-	case *PutObjectPartChunk:
-		m.SetChunk(
-			PutObjectPartChunkToGRPCMessage(t),
-		)
-	default:
-		panic(fmt.Sprintf("unknown object part %T", t))
+		m.SetBody(r.body.ToGRPCMessage().(*object.GetResponse_Body))
+		r.ResponseHeaders.ToMessage(m)
 	}
 
 	return m
 }
 
-func PutRequestBodyFromGRPCMessage(m *object.PutRequest_Body) *PutRequestBody {
-	if m == nil {
-		return nil
+func (r *GetResponse) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*object.GetResponse)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
 	}
 
-	r := new(PutRequestBody)
+	var err error
 
-	switch v := m.GetObjectPart().(type) {
+	body := v.GetBody()
+	if body == nil {
+		r.body = nil
+	} else {
+		if r.body == nil {
+			r.body = new(GetResponseBody)
+		}
+
+		err = r.body.FromGRPCMessage(body)
+		if err != nil {
+			return err
+		}
+	}
+
+	return r.ResponseHeaders.FromMessage(v)
+}
+
+func (r *PutObjectPartInit) ToGRPCMessage() grpc.Message {
+	var m *object.PutRequest_Body_Init
+
+	if r != nil {
+		m = new(object.PutRequest_Body_Init)
+
+		m.SetObjectId(r.id.ToGRPCMessage().(*refsGRPC.ObjectID))
+		m.SetSignature(r.sig.ToGRPCMessage().(*refsGRPC.Signature))
+		m.SetHeader(r.hdr.ToGRPCMessage().(*object.Header))
+		m.SetCopiesNumber(r.copyNum)
+	}
+
+	return m
+}
+
+func (r *PutObjectPartInit) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*object.PutRequest_Body_Init)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
+	}
+
+	var err error
+
+	id := v.GetObjectId()
+	if id == nil {
+		r.id = nil
+	} else {
+		if r.id == nil {
+			r.id = new(refs.ObjectID)
+		}
+
+		err = r.id.FromGRPCMessage(id)
+		if err != nil {
+			return err
+		}
+	}
+
+	sig := v.GetSignature()
+	if sig == nil {
+		r.sig = nil
+	} else {
+		if r.sig == nil {
+			r.sig = new(refs.Signature)
+		}
+
+		err = r.sig.FromGRPCMessage(sig)
+		if err != nil {
+			return err
+		}
+	}
+
+	hdr := v.GetHeader()
+	if hdr == nil {
+		r.hdr = nil
+	} else {
+		if r.hdr == nil {
+			r.hdr = new(Header)
+		}
+
+		err = r.hdr.FromGRPCMessage(hdr)
+		if err != nil {
+			return err
+		}
+	}
+
+	r.copyNum = v.GetCopiesNumber()
+
+	return nil
+}
+
+func (r *PutObjectPartChunk) ToGRPCMessage() grpc.Message {
+	var m *object.PutRequest_Body_Chunk
+
+	if r != nil {
+		m = new(object.PutRequest_Body_Chunk)
+
+		m.SetChunk(r.chunk)
+	}
+
+	return m
+}
+
+func (r *PutObjectPartChunk) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*object.PutRequest_Body_Chunk)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
+	}
+
+	r.chunk = v.GetChunk()
+
+	return nil
+}
+
+func (r *PutRequestBody) ToGRPCMessage() grpc.Message {
+	var m *object.PutRequest_Body
+
+	if r != nil {
+		m = new(object.PutRequest_Body)
+
+		switch v := r.GetObjectPart(); t := v.(type) {
+		case nil:
+			m.ObjectPart = nil
+		case *PutObjectPartInit:
+			m.SetInit(t.ToGRPCMessage().(*object.PutRequest_Body_Init))
+		case *PutObjectPartChunk:
+			m.SetChunk(t.ToGRPCMessage().(*object.PutRequest_Body_Chunk))
+		default:
+			panic(fmt.Sprintf("unknown put object part %T", t))
+		}
+	}
+
+	return m
+}
+
+func (r *PutRequestBody) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*object.PutRequest_Body)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
+	}
+
+	var err error
+
+	r.objPart = nil
+
+	switch pt := v.GetObjectPart().(type) {
 	case nil:
 	case *object.PutRequest_Body_Init_:
-		r.SetObjectPart(
-			PutObjectPartInitFromGRPCMessage(v.Init),
-		)
+		if pt != nil {
+			partInit := new(PutObjectPartInit)
+			r.objPart = partInit
+			err = partInit.FromGRPCMessage(pt.Init)
+		}
 	case *object.PutRequest_Body_Chunk:
-		r.SetObjectPart(
-			PutObjectPartChunkFromGRPCMessage(v),
-		)
+		if pt != nil {
+			partChunk := new(PutObjectPartChunk)
+			r.objPart = partChunk
+			err = partChunk.FromGRPCMessage(pt)
+		}
 	default:
-		panic(fmt.Sprintf("unknown object part %T", v))
+		err = errors.Errorf("unknown put object part %T", pt)
 	}
 
-	return r
+	return err
 }
 
-func PutRequestToGRPCMessage(r *PutRequest) *object.PutRequest {
-	if r == nil {
-		return nil
-	}
+func (r *PutRequest) ToGRPCMessage() grpc.Message {
+	var m *object.PutRequest
 
-	m := new(object.PutRequest)
+	if r != nil {
+		m = new(object.PutRequest)
 
-	m.SetBody(
-		PutRequestBodyToGRPCMessage(r.GetBody()),
-	)
-
-	session.RequestHeadersToGRPC(r, m)
-
-	return m
-}
-
-func PutRequestFromGRPCMessage(m *object.PutRequest) *PutRequest {
-	if m == nil {
-		return nil
-	}
-
-	r := new(PutRequest)
-
-	r.SetBody(
-		PutRequestBodyFromGRPCMessage(m.GetBody()),
-	)
-
-	session.RequestHeadersFromGRPC(m, r)
-
-	return r
-}
-
-func PutResponseBodyToGRPCMessage(r *PutResponseBody) *object.PutResponse_Body {
-	if r == nil {
-		return nil
-	}
-
-	m := new(object.PutResponse_Body)
-
-	m.SetObjectId(
-		refs.ObjectIDToGRPCMessage(r.GetObjectID()),
-	)
-
-	return m
-}
-
-func PutResponseBodyFromGRPCMessage(m *object.PutResponse_Body) *PutResponseBody {
-	if m == nil {
-		return nil
-	}
-
-	r := new(PutResponseBody)
-
-	r.SetObjectID(
-		refs.ObjectIDFromGRPCMessage(m.GetObjectId()),
-	)
-
-	return r
-}
-
-func PutResponseToGRPCMessage(r *PutResponse) *object.PutResponse {
-	if r == nil {
-		return nil
-	}
-
-	m := new(object.PutResponse)
-
-	m.SetBody(
-		PutResponseBodyToGRPCMessage(r.GetBody()),
-	)
-
-	session.ResponseHeadersToGRPC(r, m)
-
-	return m
-}
-
-func PutResponseFromGRPCMessage(m *object.PutResponse) *PutResponse {
-	if m == nil {
-		return nil
-	}
-
-	r := new(PutResponse)
-
-	r.SetBody(
-		PutResponseBodyFromGRPCMessage(m.GetBody()),
-	)
-
-	session.ResponseHeadersFromGRPC(m, r)
-
-	return r
-}
-
-func DeleteRequestBodyToGRPCMessage(r *DeleteRequestBody) *object.DeleteRequest_Body {
-	if r == nil {
-		return nil
-	}
-
-	m := new(object.DeleteRequest_Body)
-
-	m.SetAddress(
-		refs.AddressToGRPCMessage(r.GetAddress()),
-	)
-
-	return m
-}
-
-func DeleteRequestBodyFromGRPCMessage(m *object.DeleteRequest_Body) *DeleteRequestBody {
-	if m == nil {
-		return nil
-	}
-
-	r := new(DeleteRequestBody)
-
-	r.SetAddress(
-		refs.AddressFromGRPCMessage(m.GetAddress()),
-	)
-
-	return r
-}
-
-func DeleteRequestToGRPCMessage(r *DeleteRequest) *object.DeleteRequest {
-	if r == nil {
-		return nil
-	}
-
-	m := new(object.DeleteRequest)
-
-	m.SetBody(
-		DeleteRequestBodyToGRPCMessage(r.GetBody()),
-	)
-
-	session.RequestHeadersToGRPC(r, m)
-
-	return m
-}
-
-func DeleteRequestFromGRPCMessage(m *object.DeleteRequest) *DeleteRequest {
-	if m == nil {
-		return nil
-	}
-
-	r := new(DeleteRequest)
-
-	r.SetBody(
-		DeleteRequestBodyFromGRPCMessage(m.GetBody()),
-	)
-
-	session.RequestHeadersFromGRPC(m, r)
-
-	return r
-}
-
-func DeleteResponseBodyToGRPCMessage(r *DeleteResponseBody) *object.DeleteResponse_Body {
-	if r == nil {
-		return nil
-	}
-
-	m := new(object.DeleteResponse_Body)
-	m.SetTombstone(
-		refs.AddressToGRPCMessage(r.GetTombstone()),
-	)
-
-	return m
-}
-
-func DeleteResponseBodyFromGRPCMessage(m *object.DeleteResponse_Body) *DeleteResponseBody {
-	if m == nil {
-		return nil
-	}
-
-	r := new(DeleteResponseBody)
-	r.SetTombstone(
-		refs.AddressFromGRPCMessage(m.GetTombstone()),
-	)
-
-	return r
-}
-
-func DeleteResponseToGRPCMessage(r *DeleteResponse) *object.DeleteResponse {
-	if r == nil {
-		return nil
-	}
-
-	m := new(object.DeleteResponse)
-
-	m.SetBody(
-		DeleteResponseBodyToGRPCMessage(r.GetBody()),
-	)
-
-	session.ResponseHeadersToGRPC(r, m)
-
-	return m
-}
-
-func DeleteResponseFromGRPCMessage(m *object.DeleteResponse) *DeleteResponse {
-	if m == nil {
-		return nil
-	}
-
-	r := new(DeleteResponse)
-
-	r.SetBody(
-		DeleteResponseBodyFromGRPCMessage(m.GetBody()),
-	)
-
-	session.ResponseHeadersFromGRPC(m, r)
-
-	return r
-}
-
-func HeadRequestBodyToGRPCMessage(r *HeadRequestBody) *object.HeadRequest_Body {
-	if r == nil {
-		return nil
-	}
-
-	m := new(object.HeadRequest_Body)
-
-	m.SetAddress(
-		refs.AddressToGRPCMessage(r.GetAddress()),
-	)
-
-	m.SetMainOnly(r.GetMainOnly())
-
-	m.SetRaw(r.GetRaw())
-
-	return m
-}
-
-func HeadRequestBodyFromGRPCMessage(m *object.HeadRequest_Body) *HeadRequestBody {
-	if m == nil {
-		return nil
-	}
-
-	r := new(HeadRequestBody)
-
-	r.SetAddress(
-		refs.AddressFromGRPCMessage(m.GetAddress()),
-	)
-
-	r.SetMainOnly(m.GetMainOnly())
-
-	r.SetRaw(m.GetRaw())
-
-	return r
-}
-
-func HeadRequestToGRPCMessage(r *HeadRequest) *object.HeadRequest {
-	if r == nil {
-		return nil
-	}
-
-	m := new(object.HeadRequest)
-
-	m.SetBody(
-		HeadRequestBodyToGRPCMessage(r.GetBody()),
-	)
-
-	session.RequestHeadersToGRPC(r, m)
-
-	return m
-}
-
-func HeadRequestFromGRPCMessage(m *object.HeadRequest) *HeadRequest {
-	if m == nil {
-		return nil
-	}
-
-	r := new(HeadRequest)
-
-	r.SetBody(
-		HeadRequestBodyFromGRPCMessage(m.GetBody()),
-	)
-
-	session.RequestHeadersFromGRPC(m, r)
-
-	return r
-}
-
-func HeadResponseBodyToGRPCMessage(r *HeadResponseBody) *object.HeadResponse_Body {
-	if r == nil {
-		return nil
-	}
-
-	m := new(object.HeadResponse_Body)
-
-	switch v := r.GetHeaderPart(); t := v.(type) {
-	case nil:
-	case *HeaderWithSignature:
-		m.SetHeader(
-			HeaderWithSignatureToGRPCMessage(t),
-		)
-	case *ShortHeader:
-		m.SetShortHeader(
-			ShortHeaderToGRPCMessage(t),
-		)
-	case *SplitInfo:
-		m.SetSplitInfo(
-			SplitInfoToGRPCMessage(t),
-		)
-	default:
-		panic(fmt.Sprintf("unknown header part %T", t))
+		m.SetBody(r.body.ToGRPCMessage().(*object.PutRequest_Body))
+		r.RequestHeaders.ToMessage(m)
 	}
 
 	return m
 }
 
-func HeadResponseBodyFromGRPCMessage(m *object.HeadResponse_Body) *HeadResponseBody {
-	if m == nil {
-		return nil
+func (r *PutRequest) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*object.PutRequest)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
 	}
 
-	r := new(HeadResponseBody)
+	var err error
 
-	switch v := m.GetHead().(type) {
+	body := v.GetBody()
+	if body == nil {
+		r.body = nil
+	} else {
+		if r.body == nil {
+			r.body = new(PutRequestBody)
+		}
+
+		err = r.body.FromGRPCMessage(body)
+		if err != nil {
+			return err
+		}
+	}
+
+	return r.RequestHeaders.FromMessage(v)
+}
+
+func (r *PutResponseBody) ToGRPCMessage() grpc.Message {
+	var m *object.PutResponse_Body
+
+	if r != nil {
+		m = new(object.PutResponse_Body)
+
+		m.SetObjectId(r.id.ToGRPCMessage().(*refsGRPC.ObjectID))
+	}
+
+	return m
+}
+
+func (r *PutResponseBody) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*object.PutResponse_Body)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
+	}
+
+	var err error
+
+	id := v.GetObjectId()
+	if id == nil {
+		r.id = nil
+	} else {
+		if r.id == nil {
+			r.id = new(refs.ObjectID)
+		}
+
+		err = r.id.FromGRPCMessage(id)
+	}
+
+	return err
+}
+
+func (r *PutResponse) ToGRPCMessage() grpc.Message {
+	var m *object.PutResponse
+
+	if r != nil {
+		m = new(object.PutResponse)
+
+		m.SetBody(r.body.ToGRPCMessage().(*object.PutResponse_Body))
+		r.ResponseHeaders.ToMessage(m)
+	}
+
+	return m
+}
+
+func (r *PutResponse) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*object.PutResponse)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
+	}
+
+	var err error
+
+	body := v.GetBody()
+	if body == nil {
+		r.body = nil
+	} else {
+		if r.body == nil {
+			r.body = new(PutResponseBody)
+		}
+
+		err = r.body.FromGRPCMessage(body)
+		if err != nil {
+			return err
+		}
+	}
+
+	return r.ResponseHeaders.FromMessage(v)
+}
+
+func (r *DeleteRequestBody) ToGRPCMessage() grpc.Message {
+	var m *object.DeleteRequest_Body
+
+	if r != nil {
+		m = new(object.DeleteRequest_Body)
+
+		m.SetAddress(r.addr.ToGRPCMessage().(*refsGRPC.Address))
+	}
+
+	return m
+}
+
+func (r *DeleteRequestBody) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*object.DeleteRequest_Body)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
+	}
+
+	var err error
+
+	addr := v.GetAddress()
+	if addr == nil {
+		r.addr = nil
+	} else {
+		if r.addr == nil {
+			r.addr = new(refs.Address)
+		}
+
+		err = r.addr.FromGRPCMessage(addr)
+	}
+
+	return err
+}
+
+func (r *DeleteRequest) ToGRPCMessage() grpc.Message {
+	var m *object.DeleteRequest
+
+	if r != nil {
+		m = new(object.DeleteRequest)
+
+		m.SetBody(r.body.ToGRPCMessage().(*object.DeleteRequest_Body))
+		r.RequestHeaders.ToMessage(m)
+	}
+
+	return m
+}
+
+func (r *DeleteRequest) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*object.DeleteRequest)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
+	}
+
+	var err error
+
+	body := v.GetBody()
+	if body == nil {
+		r.body = nil
+	} else {
+		if r.body == nil {
+			r.body = new(DeleteRequestBody)
+		}
+
+		err = r.body.FromGRPCMessage(body)
+		if err != nil {
+			return err
+		}
+	}
+
+	return r.RequestHeaders.FromMessage(v)
+}
+
+func (r *DeleteResponseBody) ToGRPCMessage() grpc.Message {
+	var m *object.DeleteResponse_Body
+
+	if r != nil {
+		m = new(object.DeleteResponse_Body)
+
+		m.SetTombstone(r.tombstone.ToGRPCMessage().(*refsGRPC.Address))
+	}
+
+	return m
+}
+
+func (r *DeleteResponseBody) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*object.DeleteResponse_Body)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
+	}
+
+	var err error
+
+	tombstone := v.GetTombstone()
+	if tombstone == nil {
+		r.tombstone = nil
+	} else {
+		if r.tombstone == nil {
+			r.tombstone = new(refs.Address)
+		}
+
+		err = r.tombstone.FromGRPCMessage(tombstone)
+	}
+
+	return err
+}
+
+func (r *DeleteResponse) ToGRPCMessage() grpc.Message {
+	var m *object.DeleteResponse
+
+	if r != nil {
+		m = new(object.DeleteResponse)
+
+		m.SetBody(r.body.ToGRPCMessage().(*object.DeleteResponse_Body))
+		r.ResponseHeaders.ToMessage(m)
+	}
+
+	return m
+}
+
+func (r *DeleteResponse) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*object.DeleteResponse)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
+	}
+
+	var err error
+
+	body := v.GetBody()
+	if body == nil {
+		r.body = nil
+	} else {
+		if r.body == nil {
+			r.body = new(DeleteResponseBody)
+		}
+
+		err = r.body.FromGRPCMessage(body)
+		if err != nil {
+			return err
+		}
+	}
+
+	return r.ResponseHeaders.FromMessage(v)
+}
+
+func (r *HeadRequestBody) ToGRPCMessage() grpc.Message {
+	var m *object.HeadRequest_Body
+
+	if r != nil {
+		m = new(object.HeadRequest_Body)
+
+		m.SetAddress(r.addr.ToGRPCMessage().(*refsGRPC.Address))
+		m.SetRaw(r.raw)
+		m.SetMainOnly(r.mainOnly)
+	}
+
+	return m
+}
+
+func (r *HeadRequestBody) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*object.HeadRequest_Body)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
+	}
+
+	var err error
+
+	addr := v.GetAddress()
+	if addr == nil {
+		r.addr = nil
+	} else {
+		if r.addr == nil {
+			r.addr = new(refs.Address)
+		}
+
+		err = r.addr.FromGRPCMessage(addr)
+		if err != nil {
+			return err
+		}
+	}
+
+	r.raw = v.GetRaw()
+	r.mainOnly = v.GetMainOnly()
+
+	return nil
+}
+
+func (r *HeadRequest) ToGRPCMessage() grpc.Message {
+	var m *object.HeadRequest
+
+	if r != nil {
+		m = new(object.HeadRequest)
+
+		m.SetBody(r.body.ToGRPCMessage().(*object.HeadRequest_Body))
+		r.RequestHeaders.ToMessage(m)
+	}
+
+	return m
+}
+
+func (r *HeadRequest) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*object.HeadRequest)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
+	}
+
+	var err error
+
+	body := v.GetBody()
+	if body == nil {
+		r.body = nil
+	} else {
+		if r.body == nil {
+			r.body = new(HeadRequestBody)
+		}
+
+		err = r.body.FromGRPCMessage(body)
+		if err != nil {
+			return err
+		}
+	}
+
+	return r.RequestHeaders.FromMessage(v)
+}
+
+func (r *HeadResponseBody) ToGRPCMessage() grpc.Message {
+	var m *object.HeadResponse_Body
+
+	if r != nil {
+		m = new(object.HeadResponse_Body)
+
+		switch v := r.hdrPart.(type) {
+		case nil:
+			m.Head = nil
+		case *HeaderWithSignature:
+			m.SetHeader(v.ToGRPCMessage().(*object.HeaderWithSignature))
+		case *ShortHeader:
+			m.SetShortHeader(v.ToGRPCMessage().(*object.ShortHeader))
+		case *SplitInfo:
+			m.SetSplitInfo(v.ToGRPCMessage().(*object.SplitInfo))
+		default:
+			panic(fmt.Sprintf("unknown head part %T", v))
+		}
+	}
+
+	return m
+}
+
+func (r *HeadResponseBody) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*object.HeadResponse_Body)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
+	}
+
+	var err error
+
+	r.hdrPart = nil
+
+	switch pt := v.GetHead().(type) {
 	case nil:
 	case *object.HeadResponse_Body_Header:
-		r.SetHeaderPart(
-			HeaderWithSignatureFromGRPCMessage(v.Header),
-		)
+		if pt != nil {
+			partHdr := new(HeaderWithSignature)
+			r.hdrPart = partHdr
+			err = partHdr.FromGRPCMessage(pt.Header)
+		}
 	case *object.HeadResponse_Body_ShortHeader:
-		r.SetHeaderPart(
-			ShortHeaderFromGRPCMessage(v.ShortHeader),
-		)
+		if pt != nil {
+			partShort := new(ShortHeader)
+			r.hdrPart = partShort
+			err = partShort.FromGRPCMessage(pt.ShortHeader)
+		}
 	case *object.HeadResponse_Body_SplitInfo:
-		r.SetHeaderPart(
-			SplitInfoFromGRPCMessage(v.SplitInfo),
-		)
+		if pt != nil {
+			partSplit := new(SplitInfo)
+			r.hdrPart = partSplit
+			err = partSplit.FromGRPCMessage(pt.SplitInfo)
+		}
 	default:
-		panic(fmt.Sprintf("unknown header part %T", v))
+		err = errors.Errorf("unknown head part %T", pt)
 	}
 
-	return r
+	return err
 }
 
-func HeadResponseToGRPCMessage(r *HeadResponse) *object.HeadResponse {
-	if r == nil {
-		return nil
-	}
+func (r *HeadResponse) ToGRPCMessage() grpc.Message {
+	var m *object.HeadResponse
 
-	m := new(object.HeadResponse)
+	if r != nil {
+		m = new(object.HeadResponse)
 
-	m.SetBody(
-		HeadResponseBodyToGRPCMessage(r.GetBody()),
-	)
-
-	session.ResponseHeadersToGRPC(r, m)
-
-	return m
-}
-
-func HeadResponseFromGRPCMessage(m *object.HeadResponse) *HeadResponse {
-	if m == nil {
-		return nil
-	}
-
-	r := new(HeadResponse)
-
-	r.SetBody(
-		HeadResponseBodyFromGRPCMessage(m.GetBody()),
-	)
-
-	session.ResponseHeadersFromGRPC(m, r)
-
-	return r
-}
-
-func SearchFilterToGRPCMessage(f *SearchFilter) *object.SearchRequest_Body_Filter {
-	if f == nil {
-		return nil
-	}
-
-	m := new(object.SearchRequest_Body_Filter)
-
-	m.SetMatchType(
-		MatchTypeToGRPCField(f.GetMatchType()),
-	)
-
-	m.SetKey(f.GetKey())
-
-	m.SetValue(f.GetValue())
-
-	return m
-}
-
-func SearchFilterFromGRPCMessage(m *object.SearchRequest_Body_Filter) *SearchFilter {
-	if m == nil {
-		return nil
-	}
-
-	f := new(SearchFilter)
-
-	f.SetMatchType(
-		MatchTypeFromGRPCField(m.GetMatchType()),
-	)
-
-	f.SetKey(m.GetKey())
-
-	f.SetValue(m.GetValue())
-
-	return f
-}
-
-func SearchRequestBodyToGRPCMessage(r *SearchRequestBody) *object.SearchRequest_Body {
-	if r == nil {
-		return nil
-	}
-
-	m := new(object.SearchRequest_Body)
-
-	m.SetContainerId(
-		refs.ContainerIDToGRPCMessage(r.GetContainerID()),
-	)
-
-	m.SetVersion(r.GetVersion())
-
-	filters := r.GetFilters()
-	filterMsg := make([]*object.SearchRequest_Body_Filter, 0, len(filters))
-
-	for i := range filters {
-		filterMsg = append(filterMsg, SearchFilterToGRPCMessage(filters[i]))
-	}
-
-	m.SetFilters(filterMsg)
-
-	return m
-}
-
-func SearchRequestBodyFromGRPCMessage(m *object.SearchRequest_Body) *SearchRequestBody {
-	if m == nil {
-		return nil
-	}
-
-	r := new(SearchRequestBody)
-
-	r.SetContainerID(
-		refs.ContainerIDFromGRPCMessage(m.GetContainerId()),
-	)
-
-	r.SetVersion(m.GetVersion())
-
-	filterMsg := m.GetFilters()
-	filters := make([]*SearchFilter, 0, len(filterMsg))
-
-	for i := range filterMsg {
-		filters = append(filters, SearchFilterFromGRPCMessage(filterMsg[i]))
-	}
-
-	r.SetFilters(filters)
-
-	return r
-}
-
-func SearchRequestToGRPCMessage(r *SearchRequest) *object.SearchRequest {
-	if r == nil {
-		return nil
-	}
-
-	m := new(object.SearchRequest)
-
-	m.SetBody(
-		SearchRequestBodyToGRPCMessage(r.GetBody()),
-	)
-
-	session.RequestHeadersToGRPC(r, m)
-
-	return m
-}
-
-func SearchRequestFromGRPCMessage(m *object.SearchRequest) *SearchRequest {
-	if m == nil {
-		return nil
-	}
-
-	r := new(SearchRequest)
-
-	r.SetBody(
-		SearchRequestBodyFromGRPCMessage(m.GetBody()),
-	)
-
-	session.RequestHeadersFromGRPC(m, r)
-
-	return r
-}
-
-func SearchResponseBodyToGRPCMessage(r *SearchResponseBody) *object.SearchResponse_Body {
-	if r == nil {
-		return nil
-	}
-
-	m := new(object.SearchResponse_Body)
-
-	m.SetIdList(
-		refs.ObjectIDListToGRPCMessage(r.GetIDList()),
-	)
-
-	return m
-}
-
-func SearchResponseBodyFromGRPCMessage(m *object.SearchResponse_Body) *SearchResponseBody {
-	if m == nil {
-		return nil
-	}
-
-	r := new(SearchResponseBody)
-
-	r.SetIDList(
-		refs.ObjectIDListFromGRPCMessage(m.GetIdList()),
-	)
-
-	return r
-}
-
-func SearchResponseToGRPCMessage(r *SearchResponse) *object.SearchResponse {
-	if r == nil {
-		return nil
-	}
-
-	m := new(object.SearchResponse)
-
-	m.SetBody(
-		SearchResponseBodyToGRPCMessage(r.GetBody()),
-	)
-
-	session.ResponseHeadersToGRPC(r, m)
-
-	return m
-}
-
-func SearchResponseFromGRPCMessage(m *object.SearchResponse) *SearchResponse {
-	if m == nil {
-		return nil
-	}
-
-	r := new(SearchResponse)
-
-	r.SetBody(
-		SearchResponseBodyFromGRPCMessage(m.GetBody()),
-	)
-
-	session.ResponseHeadersFromGRPC(m, r)
-
-	return r
-}
-
-func RangeToGRPCMessage(r *Range) *object.Range {
-	if r == nil {
-		return nil
-	}
-
-	m := new(object.Range)
-
-	m.SetOffset(r.GetOffset())
-	m.SetLength(r.GetLength())
-
-	return m
-}
-
-func RangeFromGRPCMessage(m *object.Range) *Range {
-	if m == nil {
-		return nil
-	}
-
-	r := new(Range)
-
-	r.SetOffset(m.GetOffset())
-	r.SetLength(m.GetLength())
-
-	return r
-}
-
-func GetRangeRequestBodyToGRPCMessage(r *GetRangeRequestBody) *object.GetRangeRequest_Body {
-	if r == nil {
-		return nil
-	}
-
-	m := new(object.GetRangeRequest_Body)
-
-	m.SetAddress(
-		refs.AddressToGRPCMessage(r.GetAddress()),
-	)
-
-	m.SetRange(
-		RangeToGRPCMessage(r.GetRange()),
-	)
-
-	m.SetRaw(r.GetRaw())
-
-	return m
-}
-
-func GetRangeRequestBodyFromGRPCMessage(m *object.GetRangeRequest_Body) *GetRangeRequestBody {
-	if m == nil {
-		return nil
-	}
-
-	r := new(GetRangeRequestBody)
-
-	r.SetAddress(
-		refs.AddressFromGRPCMessage(m.GetAddress()),
-	)
-
-	r.SetRange(
-		RangeFromGRPCMessage(m.GetRange()),
-	)
-
-	r.SetRaw(m.GetRaw())
-
-	return r
-}
-
-func GetRangeRequestToGRPCMessage(r *GetRangeRequest) *object.GetRangeRequest {
-	if r == nil {
-		return nil
-	}
-
-	m := new(object.GetRangeRequest)
-
-	m.SetBody(
-		GetRangeRequestBodyToGRPCMessage(r.GetBody()),
-	)
-
-	session.RequestHeadersToGRPC(r, m)
-
-	return m
-}
-
-func GetRangeRequestFromGRPCMessage(m *object.GetRangeRequest) *GetRangeRequest {
-	if m == nil {
-		return nil
-	}
-
-	r := new(GetRangeRequest)
-
-	r.SetBody(
-		GetRangeRequestBodyFromGRPCMessage(m.GetBody()),
-	)
-
-	session.RequestHeadersFromGRPC(m, r)
-
-	return r
-}
-
-func GetRangePartChunkToGRPCMessage(r *GetRangePartChunk) *object.GetRangeResponse_Body_Chunk {
-	if r == nil {
-		return nil
-	}
-
-	m := new(object.GetRangeResponse_Body_Chunk)
-
-	m.SetChunk(r.GetChunk())
-
-	return m
-}
-
-func GetRangePartChunkFromGRPCMessage(m *object.GetRangeResponse_Body_Chunk) *GetRangePartChunk {
-	if m == nil {
-		return nil
-	}
-
-	r := new(GetRangePartChunk)
-
-	r.SetChunk(m.GetChunk())
-
-	return r
-}
-
-func GetRangeResponseBodyToGRPCMessage(r *GetRangeResponseBody) *object.GetRangeResponse_Body {
-	if r == nil {
-		return nil
-	}
-
-	m := new(object.GetRangeResponse_Body)
-
-	switch v := r.GetRangePart(); t := v.(type) {
-	case nil:
-	case *GetRangePartChunk:
-		m.SetChunk(
-			GetRangePartChunkToGRPCMessage(t),
-		)
-	case *SplitInfo:
-		m.SetSplitInfo(
-			SplitInfoToGRPCMessage(t),
-		)
-	default:
-		panic(fmt.Sprintf("unknown get range part %T", t))
+		m.SetBody(r.body.ToGRPCMessage().(*object.HeadResponse_Body))
+		r.ResponseHeaders.ToMessage(m)
 	}
 
 	return m
 }
 
-func GetRangeResponseBodyFromGRPCMessage(m *object.GetRangeResponse_Body) *GetRangeResponseBody {
-	if m == nil {
-		return nil
+func (r *HeadResponse) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*object.HeadResponse)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
 	}
 
-	r := new(GetRangeResponseBody)
+	var err error
 
-	switch v := m.GetRangePart().(type) {
+	body := v.GetBody()
+	if body == nil {
+		r.body = nil
+	} else {
+		if r.body == nil {
+			r.body = new(HeadResponseBody)
+		}
+
+		err = r.body.FromGRPCMessage(body)
+		if err != nil {
+			return err
+		}
+	}
+
+	return r.ResponseHeaders.FromMessage(v)
+}
+
+func (f *SearchFilter) ToGRPCMessage() grpc.Message {
+	var m *object.SearchRequest_Body_Filter
+
+	if f != nil {
+		m = new(object.SearchRequest_Body_Filter)
+
+		m.SetKey(f.key)
+		m.SetValue(f.val)
+		m.SetMatchType(MatchTypeToGRPCField(f.matchType))
+	}
+
+	return m
+}
+
+func (f *SearchFilter) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*object.SearchRequest_Body_Filter)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
+	}
+
+	f.key = v.GetKey()
+	f.val = v.GetValue()
+	f.matchType = MatchTypeFromGRPCField(v.GetMatchType())
+
+	return nil
+}
+
+func SearchFiltersToGRPC(fs []*SearchFilter) (res []*object.SearchRequest_Body_Filter) {
+	if fs != nil {
+		res = make([]*object.SearchRequest_Body_Filter, 0, len(fs))
+
+		for i := range fs {
+			res = append(res, fs[i].ToGRPCMessage().(*object.SearchRequest_Body_Filter))
+		}
+	}
+
+	return
+}
+
+func SearchFiltersFromGRPC(fs []*object.SearchRequest_Body_Filter) (res []*SearchFilter, err error) {
+	if fs != nil {
+		res = make([]*SearchFilter, 0, len(fs))
+
+		for i := range fs {
+			var x *SearchFilter
+
+			if fs[i] != nil {
+				x = new(SearchFilter)
+
+				err = x.FromGRPCMessage(fs[i])
+				if err != nil {
+					return
+				}
+			}
+
+			res = append(res, x)
+		}
+	}
+
+	return
+}
+
+func (r *SearchRequestBody) ToGRPCMessage() grpc.Message {
+	var m *object.SearchRequest_Body
+
+	if r != nil {
+		m = new(object.SearchRequest_Body)
+
+		m.SetContainerId(r.cid.ToGRPCMessage().(*refsGRPC.ContainerID))
+		m.SetFilters(SearchFiltersToGRPC(r.filters))
+		m.SetVersion(r.version)
+	}
+
+	return m
+}
+
+func (r *SearchRequestBody) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*object.SearchRequest_Body)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
+	}
+
+	var err error
+
+	cid := v.GetContainerId()
+	if cid == nil {
+		r.cid = nil
+	} else {
+		if r.cid == nil {
+			r.cid = new(refs.ContainerID)
+		}
+
+		err = r.cid.FromGRPCMessage(cid)
+		if err != nil {
+			return err
+		}
+	}
+
+	r.filters, err = SearchFiltersFromGRPC(v.GetFilters())
+	if err != nil {
+		return err
+	}
+
+	r.version = v.GetVersion()
+
+	return nil
+}
+
+func (r *SearchRequest) ToGRPCMessage() grpc.Message {
+	var m *object.SearchRequest
+
+	if r != nil {
+		m = new(object.SearchRequest)
+
+		m.SetBody(r.body.ToGRPCMessage().(*object.SearchRequest_Body))
+		r.RequestHeaders.ToMessage(m)
+	}
+
+	return m
+}
+
+func (r *SearchRequest) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*object.SearchRequest)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
+	}
+
+	var err error
+
+	body := v.GetBody()
+	if body == nil {
+		r.body = nil
+	} else {
+		if r.body == nil {
+			r.body = new(SearchRequestBody)
+		}
+
+		err = r.body.FromGRPCMessage(body)
+		if err != nil {
+			return err
+		}
+	}
+
+	return r.RequestHeaders.FromMessage(v)
+}
+
+func (r *SearchResponseBody) ToGRPCMessage() grpc.Message {
+	var m *object.SearchResponse_Body
+
+	if r != nil {
+		m = new(object.SearchResponse_Body)
+
+		m.SetIdList(refs.ObjectIDListToGRPCMessage(r.idList))
+	}
+
+	return m
+}
+
+func (r *SearchResponseBody) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*object.SearchResponse_Body)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
+	}
+
+	var err error
+
+	r.idList, err = refs.ObjectIDListFromGRPCMessage(v.GetIdList())
+
+	return err
+}
+
+func (r *SearchResponse) ToGRPCMessage() grpc.Message {
+	var m *object.SearchResponse
+
+	if r != nil {
+		m = new(object.SearchResponse)
+
+		m.SetBody(r.body.ToGRPCMessage().(*object.SearchResponse_Body))
+		r.ResponseHeaders.ToMessage(m)
+	}
+
+	return m
+}
+
+func (r *SearchResponse) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*object.SearchResponse)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
+	}
+
+	var err error
+
+	body := v.GetBody()
+	if body == nil {
+		r.body = nil
+	} else {
+		if r.body == nil {
+			r.body = new(SearchResponseBody)
+		}
+
+		err = r.body.FromGRPCMessage(body)
+		if err != nil {
+			return err
+		}
+	}
+
+	return r.ResponseHeaders.FromMessage(v)
+}
+
+func (r *Range) ToGRPCMessage() grpc.Message {
+	var m *object.Range
+
+	if r != nil {
+		m = new(object.Range)
+
+		m.SetLength(r.len)
+		m.SetOffset(r.off)
+	}
+
+	return m
+}
+
+func (r *Range) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*object.Range)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
+	}
+
+	r.len = v.GetLength()
+	r.off = v.GetOffset()
+
+	return nil
+}
+
+func RangesToGRPC(rs []*Range) (res []*object.Range) {
+	if rs != nil {
+		res = make([]*object.Range, 0, len(rs))
+
+		for i := range rs {
+			res = append(res, rs[i].ToGRPCMessage().(*object.Range))
+		}
+	}
+
+	return
+}
+
+func RangesFromGRPC(rs []*object.Range) (res []*Range, err error) {
+	if rs != nil {
+		res = make([]*Range, 0, len(rs))
+
+		for i := range rs {
+			var r *Range
+
+			if rs[i] != nil {
+				r = new(Range)
+
+				err = r.FromGRPCMessage(rs[i])
+				if err != nil {
+					return
+				}
+			}
+
+			res = append(res, r)
+		}
+	}
+
+	return
+}
+
+func (r *GetRangeRequestBody) ToGRPCMessage() grpc.Message {
+	var m *object.GetRangeRequest_Body
+
+	if r != nil {
+		m = new(object.GetRangeRequest_Body)
+
+		m.SetAddress(r.addr.ToGRPCMessage().(*refsGRPC.Address))
+		m.SetRange(r.rng.ToGRPCMessage().(*object.Range))
+		m.SetRaw(r.raw)
+	}
+
+	return m
+}
+
+func (r *GetRangeRequestBody) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*object.GetRangeRequest_Body)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
+	}
+
+	var err error
+
+	addr := v.GetAddress()
+	if addr == nil {
+		r.addr = nil
+	} else {
+		if r.addr == nil {
+			r.addr = new(refs.Address)
+		}
+
+		err = r.addr.FromGRPCMessage(addr)
+		if err != nil {
+			return err
+		}
+	}
+
+	rng := v.GetRange()
+	if rng == nil {
+		r.rng = nil
+	} else {
+		if r.rng == nil {
+			r.rng = new(Range)
+		}
+
+		err = r.rng.FromGRPCMessage(rng)
+		if err != nil {
+			return err
+		}
+	}
+
+	r.raw = v.GetRaw()
+
+	return nil
+}
+
+func (r *GetRangeRequest) ToGRPCMessage() grpc.Message {
+	var m *object.GetRangeRequest
+
+	if r != nil {
+		m = new(object.GetRangeRequest)
+
+		m.SetBody(r.body.ToGRPCMessage().(*object.GetRangeRequest_Body))
+		r.RequestHeaders.ToMessage(m)
+	}
+
+	return m
+}
+
+func (r *GetRangeRequest) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*object.GetRangeRequest)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
+	}
+
+	var err error
+
+	body := v.GetBody()
+	if body == nil {
+		r.body = nil
+	} else {
+		if r.body == nil {
+			r.body = new(GetRangeRequestBody)
+		}
+
+		err = r.body.FromGRPCMessage(body)
+		if err != nil {
+			return err
+		}
+	}
+
+	return r.RequestHeaders.FromMessage(v)
+}
+
+func (r *GetRangePartChunk) ToGRPCMessage() grpc.Message {
+	var m *object.GetRangeResponse_Body_Chunk
+
+	if r != nil {
+		m = new(object.GetRangeResponse_Body_Chunk)
+
+		m.SetChunk(r.chunk)
+	}
+
+	return m
+}
+
+func (r *GetRangePartChunk) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*object.GetRangeResponse_Body_Chunk)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
+	}
+
+	r.chunk = v.GetChunk()
+
+	return nil
+}
+
+func (r *GetRangeResponseBody) ToGRPCMessage() grpc.Message {
+	var m *object.GetRangeResponse_Body
+
+	if r != nil {
+		m = new(object.GetRangeResponse_Body)
+
+		switch v := r.rngPart.(type) {
+		case nil:
+			m.RangePart = nil
+		case *GetRangePartChunk:
+			m.SetChunk(v.ToGRPCMessage().(*object.GetRangeResponse_Body_Chunk))
+		case *SplitInfo:
+			m.SetSplitInfo(v.ToGRPCMessage().(*object.SplitInfo))
+		default:
+			panic(fmt.Sprintf("unknown get range part %T", v))
+		}
+	}
+
+	return m
+}
+
+func (r *GetRangeResponseBody) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*object.GetRangeResponse_Body)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
+	}
+
+	var err error
+
+	r.rngPart = nil
+
+	switch pt := v.GetRangePart().(type) {
 	case nil:
 	case *object.GetRangeResponse_Body_Chunk:
-		r.SetRangePart(
-			GetRangePartChunkFromGRPCMessage(v),
-		)
+		if pt != nil {
+			partChunk := new(GetRangePartChunk)
+			r.rngPart = partChunk
+			err = partChunk.FromGRPCMessage(pt)
+		}
 	case *object.GetRangeResponse_Body_SplitInfo:
-		r.SetRangePart(
-			SplitInfoFromGRPCMessage(v.SplitInfo),
-		)
+		if pt != nil {
+			partSplit := new(SplitInfo)
+			r.rngPart = partSplit
+			err = partSplit.FromGRPCMessage(pt)
+		}
 	default:
-		panic(fmt.Sprintf("unknown get range part %T", v))
+		err = errors.Errorf("unknown get range part %T", pt)
 	}
 
-	return r
+	return err
 }
 
-func GetRangeResponseToGRPCMessage(r *GetRangeResponse) *object.GetRangeResponse {
-	if r == nil {
-		return nil
+func (r *GetRangeResponse) ToGRPCMessage() grpc.Message {
+	var m *object.GetRangeResponse
+
+	if r != nil {
+		m = new(object.GetRangeResponse)
+
+		m.SetBody(r.body.ToGRPCMessage().(*object.GetRangeResponse_Body))
+		r.ResponseHeaders.ToMessage(m)
 	}
 
-	m := new(object.GetRangeResponse)
-
-	m.SetBody(
-		GetRangeResponseBodyToGRPCMessage(r.GetBody()),
-	)
-
-	session.ResponseHeadersToGRPC(r, m)
-
 	return m
 }
 
-func GetRangeResponseFromGRPCMessage(m *object.GetRangeResponse) *GetRangeResponse {
-	if m == nil {
-		return nil
+func (r *GetRangeResponse) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*object.GetRangeResponse)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
 	}
 
-	r := new(GetRangeResponse)
+	var err error
 
-	r.SetBody(
-		GetRangeResponseBodyFromGRPCMessage(m.GetBody()),
-	)
+	body := v.GetBody()
+	if body == nil {
+		r.body = nil
+	} else {
+		if r.body == nil {
+			r.body = new(GetRangeResponseBody)
+		}
 
-	session.ResponseHeadersFromGRPC(m, r)
+		err = r.body.FromGRPCMessage(body)
+		if err != nil {
+			return err
+		}
+	}
 
-	return r
+	return r.ResponseHeaders.FromMessage(v)
 }
 
-func GetRangeHashRequestBodyToGRPCMessage(r *GetRangeHashRequestBody) *object.GetRangeHashRequest_Body {
-	if r == nil {
-		return nil
+func (r *GetRangeHashRequestBody) ToGRPCMessage() grpc.Message {
+	var m *object.GetRangeHashRequest_Body
+
+	if r != nil {
+		m = new(object.GetRangeHashRequest_Body)
+
+		m.SetAddress(r.addr.ToGRPCMessage().(*refsGRPC.Address))
+		m.SetRanges(RangesToGRPC(r.rngs))
+		m.SetType(refs.ChecksumTypeToGRPC(r.typ))
+		m.SetSalt(r.salt)
 	}
 
-	m := new(object.GetRangeHashRequest_Body)
-
-	m.SetAddress(
-		refs.AddressToGRPCMessage(r.GetAddress()),
-	)
-
-	m.SetSalt(r.GetSalt())
-
-	rngs := r.GetRanges()
-	rngMsg := make([]*object.Range, 0, len(rngs))
-
-	for i := range rngs {
-		rngMsg = append(rngMsg, RangeToGRPCMessage(rngs[i]))
-	}
-
-	m.SetRanges(rngMsg)
-
-	m.SetType(refsGRPC.ChecksumType(r.GetType()))
-
 	return m
 }
 
-func GetRangeHashRequestBodyFromGRPCMessage(m *object.GetRangeHashRequest_Body) *GetRangeHashRequestBody {
-	if m == nil {
-		return nil
+func (r *GetRangeHashRequestBody) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*object.GetRangeHashRequest_Body)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
 	}
 
-	r := new(GetRangeHashRequestBody)
+	var err error
 
-	r.SetAddress(
-		refs.AddressFromGRPCMessage(m.GetAddress()),
-	)
+	addr := v.GetAddress()
+	if addr == nil {
+		r.addr = nil
+	} else {
+		if r.addr == nil {
+			r.addr = new(refs.Address)
+		}
 
-	r.SetSalt(m.GetSalt())
-
-	rngMsg := m.GetRanges()
-	rngs := make([]*Range, 0, len(rngMsg))
-
-	for i := range rngMsg {
-		rngs = append(rngs, RangeFromGRPCMessage(rngMsg[i]))
+		err = r.addr.FromGRPCMessage(addr)
+		if err != nil {
+			return err
+		}
 	}
 
-	r.SetRanges(rngs)
+	r.rngs, err = RangesFromGRPC(v.GetRanges())
+	if err != nil {
+		return err
+	}
 
-	r.SetType(refs.ChecksumType(m.GetType()))
+	r.typ = refs.ChecksumTypeFromGRPC(v.GetType())
+	r.salt = v.GetSalt()
 
-	return r
+	return nil
 }
 
-func GetRangeHashRequestToGRPCMessage(r *GetRangeHashRequest) *object.GetRangeHashRequest {
-	if r == nil {
-		return nil
+func (r *GetRangeHashRequest) ToGRPCMessage() grpc.Message {
+	var m *object.GetRangeHashRequest
+
+	if r != nil {
+		m = new(object.GetRangeHashRequest)
+
+		m.SetBody(r.body.ToGRPCMessage().(*object.GetRangeHashRequest_Body))
+		r.RequestHeaders.ToMessage(m)
 	}
 
-	m := new(object.GetRangeHashRequest)
-
-	m.SetBody(
-		GetRangeHashRequestBodyToGRPCMessage(r.GetBody()),
-	)
-
-	session.RequestHeadersToGRPC(r, m)
-
 	return m
 }
 
-func GetRangeHashRequestFromGRPCMessage(m *object.GetRangeHashRequest) *GetRangeHashRequest {
-	if m == nil {
-		return nil
+func (r *GetRangeHashRequest) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*object.GetRangeHashRequest)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
 	}
 
-	r := new(GetRangeHashRequest)
+	var err error
 
-	r.SetBody(
-		GetRangeHashRequestBodyFromGRPCMessage(m.GetBody()),
-	)
+	body := v.GetBody()
+	if body == nil {
+		r.body = nil
+	} else {
+		if r.body == nil {
+			r.body = new(GetRangeHashRequestBody)
+		}
 
-	session.RequestHeadersFromGRPC(m, r)
+		err = r.body.FromGRPCMessage(body)
+		if err != nil {
+			return err
+		}
+	}
 
-	return r
+	return r.RequestHeaders.FromMessage(v)
 }
 
-func GetRangeHashResponseBodyToGRPCMessage(r *GetRangeHashResponseBody) *object.GetRangeHashResponse_Body {
-	if r == nil {
-		return nil
+func (r *GetRangeHashResponseBody) ToGRPCMessage() grpc.Message {
+	var m *object.GetRangeHashResponse_Body
+
+	if r != nil {
+		m = new(object.GetRangeHashResponse_Body)
+
+		m.SetType(refs.ChecksumTypeToGRPC(r.typ))
+		m.SetHashList(r.hashList)
 	}
 
-	m := new(object.GetRangeHashResponse_Body)
-
-	m.SetType(refsGRPC.ChecksumType(r.GetType()))
-
-	m.SetHashList(r.GetHashList())
-
 	return m
 }
 
-func GetRangeHashResponseBodyFromGRPCMessage(m *object.GetRangeHashResponse_Body) *GetRangeHashResponseBody {
-	if m == nil {
-		return nil
+func (r *GetRangeHashResponseBody) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*object.GetRangeHashResponse_Body)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
 	}
 
-	r := new(GetRangeHashResponseBody)
+	r.typ = refs.ChecksumTypeFromGRPC(v.GetType())
+	r.hashList = v.GetHashList()
 
-	r.SetType(refs.ChecksumType(m.GetType()))
-
-	r.SetHashList(m.GetHashList())
-
-	return r
+	return nil
 }
 
-func GetRangeHashResponseToGRPCMessage(r *GetRangeHashResponse) *object.GetRangeHashResponse {
-	if r == nil {
-		return nil
+func (r *GetRangeHashResponse) ToGRPCMessage() grpc.Message {
+	var m *object.GetRangeHashResponse
+
+	if r != nil {
+		m = new(object.GetRangeHashResponse)
+
+		m.SetBody(r.body.ToGRPCMessage().(*object.GetRangeHashResponse_Body))
+		r.ResponseHeaders.ToMessage(m)
 	}
 
-	m := new(object.GetRangeHashResponse)
-
-	m.SetBody(
-		GetRangeHashResponseBodyToGRPCMessage(r.GetBody()),
-	)
-
-	session.ResponseHeadersToGRPC(r, m)
-
 	return m
 }
 
-func GetRangeHashResponseFromGRPCMessage(m *object.GetRangeHashResponse) *GetRangeHashResponse {
-	if m == nil {
-		return nil
+func (r *GetRangeHashResponse) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*object.GetRangeHashResponse)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
 	}
 
-	r := new(GetRangeHashResponse)
+	var err error
 
-	r.SetBody(
-		GetRangeHashResponseBodyFromGRPCMessage(m.GetBody()),
-	)
+	body := v.GetBody()
+	if body == nil {
+		r.body = nil
+	} else {
+		if r.body == nil {
+			r.body = new(GetRangeHashResponseBody)
+		}
 
-	session.ResponseHeadersFromGRPC(m, r)
+		err = r.body.FromGRPCMessage(body)
+		if err != nil {
+			return err
+		}
+	}
 
-	return r
+	return r.ResponseHeaders.FromMessage(v)
 }
diff --git a/v2/object/json.go b/v2/object/json.go
index e2caede..b80253e 100644
--- a/v2/object/json.go
+++ b/v2/object/json.go
@@ -1,146 +1,78 @@
 package object
 
 import (
+	"github.com/nspcc-dev/neofs-api-go/rpc/message"
 	object "github.com/nspcc-dev/neofs-api-go/v2/object/grpc"
-	"google.golang.org/protobuf/encoding/protojson"
 )
 
 func (h *ShortHeader) MarshalJSON() ([]byte, error) {
-	return protojson.MarshalOptions{
-		EmitUnpopulated: true,
-	}.Marshal(
-		ShortHeaderToGRPCMessage(h),
-	)
+	return message.MarshalJSON(h)
 }
 
 func (h *ShortHeader) UnmarshalJSON(data []byte) error {
-	msg := new(object.ShortHeader)
-
-	if err := protojson.Unmarshal(data, msg); err != nil {
-		return err
-	}
-
-	*h = *ShortHeaderFromGRPCMessage(msg)
-
-	return nil
+	return message.UnmarshalJSON(h, data, new(object.ShortHeader))
 }
 
 func (a *Attribute) MarshalJSON() ([]byte, error) {
-	return protojson.MarshalOptions{
-		EmitUnpopulated: true,
-	}.Marshal(
-		AttributeToGRPCMessage(a),
-	)
+	return message.MarshalJSON(a)
 }
 
 func (a *Attribute) UnmarshalJSON(data []byte) error {
-	msg := new(object.Header_Attribute)
-
-	if err := protojson.Unmarshal(data, msg); err != nil {
-		return err
-	}
-
-	*a = *AttributeFromGRPCMessage(msg)
-
-	return nil
+	return message.UnmarshalJSON(a, data, new(object.Header_Attribute))
 }
 
 func (h *SplitHeader) MarshalJSON() ([]byte, error) {
-	return protojson.MarshalOptions{
-		EmitUnpopulated: true,
-	}.Marshal(
-		SplitHeaderToGRPCMessage(h),
-	)
+	return message.MarshalJSON(h)
 }
 
 func (h *SplitHeader) UnmarshalJSON(data []byte) error {
-	msg := new(object.Header_Split)
-
-	if err := protojson.Unmarshal(data, msg); err != nil {
-		return err
-	}
-
-	*h = *SplitHeaderFromGRPCMessage(msg)
-
-	return nil
+	return message.UnmarshalJSON(h, data, new(object.Header_Split))
 }
 
 func (h *Header) MarshalJSON() ([]byte, error) {
-	return protojson.MarshalOptions{
-		EmitUnpopulated: true,
-	}.Marshal(
-		HeaderToGRPCMessage(h),
-	)
+	return message.MarshalJSON(h)
 }
 
 func (h *Header) UnmarshalJSON(data []byte) error {
-	msg := new(object.Header)
-
-	if err := protojson.Unmarshal(data, msg); err != nil {
-		return err
-	}
-
-	*h = *HeaderFromGRPCMessage(msg)
-
-	return nil
+	return message.UnmarshalJSON(h, data, new(object.Header))
 }
 
 func (h *HeaderWithSignature) MarshalJSON() ([]byte, error) {
-	return protojson.MarshalOptions{
-		EmitUnpopulated: true,
-	}.Marshal(
-		HeaderWithSignatureToGRPCMessage(h),
-	)
+	return message.MarshalJSON(h)
 }
 
 func (h *HeaderWithSignature) UnmarshalJSON(data []byte) error {
-	msg := new(object.HeaderWithSignature)
-
-	if err := protojson.Unmarshal(data, msg); err != nil {
-		return err
-	}
-
-	*h = *HeaderWithSignatureFromGRPCMessage(msg)
-
-	return nil
+	return message.UnmarshalJSON(h, data, new(object.HeaderWithSignature))
 }
 
 func (o *Object) MarshalJSON() ([]byte, error) {
-	return protojson.MarshalOptions{
-		EmitUnpopulated: true,
-	}.Marshal(
-		ObjectToGRPCMessage(o),
-	)
+	return message.MarshalJSON(o)
 }
 
 func (o *Object) UnmarshalJSON(data []byte) error {
-	msg := new(object.Object)
+	return message.UnmarshalJSON(o, data, new(object.Object))
+}
 
-	if err := protojson.Unmarshal(data, msg); err != nil {
-		return err
-	}
+func (s *SplitInfo) MarshalJSON() ([]byte, error) {
+	return message.MarshalJSON(s)
+}
 
-	*o = *ObjectFromGRPCMessage(msg)
-
-	return nil
+func (s *SplitInfo) UnmarshalJSON(data []byte) error {
+	return message.UnmarshalJSON(s, data, new(object.SplitInfo))
 }
 
 func (f *SearchFilter) MarshalJSON() ([]byte, error) {
-	return protojson.MarshalOptions{
-		EmitUnpopulated: true,
-	}.Marshal(
-		SearchFilterToGRPCMessage(f),
-	)
+	return message.MarshalJSON(f)
 }
 
 func (f *SearchFilter) UnmarshalJSON(data []byte) error {
-	msg := new(object.SearchRequest_Body_Filter)
-
-	if err := protojson.Unmarshal(data, msg); err != nil {
-		return err
-	}
-
-	*f = *SearchFilterFromGRPCMessage(msg)
-
-	return nil
+	return message.UnmarshalJSON(f, data, new(object.SearchRequest_Body_Filter))
+}
+
+func (r *Range) MarshalJSON() ([]byte, error) {
+	return message.MarshalJSON(r)
+}
+
+func (r *Range) UnmarshalJSON(data []byte) error {
+	return message.UnmarshalJSON(r, data, new(object.Range))
 }
diff --git a/v2/object/json_test.go b/v2/object/json_test.go
deleted file mode 100644
index e8ad5d2..0000000
--- a/v2/object/json_test.go
+++ /dev/null
@@ -1,92 +0,0 @@
-package object_test
-
-import (
-	"testing"
-
-	"github.com/nspcc-dev/neofs-api-go/v2/object"
-	"github.com/stretchr/testify/require"
-)
-
-func TestShortHeaderJSON(t *testing.T) {
-	h := generateShortHeader("id")
-
-	data, err := h.MarshalJSON()
-	require.NoError(t, err)
-
-	h2 := new(object.ShortHeader)
-	require.NoError(t, h2.UnmarshalJSON(data))
-
-	require.Equal(t, h, h2)
-}
-
-func TestAttributeJSON(t *testing.T) {
-	a := generateAttribute("key", "value")
-
-	data, err := a.MarshalJSON()
-	require.NoError(t, err)
-
-	a2 := new(object.Attribute)
-	require.NoError(t, a2.UnmarshalJSON(data))
-
-	require.Equal(t, a, a2)
-}
-
-func TestSplitHeaderJSON(t *testing.T) {
-	h := generateSplit("sig")
-
-	data, err := h.MarshalJSON()
-	require.NoError(t, err)
-
-	h2 := new(object.SplitHeader)
-	require.NoError(t, h2.UnmarshalJSON(data))
-
-	require.Equal(t, h, h2)
-}
-
-func TestHeaderJSON(t *testing.T) {
-	h := generateHeader(10)
-
-	data, err := h.MarshalJSON()
-	require.NoError(t, err)
-
-	h2 := new(object.Header)
-	require.NoError(t, h2.UnmarshalJSON(data))
-
-	require.Equal(t, h, h2)
-}
-
-func TestHeaderWithSignatureJSON(t *testing.T) {
-	h := generateHeaderWithSignature()
-
-	data, err := h.MarshalJSON()
-	require.NoError(t, err)
-
-	h2 := new(object.HeaderWithSignature)
-	require.NoError(t, h2.UnmarshalJSON(data))
-
-	require.Equal(t, h, h2)
-}
-
-func TestObjectJSON(t *testing.T) {
-	o := generateObject("data")
-
-	data, err := o.MarshalJSON()
-	require.NoError(t, err)
-
-	o2 := new(object.Object)
-	require.NoError(t, o2.UnmarshalJSON(data))
-
-	require.Equal(t, o, o2)
-}
-
-func TestSearchFilterJSON(t *testing.T) {
-	f := generateFilter("key", "value")
-
-	data, err := f.MarshalJSON()
-	require.NoError(t, err)
-
-	f2 := new(object.SearchFilter)
-	require.NoError(t, f2.UnmarshalJSON(data))
-
-	require.Equal(t, f, f2)
-}
diff --git a/v2/object/marshal.go b/v2/object/marshal.go
index b836d13..9897489 100644
--- a/v2/object/marshal.go
+++ b/v2/object/marshal.go
@@ -1,10 +1,10 @@
 package object
 
 import (
+	"github.com/nspcc-dev/neofs-api-go/rpc/message"
 	"github.com/nspcc-dev/neofs-api-go/util/proto"
 	object "github.com/nspcc-dev/neofs-api-go/v2/object/grpc"
 	"github.com/nspcc-dev/neofs-api-go/v2/refs"
-	goproto "google.golang.org/protobuf/proto"
 )
 
 const (
@@ -193,14 +193,7 @@ func (h *ShortHeader) StableSize() (size int) {
 }
 
 func (h *ShortHeader) Unmarshal(data []byte) error {
-	m := new(object.ShortHeader)
-	if err := goproto.Unmarshal(data, m); err != nil {
-		return err
-	}
-
-	*h = *ShortHeaderFromGRPCMessage(m)
-
-	return nil
+	return message.Unmarshal(h, data, new(object.ShortHeader))
 }
 
 func (a *Attribute) StableMarshal(buf []byte) ([]byte, error) {
@@ -244,14 +237,7 @@ func (a *Attribute) StableSize() (size int) {
 }
 
 func (a *Attribute) Unmarshal(data []byte) error {
-	m := new(object.Header_Attribute)
-	if err := goproto.Unmarshal(data, m); err != nil {
-		return err
-	}
-
-	*a = *AttributeFromGRPCMessage(m)
-
-	return nil
+	return message.Unmarshal(a, data, new(object.Header_Attribute))
 }
 
 func (h *SplitHeader) StableMarshal(buf []byte) ([]byte, error) {
@@ -327,14 +313,7 @@ func (h *SplitHeader) StableSize() (size int) {
 }
 
 func (h *SplitHeader) Unmarshal(data []byte) error {
-	m := new(object.Header_Split)
-	if err := goproto.Unmarshal(data, m); err != nil {
-		return err
-	}
-
-	*h = *SplitHeaderFromGRPCMessage(m)
-
-	return nil
+	return message.Unmarshal(h, data, new(object.Header_Split))
 }
 
 func (h *Header) StableMarshal(buf []byte) ([]byte, error) {
@@ -454,14 +433,7 @@ func (h *Header) StableSize() (size int) {
 }
 
 func (h *Header) Unmarshal(data []byte) error {
-	m := new(object.Header)
-	if err := goproto.Unmarshal(data, m); err != nil {
-		return err
-	}
-
-	*h = *HeaderFromGRPCMessage(m)
-
-	return nil
+	return message.Unmarshal(h, data, new(object.Header))
 }
 
 func (h *HeaderWithSignature) StableMarshal(buf []byte) ([]byte, error) {
@@ -505,14 +477,7 @@ func (h *HeaderWithSignature) StableSize() (size int) {
 }
 
 func (h *HeaderWithSignature) Unmarshal(data []byte) error {
-	m := new(object.HeaderWithSignature)
-	if err := goproto.Unmarshal(data, m); err != nil {
-		return err
-	}
-
-	*h = *HeaderWithSignatureFromGRPCMessage(m)
-
-	return nil
+	return message.Unmarshal(h, data, new(object.HeaderWithSignature))
 }
 
 func (o *Object) StableMarshal(buf []byte) ([]byte, error) {
@@ -571,30 +536,8 @@ func (o *Object) StableSize() (size int) {
 	return size
 }
 
-func (o *Object) StableUnmarshal(data []byte) error {
-	if o == nil {
-		return nil
-	}
-
-	objGRPC := new(object.Object)
-	if err := goproto.Unmarshal(data, objGRPC); err != nil {
-		return err
-	}
-
-	*o = *ObjectFromGRPCMessage(objGRPC)
-
-	return nil
-}
-
 func (o *Object) Unmarshal(data []byte) error {
-	m := new(object.Object)
-	if err := goproto.Unmarshal(data, m); err != nil {
-		return err
-	}
-
-	*o = *ObjectFromGRPCMessage(m)
-
-	return nil
+	return message.Unmarshal(o, data, new(object.Object))
 }
 
 func (s *SplitInfo) StableMarshal(buf []byte) ([]byte, error) {
@@ -646,14 +589,7 @@ func (s *SplitInfo) StableSize() (size int) {
 }
 
 func (s *SplitInfo) Unmarshal(data []byte) error {
-	m := new(object.SplitInfo)
-	if err := goproto.Unmarshal(data, m); err != nil {
-		return err
-	}
-
-	*s = *SplitInfoFromGRPCMessage(m)
-
-	return nil
+	return message.Unmarshal(s, data, new(object.SplitInfo))
 }
 
 func (r *GetRequestBody) StableMarshal(buf []byte) ([]byte, error) {
@@ -696,6 +632,10 @@ func (r *GetRequestBody) StableSize() (size int) {
 	return size
 }
 
+func (r *GetRequestBody) Unmarshal(data []byte) error {
+	return message.Unmarshal(r, data, new(object.GetRequest_Body))
+}
+
 func (r *GetObjectPartInit) StableMarshal(buf []byte) ([]byte, error) {
 	if r == nil {
 		return []byte{}, nil
@@ -744,6 +684,10 @@ func (r *GetObjectPartInit) StableSize() (size int) {
 	return size
 }
 
+func (r *GetObjectPartInit) Unmarshal(data []byte) error {
+	return message.Unmarshal(r, data, new(object.GetResponse_Body_Init))
+}
+
 func (r *GetResponseBody) StableMarshal(buf []byte) ([]byte, error) {
 	if r == nil {
 		return []byte{}, nil
@@ -753,28 +697,27 @@ func (r *GetResponseBody) StableMarshal(buf []byte) ([]byte, error) {
 		buf = make([]byte, r.StableSize())
 	}
 
-	if r.objPart != nil {
-		switch v := r.objPart.(type) {
-		case *GetObjectPartInit:
-			_, err := proto.NestedStructureMarshal(getRespBodyInitField, buf, v)
-			if err != nil {
-				return nil, err
-			}
-		case *GetObjectPartChunk:
-			if v != nil {
-				_, err := proto.BytesMarshal(getRespBodyChunkField, buf, v.chunk)
-				if err != nil {
-					return nil, err
-				}
-			}
-		case *SplitInfo:
-			_, err := proto.NestedStructureMarshal(getRespBodySplitInfoField, buf, v)
-			if err != nil {
-				return nil, err
-			}
-		default:
-			panic("unknown one of object get response body type")
+	switch v := r.objPart.(type) {
+	case nil:
+	case *GetObjectPartInit:
+		_, err := proto.NestedStructureMarshal(getRespBodyInitField, buf, v)
+		if err != nil {
+			return nil, err
 		}
+	case *GetObjectPartChunk:
+		if v != nil {
+			_, err := proto.BytesMarshal(getRespBodyChunkField, buf, v.chunk)
+			if err != nil {
+				return nil, err
+			}
+		}
+	case *SplitInfo:
+		_, err := proto.NestedStructureMarshal(getRespBodySplitInfoField, buf, v)
+		if err != nil {
+			return nil, err
+		}
+	default:
+		panic("unknown one of object get response body type")
 	}
 
 	return buf, nil
@@ -785,22 +728,25 @@ func (r *GetResponseBody) StableSize() (size int) {
 		return 0
 	}
 
-	if r.objPart != nil {
-		switch v := r.objPart.(type) {
-		case *GetObjectPartInit:
-			size += proto.NestedStructureSize(getRespBodyInitField, v)
-		case *GetObjectPartChunk:
-			if v != nil {
-				size += proto.BytesSize(getRespBodyChunkField, v.chunk)
-			}
-		case *SplitInfo:
-			size += proto.NestedStructureSize(getRespBodySplitInfoField, v)
-		default:
-			panic("unknown one of object get response body type")
+	switch v := r.objPart.(type) {
+	case nil:
+	case *GetObjectPartInit:
+		size += proto.NestedStructureSize(getRespBodyInitField, v)
+	case *GetObjectPartChunk:
+		if v != nil {
+			size += proto.BytesSize(getRespBodyChunkField, v.chunk)
 		}
+	case *SplitInfo:
+		size += proto.NestedStructureSize(getRespBodySplitInfoField, v)
+	default:
+		panic("unknown one of object get response body type")
 	}
 
-	return size
+	return
+}
+
+func (r *GetResponseBody) Unmarshal(data []byte) error {
+	return message.Unmarshal(r, data, new(object.GetResponse_Body))
 }
 
 func (r *PutObjectPartInit) StableMarshal(buf []byte) ([]byte, error) {
@@ -859,6 +805,10 @@ func (r *PutObjectPartInit) StableSize() (size int) {
 	return size
 }
 
+func (r *PutObjectPartInit) Unmarshal(data []byte) error {
+	return message.Unmarshal(r, data, new(object.PutRequest_Body_Init))
+}
+
 func (r *PutRequestBody) StableMarshal(buf []byte) ([]byte, error) {
 	if r == nil {
 		return []byte{}, nil
@@ -868,23 +818,22 @@ func (r *PutRequestBody) StableMarshal(buf []byte) ([]byte, error) {
 		buf = make([]byte, r.StableSize())
 	}
 
-	if r.objPart != nil {
-		switch v := r.objPart.(type) {
-		case *PutObjectPartInit:
-			_, err := proto.NestedStructureMarshal(putReqBodyInitField, buf, v)
+	switch v := r.objPart.(type) {
+	case nil:
+	case *PutObjectPartInit:
+		_, err := proto.NestedStructureMarshal(putReqBodyInitField, buf, v)
+		if err != nil {
+			return nil, err
+		}
+	case *PutObjectPartChunk:
+		if v != nil {
+			_, err := proto.BytesMarshal(putReqBodyChunkField, buf, v.chunk)
 			if err != nil {
 				return nil, err
 			}
-		case *PutObjectPartChunk:
-			if v != nil {
-				_, err := proto.BytesMarshal(putReqBodyChunkField, buf, v.chunk)
-				if err != nil {
-					return nil, err
-				}
-			}
-		default:
-			panic("unknown one of object put request body type")
 		}
+	default:
+		panic("unknown one of object put request body type")
 	}
 
 	return buf, nil
@@ -895,22 +844,25 @@ func (r *PutRequestBody) StableSize() (size int) {
 		return 0
 	}
 
-	if r.objPart != nil {
-		switch v := r.objPart.(type) {
-		case *PutObjectPartInit:
-			size += proto.NestedStructureSize(putReqBodyInitField, v)
-		case *PutObjectPartChunk:
-			if v != nil {
-				size += proto.BytesSize(putReqBodyChunkField, v.chunk)
-			}
-		default:
-			panic("unknown one of object get response body type")
+	switch v := r.objPart.(type) {
+	case nil:
+	case *PutObjectPartInit:
+		size += proto.NestedStructureSize(putReqBodyInitField, v)
+	case *PutObjectPartChunk:
+		if v != nil {
+			size += proto.BytesSize(putReqBodyChunkField, v.chunk)
 		}
+	default:
+		panic("unknown one of object get response body type")
 	}
 
 	return size
 }
 
+func (r *PutRequestBody) Unmarshal(data []byte) error {
+	return message.Unmarshal(r, data, new(object.PutRequest_Body))
+}
+
 func (r *PutResponseBody) StableMarshal(buf []byte) ([]byte, error) {
 	if r == nil {
 		return []byte{}, nil
@@ -938,6 +890,10 @@ func (r *PutResponseBody) StableSize() (size int) {
 	return size
 }
 
+func (r *PutResponseBody) Unmarshal(data []byte) error {
+	return message.Unmarshal(r, data, new(object.PutResponse_Body))
+}
+
 func (r *DeleteRequestBody) StableMarshal(buf []byte) ([]byte, error) {
 	if r == nil {
 		return []byte{}, nil
@@ -965,6 +921,10 @@ func (r *DeleteRequestBody) StableSize() (size int) {
 	return size
 }
 
+func (r *DeleteRequestBody) Unmarshal(data []byte) error {
+	return message.Unmarshal(r, data, new(object.DeleteRequest_Body))
+}
+
 func (r *DeleteResponseBody) StableMarshal(buf []byte) ([]byte, error) {
 	if r == nil {
 		return []byte{}, nil
@@ -992,6 +952,10 @@ func (r *DeleteResponseBody) StableSize() (size int) {
 	return size
 }
 
+func (r *DeleteResponseBody) Unmarshal(data []byte) error {
+	return message.Unmarshal(r, data, new(object.DeleteResponse_Body))
+}
+
 func (r *HeadRequestBody) StableMarshal(buf []byte) ([]byte, error) {
 	if r == nil {
 		return []byte{}, nil
@@ -1040,6 +1004,10 @@ func (r *HeadRequestBody) StableSize() (size int) {
 	return size
 }
 
+func (r *HeadRequestBody) Unmarshal(data []byte) error {
+	return message.Unmarshal(r, data, new(object.HeadRequest_Body))
+}
+
 func (r *HeadResponseBody) StableMarshal(buf []byte) ([]byte, error) {
 	if r == nil {
 		return []byte{}, nil
@@ -1049,32 +1017,31 @@ func (r *HeadResponseBody) StableMarshal(buf []byte) ([]byte, error) {
 		buf = make([]byte, r.StableSize())
 	}
 
-	if r.hdrPart != nil {
-		switch v := r.hdrPart.(type) {
-		case *HeaderWithSignature:
-			if v != nil {
-				_, err := proto.NestedStructureMarshal(headRespBodyHeaderField, buf, v)
-				if err != nil {
-					return nil, err
-				}
+	switch v := r.hdrPart.(type) {
+	case nil:
+	case *HeaderWithSignature:
+		if v != nil {
+			_, err := proto.NestedStructureMarshal(headRespBodyHeaderField, buf, v)
+			if err != nil {
+				return nil, err
 			}
-		case *ShortHeader:
-			if v != nil {
-				_, err := proto.NestedStructureMarshal(headRespBodyShortHeaderField, buf, v)
-				if err != nil {
-					return nil, err
-				}
-			}
-		case *SplitInfo:
-			if v != nil {
-				_, err := proto.NestedStructureMarshal(headRespBodySplitInfoField, buf, v)
-				if err != nil {
-					return nil, err
-				}
-			}
-		default:
-			panic("unknown one of object put request body type")
 		}
+	case *ShortHeader:
+		if v != nil {
+			_, err := proto.NestedStructureMarshal(headRespBodyShortHeaderField, buf, v)
+			if err != nil {
+				return nil, err
+			}
+		}
+	case *SplitInfo:
+		if v != nil {
+			_, err := proto.NestedStructureMarshal(headRespBodySplitInfoField, buf, v)
+			if err != nil {
+				return nil, err
+			}
+		}
+	default:
+		panic("unknown one of object put request body type")
 	}
 
 	return buf, nil
@@ -1085,26 +1052,29 @@ func (r *HeadResponseBody) StableSize() (size int) {
 		return 0
 	}
 
-	if r.hdrPart != nil {
-		switch v := r.hdrPart.(type) {
-		case *HeaderWithSignature:
-			if v != nil {
-				size += proto.NestedStructureSize(headRespBodyHeaderField, v)
-			}
-		case *ShortHeader:
-			if v != nil {
-				size += proto.NestedStructureSize(headRespBodyShortHeaderField, v)
-			}
-		case *SplitInfo:
-			if v != nil {
-				size += proto.NestedStructureSize(headRespBodySplitInfoField, v)
-			}
-		default:
-			panic("unknown one of object put request body type")
+	switch v := r.hdrPart.(type) {
+	case nil:
+	case *HeaderWithSignature:
+		if v != nil {
+			size += proto.NestedStructureSize(headRespBodyHeaderField, v)
 		}
+	case *ShortHeader:
+		if v != nil {
+			size += proto.NestedStructureSize(headRespBodyShortHeaderField, v)
+		}
+	case *SplitInfo:
+		if v != nil {
+			size += proto.NestedStructureSize(headRespBodySplitInfoField, v)
+		}
+	default:
+		panic("unknown one of object put request body type")
 	}
 
-	return size
+	return
+}
+
+func (r *HeadResponseBody) Unmarshal(data []byte) error {
+	return message.Unmarshal(r, data, new(object.HeadResponse_Body))
 }
 
 func (f *SearchFilter) StableMarshal(buf []byte) ([]byte, error) {
@@ -1155,6 +1125,10 @@ func (f *SearchFilter) StableSize() (size int) {
 	return size
 }
 
+func (f *SearchFilter) Unmarshal(data []byte) error {
+	return message.Unmarshal(f, data, new(object.SearchRequest_Body_Filter))
+}
+
 func (r *SearchRequestBody) StableMarshal(buf []byte) ([]byte, error) {
 	if r == nil {
 		return []byte{}, nil
@@ -1210,6 +1184,10 @@ func (r *SearchRequestBody) StableSize() (size int) {
 	return size
 }
 
+func (r *SearchRequestBody) Unmarshal(data []byte) error {
+	return message.Unmarshal(r, data, new(object.SearchRequest_Body))
+}
+
 func (r *SearchResponseBody) StableMarshal(buf []byte) ([]byte, error) {
 	if r == nil {
 		return []byte{}, nil
@@ -1242,6 +1220,10 @@ func (r *SearchResponseBody) StableSize() (size int) {
 	return size
 }
 
+func (r *SearchResponseBody) Unmarshal(data []byte) error {
+	return message.Unmarshal(r, data, new(object.SearchResponse_Body))
+}
+
 func (r *Range) StableMarshal(buf []byte) ([]byte, error) {
 	if r == nil {
 		return []byte{}, nil
@@ -1282,6 +1264,10 @@ func (r *Range) StableSize() (size int) {
 	return size
 }
 
+func (r *Range) Unmarshal(data []byte) error {
+	return message.Unmarshal(r, data, new(object.Range))
+}
+
 func (r *GetRangeRequestBody) StableMarshal(buf []byte) ([]byte, error) {
 	if r == nil {
 		return []byte{}, nil
@@ -1330,6 +1316,10 @@ func (r *GetRangeRequestBody) StableSize() (size int) {
 	return size
 }
 
+func (r *GetRangeRequestBody) Unmarshal(data []byte) error {
+	return message.Unmarshal(r, data, new(object.GetRangeRequest_Body))
+}
+
 func (r *GetRangeResponseBody) StableMarshal(buf []byte) ([]byte, error) {
 	if r == nil {
 		return []byte{}, nil
@@ -1339,25 +1329,24 @@ func (r *GetRangeResponseBody) StableMarshal(buf []byte) ([]byte, error) {
 		buf = make([]byte, r.StableSize())
 	}
 
-	if r.rngPart != nil {
-		switch v := r.rngPart.(type) {
-		case *GetRangePartChunk:
-			if v != nil {
-				_, err := proto.BytesMarshal(getRangeRespChunkField, buf, v.chunk)
-				if err != nil {
-					return nil, err
-				}
+	switch v := r.rngPart.(type) {
+	case nil:
+	case *GetRangePartChunk:
+		if v != nil {
+			_, err := proto.BytesMarshal(getRangeRespChunkField, buf, v.chunk)
+			if err != nil {
+				return nil, err
 			}
-		case *SplitInfo:
-			if v != nil {
-				_, err := proto.NestedStructureMarshal(getRangeRespSplitInfoField, buf, v)
-				if err != nil {
-					return nil, err
-				}
-			}
-		default:
-			panic("unknown one of object get range request body type")
 		}
+	case *SplitInfo:
+		if v != nil {
+			_, err := proto.NestedStructureMarshal(getRangeRespSplitInfoField, buf, v)
+			if err != nil {
+				return nil, err
+			}
+		}
+	default:
+		panic("unknown one of object get range request body type")
 	}
 
 	return buf, nil
@@ -1368,22 +1357,25 @@ func (r *GetRangeResponseBody) StableSize() (size int) {
 		return 0
 	}
 
-	if r.rngPart != nil {
-		switch v := r.rngPart.(type) {
-		case *GetRangePartChunk:
-			if v != nil {
-				size += proto.BytesSize(getRangeRespChunkField, v.chunk)
-			}
-		case *SplitInfo:
-			if v != nil {
-				size = proto.NestedStructureSize(getRangeRespSplitInfoField, v)
-			}
-		default:
-			panic("unknown one of object get range request body type")
+	switch v := r.rngPart.(type) {
+	case nil:
+	case *GetRangePartChunk:
+		if v != nil {
+			size += proto.BytesSize(getRangeRespChunkField, v.chunk)
 		}
+	case *SplitInfo:
+		if v != nil {
+			size = proto.NestedStructureSize(getRangeRespSplitInfoField, v)
+		}
+	default:
+		panic("unknown one of object get range request body type")
 	}
 
-	return size
+	return
+}
+
+func (r *GetRangeResponseBody) Unmarshal(data []byte) error {
+	return message.Unmarshal(r, data, new(object.GetRangeResponse_Body))
 }
 
 func (r *GetRangeHashRequestBody) StableMarshal(buf []byte) ([]byte, error) {
@@ -1448,6 +1440,10 @@ func (r *GetRangeHashRequestBody) StableSize() (size int) {
 	return size
 }
 
+func (r *GetRangeHashRequestBody) Unmarshal(data []byte) error {
+	return message.Unmarshal(r, data, new(object.GetRangeHashRequest_Body))
+}
+
 func (r *GetRangeHashResponseBody) StableMarshal(buf []byte) ([]byte, error) {
 	if r == nil {
 		return []byte{}, nil
@@ -1487,3 +1483,7 @@ func (r *GetRangeHashResponseBody) StableSize() (size int) {
 
 	return size
 }
+
+func (r *GetRangeHashResponseBody) Unmarshal(data []byte) error {
+	return message.Unmarshal(r, data, new(object.GetRangeHashResponse_Body))
+}
diff --git a/v2/object/marshal_test.go b/v2/object/marshal_test.go
deleted file mode 100644
index c2686dc..0000000
--- a/v2/object/marshal_test.go
+++ /dev/null
@@ -1,776 +0,0 @@
-package object_test
-
-import (
-	"fmt"
-	"strconv"
-	"testing"
-
-	"github.com/nspcc-dev/neofs-api-go/v2/object"
-	grpc "github.com/nspcc-dev/neofs-api-go/v2/object/grpc"
-	"github.com/nspcc-dev/neofs-api-go/v2/refs"
-	"github.com/nspcc-dev/neofs-api-go/v2/session"
-	"github.com/stretchr/testify/require"
-	goproto "google.golang.org/protobuf/proto"
-)
-
-func TestShortHeader_StableMarshal(t *testing.T) {
-	hdrFrom := generateShortHeader("Owner ID")
-
-	t.Run("non empty", func(t *testing.T) {
-		wire, err := hdrFrom.StableMarshal(nil)
-		require.NoError(t, err)
-
-		hdrTo := new(object.ShortHeader)
-		require.NoError(t, hdrTo.Unmarshal(wire))
-
-		require.Equal(t, hdrFrom, hdrTo)
-	})
-}
-
-func TestAttribute_StableMarshal(t *testing.T) {
-	from := generateAttribute("Key", "Value")
-
-	t.Run("non empty", func(t *testing.T) {
-		wire, err := from.StableMarshal(nil)
-		require.NoError(t, err)
-
-		to := new(object.Attribute)
-		require.NoError(t, to.Unmarshal(wire))
-
-		require.Equal(t, from, to)
-	})
-}
-
-func TestSplitHeader_StableMarshal(t *testing.T) {
-	from := generateSplit("Split Outside")
-	hdr := generateHeader(123)
-	from.SetParentHeader(hdr)
-
-	t.Run("non empty", func(t *testing.T) {
-		wire, err := from.StableMarshal(nil)
-		require.NoError(t, err)
-
-		to := new(object.SplitHeader)
-		require.NoError(t, to.Unmarshal(wire))
-
-		require.Equal(t, from, to)
-	})
-}
-
-func TestHeader_StableMarshal(t *testing.T) {
-	insideHeader := generateHeader(100)
-	split := generateSplit("Split")
-	split.SetParentHeader(insideHeader)
-
-	from := generateHeader(500)
-	from.SetSplit(split)
-
-	t.Run("non empty", func(t *testing.T) {
-		wire, err := from.StableMarshal(nil)
-		require.NoError(t, err)
-
-		to := new(object.Header)
-		require.NoError(t, to.Unmarshal(wire))
-
-		require.Equal(t, from, to)
-	})
-}
-
-func TestObject_StableMarshal(t *testing.T) {
-	from := generateObject("Payload")
-
-	t.Run("non empty", func(t *testing.T) {
-		wire, err := from.StableMarshal(nil)
-		require.NoError(t, err)
-
-		to := new(object.Object)
-		require.NoError(t, to.Unmarshal(wire))
-
-		require.Equal(t, from, to)
-	})
-}
-
-func TestGetRequestBody_StableMarshal(t *testing.T) {
-	from := generateGetRequestBody("Container ID", "Object ID")
-	transport := new(grpc.GetRequest_Body)
-
-	t.Run("non empty", func(t *testing.T) {
-		wire, err := from.StableMarshal(nil)
-		require.NoError(t, err)
-
-		err = goproto.Unmarshal(wire, transport)
-		require.NoError(t, err)
-
-		to := object.GetRequestBodyFromGRPCMessage(transport)
-		require.Equal(t, from, to)
-	})
-}
-
-func TestGetResponseBody_StableMarshal(t *testing.T) {
-	initFrom := generateGetResponseBody(0)
-	chunkFrom := generateGetResponseBody(1)
-	splitInfoFrom := generateGetResponseBody(2)
-	transport := new(grpc.GetResponse_Body)
-
-	t.Run("init non empty", func(t *testing.T) {
-		wire, err := initFrom.StableMarshal(nil)
-		require.NoError(t, err)
-
-		err = goproto.Unmarshal(wire, transport)
-		require.NoError(t, err)
-
-		to := object.GetResponseBodyFromGRPCMessage(transport)
-		require.Equal(t, initFrom, to)
-	})
-
-	t.Run("chunk non empty", func(t *testing.T) {
-		wire, err := chunkFrom.StableMarshal(nil)
-		require.NoError(t, err)
-
-		err = goproto.Unmarshal(wire, transport)
-		require.NoError(t, err)
-
-		to := object.GetResponseBodyFromGRPCMessage(transport)
-		require.Equal(t, chunkFrom, to)
-	})
-
-	t.Run("split info non empty", func(t *testing.T) {
-		wire, err := splitInfoFrom.StableMarshal(nil)
-		require.NoError(t, err)
-
-		err = goproto.Unmarshal(wire, transport)
-		require.NoError(t, err)
-
-		to := object.GetResponseBodyFromGRPCMessage(transport)
-		require.Equal(t, splitInfoFrom, to)
-	})
-}
-
-func TestPutRequestBody_StableMarshal(t *testing.T) {
-	initFrom := generatePutRequestBody(true)
-	chunkFrom := generatePutRequestBody(false)
-	transport := new(grpc.PutRequest_Body)
-
-	t.Run("init non empty", func(t *testing.T) {
-		wire, err := initFrom.StableMarshal(nil)
-		require.NoError(t, err)
-
-		err = goproto.Unmarshal(wire, transport)
-		require.NoError(t, err)
-
-		to := object.PutRequestBodyFromGRPCMessage(transport)
-		require.Equal(t, initFrom, to)
-	})
-
-	t.Run("chunk non empty", func(t *testing.T) {
-		wire, err := chunkFrom.StableMarshal(nil)
-		require.NoError(t, err)
-
-		err = goproto.Unmarshal(wire, transport)
-		require.NoError(t, err)
-
-		to := object.PutRequestBodyFromGRPCMessage(transport)
-		require.Equal(t, chunkFrom, to)
-	})
-}
-
-func TestPutRequestBody_StableSize(t *testing.T) {
-	from := generatePutResponseBody("Object ID")
-	transport := new(grpc.PutResponse_Body)
-
-	t.Run("non empty", func(t *testing.T) {
-		wire, err := from.StableMarshal(nil)
-		require.NoError(t, err)
-
-		err = goproto.Unmarshal(wire, transport)
-		require.NoError(t, err)
-
-		to := object.PutResponseBodyFromGRPCMessage(transport)
-		require.Equal(t, from, to)
-	})
-}
-
-func TestDeleteRequestBody_StableMarshal(t *testing.T) {
-	from := generateDeleteRequestBody("Container ID", "Object ID")
-	transport := new(grpc.DeleteRequest_Body)
-
-	t.Run("non empty", func(t *testing.T) {
-		wire, err := from.StableMarshal(nil)
-		require.NoError(t, err)
-
-		err = goproto.Unmarshal(wire, transport)
-		require.NoError(t, err)
-
-		to := object.DeleteRequestBodyFromGRPCMessage(transport)
-		require.Equal(t, from, to)
-	})
-}
-
-func TestDeleteResponseBody_StableMarshal(t *testing.T) {
-	from := generateDeleteResponseBody("CID", "OID")
-	transport := new(grpc.DeleteResponse_Body)
-
-	t.Run("non empty", func(t *testing.T) {
-		wire, err := from.StableMarshal(nil)
-		require.NoError(t, err)
-
-		err = goproto.Unmarshal(wire, transport)
-		require.NoError(t, err)
-
-		to := object.DeleteResponseBodyFromGRPCMessage(transport)
-		require.Equal(t, from, to)
-	})
-}
-
-func TestSplitHeaderFromGRPCMessage(t *testing.T) {
-	from := generateHeadRequestBody("Container ID", "Object ID")
-	transport := new(grpc.HeadRequest_Body)
-
-	t.Run("non empty", func(t *testing.T) {
-		wire, err := from.StableMarshal(nil)
-		require.NoError(t, err)
-
-		err = goproto.Unmarshal(wire, transport)
-		require.NoError(t, err)
-
-		to := object.HeadRequestBodyFromGRPCMessage(transport)
-		require.Equal(t, from, to)
-	})
-}
-
-func TestHeadResponseBody_StableMarshal(t *testing.T) {
-	shortFrom := generateHeadResponseBody(0)
-	fullFrom := generateHeadResponseBody(1)
-	splitInfoFrom := generateHeadResponseBody(2)
-	transport := new(grpc.HeadResponse_Body)
-
-	t.Run("short header non empty", func(t *testing.T) {
-		wire, err := shortFrom.StableMarshal(nil)
-		require.NoError(t, err)
-
-		err = goproto.Unmarshal(wire, transport)
-		require.NoError(t, err)
-
-		to := object.HeadResponseBodyFromGRPCMessage(transport)
-		require.Equal(t, shortFrom, to)
-	})
-
-	t.Run("full header non empty", func(t *testing.T) {
-		wire, err := fullFrom.StableMarshal(nil)
-		require.NoError(t, err)
-
-		err = goproto.Unmarshal(wire, transport)
-		require.NoError(t, err)
-
-		to := object.HeadResponseBodyFromGRPCMessage(transport)
-		require.Equal(t, fullFrom, to)
-	})
-
-	t.Run("split info non empty", func(t *testing.T) {
-		wire, err := splitInfoFrom.StableMarshal(nil)
-		require.NoError(t, err)
-
-		err = goproto.Unmarshal(wire, transport)
-		require.NoError(t, err)
-
-		to := object.HeadResponseBodyFromGRPCMessage(transport)
-		require.Equal(t, splitInfoFrom, to)
-	})
-}
-
-func TestSearchRequestBody_StableMarshal(t *testing.T) {
-	from := generateSearchRequestBody(10, "Container ID")
-	transport := new(grpc.SearchRequest_Body)
-
-	t.Run("non empty", func(t *testing.T) {
-		wire, err := from.StableMarshal(nil)
-		require.NoError(t, err)
-
-		err = goproto.Unmarshal(wire, transport)
-		require.NoError(t, err)
-
-		to := object.SearchRequestBodyFromGRPCMessage(transport)
-		require.Equal(t, from, to)
-	})
-}
-
-func TestSearchResponseBody_StableMarshal(t *testing.T) {
-	from := generateSearchResponseBody(10)
-	transport := new(grpc.SearchResponse_Body)
-
-	t.Run("non empty", func(t *testing.T) {
-		wire, err := from.StableMarshal(nil)
-		require.NoError(t, err)
-
-		err = goproto.Unmarshal(wire, transport)
-		require.NoError(t, err)
-
-		to := object.SearchResponseBodyFromGRPCMessage(transport)
-		require.Equal(t, from, to)
-	})
-}
-
-func TestGetRangeRequestBody_StableMarshal(t *testing.T) {
-	from := generateRangeRequestBody("Container ID", "Object ID")
-	transport := new(grpc.GetRangeRequest_Body)
-
-	t.Run("non empty", func(t *testing.T) {
-		wire, err := from.StableMarshal(nil)
-		require.NoError(t, err)
-
-		err = goproto.Unmarshal(wire, transport)
-		require.NoError(t, err)
-
-		to := object.GetRangeRequestBodyFromGRPCMessage(transport)
-		require.Equal(t, from, to)
-	})
-}
-
-func TestGetRangeResponseBody_StableMarshal(t *testing.T) {
-	dataFrom := generateRangeResponseBody("some data", true)
-	splitInfoFrom := generateRangeResponseBody("some data", false)
-	transport := new(grpc.GetRangeResponse_Body)
-
-	t.Run("data non empty", func(t *testing.T) {
-		wire, err := dataFrom.StableMarshal(nil)
-		require.NoError(t, err)
-
-		err = goproto.Unmarshal(wire, transport)
-		require.NoError(t, err)
-
-		to := object.GetRangeResponseBodyFromGRPCMessage(transport)
-		require.Equal(t, dataFrom, to)
-	})
-
-	t.Run("split info non empty", func(t *testing.T) {
-		wire, err := splitInfoFrom.StableMarshal(nil)
-		require.NoError(t, err)
-
-		err = goproto.Unmarshal(wire, transport)
-		require.NoError(t, err)
-
-		to := object.GetRangeResponseBodyFromGRPCMessage(transport)
-		require.Equal(t, splitInfoFrom, to)
-	})
-}
-
-func TestGetRangeHashRequestBody_StableMarshal(t *testing.T) {
-	from := generateRangeHashRequestBody("Container ID", "Object ID", 5)
-	transport := new(grpc.GetRangeHashRequest_Body)
-
-	t.Run("non empty", func(t *testing.T) {
-		wire, err := from.StableMarshal(nil)
-		require.NoError(t, err)
-
-		err = goproto.Unmarshal(wire, transport)
-		require.NoError(t, err)
-
-		to := object.GetRangeHashRequestBodyFromGRPCMessage(transport)
-		require.Equal(t, from, to)
-	})
-}
-
-func TestGetRangeHashResponseBody_StableMarshal(t *testing.T) {
-	from := generateRangeHashResponseBody(5)
-	transport := new(grpc.GetRangeHashResponse_Body)
-
-	t.Run("non empty", func(t *testing.T) {
-		wire, err := from.StableMarshal(nil)
-		require.NoError(t, err)
-
-		err = goproto.Unmarshal(wire, transport)
-		require.NoError(t, err)
-
-		to := object.GetRangeHashResponseBodyFromGRPCMessage(transport)
-		require.Equal(t, from, to)
-	})
-}
-
-func TestHeaderWithSignature_StableMarshal(t *testing.T) {
-	from := generateHeaderWithSignature()
-
-	t.Run("non empty", func(t *testing.T) {
-		wire, err := from.StableMarshal(nil)
-		require.NoError(t, err)
-
-		to := new(object.HeaderWithSignature)
-		require.NoError(t, to.Unmarshal(wire))
-
-		require.Equal(t, from, to)
-	})
-}
-
-func generateOwner(id string) *refs.OwnerID {
-	owner := new(refs.OwnerID)
-	owner.SetValue([]byte(id))
-
-	return owner
-}
-
-func generateObjectID(id string) *refs.ObjectID {
-	oid := new(refs.ObjectID)
-	oid.SetValue([]byte(id))
-
-	return oid
-}
-
-func generateContainerID(id string) *refs.ContainerID {
-	cid := new(refs.ContainerID)
-	cid.SetValue([]byte(id))
-
-	return cid
-}
-
-func generateSignature(k, v string) *refs.Signature {
-	sig := new(refs.Signature)
-	sig.SetKey([]byte(k))
-	sig.SetSign([]byte(v))
-
-	return sig
-}
-
-func generateVersion(maj, min uint32) *refs.Version {
-	version := new(refs.Version)
-	version.SetMajor(maj)
-	version.SetMinor(min)
-
-	return version
-}
-
-func generateAddress(cid, oid string) *refs.Address {
-	addr := new(refs.Address)
-	addr.SetObjectID(generateObjectID(oid))
-	addr.SetContainerID(generateContainerID(cid))
-
-	return addr
-}
-
-func generateSessionToken(id string) *session.SessionToken {
-	lifetime := new(session.TokenLifetime)
-	lifetime.SetExp(1)
-	lifetime.SetNbf(2)
-	lifetime.SetIat(3)
-
-	objectCtx := new(session.ObjectSessionContext)
-	objectCtx.SetVerb(session.ObjectVerbPut)
-	objectCtx.SetAddress(generateAddress("Container ID", "Object ID"))
-
-	tokenBody := new(session.SessionTokenBody)
-	tokenBody.SetID([]byte(id))
-	tokenBody.SetOwnerID(generateOwner("Owner ID"))
-	tokenBody.SetSessionKey([]byte(id))
-	tokenBody.SetLifetime(lifetime)
-	tokenBody.SetContext(objectCtx)
-
-	sessionToken := new(session.SessionToken)
-	sessionToken.SetBody(tokenBody)
-	sessionToken.SetSignature(generateSignature("public key", id))
-
-	return sessionToken
-}
-
-func generateShortHeader(id string) *object.ShortHeader {
-	hdr := new(object.ShortHeader)
-	hdr.SetOwnerID(generateOwner(id))
-	hdr.SetVersion(generateVersion(2, 0))
-	hdr.SetCreationEpoch(200)
-	hdr.SetObjectType(object.TypeRegular)
-	hdr.SetPayloadLength(10)
-	hdr.SetPayloadHash(generateChecksum("payload hash"))
-	hdr.SetHomomorphicHash(generateChecksum("homomorphic hash"))
-
-	return hdr
-}
-
-func generateAttribute(k, v string) *object.Attribute {
-	attr := new(object.Attribute)
-	attr.SetValue(v)
-	attr.SetKey(k)
-
-	return attr
-}
-
-func generateSplit(sig string) *object.SplitHeader {
-	split := new(object.SplitHeader)
-	split.SetChildren([]*refs.ObjectID{
-		generateObjectID("Child 1"),
-		generateObjectID("Child 2"),
-	})
-	split.SetParent(generateObjectID("Parent"))
-	split.SetParentSignature(generateSignature("Key", sig))
-	split.SetPrevious(generateObjectID("Previous"))
-	split.SetSplitID([]byte("UUIDv4"))
-
-	return split
-}
-
-func generateChecksum(data string) *refs.Checksum {
-	checksum := new(refs.Checksum)
-	checksum.SetType(refs.TillichZemor)
-	checksum.SetSum([]byte(data))
-
-	return checksum
-}
-
-func generateHeader(ln uint64) *object.Header {
-	hdr := new(object.Header)
-	hdr.SetPayloadLength(ln)
-	hdr.SetCreationEpoch(ln / 2)
-	hdr.SetVersion(generateVersion(2, 0))
-	hdr.SetOwnerID(generateOwner("Owner ID"))
-	hdr.SetContainerID(generateContainerID("Contanier ID"))
-	hdr.SetAttributes([]*object.Attribute{
-		generateAttribute("One", "Two"),
-		generateAttribute("Three", "Four"),
-	})
-	hdr.SetHomomorphicHash(generateChecksum("Homomorphic Hash"))
-	hdr.SetObjectType(object.TypeRegular)
-	hdr.SetPayloadHash(generateChecksum("Payload Hash"))
-	hdr.SetSessionToken(generateSessionToken(strconv.Itoa(int(ln))))
-
-	return hdr
-}
-
-func generateObject(data string) *object.Object {
-	insideHeader := generateHeader(100)
-	split := generateSplit("Split")
-	split.SetParentHeader(insideHeader)
-
-	outsideHeader := generateHeader(500)
-	outsideHeader.SetSplit(split)
-
-	obj := new(object.Object)
-	obj.SetSignature(generateSignature("Public Key", "Signature"))
-	obj.SetObjectID(generateObjectID("Object ID"))
-	obj.SetPayload([]byte(data))
-	obj.SetHeader(outsideHeader)
-
-	return obj
-}
-
-func generateGetRequestBody(cid, oid string) *object.GetRequestBody {
-	req := new(object.GetRequestBody)
-	req.SetAddress(generateAddress(cid, oid))
-	req.SetRaw(true)
-
-	return req
-}
-
-func generateGetResponseBody(i int) *object.GetResponseBody {
-	resp := new(object.GetResponseBody)
-	var part object.GetObjectPart
-
-	switch i {
-	case 0:
-		init := new(object.GetObjectPartInit)
-		init.SetObjectID(generateObjectID("Object ID"))
-		init.SetSignature(generateSignature("Key", "Signature"))
-		init.SetHeader(generateHeader(10))
-		part = init
-	case 1:
-		chunk := new(object.GetObjectPartChunk)
-		chunk.SetChunk([]byte("Some data chunk"))
-		part = chunk
-	default:
-		part = generateSplitInfo()
-	}
-
-	resp.SetObjectPart(part)
-
-	return resp
-}
-
-func generatePutRequestBody(flag bool) *object.PutRequestBody {
-	req := new(object.PutRequestBody)
-	var part object.PutObjectPart
-
-	if flag {
-		init := new(object.PutObjectPartInit)
-		init.SetObjectID(generateObjectID("Object ID"))
-		init.SetSignature(generateSignature("Key", "Signature"))
-		init.SetHeader(generateHeader(10))
-		init.SetCopiesNumber(1)
-		part = init
-	} else {
-		chunk := new(object.PutObjectPartChunk)
-		chunk.SetChunk([]byte("Some data chunk"))
-		part = chunk
-	}
-	req.SetObjectPart(part)
-
-	return req
-}
-
-func generatePutResponseBody(oid string) *object.PutResponseBody {
-	resp := new(object.PutResponseBody)
-	resp.SetObjectID(generateObjectID(oid))
-
-	return resp
-}
-
-func generateDeleteRequestBody(cid, oid string) *object.DeleteRequestBody {
-	req := new(object.DeleteRequestBody)
-	req.SetAddress(generateAddress(cid, oid))
-
-	return req
-}
-
-func generateDeleteResponseBody(cid, oid string) *object.DeleteResponseBody {
-	resp := new(object.DeleteResponseBody)
-	resp.SetTombstone(generateAddress(cid, oid))
-
-	return resp
-}
-
-func generateHeadRequestBody(cid, oid string) *object.HeadRequestBody {
-	req := new(object.HeadRequestBody)
-	req.SetAddress(generateAddress(cid, oid))
-	req.SetRaw(true)
-	req.SetMainOnly(true)
-
-	return req
-}
-
-func generateHeadResponseBody(flag int) *object.HeadResponseBody {
-	req := new(object.HeadResponseBody)
-	var part object.GetHeaderPart
-
-	switch flag {
-	case 0:
-		part = generateShortHeader("short id")
-	case 1:
-		part = generateHeaderWithSignature()
-	default:
-		part = generateSplitInfo()
-	}
-
-	req.SetHeaderPart(part)
-
-	return req
-}
-
-func generateHeaderWithSignature() *object.HeaderWithSignature {
-	hdrWithSig := new(object.HeaderWithSignature)
-	hdrWithSig.SetHeader(generateHeader(30))
-	hdrWithSig.SetSignature(generateSignature("sig", "key"))
-
-	return hdrWithSig
-}
-
-func generateFilter(k, v string) *object.SearchFilter {
-	f := new(object.SearchFilter)
-	f.SetKey(k)
-	f.SetValue(v)
-	f.SetMatchType(object.MatchStringEqual)
-
-	return f
-}
-
-func generateSearchRequestBody(n int, id string) *object.SearchRequestBody {
-	req := new(object.SearchRequestBody)
-	req.SetContainerID(generateContainerID(id))
-	req.SetVersion(1)
-
-	ff := make([]*object.SearchFilter, n)
-
-	for i := 0; i < n; i++ {
-		ff[i] = generateFilter("Some Key", fmt.Sprintf("Value %d", i+1))
-	}
-	req.SetFilters(ff)
-
-	return req
-}
-
-func generateSearchResponseBody(n int) *object.SearchResponseBody {
-	resp := new(object.SearchResponseBody)
-	list := make([]*refs.ObjectID, n)
-	for i := 0; i < n; i++ {
-		list[i] = generateObjectID(fmt.Sprintf("Object ID %d", i+1))
-	}
-
-	resp.SetIDList(list)
-
-	return resp
-}
-
-func generateRange(off, ln uint64) *object.Range {
-	r := new(object.Range)
-	r.SetOffset(off)
-	r.SetLength(ln)
-
-	return r
-}
-
-func generateRangeRequestBody(cid, oid string) *object.GetRangeRequestBody {
-	req := new(object.GetRangeRequestBody)
-	req.SetAddress(generateAddress(cid, oid))
-	req.SetRange(generateRange(10, 20))
-	req.SetRaw(true)
-
-	return req
-}
-
-func generateRangeResponseBody(data string, flag bool) *object.GetRangeResponseBody {
-	resp := new(object.GetRangeResponseBody)
-
-	if flag {
-		p := new(object.GetRangePartChunk)
-		p.SetChunk([]byte(data))
-		resp.SetRangePart(p)
-	} else {
-		resp.SetRangePart(generateSplitInfo())
-	}
-
-	return resp
-}
-
-func generateRangeHashRequestBody(cid, oid string, n int) *object.GetRangeHashRequestBody {
-	req := new(object.GetRangeHashRequestBody)
-	req.SetAddress(generateAddress(cid, oid))
-
-	rngs := make([]*object.Range, n)
-	for i := 0; i < n; i++ {
-		rngs[i] = generateRange(100, 200+uint64(n))
-	}
-
-	req.SetRanges(rngs)
-	req.SetSalt([]byte("xor salt"))
-	req.SetType(refs.TillichZemor)
-
-	return req
-}
-
-func generateRangeHashResponseBody(n int) *object.GetRangeHashResponseBody {
-	resp := new(object.GetRangeHashResponseBody)
-
-	list := make([][]byte, n)
-	for i := 0; i < n; i++ {
-		list[i] = []byte("Some homomorphic hash data" + strconv.Itoa(n))
-	}
-
-	resp.SetType(refs.TillichZemor)
-	resp.SetHashList(list)
-
-	return resp
-}
-
-func TestObject_StableUnmarshal(t *testing.T) {
-	obj := generateObject("some data")
-
-	data, err := obj.StableMarshal(nil)
-	require.NoError(t, err)
-
-	obj2 := new(object.Object)
-	require.NoError(t, obj2.StableUnmarshal(data))
-
-	require.Equal(t, obj, obj2)
-}
-
-func generateSplitInfo() *object.SplitInfo {
-	splitInfo := new(object.SplitInfo)
-	splitInfo.SetSplitID([]byte("splitID"))
-	splitInfo.SetLastPart(generateObjectID("Right ID"))
-	splitInfo.SetLink(generateObjectID("Link ID"))
-
-	return splitInfo
-}
diff --git a/v2/object/message_test.go b/v2/object/message_test.go
new file mode 100644
index 0000000..1639f35
--- /dev/null
+++ b/v2/object/message_test.go
@@ -0,0 +1,54 @@
+package object_test
+
+import (
+	"testing"
+
+	"github.com/nspcc-dev/neofs-api-go/rpc/message"
+	messagetest "github.com/nspcc-dev/neofs-api-go/rpc/message/test"
+	objecttest "github.com/nspcc-dev/neofs-api-go/v2/object/test"
+)
+
+func TestMessageConvert(t *testing.T) {
+	messagetest.TestRPCMessage(t,
+		func(empty bool) message.Message { return objecttest.GenerateShortHeader(empty) },
+		func(empty bool) message.Message { return objecttest.GenerateAttribute(empty) },
+		func(empty bool) message.Message { return objecttest.GenerateSplitHeader(empty) },
+		func(empty bool) message.Message { return objecttest.GenerateHeader(empty) },
+		func(empty bool) message.Message { return objecttest.GenerateObject(empty) },
+		func(empty bool) message.Message { return objecttest.GenerateSplitInfo(empty) },
+		func(empty bool) message.Message { return objecttest.GenerateGetRequestBody(empty) },
+		func(empty bool) message.Message { return objecttest.GenerateGetRequest(empty) },
+		func(empty bool) message.Message { return objecttest.GenerateGetObjectPartInit(empty) },
+		func(empty bool) message.Message { return objecttest.GenerateGetObjectPartChunk(empty) },
+		func(empty bool) message.Message { return objecttest.GenerateGetResponseBody(empty) },
+		func(empty bool) message.Message { return objecttest.GenerateGetResponse(empty) },
+		func(empty bool) message.Message { return objecttest.GeneratePutObjectPartInit(empty) },
+		func(empty bool) message.Message { return objecttest.GeneratePutObjectPartChunk(empty) },
+		func(empty bool) message.Message { return objecttest.GeneratePutRequestBody(empty) },
+		func(empty bool) message.Message { return objecttest.GeneratePutRequest(empty) },
+		func(empty bool) message.Message { return objecttest.GeneratePutResponseBody(empty) },
+		func(empty bool) message.Message { return objecttest.GeneratePutResponse(empty) },
+		func(empty bool) message.Message { return objecttest.GenerateDeleteRequestBody(empty) },
+		func(empty bool) message.Message { return objecttest.GenerateDeleteRequest(empty) },
+		func(empty bool) message.Message { return objecttest.GenerateDeleteResponseBody(empty) },
+		func(empty bool) message.Message { return objecttest.GenerateDeleteResponse(empty) },
+		func(empty bool) message.Message { return objecttest.GenerateHeadRequestBody(empty) },
+		func(empty bool) message.Message { return objecttest.GenerateHeadRequest(empty) },
+		func(empty bool) message.Message { return objecttest.GenerateHeadResponseBody(empty) },
+		func(empty bool) message.Message { return objecttest.GenerateHeadResponse(empty) },
+		func(empty bool) message.Message { return objecttest.GenerateSearchFilter(empty) },
+		func(empty bool) message.Message { return objecttest.GenerateSearchRequestBody(empty) },
+		func(empty bool) message.Message { return objecttest.GenerateSearchRequest(empty) },
+		func(empty bool) message.Message { return objecttest.GenerateSearchResponseBody(empty) },
+		func(empty bool) message.Message { return objecttest.GenerateSearchResponse(empty) },
+		func(empty bool) message.Message { return objecttest.GenerateRange(empty) },
+		func(empty bool) message.Message { return objecttest.GenerateGetRangeRequestBody(empty) },
+		func(empty bool) message.Message { return objecttest.GenerateGetRangeRequest(empty) },
+		func(empty bool) message.Message { return objecttest.GenerateGetRangeResponseBody(empty) },
+		func(empty bool) message.Message { return objecttest.GenerateGetRangeResponse(empty) },
+		func(empty bool) message.Message { return objecttest.GenerateGetRangeHashRequestBody(empty) },
+		func(empty bool) message.Message { return objecttest.GenerateGetRangeHashRequest(empty) },
+		func(empty bool) message.Message { return objecttest.GenerateGetRangeHashResponseBody(empty) },
+		func(empty bool) message.Message { return objecttest.GenerateGetRangeHashResponse(empty) },
+	)
+}
diff --git a/v2/object/service.go b/v2/object/service.go
deleted file mode 100644
index 2bf69c8..0000000
--- a/v2/object/service.go
+++ /dev/null
@@ -1,129 +0,0 @@
-package object
-
-import (
-	"context"
-
-	"github.com/nspcc-dev/neofs-api-go/v2/session"
-)
-
-type Service interface {
-	Get(context.Context, *GetRequest) (GetObjectStreamer, error)
-	Put(context.Context) (PutObjectStreamer, error)
-	Head(context.Context, *HeadRequest) (*HeadResponse, error)
-	Search(context.Context, *SearchRequest) (SearchObjectStreamer, error)
-	Delete(context.Context, *DeleteRequest) (*DeleteResponse, error)
-	GetRange(context.Context, *GetRangeRequest) (GetRangeObjectStreamer, error)
-	GetRangeHash(context.Context, *GetRangeHashRequest) (*GetRangeHashResponse, error)
-}
-
-type GetRequest struct {
-	body *GetRequestBody
-
-	metaHeader *session.RequestMetaHeader
-
-	verifyHeader *session.RequestVerificationHeader
-}
-
-type GetResponse struct {
-	body *GetResponseBody
-
-	metaHeader *session.ResponseMetaHeader
-
-	verifyHeader *session.ResponseVerificationHeader
-}
-
-type PutRequest struct {
-	body *PutRequestBody
-
-	metaHeader *session.RequestMetaHeader
-
-	verifyHeader *session.RequestVerificationHeader
-}
-
-type PutResponse struct {
-	body *PutResponseBody
-
-	metaHeader *session.ResponseMetaHeader
-
-	verifyHeader *session.ResponseVerificationHeader
-}
-
-type DeleteRequest struct {
-	body *DeleteRequestBody
-
-	metaHeader *session.RequestMetaHeader
-
-	verifyHeader *session.RequestVerificationHeader
-}
-
-type DeleteResponse struct {
-	body *DeleteResponseBody
-
-	metaHeader *session.ResponseMetaHeader
-
-	verifyHeader *session.ResponseVerificationHeader
-}
-
-type HeadRequest struct {
-	body *HeadRequestBody
-
-	metaHeader *session.RequestMetaHeader
-
-	verifyHeader *session.RequestVerificationHeader
-}
-
-type HeadResponse struct {
-	body *HeadResponseBody
-
-	metaHeader *session.ResponseMetaHeader
-
-	verifyHeader *session.ResponseVerificationHeader
-}
-
-type SearchRequest struct {
-	body *SearchRequestBody
-
-	metaHeader *session.RequestMetaHeader
-
-	verifyHeader *session.RequestVerificationHeader
-}
-
-type SearchResponse struct {
-	body *SearchResponseBody
-
-	metaHeader *session.ResponseMetaHeader
-
-	verifyHeader *session.ResponseVerificationHeader
-}
-
-type GetRangeRequest struct {
-	body *GetRangeRequestBody
-
-	metaHeader *session.RequestMetaHeader
-
-	verifyHeader *session.RequestVerificationHeader
-}
-
-type GetRangeResponse struct {
-	body *GetRangeResponseBody
-
-	metaHeader *session.ResponseMetaHeader
-
-	verifyHeader *session.ResponseVerificationHeader
-}
-
-type GetRangeHashRequest struct {
-	body *GetRangeHashRequestBody
-
-	metaHeader *session.RequestMetaHeader
-
-	verifyHeader *session.RequestVerificationHeader
-}
-
-type GetRangeHashResponse struct {
-	body *GetRangeHashResponseBody
-
-	metaHeader *session.ResponseMetaHeader
-
-	verifyHeader *session.ResponseVerificationHeader
-}
diff --git a/v2/object/test/client_test.go b/v2/object/test/client_test.go
deleted file mode 100644
index 43590f0..0000000
--- a/v2/object/test/client_test.go
+++ /dev/null
@@ -1,471 +0,0 @@
-package main
-
-import (
-	"context"
-	"crypto/ecdsa"
-	"errors"
-	"testing"
-
-	"github.com/nspcc-dev/neofs-api-go/v2/object"
-	objectGRPC "github.com/nspcc-dev/neofs-api-go/v2/object/grpc"
-	"github.com/nspcc-dev/neofs-api-go/v2/refs"
-	"github.com/nspcc-dev/neofs-api-go/v2/session"
-	"github.com/nspcc-dev/neofs-api-go/v2/signature"
-	"github.com/nspcc-dev/neofs-crypto/test"
-	"github.com/stretchr/testify/require"
-	"google.golang.org/grpc"
-)
-
-type testGRPCClient struct {
-	server objectGRPC.ObjectServiceServer
-}
-
-func (s *testGRPCClient) Get(ctx context.Context, in *objectGRPC.GetRequest, opts ...grpc.CallOption) (objectGRPC.ObjectService_GetClient, error) {
-	panic("implement me")
-}
-
-func (s *testGRPCClient) Put(ctx context.Context, opts ...grpc.CallOption) (objectGRPC.ObjectService_PutClient, error) {
-	panic("implement me")
-}
-
-func (s *testGRPCClient) Delete(ctx context.Context, in *objectGRPC.DeleteRequest, opts ...grpc.CallOption) (*objectGRPC.DeleteResponse, error) {
-	return s.server.Delete(ctx, in)
-}
-
-func (s *testGRPCClient) Head(ctx context.Context, in *objectGRPC.HeadRequest, opts ...grpc.CallOption) (*objectGRPC.HeadResponse, error) {
-	return s.server.Head(ctx, in)
-}
-
-func (s *testGRPCClient) Search(ctx context.Context, in *objectGRPC.SearchRequest, opts ...grpc.CallOption) (objectGRPC.ObjectService_SearchClient, error) {
-	panic("implement me")
-}
-
-func (s *testGRPCClient) GetRange(ctx context.Context, in *objectGRPC.GetRangeRequest, opts ...grpc.CallOption) (objectGRPC.ObjectService_GetRangeClient, error) {
-	panic("implement me")
-}
-
-func (s *testGRPCClient) GetRangeHash(ctx context.Context, in *objectGRPC.GetRangeHashRequest, opts ...grpc.CallOption) (*objectGRPC.GetRangeHashResponse, error) {
-	return s.server.GetRangeHash(ctx, in)
-}
-
-type testGRPCServer struct {
-	key              *ecdsa.PrivateKey
-	headResp         *object.HeadResponse
-	delResp          *object.DeleteResponse
-	getRangeHashResp *object.GetRangeHashResponse
-	err              error
-}
-
-func (s *testGRPCServer) Get(request *objectGRPC.GetRequest, server objectGRPC.ObjectService_GetServer) error {
-	panic("implement me")
-}
-
-func (s *testGRPCServer) Put(server objectGRPC.ObjectService_PutServer) error {
-	panic("implement me")
-}
-
-func (s *testGRPCServer) Delete(ctx context.Context, request *objectGRPC.DeleteRequest) (*objectGRPC.DeleteResponse, error) {
-	if s.err != nil {
-		return nil, s.err
-	}
-
-	// verify request structure
-	if err := signature.VerifyServiceMessage(
-		object.DeleteRequestFromGRPCMessage(request),
-	); err != nil {
-		return nil, err
-	}
-
-	// sign response structure
-	if err := signature.SignServiceMessage(s.key, s.delResp); err != nil {
-		return nil, err
-	}
-
-	return object.DeleteResponseToGRPCMessage(s.delResp), nil
-}
-
-func (s *testGRPCServer) Head(ctx context.Context, request *objectGRPC.HeadRequest) (*objectGRPC.HeadResponse, error) {
-	if s.err != nil {
-		return nil, s.err
-	}
-
-	// verify request structure
-	if err := signature.VerifyServiceMessage(
-		object.HeadRequestFromGRPCMessage(request),
-	); err != nil {
-		return nil, err
-	}
-
-	// sign response structure
-	if err := signature.SignServiceMessage(s.key, s.headResp); err != nil {
-		return nil, err
-	}
-
-	return object.HeadResponseToGRPCMessage(s.headResp), nil
-}
-
-func (s *testGRPCServer) Search(request *objectGRPC.SearchRequest, server objectGRPC.ObjectService_SearchServer) error {
-	panic("implement me")
-}
-
-func (s *testGRPCServer) GetRange(request *objectGRPC.GetRangeRequest, server objectGRPC.ObjectService_GetRangeServer) error {
-	panic("implement me")
-}
-
-func (s *testGRPCServer) GetRangeHash(ctx context.Context, request *objectGRPC.GetRangeHashRequest) (*objectGRPC.GetRangeHashResponse, error) {
-	if s.err != nil {
-		return nil, s.err
-	}
-
-	// verify request structure
-	if err := signature.VerifyServiceMessage(
-		object.GetRangeHashRequestFromGRPCMessage(request),
-	); err != nil {
-		return nil, err
-	}
-
-	// sign response structure
-	if err := signature.SignServiceMessage(s.key, s.getRangeHashResp); err != nil {
-		return nil, err
-	}
-
-	return object.GetRangeHashResponseToGRPCMessage(s.getRangeHashResp), nil
-}
-
-func testHeadRequest() *object.HeadRequest {
-	cid := new(refs.ContainerID)
-	cid.SetValue([]byte{1, 2, 3})
-
-	oid := new(refs.ObjectID)
-	oid.SetValue([]byte{4, 5, 6})
-
-	addr := new(refs.Address)
-	addr.SetContainerID(cid)
-	addr.SetObjectID(oid)
-
-	body := new(object.HeadRequestBody)
-	body.SetAddress(addr)
-
-	meta := new(session.RequestMetaHeader)
-	meta.SetTTL(1)
-	meta.SetXHeaders([]*session.XHeader{})
-
-	req := new(object.HeadRequest)
-	req.SetBody(body)
-	req.SetMetaHeader(meta)
-
-	return req
-}
-
-func testHeadResponse() *object.HeadResponse {
-	shortHdr := new(object.ShortHeader)
-	shortHdr.SetCreationEpoch(100)
-
-	body := new(object.HeadResponseBody)
-	body.SetHeaderPart(shortHdr)
-
-	meta := new(session.ResponseMetaHeader)
-	meta.SetTTL(1)
-	meta.SetXHeaders([]*session.XHeader{})
-
-	resp := new(object.HeadResponse)
-	resp.SetBody(body)
-	resp.SetMetaHeader(meta)
-
-	return resp
-}
-
-func testDeleteRequest() *object.DeleteRequest {
-	cid := new(refs.ContainerID)
-	cid.SetValue([]byte{1, 2, 3})
-
-	oid := new(refs.ObjectID)
-	oid.SetValue([]byte{4, 5, 6})
-
-	addr := new(refs.Address)
-	addr.SetContainerID(cid)
-	addr.SetObjectID(oid)
-
-	body := new(object.DeleteRequestBody)
-	body.SetAddress(addr)
-
-	meta := new(session.RequestMetaHeader)
-	meta.SetTTL(1)
-	meta.SetXHeaders([]*session.XHeader{})
-
-	req := new(object.DeleteRequest)
-	req.SetBody(body)
-	req.SetMetaHeader(meta)
-
-	return req
-}
-
-func testDeleteResponse() *object.DeleteResponse {
-	body := new(object.DeleteResponseBody)
-
-	meta := new(session.ResponseMetaHeader)
-	meta.SetTTL(1)
-	meta.SetXHeaders([]*session.XHeader{})
-
-	resp := new(object.DeleteResponse)
-	resp.SetBody(body)
-	resp.SetMetaHeader(meta)
-
-	return resp
-}
-
-func testGetRangeHashRequest() *object.GetRangeHashRequest {
-	cid := new(refs.ContainerID)
-	cid.SetValue([]byte{1, 2, 3})
-
-	oid := new(refs.ObjectID)
-	oid.SetValue([]byte{4, 5, 6})
-
-	addr := new(refs.Address)
-	addr.SetContainerID(cid)
-	addr.SetObjectID(oid)
-
-	body := new(object.GetRangeHashRequestBody)
-	body.SetAddress(addr)
-
-	meta := new(session.RequestMetaHeader)
-	meta.SetTTL(1)
-	meta.SetXHeaders([]*session.XHeader{})
-
-	req := new(object.GetRangeHashRequest)
-	req.SetBody(body)
-	req.SetMetaHeader(meta)
-
-	return req
-}
-
-func testGetRangeHashResponse() *object.GetRangeHashResponse {
-	body := new(object.GetRangeHashResponseBody)
-	body.SetHashList([][]byte{{7, 8, 9}})
-
-	meta := new(session.ResponseMetaHeader)
-	meta.SetTTL(1)
-	meta.SetXHeaders([]*session.XHeader{})
-
-	resp := new(object.GetRangeHashResponse)
-	resp.SetBody(body)
-	resp.SetMetaHeader(meta)
-
-	return resp
-}
-
-func TestGRPCClient_Head(t *testing.T) {
-	ctx := context.TODO()
-
-	cliKey := test.DecodeKey(0)
-	srvKey := test.DecodeKey(1)
-
-	t.Run("gRPC server error", func(t *testing.T) {
-		srvErr := errors.New("test server error")
-
-		srv := &testGRPCServer{
-			err: srvErr,
-		}
-
-		cli := &testGRPCClient{
-			server: srv,
-		}
-
-		c, err := object.NewClient(object.WithGRPCServiceClient(cli))
-		require.NoError(t, err)
-
-		resp, err := c.Head(ctx, new(object.HeadRequest))
-		require.True(t, errors.Is(err, srvErr))
-		require.Nil(t, resp)
-	})
-
-	t.Run("invalid request structure", func(t *testing.T) {
-		req := testHeadRequest()
-
-		require.Error(t, signature.VerifyServiceMessage(req))
-
-		c, err := object.NewClient(
-			object.WithGRPCServiceClient(
-				&testGRPCClient{
-					server: new(testGRPCServer),
-				},
-			),
-		)
-		require.NoError(t, err)
-
-		resp, err := c.Head(ctx, req)
-		require.Error(t, err)
-		require.Nil(t, resp)
-	})
-
-	t.Run("correct response", func(t *testing.T) {
-		req := testHeadRequest()
-
-		require.NoError(t, signature.SignServiceMessage(cliKey, req))
-
-		resp := testHeadResponse()
-
-		c, err := object.NewClient(
-			object.WithGRPCServiceClient(
-				&testGRPCClient{
-					server: &testGRPCServer{
-						key:      srvKey,
-						headResp: resp,
-					},
-				},
-			),
-		)
-		require.NoError(t, err)
-
-		r, err := c.Head(ctx, req)
-		require.NoError(t, err)
-
-		require.NoError(t, signature.VerifyServiceMessage(r))
-		require.Equal(t, resp.GetBody(), r.GetBody())
-		require.Equal(t, resp.GetMetaHeader(), r.GetMetaHeader())
-	})
-}
-
-func TestGRPCClient_Delete(t *testing.T) {
-	ctx := context.TODO()
-
-	cliKey := test.DecodeKey(0)
-	srvKey := test.DecodeKey(1)
-
-	t.Run("gRPC server error", func(t *testing.T) {
-		srvErr := errors.New("test server error")
-
-		srv := &testGRPCServer{
-			err: srvErr,
-		}
-
-		cli := &testGRPCClient{
-			server: srv,
-		}
-
-		c, err := object.NewClient(object.WithGRPCServiceClient(cli))
-		require.NoError(t, err)
-
-		resp, err := c.Delete(ctx, new(object.DeleteRequest))
-		require.True(t, errors.Is(err, srvErr))
-		require.Nil(t, resp)
-	})
-
-	t.Run("invalid request structure", func(t *testing.T) {
-		req := testDeleteRequest()
-
-		require.Error(t, signature.VerifyServiceMessage(req))
-
-		c, err := object.NewClient(
-			object.WithGRPCServiceClient(
-				&testGRPCClient{
-					server: new(testGRPCServer),
-				},
-			),
-		)
-		require.NoError(t, err)
-
-		resp, err := c.Delete(ctx, req)
-		require.Error(t, err)
-		require.Nil(t, resp)
-	})
-
-	t.Run("correct response", func(t *testing.T) {
-		req := testDeleteRequest()
-
-		require.NoError(t, signature.SignServiceMessage(cliKey, req))
-
-		resp := testDeleteResponse()
-
-		c, err := object.NewClient(
-			object.WithGRPCServiceClient(
-				&testGRPCClient{
-					server: &testGRPCServer{
-						key:     srvKey,
-						delResp: resp,
-					},
-				},
-			),
-		)
-		require.NoError(t, err)
-
-		r, err := c.Delete(ctx, req)
-		require.NoError(t, err)
-
-		require.NoError(t, signature.VerifyServiceMessage(r))
-		require.Equal(t, resp.GetBody(), r.GetBody())
-		require.Equal(t, resp.GetMetaHeader(), r.GetMetaHeader())
-	})
-}
-
-func TestGRPCClient_GetRangeHash(t *testing.T) {
-	ctx := context.TODO()
-
-	cliKey := test.DecodeKey(0)
-	srvKey := test.DecodeKey(1)
-
-	t.Run("gRPC server error", func(t *testing.T) {
-		srvErr := errors.New("test server error")
-
-		srv := &testGRPCServer{
-			err: srvErr,
-		}
-
-		cli := &testGRPCClient{
-			server: srv,
-		}
-
-		c, err := object.NewClient(object.WithGRPCServiceClient(cli))
-		require.NoError(t, err)
-
-		resp, err := c.GetRangeHash(ctx, new(object.GetRangeHashRequest))
-		require.True(t, errors.Is(err, srvErr))
-		require.Nil(t, resp)
-	})
-
-	t.Run("invalid request structure", func(t *testing.T) {
-		req := testGetRangeHashRequest()
-
-		require.Error(t, signature.VerifyServiceMessage(req))
-
-		c, err := object.NewClient(
-			object.WithGRPCServiceClient(
-				&testGRPCClient{
-					server: new(testGRPCServer),
-				},
-			),
-		)
-		require.NoError(t, err)
-
-		resp, err := c.GetRangeHash(ctx, req)
-		require.Error(t, err)
-		require.Nil(t, resp)
-	})
-
-	t.Run("correct response", func(t *testing.T) {
-		req := testGetRangeHashRequest()
-
-		require.NoError(t, signature.SignServiceMessage(cliKey, req))
-
-		resp := testGetRangeHashResponse()
-
-		c, err := object.NewClient(
-			object.WithGRPCServiceClient(
-				&testGRPCClient{
-					server: &testGRPCServer{
-						key:              srvKey,
-						getRangeHashResp: resp,
-					},
-				},
-			),
-		)
-		require.NoError(t, err)
-
-		r, err := c.GetRangeHash(ctx, req)
-		require.NoError(t, err)
-
-		require.NoError(t, signature.VerifyServiceMessage(r))
-		require.Equal(t, resp.GetBody(), r.GetBody())
-		require.Equal(t, resp.GetMetaHeader(), r.GetMetaHeader())
-	})
-}
diff --git a/v2/object/test/generate.go b/v2/object/test/generate.go
new file mode 100644
index 0000000..494645b
--- /dev/null
+++ b/v2/object/test/generate.go
@@ -0,0 +1,512 @@
+package objecttest
+
+import (
+	"github.com/nspcc-dev/neofs-api-go/v2/object"
+	refstest "github.com/nspcc-dev/neofs-api-go/v2/refs/test"
+	sessiontest "github.com/nspcc-dev/neofs-api-go/v2/session/test"
+)
+
+func GenerateShortHeader(empty bool) *object.ShortHeader {
+	m := new(object.ShortHeader)
+
+	if !empty {
+		m.SetObjectType(13)
+		m.SetCreationEpoch(100)
+		m.SetPayloadLength(12321)
+	}
+
+	m.SetVersion(refstest.GenerateVersion(empty))
+	m.SetOwnerID(refstest.GenerateOwnerID(empty))
+	m.SetHomomorphicHash(refstest.GenerateChecksum(empty))
+	m.SetPayloadHash(refstest.GenerateChecksum(empty))
+
+	return m
+}
+
+func GenerateAttribute(empty bool) *object.Attribute {
+	m := new(object.Attribute)
+
+	if !empty {
+		m.SetKey("object key")
+		m.SetValue("object value")
+	}
+
+	return m
+}
+
+func GenerateAttributes(empty bool) (res []*object.Attribute) {
+	if !empty {
+		res = append(res,
+			GenerateAttribute(false),
+			GenerateAttribute(false),
+		)
+	}
+
+	return
+}
+
+func GenerateSplitHeader(empty bool) *object.SplitHeader {
+	return generateSplitHeader(empty, true)
+}
+
+func generateSplitHeader(empty, withPar bool) *object.SplitHeader {
+	m := new(object.SplitHeader)
+
+	if !empty {
+		m.SetSplitID([]byte{1, 3, 5})
+	}
+
+	m.SetParent(refstest.GenerateObjectID(empty))
+	m.SetPrevious(refstest.GenerateObjectID(empty))
+	m.SetParentSignature(refstest.GenerateSignature(empty))
+	m.SetChildren(refstest.GenerateObjectIDs(empty))
+
+	if withPar {
+		m.SetParentHeader(generateHeader(empty, false))
+	}
+
+	return m
+}
+
+func GenerateHeader(empty bool) *object.Header {
+	return generateHeader(empty, true)
+}
+
+func generateHeader(empty, withSplit bool) *object.Header {
+	m := new(object.Header)
+
+	if !empty {
+		m.SetPayloadLength(777)
+		m.SetCreationEpoch(432)
+		m.SetObjectType(111)
+	}
+
+	m.SetVersion(refstest.GenerateVersion(empty))
+	m.SetPayloadHash(refstest.GenerateChecksum(empty))
+	m.SetOwnerID(refstest.GenerateOwnerID(empty))
+	m.SetHomomorphicHash(refstest.GenerateChecksum(empty))
+	m.SetContainerID(refstest.GenerateContainerID(empty))
+	m.SetSessionToken(sessiontest.GenerateSessionToken(empty))
+	m.SetAttributes(GenerateAttributes(empty))
+
+	if withSplit {
+		m.SetSplit(generateSplitHeader(empty, false))
+	}
+
+	return m
+}
+
+func GenerateHeaderWithSignature(empty bool) *object.HeaderWithSignature {
+	m := new(object.HeaderWithSignature)
+
+	m.SetSignature(refstest.GenerateSignature(empty))
+	m.SetHeader(GenerateHeader(empty))
+
+	return m
+}
+
+func GenerateObject(empty bool) *object.Object {
+	m := new(object.Object)
+
+	if !empty {
+		m.SetPayload([]byte{7, 8, 9})
+	}
+
+	m.SetObjectID(refstest.GenerateObjectID(empty))
+	m.SetSignature(refstest.GenerateSignature(empty))
+	m.SetHeader(GenerateHeader(empty))
+
+	return m
+}
+
+func GenerateSplitInfo(empty bool) *object.SplitInfo {
+	m := new(object.SplitInfo)
+
+	if !empty {
+		m.SetSplitID([]byte("splitID"))
+	}
+
+	m.SetLastPart(refstest.GenerateObjectID(empty))
+	m.SetLink(refstest.GenerateObjectID(empty))
+
+	return m
+}
+
+func GenerateGetRequestBody(empty bool) *object.GetRequestBody {
+	m := new(object.GetRequestBody)
+
+	if !empty {
+		m.SetRaw(true)
+	}
+
+	m.SetAddress(refstest.GenerateAddress(empty))
+
+	return m
+}
+
+func GenerateGetRequest(empty bool) *object.GetRequest {
+	m := new(object.GetRequest)
+
+	m.SetBody(GenerateGetRequestBody(empty))
+	m.SetMetaHeader(sessiontest.GenerateRequestMetaHeader(empty))
+	m.SetVerificationHeader(sessiontest.GenerateRequestVerificationHeader(empty))
+
+	return m
+}
+
+func GenerateGetObjectPartInit(empty bool) *object.GetObjectPartInit {
+	m := new(object.GetObjectPartInit)
+
+	m.SetObjectID(refstest.GenerateObjectID(empty))
+	m.SetSignature(refstest.GenerateSignature(empty))
+	m.SetHeader(GenerateHeader(empty))
+
+	return m
+}
+
+func GenerateGetObjectPartChunk(empty bool) *object.GetObjectPartChunk {
+	m := new(object.GetObjectPartChunk)
+
+	if !empty {
+		m.SetChunk([]byte("get chunk"))
+	}
+
+	return m
+}
+
+func GenerateGetResponseBody(empty bool) *object.GetResponseBody {
+	m := new(object.GetResponseBody)
+
+	m.SetObjectPart(GenerateGetObjectPartInit(empty))
+
+	return m
+}
+
+func GenerateGetResponse(empty bool) *object.GetResponse {
+	m := new(object.GetResponse)
+
+	m.SetBody(GenerateGetResponseBody(empty))
+	m.SetMetaHeader(sessiontest.GenerateResponseMetaHeader(empty))
+	m.SetVerificationHeader(sessiontest.GenerateResponseVerificationHeader(empty))
+
+	return m
+}
+
+func GeneratePutObjectPartInit(empty bool) *object.PutObjectPartInit {
+	m := new(object.PutObjectPartInit)
+
+	if !empty {
+		m.SetCopiesNumber(234)
+	}
+
+	m.SetObjectID(refstest.GenerateObjectID(empty))
+	m.SetSignature(refstest.GenerateSignature(empty))
+	m.SetHeader(GenerateHeader(empty))
+
+	return m
+}
+
+func GeneratePutObjectPartChunk(empty bool) *object.PutObjectPartChunk {
+	m := new(object.PutObjectPartChunk)
+
+	if !empty {
+		m.SetChunk([]byte("put chunk"))
+	}
+
+	return m
+}
+
+func GeneratePutRequestBody(empty bool) *object.PutRequestBody {
+	m := new(object.PutRequestBody)
+
+	m.SetObjectPart(GeneratePutObjectPartInit(empty))
+
+	return m
+}
+
+func GeneratePutRequest(empty bool) *object.PutRequest {
+	m := new(object.PutRequest)
+
+	m.SetBody(GeneratePutRequestBody(empty))
+	m.SetMetaHeader(sessiontest.GenerateRequestMetaHeader(empty))
+	m.SetVerificationHeader(sessiontest.GenerateRequestVerificationHeader(empty))
+
+	return m
+}
+
+func GeneratePutResponseBody(empty bool) *object.PutResponseBody {
+	m := new(object.PutResponseBody)
+
+	m.SetObjectID(refstest.GenerateObjectID(empty))
+
+	return m
+}
+
+func GeneratePutResponse(empty bool) *object.PutResponse {
+	m := new(object.PutResponse)
+
+	m.SetBody(GeneratePutResponseBody(empty))
+	m.SetMetaHeader(sessiontest.GenerateResponseMetaHeader(empty))
+	m.SetVerificationHeader(sessiontest.GenerateResponseVerificationHeader(empty))
+
+	return m
+}
+
+func GenerateDeleteRequestBody(empty bool) *object.DeleteRequestBody {
+	m := new(object.DeleteRequestBody)
+
+	m.SetAddress(refstest.GenerateAddress(empty))
+
+	return m
+}
+
+func GenerateDeleteRequest(empty bool) *object.DeleteRequest {
+	m := new(object.DeleteRequest)
+
+	m.SetBody(GenerateDeleteRequestBody(empty))
+	m.SetMetaHeader(sessiontest.GenerateRequestMetaHeader(empty))
+	m.SetVerificationHeader(sessiontest.GenerateRequestVerificationHeader(empty))
+
+	return m
+}
+
+func GenerateDeleteResponseBody(empty bool) *object.DeleteResponseBody {
+	m := new(object.DeleteResponseBody)
+
+	m.SetTombstone(refstest.GenerateAddress(empty))
+
+	return m
+}
+
+func GenerateDeleteResponse(empty bool) *object.DeleteResponse {
+	m := new(object.DeleteResponse)
+
+	m.SetBody(GenerateDeleteResponseBody(empty))
+	m.SetMetaHeader(sessiontest.GenerateResponseMetaHeader(empty))
+	m.SetVerificationHeader(sessiontest.GenerateResponseVerificationHeader(empty))
+
+	return m
+}
+
+func GenerateHeadRequestBody(empty bool) *object.HeadRequestBody {
+	m := new(object.HeadRequestBody)
+
+	if !empty {
+		m.SetRaw(true)
+		m.SetMainOnly(true)
+	}
+
+	m.SetAddress(refstest.GenerateAddress(empty))
+
+	return m
+}
+
+func GenerateHeadRequest(empty bool) *object.HeadRequest {
+	m := new(object.HeadRequest)
+
+	m.SetBody(GenerateHeadRequestBody(empty))
+	m.SetMetaHeader(sessiontest.GenerateRequestMetaHeader(empty))
+	m.SetVerificationHeader(sessiontest.GenerateRequestVerificationHeader(empty))
+
+	return m
+}
+
+func GenerateHeadResponseBody(empty bool) *object.HeadResponseBody {
+	m := new(object.HeadResponseBody)
+
+	m.SetHeaderPart(GenerateHeaderWithSignature(empty))
+
+	return m
+}
+
+func GenerateHeadResponse(empty bool) *object.HeadResponse {
+	m := new(object.HeadResponse)
+
+	m.SetBody(GenerateHeadResponseBody(empty))
+	m.SetMetaHeader(sessiontest.GenerateResponseMetaHeader(empty))
+	m.SetVerificationHeader(sessiontest.GenerateResponseVerificationHeader(empty))
+
+	return m
+}
+
+func GenerateSearchFilter(empty bool) *object.SearchFilter {
+	m := new(object.SearchFilter)
+
+	if !empty {
+		m.SetKey("search filter key")
+		m.SetValue("search filter val")
+		m.SetMatchType(987)
+	}
+
+	return m
+}
+
+func GenerateSearchFilters(empty bool) (res []*object.SearchFilter) {
+	if !empty {
+		res = append(res,
+			GenerateSearchFilter(false),
+			GenerateSearchFilter(false),
+		)
+	}
+
+	return
+}
+
+func GenerateSearchRequestBody(empty bool) *object.SearchRequestBody {
+	m := new(object.SearchRequestBody)
+
+	if !empty {
+		m.SetVersion(555)
+	}
+
+	m.SetContainerID(refstest.GenerateContainerID(empty))
+	m.SetFilters(GenerateSearchFilters(empty))
+
+	return m
+}
+
+func GenerateSearchRequest(empty bool) *object.SearchRequest {
+	m := new(object.SearchRequest)
+
+	m.SetBody(GenerateSearchRequestBody(empty))
+	m.SetMetaHeader(sessiontest.GenerateRequestMetaHeader(empty))
+	m.SetVerificationHeader(sessiontest.GenerateRequestVerificationHeader(empty))
+
+	return m
+}
+
+func GenerateSearchResponseBody(empty bool) *object.SearchResponseBody {
+	m := new(object.SearchResponseBody)
+
+	m.SetIDList(refstest.GenerateObjectIDs(empty))
+
+	return m
+}
+
+func GenerateSearchResponse(empty bool) *object.SearchResponse {
+	m := new(object.SearchResponse)
+
+	m.SetBody(GenerateSearchResponseBody(empty))
+	m.SetMetaHeader(sessiontest.GenerateResponseMetaHeader(empty))
+	m.SetVerificationHeader(sessiontest.GenerateResponseVerificationHeader(empty))
+
+	return m
+}
+
+func GenerateRange(empty bool) *object.Range {
+	m := new(object.Range)
+
+	if !empty {
+		m.SetLength(11)
+		m.SetOffset(22)
+	}
+
+	return m
+}
+
+func GenerateRanges(empty bool) (res []*object.Range) {
+	if !empty {
+		res = append(res,
+			GenerateRange(false),
+			GenerateRange(false),
+		)
+	}
+
+	return
+}
+
+func GenerateGetRangeRequestBody(empty bool) *object.GetRangeRequestBody {
+	m := new(object.GetRangeRequestBody)
+
+	if !empty {
+		m.SetRaw(true)
+	}
+
+	m.SetAddress(refstest.GenerateAddress(empty))
+	m.SetRange(GenerateRange(empty))
+
+	return m
+}
+
+func GenerateGetRangeRequest(empty bool) *object.GetRangeRequest {
+	m := new(object.GetRangeRequest)
+
+	m.SetBody(GenerateGetRangeRequestBody(empty))
+	m.SetMetaHeader(sessiontest.GenerateRequestMetaHeader(empty))
+	m.SetVerificationHeader(sessiontest.GenerateRequestVerificationHeader(empty))
+
+	return m
+}
+
+func GenerateGetRangePartChunk(empty bool) *object.GetRangePartChunk {
+	m := new(object.GetRangePartChunk)
+
+	if !empty {
+		m.SetChunk([]byte("get range chunk"))
+	}
+
+	return m
+}
+
+func GenerateGetRangeResponseBody(empty bool) *object.GetRangeResponseBody {
+	m := new(object.GetRangeResponseBody)
+
+	m.SetRangePart(GenerateGetRangePartChunk(empty))
+
+	return m
+}
+
+func GenerateGetRangeResponse(empty bool) *object.GetRangeResponse {
+	m := new(object.GetRangeResponse)
+
+	m.SetBody(GenerateGetRangeResponseBody(empty))
+	m.SetMetaHeader(sessiontest.GenerateResponseMetaHeader(empty))
+	m.SetVerificationHeader(sessiontest.GenerateResponseVerificationHeader(empty))
+
+	return m
+}
+
+func GenerateGetRangeHashRequestBody(empty bool) *object.GetRangeHashRequestBody {
+	m := new(object.GetRangeHashRequestBody)
+
+	if !empty {
+		m.SetSalt([]byte("range hash salt"))
+		m.SetType(455)
+	}
+
+	m.SetAddress(refstest.GenerateAddress(empty))
+	m.SetRanges(GenerateRanges(empty))
+
+	return m
+}
+
+func GenerateGetRangeHashRequest(empty bool) *object.GetRangeHashRequest {
+	m := new(object.GetRangeHashRequest)
+
+	m.SetBody(GenerateGetRangeHashRequestBody(empty))
+	m.SetMetaHeader(sessiontest.GenerateRequestMetaHeader(empty))
+	m.SetVerificationHeader(sessiontest.GenerateRequestVerificationHeader(empty))
+
+	return m
+}
+
+func GenerateGetRangeHashResponseBody(empty bool) *object.GetRangeHashResponseBody {
+	m := new(object.GetRangeHashResponseBody)
+
+	if !empty {
+		m.SetType(678)
+		m.SetHashList([][]byte{{1}, {2}})
+	}
+
+	return m
+}
+
+func GenerateGetRangeHashResponse(empty bool) *object.GetRangeHashResponse {
+	m := new(object.GetRangeHashResponse)
+
+	m.SetBody(GenerateGetRangeHashResponseBody(empty))
+	m.SetMetaHeader(sessiontest.GenerateResponseMetaHeader(empty))
+	m.SetVerificationHeader(sessiontest.GenerateResponseVerificationHeader(empty))
+
+	return m
+}
diff --git a/v2/object/types.go b/v2/object/types.go
index bed48d4..6b1dcc3 100644
--- a/v2/object/types.go
+++ b/v2/object/types.go
@@ -107,6 +107,12 @@ type GetObjectPartChunk struct {
 	chunk []byte
 }
 
+type GetRequest struct {
+	body *GetRequestBody
+
+	session.RequestHeaders
+}
+
 type GetResponseBody struct {
 	objPart GetObjectPart
 }
@@ -129,22 +135,52 @@ type PutObjectPartChunk struct {
 	chunk []byte
 }
 
+type GetResponse struct {
+	body *GetResponseBody
+
+	session.ResponseHeaders
+}
+
 type PutRequestBody struct {
 	objPart PutObjectPart
 }
 
+type PutRequest struct {
+	body *PutRequestBody
+
+	session.RequestHeaders
+}
+
 type PutResponseBody struct {
 	id *refs.ObjectID
 }
 
+type PutResponse struct {
+	body *PutResponseBody
+
+	session.ResponseHeaders
+}
+
 type DeleteRequestBody struct {
 	addr *refs.Address
 }
 
+type DeleteRequest struct {
+	body *DeleteRequestBody
+
+	session.RequestHeaders
+}
+
 type DeleteResponseBody struct {
 	tombstone *refs.Address
 }
 
+type DeleteResponse struct {
+	body *DeleteResponseBody
+
+	session.ResponseHeaders
+}
+
 type HeadRequestBody struct {
 	addr *refs.Address
 
@@ -155,10 +191,22 @@ type GetHeaderPart interface {
 	getHeaderPart()
 }
 
+type HeadRequest struct {
+	body *HeadRequestBody
+
+	session.RequestHeaders
+}
+
 type HeadResponseBody struct {
 	hdrPart GetHeaderPart
 }
 
+type HeadResponse struct {
+	body *HeadResponseBody
+
+	session.ResponseHeaders
+}
+
 type SearchFilter struct {
 	matchType MatchType
 
@@ -173,10 +221,22 @@ type SearchRequestBody struct {
 	filters []*SearchFilter
 }
 
+type SearchRequest struct {
+	body *SearchRequestBody
+
+	session.RequestHeaders
+}
+
 type SearchResponseBody struct {
 	idList []*refs.ObjectID
 }
 
+type SearchResponse struct {
+	body *SearchResponseBody
+
+	session.ResponseHeaders
+}
+
 type Range struct {
 	off, len uint64
 }
@@ -189,6 +249,12 @@ type GetRangeRequestBody struct {
 	raw bool
 }
 
+type GetRangeRequest struct {
+	body *GetRangeRequestBody
+
+	session.RequestHeaders
+}
+
 type GetRangePart interface {
 	getRangePart()
 }
@@ -201,6 +267,12 @@ type GetRangeResponseBody struct {
 	rngPart GetRangePart
 }
 
+type GetRangeResponse struct {
+	body *GetRangeResponseBody
+
+	session.ResponseHeaders
+}
+
 type GetRangeHashRequestBody struct {
 	addr *refs.Address
 
@@ -211,12 +283,24 @@ type GetRangeHashRequestBody struct {
 	typ refs.ChecksumType
 }
 
+type GetRangeHashRequest struct {
+	body *GetRangeHashRequestBody
+
+	session.RequestHeaders
+}
+
 type GetRangeHashResponseBody struct {
 	typ refs.ChecksumType
 
 	hashList [][]byte
 }
 
+type GetRangeHashResponse struct {
+	body *GetRangeHashResponseBody
+
+	session.ResponseHeaders
+}
+
 const (
 	TypeRegular Type = iota
 	TypeTombstone
@@ -772,34 +856,6 @@ func (r *GetRequest) SetBody(v *GetRequestBody) {
 	}
 }
 
-func (r *GetRequest) GetMetaHeader() *session.RequestMetaHeader {
-	if r != nil {
-		return r.metaHeader
-	}
-
-	return nil
-}
-
-func (r *GetRequest) SetMetaHeader(v *session.RequestMetaHeader) {
-	if r != nil {
-		r.metaHeader = v
-	}
-}
-
-func (r *GetRequest) GetVerificationHeader() *session.RequestVerificationHeader {
-	if r != nil {
-		return r.verifyHeader
-	}
-
-	return nil
-}
-
-func (r *GetRequest) SetVerificationHeader(v *session.RequestVerificationHeader) {
-	if r != nil {
-		r.verifyHeader = v
-	}
-}
-
 func (r *GetObjectPartInit) GetObjectID() *refs.ObjectID {
 	if r != nil {
 		return r.id
@@ -888,34 +944,6 @@ func (r *GetResponse) SetBody(v *GetResponseBody) {
 	}
 }
 
-func (r *GetResponse) GetMetaHeader() *session.ResponseMetaHeader {
-	if r != nil {
-		return r.metaHeader
-	}
-
-	return nil
-}
-
-func (r *GetResponse) SetMetaHeader(v *session.ResponseMetaHeader) {
-	if r != nil {
-		r.metaHeader = v
-	}
-}
-
-func (r *GetResponse) GetVerificationHeader() *session.ResponseVerificationHeader {
-	if r != nil {
-		return r.verifyHeader
-	}
-
-	return nil
-}
-
-func (r *GetResponse) SetVerificationHeader(v *session.ResponseVerificationHeader) {
-	if r != nil {
-		r.verifyHeader = v
-	}
-}
-
 func (r *PutObjectPartInit) GetObjectID() *refs.ObjectID {
 	if r != nil {
 		return r.id
@@ -1018,34 +1046,6 @@ func (r *PutRequest) SetBody(v *PutRequestBody) {
 	}
 }
 
-func (r *PutRequest) GetMetaHeader() *session.RequestMetaHeader {
-	if r != nil {
-		return r.metaHeader
-	}
-
-	return nil
-}
-
-func (r *PutRequest) SetMetaHeader(v *session.RequestMetaHeader) {
-	if r != nil {
-		r.metaHeader = v
-	}
-}
-
-func (r *PutRequest) GetVerificationHeader() *session.RequestVerificationHeader {
-	if r != nil {
-		return r.verifyHeader
-	}
-
-	return nil
-}
-
-func (r *PutRequest) SetVerificationHeader(v *session.RequestVerificationHeader) {
-	if r != nil {
-		r.verifyHeader = v
-	}
-}
-
 func (r *PutResponseBody) GetObjectID() *refs.ObjectID {
 	if r != nil {
 		return r.id
@@ -1074,34 +1074,6 @@ func (r *PutResponse) SetBody(v *PutResponseBody) {
 	}
 }
 
-func (r *PutResponse) GetMetaHeader() *session.ResponseMetaHeader {
-	if r != nil {
-		return r.metaHeader
-	}
-
-	return nil
-}
-
-func (r *PutResponse) SetMetaHeader(v *session.ResponseMetaHeader) {
-	if r != nil {
-		r.metaHeader = v
-	}
-}
-
-func (r *PutResponse) GetVerificationHeader() *session.ResponseVerificationHeader {
-	if r != nil {
-		return r.verifyHeader
-	}
-
-	return nil
-}
-
-func (r *PutResponse) SetVerificationHeader(v *session.ResponseVerificationHeader) {
-	if r != nil {
-		r.verifyHeader = v
-	}
-}
-
 func (r *DeleteRequestBody) GetAddress() *refs.Address {
 	if r != nil {
 		return r.addr
@@ -1130,34 +1102,6 @@ func (r *DeleteRequest) SetBody(v *DeleteRequestBody) {
 	}
 }
 
-func (r *DeleteRequest) GetMetaHeader() *session.RequestMetaHeader {
-	if r != nil {
-		return r.metaHeader
-	}
-
-	return nil
-}
-
-func (r *DeleteRequest) SetMetaHeader(v *session.RequestMetaHeader) {
-	if r != nil {
-		r.metaHeader = v
-	}
-}
-
-func (r *DeleteRequest) GetVerificationHeader() *session.RequestVerificationHeader {
-	if r != nil {
-		return r.verifyHeader
-	}
-
-	return nil
-}
-
-func (r *DeleteRequest) SetVerificationHeader(v *session.RequestVerificationHeader) {
-	if r != nil {
-		r.verifyHeader = v
-	}
-}
-
 // GetTombstone returns tombstone address.
 func (r *DeleteResponseBody) GetTombstone() *refs.Address {
 	if r != nil {
@@ -1188,34 +1132,6 @@ func (r *DeleteResponse) SetBody(v *DeleteResponseBody) {
 	}
 }
 
-func (r *DeleteResponse) GetMetaHeader() *session.ResponseMetaHeader {
-	if r != nil {
-		return r.metaHeader
-	}
-
-	return nil
-}
-
-func (r *DeleteResponse) SetMetaHeader(v *session.ResponseMetaHeader) {
-	if r != nil {
-		r.metaHeader = v
-	}
-}
-
-func (r *DeleteResponse) GetVerificationHeader() *session.ResponseVerificationHeader {
-	if r != nil {
-		return r.verifyHeader
-	}
-
-	return nil
-}
-
-func (r *DeleteResponse) SetVerificationHeader(v *session.ResponseVerificationHeader) {
-	if r != nil {
-		r.verifyHeader = v
-	}
-}
-
 func (r *HeadRequestBody) GetAddress() *refs.Address {
 	if r != nil {
 		return r.addr
@@ -1272,34 +1188,6 @@ func (r *HeadRequest) SetBody(v *HeadRequestBody) {
 	}
 }
 
-func (r *HeadRequest) GetMetaHeader() *session.RequestMetaHeader {
-	if r != nil {
-		return r.metaHeader
-	}
-
-	return nil
-}
-
-func (r *HeadRequest) SetMetaHeader(v *session.RequestMetaHeader) {
-	if r != nil {
-		r.metaHeader = v
-	}
-}
-
-func (r *HeadRequest) GetVerificationHeader() *session.RequestVerificationHeader {
-	if r != nil {
-		return r.verifyHeader
-	}
-
-	return nil
-}
-
-func (r *HeadRequest) SetVerificationHeader(v *session.RequestVerificationHeader) {
-	if r != nil {
-		r.verifyHeader = v
-	}
-}
-
 func (r *HeadResponseBody) GetHeaderPart() GetHeaderPart {
 	if r != nil {
 		return r.hdrPart
@@ -1328,34 +1216,6 @@ func (r *HeadResponse) SetBody(v *HeadResponseBody) {
 	}
 }
 
-func (r *HeadResponse) GetMetaHeader() *session.ResponseMetaHeader {
-	if r != nil {
-		return r.metaHeader
-	}
-
-	return nil
-}
-
-func (r *HeadResponse) SetMetaHeader(v *session.ResponseMetaHeader) {
-	if r != nil {
-		r.metaHeader = v
-	}
-}
-
-func (r *HeadResponse) GetVerificationHeader() *session.ResponseVerificationHeader {
-	if r != nil {
-		return r.verifyHeader
-	}
-
-	return nil
-}
-
-func (r *HeadResponse) SetVerificationHeader(v *session.ResponseVerificationHeader) {
-	if r != nil {
-		r.verifyHeader = v
-	}
-}
-
 func (f *SearchFilter) GetMatchType() MatchType {
 	if f != nil {
 		return f.matchType
@@ -1454,34 +1314,6 @@ func (r *SearchRequest) SetBody(v *SearchRequestBody) {
 	}
 }
 
-func (r *SearchRequest) GetMetaHeader() *session.RequestMetaHeader {
-	if r != nil {
-		return r.metaHeader
-	}
-
-	return nil
-}
-
-func (r *SearchRequest) SetMetaHeader(v *session.RequestMetaHeader) {
-	if r != nil {
-		r.metaHeader = v
-	}
-}
-
-func (r *SearchRequest) GetVerificationHeader() *session.RequestVerificationHeader {
-	if r != nil {
-		return r.verifyHeader
-	}
-
-	return nil
-}
-
-func (r *SearchRequest) SetVerificationHeader(v *session.RequestVerificationHeader) {
-	if r != nil {
-		r.verifyHeader = v
-	}
-}
-
 func (r *SearchResponseBody) GetIDList() []*refs.ObjectID {
 	if r != nil {
 		return r.idList
@@ -1510,34 +1342,6 @@ func (r *SearchResponse) SetBody(v *SearchResponseBody) {
 	}
 }
 
-func (r *SearchResponse) GetMetaHeader() *session.ResponseMetaHeader {
-	if r != nil {
-		return r.metaHeader
-	}
-
-	return nil
-}
-
-func (r *SearchResponse) SetMetaHeader(v *session.ResponseMetaHeader) {
-	if r != nil {
-		r.metaHeader = v
-	}
-}
-
-func (r *SearchResponse) GetVerificationHeader() *session.ResponseVerificationHeader {
-	if r != nil {
-		return r.verifyHeader
-	}
-
-	return nil
-}
-
-func (r *SearchResponse) SetVerificationHeader(v *session.ResponseVerificationHeader) {
-	if r != nil {
-		r.verifyHeader = v
-	}
-}
-
 func (r *Range) GetOffset() uint64 {
 	if r != nil {
 		return r.off
@@ -1622,34 +1426,6 @@ func (r *GetRangeRequest) SetBody(v *GetRangeRequestBody) {
 	}
 }
 
-func (r *GetRangeRequest) GetMetaHeader() *session.RequestMetaHeader {
-	if r != nil {
-		return r.metaHeader
-	}
-
-	return nil
-}
-
-func (r *GetRangeRequest) SetMetaHeader(v *session.RequestMetaHeader) {
-	if r != nil {
-		r.metaHeader = v
-	}
-}
-
-func (r *GetRangeRequest) GetVerificationHeader() *session.RequestVerificationHeader {
-	if r != nil {
-		return r.verifyHeader
-	}
-
-	return nil
-}
-
-func (r *GetRangeRequest) SetVerificationHeader(v *session.RequestVerificationHeader) {
-	if r != nil {
-		r.verifyHeader = v
-	}
-}
-
 func (r *GetRangePartChunk) GetChunk() []byte {
 	if r != nil {
 		return r.chunk
@@ -1694,34 +1470,6 @@ func (r *GetRangeResponse) SetBody(v *GetRangeResponseBody) {
 	}
 }
 
-func (r *GetRangeResponse) GetMetaHeader() *session.ResponseMetaHeader {
-	if r != nil {
-		return r.metaHeader
-	}
-
-	return nil
-}
-
-func (r *GetRangeResponse) SetMetaHeader(v *session.ResponseMetaHeader) {
-	if r != nil {
-		r.metaHeader = v
-	}
-}
-
-func (r *GetRangeResponse) GetVerificationHeader() *session.ResponseVerificationHeader {
-	if r != nil {
-		return r.verifyHeader
-	}
-
-	return nil
-}
-
-func (r *GetRangeResponse) SetVerificationHeader(v *session.ResponseVerificationHeader) {
-	if r != nil {
-		r.verifyHeader = v
-	}
-}
-
 func (r *GetRangeHashRequestBody) GetAddress() *refs.Address {
 	if r != nil {
 		return r.addr
@@ -1792,34 +1540,6 @@ func (r *GetRangeHashRequest) SetBody(v *GetRangeHashRequestBody) {
 	}
 }
 
-func (r *GetRangeHashRequest) GetMetaHeader() *session.RequestMetaHeader {
-	if r != nil {
-		return r.metaHeader
-	}
-
-	return nil
-}
-
-func (r *GetRangeHashRequest) SetMetaHeader(v *session.RequestMetaHeader) {
-	if r != nil {
-		r.metaHeader = v
-	}
-}
-
-func (r *GetRangeHashRequest) GetVerificationHeader() *session.RequestVerificationHeader {
-	if r != nil {
-		return r.verifyHeader
-	}
-
-	return nil
-}
-
-func (r *GetRangeHashRequest) SetVerificationHeader(v *session.RequestVerificationHeader) {
-	if r != nil {
-		r.verifyHeader = v
-	}
-}
-
 func (r *GetRangeHashResponseBody) GetType() refs.ChecksumType {
 	if r != nil {
 		return r.typ
@@ -1861,31 +1581,3 @@ func (r *GetRangeHashResponse) SetBody(v *GetRangeHashResponseBody) {
 		r.body = v
 	}
 }
-
-func (r *GetRangeHashResponse) GetMetaHeader() *session.ResponseMetaHeader {
-	if r != nil {
-		return r.metaHeader
-	}
-
-	return nil
-}
-
-func (r *GetRangeHashResponse) SetMetaHeader(v *session.ResponseMetaHeader) {
-	if r != nil {
-		r.metaHeader = v
-	}
-}
-
-func (r *GetRangeHashResponse) GetVerificationHeader() *session.ResponseVerificationHeader {
-	if r != nil {
-		return r.verifyHeader
-	}
-
-	return nil
-}
-
-func (r *GetRangeHashResponse) SetVerificationHeader(v *session.ResponseVerificationHeader) {
-	if r != nil {
-		r.verifyHeader = v
-	}
-}
diff --git a/v2/refs/convert.go b/v2/refs/convert.go
index 673fbf0..3cd87d2 100644
--- a/v2/refs/convert.go
+++ b/v2/refs/convert.go
@@ -1,221 +1,278 @@
 package refs
 
 import (
+	"github.com/nspcc-dev/neofs-api-go/rpc/grpc"
+	"github.com/nspcc-dev/neofs-api-go/rpc/message"
 	refs "github.com/nspcc-dev/neofs-api-go/v2/refs/grpc"
 )
 
-func OwnerIDToGRPCMessage(o *OwnerID) *refs.OwnerID {
-	if o == nil {
-		return nil
+func (o *OwnerID) ToGRPCMessage() grpc.Message {
+	var m *refs.OwnerID
+
+	if o != nil {
+		m = new(refs.OwnerID)
+
+		m.SetValue(o.val)
 	}
 
-	m := new(refs.OwnerID)
-
-	m.SetValue(o.GetValue())
-
 	return m
 }
 
-func OwnerIDFromGRPCMessage(m *refs.OwnerID) *OwnerID {
-	if m == nil {
-		return nil
+func (o *OwnerID) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*refs.OwnerID)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
 	}
 
-	o := new(OwnerID)
+	o.val = v.GetValue()
 
-	o.SetValue(m.GetValue())
-
-	return o
+	return nil
 }
 
-func ContainerIDToGRPCMessage(c *ContainerID) *refs.ContainerID {
-	if c == nil {
-		return nil
+func (c *ContainerID) ToGRPCMessage() grpc.Message {
+	var m *refs.ContainerID
+
+	if c != nil {
+		m = new(refs.ContainerID)
+
+		m.SetValue(c.val)
 	}
 
-	m := new(refs.ContainerID)
-
-	m.SetValue(c.GetValue())
-
 	return m
 }
 
-func ContainerIDFromGRPCMessage(m *refs.ContainerID) *ContainerID {
-	if m == nil {
-		return nil
+func (c *ContainerID) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*refs.ContainerID)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
 	}
 
-	c := new(ContainerID)
+	c.val = v.GetValue()
 
-	c.SetValue(m.GetValue())
-
-	return c
+	return nil
 }
 
-func ObjectIDToGRPCMessage(o *ObjectID) *refs.ObjectID {
-	if o == nil {
-		return nil
+func ContainerIDsToGRPCMessage(ids []*ContainerID) (res []*refs.ContainerID) {
+	if ids != nil {
+		res = make([]*refs.ContainerID, 0, len(ids))
+
+		for i := range ids {
+			res = append(res, ids[i].ToGRPCMessage().(*refs.ContainerID))
+		}
 	}
 
-	m := new(refs.ObjectID)
+	return
+}
 
-	m.SetValue(o.GetValue())
+func ContainerIDsFromGRPCMessage(idsV2 []*refs.ContainerID) (res []*ContainerID, err error) {
+	if idsV2 != nil {
+		res = make([]*ContainerID, 0, len(idsV2))
+
+		for i := range idsV2 {
+			var id *ContainerID
+
+			if idsV2[i] != nil {
+				id = new(ContainerID)
+
+				err = id.FromGRPCMessage(idsV2[i])
+				if err != nil {
+					return
+				}
+			}
+
+			res = append(res, id)
+		}
+	}
+
+	return
+}
+
+func (o *ObjectID) ToGRPCMessage() grpc.Message {
+	var m *refs.ObjectID
+
+	if o != nil {
+		m = new(refs.ObjectID)
+
+		m.SetValue(o.val)
+	}
 
 	return m
 }
 
-func ObjectIDFromGRPCMessage(m *refs.ObjectID) *ObjectID {
-	if m == nil {
-		return nil
+func (o *ObjectID) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*refs.ObjectID)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
 	}
 
-	o := new(ObjectID)
+	o.val = v.GetValue()
 
-	o.SetValue(m.GetValue())
-
-	return o
+	return nil
 }
 
-func ObjectIDListToGRPCMessage(ids []*ObjectID) []*refs.ObjectID {
-	if ids == nil {
-		return nil
+func ObjectIDListToGRPCMessage(ids []*ObjectID) (res []*refs.ObjectID) {
+	if ids != nil {
+		res = make([]*refs.ObjectID, 0, len(ids))
+
+		for i := range ids {
+			res = append(res, ids[i].ToGRPCMessage().(*refs.ObjectID))
+		}
 	}
 
-	idsV2 := make([]*refs.ObjectID, 0, len(ids))
-
-	for i := range ids {
-		idsV2 = append(idsV2, ObjectIDToGRPCMessage(ids[i]))
-	}
-
-	return idsV2
+	return
 }
 
-func ObjectIDListFromGRPCMessage(idsV2 []*refs.ObjectID) []*ObjectID {
-	if idsV2 == nil {
-		return nil
+func ObjectIDListFromGRPCMessage(idsV2 []*refs.ObjectID) (res []*ObjectID, err error) {
+	if idsV2 != nil {
+		res = make([]*ObjectID, 0, len(idsV2))
+
+		for i := range idsV2 {
+			var id *ObjectID
+
+			if idsV2[i] != nil {
+				id = new(ObjectID)
+
+				err = id.FromGRPCMessage(idsV2[i])
+				if err != nil {
+					return
+				}
+			}
+
+			res = append(res, id)
+		}
 	}
 
-	ids := make([]*ObjectID, 0, len(idsV2))
-
-	for i := range idsV2 {
-		ids = append(ids, ObjectIDFromGRPCMessage(idsV2[i]))
-	}
-
-	return ids
+	return
 }
 
-func AddressToGRPCMessage(a *Address) *refs.Address {
-	if a == nil {
-		return nil
+func (a *Address) ToGRPCMessage() grpc.Message {
+	var m *refs.Address
+
+	if a != nil {
+		m = new(refs.Address)
+
+		m.SetContainerId(a.cid.ToGRPCMessage().(*refs.ContainerID))
+		m.SetObjectId(a.oid.ToGRPCMessage().(*refs.ObjectID))
 	}
 
-	m := new(refs.Address)
-
-	m.SetContainerId(
-		ContainerIDToGRPCMessage(a.GetContainerID()),
-	)
-
-	m.SetObjectId(
-		ObjectIDToGRPCMessage(a.GetObjectID()),
-	)
-
 	return m
 }
 
-func AddressFromGRPCMessage(m *refs.Address) *Address {
-	if m == nil {
-		return nil
+func (a *Address) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*refs.Address)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
 	}
 
-	a := new(Address)
+	var err error
 
-	a.SetContainerID(
-		ContainerIDFromGRPCMessage(m.GetContainerId()),
-	)
+	cid := v.GetContainerId()
+	if cid == nil {
+		a.cid = nil
+	} else {
+		if a.cid == nil {
+			a.cid = new(ContainerID)
+		}
 
-	a.SetObjectID(
-		ObjectIDFromGRPCMessage(m.GetObjectId()),
-	)
+		err = a.cid.FromGRPCMessage(cid)
+		if err != nil {
+			return err
+		}
+	}
 
-	return a
+	oid := v.GetObjectId()
+	if oid == nil {
+		a.oid = nil
+	} else {
+		if a.oid == nil {
+			a.oid = new(ObjectID)
+		}
+
+		err = a.oid.FromGRPCMessage(oid)
+	}
+
+	return err
 }
 
-func ChecksumToGRPCMessage(c *Checksum) *refs.Checksum {
-	if c == nil {
-		return nil
+func ChecksumTypeToGRPC(t ChecksumType) refs.ChecksumType {
+	return refs.ChecksumType(t)
+}
+
+func ChecksumTypeFromGRPC(t refs.ChecksumType) ChecksumType {
+	return ChecksumType(t)
+}
+
+func (c *Checksum) ToGRPCMessage() grpc.Message {
+	var m *refs.Checksum
+
+	if c != nil {
+		m = new(refs.Checksum)
+
+		m.SetChecksumType(ChecksumTypeToGRPC(c.typ))
+		m.SetSum(c.sum)
 	}
 
-	m := new(refs.Checksum)
-
-	m.SetChecksumType(refs.ChecksumType(c.GetType()))
-
-	m.SetSum(c.GetSum())
-
 	return m
 }
 
-func ChecksumFromGRPCMessage(m *refs.Checksum) *Checksum {
-	if m == nil {
-		return nil
+func (c *Checksum) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*refs.Checksum)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
 	}
 
-	c := new(Checksum)
+	c.typ = ChecksumTypeFromGRPC(v.GetType())
+	c.sum = v.GetSum()
 
-	c.SetType(ChecksumType(m.GetType()))
-
-	c.SetSum(m.GetSum())
-
-	return c
+	return nil
 }
 
-func VersionToGRPCMessage(v *Version) *refs.Version {
-	if v == nil {
-		return nil
+func (v *Version) ToGRPCMessage() grpc.Message {
+	var m *refs.Version
+
+	if v != nil {
+		m = new(refs.Version)
+
+		m.SetMajor(v.major)
+		m.SetMinor(v.minor)
 	}
 
-	msg := new(refs.Version)
-
-	msg.SetMajor(v.GetMajor())
-	msg.SetMinor(v.GetMinor())
-
-	return msg
-}
-
-func VersionFromGRPCMessage(m *refs.Version) *Version {
-	if m == nil {
-		return nil
-	}
-
-	v := new(Version)
-
-	v.SetMajor(m.GetMajor())
-	v.SetMinor(m.GetMinor())
-
-	return v
-}
-
-func SignatureToGRPCMessage(s *Signature) *refs.Signature {
-	if s == nil {
-		return nil
-	}
-
-	m := new(refs.Signature)
-
-	m.SetKey(s.GetKey())
-	m.SetSign(s.GetSign())
-
 	return m
 }
 
-func SignatureFromGRPCMessage(m *refs.Signature) *Signature {
-	if m == nil {
-		return nil
+func (v *Version) FromGRPCMessage(m grpc.Message) error {
+	ver, ok := m.(*refs.Version)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
 	}
 
-	s := new(Signature)
+	v.major = ver.GetMajor()
+	v.minor = ver.GetMinor()
 
-	s.SetKey(m.GetKey())
-	s.SetSign(m.GetSign())
-
-	return s
+	return nil
+}
+
+func (s *Signature) ToGRPCMessage() grpc.Message {
+	var m *refs.Signature
+
+	if s != nil {
+		m = new(refs.Signature)
+
+		m.SetKey(s.key)
+		m.SetSign(s.sign)
+	}
+
+	return m
+}
+
+func (s *Signature) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*refs.Signature)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, s)
+	}
+
+	s.key = v.GetKey()
+	s.sign = v.GetSign()
+
+	return nil
 }
diff --git a/v2/refs/json.go b/v2/refs/json.go
index bd68cd6..4197e2f 100644
--- a/v2/refs/json.go
+++ b/v2/refs/json.go
@@ -1,146 +1,62 @@
 package refs
 
 import (
+	"github.com/nspcc-dev/neofs-api-go/rpc/message"
 	refs "github.com/nspcc-dev/neofs-api-go/v2/refs/grpc"
-	"google.golang.org/protobuf/encoding/protojson"
 )
 
 func (a *Address) MarshalJSON() ([]byte, error) {
-	return protojson.MarshalOptions{
-		EmitUnpopulated: true,
-	}.Marshal(
-		AddressToGRPCMessage(a),
-	)
+	return message.MarshalJSON(a)
 }
 
 func (a *Address) UnmarshalJSON(data []byte) error {
-	msg := new(refs.Address)
-
-	if err := protojson.Unmarshal(data, msg); err != nil {
-		return err
-	}
-
-	*a = *AddressFromGRPCMessage(msg)
-
-	return nil
+	return message.UnmarshalJSON(a, data, new(refs.Address))
 }
 
 func (o *ObjectID) MarshalJSON() ([]byte, error) {
-	return protojson.MarshalOptions{
-		EmitUnpopulated: true,
-	}.Marshal(
-		ObjectIDToGRPCMessage(o),
-	)
+	return message.MarshalJSON(o)
 }
 
 func (o *ObjectID) UnmarshalJSON(data []byte) error {
-	msg := new(refs.ObjectID)
-
-	if err := protojson.Unmarshal(data, msg); err != nil {
-		return err
-	}
-
-	*o = *ObjectIDFromGRPCMessage(msg)
-
-	return nil
+	return message.UnmarshalJSON(o, data, new(refs.ObjectID))
 }
 
 func (c *ContainerID) MarshalJSON() ([]byte, error) {
-	return protojson.MarshalOptions{
-		EmitUnpopulated: true,
-	}.Marshal(
-		ContainerIDToGRPCMessage(c),
-	)
+	return message.MarshalJSON(c)
 }
 
 func (c *ContainerID) UnmarshalJSON(data []byte) error {
-	msg := new(refs.ContainerID)
-
-	if err := protojson.Unmarshal(data, msg); err != nil {
-		return err
-	}
-
-	*c = *ContainerIDFromGRPCMessage(msg)
-
-	return nil
+	return message.UnmarshalJSON(c, data, new(refs.ContainerID))
 }
 
 func (o *OwnerID) MarshalJSON() ([]byte, error) {
-	return protojson.MarshalOptions{
-		EmitUnpopulated: true,
-	}.Marshal(
-		OwnerIDToGRPCMessage(o),
-	)
+	return message.MarshalJSON(o)
 }
 
 func (o *OwnerID) UnmarshalJSON(data []byte) error {
-	msg := new(refs.OwnerID)
-
-	if err := protojson.Unmarshal(data, msg); err != nil {
-		return err
-	}
-
-	*o = *OwnerIDFromGRPCMessage(msg)
-
-	return nil
+	return message.UnmarshalJSON(o, data, new(refs.OwnerID))
 }
 
 func (v *Version) MarshalJSON() ([]byte, error) {
-	return protojson.MarshalOptions{
-		EmitUnpopulated: true,
-	}.Marshal(
-		VersionToGRPCMessage(v),
-	)
+	return message.MarshalJSON(v)
 }
 
 func (v *Version) UnmarshalJSON(data []byte) error {
-	msg := new(refs.Version)
-
-	if err := protojson.Unmarshal(data, msg); err != nil {
-		return err
-	}
-
-	*v = *VersionFromGRPCMessage(msg)
-
-	return nil
+	return message.UnmarshalJSON(v, data, new(refs.Version))
 }
 
 func (s *Signature) MarshalJSON() ([]byte, error) {
-	return protojson.MarshalOptions{
-		EmitUnpopulated: true,
-	}.Marshal(
-		SignatureToGRPCMessage(s),
-	)
+	return message.MarshalJSON(s)
 }
 
 func (s *Signature) UnmarshalJSON(data []byte) error {
-	msg := new(refs.Signature)
-
-	if err := protojson.Unmarshal(data, msg); err != nil {
-		return err
-	}
-
-	*s = *SignatureFromGRPCMessage(msg)
-
-	return nil
+	return message.UnmarshalJSON(s, data, new(refs.Signature))
 }
 
 func (c *Checksum) MarshalJSON() ([]byte, error) {
-	return protojson.MarshalOptions{
-		EmitUnpopulated: true,
-	}.Marshal(
-		ChecksumToGRPCMessage(c),
-	)
+	return message.MarshalJSON(c)
 }
 
 func (c *Checksum) UnmarshalJSON(data []byte) error {
-	msg := new(refs.Checksum)
-
-	if err := protojson.Unmarshal(data, msg); err != nil {
-		return err
-	}
-
-	*c = *ChecksumFromGRPCMessage(msg)
-
-	return nil
+	return message.UnmarshalJSON(c, data, new(refs.Checksum))
 }
diff --git a/v2/refs/json_test.go b/v2/refs/json_test.go
deleted file mode 100644
index b04df1a..0000000
--- a/v2/refs/json_test.go
+++ /dev/null
@@ -1,97 +0,0 @@
-package refs_test
-
-import (
-	"testing"
-
-	"github.com/nspcc-dev/neofs-api-go/v2/refs"
-	"github.com/stretchr/testify/require"
-)
-
-func TestAddressJSON(t *testing.T) {
-	a := generateAddress([]byte{1}, []byte{2})
-
-	data, err := a.MarshalJSON()
-	require.NoError(t, err)
-
-	a2 := new(refs.Address)
-	require.NoError(t, a2.UnmarshalJSON(data))
-
-	require.Equal(t, a, a2)
-}
-
-func TestObjectIDJSON(t *testing.T) {
-	o := new(refs.ObjectID)
-	o.SetValue([]byte{1})
-
-	data, err := o.MarshalJSON()
-	require.NoError(t, err)
-
-	o2 := new(refs.ObjectID)
-	require.NoError(t, o2.UnmarshalJSON(data))
-
-	require.Equal(t, o, o2)
-}
-
-func TestContainerIDJSON(t *testing.T) {
-	cid := new(refs.ContainerID)
-	cid.SetValue([]byte{1})
-
-	data, err := cid.MarshalJSON()
-	require.NoError(t, err)
-
-	cid2 := new(refs.ContainerID)
-	require.NoError(t, cid2.UnmarshalJSON(data))
-
-	require.Equal(t, cid, cid2)
-}
-
-func TestOwnerIDJSON(t *testing.T) {
-	o := new(refs.OwnerID)
-	o.SetValue([]byte{1})
-
-	data, err := o.MarshalJSON()
-	require.NoError(t, err)
-
-	o2 := new(refs.OwnerID)
-	require.NoError(t, o2.UnmarshalJSON(data))
-
-	require.Equal(t, o, o2)
-}
-
-func TestVersionSON(t *testing.T) {
-	v := generateVersion(1, 2)
-
-	data, err := v.MarshalJSON()
-	require.NoError(t, err)
-
-	v2 := new(refs.Version)
-	require.NoError(t, v2.UnmarshalJSON(data))
-
-	require.Equal(t, v, v2)
-}
-
-func TestSignatureSON(t *testing.T) {
-	s := generateSignature("key", "sig")
-
-	data, err := s.MarshalJSON()
-	require.NoError(t, err)
-
-	s2 := new(refs.Signature)
-	require.NoError(t, s2.UnmarshalJSON(data))
-
-	require.Equal(t, s, s2)
-}
-
-func TestChecksumJSON(t *testing.T) {
-	cs := new(refs.Checksum)
-	cs.SetType(refs.SHA256)
-	cs.SetSum([]byte{1, 2, 3})
-
-	data, err := cs.MarshalJSON()
-	require.NoError(t, err)
-
-	cs2 := new(refs.Checksum)
-	require.NoError(t, cs2.UnmarshalJSON(data))
-
-	require.Equal(t, cs, cs2)
-}
diff --git a/v2/refs/marshal.go b/v2/refs/marshal.go
index 28b4d4e..806d8e4 100644
--- a/v2/refs/marshal.go
+++ b/v2/refs/marshal.go
@@ -1,9 +1,9 @@
 package refs
 
 import (
+	"github.com/nspcc-dev/neofs-api-go/rpc/message"
 	"github.com/nspcc-dev/neofs-api-go/util/proto"
 	refs "github.com/nspcc-dev/neofs-api-go/v2/refs/grpc"
-	goproto "google.golang.org/protobuf/proto"
 )
 
 const (
@@ -52,14 +52,7 @@ func (o *OwnerID) StableSize() int {
 }
 
 func (o *OwnerID) Unmarshal(data []byte) error {
-	m := new(refs.OwnerID)
-	if err := goproto.Unmarshal(data, m); err != nil {
-		return err
-	}
-
-	*o = *OwnerIDFromGRPCMessage(m)
-
-	return nil
+	return message.Unmarshal(o, data, new(refs.OwnerID))
 }
 
 func (c *ContainerID) StableMarshal(buf []byte) ([]byte, error) {
@@ -88,14 +81,7 @@ func (c *ContainerID) StableSize() int {
 }
 
 func (c *ContainerID) Unmarshal(data []byte) error {
-	m := new(refs.ContainerID)
-	if err := goproto.Unmarshal(data, m); err != nil {
-		return err
-	}
-
-	*c = *ContainerIDFromGRPCMessage(m)
-
-	return nil
+	return message.Unmarshal(c, data, new(refs.ContainerID))
 }
 
 func (o *ObjectID) StableMarshal(buf []byte) ([]byte, error) {
@@ -151,14 +137,7 @@ func ObjectIDNestedListMarshal(fNum int64, buf []byte, ids []*ObjectID) (off int
 }
 
 func (o *ObjectID) Unmarshal(data []byte) error {
-	m := new(refs.ObjectID)
-	if err := goproto.Unmarshal(data, m); err != nil {
-		return err
-	}
-
-	*o = *ObjectIDFromGRPCMessage(m)
-
-	return nil
+	return message.Unmarshal(o, data, new(refs.ObjectID))
 }
 
 func (a *Address) StableMarshal(buf []byte) ([]byte, error) {
@@ -203,14 +182,7 @@ func (a *Address) StableSize() (size int) {
 }
 
 func (a *Address) Unmarshal(data []byte) error {
-	addrGRPC := new(refs.Address)
-	if err := goproto.Unmarshal(data, addrGRPC); err != nil {
-		return err
-	}
-
-	*a = *AddressFromGRPCMessage(addrGRPC)
-
-	return nil
+	return message.Unmarshal(a, data, new(refs.Address))
 }
 
 func (c *Checksum) StableMarshal(buf []byte) ([]byte, error) {
@@ -254,14 +226,7 @@ func (c *Checksum) StableSize() (size int) {
 }
 
 func (c *Checksum) Unmarshal(data []byte) error {
-	m := new(refs.Checksum)
-	if err := goproto.Unmarshal(data, m); err != nil {
-		return err
-	}
-
-	*c = *ChecksumFromGRPCMessage(m)
-
-	return nil
+	return message.Unmarshal(c, data, new(refs.Checksum))
 }
 
 func (s *Signature) StableMarshal(buf []byte) ([]byte, error) {
@@ -305,14 +270,7 @@ func (s *Signature) StableSize() (size int) {
 }
 
 func (s *Signature) Unmarshal(data []byte) error {
-	m := new(refs.Signature)
-	if err := goproto.Unmarshal(data, m); err != nil {
-		return err
-	}
-
-	*s = *SignatureFromGRPCMessage(m)
-
-	return nil
+	return message.Unmarshal(s, data, new(refs.Signature))
 }
 
 func (v *Version) StableMarshal(buf []byte) ([]byte, error) {
@@ -356,12 +314,5 @@ func (v *Version) StableSize() (size int) {
 }
 
 func (v *Version) Unmarshal(data []byte) error {
-	m := new(refs.Version)
-	if err := goproto.Unmarshal(data, m); err != nil {
-		return err
-	}
-
-	*v = *VersionFromGRPCMessage(m)
-
-	return nil
+	return message.Unmarshal(v, data, new(refs.Version))
 }
diff --git a/v2/refs/marshal_test.go b/v2/refs/marshal_test.go
deleted file mode 100644
index 3bc2eb2..0000000
--- a/v2/refs/marshal_test.go
+++ /dev/null
@@ -1,146 +0,0 @@
-package refs_test
-
-import (
-	"testing"
-
-	"github.com/nspcc-dev/neofs-api-go/v2/refs"
-	"github.com/stretchr/testify/require"
-)
-
-func TestOwnerID_StableMarshal(t *testing.T) {
-	ownerFrom := new(refs.OwnerID)
-
-	t.Run("non empty", func(t *testing.T) {
-		ownerFrom.SetValue([]byte("Owner ID"))
-
-		wire, err := ownerFrom.StableMarshal(nil)
-		require.NoError(t, err)
-
-		ownerTo := new(refs.OwnerID)
-		require.NoError(t, ownerTo.Unmarshal(wire))
-
-		require.Equal(t, ownerFrom, ownerTo)
-	})
-}
-
-func TestContainerID_StableMarshal(t *testing.T) {
-	cnrFrom := new(refs.ContainerID)
-
-	t.Run("non empty", func(t *testing.T) {
-		cnrFrom.SetValue([]byte("Container ID"))
-
-		wire, err := cnrFrom.StableMarshal(nil)
-		require.NoError(t, err)
-
-		cnrTo := new(refs.ContainerID)
-		require.NoError(t, cnrTo.Unmarshal(wire))
-
-		require.Equal(t, cnrFrom, cnrTo)
-	})
-}
-
-func TestObjectID_StableMarshal(t *testing.T) {
-	objectIDFrom := new(refs.ObjectID)
-
-	t.Run("non empty", func(t *testing.T) {
-		objectIDFrom.SetValue([]byte("Object ID"))
-
-		wire, err := objectIDFrom.StableMarshal(nil)
-		require.NoError(t, err)
-
-		objectIDTo := new(refs.ObjectID)
-		require.NoError(t, objectIDTo.Unmarshal(wire))
-
-		require.Equal(t, objectIDFrom, objectIDTo)
-	})
-}
-
-func TestAddress_StableMarshal(t *testing.T) {
-	cid := []byte("Container ID")
-	oid := []byte("Object ID")
-
-	addressFrom := generateAddress(cid, oid)
-
-	t.Run("non empty", func(t *testing.T) {
-		wire, err := addressFrom.StableMarshal(nil)
-		require.NoError(t, err)
-
-		addressTo := new(refs.Address)
-		require.NoError(t, addressTo.Unmarshal(wire))
-
-		require.Equal(t, addressFrom, addressTo)
-	})
-}
-
-func TestChecksum_StableMarshal(t *testing.T) {
-	checksumFrom := new(refs.Checksum)
-
-	t.Run("non empty", func(t *testing.T) {
-		checksumFrom.SetType(refs.TillichZemor)
-		checksumFrom.SetSum([]byte("Homomorphic Hash"))
-
-		wire, err := checksumFrom.StableMarshal(nil)
-		require.NoError(t, err)
-
-		checksumTo := new(refs.Checksum)
-		require.NoError(t, checksumTo.Unmarshal(wire))
-
-		require.Equal(t, checksumFrom, checksumTo)
-	})
-}
-
-func TestSignature_StableMarshal(t *testing.T) {
-	signatureFrom := generateSignature("Public Key", "Signature")
-
-	t.Run("non empty", func(t *testing.T) {
-		wire, err := signatureFrom.StableMarshal(nil)
-		require.NoError(t, err)
-
-		signatureTo := new(refs.Signature)
-		require.NoError(t, signatureTo.Unmarshal(wire))
-
-		require.Equal(t, signatureFrom, signatureTo)
-	})
-}
-
-func TestVersion_StableMarshal(t *testing.T) {
-	versionFrom := generateVersion(2, 0)
-
-	t.Run("non empty", func(t *testing.T) {
-		wire, err := versionFrom.StableMarshal(nil)
-		require.NoError(t, err)
-
-		versionTo := new(refs.Version)
-		require.NoError(t, versionTo.Unmarshal(wire))
-
-		require.Equal(t, versionFrom, versionTo)
-	})
-}
-
-func generateSignature(k, v string) *refs.Signature {
-	sig := new(refs.Signature)
-	sig.SetKey([]byte(k))
-	sig.SetSign([]byte(v))
-
-	return sig
-}
-
-func generateVersion(maj, min uint32) *refs.Version {
-	version := new(refs.Version)
-	version.SetMajor(maj)
-	version.SetMinor(min)
-
-	return version
-}
-
-func generateAddress(bCid, bOid []byte) *refs.Address {
-	addr := new(refs.Address)
-
-	cid := new(refs.ContainerID)
-	cid.SetValue(bCid)
-
-	oid := new(refs.ObjectID)
-	oid.SetValue(bOid)
-
-	return addr
-}
diff --git a/v2/refs/message_test.go b/v2/refs/message_test.go
new file mode 100644
index 0000000..92eb6df
--- /dev/null
+++ b/v2/refs/message_test.go
@@ -0,0 +1,21 @@
+package refs_test
+
+import (
+	"testing"
+
+	"github.com/nspcc-dev/neofs-api-go/rpc/message"
+	messagetest "github.com/nspcc-dev/neofs-api-go/rpc/message/test"
+	refstest "github.com/nspcc-dev/neofs-api-go/v2/refs/test"
+)
+
+func TestMessageConvert(t *testing.T) {
+	messagetest.TestRPCMessage(t,
+		func(empty bool) message.Message { return refstest.GenerateOwnerID(empty) },
+		func(empty bool) message.Message { return refstest.GenerateObjectID(empty) },
+		func(empty bool) message.Message { return refstest.GenerateContainerID(empty) },
+		func(empty bool) message.Message { return refstest.GenerateAddress(empty) },
+		func(empty bool) message.Message { return refstest.GenerateChecksum(empty) },
+		func(empty bool) message.Message { return refstest.GenerateSignature(empty) },
+		func(empty bool) message.Message { return refstest.GenerateVersion(empty) },
+	)
+}
diff --git a/v2/refs/test/generate.go b/v2/refs/test/generate.go
new file mode 100644
index 0000000..1f90ce0
--- /dev/null
+++ b/v2/refs/test/generate.go
@@ -0,0 +1,101 @@
+package refstest
+
+import (
+	"github.com/nspcc-dev/neofs-api-go/v2/refs"
+)
+
+func GenerateVersion(empty bool) *refs.Version {
+	m := new(refs.Version)
+
+	if !empty {
+		m.SetMajor(2)
+		m.SetMinor(1)
+	}
+
+	return m
+}
+
+func GenerateOwnerID(empty bool) *refs.OwnerID {
+	m := new(refs.OwnerID)
+
+	if !empty {
+		m.SetValue([]byte{1, 2, 3})
+	}
+
+	return m
+}
+
+func GenerateAddress(empty bool) *refs.Address {
+	m := new(refs.Address)
+
+	m.SetObjectID(GenerateObjectID(empty))
+	m.SetContainerID(GenerateContainerID(empty))
+
+	return m
+}
+
+func GenerateObjectID(empty bool) *refs.ObjectID {
+	m := new(refs.ObjectID)
+
+	if !empty {
+		m.SetValue([]byte{1, 2, 3})
+	}
+
+	return m
+}
+
+func GenerateObjectIDs(empty bool) []*refs.ObjectID {
+	ids := make([]*refs.ObjectID, 0)
+
+	if !empty {
+		ids = append(ids,
+			GenerateObjectID(false),
+			GenerateObjectID(false),
+		)
+	}
+
+	return ids
+}
+
+func GenerateContainerID(empty bool) *refs.ContainerID {
+	m := new(refs.ContainerID)
+
+	if !empty {
+		m.SetValue([]byte{1, 2, 3})
+	}
+
+	return m
+}
+
+func GenerateContainerIDs(empty bool) (res []*refs.ContainerID) {
+	if !empty {
+		res = append(res,
+			GenerateContainerID(false),
+			GenerateContainerID(false),
+		)
+	}
+
+	return
+}
+
+func GenerateSignature(empty bool) *refs.Signature {
+	m := new(refs.Signature)
+
+	if !empty {
+		m.SetKey([]byte{1})
+		m.SetSign([]byte{2})
+	}
+
+	return m
+}
+
+func GenerateChecksum(empty bool) *refs.Checksum {
+	m := new(refs.Checksum)
+
+	if !empty {
+		m.SetType(1)
+		m.SetSum([]byte{1, 2, 3})
+	}
+
+	return m
+}
diff --git a/v2/rpc/accounting.go b/v2/rpc/accounting.go
new file mode 100644
index 0000000..0e031da
--- /dev/null
+++ b/v2/rpc/accounting.go
@@ -0,0 +1,29 @@
+package rpc
+
+import (
+	"github.com/nspcc-dev/neofs-api-go/rpc/client"
+	"github.com/nspcc-dev/neofs-api-go/rpc/common"
+	"github.com/nspcc-dev/neofs-api-go/v2/accounting"
+)
+
+const serviceAccounting = serviceNamePrefix + "accounting.AccountingService"
+
+const (
+	rpcAccountingBalance = "Balance"
+)
+
+// Balance executes AccountingService.Balance RPC.
+func Balance(
+	cli *client.Client,
+	req *accounting.BalanceRequest,
+	opts ...client.CallOption,
+) (*accounting.BalanceResponse, error) {
+	resp := new(accounting.BalanceResponse)
+
+	err := client.SendUnary(cli, common.CallMethodInfoUnary(serviceAccounting, rpcAccountingBalance), req, resp, opts...)
+	if err != nil {
+		return nil, err
+	}
+
+	return resp, nil
+}
diff --git a/v2/rpc/common.go b/v2/rpc/common.go
new file mode 100644
index 0000000..7d0bf56
--- /dev/null
+++ b/v2/rpc/common.go
@@ -0,0 +1,3 @@
+package rpc
+
+const serviceNamePrefix = "neo.fs.v2."
diff --git a/v2/rpc/container.go b/v2/rpc/container.go
new file mode 100644
index 0000000..82dbe8a
--- /dev/null
+++ b/v2/rpc/container.go
@@ -0,0 +1,131 @@
+package rpc
+
+import (
+	"github.com/nspcc-dev/neofs-api-go/rpc/client"
+	"github.com/nspcc-dev/neofs-api-go/rpc/common"
+	"github.com/nspcc-dev/neofs-api-go/v2/container"
+)
+
+const serviceContainer = serviceNamePrefix + "container.ContainerService"
+
+const (
+	rpcContainerPut       = "Put"
+	rpcContainerGet       = "Get"
+	rpcContainerDel       = "Delete"
+	rpcContainerList      = "List"
+	rpcContainerSetEACL   = "SetExtendedACL"
+	rpcContainerGetEACL   = "GetExtendedACL"
+	rpcContainerUsedSpace = "AnnounceUsedSpace"
+)
+
+// PutContainer executes ContainerService.Put RPC.
+func PutContainer(
+	cli *client.Client,
+	req *container.PutRequest,
+	opts ...client.CallOption,
+) (*container.PutResponse, error) {
+	resp := new(container.PutResponse)
+
+	err := client.SendUnary(cli, common.CallMethodInfoUnary(serviceContainer, rpcContainerPut), req, resp, opts...)
+	if err != nil {
+		return nil, err
+	}
+
+	return resp, nil
+}
+
+// GetContainer executes ContainerService.Get RPC.
+func GetContainer(
+	cli *client.Client,
+	req *container.GetRequest,
+	opts ...client.CallOption,
+) (*container.GetResponse, error) {
+	resp := new(container.GetResponse)
+
+	err := client.SendUnary(cli, common.CallMethodInfoUnary(serviceContainer, rpcContainerGet), req, resp, opts...)
+	if err != nil {
+		return nil, err
+	}
+
+	return resp, nil
+}
+
+// DeleteContainer executes ContainerService.Delete RPC.
+func DeleteContainer(
+	cli *client.Client,
+	req *container.DeleteRequest,
+	opts ...client.CallOption,
+) (*container.PutResponse, error) {
+	resp := new(container.PutResponse)
+
+	err := client.SendUnary(cli, common.CallMethodInfoUnary(serviceContainer, rpcContainerDel), req, resp, opts...)
+	if err != nil {
+		return nil, err
+	}
+
+	return resp, nil
+}
+
+// ListContainers executes ContainerService.List RPC.
+func ListContainers(
+	cli *client.Client,
+	req *container.ListRequest,
+	opts ...client.CallOption,
+) (*container.ListResponse, error) {
+	resp := new(container.ListResponse)
+
+	err := client.SendUnary(cli, common.CallMethodInfoUnary(serviceContainer, rpcContainerList), req, resp, opts...)
+	if err != nil {
+		return nil, err
+	}
+
+	return resp, nil
+}
+
+// SetEACL executes ContainerService.SetExtendedACL RPC.
+func SetEACL(
+	cli *client.Client,
+	req *container.SetExtendedACLRequest,
+	opts ...client.CallOption,
+) (*container.PutResponse, error) {
+	resp := new(container.PutResponse)
+
+	err := client.SendUnary(cli, common.CallMethodInfoUnary(serviceContainer, rpcContainerSetEACL), req, resp, opts...)
+	if err != nil {
+		return nil, err
+	}
+
+	return resp, nil
+}
+
+// GetEACL executes ContainerService.GetExtendedACL RPC.
+func GetEACL(
+	cli *client.Client,
+	req *container.GetExtendedACLRequest,
+	opts ...client.CallOption,
+) (*container.GetExtendedACLResponse, error) {
+	resp := new(container.GetExtendedACLResponse)
+
+	err := client.SendUnary(cli, common.CallMethodInfoUnary(serviceContainer, rpcContainerGetEACL), req, resp, opts...)
+	if err != nil {
+		return nil, err
+	}
+
+	return resp, nil
+}
+
+// AnnounceUsedSpace executes ContainerService.AnnounceUsedSpace RPC.
+func AnnounceUsedSpace(
+	cli *client.Client,
+	req *container.AnnounceUsedSpaceRequest,
+	opts ...client.CallOption,
+) (*container.PutResponse, error) {
+	resp := new(container.PutResponse)
+
+	err := client.SendUnary(cli, common.CallMethodInfoUnary(serviceContainer, rpcContainerUsedSpace), req, resp, opts...)
+	if err != nil {
+		return nil, err
+	}
+
+	return resp, nil
+}
diff --git a/v2/rpc/netmap.go b/v2/rpc/netmap.go
new file mode 100644
index 0000000..6e68456
--- /dev/null
+++ b/v2/rpc/netmap.go
@@ -0,0 +1,46 @@
+package rpc
+
+import (
+	"github.com/nspcc-dev/neofs-api-go/rpc/client"
+	"github.com/nspcc-dev/neofs-api-go/rpc/common"
+	"github.com/nspcc-dev/neofs-api-go/v2/netmap"
+)
+
+const serviceNetmap = serviceNamePrefix + "netmap.NetmapService"
+
+const (
+	rpcNetmapNodeInfo = "LocalNodeInfo"
+	rpcNetmapNetInfo  = "NetworkInfo"
+)
+
+// LocalNodeInfo executes NetmapService.LocalNodeInfo RPC.
+func LocalNodeInfo(
+	cli *client.Client,
+	req *netmap.LocalNodeInfoRequest,
+	opts ...client.CallOption,
+) (*netmap.LocalNodeInfoResponse, error) {
+	resp := new(netmap.LocalNodeInfoResponse)
+
+	err := client.SendUnary(cli, common.CallMethodInfoUnary(serviceNetmap, rpcNetmapNodeInfo), req, resp, opts...)
+	if err != nil {
+		return nil, err
+	}
+
+	return resp, nil
+}
+
+// NetworkInfo executes NetmapService.NetworkInfo RPC.
+func NetworkInfo(
+	cli *client.Client,
+	req *netmap.NetworkInfoRequest,
+	opts ...client.CallOption,
+) (*netmap.NetworkInfoResponse, error) {
+	resp := new(netmap.NetworkInfoResponse)
+
+	err := client.SendUnary(cli, common.CallMethodInfoUnary(serviceNetmap, rpcNetmapNetInfo), req, resp, opts...)
+	if err != nil {
+		return nil, err
+	}
+
+	return resp, nil
+}
diff --git a/v2/rpc/object.go b/v2/rpc/object.go
new file mode 100644
index 0000000..7e64a61
--- /dev/null
+++ b/v2/rpc/object.go
@@ -0,0 +1,190 @@
+package rpc
+
+import (
+	"github.com/nspcc-dev/neofs-api-go/rpc/client"
+	"github.com/nspcc-dev/neofs-api-go/rpc/common"
+	"github.com/nspcc-dev/neofs-api-go/rpc/message"
+	"github.com/nspcc-dev/neofs-api-go/v2/object"
+)
+
+const serviceObject = serviceNamePrefix + "object.ObjectService"
+
+const (
+	rpcObjectPut    = "Put"
+	rpcObjectGet    = "Get"
+	rpcObjectSearch = "Search"
+	rpcObjectRange  = "GetRange"
+	rpcObjectHash   = "GetRangeHash"
+	rpcObjectHead   = "Head"
+	rpcObjectDelete = "Delete"
+)
+
+// PutRequestWriter is an object.PutRequest
+// message streaming component.
+type PutRequestWriter struct {
+	wc client.MessageWriterCloser
+
+	resp message.Message
+}
+
+// Write writes req to the stream.
+func (w *PutRequestWriter) Write(req *object.PutRequest) error {
+	return w.wc.WriteMessage(req)
+}
+
+// Close closes the stream.
+func (w *PutRequestWriter) Close() error {
+	return w.wc.Close()
+}
+
+// PutObject executes ObjectService.Put RPC.
+func PutObject(
+	cli *client.Client,
+	resp *object.PutResponse,
+	opts ...client.CallOption,
+) (*PutRequestWriter, error) {
+	wc, err := client.OpenClientStream(cli, common.CallMethodInfoClientStream(serviceObject, rpcObjectPut), resp, opts...)
+	if err != nil {
+		return nil, err
+	}
+
+	return &PutRequestWriter{
+		wc:   wc,
+		resp: resp,
+	}, nil
+}
+
+// GetResponseReader is an object.GetResponse
+// stream reader.
+type GetResponseReader struct {
+	r client.MessageReader
+}
+
+// Read reads response from the stream.
+//
+// Returns io.EOF of streaming is finished.
+func (r *GetResponseReader) Read(resp *object.GetResponse) error {
+	return r.r.ReadMessage(resp)
+}
+
+// GetObject executes ObjectService.Get RPC.
+func GetObject(
+	cli *client.Client,
+	req *object.GetRequest,
+	opts ...client.CallOption,
+) (*GetResponseReader, error) {
+	wc, err := client.OpenServerStream(cli, common.CallMethodInfoServerStream(serviceObject, rpcObjectGet), req, opts...)
+	if err != nil {
+		return nil, err
+	}
+
+	return &GetResponseReader{
+		r: wc,
+	}, nil
+}
+
+// GetResponseReader is an object.SearchResponse
+// stream reader.
+type SearchResponseReader struct {
+	r client.MessageReader
+}
+
+// Read reads response from the stream.
+//
+// Returns io.EOF of streaming is finished.
+func (r *SearchResponseReader) Read(resp *object.SearchResponse) error {
+	return r.r.ReadMessage(resp)
+}
+
+// SearchObjects executes ObjectService.Search RPC.
+func SearchObjects(
+	cli *client.Client,
+	req *object.SearchRequest,
+	opts ...client.CallOption,
+) (*SearchResponseReader, error) {
+	wc, err := client.OpenServerStream(cli, common.CallMethodInfoServerStream(serviceObject, rpcObjectSearch), req, opts...)
+	if err != nil {
+		return nil, err
+	}
+
+	return &SearchResponseReader{
+		r: wc,
+	}, nil
+}
+
+// GetResponseReader is an object.GetRangeResponse
+// stream reader.
+type ObjectRangeResponseReader struct {
+	r client.MessageReader
+}
+
+// Read reads response from the stream.
+//
+// Returns io.EOF of streaming is finished.
+func (r *ObjectRangeResponseReader) Read(resp *object.GetRangeResponse) error {
+	return r.r.ReadMessage(resp)
+}
+
+// GetObjectRange executes ObjectService.GetRange RPC.
+func GetObjectRange(
+	cli *client.Client,
+	req *object.GetRangeRequest,
+	opts ...client.CallOption,
+) (*ObjectRangeResponseReader, error) {
+	wc, err := client.OpenServerStream(cli, common.CallMethodInfoServerStream(serviceObject, rpcObjectRange), req, opts...)
+	if err != nil {
+		return nil, err
+	}
+
+	return &ObjectRangeResponseReader{
+		r: wc,
+	}, nil
+}
+
+// HeadObject executes ObjectService.Head RPC.
+func HeadObject(
+	cli *client.Client,
+	req *object.HeadRequest,
+	opts ...client.CallOption,
+) (*object.HeadResponse, error) {
+	resp := new(object.HeadResponse)
+
+	err := client.SendUnary(cli, common.CallMethodInfoUnary(serviceObject, rpcObjectHead), req, resp, opts...)
+	if err != nil {
+		return nil, err
+	}
+
+	return resp, nil
+}
+
+// DeleteObject executes ObjectService.Delete RPC.
+func DeleteObject(
+	cli *client.Client,
+	req *object.DeleteRequest,
+	opts ...client.CallOption,
+) (*object.DeleteResponse, error) {
+	resp := new(object.DeleteResponse)
+
+	err := client.SendUnary(cli, common.CallMethodInfoUnary(serviceObject, rpcObjectDelete), req, resp, opts...)
+	if err != nil {
+		return nil, err
+	}
+
+	return resp, nil
+}
+
+// HashObjectRange executes ObjectService.GetRangeHash RPC.
+func HashObjectRange(
+	cli *client.Client,
+	req *object.GetRangeHashRequest,
+	opts ...client.CallOption,
+) (*object.GetRangeHashResponse, error) {
+	resp := new(object.GetRangeHashResponse)
+
+	err := client.SendUnary(cli, common.CallMethodInfoUnary(serviceObject, rpcObjectHash), req, resp, opts...)
+	if err != nil {
+		return nil, err
+	}
+
+	return resp, nil
+}
diff --git a/v2/rpc/session.go b/v2/rpc/session.go
new file mode 100644
index 0000000..89b5f1c
--- /dev/null
+++ b/v2/rpc/session.go
@@ -0,0 +1,28 @@
+package rpc
+
+import (
+	"github.com/nspcc-dev/neofs-api-go/rpc/client"
+	"github.com/nspcc-dev/neofs-api-go/rpc/common"
+	"github.com/nspcc-dev/neofs-api-go/v2/session"
+)
+
+const serviceSession = serviceNamePrefix + "session.SessionService"
+
+const (
+	rpcSessionCreate = "Create"
+)
+
+func CreateSession(
+	cli *client.Client,
+	req *session.CreateRequest,
+	opts ...client.CallOption,
+) (*session.CreateResponse, error) {
+	resp := new(session.CreateResponse)
+
+	err := client.SendUnary(cli, common.CallMethodInfoUnary(serviceSession, rpcSessionCreate), req, resp, opts...)
+	if err != nil {
+		return nil, err
+	}
+
+	return resp, nil
+}
diff --git a/v2/session/client.go b/v2/session/client.go
deleted file mode 100644
index f1709c7..0000000
--- a/v2/session/client.go
+++ /dev/null
@@ -1,156 +0,0 @@
-package session
-
-import (
-	"context"
-
-	"github.com/nspcc-dev/neofs-api-go/v2/client"
-	session "github.com/nspcc-dev/neofs-api-go/v2/session/grpc"
-	"github.com/pkg/errors"
-	"google.golang.org/grpc"
-)
-
-// Client represents universal session
-// transport client.
-type Client struct {
-	client *createClient
-}
-
-// Option represents Client option.
-type Option func(*cfg)
-
-type cfg struct {
-	proto client.Protocol
-
-	globalOpts []client.Option
-
-	gRPC cfgGRPC
-}
-
-type cfgGRPC struct {
-	serviceClient session.SessionServiceClient
-
-	grpcCallOpts []grpc.CallOption
-
-	callOpts []session.Option
-
-	client *session.Client
-}
-
-type createClient struct {
-	requestConverter func(*CreateRequest) interface{}
-
-	caller func(context.Context, interface{}) (interface{}, error)
-
-	responseConverter func(interface{}) *CreateResponse
-}
-
-// Create sends CreateRequest over the network and returns CreateResponse.
-//
-// It returns any error encountered during the call.
-func (c *Client) Create(ctx context.Context, req *CreateRequest) (*CreateResponse, error) {
-	resp, err := c.client.caller(ctx, c.client.requestConverter(req))
-	if err != nil {
-		return nil, errors.Wrap(err, "could not send session init request")
-	}
-
-	return c.client.responseConverter(resp), nil
-}
-
-func defaultCfg() *cfg {
-	return &cfg{
-		proto: client.ProtoGRPC,
-	}
-}
-
-func NewClient(opts ...Option) (*Client, error) {
-	cfg := defaultCfg()
-
-	for i := range opts {
-		opts[i](cfg)
-	}
-
-	var err error
-
-	switch cfg.proto {
-	case client.ProtoGRPC:
-		var c *session.Client
-		if c, err = newGRPCClient(cfg); err != nil {
-			break
-		}
-
-		return &Client{
-			client: &createClient{
-				requestConverter: func(req *CreateRequest) interface{} {
-					return CreateRequestToGRPCMessage(req)
-				},
-				caller: func(ctx context.Context, req interface{}) (interface{}, error) {
-					return c.Create(ctx, req.(*session.CreateRequest))
-				},
-				responseConverter: func(resp interface{}) *CreateResponse {
-					return CreateResponseFromGRPCMessage(resp.(*session.CreateResponse))
-				},
-			},
-		}, nil
-	default:
-		err = client.ErrProtoUnsupported
-	}
-
-	return nil, errors.Wrapf(err, "could not create %s Session client", cfg.proto)
-}
-
-func newGRPCClient(cfg *cfg) (*session.Client, error) {
-	var err error
-
-	if cfg.gRPC.client == nil {
-		if cfg.gRPC.serviceClient == nil {
-			conn, err := client.NewGRPCClientConn(cfg.globalOpts...)
-			if err != nil {
-				return nil, errors.Wrap(err, "could not open gRPC client connection")
-			}
-
-			cfg.gRPC.serviceClient = session.NewSessionServiceClient(conn)
-		}
-
-		cfg.gRPC.client, err = session.NewClient(
-			cfg.gRPC.serviceClient,
-			append(
-				cfg.gRPC.callOpts,
-				session.WithCallOptions(cfg.gRPC.grpcCallOpts),
-			)...,
-		)
-	}
-
-	return cfg.gRPC.client, err
-}
-
-func WithGlobalOpts(v ...client.Option) Option {
-	return func(c *cfg) {
-		if len(v) > 0 {
-			c.globalOpts = v
-		}
-	}
-}
-
-func WithGRPCServiceClient(v session.SessionServiceClient) Option {
-	return func(c *cfg) {
-		c.gRPC.serviceClient = v
-	}
-}
-
-func WithGRPCCallOpts(v []grpc.CallOption) Option {
-	return func(c *cfg) {
-		c.gRPC.grpcCallOpts = v
-	}
-}
-
-func WithGRPCClientOpts(v []session.Option) Option {
-	return func(c *cfg) {
-		c.gRPC.callOpts = v
-	}
-}
-
-func WithGRPCClient(v *session.Client) Option {
-	return func(c *cfg) {
-		c.gRPC.client = v
-	}
-}
diff --git a/v2/session/convert.go b/v2/session/convert.go
index 5d73dc4..084377e 100644
--- a/v2/session/convert.go
+++ b/v2/session/convert.go
@@ -3,537 +3,614 @@ package session
 import (
 	"fmt"
 
+	"github.com/nspcc-dev/neofs-api-go/rpc/grpc"
+	"github.com/nspcc-dev/neofs-api-go/rpc/message"
 	"github.com/nspcc-dev/neofs-api-go/v2/acl"
+	aclGRPC "github.com/nspcc-dev/neofs-api-go/v2/acl/grpc"
 	"github.com/nspcc-dev/neofs-api-go/v2/refs"
+	refsGRPC "github.com/nspcc-dev/neofs-api-go/v2/refs/grpc"
 	session "github.com/nspcc-dev/neofs-api-go/v2/session/grpc"
+	"github.com/pkg/errors"
 )
 
-func CreateRequestBodyToGRPCMessage(c *CreateRequestBody) *session.CreateRequest_Body {
-	if c == nil {
-		return nil
+func (c *CreateRequestBody) ToGRPCMessage() grpc.Message {
+	var m *session.CreateRequest_Body
+
+	if c != nil {
+		m = new(session.CreateRequest_Body)
+
+		m.SetOwnerId(c.ownerID.ToGRPCMessage().(*refsGRPC.OwnerID))
+		m.SetExpiration(c.expiration)
 	}
 
-	m := new(session.CreateRequest_Body)
-
-	m.SetOwnerId(
-		refs.OwnerIDToGRPCMessage(c.GetOwnerID()),
-	)
-
-	m.SetExpiration(c.GetExpiration())
-
 	return m
 }
 
-func CreateRequestBodyFromGRPCMessage(m *session.CreateRequest_Body) *CreateRequestBody {
-	if m == nil {
-		return nil
+func (c *CreateRequestBody) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*session.CreateRequest_Body)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
 	}
 
-	c := new(CreateRequestBody)
+	var err error
 
-	c.SetOwnerID(
-		refs.OwnerIDFromGRPCMessage(m.GetOwnerId()),
-	)
+	ownerID := v.GetOwnerId()
+	if ownerID == nil {
+		c.ownerID = nil
+	} else {
+		if c.ownerID == nil {
+			c.ownerID = new(refs.OwnerID)
+		}
 
-	c.SetExpiration(m.GetExpiration())
+		err = c.ownerID.FromGRPCMessage(ownerID)
+		if err != nil {
+			return err
+		}
+	}
 
-	return c
+	c.expiration = v.GetExpiration()
+
+	return nil
 }
 
-func CreateRequestToGRPCMessage(c *CreateRequest) *session.CreateRequest {
-	if c == nil {
-		return nil
+func (c *CreateRequest) ToGRPCMessage() grpc.Message {
+	var m *session.CreateRequest
+
+	if c != nil {
+		m = new(session.CreateRequest)
+
+		m.SetBody(c.body.ToGRPCMessage().(*session.CreateRequest_Body))
+		c.RequestHeaders.ToMessage(m)
 	}
 
-	m := new(session.CreateRequest)
-
-	m.SetBody(
-		CreateRequestBodyToGRPCMessage(c.GetBody()),
-	)
-
-	RequestHeadersToGRPC(c, m)
-
 	return m
 }
 
-func CreateRequestFromGRPCMessage(m *session.CreateRequest) *CreateRequest {
-	if m == nil {
-		return nil
+func (c *CreateRequest) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*session.CreateRequest)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
 	}
 
-	c := new(CreateRequest)
+	var err error
 
-	c.SetBody(
-		CreateRequestBodyFromGRPCMessage(m.GetBody()),
-	)
+	body := v.GetBody()
+	if body == nil {
+		c.body = nil
+	} else {
+		if c.body == nil {
+			c.body = new(CreateRequestBody)
+		}
 
-	RequestHeadersFromGRPC(m, c)
+		err = c.body.FromGRPCMessage(body)
+		if err != nil {
+			return err
+		}
+	}
 
-	return c
+	return c.RequestHeaders.FromMessage(v)
 }
 
-func CreateResponseBodyToGRPCMessage(c *CreateResponseBody) *session.CreateResponse_Body {
-	if c == nil {
-		return nil
+func (c *CreateResponseBody) ToGRPCMessage() grpc.Message {
+	var m *session.CreateResponse_Body
+
+	if c != nil {
+		m = new(session.CreateResponse_Body)
+
+		m.SetSessionKey(c.sessionKey)
+		m.SetId(c.id)
 	}
 
-	m := new(session.CreateResponse_Body)
-
-	m.SetId(c.GetID())
-	m.SetSessionKey(c.GetSessionKey())
-
 	return m
 }
 
-func CreateResponseBodyFromGRPCMessage(m *session.CreateResponse_Body) *CreateResponseBody {
-	if m == nil {
-		return nil
+func (c *CreateResponseBody) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*session.CreateResponse_Body)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
 	}
 
-	c := new(CreateResponseBody)
+	c.sessionKey = v.GetSessionKey()
+	c.id = v.GetId()
 
-	c.SetID(m.GetId())
-	c.SetSessionKey(m.GetSessionKey())
-
-	return c
+	return nil
 }
 
-func CreateResponseToGRPCMessage(c *CreateResponse) *session.CreateResponse {
-	if c == nil {
-		return nil
+func (c *CreateResponse) ToGRPCMessage() grpc.Message {
+	var m *session.CreateResponse
+
+	if c != nil {
+		m = new(session.CreateResponse)
+
+		m.SetBody(c.body.ToGRPCMessage().(*session.CreateResponse_Body))
+		c.ResponseHeaders.ToMessage(m)
 	}
 
-	m := new(session.CreateResponse)
-
-	m.SetBody(
-		CreateResponseBodyToGRPCMessage(c.GetBody()),
-	)
-
-	ResponseHeadersToGRPC(c, m)
-
 	return m
 }
 
-func CreateResponseFromGRPCMessage(m *session.CreateResponse) *CreateResponse {
-	if m == nil {
-		return nil
+func (c *CreateResponse) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*session.CreateResponse)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
 	}
 
-	c := new(CreateResponse)
+	var err error
 
-	c.SetBody(
-		CreateResponseBodyFromGRPCMessage(m.GetBody()),
-	)
+	body := v.GetBody()
+	if body == nil {
+		c.body = nil
+	} else {
+		if c.body == nil {
+			c.body = new(CreateResponseBody)
+		}
 
-	ResponseHeadersFromGRPC(m, c)
+		err = c.body.FromGRPCMessage(body)
+		if err != nil {
+			return err
+		}
+	}
 
-	return c
+	return c.ResponseHeaders.FromMessage(v)
 }
 
-func TokenLifetimeToGRPCMessage(tl *TokenLifetime) *session.SessionToken_Body_TokenLifetime {
-	if tl == nil {
-		return nil
+func (l *TokenLifetime) ToGRPCMessage() grpc.Message {
+	var m *session.SessionToken_Body_TokenLifetime
+
+	if l != nil {
+		m = new(session.SessionToken_Body_TokenLifetime)
+
+		m.SetExp(l.exp)
+		m.SetIat(l.iat)
+		m.SetNbf(l.nbf)
 	}
 
-	m := new(session.SessionToken_Body_TokenLifetime)
-
-	m.SetExp(tl.GetExp())
-	m.SetNbf(tl.GetNbf())
-	m.SetIat(tl.GetIat())
-
 	return m
 }
 
-func TokenLifetimeFromGRPCMessage(m *session.SessionToken_Body_TokenLifetime) *TokenLifetime {
-	if m == nil {
-		return nil
+func (l *TokenLifetime) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*session.SessionToken_Body_TokenLifetime)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
 	}
 
-	tl := new(TokenLifetime)
+	l.exp = v.GetExp()
+	l.iat = v.GetIat()
+	l.nbf = v.GetNbf()
 
-	tl.SetExp(m.GetExp())
-	tl.SetNbf(m.GetNbf())
-	tl.SetIat(m.GetIat())
-
-	return tl
+	return nil
 }
 
-func XHeaderToGRPCMessage(x *XHeader) *session.XHeader {
-	if x == nil {
-		return nil
+func (x *XHeader) ToGRPCMessage() grpc.Message {
+	var m *session.XHeader
+
+	if x != nil {
+		m = new(session.XHeader)
+
+		m.SetKey(x.key)
+		m.SetValue(x.val)
 	}
 
-	m := new(session.XHeader)
-
-	m.SetKey(x.GetKey())
-	m.SetValue(x.GetValue())
-
 	return m
 }
 
-func XHeaderFromGRPCMessage(m *session.XHeader) *XHeader {
-	if m == nil {
-		return nil
+func (x *XHeader) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*session.XHeader)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
 	}
 
-	x := new(XHeader)
+	x.key = v.GetKey()
+	x.val = v.GetValue()
 
-	x.SetKey(m.GetKey())
-	x.SetValue(m.GetValue())
-
-	return x
+	return nil
 }
 
-func SessionTokenToGRPCMessage(t *SessionToken) *session.SessionToken {
-	if t == nil {
-		return nil
+func XHeadersToGRPC(xs []*XHeader) (res []*session.XHeader) {
+	if xs != nil {
+		res = make([]*session.XHeader, 0, len(xs))
+
+		for i := range xs {
+			res = append(res, xs[i].ToGRPCMessage().(*session.XHeader))
+		}
 	}
 
-	m := new(session.SessionToken)
+	return
+}
 
-	m.SetBody(
-		SessionTokenBodyToGRPCMessage(t.GetBody()),
-	)
+func XHeadersFromGRPC(xs []*session.XHeader) (res []*XHeader, err error) {
+	if xs != nil {
+		res = make([]*XHeader, 0, len(xs))
 
-	m.SetSignature(
-		refs.SignatureToGRPCMessage(t.GetSignature()),
-	)
+		for i := range xs {
+			var x *XHeader
+
+			if xs[i] != nil {
+				x = new(XHeader)
+
+				err = x.FromGRPCMessage(xs[i])
+				if err != nil {
+					return
+				}
+			}
+
+			res = append(res, x)
+		}
+	}
+
+	return
+}
+
+func (t *SessionToken) ToGRPCMessage() grpc.Message {
+	var m *session.SessionToken
+
+	if t != nil {
+		m = new(session.SessionToken)
+
+		m.SetBody(t.body.ToGRPCMessage().(*session.SessionToken_Body))
+		m.SetSignature(t.sig.ToGRPCMessage().(*refsGRPC.Signature))
+	}
 
 	return m
 }
 
-func SessionTokenFromGRPCMessage(m *session.SessionToken) *SessionToken {
-	if m == nil {
-		return nil
+func (t *SessionToken) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*session.SessionToken)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
 	}
 
-	t := new(SessionToken)
+	var err error
 
-	t.SetBody(
-		SessionTokenBodyFromGRPCMessage(m.GetBody()),
-	)
+	body := v.GetBody()
+	if body == nil {
+		t.body = nil
+	} else {
+		if t.body == nil {
+			t.body = new(SessionTokenBody)
+		}
 
-	t.SetSignature(
-		refs.SignatureFromGRPCMessage(m.GetSignature()),
-	)
+		err = t.body.FromGRPCMessage(body)
+		if err != nil {
+			return err
+		}
+	}
 
-	return t
+	sig := v.GetSignature()
+	if sig == nil {
+		t.sig = nil
+	} else {
+		if t.sig == nil {
+			t.sig = new(refs.Signature)
+		}
+
+		err = t.sig.FromGRPCMessage(sig)
+		if err != nil {
+			return err
+		}
+	}
+
+	return nil
 }
 
-func RequestVerificationHeaderToGRPCMessage(r *RequestVerificationHeader) *session.RequestVerificationHeader {
-	if r == nil {
-		return nil
+func (r *RequestVerificationHeader) ToGRPCMessage() grpc.Message {
+	var m *session.RequestVerificationHeader
+
+	if r != nil {
+		m = new(session.RequestVerificationHeader)
+
+		m.SetBodySignature(r.bodySig.ToGRPCMessage().(*refsGRPC.Signature))
+		m.SetMetaSignature(r.metaSig.ToGRPCMessage().(*refsGRPC.Signature))
+		m.SetOriginSignature(r.originSig.ToGRPCMessage().(*refsGRPC.Signature))
+		m.SetOrigin(r.origin.ToGRPCMessage().(*session.RequestVerificationHeader))
 	}
 
-	m := new(session.RequestVerificationHeader)
-
-	m.SetBodySignature(
-		refs.SignatureToGRPCMessage(r.GetBodySignature()),
-	)
-
-	m.SetMetaSignature(
-		refs.SignatureToGRPCMessage(r.GetMetaSignature()),
-	)
-
-	m.SetOriginSignature(
-		refs.SignatureToGRPCMessage(r.GetOriginSignature()),
-	)
-
-	m.SetOrigin(
-		RequestVerificationHeaderToGRPCMessage(r.GetOrigin()),
-	)
-
 	return m
 }
 
-func RequestVerificationHeaderFromGRPCMessage(m *session.RequestVerificationHeader) *RequestVerificationHeader {
-	if m == nil {
-		return nil
+func (r *RequestVerificationHeader) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*session.RequestVerificationHeader)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
 	}
 
-	r := new(RequestVerificationHeader)
+	var err error
 
-	r.SetBodySignature(
-		refs.SignatureFromGRPCMessage(m.GetBodySignature()),
-	)
+	originSig := v.GetOriginSignature()
+	if originSig == nil {
+		r.originSig = nil
+	} else {
+		if r.originSig == nil {
+			r.originSig = new(refs.Signature)
+		}
 
-	r.SetMetaSignature(
-		refs.SignatureFromGRPCMessage(m.GetMetaSignature()),
-	)
+		err = r.originSig.FromGRPCMessage(originSig)
+		if err != nil {
+			return err
+		}
+	}
 
-	r.SetOriginSignature(
-		refs.SignatureFromGRPCMessage(m.GetOriginSignature()),
-	)
+	metaSig := v.GetMetaSignature()
+	if metaSig == nil {
+		r.metaSig = nil
+	} else {
+		if r.metaSig == nil {
+			r.metaSig = new(refs.Signature)
+		}
 
-	r.SetOrigin(
-		RequestVerificationHeaderFromGRPCMessage(m.GetOrigin()),
-	)
+		err = r.metaSig.FromGRPCMessage(metaSig)
+		if err != nil {
+			return err
+		}
+	}
 
-	return r
+	bodySig := v.GetBodySignature()
+	if bodySig == nil {
+		r.bodySig = nil
+	} else {
+		if r.bodySig == nil {
+			r.bodySig = new(refs.Signature)
+		}
+
+		err = r.bodySig.FromGRPCMessage(bodySig)
+		if err != nil {
+			return err
+		}
+	}
+
+	origin := v.GetOrigin()
+	if origin == nil {
+		r.origin = nil
+	} else {
+		if r.origin == nil {
+			r.origin = new(RequestVerificationHeader)
+		}
+
+		err = r.origin.FromGRPCMessage(origin)
+		if err != nil {
+			return err
+		}
+	}
+
+	return nil
 }
 
-func RequestMetaHeaderToGRPCMessage(r *RequestMetaHeader) *session.RequestMetaHeader {
-	if r == nil {
-		return nil
+func (r *RequestMetaHeader) ToGRPCMessage() grpc.Message {
+	var m *session.RequestMetaHeader
+
+	if r != nil {
+		m = new(session.RequestMetaHeader)
+
+		m.SetVersion(r.version.ToGRPCMessage().(*refsGRPC.Version))
+		m.SetSessionToken(r.sessionToken.ToGRPCMessage().(*session.SessionToken))
+		m.SetBearerToken(r.bearerToken.ToGRPCMessage().(*aclGRPC.BearerToken))
+		m.SetXHeaders(XHeadersToGRPC(r.xHeaders))
+		m.SetEpoch(r.epoch)
+		m.SetTtl(r.ttl)
+		m.SetOrigin(r.origin.ToGRPCMessage().(*session.RequestMetaHeader))
 	}
 
-	m := new(session.RequestMetaHeader)
-
-	m.SetTtl(r.GetTTL())
-	m.SetEpoch(r.GetEpoch())
-
-	m.SetVersion(
-		refs.VersionToGRPCMessage(r.GetVersion()),
-	)
-
-	m.SetSessionToken(
-		SessionTokenToGRPCMessage(r.GetSessionToken()),
-	)
-
-	m.SetBearerToken(
-		acl.BearerTokenToGRPCMessage(r.GetBearerToken()),
-	)
-
-	m.SetOrigin(
-		RequestMetaHeaderToGRPCMessage(r.GetOrigin()),
-	)
-
-	xHeaders := r.GetXHeaders()
-	xHdrMsg := make([]*session.XHeader, 0, len(xHeaders))
-
-	for i := range xHeaders {
-		xHdrMsg = append(xHdrMsg, XHeaderToGRPCMessage(xHeaders[i]))
-	}
-
-	m.SetXHeaders(xHdrMsg)
-
 	return m
 }
 
-func RequestMetaHeaderFromGRPCMessage(m *session.RequestMetaHeader) *RequestMetaHeader {
-	if m == nil {
-		return nil
+func (r *RequestMetaHeader) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*session.RequestMetaHeader)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
 	}
 
-	r := new(RequestMetaHeader)
+	var err error
 
-	r.SetTTL(m.GetTtl())
-	r.SetEpoch(m.GetEpoch())
+	version := v.GetVersion()
+	if version == nil {
+		r.version = nil
+	} else {
+		if r.version == nil {
+			r.version = new(refs.Version)
+		}
 
-	r.SetVersion(
-		refs.VersionFromGRPCMessage(m.GetVersion()),
-	)
-
-	r.SetSessionToken(
-		SessionTokenFromGRPCMessage(m.GetSessionToken()),
-	)
-
-	r.SetBearerToken(
-		acl.BearerTokenFromGRPCMessage(m.GetBearerToken()),
-	)
-
-	r.SetOrigin(
-		RequestMetaHeaderFromGRPCMessage(m.GetOrigin()),
-	)
-
-	xHdrMsg := m.GetXHeaders()
-	xHeaders := make([]*XHeader, 0, len(xHdrMsg))
-
-	for i := range xHdrMsg {
-		xHeaders = append(xHeaders, XHeaderFromGRPCMessage(xHdrMsg[i]))
+		err = r.version.FromGRPCMessage(version)
+		if err != nil {
+			return err
+		}
 	}
 
-	r.SetXHeaders(xHeaders)
+	sessionToken := v.GetSessionToken()
+	if sessionToken == nil {
+		r.sessionToken = nil
+	} else {
+		if r.sessionToken == nil {
+			r.sessionToken = new(SessionToken)
+		}
 
-	return r
-}
-
-func RequestHeadersToGRPC(
-	src interface {
-		GetMetaHeader() *RequestMetaHeader
-		GetVerificationHeader() *RequestVerificationHeader
-	},
-	dst interface {
-		SetMetaHeader(*session.RequestMetaHeader)
-		SetVerifyHeader(*session.RequestVerificationHeader)
-	},
-) {
-	dst.SetMetaHeader(
-		RequestMetaHeaderToGRPCMessage(src.GetMetaHeader()),
-	)
-
-	dst.SetVerifyHeader(
-		RequestVerificationHeaderToGRPCMessage(src.GetVerificationHeader()),
-	)
-}
-
-func RequestHeadersFromGRPC(
-	src interface {
-		GetMetaHeader() *session.RequestMetaHeader
-		GetVerifyHeader() *session.RequestVerificationHeader
-	},
-	dst interface {
-		SetMetaHeader(*RequestMetaHeader)
-		SetVerificationHeader(*RequestVerificationHeader)
-	},
-) {
-	dst.SetMetaHeader(
-		RequestMetaHeaderFromGRPCMessage(src.GetMetaHeader()),
-	)
-
-	dst.SetVerificationHeader(
-		RequestVerificationHeaderFromGRPCMessage(src.GetVerifyHeader()),
-	)
-}
-
-func ResponseVerificationHeaderToGRPCMessage(r *ResponseVerificationHeader) *session.ResponseVerificationHeader {
-	if r == nil {
-		return nil
+		err = r.sessionToken.FromGRPCMessage(sessionToken)
+		if err != nil {
+			return err
+		}
 	}
 
-	m := new(session.ResponseVerificationHeader)
+	bearerToken := v.GetBearerToken()
+	if bearerToken == nil {
+		r.bearerToken = nil
+	} else {
+		if r.bearerToken == nil {
+			r.bearerToken = new(acl.BearerToken)
+		}
 
-	m.SetBodySignature(
-		refs.SignatureToGRPCMessage(r.GetBodySignature()),
-	)
+		err = r.bearerToken.FromGRPCMessage(bearerToken)
+		if err != nil {
+			return err
+		}
+	}
 
-	m.SetMetaSignature(
-		refs.SignatureToGRPCMessage(r.GetMetaSignature()),
-	)
+	origin := v.GetOrigin()
+	if origin == nil {
+		r.origin = nil
+	} else {
+		if r.origin == nil {
+			r.origin = new(RequestMetaHeader)
+		}
 
-	m.SetOriginSignature(
-		refs.SignatureToGRPCMessage(r.GetOriginSignature()),
-	)
+		err = r.origin.FromGRPCMessage(origin)
+		if err != nil {
+			return err
+		}
+	}
 
-	m.SetOrigin(
-		ResponseVerificationHeaderToGRPCMessage(r.GetOrigin()),
-	)
+	r.xHeaders, err = XHeadersFromGRPC(v.GetXHeaders())
+	if err != nil {
+		return err
+	}
+
+	r.epoch = v.GetEpoch()
+	r.ttl = v.GetTtl()
+
+	return nil
+}
+
+func (r *ResponseVerificationHeader) ToGRPCMessage() grpc.Message {
+	var m *session.ResponseVerificationHeader
+
+	if r != nil {
+		m = new(session.ResponseVerificationHeader)
+
+		m.SetBodySignature(r.bodySig.ToGRPCMessage().(*refsGRPC.Signature))
+		m.SetMetaSignature(r.metaSig.ToGRPCMessage().(*refsGRPC.Signature))
+		m.SetOriginSignature(r.originSig.ToGRPCMessage().(*refsGRPC.Signature))
+		m.SetOrigin(r.origin.ToGRPCMessage().(*session.ResponseVerificationHeader))
+	}
 
 	return m
 }
 
-func ResponseVerificationHeaderFromGRPCMessage(m *session.ResponseVerificationHeader) *ResponseVerificationHeader {
-	if m == nil {
-		return nil
+func (r *ResponseVerificationHeader) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*session.ResponseVerificationHeader)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
 	}
 
-	r := new(ResponseVerificationHeader)
+	var err error
 
-	r.SetBodySignature(
-		refs.SignatureFromGRPCMessage(m.GetBodySignature()),
-	)
+	originSig := v.GetOriginSignature()
+	if originSig == nil {
+		r.originSig = nil
+	} else {
+		if r.originSig == nil {
+			r.originSig = new(refs.Signature)
+		}
 
-	r.SetMetaSignature(
-		refs.SignatureFromGRPCMessage(m.GetMetaSignature()),
-	)
+		err = r.originSig.FromGRPCMessage(originSig)
+		if err != nil {
+			return err
+		}
+	}
 
-	r.SetOriginSignature(
-		refs.SignatureFromGRPCMessage(m.GetOriginSignature()),
-	)
+	metaSig := v.GetMetaSignature()
+	if metaSig == nil {
+		r.metaSig = nil
+	} else {
+		if r.metaSig == nil {
+			r.metaSig = new(refs.Signature)
+		}
 
-	r.SetOrigin(
-		ResponseVerificationHeaderFromGRPCMessage(m.GetOrigin()),
-	)
+		err = r.metaSig.FromGRPCMessage(metaSig)
+		if err != nil {
+			return err
+		}
+	}
 
-	return r
+	bodySig := v.GetBodySignature()
+	if bodySig == nil {
+		r.bodySig = nil
+	} else {
+		if r.bodySig == nil {
+			r.bodySig = new(refs.Signature)
+		}
+
+		err = r.bodySig.FromGRPCMessage(bodySig)
+		if err != nil {
+			return err
+		}
+	}
+
+	origin := v.GetOrigin()
+	if origin == nil {
+		r.origin = nil
+	} else {
+		if r.origin == nil {
+			r.origin = new(ResponseVerificationHeader)
+		}
+
+		err = r.origin.FromGRPCMessage(origin)
+		if err != nil {
+			return err
+		}
+	}
+
+	return nil
 }
 
-func ResponseMetaHeaderToGRPCMessage(r *ResponseMetaHeader) *session.ResponseMetaHeader {
-	if r == nil {
-		return nil
+func (r *ResponseMetaHeader) ToGRPCMessage() grpc.Message {
+	var m *session.ResponseMetaHeader
+
+	if r != nil {
+		m = new(session.ResponseMetaHeader)
+
+		m.SetVersion(r.version.ToGRPCMessage().(*refsGRPC.Version))
+		m.SetXHeaders(XHeadersToGRPC(r.xHeaders))
+		m.SetEpoch(r.epoch)
+		m.SetTtl(r.ttl)
+		m.SetOrigin(r.origin.ToGRPCMessage().(*session.ResponseMetaHeader))
 	}
 
-	m := new(session.ResponseMetaHeader)
-
-	m.SetTtl(r.GetTTL())
-	m.SetEpoch(r.GetEpoch())
-
-	m.SetVersion(
-		refs.VersionToGRPCMessage(r.GetVersion()),
-	)
-
-	m.SetOrigin(
-		ResponseMetaHeaderToGRPCMessage(r.GetOrigin()),
-	)
-
-	xHeaders := r.GetXHeaders()
-	xHdrMsg := make([]*session.XHeader, 0, len(xHeaders))
-
-	for i := range xHeaders {
-		xHdrMsg = append(xHdrMsg, XHeaderToGRPCMessage(xHeaders[i]))
-	}
-
-	m.SetXHeaders(xHdrMsg)
-
 	return m
 }
 
-func ResponseMetaHeaderFromGRPCMessage(m *session.ResponseMetaHeader) *ResponseMetaHeader {
-	if m == nil {
-		return nil
+func (r *ResponseMetaHeader) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*session.ResponseMetaHeader)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
 	}
 
-	r := new(ResponseMetaHeader)
+	var err error
 
-	r.SetTTL(m.GetTtl())
-	r.SetEpoch(m.GetEpoch())
+	version := v.GetVersion()
+	if version == nil {
+		r.version = nil
+	} else {
+		if r.version == nil {
+			r.version = new(refs.Version)
+		}
 
-	r.SetVersion(
-		refs.VersionFromGRPCMessage(m.GetVersion()),
-	)
-
-	r.SetOrigin(
-		ResponseMetaHeaderFromGRPCMessage(m.GetOrigin()),
-	)
-
-	xHdrMsg := m.GetXHeaders()
-	xHeaders := make([]*XHeader, 0, len(xHdrMsg))
-
-	for i := range xHdrMsg {
-		xHeaders = append(xHeaders, XHeaderFromGRPCMessage(xHdrMsg[i]))
+		err = r.version.FromGRPCMessage(version)
+		if err != nil {
+			return err
+		}
 	}
 
-	r.SetXHeaders(xHeaders)
+	origin := v.GetOrigin()
+	if origin == nil {
+		r.origin = nil
+	} else {
+		if r.origin == nil {
+			r.origin = new(ResponseMetaHeader)
+		}
 
-	return r
-}
+		err = r.origin.FromGRPCMessage(origin)
+		if err != nil {
+			return err
+		}
+	}
 
-func ResponseHeadersToGRPC(
-	src interface {
-		GetMetaHeader() *ResponseMetaHeader
-		GetVerificationHeader() *ResponseVerificationHeader
-	},
-	dst interface {
-		SetMetaHeader(*session.ResponseMetaHeader)
-		SetVerifyHeader(*session.ResponseVerificationHeader)
-	},
-) {
-	dst.SetMetaHeader(
-		ResponseMetaHeaderToGRPCMessage(src.GetMetaHeader()),
-	)
+	r.xHeaders, err = XHeadersFromGRPC(v.GetXHeaders())
+	if err != nil {
+		return err
+	}
 
-	dst.SetVerifyHeader(
-		ResponseVerificationHeaderToGRPCMessage(src.GetVerificationHeader()),
-	)
-}
+	r.epoch = v.GetEpoch()
+	r.ttl = v.GetTtl()
 
-func ResponseHeadersFromGRPC(
-	src interface {
-		GetMetaHeader() *session.ResponseMetaHeader
-		GetVerifyHeader() *session.ResponseVerificationHeader
-	},
-	dst interface {
-		SetMetaHeader(*ResponseMetaHeader)
-		SetVerificationHeader(*ResponseVerificationHeader)
-	},
-) {
-	dst.SetMetaHeader(
-		ResponseMetaHeaderFromGRPCMessage(src.GetMetaHeader()),
-	)
-
-	dst.SetVerificationHeader(
-		ResponseVerificationHeaderFromGRPCMessage(src.GetVerifyHeader()),
-	)
+	return nil
 }
 
 func ObjectSessionVerbToGRPCField(v ObjectSessionVerb) session.ObjectSessionContext_Verb {
@@ -578,102 +655,128 @@ func ObjectSessionVerbFromGRPCField(v session.ObjectSessionContext_Verb) ObjectS
 	}
 }
 
-func ObjectSessionContextToGRPCMessage(c *ObjectSessionContext) *session.ObjectSessionContext {
-	if c == nil {
-		return nil
+func (c *ObjectSessionContext) ToGRPCMessage() grpc.Message {
+	var m *session.ObjectSessionContext
+
+	if c != nil {
+		m = new(session.ObjectSessionContext)
+
+		m.SetVerb(ObjectSessionVerbToGRPCField(c.verb))
+		m.SetAddress(c.addr.ToGRPCMessage().(*refsGRPC.Address))
 	}
 
-	m := new(session.ObjectSessionContext)
-
-	m.SetVerb(
-		ObjectSessionVerbToGRPCField(c.GetVerb()),
-	)
-
-	m.SetAddress(
-		refs.AddressToGRPCMessage(c.GetAddress()),
-	)
-
 	return m
 }
 
-func ObjectSessionContextFromGRPCMessage(m *session.ObjectSessionContext) *ObjectSessionContext {
-	if m == nil {
-		return nil
+func (c *ObjectSessionContext) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*session.ObjectSessionContext)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
 	}
 
-	c := new(ObjectSessionContext)
+	var err error
 
-	c.SetVerb(
-		ObjectSessionVerbFromGRPCField(m.GetVerb()),
-	)
+	addr := v.GetAddress()
+	if addr == nil {
+		c.addr = nil
+	} else {
+		if c.addr == nil {
+			c.addr = new(refs.Address)
+		}
 
-	c.SetAddress(
-		refs.AddressFromGRPCMessage(m.GetAddress()),
-	)
+		err = c.addr.FromGRPCMessage(addr)
+		if err != nil {
+			return err
+		}
+	}
 
-	return c
+	c.verb = ObjectSessionVerbFromGRPCField(v.GetVerb())
+
+	return nil
 }
 
-func SessionTokenBodyToGRPCMessage(t *SessionTokenBody) *session.SessionToken_Body {
-	if t == nil {
-		return nil
+func (t *SessionTokenBody) ToGRPCMessage() grpc.Message {
+	var m *session.SessionToken_Body
+
+	if t != nil {
+		m = new(session.SessionToken_Body)
+
+		switch typ := t.ctx.(type) {
+		default:
+			panic(fmt.Sprintf("unknown session context %T", typ))
+		case nil:
+			m.Context = nil
+		case *ObjectSessionContext:
+			m.SetObjectSessionContext(typ.ToGRPCMessage().(*session.ObjectSessionContext))
+		}
+
+		m.SetOwnerId(t.ownerID.ToGRPCMessage().(*refsGRPC.OwnerID))
+		m.SetId(t.id)
+		m.SetSessionKey(t.sessionKey)
+		m.SetLifetime(t.lifetime.ToGRPCMessage().(*session.SessionToken_Body_TokenLifetime))
 	}
 
-	m := new(session.SessionToken_Body)
+	return m
+}
 
-	switch v := t.GetContext(); t := v.(type) {
-	case nil:
-	case *ObjectSessionContext:
-		m.SetObjectSessionContext(
-			ObjectSessionContextToGRPCMessage(t),
-		)
+func (t *SessionTokenBody) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*session.SessionToken_Body)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
+	}
+
+	var err error
+
+	t.ctx = nil
+
+	switch val := v.GetContext().(type) {
 	default:
-		panic(fmt.Sprintf("unknown session context %T", t))
-	}
-
-	m.SetId(t.GetID())
-
-	m.SetOwnerId(
-		refs.OwnerIDToGRPCMessage(t.GetOwnerID()),
-	)
-
-	m.SetLifetime(
-		TokenLifetimeToGRPCMessage(t.GetLifetime()),
-	)
-
-	m.SetSessionKey(t.GetSessionKey())
-
-	return m
-}
-
-func SessionTokenBodyFromGRPCMessage(m *session.SessionToken_Body) *SessionTokenBody {
-	if m == nil {
-		return nil
-	}
-
-	t := new(SessionTokenBody)
-
-	switch v := m.GetContext().(type) {
+		err = errors.Errorf("unknown session context %T", val)
 	case nil:
 	case *session.SessionToken_Body_Object:
-		t.SetContext(
-			ObjectSessionContextFromGRPCMessage(v.Object),
-		)
-	default:
-		panic(fmt.Sprintf("unknown session context %T", v))
+		ctx, ok := t.ctx.(*ObjectSessionContext)
+		if !ok {
+			ctx = new(ObjectSessionContext)
+			t.ctx = ctx
+		}
+
+		err = ctx.FromGRPCMessage(val.Object)
 	}
 
-	t.SetID(m.GetId())
+	if err != nil {
+		return err
+	}
 
-	t.SetOwnerID(
-		refs.OwnerIDFromGRPCMessage(m.GetOwnerId()),
-	)
+	ownerID := v.GetOwnerId()
+	if ownerID == nil {
+		t.ownerID = nil
+	} else {
+		if t.ownerID == nil {
+			t.ownerID = new(refs.OwnerID)
+		}
 
-	t.SetLifetime(
-		TokenLifetimeFromGRPCMessage(m.GetLifetime()),
-	)
+		err = t.ownerID.FromGRPCMessage(ownerID)
+		if err != nil {
+			return err
+		}
+	}
 
-	t.SetSessionKey(m.GetSessionKey())
+	lifetime := v.GetLifetime()
+	if lifetime == nil {
+		t.lifetime = nil
+	} else {
+		if t.lifetime == nil {
+			t.lifetime = new(TokenLifetime)
+		}
 
-	return t
+		err = t.lifetime.FromGRPCMessage(lifetime)
+		if err != nil {
+			return err
+		}
+	}
+
+	t.id = v.GetId()
+	t.sessionKey = v.GetSessionKey()
+
+	return nil
 }
diff --git a/v2/session/json.go b/v2/session/json.go
index 2dea2d4..e296887 100644
--- a/v2/session/json.go
+++ b/v2/session/json.go
@@ -1,56 +1,29 @@
 package session
 
 import (
+	"github.com/nspcc-dev/neofs-api-go/rpc/message"
 	session "github.com/nspcc-dev/neofs-api-go/v2/session/grpc"
 	"google.golang.org/protobuf/encoding/protojson"
 )
 
 func (c *ObjectSessionContext) MarshalJSON() ([]byte, error) {
-	return protojson.MarshalOptions{
-		EmitUnpopulated: true,
-	}.Marshal(
-		ObjectSessionContextToGRPCMessage(c),
-	)
+	return message.MarshalJSON(c)
 }
 
 func (c *ObjectSessionContext) UnmarshalJSON(data []byte) error {
-	msg := new(session.ObjectSessionContext)
-
-	if err := protojson.Unmarshal(data, msg); err != nil {
-		return err
-	}
-
-	*c = *ObjectSessionContextFromGRPCMessage(msg)
-
-	return nil
+	return message.UnmarshalJSON(c, data, new(session.ObjectSessionContext))
 }
 
 func (l *TokenLifetime) MarshalJSON() ([]byte, error) {
-	return protojson.MarshalOptions{
-		EmitUnpopulated: true,
-	}.Marshal(
-		TokenLifetimeToGRPCMessage(l),
-	)
+	return message.MarshalJSON(l)
 }
 
 func (l *TokenLifetime) UnmarshalJSON(data []byte) error {
-	msg := new(session.SessionToken_Body_TokenLifetime)
-
-	if err := protojson.Unmarshal(data, msg); err != nil {
-		return err
-	}
-
-	*l = *TokenLifetimeFromGRPCMessage(msg)
-
-	return nil
+	return message.UnmarshalJSON(l, data, new(session.SessionToken_Body_TokenLifetime))
 }
 
 func (t *SessionTokenBody) MarshalJSON() ([]byte, error) {
-	return protojson.MarshalOptions{
-		EmitUnpopulated: true,
-	}.Marshal(
-		SessionTokenBodyToGRPCMessage(t),
-	)
+	return message.MarshalJSON(t)
 }
 
 func (t *SessionTokenBody) UnmarshalJSON(data []byte) error {
@@ -60,17 +33,11 @@ func (t *SessionTokenBody) UnmarshalJSON(data []byte) error {
 		return err
 	}
 
-	*t = *SessionTokenBodyFromGRPCMessage(msg)
-
-	return nil
+	return t.FromGRPCMessage(msg)
 }
 
 func (t *SessionToken) MarshalJSON() ([]byte, error) {
-	return protojson.MarshalOptions{
-		EmitUnpopulated: true,
-	}.Marshal(
-		SessionTokenToGRPCMessage(t),
-	)
+	return message.MarshalJSON(t)
 }
 
 func (t *SessionToken) UnmarshalJSON(data []byte) error {
@@ -80,17 +47,11 @@ func (t *SessionToken) UnmarshalJSON(data []byte) error {
 		return err
 	}
 
-	*t = *SessionTokenFromGRPCMessage(msg)
-
-	return nil
+	return t.FromGRPCMessage(msg)
 }
 
 func (x *XHeader) MarshalJSON() ([]byte, error) {
-	return protojson.MarshalOptions{
-		EmitUnpopulated: true,
-	}.Marshal(
-		XHeaderToGRPCMessage(x),
-	)
+	return message.MarshalJSON(x)
 }
 
 func (x *XHeader) UnmarshalJSON(data []byte) error {
@@ -100,17 +61,11 @@ func (x *XHeader) UnmarshalJSON(data []byte) error {
 		return err
 	}
 
-	*x = *XHeaderFromGRPCMessage(msg)
-
-	return nil
+	return x.FromGRPCMessage(msg)
 }
 
 func (r *RequestMetaHeader) MarshalJSON() ([]byte, error) {
-	return protojson.MarshalOptions{
-		EmitUnpopulated: true,
-	}.Marshal(
-		RequestMetaHeaderToGRPCMessage(r),
-	)
+	return message.MarshalJSON(r)
 }
 
 func (r *RequestMetaHeader) UnmarshalJSON(data []byte) error {
@@ -120,17 +75,11 @@ func (r *RequestMetaHeader) UnmarshalJSON(data []byte) error {
 		return err
 	}
 
-	*r = *RequestMetaHeaderFromGRPCMessage(msg)
-
-	return nil
+	return r.FromGRPCMessage(msg)
 }
 
 func (r *RequestVerificationHeader) MarshalJSON() ([]byte, error) {
-	return protojson.MarshalOptions{
-		EmitUnpopulated: true,
-	}.Marshal(
-		RequestVerificationHeaderToGRPCMessage(r),
-	)
+	return message.MarshalJSON(r)
 }
 
 func (r *RequestVerificationHeader) UnmarshalJSON(data []byte) error {
@@ -140,17 +89,11 @@ func (r *RequestVerificationHeader) UnmarshalJSON(data []byte) error {
 		return err
 	}
 
-	*r = *RequestVerificationHeaderFromGRPCMessage(msg)
-
-	return nil
+	return r.FromGRPCMessage(msg)
 }
 
 func (r *ResponseMetaHeader) MarshalJSON() ([]byte, error) {
-	return protojson.MarshalOptions{
-		EmitUnpopulated: true,
-	}.Marshal(
-		ResponseMetaHeaderToGRPCMessage(r),
-	)
+	return message.MarshalJSON(r)
 }
 
 func (r *ResponseMetaHeader) UnmarshalJSON(data []byte) error {
@@ -160,17 +103,11 @@ func (r *ResponseMetaHeader) UnmarshalJSON(data []byte) error {
 		return err
 	}
 
-	*r = *ResponseMetaHeaderFromGRPCMessage(msg)
-
-	return nil
+	return r.FromGRPCMessage(msg)
 }
 
 func (r *ResponseVerificationHeader) MarshalJSON() ([]byte, error) {
-	return protojson.MarshalOptions{
-		EmitUnpopulated: true,
-	}.Marshal(
-		ResponseVerificationHeaderToGRPCMessage(r),
-	)
+	return message.MarshalJSON(r)
 }
 
 func (r *ResponseVerificationHeader) UnmarshalJSON(data []byte) error {
@@ -180,7 +117,5 @@ func (r *ResponseVerificationHeader) UnmarshalJSON(data []byte) error {
 		return err
 	}
 
-	*r = *ResponseVerificationHeaderFromGRPCMessage(msg)
-
-	return nil
+	return r.FromGRPCMessage(msg)
 }
diff --git a/v2/session/json_test.go b/v2/session/json_test.go
deleted file mode 100644
index 00bf382..0000000
--- a/v2/session/json_test.go
+++ /dev/null
@@ -1,116 +0,0 @@
-package session_test
-
-import (
-	"testing"
-
-	"github.com/nspcc-dev/neofs-api-go/v2/session"
-	"github.com/stretchr/testify/require"
-)
-
-func TestChecksumJSON(t *testing.T) {
-	ctx := generateObjectCtx("id")
-
-	data, err := ctx.MarshalJSON()
-	require.NoError(t, err)
-
-	ctx2 := new(session.ObjectSessionContext)
-	require.NoError(t, ctx2.UnmarshalJSON(data))
-
-	require.Equal(t, ctx, ctx2)
-}
-
-func TestTokenLifetimeJSON(t *testing.T) {
-	l := generateLifetime(1, 2, 3)
-
-	data, err := l.MarshalJSON()
-	require.NoError(t, err)
-
-	l2 := new(session.TokenLifetime)
-	require.NoError(t, l2.UnmarshalJSON(data))
-
-	require.Equal(t, l, l2)
-}
-
-func TestSessionTokenBodyJSON(t *testing.T) {
-	b := generateSessionTokenBody("id")
-
-	data, err := b.MarshalJSON()
-	require.NoError(t, err)
-
-	b2 := new(session.SessionTokenBody)
-	require.NoError(t, b2.UnmarshalJSON(data))
-
-	require.Equal(t, b, b2)
-}
-
-func TestSessionTokenJSON(t *testing.T) {
-	tok := generateSessionToken("id")
-
-	data, err := tok.MarshalJSON()
-	require.NoError(t, err)
-
-	tok2 := new(session.SessionToken)
-	require.NoError(t, tok2.UnmarshalJSON(data))
-
-	require.Equal(t, tok, tok2)
-}
-
-func TestXHeaderJSON(t *testing.T) {
-	x := generateXHeader("key", "value")
-
-	data, err := x.MarshalJSON()
-	require.NoError(t, err)
-
-	x2 := new(session.XHeader)
-	require.NoError(t, x2.UnmarshalJSON(data))
-
-	require.Equal(t, x, x2)
-}
-
-func TestRequestMetaHeaderJSON(t *testing.T) {
-	r := generateRequestMetaHeader(1, "bearer", "session")
-
-	data, err := r.MarshalJSON()
-	require.NoError(t, err)
-
-	r2 := new(session.RequestMetaHeader)
-	require.NoError(t, r2.UnmarshalJSON(data))
-
-	require.Equal(t, r, r2)
-}
-
-func TestRequestVerificationHeaderJSON(t *testing.T) {
-	r := generateRequestVerificationHeader("key", "value")
-
-	data, err := r.MarshalJSON()
-	require.NoError(t, err)
-
-	r2 := new(session.RequestVerificationHeader)
-	require.NoError(t, r2.UnmarshalJSON(data))
-
-	require.Equal(t, r, r2)
-}
-
-func TestResponseMetaHeaderJSON(t *testing.T) {
-	r := generateResponseMetaHeader(1)
-
-	data, err := r.MarshalJSON()
-	require.NoError(t, err)
-
-	r2 := new(session.ResponseMetaHeader)
-	require.NoError(t, r2.UnmarshalJSON(data))
-
-	require.Equal(t, r, r2)
-}
-
-func TestResponseVerificationHeaderJSON(t *testing.T) {
-	r := generateResponseVerificationHeader("key", "value")
-
-	data, err := r.MarshalJSON()
-	require.NoError(t, err)
-
-	r2 := new(session.ResponseVerificationHeader)
-	require.NoError(t, r2.UnmarshalJSON(data))
-
-	require.Equal(t, r, r2)
-}
diff --git a/v2/session/marshal.go b/v2/session/marshal.go
index 568dfed..b6ea2c0 100644
--- a/v2/session/marshal.go
+++ b/v2/session/marshal.go
@@ -1,6 +1,7 @@
 package session
 
 import (
+	"github.com/nspcc-dev/neofs-api-go/rpc/message"
 	"github.com/nspcc-dev/neofs-api-go/util/proto"
 	session "github.com/nspcc-dev/neofs-api-go/v2/session/grpc"
 	goproto "google.golang.org/protobuf/proto"
@@ -97,6 +98,10 @@ func (c *CreateRequestBody) StableSize() (size int) {
 	return size
 }
 
+func (c *CreateRequestBody) Unmarshal(data []byte) error {
+	return message.Unmarshal(c, data, new(session.CreateRequest_Body))
+}
+
 func (c *CreateResponseBody) StableMarshal(buf []byte) ([]byte, error) {
 	if c == nil {
 		return []byte{}, nil
@@ -137,6 +142,10 @@ func (c *CreateResponseBody) StableSize() (size int) {
 	return size
 }
 
+func (c *CreateResponseBody) Unmarshal(data []byte) error {
+	return message.Unmarshal(c, data, new(session.CreateResponse_Body))
+}
+
 func (x *XHeader) StableMarshal(buf []byte) ([]byte, error) {
 	if x == nil {
 		return []byte{}, nil
@@ -183,9 +192,7 @@ func (x *XHeader) Unmarshal(data []byte) error {
 		return err
 	}
 
-	*x = *XHeaderFromGRPCMessage(m)
-
-	return nil
+	return x.FromGRPCMessage(m)
 }
 
 func (l *TokenLifetime) StableMarshal(buf []byte) ([]byte, error) {
@@ -242,9 +249,7 @@ func (l *TokenLifetime) Unmarshal(data []byte) error {
 		return err
 	}
 
-	*l = *TokenLifetimeFromGRPCMessage(m)
-
-	return nil
+	return l.FromGRPCMessage(m)
 }
 
 func (c *ObjectSessionContext) StableMarshal(buf []byte) ([]byte, error) {
@@ -293,9 +298,7 @@ func (c *ObjectSessionContext) Unmarshal(data []byte) error {
 		return err
 	}
 
-	*c = *ObjectSessionContextFromGRPCMessage(m)
-
-	return nil
+	return c.FromGRPCMessage(m)
 }
 
 func (t *SessionTokenBody) StableMarshal(buf []byte) ([]byte, error) {
@@ -383,9 +386,7 @@ func (t *SessionTokenBody) Unmarshal(data []byte) error {
 		return err
 	}
 
-	*t = *SessionTokenBodyFromGRPCMessage(m)
-
-	return nil
+	return t.FromGRPCMessage(m)
 }
 
 func (t *SessionToken) StableMarshal(buf []byte) ([]byte, error) {
@@ -434,9 +435,7 @@ func (t *SessionToken) Unmarshal(data []byte) error {
 		return err
 	}
 
-	*t = *SessionTokenFromGRPCMessage(m)
-
-	return nil
+	return t.FromGRPCMessage(m)
 }
 
 func (r *RequestMetaHeader) StableMarshal(buf []byte) ([]byte, error) {
@@ -534,9 +533,7 @@ func (r *RequestMetaHeader) Unmarshal(data []byte) error {
 		return err
 	}
 
-	*r = *RequestMetaHeaderFromGRPCMessage(m)
-
-	return nil
+	return r.FromGRPCMessage(m)
 }
 
 func (r *RequestVerificationHeader) StableMarshal(buf []byte) ([]byte, error) {
@@ -601,9 +598,7 @@ func (r *RequestVerificationHeader) Unmarshal(data []byte) error {
 		return err
 	}
 
-	*r = *RequestVerificationHeaderFromGRPCMessage(m)
-
-	return nil
+	return r.FromGRPCMessage(m)
 }
 
 func (r *ResponseMetaHeader) StableMarshal(buf []byte) ([]byte, error) {
@@ -685,9 +680,7 @@ func (r *ResponseMetaHeader) Unmarshal(data []byte) error {
 		return err
 	}
 
-	*r = *ResponseMetaHeaderFromGRPCMessage(m)
-
-	return nil
+	return r.FromGRPCMessage(m)
 }
 
 func (r *ResponseVerificationHeader) StableMarshal(buf []byte) ([]byte, error) {
@@ -752,7 +745,5 @@ func (r *ResponseVerificationHeader) Unmarshal(data []byte) error {
 		return err
 	}
 
-	*r = *ResponseVerificationHeaderFromGRPCMessage(m)
-
-	return nil
+	return r.FromGRPCMessage(m)
 }
diff --git a/v2/session/marshal_test.go b/v2/session/marshal_test.go
deleted file mode 100644
index a2b5d9d..0000000
--- a/v2/session/marshal_test.go
+++ /dev/null
@@ -1,385 +0,0 @@
-package session_test
-
-import (
-	"fmt"
-	"testing"
-
-	"github.com/nspcc-dev/neofs-api-go/v2/acl"
-	"github.com/nspcc-dev/neofs-api-go/v2/refs"
-	"github.com/nspcc-dev/neofs-api-go/v2/session"
-	grpc "github.com/nspcc-dev/neofs-api-go/v2/session/grpc"
-	"github.com/stretchr/testify/require"
-	goproto "google.golang.org/protobuf/proto"
-)
-
-func TestCreateRequestBody_StableMarshal(t *testing.T) {
-	requestFrom := generateCreateSessionRequestBody("Owner ID")
-	transport := new(grpc.CreateRequest_Body)
-
-	t.Run("non empty", func(t *testing.T) {
-		wire, err := requestFrom.StableMarshal(nil)
-		require.NoError(t, err)
-
-		err = goproto.Unmarshal(wire, transport)
-		require.NoError(t, err)
-
-		requestTo := session.CreateRequestBodyFromGRPCMessage(transport)
-		require.Equal(t, requestFrom, requestTo)
-	})
-}
-
-func TestCreateResponseBody_StableMarshal(t *testing.T) {
-	responseFrom := generateCreateSessionResponseBody("ID", "Session Public Key")
-	transport := new(grpc.CreateResponse_Body)
-
-	t.Run("non empty", func(t *testing.T) {
-		wire, err := responseFrom.StableMarshal(nil)
-		require.NoError(t, err)
-
-		err = goproto.Unmarshal(wire, transport)
-		require.NoError(t, err)
-
-		responseTo := session.CreateResponseBodyFromGRPCMessage(transport)
-		require.Equal(t, responseFrom, responseTo)
-	})
-}
-
-func TestXHeader_StableMarshal(t *testing.T) {
-	xheaderFrom := generateXHeader("X-Header-Key", "X-Header-Value")
-
-	t.Run("non empty", func(t *testing.T) {
-		wire, err := xheaderFrom.StableMarshal(nil)
-		require.NoError(t, err)
-
-		xheaderTo := new(session.XHeader)
-		require.NoError(t, xheaderTo.Unmarshal(wire))
-
-		require.Equal(t, xheaderFrom, xheaderTo)
-	})
-}
-
-func TestTokenLifetime_StableMarshal(t *testing.T) {
-	lifetimeFrom := generateLifetime(10, 20, 30)
-
-	t.Run("non empty", func(t *testing.T) {
-		wire, err := lifetimeFrom.StableMarshal(nil)
-		require.NoError(t, err)
-
-		lifetimeTo := new(session.TokenLifetime)
-		require.NoError(t, lifetimeTo.Unmarshal(wire))
-
-		require.Equal(t, lifetimeFrom, lifetimeTo)
-	})
-}
-
-func TestObjectSessionContext_StableMarshal(t *testing.T) {
-	objectCtxFrom := generateObjectCtx("Object ID")
-
-	t.Run("non empty", func(t *testing.T) {
-		wire, err := objectCtxFrom.StableMarshal(nil)
-		require.NoError(t, err)
-
-		objectCtxTo := new(session.ObjectSessionContext)
-		require.NoError(t, objectCtxTo.Unmarshal(wire))
-
-		require.Equal(t, objectCtxFrom, objectCtxTo)
-	})
-}
-
-func TestSessionTokenBody_StableMarshal(t *testing.T) {
-	sessionTokenBodyFrom := generateSessionTokenBody("Session Token Body")
-
-	t.Run("non empty", func(t *testing.T) {
-		wire, err := sessionTokenBodyFrom.StableMarshal(nil)
-		require.NoError(t, err)
-
-		sessionTokenBodyTo := new(session.SessionTokenBody)
-		require.NoError(t, sessionTokenBodyTo.Unmarshal(wire))
-
-		require.Equal(t, sessionTokenBodyFrom, sessionTokenBodyTo)
-	})
-}
-
-func TestSessionToken_StableMarshal(t *testing.T) {
-	sessionTokenFrom := generateSessionToken("Session Token")
-
-	t.Run("non empty", func(t *testing.T) {
-		wire, err := sessionTokenFrom.StableMarshal(nil)
-		require.NoError(t, err)
-
-		sessionTokenTo := new(session.SessionToken)
-		require.NoError(t, sessionTokenTo.Unmarshal(wire))
-
-		require.Equal(t, sessionTokenFrom, sessionTokenTo)
-	})
-}
-
-func TestRequestMetaHeader_StableMarshal(t *testing.T) {
-	metaHeaderOrigin := generateRequestMetaHeader(10, "Bearer One", "Session One")
-	metaHeaderFrom := generateRequestMetaHeader(20, "Bearer Two", "Session Two")
-	metaHeaderFrom.SetOrigin(metaHeaderOrigin)
-
-	t.Run("non empty", func(t *testing.T) {
-		wire, err := metaHeaderFrom.StableMarshal(nil)
-		require.NoError(t, err)
-
-		metaHeaderTo := new(session.RequestMetaHeader)
-		require.NoError(t, metaHeaderTo.Unmarshal(wire))
-
-		require.Equal(t, metaHeaderFrom, metaHeaderTo)
-	})
-}
-
-func TestRequestVerificationHeader_StableMarshal(t *testing.T) {
-	verifHeaderOrigin := generateRequestVerificationHeader("Key", "Inside")
-	verifHeaderFrom := generateRequestVerificationHeader("Value", "Outside")
-	verifHeaderFrom.SetOrigin(verifHeaderOrigin)
-
-	t.Run("non empty", func(t *testing.T) {
-		wire, err := verifHeaderFrom.StableMarshal(nil)
-		require.NoError(t, err)
-
-		verifHeaderTo := new(session.RequestVerificationHeader)
-		require.NoError(t, verifHeaderTo.Unmarshal(wire))
-
-		require.Equal(t, verifHeaderFrom, verifHeaderTo)
-	})
-}
-
-func TestResponseMetaHeader_StableMarshal(t *testing.T) {
-	metaHeaderOrigin := generateResponseMetaHeader(10)
-	metaHeaderFrom := generateResponseMetaHeader(20)
-	metaHeaderFrom.SetOrigin(metaHeaderOrigin)
-
-	t.Run("non empty", func(t *testing.T) {
-		wire, err := metaHeaderFrom.StableMarshal(nil)
-		require.NoError(t, err)
-
-		metaHeaderTo := new(session.ResponseMetaHeader)
-		require.NoError(t, metaHeaderTo.Unmarshal(wire))
-
-		require.Equal(t, metaHeaderFrom, metaHeaderTo)
-	})
-}
-
-func TestResponseVerificationHeader_StableMarshal(t *testing.T) {
-	verifHeaderOrigin := generateResponseVerificationHeader("Key", "Inside")
-	verifHeaderFrom := generateResponseVerificationHeader("Value", "Outside")
-	verifHeaderFrom.SetOrigin(verifHeaderOrigin)
-
-	t.Run("non empty", func(t *testing.T) {
-		wire, err := verifHeaderFrom.StableMarshal(nil)
-		require.NoError(t, err)
-
-		verifHeaderTo := new(session.ResponseVerificationHeader)
-		require.NoError(t, verifHeaderTo.Unmarshal(wire))
-
-		require.Equal(t, verifHeaderFrom, verifHeaderTo)
-	})
-}
-
-func generateCreateSessionRequestBody(id string) *session.CreateRequestBody {
-	lifetime := new(session.TokenLifetime)
-	lifetime.SetIat(1)
-	lifetime.SetNbf(2)
-	lifetime.SetExp(3)
-
-	owner := new(refs.OwnerID)
-	owner.SetValue([]byte(id))
-
-	s := new(session.CreateRequestBody)
-	s.SetOwnerID(owner)
-	s.SetExpiration(10)
-
-	return s
-}
-
-func generateCreateSessionResponseBody(id, key string) *session.CreateResponseBody {
-	s := new(session.CreateResponseBody)
-	s.SetID([]byte(id))
-	s.SetSessionKey([]byte(key))
-
-	return s
-}
-
-func generateSignature(k, v string) *refs.Signature {
-	sig := new(refs.Signature)
-	sig.SetKey([]byte(k))
-	sig.SetSign([]byte(v))
-
-	return sig
-}
-
-func generateVersion(maj, min uint32) *refs.Version {
-	version := new(refs.Version)
-	version.SetMajor(maj)
-	version.SetMinor(min)
-
-	return version
-}
-
-func generateXHeader(k, v string) *session.XHeader {
-	xheader := new(session.XHeader)
-	xheader.SetKey(k)
-	xheader.SetValue(v)
-
-	return xheader
-}
-
-func generateLifetime(exp, nbf, iat uint64) *session.TokenLifetime {
-	lifetime := new(session.TokenLifetime)
-	lifetime.SetExp(exp)
-	lifetime.SetNbf(nbf)
-	lifetime.SetIat(iat)
-
-	return lifetime
-}
-
-func generateBearerLifetime(exp, nbf, iat uint64) *acl.TokenLifetime {
-	lifetime := new(acl.TokenLifetime)
-	lifetime.SetExp(exp)
-	lifetime.SetNbf(nbf)
-	lifetime.SetIat(iat)
-
-	return lifetime
-}
-
-func generateObjectCtx(id string) *session.ObjectSessionContext {
-	objectCtx := new(session.ObjectSessionContext)
-	objectCtx.SetVerb(session.ObjectVerbPut)
-
-	cid := new(refs.ContainerID)
-	cid.SetValue([]byte("ContainerID"))
-
-	oid := new(refs.ObjectID)
-	oid.SetValue([]byte(id))
-
-	addr := new(refs.Address)
-	addr.SetContainerID(cid)
-	addr.SetObjectID(oid)
-
-	objectCtx.SetAddress(addr)
-
-	return objectCtx
-}
-
-func generateEACL(n int, k, v string) *acl.Table {
-	target := new(acl.Target)
-	target.SetRole(acl.RoleUser)
-
-	keys := make([][]byte, n)
-
-	for i := 0; i < n; i++ {
-		s := fmt.Sprintf("Public Key %d", i+1)
-		keys[i] = []byte(s)
-	}
-
-	filter := new(acl.HeaderFilter)
-	filter.SetHeaderType(acl.HeaderTypeObject)
-	filter.SetMatchType(acl.MatchTypeStringEqual)
-	filter.SetKey(k)
-	filter.SetValue(v)
-
-	record := new(acl.Record)
-	record.SetOperation(acl.OperationHead)
-	record.SetAction(acl.ActionDeny)
-	record.SetTargets([]*acl.Target{target})
-	record.SetFilters([]*acl.HeaderFilter{filter})
-
-	table := new(acl.Table)
-	cid := new(refs.ContainerID)
-	cid.SetValue([]byte("Container ID"))
-
-	table.SetContainerID(cid)
-	table.SetRecords([]*acl.Record{record})
-
-	return table
-}
-
-func generateSessionTokenBody(id string) *session.SessionTokenBody {
-	owner := new(refs.OwnerID)
-	owner.SetValue([]byte("Owner ID"))
-
-	tokenBody := new(session.SessionTokenBody)
-	tokenBody.SetID([]byte(id))
-	tokenBody.SetOwnerID(owner)
-	tokenBody.SetSessionKey([]byte(id))
-	tokenBody.SetLifetime(generateLifetime(1, 2, 3))
-	tokenBody.SetContext(generateObjectCtx(id))
-
-	return tokenBody
-}
-
-func generateSessionToken(id string) *session.SessionToken {
-	sessionToken := new(session.SessionToken)
-	sessionToken.SetBody(generateSessionTokenBody(id))
-	sessionToken.SetSignature(generateSignature("id", id))
-
-	return sessionToken
-}
-
-func generateBearerTokenBody(id string) *acl.BearerTokenBody {
-	owner := new(refs.OwnerID)
-	owner.SetValue([]byte(id))
-
-	tokenBody := new(acl.BearerTokenBody)
-	tokenBody.SetOwnerID(owner)
-	tokenBody.SetLifetime(generateBearerLifetime(1, 2, 3))
-	tokenBody.SetEACL(generateEACL(10, "id", id))
-
-	return tokenBody
-}
-
-func generateBearerToken(id string) *acl.BearerToken {
-	bearerToken := new(acl.BearerToken)
-	bearerToken.SetBody(generateBearerTokenBody(id))
-	bearerToken.SetSignature(generateSignature("id", id))
-
-	return bearerToken
-}
-
-func generateRequestMetaHeader(n int, b, s string) *session.RequestMetaHeader {
-	reqMetaHeader := new(session.RequestMetaHeader)
-	reqMetaHeader.SetVersion(generateVersion(2, 0))
-	reqMetaHeader.SetEpoch(uint64(n))
-	reqMetaHeader.SetTTL(uint32(n))
-	reqMetaHeader.SetXHeaders([]*session.XHeader{
-		generateXHeader("key-one", "val-one"),
-		generateXHeader("key-two", "val-two"),
-	})
-	reqMetaHeader.SetBearerToken(generateBearerToken(b))
-	reqMetaHeader.SetSessionToken(generateSessionToken(s))
-
-	return reqMetaHeader
-}
-
-func generateRequestVerificationHeader(k, v string) *session.RequestVerificationHeader {
-	reqVerifHeader := new(session.RequestVerificationHeader)
-	reqVerifHeader.SetBodySignature(generateSignature(k+"body", v+"body"))
-	reqVerifHeader.SetMetaSignature(generateSignature(k+"meta", v+"meta"))
-	reqVerifHeader.SetOriginSignature(generateSignature(k+"orig", v+"orig"))
-
-	return reqVerifHeader
-}
-
-func generateResponseMetaHeader(n int) *session.ResponseMetaHeader {
-	respMetaHeader := new(session.ResponseMetaHeader)
-	respMetaHeader.SetVersion(generateVersion(2, 0))
-	respMetaHeader.SetEpoch(uint64(n))
-	respMetaHeader.SetTTL(uint32(n))
-	respMetaHeader.SetXHeaders([]*session.XHeader{
-		generateXHeader("key-one", "val-one"),
-		generateXHeader("key-two", "val-two"),
-	})
-
-	return respMetaHeader
-}
-
-func generateResponseVerificationHeader(k, v string) *session.ResponseVerificationHeader {
-	respVerifHeader := new(session.ResponseVerificationHeader)
-	respVerifHeader.SetBodySignature(generateSignature(k+"body", v+"body"))
-	respVerifHeader.SetMetaSignature(generateSignature(k+"meta", v+"meta"))
-	respVerifHeader.SetOriginSignature(generateSignature(k+"orig", v+"orig"))
-
-	return respVerifHeader
-}
diff --git a/v2/session/message_test.go b/v2/session/message_test.go
new file mode 100644
index 0000000..ea9f1cc
--- /dev/null
+++ b/v2/session/message_test.go
@@ -0,0 +1,26 @@
+package session_test
+
+import (
+	"testing"
+
+	"github.com/nspcc-dev/neofs-api-go/rpc/message"
+	rpctest "github.com/nspcc-dev/neofs-api-go/rpc/message/test"
+	sessiontest "github.com/nspcc-dev/neofs-api-go/v2/session/test"
+)
+
+func TestMessageConvert(t *testing.T) {
+	rpctest.TestRPCMessage(t,
+		func(empty bool) message.Message { return sessiontest.GenerateCreateRequestBody(empty) },
+		func(empty bool) message.Message { return sessiontest.GenerateCreateRequest(empty) },
+		func(empty bool) message.Message { return sessiontest.GenerateCreateResponseBody(empty) },
+		func(empty bool) message.Message { return sessiontest.GenerateCreateResponse(empty) },
+		func(empty bool) message.Message { return sessiontest.GenerateTokenLifetime(empty) },
+		func(empty bool) message.Message { return sessiontest.GenerateXHeader(empty) },
+		func(empty bool) message.Message { return sessiontest.GenerateSessionTokenBody(empty) },
+		func(empty bool) message.Message { return sessiontest.GenerateSessionToken(empty) },
+		func(empty bool) message.Message { return sessiontest.GenerateRequestMetaHeader(empty) },
+		func(empty bool) message.Message { return sessiontest.GenerateRequestVerificationHeader(empty) },
+		func(empty bool) message.Message { return sessiontest.GenerateResponseMetaHeader(empty) },
+		func(empty bool) message.Message { return sessiontest.GenerateResponseVerificationHeader(empty) },
+	)
+}
diff --git a/v2/session/service.go b/v2/session/service.go
deleted file mode 100644
index 5a07e2d..0000000
--- a/v2/session/service.go
+++ /dev/null
@@ -1,25 +0,0 @@
-package session
-
-import (
-	"context"
-)
-
-type Service interface {
-	Create(context.Context, *CreateRequest) (*CreateResponse, error)
-}
-
-type CreateRequest struct {
-	body *CreateRequestBody
-
-	metaHeader *RequestMetaHeader
-
-	verifyHeader *RequestVerificationHeader
-}
-
-type CreateResponse struct {
-	body *CreateResponseBody
-
-	metaHeader *ResponseMetaHeader
-
-	verifyHeader *ResponseVerificationHeader
-}
diff --git a/v2/session/test/client_test.go b/v2/session/test/client_test.go
deleted file mode 100644
index 9ba4029..0000000
--- a/v2/session/test/client_test.go
+++ /dev/null
@@ -1,159 +0,0 @@
-package main
-
-import (
-	"context"
-	"crypto/ecdsa"
-	"errors"
-	"testing"
-
-	"github.com/nspcc-dev/neofs-api-go/v2/refs"
-	"github.com/nspcc-dev/neofs-api-go/v2/session"
-	sessionGRPC "github.com/nspcc-dev/neofs-api-go/v2/session/grpc"
-	"github.com/nspcc-dev/neofs-api-go/v2/signature"
-	"github.com/nspcc-dev/neofs-crypto/test"
-	"github.com/stretchr/testify/require"
-	"google.golang.org/grpc"
-)
-
-type testGRPCClient struct {
-	server *testGRPCServer
-}
-
-type testGRPCServer struct {
-	key  *ecdsa.PrivateKey
-	resp *session.CreateResponse
-	err  error
-}
-
-func (s *testGRPCClient) Create(ctx context.Context, in *sessionGRPC.CreateRequest, opts ...grpc.CallOption) (*sessionGRPC.CreateResponse, error) {
-	return s.server.Create(ctx, in)
-}
-
-func (s *testGRPCServer) Create(_ context.Context, req *sessionGRPC.CreateRequest) (*sessionGRPC.CreateResponse, error) {
-	if s.err != nil {
-		return nil, s.err
-	}
-
-	// verify request structure
-	if err := signature.VerifyServiceMessage(
-		session.CreateRequestFromGRPCMessage(req),
-	); err != nil {
-		return nil, err
-	}
-
-	// sign response structure
-	if err := signature.SignServiceMessage(s.key, s.resp); err != nil {
-		return nil, err
-	}
-
-	return session.CreateResponseToGRPCMessage(s.resp), nil
-}
-
-func testRequest() *session.CreateRequest {
-	ownerID := new(refs.OwnerID)
-	ownerID.SetValue([]byte{1, 2, 3})
-
-	body := new(session.CreateRequestBody)
-	body.SetOwnerID(ownerID)
-
-	meta := new(session.RequestMetaHeader)
-	meta.SetTTL(1)
-
-	req := new(session.CreateRequest)
-	req.SetBody(body)
-	req.SetMetaHeader(meta)
-
-	return req
-}
-
-func testResponse() *session.CreateResponse {
-	body := new(session.CreateResponseBody)
-	body.SetID([]byte{1, 2, 3})
-
-	meta := new(session.ResponseMetaHeader)
-	meta.SetTTL(1)
-
-	resp := new(session.CreateResponse)
-	resp.SetBody(body)
-	resp.SetMetaHeader(meta)
-
-	return resp
-}
-
-func TestGRPCClient(t *testing.T) {
-	ctx := context.TODO()
-
-	cliKey := test.DecodeKey(0)
-	srvKey := test.DecodeKey(1)
-
-	t.Run("gRPC server error", func(t *testing.T) {
-		srvErr := errors.New("test server error")
-
-		srv := &testGRPCServer{
-			err: srvErr,
-		}
-
-		cli := &testGRPCClient{
-			server: srv,
-		}
-
-		c, err := session.NewClient(session.WithGRPCServiceClient(cli))
-		require.NoError(t, err)
-
-		resp, err := c.Create(ctx, new(session.CreateRequest))
-		require.True(t, errors.Is(err, srvErr))
-		require.Nil(t, resp)
-	})
-
-	t.Run("invalid request structure", func(t *testing.T) {
-		req := testRequest()
-
-		require.Error(t, signature.VerifyServiceMessage(req))
-
-		c, err := session.NewClient(
-			session.WithGRPCServiceClient(
-				&testGRPCClient{
-					server: new(testGRPCServer),
-				},
-			),
-		)
-		require.NoError(t, err)
-
-		resp, err := c.Create(ctx, req)
-		require.Error(t, err)
-		require.Nil(t, resp)
-	})
-
-	t.Run("correct response", func(t *testing.T) {
-		req := testRequest()
-
-		require.NoError(t, signature.SignServiceMessage(cliKey, req))
-
-		resp := testResponse()
-
-		{ // w/o this require.Equal fails due to nil and []T{} difference
-			meta := new(session.ResponseMetaHeader)
-			meta.SetXHeaders([]*session.XHeader{})
-			resp.SetMetaHeader(meta)
-		}
-
-		c, err := session.NewClient(
-			session.WithGRPCServiceClient(
-				&testGRPCClient{
-					server: &testGRPCServer{
-						key:  srvKey,
-						resp: resp,
-					},
-				},
-			),
-		)
-		require.NoError(t, err)
-
-		r, err := c.Create(ctx, req)
-		require.NoError(t, err)
-
-		require.NoError(t, signature.VerifyServiceMessage(r))
-		require.Equal(t, resp.GetBody(), r.GetBody())
-		require.Equal(t, resp.GetMetaHeader(), r.GetMetaHeader())
-	})
-}
diff --git a/v2/session/test/generate.go b/v2/session/test/generate.go
new file mode 100644
index 0000000..d1a5b56
--- /dev/null
+++ b/v2/session/test/generate.go
@@ -0,0 +1,204 @@
+package sessiontest
+
+import (
+	acltest "github.com/nspcc-dev/neofs-api-go/v2/acl/test"
+	refstest "github.com/nspcc-dev/neofs-api-go/v2/refs/test"
+	"github.com/nspcc-dev/neofs-api-go/v2/session"
+)
+
+func GenerateCreateRequestBody(empty bool) *session.CreateRequestBody {
+	m := new(session.CreateRequestBody)
+
+	if !empty {
+		m.SetExpiration(555)
+	}
+
+	m.SetOwnerID(refstest.GenerateOwnerID(empty))
+
+	return m
+}
+
+func GenerateCreateRequest(empty bool) *session.CreateRequest {
+	m := new(session.CreateRequest)
+
+	m.SetBody(GenerateCreateRequestBody(empty))
+	m.SetMetaHeader(GenerateRequestMetaHeader(empty))
+	m.SetVerificationHeader(GenerateRequestVerificationHeader(empty))
+
+	return m
+}
+
+func GenerateCreateResponseBody(empty bool) *session.CreateResponseBody {
+	m := new(session.CreateResponseBody)
+
+	if !empty {
+		m.SetID([]byte{1, 2, 3})
+		m.SetSessionKey([]byte{4, 5, 6})
+	}
+
+	return m
+}
+
+func GenerateCreateResponse(empty bool) *session.CreateResponse {
+	m := new(session.CreateResponse)
+
+	m.SetBody(GenerateCreateResponseBody(empty))
+	m.SetMetaHeader(GenerateResponseMetaHeader(empty))
+	m.SetVerificationHeader(GenerateResponseVerificationHeader(empty))
+
+	return m
+}
+
+func GenerateResponseVerificationHeader(empty bool) *session.ResponseVerificationHeader {
+	return generateResponseVerificationHeader(empty, true)
+}
+
+func generateResponseVerificationHeader(empty, withOrigin bool) *session.ResponseVerificationHeader {
+	m := new(session.ResponseVerificationHeader)
+
+	m.SetBodySignature(refstest.GenerateSignature(empty))
+	m.SetMetaSignature(refstest.GenerateSignature(empty))
+	m.SetOriginSignature(refstest.GenerateSignature(empty))
+
+	if withOrigin {
+		m.SetOrigin(generateResponseVerificationHeader(empty, false))
+	}
+
+	return m
+}
+
+func GenerateResponseMetaHeader(empty bool) *session.ResponseMetaHeader {
+	return generateResponseMetaHeader(empty, true)
+}
+
+func generateResponseMetaHeader(empty, withOrigin bool) *session.ResponseMetaHeader {
+	m := new(session.ResponseMetaHeader)
+
+	if !empty {
+		m.SetEpoch(13)
+		m.SetTTL(100)
+	}
+
+	m.SetXHeaders(GenerateXHeaders(empty))
+	m.SetVersion(refstest.GenerateVersion(empty))
+
+	if withOrigin {
+		m.SetOrigin(generateResponseMetaHeader(empty, false))
+	}
+
+	return m
+}
+
+func GenerateRequestVerificationHeader(empty bool) *session.RequestVerificationHeader {
+	return generateRequestVerificationHeader(empty, true)
+}
+
+func generateRequestVerificationHeader(empty, withOrigin bool) *session.RequestVerificationHeader {
+	m := new(session.RequestVerificationHeader)
+
+	m.SetBodySignature(refstest.GenerateSignature(empty))
+	m.SetMetaSignature(refstest.GenerateSignature(empty))
+	m.SetOriginSignature(refstest.GenerateSignature(empty))
+
+	if withOrigin {
+		m.SetOrigin(generateRequestVerificationHeader(empty, false))
+	}
+
+	return m
+}
+
+func GenerateRequestMetaHeader(empty bool) *session.RequestMetaHeader {
+	return generateRequestMetaHeader(empty, true)
+}
+
+func generateRequestMetaHeader(empty, withOrigin bool) *session.RequestMetaHeader {
+	m := new(session.RequestMetaHeader)
+
+	if !empty {
+		m.SetEpoch(13)
+		m.SetTTL(100)
+	}
+
+	m.SetXHeaders(GenerateXHeaders(empty))
+	m.SetVersion(refstest.GenerateVersion(empty))
+	m.SetSessionToken(GenerateSessionToken(empty))
+	m.SetBearerToken(acltest.GenerateBearerToken(empty))
+
+	if withOrigin {
+		m.SetOrigin(generateRequestMetaHeader(empty, false))
+	}
+
+	return m
+}
+
+func GenerateSessionToken(empty bool) *session.SessionToken {
+	m := new(session.SessionToken)
+
+	m.SetBody(GenerateSessionTokenBody(empty))
+	m.SetSignature(refstest.GenerateSignature(empty))
+
+	return m
+}
+
+func GenerateSessionTokenBody(empty bool) *session.SessionTokenBody {
+	m := new(session.SessionTokenBody)
+
+	if !empty {
+		m.SetID([]byte{1})
+		m.SetSessionKey([]byte{2})
+	}
+
+	m.SetOwnerID(refstest.GenerateOwnerID(empty))
+	m.SetLifetime(GenerateTokenLifetime(empty))
+	m.SetContext(GenerateObjectSessionContext(empty))
+
+	return m
+}
+
+func GenerateTokenLifetime(empty bool) *session.TokenLifetime {
+	m := new(session.TokenLifetime)
+
+	if !empty {
+		m.SetExp(1)
+		m.SetIat(2)
+		m.SetExp(3)
+	}
+
+	return m
+}
+
+func GenerateObjectSessionContext(empty bool) *session.ObjectSessionContext {
+	m := new(session.ObjectSessionContext)
+
+	if !empty {
+		m.SetVerb(session.ObjectVerbHead)
+	}
+
+	m.SetAddress(refstest.GenerateAddress(empty))
+
+	return m
+}
+
+func GenerateXHeader(empty bool) *session.XHeader {
+	m := new(session.XHeader)
+
+	if !empty {
+		m.SetKey("key")
+		m.SetValue("val")
+	}
+
+	return m
+}
+
+func GenerateXHeaders(empty bool) []*session.XHeader {
+	xs := make([]*session.XHeader, 0)
+
+	if !empty {
+		xs = append(xs,
+			GenerateXHeader(false),
+			GenerateXHeader(false),
+		)
+	}
+
+	return xs
+}
diff --git a/v2/session/types.go b/v2/session/types.go
index 0322b25..68c3a10 100644
--- a/v2/session/types.go
+++ b/v2/session/types.go
@@ -11,12 +11,24 @@ type CreateRequestBody struct {
 	expiration uint64
 }
 
+type CreateRequest struct {
+	body *CreateRequestBody
+
+	RequestHeaders
+}
+
 type CreateResponseBody struct {
 	id []byte
 
 	sessionKey []byte
 }
 
+type CreateResponse struct {
+	body *CreateResponseBody
+
+	ResponseHeaders
+}
+
 type XHeader struct {
 	key, val string
 }
diff --git a/v2/session/util.go b/v2/session/util.go
index 0235429..5ee46d3 100644
--- a/v2/session/util.go
+++ b/v2/session/util.go
@@ -1,5 +1,9 @@
 package session
 
+import (
+	session "github.com/nspcc-dev/neofs-api-go/v2/session/grpc"
+)
+
 // RequestHeaders represents common part of
 // all NeoFS requests including headers.
 type RequestHeaders struct {
@@ -40,6 +44,49 @@ func (c *RequestHeaders) SetVerificationHeader(v *RequestVerificationHeader) {
 	}
 }
 
+func (c *RequestHeaders) ToMessage(m interface {
+	SetMetaHeader(*session.RequestMetaHeader)
+	SetVerifyHeader(*session.RequestVerificationHeader)
+}) {
+	m.SetMetaHeader(c.metaHeader.ToGRPCMessage().(*session.RequestMetaHeader))
+	m.SetVerifyHeader(c.verifyHeader.ToGRPCMessage().(*session.RequestVerificationHeader))
+}
+
+func (c *RequestHeaders) FromMessage(m interface {
+	GetMetaHeader() *session.RequestMetaHeader
+	GetVerifyHeader() *session.RequestVerificationHeader
+}) error {
+	metaHdr := m.GetMetaHeader()
+	if metaHdr == nil {
+		c.metaHeader = nil
+	} else {
+		if c.metaHeader == nil {
+			c.metaHeader = new(RequestMetaHeader)
+		}
+
+		err := c.metaHeader.FromGRPCMessage(metaHdr)
+		if err != nil {
+			return err
+		}
+	}
+
+	verifyHdr := m.GetVerifyHeader()
+	if verifyHdr == nil {
+		c.verifyHeader = nil
+	} else {
+		if c.verifyHeader == nil {
+			c.verifyHeader = new(RequestVerificationHeader)
+		}
+
+		err := c.verifyHeader.FromGRPCMessage(verifyHdr)
+		if err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
 // ResponseHeaders represents common part of
 // all NeoFS responses including headers.
 type ResponseHeaders struct {
@@ -79,3 +126,46 @@ func (c *ResponseHeaders) SetVerificationHeader(v *ResponseVerificationHeader) {
 		c.verifyHeader = v
 	}
 }
+
+func (c *ResponseHeaders) ToMessage(m interface {
+	SetMetaHeader(*session.ResponseMetaHeader)
+	SetVerifyHeader(*session.ResponseVerificationHeader)
+}) {
+	m.SetMetaHeader(c.metaHeader.ToGRPCMessage().(*session.ResponseMetaHeader))
+	m.SetVerifyHeader(c.verifyHeader.ToGRPCMessage().(*session.ResponseVerificationHeader))
+}
+
+func (c *ResponseHeaders) FromMessage(m interface {
+	GetMetaHeader() *session.ResponseMetaHeader
+	GetVerifyHeader() *session.ResponseVerificationHeader
+}) error {
+	metaHdr := m.GetMetaHeader()
+	if metaHdr == nil {
+		c.metaHeader = nil
+	} else {
+		if c.metaHeader == nil {
+			c.metaHeader = new(ResponseMetaHeader)
+		}
+
+		err := c.metaHeader.FromGRPCMessage(metaHdr)
+		if err != nil {
+			return err
+		}
+	}
+
+	verifyHdr := m.GetVerifyHeader()
+	if verifyHdr == nil {
+		c.verifyHeader = nil
+	} else {
+		if c.verifyHeader == nil {
+			c.verifyHeader = new(ResponseVerificationHeader)
+		}
+
+		err := c.verifyHeader.FromGRPCMessage(verifyHdr)
+		if err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
diff --git a/v2/storagegroup/convert.go b/v2/storagegroup/convert.go
index e78312c..61e49d0 100644
--- a/v2/storagegroup/convert.go
+++ b/v2/storagegroup/convert.go
@@ -1,48 +1,57 @@
 package storagegroup
 
 import (
+	"github.com/nspcc-dev/neofs-api-go/rpc/grpc"
+	"github.com/nspcc-dev/neofs-api-go/rpc/message"
 	"github.com/nspcc-dev/neofs-api-go/v2/refs"
+	refsGRPC "github.com/nspcc-dev/neofs-api-go/v2/refs/grpc"
 	sg "github.com/nspcc-dev/neofs-api-go/v2/storagegroup/grpc"
 )
 
-// StorageGroupToGRPCMessage converts unified proto structure into grpc structure.
-func StorageGroupToGRPCMessage(s *StorageGroup) *sg.StorageGroup {
-	if s == nil {
-		return nil
-	}
-
+func (s *StorageGroup) ToGRPCMessage() grpc.Message {
 	m := new(sg.StorageGroup)
 
-	m.SetValidationDataSize(s.GetValidationDataSize())
-	m.SetValidationHash(
-		refs.ChecksumToGRPCMessage(s.GetValidationHash()),
-	)
-	m.SetExpirationEpoch(s.GetExpirationEpoch())
+	if s != nil {
+		m = new(sg.StorageGroup)
 
-	m.SetMembers(
-		refs.ObjectIDListToGRPCMessage(s.GetMembers()),
-	)
+		m.SetMembers(refs.ObjectIDListToGRPCMessage(s.members))
+		m.SetExpirationEpoch(s.exp)
+		m.SetValidationDataSize(s.size)
+		m.SetValidationHash(s.hash.ToGRPCMessage().(*refsGRPC.Checksum))
+	}
 
 	return m
 }
 
-// StorageGroupFromGRPCMessage converts grpc structure into unified proto structure.
-func StorageGroupFromGRPCMessage(m *sg.StorageGroup) *StorageGroup {
-	if m == nil {
-		return nil
+func (s *StorageGroup) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*sg.StorageGroup)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
 	}
 
-	s := new(StorageGroup)
+	var err error
 
-	s.SetValidationDataSize(m.GetValidationDataSize())
-	s.SetValidationHash(
-		refs.ChecksumFromGRPCMessage(m.GetValidationHash()),
-	)
-	s.SetExpirationEpoch(m.GetExpirationEpoch())
+	hash := v.GetValidationHash()
+	if hash == nil {
+		s.hash = nil
+	} else {
+		if s.hash == nil {
+			s.hash = new(refs.Checksum)
+		}
 
-	s.SetMembers(
-		refs.ObjectIDListFromGRPCMessage(m.GetMembers()),
-	)
+		err = s.hash.FromGRPCMessage(hash)
+		if err != nil {
+			return err
+		}
+	}
 
-	return s
+	s.members, err = refs.ObjectIDListFromGRPCMessage(v.GetMembers())
+	if err != nil {
+		return err
+	}
+
+	s.exp = v.GetExpirationEpoch()
+	s.size = v.GetValidationDataSize()
+
+	return nil
 }
diff --git a/v2/storagegroup/json.go b/v2/storagegroup/json.go
index efdb311..82e97e9 100644
--- a/v2/storagegroup/json.go
+++ b/v2/storagegroup/json.go
@@ -1,26 +1,14 @@
 package storagegroup
 
 import (
+	"github.com/nspcc-dev/neofs-api-go/rpc/message"
 	storagegroup "github.com/nspcc-dev/neofs-api-go/v2/storagegroup/grpc"
-	"google.golang.org/protobuf/encoding/protojson"
 )
 
 func (s *StorageGroup) MarshalJSON() ([]byte, error) {
-	return protojson.MarshalOptions{
-		EmitUnpopulated: true,
-	}.Marshal(
-		StorageGroupToGRPCMessage(s),
-	)
+	return message.MarshalJSON(s)
 }
 
 func (s *StorageGroup) UnmarshalJSON(data []byte) error {
-	msg := new(storagegroup.StorageGroup)
-
-	if err := protojson.Unmarshal(data, msg); err != nil {
-		return err
-	}
-
-	*s = *StorageGroupFromGRPCMessage(msg)
-
-	return nil
+	return message.UnmarshalJSON(s, data, new(storagegroup.StorageGroup))
 }
diff --git a/v2/storagegroup/json_test.go b/v2/storagegroup/json_test.go
deleted file mode 100644
index 9da4920..0000000
--- a/v2/storagegroup/json_test.go
+++ /dev/null
@@ -1,20 +0,0 @@
-package storagegroup_test
-
-import (
-	"testing"
-
-	"github.com/nspcc-dev/neofs-api-go/v2/storagegroup"
-	"github.com/stretchr/testify/require"
-)
-
-func TestStorageGroupJSON(t *testing.T) {
-	sg := generateSG()
-
-	data, err := sg.MarshalJSON()
-	require.NoError(t, err)
-
-	sg2 := new(storagegroup.StorageGroup)
-	require.NoError(t, sg2.UnmarshalJSON(data))
-
-	require.Equal(t, sg, sg2)
-}
diff --git a/v2/storagegroup/marshal.go b/v2/storagegroup/marshal.go
index f7b1ed1..75b3360 100644
--- a/v2/storagegroup/marshal.go
+++ b/v2/storagegroup/marshal.go
@@ -1,10 +1,10 @@
 package storagegroup
 
 import (
+	"github.com/nspcc-dev/neofs-api-go/rpc/message"
 	"github.com/nspcc-dev/neofs-api-go/util/proto"
 	"github.com/nspcc-dev/neofs-api-go/v2/refs"
 	storagegroup "github.com/nspcc-dev/neofs-api-go/v2/storagegroup/grpc"
-	goproto "google.golang.org/protobuf/proto"
 )
 
 const (
@@ -74,12 +74,5 @@ func (s *StorageGroup) StableSize() (size int) {
 }
 
 func (s *StorageGroup) Unmarshal(data []byte) error {
-	m := new(storagegroup.StorageGroup)
-	if err := goproto.Unmarshal(data, m); err != nil {
-		return err
-	}
-
-	*s = *StorageGroupFromGRPCMessage(m)
-
-	return nil
+	return message.Unmarshal(s, data, new(storagegroup.StorageGroup))
 }
diff --git a/v2/storagegroup/marshal_test.go b/v2/storagegroup/marshal_test.go
deleted file mode 100644
index 15f12c9..0000000
--- a/v2/storagegroup/marshal_test.go
+++ /dev/null
@@ -1,48 +0,0 @@
-package storagegroup_test
-
-import (
-	"testing"
-
-	"github.com/nspcc-dev/neofs-api-go/v2/refs"
-	"github.com/nspcc-dev/neofs-api-go/v2/storagegroup"
-	"github.com/stretchr/testify/require"
-)
-
-func TestStorageGroup_StableMarshal(t *testing.T) {
-	storageGroupFrom := generateSG()
-
-	t.Run("non empty", func(t *testing.T) {
-		wire, err := storageGroupFrom.StableMarshal(nil)
-		require.NoError(t, err)
-
-		storageGroupTo := new(storagegroup.StorageGroup)
-		require.NoError(t, storageGroupTo.Unmarshal(wire))
-
-		require.Equal(t, storageGroupFrom, storageGroupTo)
-	})
-}
-
-func generateChecksum(data string) *refs.Checksum {
-	checksum := new(refs.Checksum)
-	checksum.SetType(refs.TillichZemor)
-	checksum.SetSum([]byte(data))
-
-	return checksum
-}
-
-func generateSG() *storagegroup.StorageGroup {
-	sg := new(storagegroup.StorageGroup)
-
-	oid1 := new(refs.ObjectID)
-	oid1.SetValue([]byte("Object ID 1"))
-
-	oid2 := new(refs.ObjectID)
-	oid2.SetValue([]byte("Object ID 2"))
-
-	sg.SetValidationDataSize(300)
-	sg.SetValidationHash(generateChecksum("Homomorphic hash"))
-	sg.SetExpirationEpoch(100)
-	sg.SetMembers([]*refs.ObjectID{oid1, oid2})
-
-	return sg
-}
diff --git a/v2/storagegroup/message_test.go b/v2/storagegroup/message_test.go
new file mode 100644
index 0000000..3a787d2
--- /dev/null
+++ b/v2/storagegroup/message_test.go
@@ -0,0 +1,15 @@
+package storagegroup_test
+
+import (
+	"testing"
+
+	"github.com/nspcc-dev/neofs-api-go/rpc/message"
+	messagetest "github.com/nspcc-dev/neofs-api-go/rpc/message/test"
+	storagegrouptest "github.com/nspcc-dev/neofs-api-go/v2/storagegroup/test"
+)
+
+func TestMessageConvert(t *testing.T) {
+	messagetest.TestRPCMessage(t,
+		func(empty bool) message.Message { return storagegrouptest.GenerateStorageGroup(empty) },
+	)
+}
diff --git a/v2/storagegroup/test/generate.go b/v2/storagegroup/test/generate.go
new file mode 100644
index 0000000..c8c67ba
--- /dev/null
+++ b/v2/storagegroup/test/generate.go
@@ -0,0 +1,20 @@
+package storagegrouptest
+
+import (
+	refstest "github.com/nspcc-dev/neofs-api-go/v2/refs/test"
+	"github.com/nspcc-dev/neofs-api-go/v2/storagegroup"
+)
+
+func GenerateStorageGroup(empty bool) *storagegroup.StorageGroup {
+	m := new(storagegroup.StorageGroup)
+
+	if !empty {
+		m.SetValidationDataSize(44)
+		m.SetExpirationEpoch(55)
+	}
+
+	m.SetValidationHash(refstest.GenerateChecksum(empty))
+	m.SetMembers(refstest.GenerateObjectIDs(empty))
+
+	return m
+}
diff --git a/v2/tombstone/convert.go b/v2/tombstone/convert.go
index c2e6ecd..204c0a3 100644
--- a/v2/tombstone/convert.go
+++ b/v2/tombstone/convert.go
@@ -1,53 +1,41 @@
 package tombstone
 
 import (
+	"github.com/nspcc-dev/neofs-api-go/rpc/grpc"
+	"github.com/nspcc-dev/neofs-api-go/rpc/message"
 	"github.com/nspcc-dev/neofs-api-go/v2/refs"
-	refsGRPC "github.com/nspcc-dev/neofs-api-go/v2/refs/grpc"
 	tombstone "github.com/nspcc-dev/neofs-api-go/v2/tombstone/grpc"
 )
 
-// TombstoneToGRPCMessage converts unified tombstone message into gRPC message.
-func TombstoneToGRPCMessage(t *Tombstone) *tombstone.Tombstone {
-	if t == nil {
-		return nil
+func (s *Tombstone) ToGRPCMessage() grpc.Message {
+	var m *tombstone.Tombstone
+
+	if s != nil {
+		m = new(tombstone.Tombstone)
+
+		m.SetMembers(refs.ObjectIDListToGRPCMessage(s.members))
+		m.SetExpirationEpoch(s.exp)
+		m.SetSplitId(s.splitID)
 	}
 
-	m := new(tombstone.Tombstone)
-
-	m.SetExpirationEpoch(t.GetExpirationEpoch())
-	m.SetSplitId(t.GetSplitID())
-
-	members := t.GetMembers()
-	memberMsg := make([]*refsGRPC.ObjectID, 0, len(members))
-
-	for i := range members {
-		memberMsg = append(memberMsg, refs.ObjectIDToGRPCMessage(members[i]))
-	}
-
-	m.SetMembers(memberMsg)
-
 	return m
 }
 
-// TombstoneFromGRPCMessage converts gRPC message into unified tombstone message.
-func TombstoneFromGRPCMessage(m *tombstone.Tombstone) *Tombstone {
-	if m == nil {
-		return nil
+func (s *Tombstone) FromGRPCMessage(m grpc.Message) error {
+	v, ok := m.(*tombstone.Tombstone)
+	if !ok {
+		return message.NewUnexpectedMessageType(m, v)
 	}
 
-	t := new(Tombstone)
+	var err error
 
-	t.SetExpirationEpoch(m.GetExpirationEpoch())
-	t.SetSplitID(m.GetSplitId())
-
-	memberMsg := m.GetMembers()
-	members := make([]*refs.ObjectID, 0, len(memberMsg))
-
-	for i := range memberMsg {
-		members = append(members, refs.ObjectIDFromGRPCMessage(memberMsg[i]))
+	s.members, err = refs.ObjectIDListFromGRPCMessage(v.GetMembers())
+	if err != nil {
+		return err
 	}
 
-	t.SetMembers(members)
+	s.exp = v.GetExpirationEpoch()
+	s.splitID = v.GetSplitId()
 
-	return t
+	return nil
 }
diff --git a/v2/tombstone/json.go b/v2/tombstone/json.go
index ffa0e15..be89a1c 100644
--- a/v2/tombstone/json.go
+++ b/v2/tombstone/json.go
@@ -1,26 +1,14 @@
 package tombstone
 
 import (
+	"github.com/nspcc-dev/neofs-api-go/rpc/message"
 	tombstone "github.com/nspcc-dev/neofs-api-go/v2/tombstone/grpc"
-	"google.golang.org/protobuf/encoding/protojson"
 )
 
 func (s *Tombstone) MarshalJSON() ([]byte, error) {
-	return protojson.MarshalOptions{
-		EmitUnpopulated: true,
-	}.Marshal(
-		TombstoneToGRPCMessage(s),
-	)
+	return message.MarshalJSON(s)
 }
 
 func (s *Tombstone) UnmarshalJSON(data []byte) error {
-	msg := new(tombstone.Tombstone)
-
-	if err := protojson.Unmarshal(data, msg); err != nil {
-		return err
-	}
-
-	*s = *TombstoneFromGRPCMessage(msg)
-
-	return nil
+	return message.UnmarshalJSON(s, data, new(tombstone.Tombstone))
 }
diff --git a/v2/tombstone/json_test.go b/v2/tombstone/json_test.go
deleted file mode 100644
index 441f173..0000000
--- a/v2/tombstone/json_test.go
+++ /dev/null
@@ -1,20 +0,0 @@
-package tombstone_test
-
-import (
-	"testing"
-
-	"github.com/nspcc-dev/neofs-api-go/v2/tombstone"
-	"github.com/stretchr/testify/require"
-)
-
-func TestTombstoneJSON(t *testing.T) {
-	from := generateTombstone()
-
-	data, err := from.MarshalJSON()
-	require.NoError(t, err)
-
-	to := new(tombstone.Tombstone)
-	require.NoError(t, to.UnmarshalJSON(data))
-
-	require.Equal(t, from, to)
-}
diff --git a/v2/tombstone/marshal.go b/v2/tombstone/marshal.go
index 9b9c898..eb5bb38 100644
--- a/v2/tombstone/marshal.go
+++ b/v2/tombstone/marshal.go
@@ -1,9 +1,9 @@
 package tombstone
 
 import (
+	"github.com/nspcc-dev/neofs-api-go/rpc/message"
 	"github.com/nspcc-dev/neofs-api-go/util/proto"
 	tombstone "github.com/nspcc-dev/neofs-api-go/v2/tombstone/grpc"
-	goproto "google.golang.org/protobuf/proto"
 )
 
 const (
@@ -72,12 +72,5 @@ func (s *Tombstone) StableSize() (size int) {
 
 // Unmarshal unmarshal tombstone message from its binary representation.
 func (s *Tombstone) Unmarshal(data []byte) error {
-	m := new(tombstone.Tombstone)
-	if err := goproto.Unmarshal(data, m); err != nil {
-		return err
-	}
-
-	*s = *TombstoneFromGRPCMessage(m)
-
-	return nil
+	return message.Unmarshal(s, data, new(tombstone.Tombstone))
 }
diff --git a/v2/tombstone/marshal_test.go b/v2/tombstone/marshal_test.go
deleted file mode 100644
index c7eda2b..0000000
--- a/v2/tombstone/marshal_test.go
+++ /dev/null
@@ -1,39 +0,0 @@
-package tombstone_test
-
-import (
-	"testing"
-
-	"github.com/nspcc-dev/neofs-api-go/v2/refs"
-	"github.com/nspcc-dev/neofs-api-go/v2/tombstone"
-	"github.com/stretchr/testify/require"
-)
-
-func TestTombstone_StableMarshal(t *testing.T) {
-	from := generateTombstone()
-
-	t.Run("non empty", func(t *testing.T) {
-		wire, err := from.StableMarshal(nil)
-		require.NoError(t, err)
-
-		to := new(tombstone.Tombstone)
-		require.NoError(t, to.Unmarshal(wire))
-
-		require.Equal(t, from, to)
-	})
-}
-
-func generateTombstone() *tombstone.Tombstone {
-	t := new(tombstone.Tombstone)
-
-	oid1 := new(refs.ObjectID)
-	oid1.SetValue([]byte("Object ID 1"))
-
-	oid2 := new(refs.ObjectID)
-	oid2.SetValue([]byte("Object ID 2"))
-
-	t.SetExpirationEpoch(100)
-	t.SetSplitID([]byte("split ID"))
-	t.SetMembers([]*refs.ObjectID{oid1, oid2})
-
-	return t
-}
diff --git a/v2/tombstone/message_test.go b/v2/tombstone/message_test.go
new file mode 100644
index 0000000..4609cdd
--- /dev/null
+++ b/v2/tombstone/message_test.go
@@ -0,0 +1,15 @@
+package tombstone_test
+
+import (
+	"testing"
+
+	"github.com/nspcc-dev/neofs-api-go/rpc/message"
+	messagetest "github.com/nspcc-dev/neofs-api-go/rpc/message/test"
+	tombstonetest "github.com/nspcc-dev/neofs-api-go/v2/tombstone/test"
+)
+
+func TestMessageConvert(t *testing.T) {
+	messagetest.TestRPCMessage(t,
+		func(empty bool) message.Message { return tombstonetest.GenerateTombstone(empty) },
+	)
+}
diff --git a/v2/tombstone/test/generate.go b/v2/tombstone/test/generate.go
new file mode 100644
index 0000000..678323a
--- /dev/null
+++ b/v2/tombstone/test/generate.go
@@ -0,0 +1,19 @@
+package tombstonetest
+
+import (
+	refstest "github.com/nspcc-dev/neofs-api-go/v2/refs/test"
+	"github.com/nspcc-dev/neofs-api-go/v2/tombstone"
+)
+
+func GenerateTombstone(empty bool) *tombstone.Tombstone {
+	m := new(tombstone.Tombstone)
+
+	if !empty {
+		m.SetExpirationEpoch(89)
+		m.SetSplitID([]byte{3, 2, 1})
+	}
+
+	m.SetMembers(refstest.GenerateObjectIDs(empty))
+
+	return m
+}