@@ -27,6 +27,7 @@ import (
2727 "google.golang.org/protobuf/types/descriptorpb"
2828
2929 "github.com/openconfig/gnmi/value"
30+ "github.com/openconfig/ygot/util"
3031 "github.com/openconfig/ygot/ygot"
3132
3233 gpb "github.com/openconfig/gnmi/proto/gnmi"
@@ -361,20 +362,106 @@ func resolvedPath(basePath, annotatedPath *gpb.Path) *gpb.Path {
361362 return np
362363}
363364
364- // ProtoFromPaths takes an input ygot-generated protobuf and unmarshals the values that are specified in the map
365- // vals into it, using prefix as the prefix to any paths within the vals map. The message, p, is modified in place.
366- // The map, vals, must be keyed by the gNMI path to the field which is annotated in the ygot generated protobuf,
367- // the complete path is taken to be prefix + the key found in the map.
368- func ProtoFromPaths (p proto.Message , vals map [* gpb.Path ]interface {}, prefix * gpb.Path ) error {
365+ // UnmapOpt marks that a particular option can be supplied as an argument
366+ // to the ProtoFromPaths function.
367+ type UnmapOpt interface {
368+ isUnmapOpt ()
369+ }
370+
371+ // IgnoreExtraPaths indicates that unmapping should ignore any additional
372+ // paths that are found in the gNMI Notifications that do not have corresponding
373+ // fields in the protobuf.
374+ //
375+ // This option is typically used in conjunction with path compression where there
376+ // are some leaves that do not have corresponding fields.
377+ func IgnoreExtraPaths () * ignoreExtraPaths { return & ignoreExtraPaths {} }
378+
379+ type ignoreExtraPaths struct {}
380+
381+ // isUnmapOpt marks ignoreExtraPaths as an unmap option.
382+ func (* ignoreExtraPaths ) isUnmapOpt () {}
383+
384+ // ValuePathPrefix indicates that the values in the supplied map have a prefix which
385+ // is equal to the supplied path. The prefix plus each path in the vals map must be
386+ // equal to the absolute path for the supplied values.
387+ func ValuePathPrefix (path * gpb.Path ) * valuePathPrefix {
388+ return & valuePathPrefix {p : path }
389+ }
390+
391+ type valuePathPrefix struct { p * gpb.Path }
392+
393+ // isUnmapOpt marks valuePathPrefix as an unmap option.
394+ func (* valuePathPrefix ) isUnmapOpt () {}
395+
396+ // ProtobufMessagePrefix specifies the path that the protobuf message supplied to ProtoFromPaths
397+ // makes up. This is used in cases where the message itself is not the root - and hence unmarshalling
398+ // should look for paths relative to the specified path in the vals map.
399+ func ProtobufMessagePrefix (path * gpb.Path ) * protoMsgPrefix {
400+ return & protoMsgPrefix {p : path }
401+ }
402+
403+ type protoMsgPrefix struct { p * gpb.Path }
404+
405+ // isUnmapOpt marks protoMsgPrefix as an unmap option.
406+ func (* protoMsgPrefix ) isUnmapOpt () {}
407+
408+ // ProtoFromPaths takes an input ygot-generated protobuf and unmarshals the values provided in vals into the map.
409+ // The vals map must be keyed by the gNMI path to the leaf, with the interface{} value being the value that the
410+ // leaf at the field should be set to.
411+ //
412+ // The protobuf p is modified in place to add the values.
413+ //
414+ // The set of UnmapOpts that are provided (opt) are used to control the behaviour of unmarshalling the specified data.
415+ //
416+ // ProtoFromPaths returns an error if the data cannot be unmarshalled.
417+ func ProtoFromPaths (p proto.Message , vals map [* gpb.Path ]interface {}, opt ... UnmapOpt ) error {
369418 if p == nil {
370419 return errors .New ("nil protobuf supplied" )
371420 }
372421
422+ valPrefix , err := hasValuePathPrefix (opt )
423+ if err != nil {
424+ return fmt .Errorf ("invalid value prefix supplied, %v" , err )
425+ }
426+
427+ protoPrefix , err := hasProtoMsgPrefix (opt )
428+ if err != nil {
429+ return fmt .Errorf ("invalid protobuf message prefix supplied in options, %v" , err )
430+ }
431+
432+ schemaPath := func (p * gpb.Path ) * gpb.Path {
433+ np := proto .Clone (p ).(* gpb.Path )
434+ for _ , e := range np .Elem {
435+ e .Key = nil
436+ }
437+ return np
438+ }
439+
440+ // directCh is a map between the absolute schema path for a particular value, and
441+ // the value specified.
373442 directCh := map [* gpb.Path ]interface {}{}
374443 for p , v := range vals {
375- // TODO(robjs): needs fixing for compressed schemas.
376- if len (p .GetElem ()) == len (prefix .GetElem ())+ 1 {
377- directCh [p ] = v
444+ absPath := & gpb.Path {
445+ Elem : append (append ([]* gpb.PathElem {}, schemaPath (valPrefix ).Elem ... ), p .Elem ... ),
446+ }
447+
448+ if ! util .PathMatchesPathElemPrefix (absPath , protoPrefix ) {
449+ return fmt .Errorf ("invalid path provided, absolute paths must be used, %s does not have prefix %s" , absPath , protoPrefix )
450+ }
451+
452+ // make the path absolute, and a schema path.
453+ pp := util .TrimGNMIPathElemPrefix (absPath , protoPrefix )
454+
455+ if len (pp .GetElem ()) == 1 {
456+ directCh [pp ] = v
457+ }
458+ // TODO(robjs): it'd be good to have something here that tells us whether we are in
459+ // a compressed schema. Potentially we should add something to the generated protobuf
460+ // as a fileoption that would give us this indication.
461+ if len (pp .Elem ) == 2 {
462+ if pp .Elem [len (pp .Elem )- 2 ].Name == "config" || pp .Elem [len (pp .Elem )- 2 ].Name == "state" {
463+ directCh [pp ] = v
464+ }
378465 }
379466 }
380467
@@ -389,8 +476,13 @@ func ProtoFromPaths(p proto.Message, vals map[*gpb.Path]interface{}, prefix *gpb
389476 }
390477
391478 for _ , ap := range annotatedPath {
479+ if ! util .PathMatchesPathElemPrefix (ap , protoPrefix ) {
480+ rangeErr = fmt .Errorf ("annotation %s does not match the supplied prefix %s" , ap , protoPrefix )
481+ return false
482+ }
483+ trimmedAP := util .TrimGNMIPathElemPrefix (ap , protoPrefix )
392484 for chp , chv := range directCh {
393- if proto .Equal (ap , chp ) {
485+ if proto .Equal (trimmedAP , chp ) {
394486 switch fd .Kind () {
395487 case protoreflect .MessageKind :
396488 v , isWrap , err := makeWrapper (m , fd , chv )
@@ -428,15 +520,56 @@ func ProtoFromPaths(p proto.Message, vals map[*gpb.Path]interface{}, prefix *gpb
428520 return rangeErr
429521 }
430522
431- for chp := range directCh {
432- if ! mapped [chp ] {
433- return fmt .Errorf ("did not map path %s to a proto field" , chp )
523+ if ! hasIgnoreExtraPaths (opt ) {
524+ for chp := range directCh {
525+ if ! mapped [chp ] {
526+ return fmt .Errorf ("did not map path %s to a proto field" , chp )
527+ }
434528 }
435529 }
436530
437531 return nil
438532}
439533
534+ // hasIgnoreExtraPaths checks whether the supplied opts slice contains the
535+ // ignoreExtraPaths option.
536+ func hasIgnoreExtraPaths (opts []UnmapOpt ) bool {
537+ for _ , o := range opts {
538+ if _ , ok := o .(* ignoreExtraPaths ); ok {
539+ return true
540+ }
541+ }
542+ return false
543+ }
544+
545+ // hasProtoMsgPrefix checks whether the supplied opts slice contains the
546+ // protoMsgPrefix option, and validates and returns the path it contains.
547+ func hasProtoMsgPrefix (opts []UnmapOpt ) (* gpb.Path , error ) {
548+ for _ , o := range opts {
549+ if v , ok := o .(* protoMsgPrefix ); ok {
550+ if v .p == nil {
551+ return nil , fmt .Errorf ("invalid protobuf prefix supplied, %+v" , v )
552+ }
553+ return v .p , nil
554+ }
555+ }
556+ return & gpb.Path {}, nil
557+ }
558+
559+ // hasValuePathPrefix checks whether the supplied opts slice contains
560+ // the valuePathPrefix option, and validates and returns the apth it contains.
561+ func hasValuePathPrefix (opts []UnmapOpt ) (* gpb.Path , error ) {
562+ for _ , o := range opts {
563+ if v , ok := o .(* valuePathPrefix ); ok {
564+ if v .p == nil {
565+ return nil , fmt .Errorf ("invalid protobuf prefix supplied, %+v" , v )
566+ }
567+ return v .p , nil
568+ }
569+ }
570+ return & gpb.Path {}, nil
571+ }
572+
440573// makeWrapper generates a new message for field fd of the proto message msg with the value set to val.
441574// The field fd must describe a field that has a message type. An error is returned if the wrong
442575// type of payload is provided for the message. The second, boolean, return argument specifies whether
0 commit comments