github.com/kubiko/snapd@v0.0.0-20201013125620-d4f3094d9ddf/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  	"unicode"
    30  
    31  	"github.com/snapcore/snapd/snap/naming"
    32  )
    33  
    34  // AttrMatchContext has contextual helpers for evaluating attribute constraints.
    35  type AttrMatchContext interface {
    36  	PlugAttr(arg string) (interface{}, error)
    37  	SlotAttr(arg string) (interface{}, error)
    38  }
    39  
    40  const (
    41  	// feature label for $SLOT()/$PLUG()/$MISSING
    42  	dollarAttrConstraintsFeature = "dollar-attr-constraints"
    43  	// feature label for on-store/on-brand/on-model
    44  	deviceScopeConstraintsFeature = "device-scope-constraints"
    45  	// feature label for plug-names/slot-names constraints
    46  	nameConstraintsFeature = "name-constraints"
    47  )
    48  
    49  type attrMatcher interface {
    50  	match(apath string, v interface{}, ctx AttrMatchContext) error
    51  
    52  	feature(flabel string) bool
    53  }
    54  
    55  func chain(path, k string) string {
    56  	if path == "" {
    57  		return k
    58  	}
    59  	return fmt.Sprintf("%s.%s", path, k)
    60  }
    61  
    62  type compileContext struct {
    63  	dotted string
    64  	hadMap bool
    65  	wasAlt bool
    66  }
    67  
    68  func (cc compileContext) String() string {
    69  	return cc.dotted
    70  }
    71  
    72  func (cc compileContext) keyEntry(k string) compileContext {
    73  	return compileContext{
    74  		dotted: chain(cc.dotted, k),
    75  		hadMap: true,
    76  		wasAlt: false,
    77  	}
    78  }
    79  
    80  func (cc compileContext) alt(alt int) compileContext {
    81  	return compileContext{
    82  		dotted: fmt.Sprintf("%s/alt#%d/", cc.dotted, alt+1),
    83  		hadMap: cc.hadMap,
    84  		wasAlt: true,
    85  	}
    86  }
    87  
    88  // compileAttrMatcher compiles an attrMatcher derived from constraints,
    89  func compileAttrMatcher(cc compileContext, constraints interface{}) (attrMatcher, error) {
    90  	switch x := constraints.(type) {
    91  	case map[string]interface{}:
    92  		return compileMapAttrMatcher(cc, x)
    93  	case []interface{}:
    94  		if cc.wasAlt {
    95  			return nil, fmt.Errorf("cannot nest alternative constraints directly at %q", cc)
    96  		}
    97  		return compileAltAttrMatcher(cc, x)
    98  	case string:
    99  		if !cc.hadMap {
   100  			return nil, fmt.Errorf("first level of non alternative constraints must be a set of key-value contraints")
   101  		}
   102  		if strings.HasPrefix(x, "$") {
   103  			if x == "$MISSING" {
   104  				return missingAttrMatcher{}, nil
   105  			}
   106  			return compileEvalAttrMatcher(cc, x)
   107  		}
   108  		return compileRegexpAttrMatcher(cc, x)
   109  	default:
   110  		return nil, fmt.Errorf("constraint %q must be a key-value map, regexp or a list of alternative constraints: %v", cc, x)
   111  	}
   112  }
   113  
   114  type mapAttrMatcher map[string]attrMatcher
   115  
   116  func compileMapAttrMatcher(cc compileContext, m map[string]interface{}) (attrMatcher, error) {
   117  	matcher := make(mapAttrMatcher)
   118  	for k, constraint := range m {
   119  		matcher1, err := compileAttrMatcher(cc.keyEntry(k), constraint)
   120  		if err != nil {
   121  			return nil, err
   122  		}
   123  		matcher[k] = matcher1
   124  	}
   125  	return matcher, nil
   126  }
   127  
   128  func matchEntry(apath, k string, matcher1 attrMatcher, v interface{}, ctx AttrMatchContext) error {
   129  	apath = chain(apath, k)
   130  	// every entry matcher expects the attribute to be set except for $MISSING
   131  	if _, ok := matcher1.(missingAttrMatcher); !ok && v == nil {
   132  		return fmt.Errorf("attribute %q has constraints but is unset", apath)
   133  	}
   134  	if err := matcher1.match(apath, v, ctx); err != nil {
   135  		return err
   136  	}
   137  	return nil
   138  }
   139  
   140  func matchList(apath string, matcher attrMatcher, l []interface{}, ctx AttrMatchContext) error {
   141  	for i, elem := range l {
   142  		if err := matcher.match(chain(apath, strconv.Itoa(i)), elem, ctx); err != nil {
   143  			return err
   144  		}
   145  	}
   146  	return nil
   147  }
   148  
   149  func (matcher mapAttrMatcher) feature(flabel string) bool {
   150  	for _, matcher1 := range matcher {
   151  		if matcher1.feature(flabel) {
   152  			return true
   153  		}
   154  	}
   155  	return false
   156  }
   157  
   158  func (matcher mapAttrMatcher) match(apath string, v interface{}, ctx AttrMatchContext) error {
   159  	switch x := v.(type) {
   160  	case Attrer:
   161  		// we get Atter from root-level Check (apath is "")
   162  		for k, matcher1 := range matcher {
   163  			v, _ := x.Lookup(k)
   164  			if err := matchEntry("", k, matcher1, v, ctx); err != nil {
   165  				return err
   166  			}
   167  		}
   168  	case map[string]interface{}: // maps in attributes look like this
   169  		for k, matcher1 := range matcher {
   170  			if err := matchEntry(apath, k, matcher1, x[k], ctx); err != nil {
   171  				return err
   172  			}
   173  		}
   174  	case []interface{}:
   175  		return matchList(apath, matcher, x, ctx)
   176  	default:
   177  		return fmt.Errorf("attribute %q must be a map", apath)
   178  	}
   179  	return nil
   180  }
   181  
   182  type missingAttrMatcher struct{}
   183  
   184  func (matcher missingAttrMatcher) feature(flabel string) bool {
   185  	return flabel == dollarAttrConstraintsFeature
   186  }
   187  
   188  func (matcher missingAttrMatcher) match(apath string, v interface{}, ctx AttrMatchContext) error {
   189  	if v != nil {
   190  		return fmt.Errorf("attribute %q is constrained to be missing but is set", apath)
   191  	}
   192  	return nil
   193  }
   194  
   195  type evalAttrMatcher struct {
   196  	// first iteration supports just $(SLOT|PLUG)(arg)
   197  	op  string
   198  	arg string
   199  }
   200  
   201  var (
   202  	validEvalAttrMatcher = regexp.MustCompile(`^\$(SLOT|PLUG)\((.+)\)$`)
   203  )
   204  
   205  func compileEvalAttrMatcher(cc compileContext, s string) (attrMatcher, error) {
   206  	ops := validEvalAttrMatcher.FindStringSubmatch(s)
   207  	if len(ops) == 0 {
   208  		return nil, fmt.Errorf("cannot compile %q constraint %q: not a valid $SLOT()/$PLUG() constraint", cc, s)
   209  	}
   210  	return evalAttrMatcher{
   211  		op:  ops[1],
   212  		arg: ops[2],
   213  	}, nil
   214  }
   215  
   216  func (matcher evalAttrMatcher) feature(flabel string) bool {
   217  	return flabel == dollarAttrConstraintsFeature
   218  }
   219  
   220  func (matcher evalAttrMatcher) match(apath string, v interface{}, ctx AttrMatchContext) error {
   221  	if ctx == nil {
   222  		return fmt.Errorf("attribute %q cannot be matched without context", apath)
   223  	}
   224  	var comp func(string) (interface{}, error)
   225  	switch matcher.op {
   226  	case "SLOT":
   227  		comp = ctx.SlotAttr
   228  	case "PLUG":
   229  		comp = ctx.PlugAttr
   230  	}
   231  	v1, err := comp(matcher.arg)
   232  	if err != nil {
   233  		return fmt.Errorf("attribute %q constraint $%s(%s) cannot be evaluated: %v", apath, matcher.op, matcher.arg, err)
   234  	}
   235  	if !reflect.DeepEqual(v, v1) {
   236  		return fmt.Errorf("attribute %q does not match $%s(%s): %v != %v", apath, matcher.op, matcher.arg, v, v1)
   237  	}
   238  	return nil
   239  }
   240  
   241  type regexpAttrMatcher struct {
   242  	*regexp.Regexp
   243  }
   244  
   245  func compileRegexpAttrMatcher(cc compileContext, s string) (attrMatcher, error) {
   246  	rx, err := regexp.Compile("^(" + s + ")$")
   247  	if err != nil {
   248  		return nil, fmt.Errorf("cannot compile %q constraint %q: %v", cc, s, err)
   249  	}
   250  	return regexpAttrMatcher{rx}, nil
   251  }
   252  
   253  func (matcher regexpAttrMatcher) feature(flabel string) bool {
   254  	return false
   255  }
   256  
   257  func (matcher regexpAttrMatcher) match(apath string, v interface{}, ctx AttrMatchContext) error {
   258  	var s string
   259  	switch x := v.(type) {
   260  	case string:
   261  		s = x
   262  	case bool:
   263  		s = strconv.FormatBool(x)
   264  	case int64:
   265  		s = strconv.FormatInt(x, 10)
   266  	case []interface{}:
   267  		return matchList(apath, matcher, x, ctx)
   268  	default:
   269  		return fmt.Errorf("attribute %q must be a scalar or list", apath)
   270  	}
   271  	if !matcher.Regexp.MatchString(s) {
   272  		return fmt.Errorf("attribute %q value %q does not match %v", apath, s, matcher.Regexp)
   273  	}
   274  	return nil
   275  
   276  }
   277  
   278  type altAttrMatcher struct {
   279  	alts []attrMatcher
   280  }
   281  
   282  func compileAltAttrMatcher(cc compileContext, l []interface{}) (attrMatcher, error) {
   283  	alts := make([]attrMatcher, len(l))
   284  	for i, constraint := range l {
   285  		matcher1, err := compileAttrMatcher(cc.alt(i), constraint)
   286  		if err != nil {
   287  			return nil, err
   288  		}
   289  		alts[i] = matcher1
   290  	}
   291  	return altAttrMatcher{alts}, nil
   292  
   293  }
   294  
   295  func (matcher altAttrMatcher) feature(flabel string) bool {
   296  	for _, alt := range matcher.alts {
   297  		if alt.feature(flabel) {
   298  			return true
   299  		}
   300  	}
   301  	return false
   302  }
   303  
   304  func (matcher altAttrMatcher) match(apath string, v interface{}, ctx AttrMatchContext) error {
   305  	var firstErr error
   306  	for _, alt := range matcher.alts {
   307  		err := alt.match(apath, v, ctx)
   308  		if err == nil {
   309  			return nil
   310  		}
   311  		if firstErr == nil {
   312  			firstErr = err
   313  		}
   314  	}
   315  	apathDescr := ""
   316  	if apath != "" {
   317  		apathDescr = fmt.Sprintf(" for attribute %q", apath)
   318  	}
   319  	return fmt.Errorf("no alternative%s matches: %v", apathDescr, firstErr)
   320  }
   321  
   322  // AttributeConstraints implements a set of constraints on the attributes of a slot or plug.
   323  type AttributeConstraints struct {
   324  	matcher attrMatcher
   325  }
   326  
   327  func (ac *AttributeConstraints) feature(flabel string) bool {
   328  	return ac.matcher.feature(flabel)
   329  }
   330  
   331  // compileAttributeConstraints checks and compiles a mapping or list
   332  // from the assertion format into AttributeConstraints.
   333  func compileAttributeConstraints(constraints interface{}) (*AttributeConstraints, error) {
   334  	matcher, err := compileAttrMatcher(compileContext{}, constraints)
   335  	if err != nil {
   336  		return nil, err
   337  	}
   338  	return &AttributeConstraints{matcher: matcher}, nil
   339  }
   340  
   341  type fixedAttrMatcher struct {
   342  	result error
   343  }
   344  
   345  func (matcher fixedAttrMatcher) feature(flabel string) bool {
   346  	return false
   347  }
   348  
   349  func (matcher fixedAttrMatcher) match(apath string, v interface{}, ctx AttrMatchContext) error {
   350  	return matcher.result
   351  }
   352  
   353  var (
   354  	AlwaysMatchAttributes = &AttributeConstraints{matcher: fixedAttrMatcher{nil}}
   355  	NeverMatchAttributes  = &AttributeConstraints{matcher: fixedAttrMatcher{errors.New("not allowed")}}
   356  )
   357  
   358  // Attrer reflects part of the Attrer interface (see interfaces.Attrer).
   359  type Attrer interface {
   360  	Lookup(path string) (interface{}, bool)
   361  }
   362  
   363  // Check checks whether attrs don't match the constraints.
   364  func (c *AttributeConstraints) Check(attrer Attrer, ctx AttrMatchContext) error {
   365  	return c.matcher.match("", attrer, ctx)
   366  }
   367  
   368  // SideArityConstraint specifies a constraint for the overall arity of
   369  // the set of connected slots for a given plug or the set of
   370  // connected plugs for a given slot.
   371  // It is used to express parsed slots-per-plug and plugs-per-slot
   372  // constraints.
   373  // See https://forum.snapcraft.io/t/plug-slot-declaration-rules-greedy-plugs/12438
   374  type SideArityConstraint struct {
   375  	// N can be:
   376  	// =>1
   377  	// 0 means default and is used only internally during rule
   378  	// compilation or on deny- rules where these constraints are
   379  	// not applicable
   380  	// -1 represents *, that means any (number of)
   381  	N int
   382  }
   383  
   384  // Any returns whether this represents the * (any number of) constraint.
   385  func (ac SideArityConstraint) Any() bool {
   386  	return ac.N == -1
   387  }
   388  
   389  func compileSideArityConstraint(context *subruleContext, which string, v interface{}) (SideArityConstraint, error) {
   390  	var a SideArityConstraint
   391  	if context.installation() || !context.allow() {
   392  		return a, fmt.Errorf("%s cannot specify a %s constraint, they apply only to allow-*connection", context, which)
   393  	}
   394  	x, ok := v.(string)
   395  	if !ok || len(x) == 0 {
   396  		return a, fmt.Errorf("%s in %s must be an integer >=1 or *", which, context)
   397  	}
   398  	if x == "*" {
   399  		return SideArityConstraint{N: -1}, nil
   400  	}
   401  	n, err := atoi(x, "%s in %s", which, context)
   402  	switch _, syntax := err.(intSyntaxError); {
   403  	case err == nil && n < 1:
   404  		fallthrough
   405  	case syntax:
   406  		return a, fmt.Errorf("%s in %s must be an integer >=1 or *", which, context)
   407  	case err != nil:
   408  		return a, err
   409  	}
   410  	return SideArityConstraint{N: n}, nil
   411  }
   412  
   413  type sideArityConstraintsHolder interface {
   414  	setSlotsPerPlug(SideArityConstraint)
   415  	setPlugsPerSlot(SideArityConstraint)
   416  
   417  	slotsPerPlug() SideArityConstraint
   418  	plugsPerSlot() SideArityConstraint
   419  }
   420  
   421  func normalizeSideArityConstraints(context *subruleContext, c sideArityConstraintsHolder) {
   422  	if !context.allow() {
   423  		return
   424  	}
   425  	any := SideArityConstraint{N: -1}
   426  	// normalized plugs-per-slot is always *
   427  	c.setPlugsPerSlot(any)
   428  	slotsPerPlug := c.slotsPerPlug()
   429  	if context.autoConnection() {
   430  		// auto-connection slots-per-plug can be any or 1
   431  		if !slotsPerPlug.Any() {
   432  			c.setSlotsPerPlug(SideArityConstraint{N: 1})
   433  		}
   434  	} else {
   435  		// connection slots-per-plug can be only any
   436  		c.setSlotsPerPlug(any)
   437  	}
   438  }
   439  
   440  var (
   441  	sideArityConstraints        = []string{"slots-per-plug", "plugs-per-slot"}
   442  	sideArityConstraintsSetters = map[string]func(sideArityConstraintsHolder, SideArityConstraint){
   443  		"slots-per-plug": sideArityConstraintsHolder.setSlotsPerPlug,
   444  		"plugs-per-slot": sideArityConstraintsHolder.setPlugsPerSlot,
   445  	}
   446  )
   447  
   448  // OnClassicConstraint specifies a constraint based whether the system is classic and optional specific distros' sets.
   449  type OnClassicConstraint struct {
   450  	Classic   bool
   451  	SystemIDs []string
   452  }
   453  
   454  // DeviceScopeConstraint specifies a constraints based on which brand
   455  // store, brand or model the device belongs to.
   456  type DeviceScopeConstraint struct {
   457  	Store []string
   458  	Brand []string
   459  	// Model is a list of precise "<brand>/<model>" constraints
   460  	Model []string
   461  }
   462  
   463  var (
   464  	validStoreID         = regexp.MustCompile("^[-A-Z0-9a-z_]+$")
   465  	validBrandSlashModel = regexp.MustCompile("^(" +
   466  		strings.Trim(validAccountID.String(), "^$") +
   467  		")/(" +
   468  		strings.Trim(validModel.String(), "^$") +
   469  		")$")
   470  	deviceScopeConstraints = map[string]*regexp.Regexp{
   471  		"on-store": validStoreID,
   472  		"on-brand": validAccountID,
   473  		// on-model constraints are of the form list of
   474  		// <brand>/<model> strings where <brand> are account
   475  		// IDs as they appear in the respective model assertion
   476  		"on-model": validBrandSlashModel,
   477  	}
   478  )
   479  
   480  func detectDeviceScopeConstraint(cMap map[string]interface{}) bool {
   481  	// for consistency and simplicity we support all of on-store,
   482  	// on-brand, and on-model to appear together. The interpretation
   483  	// layer will AND them as usual
   484  	for field := range deviceScopeConstraints {
   485  		if cMap[field] != nil {
   486  			return true
   487  		}
   488  	}
   489  	return false
   490  }
   491  
   492  func compileDeviceScopeConstraint(cMap map[string]interface{}, context string) (constr *DeviceScopeConstraint, err error) {
   493  	// initial map size of 2: we expect usual cases to have just one of the
   494  	// constraints or rarely 2
   495  	deviceConstr := make(map[string][]string, 2)
   496  	for field, validRegexp := range deviceScopeConstraints {
   497  		vals, err := checkStringListInMap(cMap, field, fmt.Sprintf("%s in %s", field, context), validRegexp)
   498  		if err != nil {
   499  			return nil, err
   500  		}
   501  		deviceConstr[field] = vals
   502  	}
   503  
   504  	if len(deviceConstr) == 0 {
   505  		return nil, fmt.Errorf("internal error: misdetected device scope constraints in %s", context)
   506  	}
   507  	return &DeviceScopeConstraint{
   508  		Store: deviceConstr["on-store"],
   509  		Brand: deviceConstr["on-brand"],
   510  		Model: deviceConstr["on-model"],
   511  	}, nil
   512  }
   513  
   514  type nameMatcher interface {
   515  	match(name string, special map[string]string) error
   516  }
   517  
   518  var (
   519  	// validates special name constraints like $INTERFACE
   520  	validSpecialNameConstraint = regexp.MustCompile("^\\$[A-Z][A-Z0-9_]*$")
   521  )
   522  
   523  func compileNameMatcher(whichName string, v interface{}) (nameMatcher, error) {
   524  	s, ok := v.(string)
   525  	if !ok {
   526  		return nil, fmt.Errorf("%s constraint entry must be a regexp or special $ value", whichName)
   527  	}
   528  	if strings.HasPrefix(s, "$") {
   529  		if !validSpecialNameConstraint.MatchString(s) {
   530  			return nil, fmt.Errorf("%s constraint entry special value %q is invalid", whichName, s)
   531  		}
   532  		return specialNameMatcher{special: s}, nil
   533  	}
   534  	if strings.IndexFunc(s, unicode.IsSpace) != -1 {
   535  		return nil, fmt.Errorf("%s constraint entry regexp contains unexpected spaces", whichName)
   536  	}
   537  	rx, err := regexp.Compile("^(" + s + ")$")
   538  	if err != nil {
   539  		return nil, fmt.Errorf("cannot compile %s constraint entry %q: %v", whichName, s, err)
   540  	}
   541  	return regexpNameMatcher{rx}, nil
   542  }
   543  
   544  type regexpNameMatcher struct {
   545  	*regexp.Regexp
   546  }
   547  
   548  func (matcher regexpNameMatcher) match(name string, special map[string]string) error {
   549  	if !matcher.Regexp.MatchString(name) {
   550  		return fmt.Errorf("%q does not match %v", name, matcher.Regexp)
   551  	}
   552  	return nil
   553  }
   554  
   555  type specialNameMatcher struct {
   556  	special string
   557  }
   558  
   559  func (matcher specialNameMatcher) match(name string, special map[string]string) error {
   560  	expected := special[matcher.special]
   561  	if expected == "" || expected != name {
   562  		return fmt.Errorf("%q does not match %v", name, matcher.special)
   563  	}
   564  	return nil
   565  }
   566  
   567  // NameConstraints implements a set of constraints on the names of slots or plugs.
   568  // See https://forum.snapcraft.io/t/plug-slot-rules-plug-names-slot-names-constraints/12439
   569  type NameConstraints struct {
   570  	matchers []nameMatcher
   571  }
   572  
   573  func compileNameConstraints(whichName string, constraints interface{}) (*NameConstraints, error) {
   574  	l, ok := constraints.([]interface{})
   575  	if !ok {
   576  		return nil, fmt.Errorf("%s constraints must be a list of regexps and special $ values", whichName)
   577  	}
   578  	matchers := make([]nameMatcher, 0, len(l))
   579  	for _, nm := range l {
   580  		matcher, err := compileNameMatcher(whichName, nm)
   581  		if err != nil {
   582  			return nil, err
   583  		}
   584  		matchers = append(matchers, matcher)
   585  	}
   586  	return &NameConstraints{matchers: matchers}, nil
   587  }
   588  
   589  // Check checks whether name doesn't match the constraints.
   590  func (nc *NameConstraints) Check(whichName, name string, special map[string]string) error {
   591  	for _, m := range nc.matchers {
   592  		if err := m.match(name, special); err == nil {
   593  			return nil
   594  		}
   595  	}
   596  	return fmt.Errorf("%s %q does not match constraints", whichName, name)
   597  }
   598  
   599  // rules
   600  
   601  var (
   602  	validSnapType  = regexp.MustCompile("^(?:core|kernel|gadget|app)$")
   603  	validDistro    = regexp.MustCompile("^[-0-9a-z._]+$")
   604  	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
   605  
   606  	validIDConstraints = map[string]*regexp.Regexp{
   607  		"slot-snap-type":    validSnapType,
   608  		"slot-snap-id":      naming.ValidSnapID,
   609  		"slot-publisher-id": validPublisher,
   610  		"plug-snap-type":    validSnapType,
   611  		"plug-snap-id":      naming.ValidSnapID,
   612  		"plug-publisher-id": validPublisher,
   613  	}
   614  )
   615  
   616  func checkMapOrShortcut(v interface{}) (m map[string]interface{}, invert bool, err error) {
   617  	switch x := v.(type) {
   618  	case map[string]interface{}:
   619  		return x, false, nil
   620  	case string:
   621  		switch x {
   622  		case "true":
   623  			return nil, false, nil
   624  		case "false":
   625  			return nil, true, nil
   626  		}
   627  	}
   628  	return nil, false, errors.New("unexpected type")
   629  }
   630  
   631  type constraintsHolder interface {
   632  	setNameConstraints(field string, cstrs *NameConstraints)
   633  	setAttributeConstraints(field string, cstrs *AttributeConstraints)
   634  	setIDConstraints(field string, cstrs []string)
   635  	setOnClassicConstraint(onClassic *OnClassicConstraint)
   636  	setDeviceScopeConstraint(deviceScope *DeviceScopeConstraint)
   637  }
   638  
   639  func baseCompileConstraints(context *subruleContext, cDef constraintsDef, target constraintsHolder, nameConstraints, attrConstraints, idConstraints []string) error {
   640  	cMap := cDef.cMap
   641  	if cMap == nil {
   642  		fixed := AlwaysMatchAttributes // "true"
   643  		if cDef.invert {               // "false"
   644  			fixed = NeverMatchAttributes
   645  		}
   646  		for _, field := range attrConstraints {
   647  			target.setAttributeConstraints(field, fixed)
   648  		}
   649  		return nil
   650  	}
   651  	defaultUsed := 0
   652  	for _, field := range nameConstraints {
   653  		v := cMap[field]
   654  		if v != nil {
   655  			nc, err := compileNameConstraints(field, v)
   656  			if err != nil {
   657  				return err
   658  			}
   659  			target.setNameConstraints(field, nc)
   660  		} else {
   661  			defaultUsed++
   662  		}
   663  	}
   664  	for _, field := range attrConstraints {
   665  		cstrs := AlwaysMatchAttributes
   666  		v := cMap[field]
   667  		if v != nil {
   668  			var err error
   669  			cstrs, err = compileAttributeConstraints(cMap[field])
   670  			if err != nil {
   671  				return fmt.Errorf("cannot compile %s in %s: %v", field, context, err)
   672  			}
   673  		} else {
   674  			defaultUsed++
   675  		}
   676  		target.setAttributeConstraints(field, cstrs)
   677  	}
   678  	for _, field := range idConstraints {
   679  		lst, err := checkStringListInMap(cMap, field, fmt.Sprintf("%s in %s", field, context), validIDConstraints[field])
   680  		if err != nil {
   681  			return err
   682  		}
   683  		if lst == nil {
   684  			defaultUsed++
   685  		}
   686  		target.setIDConstraints(field, lst)
   687  	}
   688  	for _, field := range sideArityConstraints {
   689  		v := cMap[field]
   690  		if v != nil {
   691  			c, err := compileSideArityConstraint(context, field, v)
   692  			if err != nil {
   693  				return err
   694  			}
   695  			h, ok := target.(sideArityConstraintsHolder)
   696  			if !ok {
   697  				return fmt.Errorf("internal error: side arity constraint compiled for unexpected subrule %T", target)
   698  			}
   699  			sideArityConstraintsSetters[field](h, c)
   700  		} else {
   701  			defaultUsed++
   702  		}
   703  	}
   704  	onClassic := cMap["on-classic"]
   705  	if onClassic == nil {
   706  		defaultUsed++
   707  	} else {
   708  		var c *OnClassicConstraint
   709  		switch x := onClassic.(type) {
   710  		case string:
   711  			switch x {
   712  			case "true":
   713  				c = &OnClassicConstraint{Classic: true}
   714  			case "false":
   715  				c = &OnClassicConstraint{Classic: false}
   716  			}
   717  		case []interface{}:
   718  			lst, err := checkStringListInMap(cMap, "on-classic", fmt.Sprintf("on-classic in %s", context), validDistro)
   719  			if err != nil {
   720  				return err
   721  			}
   722  			c = &OnClassicConstraint{Classic: true, SystemIDs: lst}
   723  		}
   724  		if c == nil {
   725  			return fmt.Errorf("on-classic in %s must be 'true', 'false' or a list of operating system IDs", context)
   726  		}
   727  		target.setOnClassicConstraint(c)
   728  	}
   729  	if !detectDeviceScopeConstraint(cMap) {
   730  		defaultUsed++
   731  	} else {
   732  		c, err := compileDeviceScopeConstraint(cMap, context.String())
   733  		if err != nil {
   734  			return err
   735  		}
   736  		target.setDeviceScopeConstraint(c)
   737  	}
   738  	// checks whether defaults have been used for everything, which is not
   739  	// well-formed
   740  	// +1+1 accounts for defaults for missing on-classic plus missing
   741  	// on-store/on-brand/on-model
   742  	if defaultUsed == len(nameConstraints)+len(attributeConstraints)+len(idConstraints)+len(sideArityConstraints)+1+1 {
   743  		return fmt.Errorf("%s must specify at least one of %s, %s, %s, %s, on-classic, on-store, on-brand, on-model", context, strings.Join(nameConstraints, ", "), strings.Join(attrConstraints, ", "), strings.Join(idConstraints, ", "), strings.Join(sideArityConstraints, ", "))
   744  	}
   745  	return nil
   746  }
   747  
   748  type rule interface {
   749  	setConstraints(field string, cstrs []constraintsHolder)
   750  }
   751  
   752  type constraintsDef struct {
   753  	cMap   map[string]interface{}
   754  	invert bool
   755  }
   756  
   757  // subruleContext carries queryable context information about one the
   758  // {allow,deny}-* subrules that end up compiled as
   759  // Plug|Slot*Constraints.  The information includes the parent rule,
   760  // the introductory subrule key ({allow,deny}-*) and which alternative
   761  // it corresponds to if any.
   762  // The information is useful for constraints compilation now that we
   763  // have constraints with different behavior depending on the kind of
   764  // subrule that hosts them (e.g. slots-per-plug, plugs-per-slot).
   765  type subruleContext struct {
   766  	// rule is the parent rule context description
   767  	rule string
   768  	// subrule is the subrule key
   769  	subrule string
   770  	// alt is which alternative this is (if > 0)
   771  	alt int
   772  }
   773  
   774  func (c *subruleContext) String() string {
   775  	subctxt := fmt.Sprintf("%s in %s", c.subrule, c.rule)
   776  	if c.alt != 0 {
   777  		subctxt = fmt.Sprintf("alternative %d of %s", c.alt, subctxt)
   778  	}
   779  	return subctxt
   780  }
   781  
   782  // allow returns whether the subrule is an allow-* subrule.
   783  func (c *subruleContext) allow() bool {
   784  	return strings.HasPrefix(c.subrule, "allow-")
   785  }
   786  
   787  // installation returns whether the subrule is an *-installation subrule.
   788  func (c *subruleContext) installation() bool {
   789  	return strings.HasSuffix(c.subrule, "-installation")
   790  }
   791  
   792  // autoConnection returns whether the subrule is an *-auto-connection subrule.
   793  func (c *subruleContext) autoConnection() bool {
   794  	return strings.HasSuffix(c.subrule, "-auto-connection")
   795  }
   796  
   797  type subruleCompiler func(context *subruleContext, def constraintsDef) (constraintsHolder, error)
   798  
   799  func baseCompileRule(context string, rule interface{}, target rule, subrules []string, compilers map[string]subruleCompiler, defaultOutcome, invertedOutcome map[string]interface{}) error {
   800  	rMap, invert, err := checkMapOrShortcut(rule)
   801  	if err != nil {
   802  		return fmt.Errorf("%s must be a map or one of the shortcuts 'true' or 'false'", context)
   803  	}
   804  	if rMap == nil {
   805  		rMap = defaultOutcome // "true"
   806  		if invert {
   807  			rMap = invertedOutcome // "false"
   808  		}
   809  	}
   810  	defaultUsed := 0
   811  	// compile and set subrules
   812  	for _, subrule := range subrules {
   813  		v := rMap[subrule]
   814  		var lst []interface{}
   815  		alternatives := false
   816  		switch x := v.(type) {
   817  		case nil:
   818  			v = defaultOutcome[subrule]
   819  			defaultUsed++
   820  		case []interface{}:
   821  			alternatives = true
   822  			lst = x
   823  		}
   824  		if lst == nil { // v is map or a string, checked below
   825  			lst = []interface{}{v}
   826  		}
   827  		compiler := compilers[subrule]
   828  		if compiler == nil {
   829  			panic(fmt.Sprintf("no compiler for %s in %s", subrule, context))
   830  		}
   831  		alts := make([]constraintsHolder, len(lst))
   832  		for i, alt := range lst {
   833  			subctxt := &subruleContext{
   834  				rule:    context,
   835  				subrule: subrule,
   836  			}
   837  			if alternatives {
   838  				subctxt.alt = i + 1
   839  			}
   840  			cMap, invert, err := checkMapOrShortcut(alt)
   841  			if err != nil || (cMap == nil && alternatives) {
   842  				efmt := "%s must be a map"
   843  				if !alternatives {
   844  					efmt = "%s must be a map or one of the shortcuts 'true' or 'false'"
   845  				}
   846  				return fmt.Errorf(efmt, subctxt)
   847  			}
   848  
   849  			cstrs, err := compiler(subctxt, constraintsDef{
   850  				cMap:   cMap,
   851  				invert: invert,
   852  			})
   853  			if err != nil {
   854  				return err
   855  			}
   856  			alts[i] = cstrs
   857  		}
   858  		target.setConstraints(subrule, alts)
   859  	}
   860  	if defaultUsed == len(subrules) {
   861  		return fmt.Errorf("%s must specify at least one of %s", context, strings.Join(subrules, ", "))
   862  	}
   863  	return nil
   864  }
   865  
   866  // PlugRule holds the rule of what is allowed, wrt installation and
   867  // connection, for a plug of a specific interface for a snap.
   868  type PlugRule struct {
   869  	Interface string
   870  
   871  	AllowInstallation []*PlugInstallationConstraints
   872  	DenyInstallation  []*PlugInstallationConstraints
   873  
   874  	AllowConnection []*PlugConnectionConstraints
   875  	DenyConnection  []*PlugConnectionConstraints
   876  
   877  	AllowAutoConnection []*PlugConnectionConstraints
   878  	DenyAutoConnection  []*PlugConnectionConstraints
   879  }
   880  
   881  func (r *PlugRule) feature(flabel string) bool {
   882  	for _, cs := range [][]*PlugInstallationConstraints{r.AllowInstallation, r.DenyInstallation} {
   883  		for _, c := range cs {
   884  			if c.feature(flabel) {
   885  				return true
   886  			}
   887  		}
   888  	}
   889  
   890  	for _, cs := range [][]*PlugConnectionConstraints{r.AllowConnection, r.DenyConnection, r.AllowAutoConnection, r.DenyAutoConnection} {
   891  		for _, c := range cs {
   892  			if c.feature(flabel) {
   893  				return true
   894  			}
   895  		}
   896  	}
   897  
   898  	return false
   899  }
   900  
   901  func castPlugInstallationConstraints(cstrs []constraintsHolder) (res []*PlugInstallationConstraints) {
   902  	res = make([]*PlugInstallationConstraints, len(cstrs))
   903  	for i, cstr := range cstrs {
   904  		res[i] = cstr.(*PlugInstallationConstraints)
   905  	}
   906  	return res
   907  }
   908  
   909  func castPlugConnectionConstraints(cstrs []constraintsHolder) (res []*PlugConnectionConstraints) {
   910  	res = make([]*PlugConnectionConstraints, len(cstrs))
   911  	for i, cstr := range cstrs {
   912  		res[i] = cstr.(*PlugConnectionConstraints)
   913  	}
   914  	return res
   915  }
   916  
   917  func (r *PlugRule) setConstraints(field string, cstrs []constraintsHolder) {
   918  	if len(cstrs) == 0 {
   919  		panic(fmt.Sprintf("cannot set PlugRule field %q to empty", field))
   920  	}
   921  	switch cstrs[0].(type) {
   922  	case *PlugInstallationConstraints:
   923  		switch field {
   924  		case "allow-installation":
   925  			r.AllowInstallation = castPlugInstallationConstraints(cstrs)
   926  			return
   927  		case "deny-installation":
   928  			r.DenyInstallation = castPlugInstallationConstraints(cstrs)
   929  			return
   930  		}
   931  	case *PlugConnectionConstraints:
   932  		switch field {
   933  		case "allow-connection":
   934  			r.AllowConnection = castPlugConnectionConstraints(cstrs)
   935  			return
   936  		case "deny-connection":
   937  			r.DenyConnection = castPlugConnectionConstraints(cstrs)
   938  			return
   939  		case "allow-auto-connection":
   940  			r.AllowAutoConnection = castPlugConnectionConstraints(cstrs)
   941  			return
   942  		case "deny-auto-connection":
   943  			r.DenyAutoConnection = castPlugConnectionConstraints(cstrs)
   944  			return
   945  		}
   946  	}
   947  	panic(fmt.Sprintf("cannot set PlugRule field %q with %T elements", field, cstrs[0]))
   948  }
   949  
   950  // PlugInstallationConstraints specifies a set of constraints on an interface plug relevant to the installation of snap.
   951  type PlugInstallationConstraints struct {
   952  	PlugSnapTypes []string
   953  
   954  	PlugNames *NameConstraints
   955  
   956  	PlugAttributes *AttributeConstraints
   957  
   958  	OnClassic *OnClassicConstraint
   959  
   960  	DeviceScope *DeviceScopeConstraint
   961  }
   962  
   963  func (c *PlugInstallationConstraints) feature(flabel string) bool {
   964  	if flabel == deviceScopeConstraintsFeature {
   965  		return c.DeviceScope != nil
   966  	}
   967  	if flabel == nameConstraintsFeature {
   968  		return c.PlugNames != nil
   969  	}
   970  	return c.PlugAttributes.feature(flabel)
   971  }
   972  
   973  func (c *PlugInstallationConstraints) setNameConstraints(field string, cstrs *NameConstraints) {
   974  	switch field {
   975  	case "plug-names":
   976  		c.PlugNames = cstrs
   977  	default:
   978  		panic("unknown PlugInstallationConstraints field " + field)
   979  	}
   980  }
   981  
   982  func (c *PlugInstallationConstraints) setAttributeConstraints(field string, cstrs *AttributeConstraints) {
   983  	switch field {
   984  	case "plug-attributes":
   985  		c.PlugAttributes = cstrs
   986  	default:
   987  		panic("unknown PlugInstallationConstraints field " + field)
   988  	}
   989  }
   990  
   991  func (c *PlugInstallationConstraints) setIDConstraints(field string, cstrs []string) {
   992  	switch field {
   993  	case "plug-snap-type":
   994  		c.PlugSnapTypes = cstrs
   995  	default:
   996  		panic("unknown PlugInstallationConstraints field " + field)
   997  	}
   998  }
   999  
  1000  func (c *PlugInstallationConstraints) setOnClassicConstraint(onClassic *OnClassicConstraint) {
  1001  	c.OnClassic = onClassic
  1002  }
  1003  
  1004  func (c *PlugInstallationConstraints) setDeviceScopeConstraint(deviceScope *DeviceScopeConstraint) {
  1005  	c.DeviceScope = deviceScope
  1006  }
  1007  
  1008  func compilePlugInstallationConstraints(context *subruleContext, cDef constraintsDef) (constraintsHolder, error) {
  1009  	plugInstCstrs := &PlugInstallationConstraints{}
  1010  	err := baseCompileConstraints(context, cDef, plugInstCstrs, []string{"plug-names"}, []string{"plug-attributes"}, []string{"plug-snap-type"})
  1011  	if err != nil {
  1012  		return nil, err
  1013  	}
  1014  	return plugInstCstrs, nil
  1015  }
  1016  
  1017  // PlugConnectionConstraints specfies a set of constraints on an
  1018  // interface plug for a snap relevant to its connection or
  1019  // auto-connection.
  1020  type PlugConnectionConstraints struct {
  1021  	SlotSnapTypes    []string
  1022  	SlotSnapIDs      []string
  1023  	SlotPublisherIDs []string
  1024  
  1025  	PlugNames *NameConstraints
  1026  	SlotNames *NameConstraints
  1027  
  1028  	PlugAttributes *AttributeConstraints
  1029  	SlotAttributes *AttributeConstraints
  1030  
  1031  	// SlotsPerPlug defaults to 1 for auto-connection, can be * (any)
  1032  	SlotsPerPlug SideArityConstraint
  1033  	// PlugsPerSlot is always * (any) (for now)
  1034  	PlugsPerSlot SideArityConstraint
  1035  
  1036  	OnClassic *OnClassicConstraint
  1037  
  1038  	DeviceScope *DeviceScopeConstraint
  1039  }
  1040  
  1041  func (c *PlugConnectionConstraints) feature(flabel string) bool {
  1042  	if flabel == deviceScopeConstraintsFeature {
  1043  		return c.DeviceScope != nil
  1044  	}
  1045  	if flabel == nameConstraintsFeature {
  1046  		return c.PlugNames != nil || c.SlotNames != nil
  1047  	}
  1048  	return c.PlugAttributes.feature(flabel) || c.SlotAttributes.feature(flabel)
  1049  }
  1050  
  1051  func (c *PlugConnectionConstraints) setNameConstraints(field string, cstrs *NameConstraints) {
  1052  	switch field {
  1053  	case "plug-names":
  1054  		c.PlugNames = cstrs
  1055  	case "slot-names":
  1056  		c.SlotNames = cstrs
  1057  	default:
  1058  		panic("unknown PlugConnectionConstraints field " + field)
  1059  	}
  1060  }
  1061  
  1062  func (c *PlugConnectionConstraints) setAttributeConstraints(field string, cstrs *AttributeConstraints) {
  1063  	switch field {
  1064  	case "plug-attributes":
  1065  		c.PlugAttributes = cstrs
  1066  	case "slot-attributes":
  1067  		c.SlotAttributes = cstrs
  1068  	default:
  1069  		panic("unknown PlugConnectionConstraints field " + field)
  1070  	}
  1071  }
  1072  
  1073  func (c *PlugConnectionConstraints) setIDConstraints(field string, cstrs []string) {
  1074  	switch field {
  1075  	case "slot-snap-type":
  1076  		c.SlotSnapTypes = cstrs
  1077  	case "slot-snap-id":
  1078  		c.SlotSnapIDs = cstrs
  1079  	case "slot-publisher-id":
  1080  		c.SlotPublisherIDs = cstrs
  1081  	default:
  1082  		panic("unknown PlugConnectionConstraints field " + field)
  1083  	}
  1084  }
  1085  
  1086  func (c *PlugConnectionConstraints) setSlotsPerPlug(a SideArityConstraint) {
  1087  	c.SlotsPerPlug = a
  1088  }
  1089  
  1090  func (c *PlugConnectionConstraints) setPlugsPerSlot(a SideArityConstraint) {
  1091  	c.PlugsPerSlot = a
  1092  }
  1093  
  1094  func (c *PlugConnectionConstraints) slotsPerPlug() SideArityConstraint {
  1095  	return c.SlotsPerPlug
  1096  }
  1097  
  1098  func (c *PlugConnectionConstraints) plugsPerSlot() SideArityConstraint {
  1099  	return c.PlugsPerSlot
  1100  }
  1101  
  1102  func (c *PlugConnectionConstraints) setOnClassicConstraint(onClassic *OnClassicConstraint) {
  1103  	c.OnClassic = onClassic
  1104  }
  1105  
  1106  func (c *PlugConnectionConstraints) setDeviceScopeConstraint(deviceScope *DeviceScopeConstraint) {
  1107  	c.DeviceScope = deviceScope
  1108  }
  1109  
  1110  var (
  1111  	nameConstraints      = []string{"plug-names", "slot-names"}
  1112  	attributeConstraints = []string{"plug-attributes", "slot-attributes"}
  1113  	plugIDConstraints    = []string{"slot-snap-type", "slot-publisher-id", "slot-snap-id"}
  1114  )
  1115  
  1116  func compilePlugConnectionConstraints(context *subruleContext, cDef constraintsDef) (constraintsHolder, error) {
  1117  	plugConnCstrs := &PlugConnectionConstraints{}
  1118  	err := baseCompileConstraints(context, cDef, plugConnCstrs, nameConstraints, attributeConstraints, plugIDConstraints)
  1119  	if err != nil {
  1120  		return nil, err
  1121  	}
  1122  	normalizeSideArityConstraints(context, plugConnCstrs)
  1123  	return plugConnCstrs, nil
  1124  }
  1125  
  1126  var (
  1127  	defaultOutcome = map[string]interface{}{
  1128  		"allow-installation":    "true",
  1129  		"allow-connection":      "true",
  1130  		"allow-auto-connection": "true",
  1131  		"deny-installation":     "false",
  1132  		"deny-connection":       "false",
  1133  		"deny-auto-connection":  "false",
  1134  	}
  1135  
  1136  	invertedOutcome = map[string]interface{}{
  1137  		"allow-installation":    "false",
  1138  		"allow-connection":      "false",
  1139  		"allow-auto-connection": "false",
  1140  		"deny-installation":     "true",
  1141  		"deny-connection":       "true",
  1142  		"deny-auto-connection":  "true",
  1143  	}
  1144  
  1145  	ruleSubrules = []string{"allow-installation", "deny-installation", "allow-connection", "deny-connection", "allow-auto-connection", "deny-auto-connection"}
  1146  )
  1147  
  1148  var plugRuleCompilers = map[string]subruleCompiler{
  1149  	"allow-installation":    compilePlugInstallationConstraints,
  1150  	"deny-installation":     compilePlugInstallationConstraints,
  1151  	"allow-connection":      compilePlugConnectionConstraints,
  1152  	"deny-connection":       compilePlugConnectionConstraints,
  1153  	"allow-auto-connection": compilePlugConnectionConstraints,
  1154  	"deny-auto-connection":  compilePlugConnectionConstraints,
  1155  }
  1156  
  1157  func compilePlugRule(interfaceName string, rule interface{}) (*PlugRule, error) {
  1158  	context := fmt.Sprintf("plug rule for interface %q", interfaceName)
  1159  	plugRule := &PlugRule{
  1160  		Interface: interfaceName,
  1161  	}
  1162  	err := baseCompileRule(context, rule, plugRule, ruleSubrules, plugRuleCompilers, defaultOutcome, invertedOutcome)
  1163  	if err != nil {
  1164  		return nil, err
  1165  	}
  1166  	return plugRule, nil
  1167  }
  1168  
  1169  // SlotRule holds the rule of what is allowed, wrt installation and
  1170  // connection, for a slot of a specific interface for a snap.
  1171  type SlotRule struct {
  1172  	Interface string
  1173  
  1174  	AllowInstallation []*SlotInstallationConstraints
  1175  	DenyInstallation  []*SlotInstallationConstraints
  1176  
  1177  	AllowConnection []*SlotConnectionConstraints
  1178  	DenyConnection  []*SlotConnectionConstraints
  1179  
  1180  	AllowAutoConnection []*SlotConnectionConstraints
  1181  	DenyAutoConnection  []*SlotConnectionConstraints
  1182  }
  1183  
  1184  func castSlotInstallationConstraints(cstrs []constraintsHolder) (res []*SlotInstallationConstraints) {
  1185  	res = make([]*SlotInstallationConstraints, len(cstrs))
  1186  	for i, cstr := range cstrs {
  1187  		res[i] = cstr.(*SlotInstallationConstraints)
  1188  	}
  1189  	return res
  1190  }
  1191  
  1192  func (r *SlotRule) feature(flabel string) bool {
  1193  	for _, cs := range [][]*SlotInstallationConstraints{r.AllowInstallation, r.DenyInstallation} {
  1194  		for _, c := range cs {
  1195  			if c.feature(flabel) {
  1196  				return true
  1197  			}
  1198  		}
  1199  	}
  1200  
  1201  	for _, cs := range [][]*SlotConnectionConstraints{r.AllowConnection, r.DenyConnection, r.AllowAutoConnection, r.DenyAutoConnection} {
  1202  		for _, c := range cs {
  1203  			if c.feature(flabel) {
  1204  				return true
  1205  			}
  1206  		}
  1207  	}
  1208  
  1209  	return false
  1210  }
  1211  
  1212  func castSlotConnectionConstraints(cstrs []constraintsHolder) (res []*SlotConnectionConstraints) {
  1213  	res = make([]*SlotConnectionConstraints, len(cstrs))
  1214  	for i, cstr := range cstrs {
  1215  		res[i] = cstr.(*SlotConnectionConstraints)
  1216  	}
  1217  	return res
  1218  }
  1219  
  1220  func (r *SlotRule) setConstraints(field string, cstrs []constraintsHolder) {
  1221  	if len(cstrs) == 0 {
  1222  		panic(fmt.Sprintf("cannot set SlotRule field %q to empty", field))
  1223  	}
  1224  	switch cstrs[0].(type) {
  1225  	case *SlotInstallationConstraints:
  1226  		switch field {
  1227  		case "allow-installation":
  1228  			r.AllowInstallation = castSlotInstallationConstraints(cstrs)
  1229  			return
  1230  		case "deny-installation":
  1231  			r.DenyInstallation = castSlotInstallationConstraints(cstrs)
  1232  			return
  1233  		}
  1234  	case *SlotConnectionConstraints:
  1235  		switch field {
  1236  		case "allow-connection":
  1237  			r.AllowConnection = castSlotConnectionConstraints(cstrs)
  1238  			return
  1239  		case "deny-connection":
  1240  			r.DenyConnection = castSlotConnectionConstraints(cstrs)
  1241  			return
  1242  		case "allow-auto-connection":
  1243  			r.AllowAutoConnection = castSlotConnectionConstraints(cstrs)
  1244  			return
  1245  		case "deny-auto-connection":
  1246  			r.DenyAutoConnection = castSlotConnectionConstraints(cstrs)
  1247  			return
  1248  		}
  1249  	}
  1250  	panic(fmt.Sprintf("cannot set SlotRule field %q with %T elements", field, cstrs[0]))
  1251  }
  1252  
  1253  // SlotInstallationConstraints specifies a set of constraints on an
  1254  // interface slot relevant to the installation of snap.
  1255  type SlotInstallationConstraints struct {
  1256  	SlotSnapTypes []string
  1257  
  1258  	SlotNames *NameConstraints
  1259  
  1260  	SlotAttributes *AttributeConstraints
  1261  
  1262  	OnClassic *OnClassicConstraint
  1263  
  1264  	DeviceScope *DeviceScopeConstraint
  1265  }
  1266  
  1267  func (c *SlotInstallationConstraints) feature(flabel string) bool {
  1268  	if flabel == deviceScopeConstraintsFeature {
  1269  		return c.DeviceScope != nil
  1270  	}
  1271  	if flabel == nameConstraintsFeature {
  1272  		return c.SlotNames != nil
  1273  	}
  1274  	return c.SlotAttributes.feature(flabel)
  1275  }
  1276  
  1277  func (c *SlotInstallationConstraints) setNameConstraints(field string, cstrs *NameConstraints) {
  1278  	switch field {
  1279  	case "slot-names":
  1280  		c.SlotNames = cstrs
  1281  	default:
  1282  		panic("unknown SlotInstallationConstraints field " + field)
  1283  	}
  1284  }
  1285  
  1286  func (c *SlotInstallationConstraints) setAttributeConstraints(field string, cstrs *AttributeConstraints) {
  1287  	switch field {
  1288  	case "slot-attributes":
  1289  		c.SlotAttributes = cstrs
  1290  	default:
  1291  		panic("unknown SlotInstallationConstraints field " + field)
  1292  	}
  1293  }
  1294  
  1295  func (c *SlotInstallationConstraints) setIDConstraints(field string, cstrs []string) {
  1296  	switch field {
  1297  	case "slot-snap-type":
  1298  		c.SlotSnapTypes = cstrs
  1299  	default:
  1300  		panic("unknown SlotInstallationConstraints field " + field)
  1301  	}
  1302  }
  1303  
  1304  func (c *SlotInstallationConstraints) setOnClassicConstraint(onClassic *OnClassicConstraint) {
  1305  	c.OnClassic = onClassic
  1306  }
  1307  
  1308  func (c *SlotInstallationConstraints) setDeviceScopeConstraint(deviceScope *DeviceScopeConstraint) {
  1309  	c.DeviceScope = deviceScope
  1310  }
  1311  
  1312  func compileSlotInstallationConstraints(context *subruleContext, cDef constraintsDef) (constraintsHolder, error) {
  1313  	slotInstCstrs := &SlotInstallationConstraints{}
  1314  	err := baseCompileConstraints(context, cDef, slotInstCstrs, []string{"slot-names"}, []string{"slot-attributes"}, []string{"slot-snap-type"})
  1315  	if err != nil {
  1316  		return nil, err
  1317  	}
  1318  	return slotInstCstrs, nil
  1319  }
  1320  
  1321  // SlotConnectionConstraints specfies a set of constraints on an
  1322  // interface slot for a snap relevant to its connection or
  1323  // auto-connection.
  1324  type SlotConnectionConstraints struct {
  1325  	PlugSnapTypes    []string
  1326  	PlugSnapIDs      []string
  1327  	PlugPublisherIDs []string
  1328  
  1329  	SlotNames *NameConstraints
  1330  	PlugNames *NameConstraints
  1331  
  1332  	SlotAttributes *AttributeConstraints
  1333  	PlugAttributes *AttributeConstraints
  1334  
  1335  	// SlotsPerPlug defaults to 1 for auto-connection, can be * (any)
  1336  	SlotsPerPlug SideArityConstraint
  1337  	// PlugsPerSlot is always * (any) (for now)
  1338  	PlugsPerSlot SideArityConstraint
  1339  
  1340  	OnClassic *OnClassicConstraint
  1341  
  1342  	DeviceScope *DeviceScopeConstraint
  1343  }
  1344  
  1345  func (c *SlotConnectionConstraints) feature(flabel string) bool {
  1346  	if flabel == deviceScopeConstraintsFeature {
  1347  		return c.DeviceScope != nil
  1348  	}
  1349  	if flabel == nameConstraintsFeature {
  1350  		return c.PlugNames != nil || c.SlotNames != nil
  1351  	}
  1352  	return c.PlugAttributes.feature(flabel) || c.SlotAttributes.feature(flabel)
  1353  }
  1354  
  1355  func (c *SlotConnectionConstraints) setNameConstraints(field string, cstrs *NameConstraints) {
  1356  	switch field {
  1357  	case "plug-names":
  1358  		c.PlugNames = cstrs
  1359  	case "slot-names":
  1360  		c.SlotNames = cstrs
  1361  	default:
  1362  		panic("unknown SlotConnectionConstraints field " + field)
  1363  	}
  1364  }
  1365  
  1366  func (c *SlotConnectionConstraints) setAttributeConstraints(field string, cstrs *AttributeConstraints) {
  1367  	switch field {
  1368  	case "plug-attributes":
  1369  		c.PlugAttributes = cstrs
  1370  	case "slot-attributes":
  1371  		c.SlotAttributes = cstrs
  1372  	default:
  1373  		panic("unknown SlotConnectionConstraints field " + field)
  1374  	}
  1375  }
  1376  
  1377  func (c *SlotConnectionConstraints) setIDConstraints(field string, cstrs []string) {
  1378  	switch field {
  1379  	case "plug-snap-type":
  1380  		c.PlugSnapTypes = cstrs
  1381  	case "plug-snap-id":
  1382  		c.PlugSnapIDs = cstrs
  1383  	case "plug-publisher-id":
  1384  		c.PlugPublisherIDs = cstrs
  1385  	default:
  1386  		panic("unknown SlotConnectionConstraints field " + field)
  1387  	}
  1388  }
  1389  
  1390  var (
  1391  	slotIDConstraints = []string{"plug-snap-type", "plug-publisher-id", "plug-snap-id"}
  1392  )
  1393  
  1394  func (c *SlotConnectionConstraints) setSlotsPerPlug(a SideArityConstraint) {
  1395  	c.SlotsPerPlug = a
  1396  }
  1397  
  1398  func (c *SlotConnectionConstraints) setPlugsPerSlot(a SideArityConstraint) {
  1399  	c.PlugsPerSlot = a
  1400  }
  1401  
  1402  func (c *SlotConnectionConstraints) slotsPerPlug() SideArityConstraint {
  1403  	return c.SlotsPerPlug
  1404  }
  1405  
  1406  func (c *SlotConnectionConstraints) plugsPerSlot() SideArityConstraint {
  1407  	return c.PlugsPerSlot
  1408  }
  1409  
  1410  func (c *SlotConnectionConstraints) setOnClassicConstraint(onClassic *OnClassicConstraint) {
  1411  	c.OnClassic = onClassic
  1412  }
  1413  
  1414  func (c *SlotConnectionConstraints) setDeviceScopeConstraint(deviceScope *DeviceScopeConstraint) {
  1415  	c.DeviceScope = deviceScope
  1416  }
  1417  
  1418  func compileSlotConnectionConstraints(context *subruleContext, cDef constraintsDef) (constraintsHolder, error) {
  1419  	slotConnCstrs := &SlotConnectionConstraints{}
  1420  	err := baseCompileConstraints(context, cDef, slotConnCstrs, nameConstraints, attributeConstraints, slotIDConstraints)
  1421  	if err != nil {
  1422  		return nil, err
  1423  	}
  1424  	normalizeSideArityConstraints(context, slotConnCstrs)
  1425  	return slotConnCstrs, nil
  1426  }
  1427  
  1428  var slotRuleCompilers = map[string]subruleCompiler{
  1429  	"allow-installation":    compileSlotInstallationConstraints,
  1430  	"deny-installation":     compileSlotInstallationConstraints,
  1431  	"allow-connection":      compileSlotConnectionConstraints,
  1432  	"deny-connection":       compileSlotConnectionConstraints,
  1433  	"allow-auto-connection": compileSlotConnectionConstraints,
  1434  	"deny-auto-connection":  compileSlotConnectionConstraints,
  1435  }
  1436  
  1437  func compileSlotRule(interfaceName string, rule interface{}) (*SlotRule, error) {
  1438  	context := fmt.Sprintf("slot rule for interface %q", interfaceName)
  1439  	slotRule := &SlotRule{
  1440  		Interface: interfaceName,
  1441  	}
  1442  	err := baseCompileRule(context, rule, slotRule, ruleSubrules, slotRuleCompilers, defaultOutcome, invertedOutcome)
  1443  	if err != nil {
  1444  		return nil, err
  1445  	}
  1446  	return slotRule, nil
  1447  }