// estimateUnknownSize returns the expected bytes consumed by a given runtime.Unknown // object with a nil RawJSON struct and the expected size of the provided buffer. The // returned size will not be correct if RawJSOn is set on unk. func estimateUnknownSize(unk *runtime.Unknown, byteSize uint64) uint64 { size := uint64(unk.Size()) // protobuf uses 1 byte for the tag, a varint for the length of the array (at most 8 bytes - uint64 - here), // and the size of the array. size += 1 + 8 + byteSize return size }
// Decode attempts to convert the provided data into a protobuf message, extract the stored schema kind, apply the provided default // gvk, and then load that data into an object matching the desired schema kind or the provided into. If into is *runtime.Unknown, // the raw data will be extracted and no decoding will be performed. If into is not registered with the typer, then the object will // be straight decoded using normal protobuf unmarshalling (the MarshalTo interface). If into is provided and the original data is // not fully qualified with kind/version/group, the type of the into will be used to alter the returned gvk. On success or most // errors, the method will return the calculated schema kind. func (s *Serializer) Decode(originalData []byte, gvk *unversioned.GroupVersionKind, into runtime.Object) (runtime.Object, *unversioned.GroupVersionKind, error) { if versioned, ok := into.(*runtime.VersionedObjects); ok { into = versioned.Last() obj, actual, err := s.Decode(originalData, gvk, into) if err != nil { return nil, actual, err } // the last item in versioned becomes into, so if versioned was not originally empty we reset the object // array so the first position is the decoded object and the second position is the outermost object. // if there were no objects in the versioned list passed to us, only add ourselves. if into != nil && into != obj { versioned.Objects = []runtime.Object{obj, into} } else { versioned.Objects = []runtime.Object{obj} } return versioned, actual, err } prefixLen := len(s.prefix) switch { case len(originalData) == 0: // TODO: treat like decoding {} from JSON with defaulting return nil, nil, fmt.Errorf("empty data") case len(originalData) < prefixLen || !bytes.Equal(s.prefix, originalData[:prefixLen]): return nil, nil, fmt.Errorf("provided data does not appear to be a protobuf message, expected prefix %v", s.prefix) case len(originalData) == prefixLen: // TODO: treat like decoding {} from JSON with defaulting return nil, nil, fmt.Errorf("empty body") } data := originalData[prefixLen:] unk := runtime.Unknown{} if err := unk.Unmarshal(data); err != nil { return nil, nil, err } actual := unk.GroupVersionKind() copyKindDefaults(&actual, gvk) if intoUnknown, ok := into.(*runtime.Unknown); ok && intoUnknown != nil { *intoUnknown = unk if ok, _, _ := s.RecognizesData(bytes.NewBuffer(unk.Raw)); ok { intoUnknown.ContentType = s.contentType } return intoUnknown, &actual, nil } if into != nil { types, _, err := s.typer.ObjectKinds(into) switch { case runtime.IsNotRegisteredError(err): pb, ok := into.(proto.Message) if !ok { return nil, &actual, errNotMarshalable{reflect.TypeOf(into)} } if err := proto.Unmarshal(unk.Raw, pb); err != nil { return nil, &actual, err } return into, &actual, nil case err != nil: return nil, &actual, err default: copyKindDefaults(&actual, &types[0]) // if the result of defaulting did not set a version or group, ensure that at least group is set // (copyKindDefaults will not assign Group if version is already set). This guarantees that the group // of into is set if there is no better information from the caller or object. if len(actual.Version) == 0 && len(actual.Group) == 0 { actual.Group = types[0].Group } } } if len(actual.Kind) == 0 { return nil, &actual, runtime.NewMissingKindErr(fmt.Sprintf("%#v", unk.TypeMeta)) } if len(actual.Version) == 0 { return nil, &actual, runtime.NewMissingVersionErr(fmt.Sprintf("%#v", unk.TypeMeta)) } return unmarshalToObject(s.typer, s.creater, &actual, into, unk.Raw) }
// Encode serializes the provided object to the given writer. func (s *Serializer) Encode(obj runtime.Object, w io.Writer) error { prefixSize := uint64(len(s.prefix)) var unk runtime.Unknown switch t := obj.(type) { case *runtime.Unknown: estimatedSize := prefixSize + uint64(t.Size()) data := make([]byte, estimatedSize) i, err := t.MarshalTo(data[prefixSize:]) if err != nil { return err } copy(data, s.prefix) _, err = w.Write(data[:prefixSize+uint64(i)]) return err default: kind := obj.GetObjectKind().GroupVersionKind() unk = runtime.Unknown{ TypeMeta: runtime.TypeMeta{ Kind: kind.Kind, APIVersion: kind.GroupVersion().String(), }, } } switch t := obj.(type) { case bufferedMarshaller: // this path performs a single allocation during write but requires the caller to implement // the more efficient Size and MarshalTo methods encodedSize := uint64(t.Size()) estimatedSize := prefixSize + estimateUnknownSize(&unk, encodedSize) data := make([]byte, estimatedSize) i, err := unk.NestedMarshalTo(data[prefixSize:], t, encodedSize) if err != nil { return err } copy(data, s.prefix) _, err = w.Write(data[:prefixSize+uint64(i)]) return err case proto.Marshaler: // this path performs extra allocations data, err := t.Marshal() if err != nil { return err } unk.Raw = data estimatedSize := prefixSize + uint64(unk.Size()) data = make([]byte, estimatedSize) i, err := unk.MarshalTo(data[prefixSize:]) if err != nil { return err } copy(data, s.prefix) _, err = w.Write(data[:prefixSize+uint64(i)]) return err default: // TODO: marshal with a different content type and serializer (JSON for third party objects) return errNotMarshalable{reflect.TypeOf(obj)} } }