blob: 7f6084711a1ae481d634848702ffe29395a9973a [file] [log] [blame]
// Copyright Istio Authors
//
// 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 fuzz
import (
"bytes"
"encoding/hex"
"fmt"
"reflect"
"strings"
"sync"
)
import (
fuzz "github.com/AdaLogics/go-fuzz-headers"
"github.com/davecgh/go-spew/spew"
"github.com/golang/protobuf/proto" // nolint: staticcheck
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"google.golang.org/protobuf/testing/protocmp"
clientextensions "istio.io/client-go/pkg/apis/extensions/v1alpha1"
clientnetworkingalpha "istio.io/client-go/pkg/apis/networking/v1alpha3"
clientnetworkingbeta "istio.io/client-go/pkg/apis/networking/v1beta1"
clientsecurity "istio.io/client-go/pkg/apis/security/v1beta1"
clienttelemetry "istio.io/client-go/pkg/apis/telemetry/v1alpha1"
apimeta "k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/runtime/serializer/json"
)
import (
"github.com/apache/dubbo-go-pixiu/pkg/config/schema/collections"
)
var (
scheme = runtime.NewScheme()
initter sync.Once
)
func initRoundTrip() {
clientnetworkingalpha.AddToScheme(scheme)
clientnetworkingbeta.AddToScheme(scheme)
clientsecurity.AddToScheme(scheme)
clientextensions.AddToScheme(scheme)
clienttelemetry.AddToScheme(scheme)
}
// FuzzRoundtrip tests whether the pilot CRDs
// can be encoded and decoded.
func FuzzCRDRoundtrip(data []byte) int {
initter.Do(initRoundTrip)
if len(data) < 100 {
return 0
}
// select a target:
r := collections.Pilot.All()[int(data[0])%len(collections.Pilot.All())]
gvk := r.Resource().GroupVersionKind()
kgvk := schema.GroupVersionKind{
Group: gvk.Group,
Version: gvk.Version,
Kind: gvk.Kind,
}
object, err := scheme.New(kgvk)
if err != nil {
return 0
}
typeAcc, err := apimeta.TypeAccessor(object)
if err != nil {
panic(fmt.Sprintf("%q is not a TypeMeta and cannot be tested - add it to nonRoundTrippableInternalTypes: %v\n", kgvk, err))
}
f := fuzz.NewConsumer(data[1:])
err = f.GenerateStruct(object)
if err != nil {
return 0
}
err = checkForNilValues(object)
if err != nil {
return 0
}
typeAcc.SetKind(kgvk.Kind)
typeAcc.SetAPIVersion(kgvk.GroupVersion().String())
roundTrip(json.NewSerializer(json.DefaultMetaFactory, scheme, scheme, false), object)
return 1
}
// roundTrip performs the roundtrip of the object.
func roundTrip(codec runtime.Codec, object runtime.Object) {
printer := spew.ConfigState{DisableMethods: true}
// deep copy the original object
object = object.DeepCopyObject()
name := reflect.TypeOf(object).Elem().Name()
// encode (serialize) the deep copy using the provided codec
data, err := runtime.Encode(codec, object)
if err != nil {
return
}
// encode (serialize) a second time to verify that it was not varying
secondData, err := runtime.Encode(codec, object)
if err != nil {
panic("This should not fail since we are encoding for the second time")
}
// serialization to the wire must be stable to ensure that we don't write twice to the DB
// when the object hasn't changed.
if !bytes.Equal(data, secondData) {
panic(fmt.Sprintf("%v: serialization is not stable: %s\n", name, printer.Sprintf("%#v", object)))
}
// decode (deserialize) the encoded data back into an object
obj2, err := runtime.Decode(codec, data)
if err != nil {
panic(fmt.Sprintf("%v: %v\nCodec: %#v\nData: %s\nSource: %#v\n", name, err, codec, dataAsString(data), printer.Sprintf("%#v", object)))
}
// decode the encoded data into a new object (instead of letting the codec
// create a new object)
obj3 := reflect.New(reflect.TypeOf(object).Elem()).Interface().(runtime.Object)
if err := runtime.DecodeInto(codec, data, obj3); err != nil {
panic(fmt.Sprintf("%v: %v\n", name, err))
}
// ensure that the object produced from decoding the encoded data is equal
// to the original object
if diff := cmp.Diff(obj2, obj3, protocmp.Transform(), cmpopts.EquateNaNs()); diff != "" {
panic("These should not be different: " + diff)
}
}
// dataAsString is a simple helper.
func dataAsString(data []byte) string {
dataString := string(data)
if !strings.HasPrefix(dataString, "{") {
dataString = "\n" + hex.Dump(data)
proto.NewBuffer(make([]byte, 0, 1024)).DebugPrint("decoded object", data)
}
return dataString
}
// checkForNilValues is a helper to check for nil
// values in the runtime objects.
// This part only converts the interface to a reflect.Value.
func checkForNilValues(targetStruct interface{}) error {
v := reflect.ValueOf(targetStruct)
e := v.Elem()
err := checkForNil(e)
if err != nil {
return err
}
return nil
}
// Checks for nil values in a reflect.Value.
func checkForNil(e reflect.Value) error {
switch e.Kind() {
case reflect.Struct:
for i := 0; i < e.NumField(); i++ {
err := checkForNil(e.Field(i))
if err != nil {
return err
}
}
case reflect.Array, reflect.Slice:
for i := 0; i < e.Len(); i++ {
err := checkForNil(e.Index(i))
if err != nil {
return err
}
}
case reflect.Map:
if e.IsNil() {
return fmt.Errorf("field is nil")
}
for _, k := range e.MapKeys() {
if e.IsNil() {
return fmt.Errorf("field is nil")
}
err := checkForNil(e.MapIndex(k))
if err != nil {
return err
}
}
case reflect.Ptr:
if e.IsNil() {
return fmt.Errorf("field is nil")
}
err := checkForNil(e.Elem())
if err != nil {
return err
}
default:
return nil
}
return nil
}