| // Copyright 2021-2023 Buf Technologies, Inc. |
| // |
| // 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 triple_protocol |
| |
| import ( |
| "bytes" |
| "dubbo.apache.org/dubbo-go/v3/protocol/triple/triple_protocol/internal/interoperability" |
| "encoding/json" |
| "errors" |
| "fmt" |
| hessian "github.com/apache/dubbo-go-hessian2" |
| perrors "github.com/pkg/errors" |
| "reflect" |
| "time" |
| ) |
| |
| import ( |
| "google.golang.org/protobuf/encoding/protojson" |
| |
| "google.golang.org/protobuf/proto" |
| |
| "google.golang.org/protobuf/runtime/protoiface" |
| |
| msgpack "github.com/ugorji/go/codec" |
| ) |
| |
| const ( |
| codecNameProto = "proto" |
| codecNameJSON = "json" |
| codecNameHessian2 = "hessian2" |
| codecNameMsgPack = "msgpack" |
| codecNameJSONCharsetUTF8 = codecNameJSON + "; charset=utf-8" |
| ) |
| |
| // Codec marshals structs (typically generated from a schema) to and from bytes. |
| type Codec interface { |
| // Name returns the name of the Codec. |
| // |
| // This may be used as part of the Content-Type within HTTP. For example, |
| // with gRPC this is the content subtype, so "application/grpc+proto" will |
| // map to the Codec with name "proto". |
| // |
| // Names must not be empty. |
| Name() string |
| // Marshal marshals the given message. |
| // |
| // Marshal may expect a specific type of message, and will error if this type |
| // is not given. |
| Marshal(interface{}) ([]byte, error) |
| // Unmarshal unmarshals the given message. |
| // |
| // Unmarshal may expect a specific type of message, and will error if this |
| // type is not given. |
| Unmarshal([]byte, interface{}) error |
| } |
| |
| // stableCodec is an extension to Codec for serializing with stable output. |
| type stableCodec interface { |
| Codec |
| |
| // MarshalStable marshals the given message with stable field ordering. |
| // |
| // MarshalStable should return the same output for a given input. Although |
| // it is not guaranteed to be canonicalized, the marshaling routine for |
| // MarshalStable will opt for the most normalized output available for a |
| // given serialization. |
| // |
| // For practical reasons, it is possible for MarshalStable to return two |
| // different results for two inputs considered to be "equal" in their own |
| // domain, and it may change in the future with codec updates, but for |
| // any given concrete value and any given version, it should return the |
| // same output. |
| MarshalStable(interface{}) ([]byte, error) |
| |
| // IsBinary returns true if the marshaled data is binary for this codec. |
| // |
| // If this function returns false, the data returned from Marshal and |
| // MarshalStable are considered valid text and may be used in contexts |
| // where text is expected. |
| IsBinary() bool |
| } |
| |
| type protoBinaryCodec struct{} |
| |
| var _ Codec = (*protoBinaryCodec)(nil) |
| |
| func (c *protoBinaryCodec) Name() string { return codecNameProto } |
| |
| func (c *protoBinaryCodec) Marshal(message interface{}) ([]byte, error) { |
| protoMessage, ok := message.(proto.Message) |
| if !ok { |
| return nil, errNotProto(message) |
| } |
| return proto.Marshal(protoMessage) |
| } |
| |
| func (c *protoBinaryCodec) Unmarshal(data []byte, message interface{}) error { |
| protoMessage, ok := message.(proto.Message) |
| if !ok { |
| return errNotProto(message) |
| } |
| return proto.Unmarshal(data, protoMessage) |
| } |
| |
| func (c *protoBinaryCodec) MarshalStable(message interface{}) ([]byte, error) { |
| protoMessage, ok := message.(proto.Message) |
| if !ok { |
| return nil, errNotProto(message) |
| } |
| // protobuf does not offer a canonical output today, so this format is not |
| // guaranteed to match deterministic output from other protobuf libraries. |
| // In addition, unknown fields may cause inconsistent output for otherwise |
| // equal messages. |
| // https://github.com/golang/protobuf/issues/1121 |
| options := proto.MarshalOptions{Deterministic: true} |
| return options.Marshal(protoMessage) |
| } |
| |
| func (c *protoBinaryCodec) IsBinary() bool { |
| return true |
| } |
| |
| type protoJSONCodec struct { |
| name string |
| } |
| |
| var _ Codec = (*protoJSONCodec)(nil) |
| |
| func (c *protoJSONCodec) Name() string { return c.name } |
| |
| func (c *protoJSONCodec) Marshal(message interface{}) ([]byte, error) { |
| protoMessage, ok := message.(proto.Message) |
| if !ok { |
| return nil, errNotProto(message) |
| } |
| var options protojson.MarshalOptions |
| return options.Marshal(protoMessage) |
| } |
| |
| func (c *protoJSONCodec) Unmarshal(binary []byte, message interface{}) error { |
| protoMessage, ok := message.(proto.Message) |
| if !ok { |
| return errNotProto(message) |
| } |
| if len(binary) == 0 { |
| return errors.New("zero-length payload is not a valid JSON object") |
| } |
| var options protojson.UnmarshalOptions |
| return options.Unmarshal(binary, protoMessage) |
| } |
| |
| func (c *protoJSONCodec) MarshalStable(message interface{}) ([]byte, error) { |
| // protojson does not offer a "deterministic" field ordering, but fields |
| // are still ordered consistently by their index. However, protojson can |
| // output inconsistent whitespace for some reason, therefore it is |
| // suggested to use a formatter to ensure consistent formatting. |
| // https://github.com/golang/protobuf/issues/1373 |
| messageJSON, err := c.Marshal(message) |
| if err != nil { |
| return nil, err |
| } |
| compactedJSON := bytes.NewBuffer(messageJSON[:0]) |
| if err = json.Compact(compactedJSON, messageJSON); err != nil { |
| return nil, err |
| } |
| return compactedJSON.Bytes(), nil |
| } |
| |
| func (c *protoJSONCodec) IsBinary() bool { |
| return false |
| } |
| |
| // todo(DMwangnima): add unit tests |
| type protoWrapperCodec struct { |
| innerCodec Codec |
| } |
| |
| func (c *protoWrapperCodec) Name() string { |
| return c.innerCodec.Name() |
| } |
| |
| func (c *protoWrapperCodec) Marshal(message interface{}) ([]byte, error) { |
| reqs, ok := message.([]interface{}) |
| if !ok { |
| return c.innerCodec.Marshal(message) |
| } |
| reqsLen := len(reqs) |
| reqsBytes := make([][]byte, reqsLen) |
| reqsTypes := make([]string, reqsLen) |
| for i, req := range reqs { |
| reqBytes, err := c.innerCodec.Marshal(req) |
| if err != nil { |
| return nil, err |
| } |
| reqsBytes[i] = reqBytes |
| reqsTypes[i] = getArgType(req) |
| } |
| |
| wrapperReq := &interoperability.TripleRequestWrapper{ |
| SerializeType: c.innerCodec.Name(), |
| Args: reqsBytes, |
| ArgTypes: reqsTypes, |
| } |
| |
| return proto.Marshal(wrapperReq) |
| } |
| |
| func (c *protoWrapperCodec) Unmarshal(binary []byte, message interface{}) error { |
| params, ok := message.([]interface{}) |
| if !ok { |
| return c.innerCodec.Unmarshal(binary, message) |
| } |
| |
| var wrapperReq interoperability.TripleRequestWrapper |
| if err := proto.Unmarshal(binary, &wrapperReq); err != nil { |
| return err |
| } |
| if len(wrapperReq.Args) != len(params) { |
| return fmt.Errorf("error, request params len is %d, but has %d actually", len(wrapperReq.Args), len(params)) |
| } |
| |
| for i, arg := range wrapperReq.Args { |
| if err := c.innerCodec.Unmarshal(arg, params[i]); err != nil { |
| return err |
| } |
| } |
| |
| return nil |
| } |
| |
| func newProtoWrapperCodec(innerCodec Codec) *protoWrapperCodec { |
| return &protoWrapperCodec{innerCodec: innerCodec} |
| } |
| |
| // todo(DMwangnima): add unit tests |
| type hessian2Codec struct{} |
| |
| func (h *hessian2Codec) Name() string { |
| return codecNameHessian2 |
| } |
| |
| func (c *hessian2Codec) Marshal(message interface{}) ([]byte, error) { |
| encoder := hessian.NewEncoder() |
| if err := encoder.Encode(message); err != nil { |
| return nil, err |
| } |
| |
| return encoder.Buffer(), nil |
| } |
| |
| func (c *hessian2Codec) Unmarshal(binary []byte, message interface{}) error { |
| decoder := hessian.NewDecoder(binary) |
| val, err := decoder.Decode() |
| if err != nil { |
| return err |
| } |
| return reflectResponse(val, message) |
| } |
| |
| // todo(DMwangnima): add unit tests |
| type msgpackCodec struct{} |
| |
| func (c *msgpackCodec) Name() string { |
| return codecNameMsgPack |
| } |
| |
| func (c *msgpackCodec) Marshal(message interface{}) ([]byte, error) { |
| var out []byte |
| encoder := msgpack.NewEncoderBytes(&out, new(msgpack.MsgpackHandle)) |
| return out, encoder.Encode(message) |
| } |
| |
| func (c *msgpackCodec) Unmarshal(binary []byte, message interface{}) error { |
| decoder := msgpack.NewDecoderBytes(binary, new(msgpack.MsgpackHandle)) |
| return decoder.Decode(message) |
| } |
| |
| // readOnlyCodecs is a read-only interface to a map of named codecs. |
| type readOnlyCodecs interface { |
| // Get gets the Codec with the given name. |
| Get(string) Codec |
| // Protobuf gets the user-supplied protobuf codec, falling back to the default |
| // implementation if necessary. |
| // |
| // This is helpful in the gRPC protocol, where the wire protocol requires |
| // marshaling protobuf structs to binary even if the RPC procedures were |
| // generated from a different IDL. |
| Protobuf() Codec |
| // Names returns a copy of the registered codec names. The returned slice is |
| // safe for the caller to mutate. |
| Names() []string |
| } |
| |
| func newReadOnlyCodecs(nameToCodec map[string]Codec) readOnlyCodecs { |
| return &codecMap{ |
| nameToCodec: nameToCodec, |
| } |
| } |
| |
| type codecMap struct { |
| nameToCodec map[string]Codec |
| } |
| |
| func (m *codecMap) Get(name string) Codec { |
| return m.nameToCodec[name] |
| } |
| |
| func (m *codecMap) Protobuf() Codec { |
| if pb, ok := m.nameToCodec[codecNameProto]; ok { |
| return pb |
| } |
| return &protoBinaryCodec{} |
| } |
| |
| func (m *codecMap) Names() []string { |
| names := make([]string, 0, len(m.nameToCodec)) |
| for name := range m.nameToCodec { |
| names = append(names, name) |
| } |
| return names |
| } |
| |
| func errNotProto(message interface{}) error { |
| if _, ok := message.(protoiface.MessageV1); ok { |
| return fmt.Errorf("%T uses github.com/golang/protobuf, but triple only supports google.golang.org/protobuf: see https://go.dev/blog/protobuf-apiv2", message) |
| } |
| return fmt.Errorf("%T doesn't implement proto.Message", message) |
| } |
| |
| // Definitions from dubbogo/grpc-go |
| func getArgType(v interface{}) string { |
| if v == nil { |
| return "V" |
| } |
| |
| switch v.(type) { |
| // Serialized tags for base types |
| case nil: |
| return "V" |
| case bool: |
| return "boolean" |
| case []bool: |
| return "[Z" |
| case byte: |
| return "byte" |
| case []byte: |
| return "[B" |
| case int8: |
| return "byte" |
| case []int8: |
| return "[B" |
| case int16: |
| return "short" |
| case []int16: |
| return "[S" |
| case uint16: // Equivalent to Char of Java |
| return "char" |
| case []uint16: |
| return "[C" |
| case int: // Equivalent to Long of Java |
| return "long" |
| case []int: |
| return "[J" |
| case int32: |
| return "int" |
| case []int32: |
| return "[I" |
| case int64: |
| return "long" |
| case []int64: |
| return "[J" |
| case time.Time: |
| return "java.util.Date" |
| case []time.Time: |
| return "[Ljava.util.Date" |
| case float32: |
| return "float" |
| case []float32: |
| return "[F" |
| case float64: |
| return "double" |
| case []float64: |
| return "[D" |
| case string: |
| return "java.lang.String" |
| case []string: |
| return "[Ljava.lang.String;" |
| case []hessian.Object: |
| return "[Ljava.lang.Object;" |
| case map[interface{}]interface{}: |
| // return "java.util.HashMap" |
| return "java.util.Map" |
| case hessian.POJOEnum: |
| return v.(hessian.POJOEnum).JavaClassName() |
| // Serialized tags for complex types |
| default: |
| t := reflect.TypeOf(v) |
| if reflect.Ptr == t.Kind() { |
| t = t.Elem() |
| } |
| switch t.Kind() { |
| case reflect.Struct: |
| v, ok := v.(hessian.POJO) |
| if ok { |
| return v.JavaClassName() |
| } |
| return "java.lang.Object" |
| case reflect.Slice, reflect.Array: |
| if t.Elem().Kind() == reflect.Struct { |
| return "[Ljava.lang.Object;" |
| } |
| // return "java.util.ArrayList" |
| return "java.util.List" |
| case reflect.Map: // Enter here, map may be map[string]int |
| return "java.util.Map" |
| default: |
| return "" |
| } |
| } |
| } |
| |
| func reflectResponse(in interface{}, out interface{}) error { |
| if in == nil { |
| return perrors.Errorf("@in is nil") |
| } |
| |
| if out == nil { |
| return perrors.Errorf("@out is nil") |
| } |
| if reflect.TypeOf(out).Kind() != reflect.Ptr { |
| return perrors.Errorf("@out should be a pointer") |
| } |
| |
| inValue := hessian.EnsurePackValue(in) |
| outValue := hessian.EnsurePackValue(out) |
| |
| outType := outValue.Type().String() |
| if outType == "interface {}" || outType == "*interface {}" { |
| hessian.SetValue(outValue, inValue) |
| return nil |
| } |
| |
| switch inValue.Type().Kind() { |
| case reflect.Slice, reflect.Array: |
| return copySlice(inValue, outValue) |
| case reflect.Map: |
| return copyMap(inValue, outValue) |
| default: |
| hessian.SetValue(outValue, inValue) |
| } |
| |
| return nil |
| } |
| |
| // copySlice copy from inSlice to outSlice |
| func copySlice(inSlice, outSlice reflect.Value) error { |
| if inSlice.IsNil() { |
| return perrors.New("@in is nil") |
| } |
| if inSlice.Kind() != reflect.Slice { |
| return perrors.Errorf("@in is not slice, but %v", inSlice.Kind()) |
| } |
| |
| for outSlice.Kind() == reflect.Ptr { |
| outSlice = outSlice.Elem() |
| } |
| |
| size := inSlice.Len() |
| outSlice.Set(reflect.MakeSlice(outSlice.Type(), size, size)) |
| |
| for i := 0; i < size; i++ { |
| inSliceValue := inSlice.Index(i) |
| if !inSliceValue.Type().AssignableTo(outSlice.Index(i).Type()) { |
| return perrors.Errorf("in element type [%s] can not assign to out element type [%s]", |
| inSliceValue.Type().String(), outSlice.Type().String()) |
| } |
| outSlice.Index(i).Set(inSliceValue) |
| } |
| |
| return nil |
| } |
| |
| // copyMap copy from in map to out map |
| func copyMap(inMapValue, outMapValue reflect.Value) error { |
| if inMapValue.IsNil() { |
| return perrors.New("@in is nil") |
| } |
| if !inMapValue.CanInterface() { |
| return perrors.New("@in's Interface can not be used.") |
| } |
| if inMapValue.Kind() != reflect.Map { |
| return perrors.Errorf("@in is not map, but %v", inMapValue.Kind()) |
| } |
| |
| outMapType := hessian.UnpackPtrType(outMapValue.Type()) |
| hessian.SetValue(outMapValue, reflect.MakeMap(outMapType)) |
| |
| outKeyType := outMapType.Key() |
| |
| outMapValue = hessian.UnpackPtrValue(outMapValue) |
| outValueType := outMapValue.Type().Elem() |
| |
| for _, inKey := range inMapValue.MapKeys() { |
| inValue := inMapValue.MapIndex(inKey) |
| |
| if !inKey.Type().AssignableTo(outKeyType) { |
| return perrors.Errorf("in Key:{type:%s, value:%#v} can not assign to out Key:{type:%s} ", |
| inKey.Type().String(), inKey, outKeyType.String()) |
| } |
| if !inValue.Type().AssignableTo(outValueType) { |
| return perrors.Errorf("in Value:{type:%s, value:%#v} can not assign to out value:{type:%s}", |
| inValue.Type().String(), inValue, outValueType.String()) |
| } |
| outMapValue.SetMapIndex(inKey, inValue) |
| } |
| |
| return nil |
| } |