github.com/rigado/snapd@v2.42.5-go-mod+incompatible/asserts/ifacedecls.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2015-2017 Canonical Ltd
     5   *
     6   * This program is free software: you can redistribute it and/or modify
     7   * it under the terms of the GNU General Public License version 3 as
     8   * published by the Free Software Foundation.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  
    20  package asserts
    21  
    22  import (
    23  	"errors"
    24  	"fmt"
    25  	"reflect"
    26  	"regexp"
    27  	"strconv"
    28  	"strings"
    29  )
    30  
    31  // AttrMatchContext has contextual helpers for evaluating attribute constraints.
    32  type AttrMatchContext interface {
    33  	PlugAttr(arg string) (interface{}, error)
    34  	SlotAttr(arg string) (interface{}, error)
    35  }
    36  
    37  const (
    38  	// feature label for $SLOT()/$PLUG()/$MISSING
    39  	dollarAttrConstraintsFeature = "dollar-attr-constraints"
    40  	// feature label for on-store/on-brand/on-model
    41  	deviceScopeConstraintsFeature = "device-scope-constraints"
    42  )
    43  
    44  type attrMatcher interface {
    45  	match(apath string, v interface{}, ctx AttrMatchContext) error
    46  
    47  	feature(flabel string) bool
    48  }
    49  
    50  func chain(path, k string) string {
    51  	if path == "" {
    52  		return k
    53  	}
    54  	return fmt.Sprintf("%s.%s", path, k)
    55  }
    56  
    57  type compileContext struct {
    58  	dotted string
    59  	hadMap bool
    60  	wasAlt bool
    61  }
    62  
    63  func (cc compileContext) String() string {
    64  	return cc.dotted
    65  }
    66  
    67  func (cc compileContext) keyEntry(k string) compileContext {
    68  	return compileContext{
    69  		dotted: chain(cc.dotted, k),
    70  		hadMap: true,
    71  		wasAlt: false,
    72  	}
    73  }
    74  
    75  func (cc compileContext) alt(alt int) compileContext {
    76  	return compileContext{
    77  		dotted: fmt.Sprintf("%s/alt#%d/", cc.dotted, alt+1),
    78  		hadMap: cc.hadMap,
    79  		wasAlt: true,
    80  	}
    81  }
    82  
    83  // compileAttrMatcher compiles an attrMatcher derived from constraints,
    84  func compileAttrMatcher(cc compileContext, constraints interface{}) (attrMatcher, error) {
    85  	switch x := constraints.(type) {
    86  	case map[string]interface{}:
    87  		return compileMapAttrMatcher(cc, x)
    88  	case []interface{}:
    89  		if cc.wasAlt {
    90  			return nil, fmt.Errorf("cannot nest alternative constraints directly at %q", cc)
    91  		}
    92  		return compileAltAttrMatcher(cc, x)
    93  	case string:
    94  		if !cc.hadMap {
    95  			return nil, fmt.Errorf("first level of non alternative constraints must be a set of key-value contraints")
    96  		}
    97  		if strings.HasPrefix(x, "$") {
    98  			if x == "$MISSING" {
    99  				return missingAttrMatcher{}, nil
   100  			}
   101  			return compileEvalAttrMatcher(cc, x)
   102  		}
   103  		return compileRegexpAttrMatcher(cc, x)
   104  	default:
   105  		return nil, fmt.Errorf("constraint %q must be a key-value map, regexp or a list of alternative constraints: %v", cc, x)
   106  	}
   107  }
   108  
   109  type mapAttrMatcher map[string]attrMatcher
   110  
   111  func compileMapAttrMatcher(cc compileContext, m map[string]interface{}) (attrMatcher, error) {
   112  	matcher := make(mapAttrMatcher)
   113  	for k, constraint := range m {
   114  		matcher1, err := compileAttrMatcher(cc.keyEntry(k), constraint)
   115  		if err != nil {
   116  			return nil, err
   117  		}
   118  		matcher[k] = matcher1
   119  	}
   120  	return matcher, nil
   121  }
   122  
   123  func matchEntry(apath, k string, matcher1 attrMatcher, v interface{}, ctx AttrMatchContext) error {
   124  	apath = chain(apath, k)
   125  	// every entry matcher expects the attribute to be set except for $MISSING
   126  	if _, ok := matcher1.(missingAttrMatcher); !ok && v == nil {
   127  		return fmt.Errorf("attribute %q has constraints but is unset", apath)
   128  	}
   129  	if err := matcher1.match(apath, v, ctx); err != nil {
   130  		return err
   131  	}
   132  	return nil
   133  }
   134  
   135  func matchList(apath string, matcher attrMatcher, l []interface{}, ctx AttrMatchContext) error {
   136  	for i, elem := range l {
   137  		if err := matcher.match(chain(apath, strconv.Itoa(i)), elem, ctx); err != nil {
   138  			return err
   139  		}
   140  	}
   141  	return nil
   142  }
   143  
   144  func (matcher mapAttrMatcher) feature(flabel string) bool {
   145  	for _, matcher1 := range matcher {
   146  		if matcher1.feature(flabel) {
   147  			return true
   148  		}
   149  	}
   150  	return false
   151  }
   152  
   153  func (matcher mapAttrMatcher) match(apath string, v interface{}, ctx AttrMatchContext) error {
   154  	switch x := v.(type) {
   155  	case Attrer:
   156  		// we get Atter from root-level Check (apath is "")
   157  		for k, matcher1 := range matcher {
   158  			v, _ := x.Lookup(k)
   159  			if err := matchEntry("", k, matcher1, v, ctx); err != nil {
   160  				return err
   161  			}
   162  		}
   163  	case map[string]interface{}: // maps in attributes look like this
   164  		for k, matcher1 := range matcher {
   165  			if err := matchEntry(apath, k, matcher1, x[k], ctx); err != nil {
   166  				return err
   167  			}
   168  		}
   169  	case []interface{}:
   170  		return matchList(apath, matcher, x, ctx)
   171  	default:
   172  		return fmt.Errorf("attribute %q must be a map", apath)
   173  	}
   174  	return nil
   175  }
   176  
   177  type missingAttrMatcher struct{}
   178  
   179  func (matcher missingAttrMatcher) feature(flabel string) bool {
   180  	return flabel == dollarAttrConstraintsFeature
   181  }
   182  
   183  func (matcher missingAttrMatcher) match(apath string, v interface{}, ctx AttrMatchContext) error {
   184  	if v != nil {
   185  		return fmt.Errorf("attribute %q is constrained to be missing but is set", apath)
   186  	}
   187  	return nil
   188  }
   189  
   190  type evalAttrMatcher struct {
   191  	// first iteration supports just $(SLOT|PLUG)(arg)
   192  	op  string
   193  	arg string
   194  }
   195  
   196  var (
   197  	validEvalAttrMatcher = regexp.MustCompile(`^\$(SLOT|PLUG)\((.+)\)$`)
   198  )
   199  
   200  func compileEvalAttrMatcher(cc compileContext, s string) (attrMatcher, error) {
   201  	ops := validEvalAttrMatcher.FindStringSubmatch(s)
   202  	if len(ops) == 0 {
   203  		return nil, fmt.Errorf("cannot compile %q constraint %q: not a valid $SLOT()/$PLUG() constraint", cc, s)
   204  	}
   205  	return evalAttrMatcher{
   206  		op:  ops[1],
   207  		arg: ops[2],
   208  	}, nil
   209  }
   210  
   211  func (matcher evalAttrMatcher) feature(flabel string) bool {
   212  	return flabel == dollarAttrConstraintsFeature
   213  }
   214  
   215  func (matcher evalAttrMatcher) match(apath string, v interface{}, ctx AttrMatchContext) error {
   216  	if ctx == nil {
   217  		return fmt.Errorf("attribute %q cannot be matched without context", apath)
   218  	}
   219  	var comp func(string) (interface{}, error)
   220  	switch matcher.op {
   221  	case "SLOT":
   222  		comp = ctx.SlotAttr
   223  	case "PLUG":
   224  		comp = ctx.PlugAttr
   225  	}
   226  	v1, err := comp(matcher.arg)
   227  	if err != nil {
   228  		return fmt.Errorf("attribute %q constraint $%s(%s) cannot be evaluated: %v", apath, matcher.op, matcher.arg, err)
   229  	}
   230  	if !reflect.DeepEqual(v, v1) {
   231  		return fmt.Errorf("attribute %q does not match $%s(%s): %v != %v", apath, matcher.op, matcher.arg, v, v1)
   232  	}
   233  	return nil
   234  }
   235  
   236  type regexpAttrMatcher struct {
   237  	*regexp.Regexp
   238  }
   239  
   240  func compileRegexpAttrMatcher(cc compileContext, s string) (attrMatcher, error) {
   241  	rx, err := regexp.Compile("^(" + s + ")$")
   242  	if err != nil {
   243  		return nil, fmt.Errorf("cannot compile %q constraint %q: %v", cc, s, err)
   244  	}
   245  	return regexpAttrMatcher{rx}, nil
   246  }
   247  
   248  func (matcher regexpAttrMatcher) feature(flabel string) bool {
   249  	return false
   250  }
   251  
   252  func (matcher regexpAttrMatcher) match(apath string, v interface{}, ctx AttrMatchContext) error {
   253  	var s string
   254  	switch x := v.(type) {
   255  	case string:
   256  		s = x
   257  	case bool:
   258  		s = strconv.FormatBool(x)
   259  	case int64:
   260  		s = strconv.FormatInt(x, 10)
   261  	case []interface{}:
   262  		return matchList(apath, matcher, x, ctx)
   263  	default:
   264  		return fmt.Errorf("attribute %q must be a scalar or list", apath)
   265  	}
   266  	if !matcher.Regexp.MatchString(s) {
   267  		return fmt.Errorf("attribute %q value %q does not match %v", apath, s, matcher.Regexp)
   268  	}
   269  	return nil
   270  
   271  }
   272  
   273  type altAttrMatcher struct {
   274  	alts []attrMatcher
   275  }
   276  
   277  func compileAltAttrMatcher(cc compileContext, l []interface{}) (attrMatcher, error) {
   278  	alts := make([]attrMatcher, len(l))
   279  	for i, constraint := range l {
   280  		matcher1, err := compileAttrMatcher(cc.alt(i), constraint)
   281  		if err != nil {
   282  			return nil, err
   283  		}
   284  		alts[i] = matcher1
   285  	}
   286  	return altAttrMatcher{alts}, nil
   287  
   288  }
   289  
   290  func (matcher altAttrMatcher) feature(flabel string) bool {
   291  	for _, alt := range matcher.alts {
   292  		if alt.feature(flabel) {
   293  			return true
   294  		}
   295  	}
   296  	return false
   297  }
   298  
   299  func (matcher altAttrMatcher) match(apath string, v interface{}, ctx AttrMatchContext) error {
   300  	var firstErr error
   301  	for _, alt := range matcher.alts {
   302  		err := alt.match(apath, v, ctx)
   303  		if err == nil {
   304  			return nil
   305  		}
   306  		if firstErr == nil {
   307  			firstErr = err
   308  		}
   309  	}
   310  	apathDescr := ""
   311  	if apath != "" {
   312  		apathDescr = fmt.Sprintf(" for attribute %q", apath)
   313  	}
   314  	return fmt.Errorf("no alternative%s matches: %v", apathDescr, firstErr)
   315  }
   316  
   317  // AttributeConstraints implements a set of constraints on the attributes of a slot or plug.
   318  type AttributeConstraints struct {
   319  	matcher attrMatcher
   320  }
   321  
   322  func (ac *AttributeConstraints) feature(flabel string) bool {
   323  	return ac.matcher.feature(flabel)
   324  }
   325  
   326  // compileAttributeConstraints checks and compiles a mapping or list
   327  // from the assertion format into AttributeConstraints.
   328  func compileAttributeConstraints(constraints interface{}) (*AttributeConstraints, error) {
   329  	matcher, err := compileAttrMatcher(compileContext{}, constraints)
   330  	if err != nil {
   331  		return nil, err
   332  	}
   333  	return &AttributeConstraints{matcher: matcher}, nil
   334  }
   335  
   336  type fixedAttrMatcher struct {
   337  	result error
   338  }
   339  
   340  func (matcher fixedAttrMatcher) feature(flabel string) bool {
   341  	return false
   342  }
   343  
   344  func (matcher fixedAttrMatcher) match(apath string, v interface{}, ctx AttrMatchContext) error {
   345  	return matcher.result
   346  }
   347  
   348  var (
   349  	AlwaysMatchAttributes = &AttributeConstraints{matcher: fixedAttrMatcher{nil}}
   350  	NeverMatchAttributes  = &AttributeConstraints{matcher: fixedAttrMatcher{errors.New("not allowed")}}
   351  )
   352  
   353  // Attrer reflects part of the Attrer interface (see interfaces.Attrer).
   354  type Attrer interface {
   355  	Lookup(path string) (interface{}, bool)
   356  }
   357  
   358  // Check checks whether attrs don't match the constraints.
   359  func (c *AttributeConstraints) Check(attrer Attrer, ctx AttrMatchContext) error {
   360  	return c.matcher.match("", attrer, ctx)
   361  }
   362  
   363  // OnClassicConstraint specifies a constraint based whether the system is classic and optional specific distros' sets.
   364  type OnClassicConstraint struct {
   365  	Classic   bool
   366  	SystemIDs []string
   367  }
   368  
   369  // DeviceScopeConstraint specifies a constraints based on which brand
   370  // store, brand or model the device belongs to.
   371  type DeviceScopeConstraint struct {
   372  	Store []string
   373  	Brand []string
   374  	// Model is a list of precise "<brand>/<model>" constraints
   375  	Model []string
   376  }
   377  
   378  var (
   379  	validStoreID         = regexp.MustCompile("^[-A-Z0-9a-z_]+$")
   380  	validBrandSlashModel = regexp.MustCompile("^(" +
   381  		strings.Trim(validAccountID.String(), "^$") +
   382  		")/(" +
   383  		strings.Trim(validModel.String(), "^$") +
   384  		")$")
   385  	deviceScopeConstraints = map[string]*regexp.Regexp{
   386  		"on-store": validStoreID,
   387  		"on-brand": validAccountID,
   388  		// on-model constraints are of the form list of
   389  		// <brand>/<model> strings where <brand> are account
   390  		// IDs as they appear in the respective model assertion
   391  		"on-model": validBrandSlashModel,
   392  	}
   393  )
   394  
   395  func detectDeviceScopeConstraint(cMap map[string]interface{}) bool {
   396  	// for consistency and simplicity we support all of on-store,
   397  	// on-brand, and on-model to appear together. The interpretation
   398  	// layer will AND them as usual
   399  	for field := range deviceScopeConstraints {
   400  		if cMap[field] != nil {
   401  			return true
   402  		}
   403  	}
   404  	return false
   405  }
   406  
   407  func compileDeviceScopeConstraint(cMap map[string]interface{}, context string) (constr *DeviceScopeConstraint, err error) {
   408  	// initial map size of 2: we expect usual cases to have just one of the
   409  	// constraints or rarely 2
   410  	deviceConstr := make(map[string][]string, 2)
   411  	for field, validRegexp := range deviceScopeConstraints {
   412  		vals, err := checkStringListInMap(cMap, field, fmt.Sprintf("%s in %s", field, context), validRegexp)
   413  		if err != nil {
   414  			return nil, err
   415  		}
   416  		deviceConstr[field] = vals
   417  	}
   418  
   419  	if len(deviceConstr) == 0 {
   420  		return nil, fmt.Errorf("internal error: misdetected device scope constraints in %s", context)
   421  	}
   422  	return &DeviceScopeConstraint{
   423  		Store: deviceConstr["on-store"],
   424  		Brand: deviceConstr["on-brand"],
   425  		Model: deviceConstr["on-model"],
   426  	}, nil
   427  }
   428  
   429  // rules
   430  
   431  var (
   432  	validSnapType  = regexp.MustCompile("^(?:core|kernel|gadget|app)$")
   433  	validDistro    = regexp.MustCompile("^[-0-9a-z._]+$")
   434  	validSnapID    = regexp.MustCompile("^[a-z0-9A-Z]{32}$")                                        // snap-ids look like this
   435  	validPublisher = regexp.MustCompile("^(?:[a-z0-9A-Z]{32}|[-a-z0-9]{2,28}|\\$[A-Z][A-Z0-9_]*)$") // account ids look like snap-ids or are nice identifiers, support our own special markers $MARKER
   436  
   437  	validIDConstraints = map[string]*regexp.Regexp{
   438  		"slot-snap-type":    validSnapType,
   439  		"slot-snap-id":      validSnapID,
   440  		"slot-publisher-id": validPublisher,
   441  		"plug-snap-type":    validSnapType,
   442  		"plug-snap-id":      validSnapID,
   443  		"plug-publisher-id": validPublisher,
   444  	}
   445  )
   446  
   447  func checkMapOrShortcut(context string, v interface{}) (m map[string]interface{}, invert bool, err error) {
   448  	switch x := v.(type) {
   449  	case map[string]interface{}:
   450  		return x, false, nil
   451  	case string:
   452  		switch x {
   453  		case "true":
   454  			return nil, false, nil
   455  		case "false":
   456  			return nil, true, nil
   457  		}
   458  	}
   459  	return nil, false, errors.New("unexpected type")
   460  }
   461  
   462  type constraintsHolder interface {
   463  	setAttributeConstraints(field string, cstrs *AttributeConstraints)
   464  	setIDConstraints(field string, cstrs []string)
   465  	setOnClassicConstraint(onClassic *OnClassicConstraint)
   466  	setDeviceScopeConstraint(deviceScope *DeviceScopeConstraint)
   467  }
   468  
   469  func baseCompileConstraints(context string, cDef constraintsDef, target constraintsHolder, attrConstraints, idConstraints []string) error {
   470  	cMap := cDef.cMap
   471  	if cMap == nil {
   472  		fixed := AlwaysMatchAttributes // "true"
   473  		if cDef.invert {               // "false"
   474  			fixed = NeverMatchAttributes
   475  		}
   476  		for _, field := range attrConstraints {
   477  			target.setAttributeConstraints(field, fixed)
   478  		}
   479  		return nil
   480  	}
   481  	defaultUsed := 0
   482  	for _, field := range idConstraints {
   483  		lst, err := checkStringListInMap(cMap, field, fmt.Sprintf("%s in %s", field, context), validIDConstraints[field])
   484  		if err != nil {
   485  			return err
   486  		}
   487  		if lst == nil {
   488  			defaultUsed++
   489  		}
   490  		target.setIDConstraints(field, lst)
   491  	}
   492  	for _, field := range attrConstraints {
   493  		cstrs := AlwaysMatchAttributes
   494  		v := cMap[field]
   495  		if v != nil {
   496  			var err error
   497  			cstrs, err = compileAttributeConstraints(cMap[field])
   498  			if err != nil {
   499  				return fmt.Errorf("cannot compile %s in %s: %v", field, context, err)
   500  			}
   501  		} else {
   502  			defaultUsed++
   503  		}
   504  		target.setAttributeConstraints(field, cstrs)
   505  	}
   506  	onClassic := cMap["on-classic"]
   507  	if onClassic == nil {
   508  		defaultUsed++
   509  	} else {
   510  		var c *OnClassicConstraint
   511  		switch x := onClassic.(type) {
   512  		case string:
   513  			switch x {
   514  			case "true":
   515  				c = &OnClassicConstraint{Classic: true}
   516  			case "false":
   517  				c = &OnClassicConstraint{Classic: false}
   518  			}
   519  		case []interface{}:
   520  			lst, err := checkStringListInMap(cMap, "on-classic", fmt.Sprintf("on-classic in %s", context), validDistro)
   521  			if err != nil {
   522  				return err
   523  			}
   524  			c = &OnClassicConstraint{Classic: true, SystemIDs: lst}
   525  		}
   526  		if c == nil {
   527  			return fmt.Errorf("on-classic in %s must be 'true', 'false' or a list of operating system IDs", context)
   528  		}
   529  		target.setOnClassicConstraint(c)
   530  	}
   531  	if !detectDeviceScopeConstraint(cMap) {
   532  		defaultUsed++
   533  	} else {
   534  		c, err := compileDeviceScopeConstraint(cMap, context)
   535  		if err != nil {
   536  			return err
   537  		}
   538  		target.setDeviceScopeConstraint(c)
   539  	}
   540  	// checks whether defaults have been used for everything, which is not
   541  	// well-formed
   542  	// +1+1 accounts for defaults for missing on-classic plus missing
   543  	// on-store/on-brand/on-model
   544  	if defaultUsed == len(attributeConstraints)+len(idConstraints)+1+1 {
   545  		return fmt.Errorf("%s must specify at least one of %s, %s, on-classic, on-store, on-brand, on-model", context, strings.Join(attrConstraints, ", "), strings.Join(idConstraints, ", "))
   546  	}
   547  	return nil
   548  }
   549  
   550  type rule interface {
   551  	setConstraints(field string, cstrs []constraintsHolder)
   552  }
   553  
   554  type constraintsDef struct {
   555  	cMap   map[string]interface{}
   556  	invert bool
   557  }
   558  
   559  type subruleCompiler func(context string, def constraintsDef) (constraintsHolder, error)
   560  
   561  func baseCompileRule(context string, rule interface{}, target rule, subrules []string, compilers map[string]subruleCompiler, defaultOutcome, invertedOutcome map[string]interface{}) error {
   562  	rMap, invert, err := checkMapOrShortcut(context, rule)
   563  	if err != nil {
   564  		return fmt.Errorf("%s must be a map or one of the shortcuts 'true' or 'false'", context)
   565  	}
   566  	if rMap == nil {
   567  		rMap = defaultOutcome // "true"
   568  		if invert {
   569  			rMap = invertedOutcome // "false"
   570  		}
   571  	}
   572  	defaultUsed := 0
   573  	// compile and set subrules
   574  	for _, subrule := range subrules {
   575  		v := rMap[subrule]
   576  		var lst []interface{}
   577  		alternatives := false
   578  		switch x := v.(type) {
   579  		case nil:
   580  			v = defaultOutcome[subrule]
   581  			defaultUsed++
   582  		case []interface{}:
   583  			alternatives = true
   584  			lst = x
   585  		}
   586  		if lst == nil { // v is map or a string, checked below
   587  			lst = []interface{}{v}
   588  		}
   589  		compiler := compilers[subrule]
   590  		if compiler == nil {
   591  			panic(fmt.Sprintf("no compiler for %s in %s", subrule, context))
   592  		}
   593  		alts := make([]constraintsHolder, len(lst))
   594  		for i, alt := range lst {
   595  			subctxt := fmt.Sprintf("%s in %s", subrule, context)
   596  			if alternatives {
   597  				subctxt = fmt.Sprintf("alternative %d of %s", i+1, subctxt)
   598  			}
   599  			cMap, invert, err := checkMapOrShortcut(subctxt, alt)
   600  			if err != nil || (cMap == nil && alternatives) {
   601  				efmt := "%s must be a map"
   602  				if !alternatives {
   603  					efmt = "%s must be a map or one of the shortcuts 'true' or 'false'"
   604  				}
   605  				return fmt.Errorf(efmt, subctxt)
   606  			}
   607  
   608  			cstrs, err := compiler(subctxt, constraintsDef{
   609  				cMap:   cMap,
   610  				invert: invert,
   611  			})
   612  			if err != nil {
   613  				return err
   614  			}
   615  			alts[i] = cstrs
   616  		}
   617  		target.setConstraints(subrule, alts)
   618  	}
   619  	if defaultUsed == len(subrules) {
   620  		return fmt.Errorf("%s must specify at least one of %s", context, strings.Join(subrules, ", "))
   621  	}
   622  	return nil
   623  }
   624  
   625  // PlugRule holds the rule of what is allowed, wrt installation and
   626  // connection, for a plug of a specific interface for a snap.
   627  type PlugRule struct {
   628  	Interface string
   629  
   630  	AllowInstallation []*PlugInstallationConstraints
   631  	DenyInstallation  []*PlugInstallationConstraints
   632  
   633  	AllowConnection []*PlugConnectionConstraints
   634  	DenyConnection  []*PlugConnectionConstraints
   635  
   636  	AllowAutoConnection []*PlugConnectionConstraints
   637  	DenyAutoConnection  []*PlugConnectionConstraints
   638  }
   639  
   640  func (r *PlugRule) feature(flabel string) bool {
   641  	for _, cs := range [][]*PlugInstallationConstraints{r.AllowInstallation, r.DenyInstallation} {
   642  		for _, c := range cs {
   643  			if c.feature(flabel) {
   644  				return true
   645  			}
   646  		}
   647  	}
   648  
   649  	for _, cs := range [][]*PlugConnectionConstraints{r.AllowConnection, r.DenyConnection, r.AllowAutoConnection, r.DenyAutoConnection} {
   650  		for _, c := range cs {
   651  			if c.feature(flabel) {
   652  				return true
   653  			}
   654  		}
   655  	}
   656  
   657  	return false
   658  }
   659  
   660  func castPlugInstallationConstraints(cstrs []constraintsHolder) (res []*PlugInstallationConstraints) {
   661  	res = make([]*PlugInstallationConstraints, len(cstrs))
   662  	for i, cstr := range cstrs {
   663  		res[i] = cstr.(*PlugInstallationConstraints)
   664  	}
   665  	return res
   666  }
   667  
   668  func castPlugConnectionConstraints(cstrs []constraintsHolder) (res []*PlugConnectionConstraints) {
   669  	res = make([]*PlugConnectionConstraints, len(cstrs))
   670  	for i, cstr := range cstrs {
   671  		res[i] = cstr.(*PlugConnectionConstraints)
   672  	}
   673  	return res
   674  }
   675  
   676  func (r *PlugRule) setConstraints(field string, cstrs []constraintsHolder) {
   677  	if len(cstrs) == 0 {
   678  		panic(fmt.Sprintf("cannot set PlugRule field %q to empty", field))
   679  	}
   680  	switch cstrs[0].(type) {
   681  	case *PlugInstallationConstraints:
   682  		switch field {
   683  		case "allow-installation":
   684  			r.AllowInstallation = castPlugInstallationConstraints(cstrs)
   685  			return
   686  		case "deny-installation":
   687  			r.DenyInstallation = castPlugInstallationConstraints(cstrs)
   688  			return
   689  		}
   690  	case *PlugConnectionConstraints:
   691  		switch field {
   692  		case "allow-connection":
   693  			r.AllowConnection = castPlugConnectionConstraints(cstrs)
   694  			return
   695  		case "deny-connection":
   696  			r.DenyConnection = castPlugConnectionConstraints(cstrs)
   697  			return
   698  		case "allow-auto-connection":
   699  			r.AllowAutoConnection = castPlugConnectionConstraints(cstrs)
   700  			return
   701  		case "deny-auto-connection":
   702  			r.DenyAutoConnection = castPlugConnectionConstraints(cstrs)
   703  			return
   704  		}
   705  	}
   706  	panic(fmt.Sprintf("cannot set PlugRule field %q with %T elements", field, cstrs[0]))
   707  }
   708  
   709  // PlugInstallationConstraints specifies a set of constraints on an interface plug relevant to the installation of snap.
   710  type PlugInstallationConstraints struct {
   711  	PlugSnapTypes []string
   712  
   713  	PlugAttributes *AttributeConstraints
   714  
   715  	OnClassic *OnClassicConstraint
   716  
   717  	DeviceScope *DeviceScopeConstraint
   718  }
   719  
   720  func (c *PlugInstallationConstraints) feature(flabel string) bool {
   721  	if flabel == deviceScopeConstraintsFeature {
   722  		return c.DeviceScope != nil
   723  	}
   724  	return c.PlugAttributes.feature(flabel)
   725  }
   726  
   727  func (c *PlugInstallationConstraints) setAttributeConstraints(field string, cstrs *AttributeConstraints) {
   728  	switch field {
   729  	case "plug-attributes":
   730  		c.PlugAttributes = cstrs
   731  	default:
   732  		panic("unknown PlugInstallationConstraints field " + field)
   733  	}
   734  }
   735  
   736  func (c *PlugInstallationConstraints) setIDConstraints(field string, cstrs []string) {
   737  	switch field {
   738  	case "plug-snap-type":
   739  		c.PlugSnapTypes = cstrs
   740  	default:
   741  		panic("unknown PlugInstallationConstraints field " + field)
   742  	}
   743  }
   744  
   745  func (c *PlugInstallationConstraints) setOnClassicConstraint(onClassic *OnClassicConstraint) {
   746  	c.OnClassic = onClassic
   747  }
   748  
   749  func (c *PlugInstallationConstraints) setDeviceScopeConstraint(deviceScope *DeviceScopeConstraint) {
   750  	c.DeviceScope = deviceScope
   751  }
   752  
   753  func compilePlugInstallationConstraints(context string, cDef constraintsDef) (constraintsHolder, error) {
   754  	plugInstCstrs := &PlugInstallationConstraints{}
   755  	err := baseCompileConstraints(context, cDef, plugInstCstrs, []string{"plug-attributes"}, []string{"plug-snap-type"})
   756  	if err != nil {
   757  		return nil, err
   758  	}
   759  	return plugInstCstrs, nil
   760  }
   761  
   762  // PlugConnectionConstraints specfies a set of constraints on an
   763  // interface plug for a snap relevant to its connection or
   764  // auto-connection.
   765  type PlugConnectionConstraints struct {
   766  	SlotSnapTypes    []string
   767  	SlotSnapIDs      []string
   768  	SlotPublisherIDs []string
   769  
   770  	PlugAttributes *AttributeConstraints
   771  	SlotAttributes *AttributeConstraints
   772  
   773  	OnClassic *OnClassicConstraint
   774  
   775  	DeviceScope *DeviceScopeConstraint
   776  }
   777  
   778  func (c *PlugConnectionConstraints) feature(flabel string) bool {
   779  	if flabel == deviceScopeConstraintsFeature {
   780  		return c.DeviceScope != nil
   781  	}
   782  	return c.PlugAttributes.feature(flabel) || c.SlotAttributes.feature(flabel)
   783  }
   784  
   785  func (c *PlugConnectionConstraints) setAttributeConstraints(field string, cstrs *AttributeConstraints) {
   786  	switch field {
   787  	case "plug-attributes":
   788  		c.PlugAttributes = cstrs
   789  	case "slot-attributes":
   790  		c.SlotAttributes = cstrs
   791  	default:
   792  		panic("unknown PlugConnectionConstraints field " + field)
   793  	}
   794  }
   795  
   796  func (c *PlugConnectionConstraints) setIDConstraints(field string, cstrs []string) {
   797  	switch field {
   798  	case "slot-snap-type":
   799  		c.SlotSnapTypes = cstrs
   800  	case "slot-snap-id":
   801  		c.SlotSnapIDs = cstrs
   802  	case "slot-publisher-id":
   803  		c.SlotPublisherIDs = cstrs
   804  	default:
   805  		panic("unknown PlugConnectionConstraints field " + field)
   806  	}
   807  }
   808  
   809  func (c *PlugConnectionConstraints) setOnClassicConstraint(onClassic *OnClassicConstraint) {
   810  	c.OnClassic = onClassic
   811  }
   812  
   813  func (c *PlugConnectionConstraints) setDeviceScopeConstraint(deviceScope *DeviceScopeConstraint) {
   814  	c.DeviceScope = deviceScope
   815  }
   816  
   817  var (
   818  	attributeConstraints = []string{"plug-attributes", "slot-attributes"}
   819  	plugIDConstraints    = []string{"slot-snap-type", "slot-publisher-id", "slot-snap-id"}
   820  )
   821  
   822  func compilePlugConnectionConstraints(context string, cDef constraintsDef) (constraintsHolder, error) {
   823  	plugConnCstrs := &PlugConnectionConstraints{}
   824  	err := baseCompileConstraints(context, cDef, plugConnCstrs, attributeConstraints, plugIDConstraints)
   825  	if err != nil {
   826  		return nil, err
   827  	}
   828  	return plugConnCstrs, nil
   829  }
   830  
   831  var (
   832  	defaultOutcome = map[string]interface{}{
   833  		"allow-installation":    "true",
   834  		"allow-connection":      "true",
   835  		"allow-auto-connection": "true",
   836  		"deny-installation":     "false",
   837  		"deny-connection":       "false",
   838  		"deny-auto-connection":  "false",
   839  	}
   840  
   841  	invertedOutcome = map[string]interface{}{
   842  		"allow-installation":    "false",
   843  		"allow-connection":      "false",
   844  		"allow-auto-connection": "false",
   845  		"deny-installation":     "true",
   846  		"deny-connection":       "true",
   847  		"deny-auto-connection":  "true",
   848  	}
   849  
   850  	ruleSubrules = []string{"allow-installation", "deny-installation", "allow-connection", "deny-connection", "allow-auto-connection", "deny-auto-connection"}
   851  )
   852  
   853  var plugRuleCompilers = map[string]subruleCompiler{
   854  	"allow-installation":    compilePlugInstallationConstraints,
   855  	"deny-installation":     compilePlugInstallationConstraints,
   856  	"allow-connection":      compilePlugConnectionConstraints,
   857  	"deny-connection":       compilePlugConnectionConstraints,
   858  	"allow-auto-connection": compilePlugConnectionConstraints,
   859  	"deny-auto-connection":  compilePlugConnectionConstraints,
   860  }
   861  
   862  func compilePlugRule(interfaceName string, rule interface{}) (*PlugRule, error) {
   863  	context := fmt.Sprintf("plug rule for interface %q", interfaceName)
   864  	plugRule := &PlugRule{
   865  		Interface: interfaceName,
   866  	}
   867  	err := baseCompileRule(context, rule, plugRule, ruleSubrules, plugRuleCompilers, defaultOutcome, invertedOutcome)
   868  	if err != nil {
   869  		return nil, err
   870  	}
   871  	return plugRule, nil
   872  }
   873  
   874  // SlotRule holds the rule of what is allowed, wrt installation and
   875  // connection, for a slot of a specific interface for a snap.
   876  type SlotRule struct {
   877  	Interface string
   878  
   879  	AllowInstallation []*SlotInstallationConstraints
   880  	DenyInstallation  []*SlotInstallationConstraints
   881  
   882  	AllowConnection []*SlotConnectionConstraints
   883  	DenyConnection  []*SlotConnectionConstraints
   884  
   885  	AllowAutoConnection []*SlotConnectionConstraints
   886  	DenyAutoConnection  []*SlotConnectionConstraints
   887  }
   888  
   889  func castSlotInstallationConstraints(cstrs []constraintsHolder) (res []*SlotInstallationConstraints) {
   890  	res = make([]*SlotInstallationConstraints, len(cstrs))
   891  	for i, cstr := range cstrs {
   892  		res[i] = cstr.(*SlotInstallationConstraints)
   893  	}
   894  	return res
   895  }
   896  
   897  func (r *SlotRule) feature(flabel string) bool {
   898  	for _, cs := range [][]*SlotInstallationConstraints{r.AllowInstallation, r.DenyInstallation} {
   899  		for _, c := range cs {
   900  			if c.feature(flabel) {
   901  				return true
   902  			}
   903  		}
   904  	}
   905  
   906  	for _, cs := range [][]*SlotConnectionConstraints{r.AllowConnection, r.DenyConnection, r.AllowAutoConnection, r.DenyAutoConnection} {
   907  		for _, c := range cs {
   908  			if c.feature(flabel) {
   909  				return true
   910  			}
   911  		}
   912  	}
   913  
   914  	return false
   915  }
   916  
   917  func castSlotConnectionConstraints(cstrs []constraintsHolder) (res []*SlotConnectionConstraints) {
   918  	res = make([]*SlotConnectionConstraints, len(cstrs))
   919  	for i, cstr := range cstrs {
   920  		res[i] = cstr.(*SlotConnectionConstraints)
   921  	}
   922  	return res
   923  }
   924  
   925  func (r *SlotRule) setConstraints(field string, cstrs []constraintsHolder) {
   926  	if len(cstrs) == 0 {
   927  		panic(fmt.Sprintf("cannot set SlotRule field %q to empty", field))
   928  	}
   929  	switch cstrs[0].(type) {
   930  	case *SlotInstallationConstraints:
   931  		switch field {
   932  		case "allow-installation":
   933  			r.AllowInstallation = castSlotInstallationConstraints(cstrs)
   934  			return
   935  		case "deny-installation":
   936  			r.DenyInstallation = castSlotInstallationConstraints(cstrs)
   937  			return
   938  		}
   939  	case *SlotConnectionConstraints:
   940  		switch field {
   941  		case "allow-connection":
   942  			r.AllowConnection = castSlotConnectionConstraints(cstrs)
   943  			return
   944  		case "deny-connection":
   945  			r.DenyConnection = castSlotConnectionConstraints(cstrs)
   946  			return
   947  		case "allow-auto-connection":
   948  			r.AllowAutoConnection = castSlotConnectionConstraints(cstrs)
   949  			return
   950  		case "deny-auto-connection":
   951  			r.DenyAutoConnection = castSlotConnectionConstraints(cstrs)
   952  			return
   953  		}
   954  	}
   955  	panic(fmt.Sprintf("cannot set SlotRule field %q with %T elements", field, cstrs[0]))
   956  }
   957  
   958  // SlotInstallationConstraints specifies a set of constraints on an
   959  // interface slot relevant to the installation of snap.
   960  type SlotInstallationConstraints struct {
   961  	SlotSnapTypes []string
   962  
   963  	SlotAttributes *AttributeConstraints
   964  
   965  	OnClassic *OnClassicConstraint
   966  
   967  	DeviceScope *DeviceScopeConstraint
   968  }
   969  
   970  func (c *SlotInstallationConstraints) feature(flabel string) bool {
   971  	if flabel == deviceScopeConstraintsFeature {
   972  		return c.DeviceScope != nil
   973  	}
   974  	return c.SlotAttributes.feature(flabel)
   975  }
   976  
   977  func (c *SlotInstallationConstraints) setAttributeConstraints(field string, cstrs *AttributeConstraints) {
   978  	switch field {
   979  	case "slot-attributes":
   980  		c.SlotAttributes = cstrs
   981  	default:
   982  		panic("unknown SlotInstallationConstraints field " + field)
   983  	}
   984  }
   985  
   986  func (c *SlotInstallationConstraints) setIDConstraints(field string, cstrs []string) {
   987  	switch field {
   988  	case "slot-snap-type":
   989  		c.SlotSnapTypes = cstrs
   990  	default:
   991  		panic("unknown SlotInstallationConstraints field " + field)
   992  	}
   993  }
   994  
   995  func (c *SlotInstallationConstraints) setOnClassicConstraint(onClassic *OnClassicConstraint) {
   996  	c.OnClassic = onClassic
   997  }
   998  
   999  func (c *SlotInstallationConstraints) setDeviceScopeConstraint(deviceScope *DeviceScopeConstraint) {
  1000  	c.DeviceScope = deviceScope
  1001  }
  1002  
  1003  func compileSlotInstallationConstraints(context string, cDef constraintsDef) (constraintsHolder, error) {
  1004  	slotInstCstrs := &SlotInstallationConstraints{}
  1005  	err := baseCompileConstraints(context, cDef, slotInstCstrs, []string{"slot-attributes"}, []string{"slot-snap-type"})
  1006  	if err != nil {
  1007  		return nil, err
  1008  	}
  1009  	return slotInstCstrs, nil
  1010  }
  1011  
  1012  // SlotConnectionConstraints specfies a set of constraints on an
  1013  // interface slot for a snap relevant to its connection or
  1014  // auto-connection.
  1015  type SlotConnectionConstraints struct {
  1016  	PlugSnapTypes    []string
  1017  	PlugSnapIDs      []string
  1018  	PlugPublisherIDs []string
  1019  
  1020  	SlotAttributes *AttributeConstraints
  1021  	PlugAttributes *AttributeConstraints
  1022  
  1023  	OnClassic *OnClassicConstraint
  1024  
  1025  	DeviceScope *DeviceScopeConstraint
  1026  }
  1027  
  1028  func (c *SlotConnectionConstraints) feature(flabel string) bool {
  1029  	if flabel == deviceScopeConstraintsFeature {
  1030  		return c.DeviceScope != nil
  1031  	}
  1032  	return c.PlugAttributes.feature(flabel) || c.SlotAttributes.feature(flabel)
  1033  }
  1034  
  1035  func (c *SlotConnectionConstraints) setAttributeConstraints(field string, cstrs *AttributeConstraints) {
  1036  	switch field {
  1037  	case "plug-attributes":
  1038  		c.PlugAttributes = cstrs
  1039  	case "slot-attributes":
  1040  		c.SlotAttributes = cstrs
  1041  	default:
  1042  		panic("unknown SlotConnectionConstraints field " + field)
  1043  	}
  1044  }
  1045  
  1046  func (c *SlotConnectionConstraints) setIDConstraints(field string, cstrs []string) {
  1047  	switch field {
  1048  	case "plug-snap-type":
  1049  		c.PlugSnapTypes = cstrs
  1050  	case "plug-snap-id":
  1051  		c.PlugSnapIDs = cstrs
  1052  	case "plug-publisher-id":
  1053  		c.PlugPublisherIDs = cstrs
  1054  	default:
  1055  		panic("unknown SlotConnectionConstraints field " + field)
  1056  	}
  1057  }
  1058  
  1059  var (
  1060  	slotIDConstraints = []string{"plug-snap-type", "plug-publisher-id", "plug-snap-id"}
  1061  )
  1062  
  1063  func (c *SlotConnectionConstraints) setOnClassicConstraint(onClassic *OnClassicConstraint) {
  1064  	c.OnClassic = onClassic
  1065  }
  1066  
  1067  func (c *SlotConnectionConstraints) setDeviceScopeConstraint(deviceScope *DeviceScopeConstraint) {
  1068  	c.DeviceScope = deviceScope
  1069  }
  1070  
  1071  func compileSlotConnectionConstraints(context string, cDef constraintsDef) (constraintsHolder, error) {
  1072  	slotConnCstrs := &SlotConnectionConstraints{}
  1073  	err := baseCompileConstraints(context, cDef, slotConnCstrs, attributeConstraints, slotIDConstraints)
  1074  	if err != nil {
  1075  		return nil, err
  1076  	}
  1077  	return slotConnCstrs, nil
  1078  }
  1079  
  1080  var slotRuleCompilers = map[string]subruleCompiler{
  1081  	"allow-installation":    compileSlotInstallationConstraints,
  1082  	"deny-installation":     compileSlotInstallationConstraints,
  1083  	"allow-connection":      compileSlotConnectionConstraints,
  1084  	"deny-connection":       compileSlotConnectionConstraints,
  1085  	"allow-auto-connection": compileSlotConnectionConstraints,
  1086  	"deny-auto-connection":  compileSlotConnectionConstraints,
  1087  }
  1088  
  1089  func compileSlotRule(interfaceName string, rule interface{}) (*SlotRule, error) {
  1090  	context := fmt.Sprintf("slot rule for interface %q", interfaceName)
  1091  	slotRule := &SlotRule{
  1092  		Interface: interfaceName,
  1093  	}
  1094  	err := baseCompileRule(context, rule, slotRule, ruleSubrules, slotRuleCompilers, defaultOutcome, invertedOutcome)
  1095  	if err != nil {
  1096  		return nil, err
  1097  	}
  1098  	return slotRule, nil
  1099  }