Skip to content

Commit 7fd6a90

Browse files
author
Jason Yellick
committed
[FAB-4102] Proto translator statically opaque comp
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 statically opaque proto msg handler to the proto translation framework. Change-Id: Ifebc46f3bd3ec2087a9f2d579de30da5a3cb784d Signed-off-by: Jason Yellick <[email protected]>
1 parent 5a94f1a commit 7fd6a90

File tree

7 files changed

+477
-22
lines changed

7 files changed

+477
-22
lines changed

common/tools/protolator/api.go

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*
2+
Copyright IBM Corp. 2017 All Rights Reserved.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package protolator
18+
19+
import (
20+
"github.com/golang/protobuf/proto"
21+
)
22+
23+
///////////////////////////////////////////////////////////////////////////////////////////////////
24+
//
25+
// This set of interfaces and methods is designed to allow protos to have Go methods attached
26+
// to them, so that they may be automatically marshaled to human readable JSON (where the
27+
// opaque byte fields are represented as their expanded proto contents) and back once again
28+
// to standard proto messages.
29+
//
30+
///////////////////////////////////////////////////////////////////////////////////////////////////
31+
32+
// StaticallyOpaqueFieldProto should be implemented by protos which have bytes fields which
33+
// are the marshaled value of a fixed type
34+
type StaticallyOpaqueFieldProto interface {
35+
// StaticallyOpaqueFields returns the field names which contain opaque data
36+
StaticallyOpaqueFields() []string
37+
38+
// StaticallyOpaqueFieldProto returns a newly allocated proto message of the correct
39+
// type for the field name.
40+
StaticallyOpaqueFieldProto(name string) (proto.Message, error)
41+
}
42+
43+
// StaticallyOpaqueMapFieldProto should be implemented by protos which have maps to bytes fields
44+
// which are the marshaled value of a fixed type
45+
type StaticallyOpaqueMapFieldProto interface {
46+
// StaticallyOpaqueFields returns the field names which contain opaque data
47+
StaticallyOpaqueMapFields() []string
48+
49+
// StaticallyOpaqueFieldProto returns a newly allocated proto message of the correct
50+
// type for the field name.
51+
StaticallyOpaqueMapFieldProto(name string, key string) (proto.Message, error)
52+
}
53+
54+
// StaticallyOpaqueSliceFieldProto should be implemented by protos which have maps to bytes fields
55+
// which are the marshaled value of a fixed type
56+
type StaticallyOpaqueSliceFieldProto interface {
57+
// StaticallyOpaqueFields returns the field names which contain opaque data
58+
StaticallyOpaqueSliceFields() []string
59+
60+
// StaticallyOpaqueFieldProto returns a newly allocated proto message of the correct
61+
// type for the field name.
62+
StaticallyOpaqueSliceFieldProto(name string, index int) (proto.Message, error)
63+
}

common/tools/protolator/json.go

