Skip to content

Commit

Permalink
[FAB-4103] Proto translator variably opaque comp
Browse files Browse the repository at this point in the history
The proto translation framework introduced in FAB-4100 needs to be
applied to messages with opaque fields.  This is one of the key goals of
proto translation, which prevents the protos as defined from being human
readable.

This CR adds a variably opaque proto msg handler to the proto
translation framework.  Documented further in api.go, but variably
opaque fields differ from statically opaque ones, in that variably
oapque fields may depend upon the contents of the proto being populated
to determine type information at runtime and will be evaluated after the
static ones.

Change-Id: Iacbba1316f8e82dee68695aa98bf4c30a1a139da
Signed-off-by: Jason Yellick <jyellick@us.ibm.com>
  • Loading branch information
Jason Yellick committed May 26, 2017
1 parent 7fd6a90 commit 7b5b661
Show file tree
Hide file tree
Showing 7 changed files with 432 additions and 27 deletions.
46 changes: 46 additions & 0 deletions common/tools/protolator/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,19 @@ import (
// opaque byte fields are represented as their expanded proto contents) and back once again
// to standard proto messages.
//
// There are currently two different types of interfaces available for protos to implement:
//
// 1. StaticallyOpaque*FieldProto: These interfaces should be implemented by protos which have
// opaque byte fields whose marshaled type is known at compile time. This is mostly true
// for the signature oriented fields like the Envelope.Payload, or Header.ChannelHeader
//
// 2. VariablyOpaque*FieldProto: These interfaces are identical to the StaticallyOpaque*FieldProto
// definitions, with the exception that they are guaranteed to be evaluated after the
// StaticallyOpaque*FieldProto definitions. In particular, this allows for the type selection of
// a VariablyOpaque*FieldProto to depend on data populated by the StaticallyOpaque*FieldProtos.
// For example, the Payload.data field depends upon the Payload.Header.ChannelHeader.type field,
// which is along a statically marshaled path.
//
///////////////////////////////////////////////////////////////////////////////////////////////////

// StaticallyOpaqueFieldProto should be implemented by protos which have bytes fields which
Expand Down Expand Up @@ -61,3 +74,36 @@ type StaticallyOpaqueSliceFieldProto interface {
// type for the field name.
StaticallyOpaqueSliceFieldProto(name string, index int) (proto.Message, error)
}

// VariablyOpaqueFieldProto should be implemented by protos which have bytes fields which
// are the marshaled value depends upon the other contents of the proto
type VariablyOpaqueFieldProto interface {
// VariablyOpaqueFields returns the field names which contain opaque data
VariablyOpaqueFields() []string

// VariablyOpaqueFieldProto returns a newly allocated proto message of the correct
// type for the field name.
VariablyOpaqueFieldProto(name string) (proto.Message, error)
}

// VariablyOpaqueMapFieldProto should be implemented by protos which have maps to bytes fields
// which are the marshaled value of a a message type determined by the other contents of the proto
type VariablyOpaqueMapFieldProto interface {
// VariablyOpaqueFields returns the field names which contain opaque data
VariablyOpaqueMapFields() []string

// VariablyOpaqueFieldProto returns a newly allocated proto message of the correct
// type for the field name.
VariablyOpaqueMapFieldProto(name string, key string) (proto.Message, error)
}

// VariablyOpaqueSliceFieldProto should be implemented by protos which have maps to bytes fields
// which are the marshaled value of a a message type determined by the other contents of the proto
type VariablyOpaqueSliceFieldProto interface {
// VariablyOpaqueFields returns the field names which contain opaque data
VariablyOpaqueSliceFields() []string

// VariablyOpaqueFieldProto returns a newly allocated proto message of the correct
// type for the field name.
VariablyOpaqueSliceFieldProto(name string, index int) (proto.Message, error)
}
3 changes: 3 additions & 0 deletions common/tools/protolator/json.go
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,9 @@ func jsonToMap(marshaled []byte) (map[string]interface{}, error) {
// Factories listed lower, may depend on factories listed higher being
// evaluated first.
var fieldFactories = []protoFieldFactory{
variablyOpaqueSliceFieldFactory{},
variablyOpaqueMapFieldFactory{},
variablyOpaqueFieldFactory{},
staticallyOpaqueSliceFieldFactory{},
staticallyOpaqueMapFieldFactory{},
staticallyOpaqueFieldFactory{},
Expand Down
51 changes: 51 additions & 0 deletions common/tools/protolator/testprotos/sample.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,54 @@ func (som *StaticallyOpaqueMsg) StaticallyOpaqueSliceFieldProto(name string, ind

return &SimpleMsg{}, nil
}

func typeSwitch(typeName string) (proto.Message, error) {
switch typeName {
case "SimpleMsg":
return &SimpleMsg{}, nil
case "NestedMsg":
return &NestedMsg{}, nil
case "StaticallyOpaqueMsg":
return &StaticallyOpaqueMsg{}, nil
case "VariablyOpaqueMsg":
return &VariablyOpaqueMsg{}, nil
default:
return nil, fmt.Errorf("unknown message type: %s", typeName)
}
}

func (vom *VariablyOpaqueMsg) VariablyOpaqueFields() []string {
return []string{"plain_opaque_field"}
}

func (vom *VariablyOpaqueMsg) VariablyOpaqueFieldProto(name string) (proto.Message, error) {
if name != vom.VariablyOpaqueFields()[0] {
return nil, fmt.Errorf("not a statically opaque field: %s", name)
}

return typeSwitch(vom.OpaqueType)
}

func (vom *VariablyOpaqueMsg) VariablyOpaqueMapFields() []string {
return []string{"map_opaque_field"}
}

func (vom *VariablyOpaqueMsg) VariablyOpaqueMapFieldProto(name string, key string) (proto.Message, error) {
if name != vom.VariablyOpaqueMapFields()[0] {
return nil, fmt.Errorf("not a statically opaque field: %s", name)
}

return typeSwitch(vom.OpaqueType)
}

func (vom *VariablyOpaqueMsg) VariablyOpaqueSliceFields() []string {
return []string{"slice_opaque_field"}
}

func (vom *VariablyOpaqueMsg) VariablyOpaqueSliceFieldProto(name string, index int) (proto.Message, error) {
if name != vom.VariablyOpaqueSliceFields()[0] {
return nil, fmt.Errorf("not a statically opaque field: %s", name)
}

return typeSwitch(vom.OpaqueType)
}
81 changes: 54 additions & 27 deletions common/tools/protolator/testprotos/sample.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions common/tools/protolator/testprotos/sample.proto
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,18 @@ message NestedMsg {
}

// StaticallyOpaqueMsg is designed to test the statically opaque message component
// All fields are statically marshaled to the NestedMsg type
message StaticallyOpaqueMsg {
bytes plain_opaque_field = 1;
map<string, bytes> map_opaque_field = 2;
repeated bytes slice_opaque_field = 3;
}

// VariablyOpaqueMsg is designed to test the staticaly opaque message component
// The opaque type is determined by opaque_type
message VariablyOpaqueMsg {
string opaque_type = 1;
bytes plain_opaque_field = 2;
map<string, bytes> map_opaque_field = 3;
repeated bytes slice_opaque_field = 4;
}
124 changes: 124 additions & 0 deletions common/tools/protolator/variably_opaque.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/*
Copyright IBM Corp. 2017 All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package protolator

import (
"reflect"

"github.com/golang/protobuf/proto"
)

type variablyOpaqueFieldFactory struct{}

func (soff variablyOpaqueFieldFactory) Handles(msg proto.Message, fieldName string, fieldType reflect.Type, fieldValue reflect.Value) bool {
opaqueProto, ok := msg.(VariablyOpaqueFieldProto)
if !ok {
return false
}

return stringInSlice(fieldName, opaqueProto.VariablyOpaqueFields())
}

func (soff variablyOpaqueFieldFactory) NewProtoField(msg proto.Message, fieldName string, fieldType reflect.Type, fieldValue reflect.Value) (protoField, error) {
opaqueProto := msg.(VariablyOpaqueFieldProto) // Type checked in Handles

return &plainField{
baseField: baseField{
msg: msg,
name: fieldName,
fType: mapStringInterfaceType,
vType: bytesType,
value: fieldValue,
},
populateFrom: func(v interface{}, dT reflect.Type) (reflect.Value, error) {
return opaqueFrom(func() (proto.Message, error) { return opaqueProto.VariablyOpaqueFieldProto(fieldName) }, v, dT)
},
populateTo: func(v reflect.Value) (interface{}, error) {
return opaqueTo(func() (proto.Message, error) { return opaqueProto.VariablyOpaqueFieldProto(fieldName) }, v)
},
}, nil
}

type variablyOpaqueMapFieldFactory struct{}

func (soff variablyOpaqueMapFieldFactory) Handles(msg proto.Message, fieldName string, fieldType reflect.Type, fieldValue reflect.Value) bool {
opaqueProto, ok := msg.(VariablyOpaqueMapFieldProto)
if !ok {
return false
}

return stringInSlice(fieldName, opaqueProto.VariablyOpaqueMapFields())
}

func (soff variablyOpaqueMapFieldFactory) NewProtoField(msg proto.Message, fieldName string, fieldType reflect.Type, fieldValue reflect.Value) (protoField, error) {
opaqueProto := msg.(VariablyOpaqueMapFieldProto) // Type checked in Handles

return &mapField{
baseField: baseField{
msg: msg,
name: fieldName,
fType: mapStringInterfaceType,
vType: fieldType,
value: fieldValue,
},
populateFrom: func(key string, v interface{}, dT reflect.Type) (reflect.Value, error) {
return opaqueFrom(func() (proto.Message, error) {
return opaqueProto.VariablyOpaqueMapFieldProto(fieldName, key)
}, v, dT)
},
populateTo: func(key string, v reflect.Value) (interface{}, error) {
return opaqueTo(func() (proto.Message, error) {
return opaqueProto.VariablyOpaqueMapFieldProto(fieldName, key)
}, v)
},
}, nil
}

type variablyOpaqueSliceFieldFactory struct{}

func (soff variablyOpaqueSliceFieldFactory) Handles(msg proto.Message, fieldName string, fieldType reflect.Type, fieldValue reflect.Value) bool {
opaqueProto, ok := msg.(VariablyOpaqueSliceFieldProto)
if !ok {
return false
}

return stringInSlice(fieldName, opaqueProto.VariablyOpaqueSliceFields())
}

func (soff variablyOpaqueSliceFieldFactory) NewProtoField(msg proto.Message, fieldName string, fieldType reflect.Type, fieldValue reflect.Value) (protoField, error) {
opaqueProto := msg.(VariablyOpaqueSliceFieldProto) // Type checked in Handles

return &sliceField{
baseField: baseField{
msg: msg,
name: fieldName,
fType: mapStringInterfaceType,
vType: fieldType,
value: fieldValue,
},
populateFrom: func(index int, v interface{}, dT reflect.Type) (reflect.Value, error) {
return opaqueFrom(func() (proto.Message, error) {
return opaqueProto.VariablyOpaqueSliceFieldProto(fieldName, index)
}, v, dT)
},
populateTo: func(index int, v reflect.Value) (interface{}, error) {
return opaqueTo(func() (proto.Message, error) {
return opaqueProto.VariablyOpaqueSliceFieldProto(fieldName, index)
}, v)
},
}, nil
}
Loading

0 comments on commit 7b5b661

Please sign in to comment.