package registry import ( "bytes" "context" "reflect" "strings" "sync" "github.com/golang/protobuf/jsonpb" "github.com/golang/protobuf/proto" protov2 "google.golang.org/protobuf/proto" "github.com/v2fly/v2ray-core/v5/common/protoext" "github.com/v2fly/v2ray-core/v5/common/protofilter" "github.com/v2fly/v2ray-core/v5/common/serial" ) type implementationRegistry struct { implSet map[string]*implementationSet } func (i *implementationRegistry) RegisterImplementation(name string, opt *protoext.MessageOpt, loader CustomLoader) { interfaceType := opt.GetType() for _, v := range interfaceType { i.registerSingleImplementation(v, name, opt, loader) } } func (i *implementationRegistry) registerSingleImplementation(interfaceType, name string, opt *protoext.MessageOpt, loader CustomLoader) { implSet, found := i.implSet[interfaceType] if !found { implSet = newImplementationSet() i.implSet[interfaceType] = implSet } implSet.RegisterImplementation(name, opt, loader) } func (i *implementationRegistry) findImplementationByAlias(interfaceType, alias string) (string, CustomLoader, error) { implSet, found := i.implSet[interfaceType] if !found { return "", nil, newError("cannot find implemention unknown interface type") } return implSet.findImplementationByAlias(alias) } func (i *implementationRegistry) LoadImplementationByAlias(ctx context.Context, interfaceType, alias string, data []byte) (proto.Message, error) { var implementationFullName string if strings.HasPrefix(alias, "#") { // skip resolution for full name implementationFullName, _ = strings.CutPrefix(alias, "#") } else { registryResult, customLoader, err := i.findImplementationByAlias(interfaceType, alias) if err != nil { return nil, newError("unable to find implementation").Base(err) } if customLoader != nil { return customLoader(data, i) } implementationFullName = registryResult } implementationConfigInstance, err := serial.GetInstance(implementationFullName) if err != nil { return nil, newError("unable to create implementation config instance").Base(err) } unmarshaler := jsonpb.Unmarshaler{AllowUnknownFields: false} err = unmarshaler.Unmarshal(bytes.NewReader(data), implementationConfigInstance.(proto.Message)) if err != nil { return nil, newError("unable to parse json content").Base(err) } implementationConfigInstancev2 := proto.MessageV2(implementationConfigInstance) if isRestrictedModeContext(ctx) { if err := enforceRestriction(implementationConfigInstancev2); err != nil { return nil, err } } if err := protofilter.FilterProtoConfig(ctx, implementationConfigInstancev2); err != nil { return nil, err } return implementationConfigInstance.(proto.Message), nil } func newImplementationRegistry() *implementationRegistry { return &implementationRegistry{implSet: map[string]*implementationSet{}} } var globalImplementationRegistry = newImplementationRegistry() var initialized = &sync.Once{} type registerRequest struct { proto interface{} loader CustomLoader } var registerRequests []registerRequest // RegisterImplementation register an implementation of a type of interface // loader(CustomLoader) is a private API, its interface is subject to breaking changes func RegisterImplementation(proto interface{}, loader CustomLoader) error { registerRequests = append(registerRequests, registerRequest{ proto: proto, loader: loader, }) return nil } func registerImplementation(proto interface{}, loader CustomLoader) error { protoReflect := reflect.New(reflect.TypeOf(proto).Elem()) proto2 := protoReflect.Interface().(protov2.Message) msgDesc := proto2.ProtoReflect().Descriptor() fullName := string(msgDesc.FullName()) msgOpts, err := protoext.GetMessageOptions(msgDesc) if err != nil { return newError("unable to find message options").Base(err) } globalImplementationRegistry.RegisterImplementation(fullName, msgOpts, loader) return nil } type LoadByAlias interface { LoadImplementationByAlias(ctx context.Context, interfaceType, alias string, data []byte) (proto.Message, error) } func LoadImplementationByAlias(ctx context.Context, interfaceType, alias string, data []byte) (proto.Message, error) { initialized.Do(func() { for _, v := range registerRequests { registerImplementation(v.proto, v.loader) } }) return globalImplementationRegistry.LoadImplementationByAlias(ctx, interfaceType, alias, data) }