+13
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ type protoField interface {
5757
var (
5858
protoMsgType = reflect.TypeOf((*proto.Message)(nil)).Elem()
5959
mapStringInterfaceType = reflect.TypeOf(map[string]interface{}{})
60+
bytesType = reflect.TypeOf([]byte{})
6061
)
6162

6263
type baseField struct {
@@ -201,6 +202,15 @@ func (sf *sliceField) PopulateTo() (interface{}, error) {
201202
return result, nil
202203
}
203204

205+
func stringInSlice(target string, slice []string) bool {
206+
for _, name := range slice {
207+
if name == target {
208+
return true
209+
}
210+
}
211+
return false
212+
}
213+
204214
// protoToJSON is a simple shortcut wrapper around the proto JSON marshaler
205215
func protoToJSON(msg proto.Message) ([]byte, error) {
206216
var b bytes.Buffer
@@ -241,6 +251,9 @@ func jsonToMap(marshaled []byte) (map[string]interface{}, error) {
241251
// Factories listed lower, may depend on factories listed higher being
242252
// evaluated first.
243253
var fieldFactories = []protoFieldFactory{
254+
staticallyOpaqueSliceFieldFactory{},
255+
staticallyOpaqueMapFieldFactory{},
256+
staticallyOpaqueFieldFactory{},
244257
nestedSliceFieldFactory{},
245258
nestedMapFieldFactory{},
246259
nestedFieldFactory{},
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
/*
2+
Copyright IBM Corp. 2017 All Rights Reserved.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package protolator
18+
19+
import (
20+
"reflect"
21+
22+
"github.com/golang/protobuf/proto"
23+
)
24+
25+
func opaqueFrom(opaqueType func() (proto.Message, error), value interface{}, destType reflect.Type) (reflect.Value, error) {
26+
tree := value.(map[string]interface{}) // Safe, already checked
27+
nMsg, err := opaqueType()
28+
if err != nil {
29+
return reflect.Value{}, err
30+
}
31+
if err := recursivelyPopulateMessageFromTree(tree, nMsg); err != nil {
32+
return reflect.Value{}, err
33+
}
34+
mMsg, err := proto.Marshal(nMsg)
35+
if err != nil {
36+
return reflect.Value{}, err
37+
}
38+
return reflect.ValueOf(mMsg), nil
39+
}
40+
41+
func opaqueTo(opaqueType func() (proto.Message, error), value reflect.Value) (interface{}, error) {
42+
nMsg, err := opaqueType()
43+
if err != nil {
44+
return nil, err
45+
}
46+
mMsg := value.Interface().([]byte) // Safe, already checked
47+
if err = proto.Unmarshal(mMsg, nMsg); err != nil {
48+
return nil, err
49+
}
50+
return recursivelyCreateTreeFromMessage(nMsg)
51+
}
52+
53+
type staticallyOpaqueFieldFactory struct{}
54+
55+
func (soff staticallyOpaqueFieldFactory) Handles(msg proto.Message, fieldName string, fieldType reflect.Type, fieldValue reflect.Value) bool {
56+
opaqueProto, ok := msg.(StaticallyOpaqueFieldProto)
57+
if !ok {
58+
return false
59+
}
60+
61+
return stringInSlice(fieldName, opaqueProto.StaticallyOpaqueFields())
62+
}
63+
64+
func (soff staticallyOpaqueFieldFactory) NewProtoField(msg proto.Message, fieldName string, fieldType reflect.Type, fieldValue reflect.Value) (protoField, error) {
65+
opaqueProto := msg.(StaticallyOpaqueFieldProto) // Type checked in Handles
66+
67+
return &plainField{
68+
baseField: baseField{
69+
msg: msg,
70+
name: fieldName,
71+
fType: mapStringInterfaceType,
72+
vType: bytesType,
73+
value: fieldValue,
74+
},
75+
populateFrom: func(v interface{}, dT reflect.Type) (reflect.Value, error) {
76+
return opaqueFrom(func() (proto.Message, error) { return opaqueProto.StaticallyOpaqueFieldProto(fieldName) }, v, dT)
77+
},
78+
populateTo: func(v reflect.Value) (interface{}, error) {
79+
return opaqueTo(func() (proto.Message, error) { return opaqueProto.StaticallyOpaqueFieldProto(fieldName) }, v)
80+
},
81+
}, nil
82+
}
83+
84+
type staticallyOpaqueMapFieldFactory struct{}
85+
86+
func (soff staticallyOpaqueMapFieldFactory) Handles(msg proto.Message, fieldName string, fieldType reflect.Type, fieldValue reflect.Value) bool {
87+
opaqueProto, ok := msg.(StaticallyOpaqueMapFieldProto)
88+
if !ok {
89+
return false
90+
}
91+
92+
return stringInSlice(fieldName, opaqueProto.StaticallyOpaqueMapFields())
93+
}
94+
95+
func (soff staticallyOpaqueMapFieldFactory) NewProtoField(msg proto.Message, fieldName string, fieldType reflect.Type, fieldValue reflect.Value) (protoField, error) {
96+
opaqueProto := msg.(StaticallyOpaqueMapFieldProto) // Type checked in Handles
97+
98+
return &mapField{
99+
baseField: baseField{
100+
msg: msg,
101+
name: fieldName,
102+
fType: mapStringInterfaceType,
103+
vType: fieldType,
104+
value: fieldValue,
105+
},
106+
populateFrom: func(key string, v interface{}, dT reflect.Type) (reflect.Value, error) {
107+
return opaqueFrom(func() (proto.Message, error) {
108+
return opaqueProto.StaticallyOpaqueMapFieldProto(fieldName, key)
109+
}, v, dT)
110+
},
111+
populateTo: func(key string, v reflect.Value) (interface{}, error) {
112+
return opaqueTo(func() (proto.Message, error) {
113+
return opaqueProto.StaticallyOpaqueMapFieldProto(fieldName, key)
114+
}, v)
115+
},
116+
}, nil
117+
}
118+
119+
type staticallyOpaqueSliceFieldFactory struct{}
120+
121+
func (soff staticallyOpaqueSliceFieldFactory) Handles(msg proto.Message, fieldName string, fieldType reflect.Type, fieldValue reflect.Value) bool {
122+
opaqueProto, ok := msg.(StaticallyOpaqueSliceFieldProto)
123+
if !ok {
124+
return false
125+
}
126+
127+
return stringInSlice(fieldName, opaqueProto.StaticallyOpaqueSliceFields())
128+
}
129+
130+
func (soff staticallyOpaqueSliceFieldFactory) NewProtoField(msg proto.Message, fieldName string, fieldType reflect.Type, fieldValue reflect.Value) (protoField, error) {
131+
opaqueProto := msg.(StaticallyOpaqueSliceFieldProto) // Type checked in Handles
132+
133+
return &sliceField{
134+
baseField: baseField{
135+
msg: msg,
136+
name: fieldName,
137+
fType: mapStringInterfaceType,
138+
vType: fieldType,
139+
value: fieldValue,
140+
},
141+
populateFrom: func(index int, v interface{}, dT reflect.Type) (reflect.Value, error) {
142+
return opaqueFrom(func() (proto.Message, error) {
143+
return opaqueProto.StaticallyOpaqueSliceFieldProto(fieldName, index)
144+
}, v, dT)
145+
},
146+
populateTo: func(index int, v reflect.Value) (interface{}, error) {
147+
return opaqueTo(func() (proto.Message, error) {
148+
return opaqueProto.StaticallyOpaqueSliceFieldProto(fieldName, index)
149+
}, v)
150+
},
151+
}, nil
152+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
/*
2+
Copyright IBM Corp. 2017 All Rights Reserved.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package protolator
18+
19+
import (
20+
"bytes"
21+
"testing"
22+
23+
"github.com/hyperledger/fabric/common/tools/protolator/testprotos"
24+
"github.com/hyperledger/fabric/protos/utils"
25+
26+
"github.com/golang/protobuf/proto"
27+
"github.com/stretchr/testify/assert"
28+
)
29+
30+
func extractSimpleMsgPlainField(source []byte) string {
31+
result := &testprotos.SimpleMsg{}
32+
err := proto.Unmarshal(source, result)
33+
if err != nil {
34+
panic(err)
35+
}
36+
return result.PlainField
37+
}
38+
39+
func TestPlainStaticallyOpaqueMsg(t *testing.T) {
40+
fromPrefix := "from"
41+
toPrefix := "to"
42+
tppff := &testProtoPlainFieldFactory{
43+
fromPrefix: fromPrefix,
44+
toPrefix: toPrefix,
45+
}
46+
47+
fieldFactories = []protoFieldFactory{tppff}
48+
49+
pfValue := "foo"
50+
startMsg := &testprotos.StaticallyOpaqueMsg{
51+
PlainOpaqueField: utils.MarshalOrPanic(&testprotos.SimpleMsg{
52+
PlainField: pfValue,
53+
}),
54+
}
55+
56+
var buffer bytes.Buffer
57+
assert.NoError(t, DeepMarshalJSON(&buffer, startMsg))
58+
newMsg := &testprotos.StaticallyOpaqueMsg{}
59+
assert.NoError(t, DeepUnmarshalJSON(bytes.NewReader(buffer.Bytes()), newMsg))
60+
assert.NotEqual(t, fromPrefix+toPrefix+extractSimpleMsgPlainField(startMsg.PlainOpaqueField), extractSimpleMsgPlainField(newMsg.PlainOpaqueField))
61+
62+
fieldFactories = []protoFieldFactory{tppff, staticallyOpaqueFieldFactory{}}
63+
64+
buffer.Reset()
65+
assert.NoError(t, DeepMarshalJSON(&buffer, startMsg))
66+
assert.NoError(t, DeepUnmarshalJSON(bytes.NewReader(buffer.Bytes()), newMsg))
67+
assert.Equal(t, fromPrefix+toPrefix+extractSimpleMsgPlainField(startMsg.PlainOpaqueField), extractSimpleMsgPlainField(newMsg.PlainOpaqueField))
68+
}
69+
70+
func TestMapStaticallyOpaqueMsg(t *testing.T) {
71+
fromPrefix := "from"
72+
toPrefix := "to"
73+
tppff := &testProtoPlainFieldFactory{
74+
fromPrefix: fromPrefix,
75+
toPrefix: toPrefix,
76+
}
77+
78+
fieldFactories = []protoFieldFactory{tppff}
79+
80+
pfValue := "foo"
81+
mapKey := "bar"
82+
startMsg := &testprotos.StaticallyOpaqueMsg{
83+
MapOpaqueField: map[string][]byte{
84+
mapKey: utils.MarshalOrPanic(&testprotos.SimpleMsg{
85+
PlainField: pfValue,
86+
}),
87+
},
88+
}
89+
90+
var buffer bytes.Buffer
91+
assert.NoError(t, DeepMarshalJSON(&buffer, startMsg))
92+
newMsg := &testprotos.StaticallyOpaqueMsg{}
93+
assert.NoError(t, DeepUnmarshalJSON(bytes.NewReader(buffer.Bytes()), newMsg))
94+
assert.NotEqual(t, fromPrefix+toPrefix+extractSimpleMsgPlainField(startMsg.MapOpaqueField[mapKey]), extractSimpleMsgPlainField(newMsg.MapOpaqueField[mapKey]))
95+
96+
fieldFactories = []protoFieldFactory{tppff, staticallyOpaqueMapFieldFactory{}}
97+
98+
buffer.Reset()
99+
assert.NoError(t, DeepMarshalJSON(&buffer, startMsg))
100+
assert.NoError(t, DeepUnmarshalJSON(bytes.NewReader(buffer.Bytes()), newMsg))
101+
assert.Equal(t, fromPrefix+toPrefix+extractSimpleMsgPlainField(startMsg.MapOpaqueField[mapKey]), extractSimpleMsgPlainField(newMsg.MapOpaqueField[mapKey]))
102+
}
103+
104+
func TestSliceStaticallyOpaqueMsg(t *testing.T) {
105+
fromPrefix := "from"
106+
toPrefix := "to"
107+
tppff := &testProtoPlainFieldFactory{
108+
fromPrefix: fromPrefix,
109+
toPrefix: toPrefix,
110+
}
111+
112+
fieldFactories = []protoFieldFactory{tppff}
113+
114+
pfValue := "foo"
115+
startMsg := &testprotos.StaticallyOpaqueMsg{
116+
SliceOpaqueField: [][]byte{
117+
utils.MarshalOrPanic(&testprotos.SimpleMsg{
118+
PlainField: pfValue,
119+
}),
120+
},
121+
}
122+
123+
var buffer bytes.Buffer
124+
assert.NoError(t, DeepMarshalJSON(&buffer, startMsg))
125+
newMsg := &testprotos.StaticallyOpaqueMsg{}
126+
assert.NoError(t, DeepUnmarshalJSON(bytes.NewReader(buffer.Bytes()), newMsg))
127+
assert.NotEqual(t, fromPrefix+toPrefix+extractSimpleMsgPlainField(startMsg.SliceOpaqueField[0]), extractSimpleMsgPlainField(newMsg.SliceOpaqueField[0]))
128+
129+
fieldFactories = []protoFieldFactory{tppff, staticallyOpaqueSliceFieldFactory{}}
130+
131+
buffer.Reset()
132+
assert.NoError(t, DeepMarshalJSON(&buffer, startMsg))
133+
assert.NoError(t, DeepUnmarshalJSON(bytes.NewReader(buffer.Bytes()), newMsg))
134+
assert.Equal(t, fromPrefix+toPrefix+extractSimpleMsgPlainField(startMsg.SliceOpaqueField[0]), extractSimpleMsgPlainField(newMsg.SliceOpaqueField[0]))
135+
}

0 commit comments

Comments
 (0)