github.com/kubiko/snapd@v0.0.0-20201013125620-d4f3094d9ddf/asserts/ifacedecls_test.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_test
    21  
    22  import (
    23  	"fmt"
    24  	"regexp"
    25  	"strings"
    26  
    27  	. "gopkg.in/check.v1"
    28  	"gopkg.in/yaml.v2"
    29  
    30  	"github.com/snapcore/snapd/asserts"
    31  	"github.com/snapcore/snapd/snap"
    32  
    33  	"github.com/snapcore/snapd/testutil"
    34  )
    35  
    36  var (
    37  	_ = Suite(&attrConstraintsSuite{})
    38  	_ = Suite(&nameConstraintsSuite{})
    39  	_ = Suite(&plugSlotRulesSuite{})
    40  )
    41  
    42  type attrConstraintsSuite struct {
    43  	testutil.BaseTest
    44  }
    45  
    46  type attrerObject map[string]interface{}
    47  
    48  func (o attrerObject) Lookup(path string) (interface{}, bool) {
    49  	v, ok := o[path]
    50  	return v, ok
    51  }
    52  
    53  func attrs(yml string) *attrerObject {
    54  	var attrs map[string]interface{}
    55  	err := yaml.Unmarshal([]byte(yml), &attrs)
    56  	if err != nil {
    57  		panic(err)
    58  	}
    59  	snapYaml, err := yaml.Marshal(map[string]interface{}{
    60  		"name": "sample",
    61  		"plugs": map[string]interface{}{
    62  			"plug": attrs,
    63  		},
    64  	})
    65  	if err != nil {
    66  		panic(err)
    67  	}
    68  
    69  	// NOTE: it's important to go through snap yaml here even though we're really interested in Attrs only,
    70  	// as InfoFromSnapYaml normalizes yaml values.
    71  	info, err := snap.InfoFromSnapYaml(snapYaml)
    72  	if err != nil {
    73  		panic(err)
    74  	}
    75  
    76  	var ao attrerObject
    77  	ao = info.Plugs["plug"].Attrs
    78  	return &ao
    79  }
    80  
    81  func (s *attrConstraintsSuite) SetUpTest(c *C) {
    82  	s.BaseTest.SetUpTest(c)
    83  	s.BaseTest.AddCleanup(snap.MockSanitizePlugsSlots(func(snapInfo *snap.Info) {}))
    84  }
    85  
    86  func (s *attrConstraintsSuite) TearDownTest(c *C) {
    87  	s.BaseTest.TearDownTest(c)
    88  }
    89  
    90  func (s *attrConstraintsSuite) TestSimple(c *C) {
    91  	m, err := asserts.ParseHeaders([]byte(`attrs:
    92    foo: FOO
    93    bar: BAR`))
    94  	c.Assert(err, IsNil)
    95  
    96  	cstrs, err := asserts.CompileAttributeConstraints(m["attrs"].(map[string]interface{}))
    97  	c.Assert(err, IsNil)
    98  
    99  	plug := attrerObject(map[string]interface{}{
   100  		"foo": "FOO",
   101  		"bar": "BAR",
   102  		"baz": "BAZ",
   103  	})
   104  	err = cstrs.Check(plug, nil)
   105  	c.Check(err, IsNil)
   106  
   107  	plug = attrerObject(map[string]interface{}{
   108  		"foo": "FOO",
   109  		"bar": "BAZ",
   110  		"baz": "BAZ",
   111  	})
   112  	err = cstrs.Check(plug, nil)
   113  	c.Check(err, ErrorMatches, `attribute "bar" value "BAZ" does not match \^\(BAR\)\$`)
   114  
   115  	plug = attrerObject(map[string]interface{}{
   116  		"foo": "FOO",
   117  		"baz": "BAZ",
   118  	})
   119  	err = cstrs.Check(plug, nil)
   120  	c.Check(err, ErrorMatches, `attribute "bar" has constraints but is unset`)
   121  }
   122  
   123  func (s *attrConstraintsSuite) TestSimpleAnchorsVsRegexpAlt(c *C) {
   124  	m, err := asserts.ParseHeaders([]byte(`attrs:
   125    bar: BAR|BAZ`))
   126  	c.Assert(err, IsNil)
   127  
   128  	cstrs, err := asserts.CompileAttributeConstraints(m["attrs"].(map[string]interface{}))
   129  	c.Assert(err, IsNil)
   130  
   131  	plug := attrerObject(map[string]interface{}{
   132  		"bar": "BAR",
   133  	})
   134  	err = cstrs.Check(plug, nil)
   135  	c.Check(err, IsNil)
   136  
   137  	plug = attrerObject(map[string]interface{}{
   138  		"bar": "BARR",
   139  	})
   140  	err = cstrs.Check(plug, nil)
   141  	c.Check(err, ErrorMatches, `attribute "bar" value "BARR" does not match \^\(BAR|BAZ\)\$`)
   142  
   143  	plug = attrerObject(map[string]interface{}{
   144  		"bar": "BBAZ",
   145  	})
   146  	err = cstrs.Check(plug, nil)
   147  	c.Check(err, ErrorMatches, `attribute "bar" value "BAZZ" does not match \^\(BAR|BAZ\)\$`)
   148  
   149  	plug = attrerObject(map[string]interface{}{
   150  		"bar": "BABAZ",
   151  	})
   152  	err = cstrs.Check(plug, nil)
   153  	c.Check(err, ErrorMatches, `attribute "bar" value "BABAZ" does not match \^\(BAR|BAZ\)\$`)
   154  
   155  	plug = attrerObject(map[string]interface{}{
   156  		"bar": "BARAZ",
   157  	})
   158  	err = cstrs.Check(plug, nil)
   159  	c.Check(err, ErrorMatches, `attribute "bar" value "BARAZ" does not match \^\(BAR|BAZ\)\$`)
   160  }
   161  
   162  func (s *attrConstraintsSuite) TestNested(c *C) {
   163  	m, err := asserts.ParseHeaders([]byte(`attrs:
   164    foo: FOO
   165    bar:
   166      bar1: BAR1
   167      bar2: BAR2`))
   168  	c.Assert(err, IsNil)
   169  
   170  	cstrs, err := asserts.CompileAttributeConstraints(m["attrs"].(map[string]interface{}))
   171  	c.Assert(err, IsNil)
   172  
   173  	err = cstrs.Check(attrs(`
   174  foo: FOO
   175  bar:
   176    bar1: BAR1
   177    bar2: BAR2
   178    bar3: BAR3
   179  baz: BAZ
   180  `), nil)
   181  	c.Check(err, IsNil)
   182  
   183  	err = cstrs.Check(attrs(`
   184  foo: FOO
   185  bar: BAZ
   186  baz: BAZ
   187  `), nil)
   188  	c.Check(err, ErrorMatches, `attribute "bar" must be a map`)
   189  
   190  	err = cstrs.Check(attrs(`
   191  foo: FOO
   192  bar:
   193    bar1: BAR1
   194    bar2: BAR22
   195    bar3: BAR3
   196  baz: BAZ
   197  `), nil)
   198  	c.Check(err, ErrorMatches, `attribute "bar\.bar2" value "BAR22" does not match \^\(BAR2\)\$`)
   199  
   200  	err = cstrs.Check(attrs(`
   201  foo: FOO
   202  bar:
   203    bar1: BAR1
   204    bar2:
   205      bar22: true
   206    bar3: BAR3
   207  baz: BAZ
   208  `), nil)
   209  	c.Check(err, ErrorMatches, `attribute "bar\.bar2" must be a scalar or list`)
   210  }
   211  
   212  func (s *attrConstraintsSuite) TestAlternative(c *C) {
   213  	m, err := asserts.ParseHeaders([]byte(`attrs:
   214    -
   215      foo: FOO
   216      bar: BAR
   217    -
   218      foo: FOO
   219      bar: BAZ`))
   220  	c.Assert(err, IsNil)
   221  
   222  	cstrs, err := asserts.CompileAttributeConstraints(m["attrs"].([]interface{}))
   223  	c.Assert(err, IsNil)
   224  
   225  	plug := attrerObject(map[string]interface{}{
   226  		"foo": "FOO",
   227  		"bar": "BAR",
   228  		"baz": "BAZ",
   229  	})
   230  	err = cstrs.Check(plug, nil)
   231  	c.Check(err, IsNil)
   232  
   233  	plug = attrerObject(map[string]interface{}{
   234  		"foo": "FOO",
   235  		"bar": "BAZ",
   236  		"baz": "BAZ",
   237  	})
   238  	err = cstrs.Check(plug, nil)
   239  	c.Check(err, IsNil)
   240  
   241  	plug = attrerObject(map[string]interface{}{
   242  		"foo": "FOO",
   243  		"bar": "BARR",
   244  		"baz": "BAR",
   245  	})
   246  	err = cstrs.Check(plug, nil)
   247  	c.Check(err, ErrorMatches, `no alternative matches: attribute "bar" value "BARR" does not match \^\(BAR\)\$`)
   248  }
   249  
   250  func (s *attrConstraintsSuite) TestNestedAlternative(c *C) {
   251  	m, err := asserts.ParseHeaders([]byte(`attrs:
   252    foo: FOO
   253    bar:
   254      bar1: BAR1
   255      bar2:
   256        - BAR2
   257        - BAR22`))
   258  	c.Assert(err, IsNil)
   259  
   260  	cstrs, err := asserts.CompileAttributeConstraints(m["attrs"].(map[string]interface{}))
   261  	c.Assert(err, IsNil)
   262  
   263  	err = cstrs.Check(attrs(`
   264  foo: FOO
   265  bar:
   266    bar1: BAR1
   267    bar2: BAR2
   268  `), nil)
   269  	c.Check(err, IsNil)
   270  
   271  	err = cstrs.Check(attrs(`
   272  foo: FOO
   273  bar:
   274    bar1: BAR1
   275    bar2: BAR22
   276  `), nil)
   277  	c.Check(err, IsNil)
   278  
   279  	err = cstrs.Check(attrs(`
   280  foo: FOO
   281  bar:
   282    bar1: BAR1
   283    bar2: BAR3
   284  `), nil)
   285  	c.Check(err, ErrorMatches, `no alternative for attribute "bar\.bar2" matches: attribute "bar\.bar2" value "BAR3" does not match \^\(BAR2\)\$`)
   286  }
   287  
   288  func (s *attrConstraintsSuite) TestOtherScalars(c *C) {
   289  	m, err := asserts.ParseHeaders([]byte(`attrs:
   290    foo: 1
   291    bar: true`))
   292  	c.Assert(err, IsNil)
   293  
   294  	cstrs, err := asserts.CompileAttributeConstraints(m["attrs"].(map[string]interface{}))
   295  	c.Assert(err, IsNil)
   296  
   297  	err = cstrs.Check(attrs(`
   298  foo: 1
   299  bar: true
   300  `), nil)
   301  	c.Check(err, IsNil)
   302  
   303  	plug := attrerObject(map[string]interface{}{
   304  		"foo": int64(1),
   305  		"bar": true,
   306  	})
   307  	err = cstrs.Check(plug, nil)
   308  	c.Check(err, IsNil)
   309  }
   310  
   311  func (s *attrConstraintsSuite) TestCompileErrors(c *C) {
   312  	_, err := asserts.CompileAttributeConstraints(map[string]interface{}{
   313  		"foo": "[",
   314  	})
   315  	c.Check(err, ErrorMatches, `cannot compile "foo" constraint "\[": error parsing regexp:.*`)
   316  
   317  	_, err = asserts.CompileAttributeConstraints(map[string]interface{}{
   318  		"foo": []interface{}{"foo", "["},
   319  	})
   320  	c.Check(err, ErrorMatches, `cannot compile "foo/alt#2/" constraint "\[": error parsing regexp:.*`)
   321  
   322  	_, err = asserts.CompileAttributeConstraints(map[string]interface{}{
   323  		"foo": []interface{}{"foo", []interface{}{"bar", "baz"}},
   324  	})
   325  	c.Check(err, ErrorMatches, `cannot nest alternative constraints directly at "foo/alt#2/"`)
   326  
   327  	_, err = asserts.CompileAttributeConstraints("FOO")
   328  	c.Check(err, ErrorMatches, `first level of non alternative constraints must be a set of key-value contraints`)
   329  
   330  	_, err = asserts.CompileAttributeConstraints([]interface{}{"FOO"})
   331  	c.Check(err, ErrorMatches, `first level of non alternative constraints must be a set of key-value contraints`)
   332  
   333  	wrongDollarConstraints := []string{
   334  		"$",
   335  		"$FOO(a)",
   336  		"$SLOT",
   337  		"$SLOT()",
   338  	}
   339  
   340  	for _, wrong := range wrongDollarConstraints {
   341  		_, err := asserts.CompileAttributeConstraints(map[string]interface{}{
   342  			"foo": wrong,
   343  		})
   344  		c.Check(err, ErrorMatches, fmt.Sprintf(`cannot compile "foo" constraint "%s": not a valid \$SLOT\(\)/\$PLUG\(\) constraint`, regexp.QuoteMeta(wrong)))
   345  
   346  	}
   347  }
   348  
   349  func (s *attrConstraintsSuite) TestMatchingListsSimple(c *C) {
   350  	m, err := asserts.ParseHeaders([]byte(`attrs:
   351    foo: /foo/.*`))
   352  	c.Assert(err, IsNil)
   353  
   354  	cstrs, err := asserts.CompileAttributeConstraints(m["attrs"].(map[string]interface{}))
   355  	c.Assert(err, IsNil)
   356  
   357  	err = cstrs.Check(attrs(`
   358  foo: ["/foo/x", "/foo/y"]
   359  `), nil)
   360  	c.Check(err, IsNil)
   361  
   362  	err = cstrs.Check(attrs(`
   363  foo: ["/foo/x", "/foo"]
   364  `), nil)
   365  	c.Check(err, ErrorMatches, `attribute "foo\.1" value "/foo" does not match \^\(/foo/\.\*\)\$`)
   366  }
   367  
   368  func (s *attrConstraintsSuite) TestMissingCheck(c *C) {
   369  	m, err := asserts.ParseHeaders([]byte(`attrs:
   370    foo: $MISSING`))
   371  	c.Assert(err, IsNil)
   372  
   373  	cstrs, err := asserts.CompileAttributeConstraints(m["attrs"].(map[string]interface{}))
   374  	c.Assert(err, IsNil)
   375  	c.Check(asserts.RuleFeature(cstrs, "dollar-attr-constraints"), Equals, true)
   376  
   377  	err = cstrs.Check(attrs(`
   378  bar: baz
   379  `), nil)
   380  	c.Check(err, IsNil)
   381  
   382  	err = cstrs.Check(attrs(`
   383  foo: ["x"]
   384  `), nil)
   385  	c.Check(err, ErrorMatches, `attribute "foo" is constrained to be missing but is set`)
   386  }
   387  
   388  type testEvalAttr struct {
   389  	comp func(side string, arg string) (interface{}, error)
   390  }
   391  
   392  func (ca testEvalAttr) SlotAttr(arg string) (interface{}, error) {
   393  	return ca.comp("slot", arg)
   394  }
   395  
   396  func (ca testEvalAttr) PlugAttr(arg string) (interface{}, error) {
   397  	return ca.comp("plug", arg)
   398  }
   399  
   400  func (s *attrConstraintsSuite) TestEvalCheck(c *C) {
   401  	m, err := asserts.ParseHeaders([]byte(`attrs:
   402    foo: $SLOT(foo)
   403    bar: $PLUG(bar.baz)`))
   404  	c.Assert(err, IsNil)
   405  
   406  	cstrs, err := asserts.CompileAttributeConstraints(m["attrs"].(map[string]interface{}))
   407  	c.Assert(err, IsNil)
   408  	c.Check(asserts.RuleFeature(cstrs, "dollar-attr-constraints"), Equals, true)
   409  
   410  	err = cstrs.Check(attrs(`
   411  foo: foo
   412  bar: bar
   413  `), nil)
   414  	c.Check(err, ErrorMatches, `attribute "(foo|bar)" cannot be matched without context`)
   415  
   416  	calls := make(map[[2]string]bool)
   417  	comp1 := func(op string, arg string) (interface{}, error) {
   418  		calls[[2]string{op, arg}] = true
   419  		return arg, nil
   420  	}
   421  
   422  	err = cstrs.Check(attrs(`
   423  foo: foo
   424  bar: bar.baz
   425  `), testEvalAttr{comp1})
   426  	c.Check(err, IsNil)
   427  
   428  	c.Check(calls, DeepEquals, map[[2]string]bool{
   429  		{"slot", "foo"}:     true,
   430  		{"plug", "bar.baz"}: true,
   431  	})
   432  
   433  	comp2 := func(op string, arg string) (interface{}, error) {
   434  		if op == "plug" {
   435  			return nil, fmt.Errorf("boom")
   436  		}
   437  		return arg, nil
   438  	}
   439  
   440  	err = cstrs.Check(attrs(`
   441  foo: foo
   442  bar: bar.baz
   443  `), testEvalAttr{comp2})
   444  	c.Check(err, ErrorMatches, `attribute "bar" constraint \$PLUG\(bar\.baz\) cannot be evaluated: boom`)
   445  
   446  	comp3 := func(op string, arg string) (interface{}, error) {
   447  		if op == "slot" {
   448  			return "other-value", nil
   449  		}
   450  		return arg, nil
   451  	}
   452  
   453  	err = cstrs.Check(attrs(`
   454  foo: foo
   455  bar: bar.baz
   456  `), testEvalAttr{comp3})
   457  	c.Check(err, ErrorMatches, `attribute "foo" does not match \$SLOT\(foo\): foo != other-value`)
   458  }
   459  
   460  func (s *attrConstraintsSuite) TestMatchingListsMap(c *C) {
   461  	m, err := asserts.ParseHeaders([]byte(`attrs:
   462    foo:
   463      p: /foo/.*`))
   464  	c.Assert(err, IsNil)
   465  
   466  	cstrs, err := asserts.CompileAttributeConstraints(m["attrs"].(map[string]interface{}))
   467  	c.Assert(err, IsNil)
   468  
   469  	err = cstrs.Check(attrs(`
   470  foo: [{p: "/foo/x"}, {p: "/foo/y"}]
   471  `), nil)
   472  	c.Check(err, IsNil)
   473  
   474  	err = cstrs.Check(attrs(`
   475  foo: [{p: "zzz"}, {p: "/foo/y"}]
   476  `), nil)
   477  	c.Check(err, ErrorMatches, `attribute "foo\.0\.p" value "zzz" does not match \^\(/foo/\.\*\)\$`)
   478  }
   479  
   480  func (s *attrConstraintsSuite) TestAlwaysMatchAttributeConstraints(c *C) {
   481  	c.Check(asserts.AlwaysMatchAttributes.Check(nil, nil), IsNil)
   482  }
   483  
   484  func (s *attrConstraintsSuite) TestNeverMatchAttributeConstraints(c *C) {
   485  	c.Check(asserts.NeverMatchAttributes.Check(nil, nil), NotNil)
   486  }
   487  
   488  type nameConstraintsSuite struct{}
   489  
   490  func (s *nameConstraintsSuite) TestCompileErrors(c *C) {
   491  	_, err := asserts.CompileNameConstraints("slot-names", "true")
   492  	c.Check(err, ErrorMatches, `slot-names constraints must be a list of regexps and special \$ values`)
   493  
   494  	_, err = asserts.CompileNameConstraints("slot-names", []interface{}{map[string]interface{}{"foo": "bar"}})
   495  	c.Check(err, ErrorMatches, `slot-names constraint entry must be a regexp or special \$ value`)
   496  
   497  	_, err = asserts.CompileNameConstraints("plug-names", []interface{}{"["})
   498  	c.Check(err, ErrorMatches, `cannot compile plug-names constraint entry "\[":.*`)
   499  
   500  	_, err = asserts.CompileNameConstraints("plug-names", []interface{}{"$"})
   501  	c.Check(err, ErrorMatches, `plug-names constraint entry special value "\$" is invalid`)
   502  
   503  	_, err = asserts.CompileNameConstraints("slot-names", []interface{}{"$12"})
   504  	c.Check(err, ErrorMatches, `slot-names constraint entry special value "\$12" is invalid`)
   505  
   506  	_, err = asserts.CompileNameConstraints("plug-names", []interface{}{"a b"})
   507  	c.Check(err, ErrorMatches, `plug-names constraint entry regexp contains unexpected spaces`)
   508  }
   509  
   510  func (s *nameConstraintsSuite) TestCheck(c *C) {
   511  	nc, err := asserts.CompileNameConstraints("slot-names", []interface{}{"foo[0-9]", "bar"})
   512  	c.Assert(err, IsNil)
   513  
   514  	for _, matching := range []string{"foo0", "foo1", "bar"} {
   515  		c.Check(nc.Check("slot name", matching, nil), IsNil)
   516  	}
   517  
   518  	for _, notMatching := range []string{"baz", "fooo", "foo12"} {
   519  		c.Check(nc.Check("slot name", notMatching, nil), ErrorMatches, fmt.Sprintf(`slot name %q does not match constraints`, notMatching))
   520  	}
   521  
   522  }
   523  
   524  func (s *nameConstraintsSuite) TestCheckSpecial(c *C) {
   525  	nc, err := asserts.CompileNameConstraints("slot-names", []interface{}{"$INTERFACE"})
   526  	c.Assert(err, IsNil)
   527  
   528  	c.Check(nc.Check("slot name", "foo", nil), ErrorMatches, `slot name "foo" does not match constraints`)
   529  	c.Check(nc.Check("slot name", "foo", map[string]string{"$INTERFACE": "foo"}), IsNil)
   530  	c.Check(nc.Check("slot name", "bar", map[string]string{"$INTERFACE": "foo"}), ErrorMatches, `slot name "bar" does not match constraints`)
   531  }
   532  
   533  type plugSlotRulesSuite struct{}
   534  
   535  func checkAttrs(c *C, attrs *asserts.AttributeConstraints, witness, expected string) {
   536  	plug := attrerObject(map[string]interface{}{
   537  		witness: "XYZ",
   538  	})
   539  	c.Check(attrs.Check(plug, nil), ErrorMatches, fmt.Sprintf(`attribute "%s".*does not match.*`, witness))
   540  	plug = attrerObject(map[string]interface{}{
   541  		witness: expected,
   542  	})
   543  	c.Check(attrs.Check(plug, nil), IsNil)
   544  }
   545  
   546  var (
   547  	sideArityAny = asserts.SideArityConstraint{N: -1}
   548  	sideArityOne = asserts.SideArityConstraint{N: 1}
   549  )
   550  
   551  func checkBoolPlugConnConstraints(c *C, subrule string, cstrs []*asserts.PlugConnectionConstraints, always bool) {
   552  	expected := asserts.NeverMatchAttributes
   553  	if always {
   554  		expected = asserts.AlwaysMatchAttributes
   555  	}
   556  	c.Assert(cstrs, HasLen, 1)
   557  	cstrs1 := cstrs[0]
   558  	c.Check(cstrs1.PlugAttributes, Equals, expected)
   559  	c.Check(cstrs1.SlotAttributes, Equals, expected)
   560  	if strings.HasPrefix(subrule, "deny-") {
   561  		undef := asserts.SideArityConstraint{}
   562  		c.Check(cstrs1.SlotsPerPlug, Equals, undef)
   563  		c.Check(cstrs1.PlugsPerSlot, Equals, undef)
   564  	} else {
   565  		c.Check(cstrs1.PlugsPerSlot, Equals, sideArityAny)
   566  		if strings.HasSuffix(subrule, "-auto-connection") {
   567  			c.Check(cstrs1.SlotsPerPlug, Equals, sideArityOne)
   568  		} else {
   569  			c.Check(cstrs1.SlotsPerPlug, Equals, sideArityAny)
   570  		}
   571  	}
   572  	c.Check(cstrs1.SlotSnapIDs, HasLen, 0)
   573  	c.Check(cstrs1.SlotPublisherIDs, HasLen, 0)
   574  	c.Check(cstrs1.SlotSnapTypes, HasLen, 0)
   575  }
   576  
   577  func checkBoolSlotConnConstraints(c *C, subrule string, cstrs []*asserts.SlotConnectionConstraints, always bool) {
   578  	expected := asserts.NeverMatchAttributes
   579  	if always {
   580  		expected = asserts.AlwaysMatchAttributes
   581  	}
   582  	c.Assert(cstrs, HasLen, 1)
   583  	cstrs1 := cstrs[0]
   584  	c.Check(cstrs1.PlugAttributes, Equals, expected)
   585  	c.Check(cstrs1.SlotAttributes, Equals, expected)
   586  	if strings.HasPrefix(subrule, "deny-") {
   587  		undef := asserts.SideArityConstraint{}
   588  		c.Check(cstrs1.SlotsPerPlug, Equals, undef)
   589  		c.Check(cstrs1.PlugsPerSlot, Equals, undef)
   590  	} else {
   591  		c.Check(cstrs1.PlugsPerSlot, Equals, sideArityAny)
   592  		if strings.HasSuffix(subrule, "-auto-connection") {
   593  			c.Check(cstrs1.SlotsPerPlug, Equals, sideArityOne)
   594  		} else {
   595  			c.Check(cstrs1.SlotsPerPlug, Equals, sideArityAny)
   596  		}
   597  	}
   598  	c.Check(cstrs1.PlugSnapIDs, HasLen, 0)
   599  	c.Check(cstrs1.PlugPublisherIDs, HasLen, 0)
   600  	c.Check(cstrs1.PlugSnapTypes, HasLen, 0)
   601  }
   602  
   603  func (s *plugSlotRulesSuite) TestCompilePlugRuleAllAllowDenyStanzas(c *C) {
   604  	m, err := asserts.ParseHeaders([]byte(`iface:
   605    allow-installation:
   606      plug-attributes:
   607        a1: A1
   608    deny-installation:
   609      plug-attributes:
   610        a2: A2
   611    allow-connection:
   612      plug-attributes:
   613        pa3: PA3
   614      slot-attributes:
   615        sa3: SA3
   616    deny-connection:
   617      plug-attributes:
   618        pa4: PA4
   619      slot-attributes:
   620        sa4: SA4
   621    allow-auto-connection:
   622      plug-attributes:
   623        pa5: PA5
   624      slot-attributes:
   625        sa5: SA5
   626    deny-auto-connection:
   627      plug-attributes:
   628        pa6: PA6
   629      slot-attributes:
   630        sa6: SA6`))
   631  	c.Assert(err, IsNil)
   632  
   633  	rule, err := asserts.CompilePlugRule("iface", m["iface"].(map[string]interface{}))
   634  	c.Assert(err, IsNil)
   635  
   636  	c.Check(rule.Interface, Equals, "iface")
   637  	// install subrules
   638  	c.Assert(rule.AllowInstallation, HasLen, 1)
   639  	checkAttrs(c, rule.AllowInstallation[0].PlugAttributes, "a1", "A1")
   640  	c.Assert(rule.DenyInstallation, HasLen, 1)
   641  	checkAttrs(c, rule.DenyInstallation[0].PlugAttributes, "a2", "A2")
   642  	// connection subrules
   643  	c.Assert(rule.AllowConnection, HasLen, 1)
   644  	checkAttrs(c, rule.AllowConnection[0].PlugAttributes, "pa3", "PA3")
   645  	checkAttrs(c, rule.AllowConnection[0].SlotAttributes, "sa3", "SA3")
   646  	c.Assert(rule.DenyConnection, HasLen, 1)
   647  	checkAttrs(c, rule.DenyConnection[0].PlugAttributes, "pa4", "PA4")
   648  	checkAttrs(c, rule.DenyConnection[0].SlotAttributes, "sa4", "SA4")
   649  	// auto-connection subrules
   650  	c.Assert(rule.AllowAutoConnection, HasLen, 1)
   651  	checkAttrs(c, rule.AllowAutoConnection[0].PlugAttributes, "pa5", "PA5")
   652  	checkAttrs(c, rule.AllowAutoConnection[0].SlotAttributes, "sa5", "SA5")
   653  	c.Assert(rule.DenyAutoConnection, HasLen, 1)
   654  	checkAttrs(c, rule.DenyAutoConnection[0].PlugAttributes, "pa6", "PA6")
   655  	checkAttrs(c, rule.DenyAutoConnection[0].SlotAttributes, "sa6", "SA6")
   656  }
   657  
   658  func (s *plugSlotRulesSuite) TestCompilePlugRuleAllAllowDenyOrStanzas(c *C) {
   659  	m, err := asserts.ParseHeaders([]byte(`iface:
   660    allow-installation:
   661      -
   662        plug-attributes:
   663          a1: A1
   664      -
   665        plug-attributes:
   666          a1: A1alt
   667    deny-installation:
   668      -
   669        plug-attributes:
   670          a2: A2
   671      -
   672        plug-attributes:
   673          a2: A2alt
   674    allow-connection:
   675      -
   676        plug-attributes:
   677          pa3: PA3
   678        slot-attributes:
   679          sa3: SA3
   680      -
   681        plug-attributes:
   682          pa3: PA3alt
   683    deny-connection:
   684      -
   685        plug-attributes:
   686          pa4: PA4
   687        slot-attributes:
   688          sa4: SA4
   689      -
   690        plug-attributes:
   691          pa4: PA4alt
   692    allow-auto-connection:
   693      -
   694        plug-attributes:
   695          pa5: PA5
   696        slot-attributes:
   697          sa5: SA5
   698      -
   699        plug-attributes:
   700          pa5: PA5alt
   701    deny-auto-connection:
   702      -
   703        plug-attributes:
   704          pa6: PA6
   705        slot-attributes:
   706          sa6: SA6
   707      -
   708        plug-attributes:
   709          pa6: PA6alt`))
   710  	c.Assert(err, IsNil)
   711  
   712  	rule, err := asserts.CompilePlugRule("iface", m["iface"].(map[string]interface{}))
   713  	c.Assert(err, IsNil)
   714  
   715  	c.Check(rule.Interface, Equals, "iface")
   716  	// install subrules
   717  	c.Assert(rule.AllowInstallation, HasLen, 2)
   718  	checkAttrs(c, rule.AllowInstallation[0].PlugAttributes, "a1", "A1")
   719  	checkAttrs(c, rule.AllowInstallation[1].PlugAttributes, "a1", "A1alt")
   720  	c.Assert(rule.DenyInstallation, HasLen, 2)
   721  	checkAttrs(c, rule.DenyInstallation[0].PlugAttributes, "a2", "A2")
   722  	checkAttrs(c, rule.DenyInstallation[1].PlugAttributes, "a2", "A2alt")
   723  	// connection subrules
   724  	c.Assert(rule.AllowConnection, HasLen, 2)
   725  	checkAttrs(c, rule.AllowConnection[0].PlugAttributes, "pa3", "PA3")
   726  	checkAttrs(c, rule.AllowConnection[0].SlotAttributes, "sa3", "SA3")
   727  	checkAttrs(c, rule.AllowConnection[1].PlugAttributes, "pa3", "PA3alt")
   728  	c.Assert(rule.DenyConnection, HasLen, 2)
   729  	checkAttrs(c, rule.DenyConnection[0].PlugAttributes, "pa4", "PA4")
   730  	checkAttrs(c, rule.DenyConnection[0].SlotAttributes, "sa4", "SA4")
   731  	checkAttrs(c, rule.DenyConnection[1].PlugAttributes, "pa4", "PA4alt")
   732  	// auto-connection subrules
   733  	c.Assert(rule.AllowAutoConnection, HasLen, 2)
   734  	checkAttrs(c, rule.AllowAutoConnection[0].PlugAttributes, "pa5", "PA5")
   735  	checkAttrs(c, rule.AllowAutoConnection[0].SlotAttributes, "sa5", "SA5")
   736  	checkAttrs(c, rule.AllowAutoConnection[1].PlugAttributes, "pa5", "PA5alt")
   737  	c.Assert(rule.DenyAutoConnection, HasLen, 2)
   738  	checkAttrs(c, rule.DenyAutoConnection[0].PlugAttributes, "pa6", "PA6")
   739  	checkAttrs(c, rule.DenyAutoConnection[0].SlotAttributes, "sa6", "SA6")
   740  	checkAttrs(c, rule.DenyAutoConnection[1].PlugAttributes, "pa6", "PA6alt")
   741  }
   742  
   743  func (s *plugSlotRulesSuite) TestCompilePlugRuleShortcutTrue(c *C) {
   744  	rule, err := asserts.CompilePlugRule("iface", "true")
   745  	c.Assert(err, IsNil)
   746  
   747  	c.Check(rule.Interface, Equals, "iface")
   748  	// install subrules
   749  	c.Assert(rule.AllowInstallation, HasLen, 1)
   750  	c.Check(rule.AllowInstallation[0].PlugAttributes, Equals, asserts.AlwaysMatchAttributes)
   751  	c.Assert(rule.DenyInstallation, HasLen, 1)
   752  	c.Check(rule.DenyInstallation[0].PlugAttributes, Equals, asserts.NeverMatchAttributes)
   753  	// connection subrules
   754  	checkBoolPlugConnConstraints(c, "allow-connection", rule.AllowConnection, true)
   755  	checkBoolPlugConnConstraints(c, "deny-connection", rule.DenyConnection, false)
   756  	// auto-connection subrules
   757  	checkBoolPlugConnConstraints(c, "allow-auto-connection", rule.AllowAutoConnection, true)
   758  	checkBoolPlugConnConstraints(c, "deny-auto-connection", rule.DenyAutoConnection, false)
   759  }
   760  
   761  func (s *plugSlotRulesSuite) TestCompilePlugRuleShortcutFalse(c *C) {
   762  	rule, err := asserts.CompilePlugRule("iface", "false")
   763  	c.Assert(err, IsNil)
   764  
   765  	// install subrules
   766  	c.Assert(rule.AllowInstallation, HasLen, 1)
   767  	c.Check(rule.AllowInstallation[0].PlugAttributes, Equals, asserts.NeverMatchAttributes)
   768  	c.Assert(rule.DenyInstallation, HasLen, 1)
   769  	c.Check(rule.DenyInstallation[0].PlugAttributes, Equals, asserts.AlwaysMatchAttributes)
   770  	// connection subrules
   771  	checkBoolPlugConnConstraints(c, "allow-connection", rule.AllowConnection, false)
   772  	checkBoolPlugConnConstraints(c, "deny-connection", rule.DenyConnection, true)
   773  	// auto-connection subrules
   774  	checkBoolPlugConnConstraints(c, "allow-auto-connection", rule.AllowAutoConnection, false)
   775  	checkBoolPlugConnConstraints(c, "deny-auto-connection", rule.DenyAutoConnection, true)
   776  }
   777  
   778  func (s *plugSlotRulesSuite) TestCompilePlugRuleDefaults(c *C) {
   779  	rule, err := asserts.CompilePlugRule("iface", map[string]interface{}{
   780  		"deny-auto-connection": "true",
   781  	})
   782  	c.Assert(err, IsNil)
   783  
   784  	// everything follows the defaults...
   785  
   786  	// install subrules
   787  	c.Assert(rule.AllowInstallation, HasLen, 1)
   788  	c.Check(rule.AllowInstallation[0].PlugAttributes, Equals, asserts.AlwaysMatchAttributes)
   789  	c.Assert(rule.DenyInstallation, HasLen, 1)
   790  	c.Check(rule.DenyInstallation[0].PlugAttributes, Equals, asserts.NeverMatchAttributes)
   791  	// connection subrules
   792  	checkBoolPlugConnConstraints(c, "allow-connection", rule.AllowConnection, true)
   793  	checkBoolPlugConnConstraints(c, "deny-connection", rule.DenyConnection, false)
   794  	// auto-connection subrules
   795  	checkBoolPlugConnConstraints(c, "allow-auto-connection", rule.AllowAutoConnection, true)
   796  	// ... but deny-auto-connection is on
   797  	checkBoolPlugConnConstraints(c, "deny-auto-connection", rule.DenyAutoConnection, true)
   798  }
   799  
   800  func (s *plugSlotRulesSuite) TestCompilePlugRuleInstalationConstraintsIDConstraints(c *C) {
   801  	rule, err := asserts.CompilePlugRule("iface", map[string]interface{}{
   802  		"allow-installation": map[string]interface{}{
   803  			"plug-snap-type": []interface{}{"core", "kernel", "gadget", "app"},
   804  		},
   805  	})
   806  	c.Assert(err, IsNil)
   807  
   808  	c.Assert(rule.AllowInstallation, HasLen, 1)
   809  	cstrs := rule.AllowInstallation[0]
   810  	c.Check(cstrs.PlugSnapTypes, DeepEquals, []string{"core", "kernel", "gadget", "app"})
   811  }
   812  
   813  func (s *plugSlotRulesSuite) TestCompilePlugRuleInstallationConstraintsOnClassic(c *C) {
   814  	m, err := asserts.ParseHeaders([]byte(`iface:
   815    allow-installation: true`))
   816  	c.Assert(err, IsNil)
   817  
   818  	rule, err := asserts.CompilePlugRule("iface", m["iface"].(map[string]interface{}))
   819  	c.Assert(err, IsNil)
   820  
   821  	c.Check(rule.AllowInstallation[0].OnClassic, IsNil)
   822  
   823  	m, err = asserts.ParseHeaders([]byte(`iface:
   824    allow-installation:
   825      on-classic: false`))
   826  	c.Assert(err, IsNil)
   827  
   828  	rule, err = asserts.CompilePlugRule("iface", m["iface"].(map[string]interface{}))
   829  	c.Assert(err, IsNil)
   830  
   831  	c.Check(rule.AllowInstallation[0].OnClassic, DeepEquals, &asserts.OnClassicConstraint{})
   832  
   833  	m, err = asserts.ParseHeaders([]byte(`iface:
   834    allow-installation:
   835      on-classic: true`))
   836  	c.Assert(err, IsNil)
   837  
   838  	rule, err = asserts.CompilePlugRule("iface", m["iface"].(map[string]interface{}))
   839  	c.Assert(err, IsNil)
   840  
   841  	c.Check(rule.AllowInstallation[0].OnClassic, DeepEquals, &asserts.OnClassicConstraint{Classic: true})
   842  
   843  	m, err = asserts.ParseHeaders([]byte(`iface:
   844    allow-installation:
   845      on-classic:
   846        - ubuntu
   847        - debian`))
   848  	c.Assert(err, IsNil)
   849  
   850  	rule, err = asserts.CompilePlugRule("iface", m["iface"].(map[string]interface{}))
   851  	c.Assert(err, IsNil)
   852  
   853  	c.Check(rule.AllowInstallation[0].OnClassic, DeepEquals, &asserts.OnClassicConstraint{Classic: true, SystemIDs: []string{"ubuntu", "debian"}})
   854  }
   855  
   856  func (s *plugSlotRulesSuite) TestCompilePlugRuleInstallationConstraintsDeviceScope(c *C) {
   857  	m, err := asserts.ParseHeaders([]byte(`iface:
   858    allow-installation: true`))
   859  	c.Assert(err, IsNil)
   860  
   861  	rule, err := asserts.CompilePlugRule("iface", m["iface"].(map[string]interface{}))
   862  	c.Assert(err, IsNil)
   863  
   864  	c.Check(rule.AllowInstallation[0].DeviceScope, IsNil)
   865  
   866  	tests := []struct {
   867  		rule     string
   868  		expected asserts.DeviceScopeConstraint
   869  	}{
   870  		{`iface:
   871    allow-installation:
   872      on-store:
   873        - my-store`, asserts.DeviceScopeConstraint{Store: []string{"my-store"}}},
   874  		{`iface:
   875    allow-installation:
   876      on-store:
   877        - my-store
   878        - other-store`, asserts.DeviceScopeConstraint{Store: []string{"my-store", "other-store"}}},
   879  		{`iface:
   880    allow-installation:
   881      on-brand:
   882        - my-brand
   883        - s9zGdwb16ysLeRW6nRivwZS5r9puP8JT`, asserts.DeviceScopeConstraint{Brand: []string{"my-brand", "s9zGdwb16ysLeRW6nRivwZS5r9puP8JT"}}},
   884  		{`iface:
   885    allow-installation:
   886      on-model:
   887        - my-brand/bar
   888        - s9zGdwb16ysLeRW6nRivwZS5r9puP8JT/baz`, asserts.DeviceScopeConstraint{Model: []string{"my-brand/bar", "s9zGdwb16ysLeRW6nRivwZS5r9puP8JT/baz"}}},
   889  		{`iface:
   890    allow-installation:
   891      on-store:
   892        - store1
   893        - store2
   894      on-brand:
   895        - my-brand
   896      on-model:
   897        - my-brand/bar
   898        - s9zGdwb16ysLeRW6nRivwZS5r9puP8JT/baz`, asserts.DeviceScopeConstraint{
   899  			Store: []string{"store1", "store2"},
   900  			Brand: []string{"my-brand"},
   901  			Model: []string{"my-brand/bar", "s9zGdwb16ysLeRW6nRivwZS5r9puP8JT/baz"}}},
   902  	}
   903  
   904  	for _, t := range tests {
   905  		m, err = asserts.ParseHeaders([]byte(t.rule))
   906  		c.Assert(err, IsNil)
   907  
   908  		rule, err = asserts.CompilePlugRule("iface", m["iface"].(map[string]interface{}))
   909  		c.Assert(err, IsNil)
   910  
   911  		c.Check(rule.AllowInstallation[0].DeviceScope, DeepEquals, &t.expected)
   912  	}
   913  }
   914  
   915  func (s *plugSlotRulesSuite) TestCompilePlugRuleInstallationConstraintsPlugNames(c *C) {
   916  	m, err := asserts.ParseHeaders([]byte(`iface:
   917    allow-installation: true`))
   918  	c.Assert(err, IsNil)
   919  
   920  	rule, err := asserts.CompilePlugRule("iface", m["iface"].(map[string]interface{}))
   921  	c.Assert(err, IsNil)
   922  
   923  	c.Check(rule.AllowInstallation[0].PlugNames, IsNil)
   924  
   925  	tests := []struct {
   926  		rule        string
   927  		matching    []string
   928  		notMatching []string
   929  	}{
   930  		{`iface:
   931    allow-installation:
   932      plug-names:
   933        - foo`, []string{"foo"}, []string{"bar"}},
   934  		{`iface:
   935    allow-installation:
   936      plug-names:
   937        - foo
   938        - bar`, []string{"foo", "bar"}, []string{"baz"}},
   939  		{`iface:
   940    allow-installation:
   941      plug-names:
   942        - foo[0-9]
   943        - bar`, []string{"foo0", "foo1", "bar"}, []string{"baz", "fooo", "foo12"}},
   944  	}
   945  	for _, t := range tests {
   946  		m, err = asserts.ParseHeaders([]byte(t.rule))
   947  		c.Assert(err, IsNil)
   948  
   949  		rule, err = asserts.CompilePlugRule("iface", m["iface"].(map[string]interface{}))
   950  		c.Assert(err, IsNil)
   951  
   952  		for _, matching := range t.matching {
   953  			c.Check(rule.AllowInstallation[0].PlugNames.Check("plug name", matching, nil), IsNil)
   954  		}
   955  		for _, notMatching := range t.notMatching {
   956  			c.Check(rule.AllowInstallation[0].PlugNames.Check("plug name", notMatching, nil), NotNil)
   957  		}
   958  	}
   959  }
   960  
   961  func (s *plugSlotRulesSuite) TestCompilePlugRuleConnectionConstraintsIDConstraints(c *C) {
   962  	rule, err := asserts.CompilePlugRule("iface", map[string]interface{}{
   963  		"allow-connection": map[string]interface{}{
   964  			"slot-snap-type":    []interface{}{"core", "kernel", "gadget", "app"},
   965  			"slot-snap-id":      []interface{}{"snapidsnapidsnapidsnapidsnapid01", "snapidsnapidsnapidsnapidsnapid02"},
   966  			"slot-publisher-id": []interface{}{"pubidpubidpubidpubidpubidpubid09", "canonical", "$SAME"},
   967  		},
   968  	})
   969  	c.Assert(err, IsNil)
   970  
   971  	c.Assert(rule.AllowConnection, HasLen, 1)
   972  	cstrs := rule.AllowConnection[0]
   973  	c.Check(cstrs.SlotSnapTypes, DeepEquals, []string{"core", "kernel", "gadget", "app"})
   974  	c.Check(cstrs.SlotSnapIDs, DeepEquals, []string{"snapidsnapidsnapidsnapidsnapid01", "snapidsnapidsnapidsnapidsnapid02"})
   975  	c.Check(cstrs.SlotPublisherIDs, DeepEquals, []string{"pubidpubidpubidpubidpubidpubid09", "canonical", "$SAME"})
   976  
   977  }
   978  
   979  func (s *plugSlotRulesSuite) TestCompilePlugRuleConnectionConstraintsOnClassic(c *C) {
   980  	m, err := asserts.ParseHeaders([]byte(`iface:
   981    allow-connection: true`))
   982  	c.Assert(err, IsNil)
   983  
   984  	rule, err := asserts.CompilePlugRule("iface", m["iface"].(map[string]interface{}))
   985  	c.Assert(err, IsNil)
   986  
   987  	c.Check(rule.AllowConnection[0].OnClassic, IsNil)
   988  
   989  	m, err = asserts.ParseHeaders([]byte(`iface:
   990    allow-connection:
   991      on-classic: false`))
   992  	c.Assert(err, IsNil)
   993  
   994  	rule, err = asserts.CompilePlugRule("iface", m["iface"].(map[string]interface{}))
   995  	c.Assert(err, IsNil)
   996  
   997  	c.Check(rule.AllowConnection[0].OnClassic, DeepEquals, &asserts.OnClassicConstraint{})
   998  
   999  	m, err = asserts.ParseHeaders([]byte(`iface:
  1000    allow-connection:
  1001      on-classic: true`))
  1002  	c.Assert(err, IsNil)
  1003  
  1004  	rule, err = asserts.CompilePlugRule("iface", m["iface"].(map[string]interface{}))
  1005  	c.Assert(err, IsNil)
  1006  
  1007  	c.Check(rule.AllowConnection[0].OnClassic, DeepEquals, &asserts.OnClassicConstraint{Classic: true})
  1008  
  1009  	m, err = asserts.ParseHeaders([]byte(`iface:
  1010    allow-connection:
  1011      on-classic:
  1012        - ubuntu
  1013        - debian`))
  1014  	c.Assert(err, IsNil)
  1015  
  1016  	rule, err = asserts.CompilePlugRule("iface", m["iface"].(map[string]interface{}))
  1017  	c.Assert(err, IsNil)
  1018  
  1019  	c.Check(rule.AllowConnection[0].OnClassic, DeepEquals, &asserts.OnClassicConstraint{Classic: true, SystemIDs: []string{"ubuntu", "debian"}})
  1020  }
  1021  
  1022  func (s *plugSlotRulesSuite) TestCompilePlugRuleConnectionConstraintsDeviceScope(c *C) {
  1023  	m, err := asserts.ParseHeaders([]byte(`iface:
  1024    allow-connection: true`))
  1025  	c.Assert(err, IsNil)
  1026  
  1027  	rule, err := asserts.CompilePlugRule("iface", m["iface"].(map[string]interface{}))
  1028  	c.Assert(err, IsNil)
  1029  
  1030  	c.Check(rule.AllowInstallation[0].DeviceScope, IsNil)
  1031  
  1032  	tests := []struct {
  1033  		rule     string
  1034  		expected asserts.DeviceScopeConstraint
  1035  	}{
  1036  		{`iface:
  1037    allow-connection:
  1038      on-store:
  1039        - my-store`, asserts.DeviceScopeConstraint{Store: []string{"my-store"}}},
  1040  		{`iface:
  1041    allow-connection:
  1042      on-store:
  1043        - my-store
  1044        - other-store`, asserts.DeviceScopeConstraint{Store: []string{"my-store", "other-store"}}},
  1045  		{`iface:
  1046    allow-connection:
  1047      on-brand:
  1048        - my-brand
  1049        - s9zGdwb16ysLeRW6nRivwZS5r9puP8JT`, asserts.DeviceScopeConstraint{Brand: []string{"my-brand", "s9zGdwb16ysLeRW6nRivwZS5r9puP8JT"}}},
  1050  		{`iface:
  1051    allow-connection:
  1052      on-model:
  1053        - my-brand/bar
  1054        - s9zGdwb16ysLeRW6nRivwZS5r9puP8JT/baz`, asserts.DeviceScopeConstraint{Model: []string{"my-brand/bar", "s9zGdwb16ysLeRW6nRivwZS5r9puP8JT/baz"}}},
  1055  		{`iface:
  1056    allow-connection:
  1057      on-store:
  1058        - store1
  1059        - store2
  1060      on-brand:
  1061        - my-brand
  1062      on-model:
  1063        - my-brand/bar
  1064        - s9zGdwb16ysLeRW6nRivwZS5r9puP8JT/baz`, asserts.DeviceScopeConstraint{
  1065  			Store: []string{"store1", "store2"},
  1066  			Brand: []string{"my-brand"},
  1067  			Model: []string{"my-brand/bar", "s9zGdwb16ysLeRW6nRivwZS5r9puP8JT/baz"}}},
  1068  	}
  1069  
  1070  	for _, t := range tests {
  1071  		m, err = asserts.ParseHeaders([]byte(t.rule))
  1072  		c.Assert(err, IsNil)
  1073  
  1074  		rule, err = asserts.CompilePlugRule("iface", m["iface"].(map[string]interface{}))
  1075  		c.Assert(err, IsNil)
  1076  
  1077  		c.Check(rule.AllowConnection[0].DeviceScope, DeepEquals, &t.expected)
  1078  	}
  1079  }
  1080  
  1081  func (s *plugSlotRulesSuite) TestCompilePlugRuleConnectionConstraintsPlugNamesSlotNames(c *C) {
  1082  	m, err := asserts.ParseHeaders([]byte(`iface:
  1083    allow-connection: true`))
  1084  	c.Assert(err, IsNil)
  1085  
  1086  	rule, err := asserts.CompilePlugRule("iface", m["iface"].(map[string]interface{}))
  1087  	c.Assert(err, IsNil)
  1088  
  1089  	c.Check(rule.AllowConnection[0].PlugNames, IsNil)
  1090  	c.Check(rule.AllowConnection[0].SlotNames, IsNil)
  1091  
  1092  	tests := []struct {
  1093  		rule        string
  1094  		matching    []string
  1095  		notMatching []string
  1096  	}{
  1097  		{`iface:
  1098    allow-connection:
  1099      plug-names:
  1100        - Pfoo
  1101      slot-names:
  1102        - Sfoo`, []string{"foo"}, []string{"bar"}},
  1103  		{`iface:
  1104    allow-connection:
  1105      plug-names:
  1106        - Pfoo
  1107        - Pbar
  1108      slot-names:
  1109        - Sfoo
  1110        - Sbar`, []string{"foo", "bar"}, []string{"baz"}},
  1111  		{`iface:
  1112    allow-connection:
  1113      plug-names:
  1114        - Pfoo[0-9]
  1115        - Pbar
  1116      slot-names:
  1117        - Sfoo[0-9]
  1118        - Sbar`, []string{"foo0", "foo1", "bar"}, []string{"baz", "fooo", "foo12"}},
  1119  	}
  1120  	for _, t := range tests {
  1121  		m, err = asserts.ParseHeaders([]byte(t.rule))
  1122  		c.Assert(err, IsNil)
  1123  
  1124  		rule, err = asserts.CompilePlugRule("iface", m["iface"].(map[string]interface{}))
  1125  		c.Assert(err, IsNil)
  1126  
  1127  		for _, matching := range t.matching {
  1128  			c.Check(rule.AllowConnection[0].PlugNames.Check("plug name", "P"+matching, nil), IsNil)
  1129  
  1130  			c.Check(rule.AllowConnection[0].SlotNames.Check("slot name", "S"+matching, nil), IsNil)
  1131  		}
  1132  
  1133  		for _, notMatching := range t.notMatching {
  1134  			c.Check(rule.AllowConnection[0].SlotNames.Check("plug name", "P"+notMatching, nil), NotNil)
  1135  
  1136  			c.Check(rule.AllowConnection[0].SlotNames.Check("slot name", "S"+notMatching, nil), NotNil)
  1137  		}
  1138  	}
  1139  }
  1140  
  1141  func (s *plugSlotRulesSuite) TestCompilePlugRuleConnectionConstraintsSideArityConstraints(c *C) {
  1142  	m, err := asserts.ParseHeaders([]byte(`iface:
  1143    allow-auto-connection: true`))
  1144  	c.Assert(err, IsNil)
  1145  
  1146  	rule, err := asserts.CompilePlugRule("iface", m["iface"].(map[string]interface{}))
  1147  	c.Assert(err, IsNil)
  1148  
  1149  	// defaults
  1150  	c.Check(rule.AllowAutoConnection[0].SlotsPerPlug, Equals, asserts.SideArityConstraint{N: 1})
  1151  	c.Check(rule.AllowAutoConnection[0].PlugsPerSlot.Any(), Equals, true)
  1152  
  1153  	c.Check(rule.AllowConnection[0].SlotsPerPlug.Any(), Equals, true)
  1154  	c.Check(rule.AllowConnection[0].PlugsPerSlot.Any(), Equals, true)
  1155  
  1156  	// test that the arity constraints get normalized away to any
  1157  	// under allow-connection
  1158  	// see https://forum.snapcraft.io/t/plug-slot-declaration-rules-greedy-plugs/12438
  1159  	allowConnTests := []string{
  1160  		`iface:
  1161    allow-connection:
  1162      slots-per-plug: 1
  1163      plugs-per-slot: 2`,
  1164  		`iface:
  1165    allow-connection:
  1166      slots-per-plug: *
  1167      plugs-per-slot: 1`,
  1168  		`iface:
  1169    allow-connection:
  1170      slots-per-plug: 2
  1171      plugs-per-slot: *`,
  1172  	}
  1173  
  1174  	for _, t := range allowConnTests {
  1175  		m, err = asserts.ParseHeaders([]byte(t))
  1176  		c.Assert(err, IsNil)
  1177  
  1178  		rule, err = asserts.CompilePlugRule("iface", m["iface"].(map[string]interface{}))
  1179  		c.Assert(err, IsNil)
  1180  
  1181  		c.Check(rule.AllowConnection[0].SlotsPerPlug.Any(), Equals, true)
  1182  		c.Check(rule.AllowConnection[0].PlugsPerSlot.Any(), Equals, true)
  1183  	}
  1184  
  1185  	// test that under allow-auto-connection:
  1186  	// slots-per-plug can be * (any) or otherwise gets normalized to 1
  1187  	// plugs-per-slot gets normalized to any (*)
  1188  	// see https://forum.snapcraft.io/t/plug-slot-declaration-rules-greedy-plugs/12438
  1189  	allowAutoConnTests := []struct {
  1190  		rule         string
  1191  		slotsPerPlug asserts.SideArityConstraint
  1192  	}{
  1193  		{`iface:
  1194    allow-auto-connection:
  1195      slots-per-plug: 1
  1196      plugs-per-slot: 2`, sideArityOne},
  1197  		{`iface:
  1198    allow-auto-connection:
  1199      slots-per-plug: *
  1200      plugs-per-slot: 1`, sideArityAny},
  1201  		{`iface:
  1202    allow-auto-connection:
  1203      slots-per-plug: 2
  1204      plugs-per-slot: *`, sideArityOne},
  1205  	}
  1206  
  1207  	for _, t := range allowAutoConnTests {
  1208  		m, err = asserts.ParseHeaders([]byte(t.rule))
  1209  		c.Assert(err, IsNil)
  1210  
  1211  		rule, err = asserts.CompilePlugRule("iface", m["iface"].(map[string]interface{}))
  1212  		c.Assert(err, IsNil)
  1213  
  1214  		c.Check(rule.AllowAutoConnection[0].SlotsPerPlug, Equals, t.slotsPerPlug)
  1215  		c.Check(rule.AllowAutoConnection[0].PlugsPerSlot.Any(), Equals, true)
  1216  	}
  1217  }
  1218  
  1219  func (s *plugSlotRulesSuite) TestCompilePlugRuleConnectionConstraintsAttributesDefault(c *C) {
  1220  	rule, err := asserts.CompilePlugRule("iface", map[string]interface{}{
  1221  		"allow-connection": map[string]interface{}{
  1222  			"slot-snap-id": []interface{}{"snapidsnapidsnapidsnapidsnapid01"},
  1223  		},
  1224  	})
  1225  	c.Assert(err, IsNil)
  1226  
  1227  	// attributes default to always matching here
  1228  	cstrs := rule.AllowConnection[0]
  1229  	c.Check(cstrs.PlugAttributes, Equals, asserts.AlwaysMatchAttributes)
  1230  	c.Check(cstrs.SlotAttributes, Equals, asserts.AlwaysMatchAttributes)
  1231  }
  1232  
  1233  func (s *plugSlotRulesSuite) TestCompilePlugRuleErrors(c *C) {
  1234  	tests := []struct {
  1235  		stanza string
  1236  		err    string
  1237  	}{
  1238  		{`iface: foo`, `plug rule for interface "iface" must be a map or one of the shortcuts 'true' or 'false'`},
  1239  		{`iface:
  1240    - allow`, `plug rule for interface "iface" must be a map or one of the shortcuts 'true' or 'false'`},
  1241  		{`iface:
  1242    allow-installation: foo`, `allow-installation in plug rule for interface "iface" must be a map or one of the shortcuts 'true' or 'false'`},
  1243  		{`iface:
  1244    deny-installation: foo`, `deny-installation in plug rule for interface "iface" must be a map or one of the shortcuts 'true' or 'false'`},
  1245  		{`iface:
  1246    allow-connection: foo`, `allow-connection in plug rule for interface "iface" must be a map or one of the shortcuts 'true' or 'false'`},
  1247  		{`iface:
  1248    allow-connection:
  1249      - foo`, `alternative 1 of allow-connection in plug rule for interface "iface" must be a map`},
  1250  		{`iface:
  1251    allow-connection:
  1252      - true`, `alternative 1 of allow-connection in plug rule for interface "iface" must be a map`},
  1253  		{`iface:
  1254    allow-installation:
  1255      plug-attributes:
  1256        a1: [`, `cannot compile plug-attributes in allow-installation in plug rule for interface "iface": cannot compile "a1" constraint .*`},
  1257  		{`iface:
  1258    allow-connection:
  1259      slot-attributes:
  1260        a2: [`, `cannot compile slot-attributes in allow-connection in plug rule for interface "iface": cannot compile "a2" constraint .*`},
  1261  		{`iface:
  1262    allow-connection:
  1263      slot-snap-id:
  1264        -
  1265          foo: 1`, `slot-snap-id in allow-connection in plug rule for interface "iface" must be a list of strings`},
  1266  		{`iface:
  1267    allow-connection:
  1268      slot-snap-id:
  1269        - foo`, `slot-snap-id in allow-connection in plug rule for interface "iface" contains an invalid element: "foo"`},
  1270  		{`iface:
  1271    allow-connection:
  1272      slot-snap-type:
  1273        - foo`, `slot-snap-type in allow-connection in plug rule for interface "iface" contains an invalid element: "foo"`},
  1274  		{`iface:
  1275    allow-connection:
  1276      slot-snap-type:
  1277        - xapp`, `slot-snap-type in allow-connection in plug rule for interface "iface" contains an invalid element: "xapp"`},
  1278  		{`iface:
  1279    allow-connection:
  1280      slot-snap-ids:
  1281        - foo`, `allow-connection in plug rule for interface "iface" must specify at least one of plug-names, slot-names, plug-attributes, slot-attributes, slot-snap-type, slot-publisher-id, slot-snap-id, slots-per-plug, plugs-per-slot, on-classic, on-store, on-brand, on-model`},
  1282  		{`iface:
  1283    deny-connection:
  1284      slot-snap-ids:
  1285        - foo`, `deny-connection in plug rule for interface "iface" must specify at least one of plug-names, slot-names, plug-attributes, slot-attributes, slot-snap-type, slot-publisher-id, slot-snap-id, slots-per-plug, plugs-per-slot, on-classic, on-store, on-brand, on-model`},
  1286  		{`iface:
  1287    allow-auto-connection:
  1288      slot-snap-ids:
  1289        - foo`, `allow-auto-connection in plug rule for interface "iface" must specify at least one of plug-names, slot-names, plug-attributes, slot-attributes, slot-snap-type, slot-publisher-id, slot-snap-id, slots-per-plug, plugs-per-slot, on-classic, on-store, on-brand, on-model`},
  1290  		{`iface:
  1291    deny-auto-connection:
  1292      slot-snap-ids:
  1293        - foo`, `deny-auto-connection in plug rule for interface "iface" must specify at least one of plug-names, slot-names, plug-attributes, slot-attributes, slot-snap-type, slot-publisher-id, slot-snap-id, slots-per-plug, plugs-per-slot, on-classic, on-store, on-brand, on-model`},
  1294  		{`iface:
  1295    allow-connect: true`, `plug rule for interface "iface" must specify at least one of allow-installation, deny-installation, allow-connection, deny-connection, allow-auto-connection, deny-auto-connection`},
  1296  		{`iface:
  1297    allow-installation:
  1298      on-store: true`, `on-store in allow-installation in plug rule for interface \"iface\" must be a list of strings`},
  1299  		{`iface:
  1300    allow-installation:
  1301      on-store: store1`, `on-store in allow-installation in plug rule for interface \"iface\" must be a list of strings`},
  1302  		{`iface:
  1303    allow-installation:
  1304      on-store:
  1305        - zoom!`, `on-store in allow-installation in plug rule for interface \"iface\" contains an invalid element: \"zoom!\"`},
  1306  		{`iface:
  1307    allow-connection:
  1308      on-brand: true`, `on-brand in allow-connection in plug rule for interface \"iface\" must be a list of strings`},
  1309  		{`iface:
  1310    allow-connection:
  1311      on-brand: brand1`, `on-brand in allow-connection in plug rule for interface \"iface\" must be a list of strings`},
  1312  		{`iface:
  1313    allow-connection:
  1314      on-brand:
  1315        - zoom!`, `on-brand in allow-connection in plug rule for interface \"iface\" contains an invalid element: \"zoom!\"`},
  1316  		{`iface:
  1317    allow-auto-connection:
  1318      on-model: true`, `on-model in allow-auto-connection in plug rule for interface \"iface\" must be a list of strings`},
  1319  		{`iface:
  1320    allow-auto-connection:
  1321      on-model: foo/bar`, `on-model in allow-auto-connection in plug rule for interface \"iface\" must be a list of strings`},
  1322  		{`iface:
  1323    allow-auto-connection:
  1324      on-model:
  1325        - foo/!qz`, `on-model in allow-auto-connection in plug rule for interface \"iface\" contains an invalid element: \"foo/!qz"`},
  1326  		{`iface:
  1327    allow-installation:
  1328      slots-per-plug: 1`, `allow-installation in plug rule for interface "iface" cannot specify a slots-per-plug constraint, they apply only to allow-\*connection`},
  1329  		{`iface:
  1330    deny-connection:
  1331      slots-per-plug: 1`, `deny-connection in plug rule for interface "iface" cannot specify a slots-per-plug constraint, they apply only to allow-\*connection`},
  1332  		{`iface:
  1333    allow-auto-connection:
  1334      plugs-per-slot: any`, `plugs-per-slot in allow-auto-connection in plug rule for interface "iface" must be an integer >=1 or \*`},
  1335  		{`iface:
  1336    allow-auto-connection:
  1337      slots-per-plug: 00`, `slots-per-plug in allow-auto-connection in plug rule for interface "iface" has invalid prefix zeros: 00`},
  1338  		{`iface:
  1339    allow-auto-connection:
  1340      slots-per-plug: 99999999999999999999`, `slots-per-plug in allow-auto-connection in plug rule for interface "iface" is out of range: 99999999999999999999`},
  1341  		{`iface:
  1342    allow-auto-connection:
  1343      slots-per-plug: 0`, `slots-per-plug in allow-auto-connection in plug rule for interface "iface" must be an integer >=1 or \*`},
  1344  		{`iface:
  1345    allow-auto-connection:
  1346      slots-per-plug:
  1347        what: 1`, `slots-per-plug in allow-auto-connection in plug rule for interface "iface" must be an integer >=1 or \*`},
  1348  		{`iface:
  1349    allow-auto-connection:
  1350      plug-names: true`, `plug-names constraints must be a list of regexps and special \$ values`},
  1351  		{`iface:
  1352    allow-auto-connection:
  1353      slot-names: true`, `slot-names constraints must be a list of regexps and special \$ values`},
  1354  	}
  1355  
  1356  	for _, t := range tests {
  1357  		m, err := asserts.ParseHeaders([]byte(t.stanza))
  1358  		c.Assert(err, IsNil, Commentf(t.stanza))
  1359  
  1360  		_, err = asserts.CompilePlugRule("iface", m["iface"])
  1361  		c.Check(err, ErrorMatches, t.err, Commentf(t.stanza))
  1362  	}
  1363  }
  1364  
  1365  var (
  1366  	deviceScopeConstrs = map[string][]interface{}{
  1367  		"on-store": {"store"},
  1368  		"on-brand": {"brand"},
  1369  		"on-model": {"brand/model"},
  1370  	}
  1371  )
  1372  
  1373  func (s *plugSlotRulesSuite) TestPlugRuleFeatures(c *C) {
  1374  	combos := []struct {
  1375  		subrule             string
  1376  		constraintsPrefixes []string
  1377  	}{
  1378  		{"allow-installation", []string{"plug-"}},
  1379  		{"deny-installation", []string{"plug-"}},
  1380  		{"allow-connection", []string{"plug-", "slot-"}},
  1381  		{"deny-connection", []string{"plug-", "slot-"}},
  1382  		{"allow-auto-connection", []string{"plug-", "slot-"}},
  1383  		{"deny-auto-connection", []string{"plug-", "slot-"}},
  1384  	}
  1385  
  1386  	for _, combo := range combos {
  1387  		for _, attrConstrPrefix := range combo.constraintsPrefixes {
  1388  			attrConstraintMap := map[string]interface{}{
  1389  				"a":     "ATTR",
  1390  				"other": []interface{}{"x", "y"},
  1391  			}
  1392  			ruleMap := map[string]interface{}{
  1393  				combo.subrule: map[string]interface{}{
  1394  					attrConstrPrefix + "attributes": attrConstraintMap,
  1395  				},
  1396  			}
  1397  
  1398  			rule, err := asserts.CompilePlugRule("iface", ruleMap)
  1399  			c.Assert(err, IsNil)
  1400  			c.Check(asserts.RuleFeature(rule, "dollar-attr-constraints"), Equals, false, Commentf("%v", ruleMap))
  1401  
  1402  			c.Check(asserts.RuleFeature(rule, "device-scope-constraints"), Equals, false, Commentf("%v", ruleMap))
  1403  			c.Check(asserts.RuleFeature(rule, "name-constraints"), Equals, false, Commentf("%v", ruleMap))
  1404  
  1405  			attrConstraintMap["a"] = "$MISSING"
  1406  			rule, err = asserts.CompilePlugRule("iface", ruleMap)
  1407  			c.Assert(err, IsNil)
  1408  			c.Check(asserts.RuleFeature(rule, "dollar-attr-constraints"), Equals, true, Commentf("%v", ruleMap))
  1409  
  1410  			// covers also alternation
  1411  			attrConstraintMap["a"] = []interface{}{"$SLOT(a)"}
  1412  			rule, err = asserts.CompilePlugRule("iface", ruleMap)
  1413  			c.Assert(err, IsNil)
  1414  			c.Check(asserts.RuleFeature(rule, "dollar-attr-constraints"), Equals, true, Commentf("%v", ruleMap))
  1415  
  1416  			c.Check(asserts.RuleFeature(rule, "device-scope-constraints"), Equals, false, Commentf("%v", ruleMap))
  1417  			c.Check(asserts.RuleFeature(rule, "name-constraints"), Equals, false, Commentf("%v", ruleMap))
  1418  
  1419  		}
  1420  
  1421  		for deviceScopeConstr, value := range deviceScopeConstrs {
  1422  			ruleMap := map[string]interface{}{
  1423  				combo.subrule: map[string]interface{}{
  1424  					deviceScopeConstr: value,
  1425  				},
  1426  			}
  1427  
  1428  			rule, err := asserts.CompilePlugRule("iface", ruleMap)
  1429  			c.Assert(err, IsNil)
  1430  			c.Check(asserts.RuleFeature(rule, "device-scope-constraints"), Equals, true, Commentf("%v", ruleMap))
  1431  		}
  1432  
  1433  		for _, nameConstrPrefix := range combo.constraintsPrefixes {
  1434  			ruleMap := map[string]interface{}{
  1435  				combo.subrule: map[string]interface{}{
  1436  					nameConstrPrefix + "names": []interface{}{"foo"},
  1437  				},
  1438  			}
  1439  
  1440  			rule, err := asserts.CompilePlugRule("iface", ruleMap)
  1441  			c.Assert(err, IsNil)
  1442  			c.Check(asserts.RuleFeature(rule, "name-constraints"), Equals, true, Commentf("%v", ruleMap))
  1443  		}
  1444  	}
  1445  }
  1446  
  1447  func (s *plugSlotRulesSuite) TestCompileSlotRuleAllAllowDenyStanzas(c *C) {
  1448  	m, err := asserts.ParseHeaders([]byte(`iface:
  1449    allow-installation:
  1450      slot-attributes:
  1451        a1: A1
  1452    deny-installation:
  1453      slot-attributes:
  1454        a2: A2
  1455    allow-connection:
  1456      plug-attributes:
  1457        pa3: PA3
  1458      slot-attributes:
  1459        sa3: SA3
  1460    deny-connection:
  1461      plug-attributes:
  1462        pa4: PA4
  1463      slot-attributes:
  1464        sa4: SA4
  1465    allow-auto-connection:
  1466      plug-attributes:
  1467        pa5: PA5
  1468      slot-attributes:
  1469        sa5: SA5
  1470    deny-auto-connection:
  1471      plug-attributes:
  1472        pa6: PA6
  1473      slot-attributes:
  1474        sa6: SA6`))
  1475  	c.Assert(err, IsNil)
  1476  
  1477  	rule, err := asserts.CompileSlotRule("iface", m["iface"].(map[string]interface{}))
  1478  	c.Assert(err, IsNil)
  1479  
  1480  	c.Check(rule.Interface, Equals, "iface")
  1481  	// install subrules
  1482  	c.Assert(rule.AllowInstallation, HasLen, 1)
  1483  	checkAttrs(c, rule.AllowInstallation[0].SlotAttributes, "a1", "A1")
  1484  	c.Assert(rule.DenyInstallation, HasLen, 1)
  1485  	checkAttrs(c, rule.DenyInstallation[0].SlotAttributes, "a2", "A2")
  1486  	// connection subrules
  1487  	c.Assert(rule.AllowConnection, HasLen, 1)
  1488  	checkAttrs(c, rule.AllowConnection[0].PlugAttributes, "pa3", "PA3")
  1489  	checkAttrs(c, rule.AllowConnection[0].SlotAttributes, "sa3", "SA3")
  1490  	c.Assert(rule.DenyConnection, HasLen, 1)
  1491  	checkAttrs(c, rule.DenyConnection[0].PlugAttributes, "pa4", "PA4")
  1492  	checkAttrs(c, rule.DenyConnection[0].SlotAttributes, "sa4", "SA4")
  1493  	// auto-connection subrules
  1494  	c.Assert(rule.AllowAutoConnection, HasLen, 1)
  1495  	checkAttrs(c, rule.AllowAutoConnection[0].PlugAttributes, "pa5", "PA5")
  1496  	checkAttrs(c, rule.AllowAutoConnection[0].SlotAttributes, "sa5", "SA5")
  1497  	c.Assert(rule.DenyAutoConnection, HasLen, 1)
  1498  	checkAttrs(c, rule.DenyAutoConnection[0].PlugAttributes, "pa6", "PA6")
  1499  	checkAttrs(c, rule.DenyAutoConnection[0].SlotAttributes, "sa6", "SA6")
  1500  }
  1501  
  1502  func (s *plugSlotRulesSuite) TestCompileSlotRuleAllAllowDenyOrStanzas(c *C) {
  1503  	m, err := asserts.ParseHeaders([]byte(`iface:
  1504    allow-installation:
  1505      -
  1506        slot-attributes:
  1507          a1: A1
  1508      -
  1509        slot-attributes:
  1510          a1: A1alt
  1511    deny-installation:
  1512      -
  1513        slot-attributes:
  1514          a2: A2
  1515      -
  1516        slot-attributes:
  1517          a2: A2alt
  1518    allow-connection:
  1519      -
  1520        plug-attributes:
  1521          pa3: PA3
  1522        slot-attributes:
  1523          sa3: SA3
  1524      -
  1525        slot-attributes:
  1526          sa3: SA3alt
  1527    deny-connection:
  1528      -
  1529        plug-attributes:
  1530          pa4: PA4
  1531        slot-attributes:
  1532          sa4: SA4
  1533      -
  1534        slot-attributes:
  1535          sa4: SA4alt
  1536    allow-auto-connection:
  1537      -
  1538        plug-attributes:
  1539          pa5: PA5
  1540        slot-attributes:
  1541          sa5: SA5
  1542      -
  1543        slot-attributes:
  1544          sa5: SA5alt
  1545    deny-auto-connection:
  1546      -
  1547        plug-attributes:
  1548          pa6: PA6
  1549        slot-attributes:
  1550          sa6: SA6
  1551      -
  1552        slot-attributes:
  1553          sa6: SA6alt`))
  1554  	c.Assert(err, IsNil)
  1555  
  1556  	rule, err := asserts.CompileSlotRule("iface", m["iface"].(map[string]interface{}))
  1557  	c.Assert(err, IsNil)
  1558  
  1559  	c.Check(rule.Interface, Equals, "iface")
  1560  	// install subrules
  1561  	c.Assert(rule.AllowInstallation, HasLen, 2)
  1562  	checkAttrs(c, rule.AllowInstallation[0].SlotAttributes, "a1", "A1")
  1563  	checkAttrs(c, rule.AllowInstallation[1].SlotAttributes, "a1", "A1alt")
  1564  	c.Assert(rule.DenyInstallation, HasLen, 2)
  1565  	checkAttrs(c, rule.DenyInstallation[0].SlotAttributes, "a2", "A2")
  1566  	checkAttrs(c, rule.DenyInstallation[1].SlotAttributes, "a2", "A2alt")
  1567  	// connection subrules
  1568  	c.Assert(rule.AllowConnection, HasLen, 2)
  1569  	checkAttrs(c, rule.AllowConnection[0].PlugAttributes, "pa3", "PA3")
  1570  	checkAttrs(c, rule.AllowConnection[0].SlotAttributes, "sa3", "SA3")
  1571  	checkAttrs(c, rule.AllowConnection[1].SlotAttributes, "sa3", "SA3alt")
  1572  	c.Assert(rule.DenyConnection, HasLen, 2)
  1573  	checkAttrs(c, rule.DenyConnection[0].PlugAttributes, "pa4", "PA4")
  1574  	checkAttrs(c, rule.DenyConnection[0].SlotAttributes, "sa4", "SA4")
  1575  	checkAttrs(c, rule.DenyConnection[1].SlotAttributes, "sa4", "SA4alt")
  1576  	// auto-connection subrules
  1577  	c.Assert(rule.AllowAutoConnection, HasLen, 2)
  1578  	checkAttrs(c, rule.AllowAutoConnection[0].PlugAttributes, "pa5", "PA5")
  1579  	checkAttrs(c, rule.AllowAutoConnection[0].SlotAttributes, "sa5", "SA5")
  1580  	checkAttrs(c, rule.AllowAutoConnection[1].SlotAttributes, "sa5", "SA5alt")
  1581  	c.Assert(rule.DenyAutoConnection, HasLen, 2)
  1582  	checkAttrs(c, rule.DenyAutoConnection[0].PlugAttributes, "pa6", "PA6")
  1583  	checkAttrs(c, rule.DenyAutoConnection[0].SlotAttributes, "sa6", "SA6")
  1584  	checkAttrs(c, rule.DenyAutoConnection[1].SlotAttributes, "sa6", "SA6alt")
  1585  }
  1586  
  1587  func (s *plugSlotRulesSuite) TestCompileSlotRuleShortcutTrue(c *C) {
  1588  	rule, err := asserts.CompileSlotRule("iface", "true")
  1589  	c.Assert(err, IsNil)
  1590  
  1591  	c.Check(rule.Interface, Equals, "iface")
  1592  	// install subrules
  1593  	c.Assert(rule.AllowInstallation, HasLen, 1)
  1594  	c.Check(rule.AllowInstallation[0].SlotAttributes, Equals, asserts.AlwaysMatchAttributes)
  1595  	c.Assert(rule.DenyInstallation, HasLen, 1)
  1596  	c.Check(rule.DenyInstallation[0].SlotAttributes, Equals, asserts.NeverMatchAttributes)
  1597  	// connection subrules
  1598  	checkBoolSlotConnConstraints(c, "allow-connection", rule.AllowConnection, true)
  1599  	checkBoolSlotConnConstraints(c, "deny-connection", rule.DenyConnection, false)
  1600  	// auto-connection subrules
  1601  	checkBoolSlotConnConstraints(c, "allow-auto-connection", rule.AllowAutoConnection, true)
  1602  	checkBoolSlotConnConstraints(c, "deny-auto-connection", rule.DenyAutoConnection, false)
  1603  }
  1604  
  1605  func (s *plugSlotRulesSuite) TestCompileSlotRuleShortcutFalse(c *C) {
  1606  	rule, err := asserts.CompileSlotRule("iface", "false")
  1607  	c.Assert(err, IsNil)
  1608  
  1609  	// install subrules
  1610  	c.Assert(rule.AllowInstallation, HasLen, 1)
  1611  	c.Check(rule.AllowInstallation[0].SlotAttributes, Equals, asserts.NeverMatchAttributes)
  1612  	c.Assert(rule.DenyInstallation, HasLen, 1)
  1613  	c.Check(rule.DenyInstallation[0].SlotAttributes, Equals, asserts.AlwaysMatchAttributes)
  1614  	// connection subrules
  1615  	checkBoolSlotConnConstraints(c, "allwo-connection", rule.AllowConnection, false)
  1616  	checkBoolSlotConnConstraints(c, "deny-connection", rule.DenyConnection, true)
  1617  	// auto-connection subrules
  1618  	checkBoolSlotConnConstraints(c, "allow-auto-connection", rule.AllowAutoConnection, false)
  1619  	checkBoolSlotConnConstraints(c, "deny-auto-connection", rule.DenyAutoConnection, true)
  1620  }
  1621  
  1622  func (s *plugSlotRulesSuite) TestCompileSlotRuleDefaults(c *C) {
  1623  	rule, err := asserts.CompileSlotRule("iface", map[string]interface{}{
  1624  		"deny-auto-connection": "true",
  1625  	})
  1626  	c.Assert(err, IsNil)
  1627  
  1628  	// everything follows the defaults...
  1629  
  1630  	// install subrules
  1631  	c.Assert(rule.AllowInstallation, HasLen, 1)
  1632  	c.Check(rule.AllowInstallation[0].SlotAttributes, Equals, asserts.AlwaysMatchAttributes)
  1633  	c.Assert(rule.DenyInstallation, HasLen, 1)
  1634  	c.Check(rule.DenyInstallation[0].SlotAttributes, Equals, asserts.NeverMatchAttributes)
  1635  	// connection subrules
  1636  	checkBoolSlotConnConstraints(c, "allow-connection", rule.AllowConnection, true)
  1637  	checkBoolSlotConnConstraints(c, "deny-connection", rule.DenyConnection, false)
  1638  	// auto-connection subrules
  1639  	checkBoolSlotConnConstraints(c, "allow-auto-connection", rule.AllowAutoConnection, true)
  1640  	// ... but deny-auto-connection is on
  1641  	checkBoolSlotConnConstraints(c, "deny-auto-connection", rule.DenyAutoConnection, true)
  1642  }
  1643  
  1644  func (s *plugSlotRulesSuite) TestCompileSlotRuleInstallationConstraintsIDConstraints(c *C) {
  1645  	rule, err := asserts.CompileSlotRule("iface", map[string]interface{}{
  1646  		"allow-installation": map[string]interface{}{
  1647  			"slot-snap-type": []interface{}{"core", "kernel", "gadget", "app"},
  1648  		},
  1649  	})
  1650  	c.Assert(err, IsNil)
  1651  
  1652  	c.Assert(rule.AllowInstallation, HasLen, 1)
  1653  	cstrs := rule.AllowInstallation[0]
  1654  	c.Check(cstrs.SlotSnapTypes, DeepEquals, []string{"core", "kernel", "gadget", "app"})
  1655  }
  1656  
  1657  func (s *plugSlotRulesSuite) TestCompileSlotRuleInstallationConstraintsOnClassic(c *C) {
  1658  	m, err := asserts.ParseHeaders([]byte(`iface:
  1659    allow-installation: true`))
  1660  	c.Assert(err, IsNil)
  1661  
  1662  	rule, err := asserts.CompileSlotRule("iface", m["iface"].(map[string]interface{}))
  1663  	c.Assert(err, IsNil)
  1664  
  1665  	c.Check(rule.AllowInstallation[0].OnClassic, IsNil)
  1666  
  1667  	m, err = asserts.ParseHeaders([]byte(`iface:
  1668    allow-installation:
  1669      on-classic: false`))
  1670  	c.Assert(err, IsNil)
  1671  
  1672  	rule, err = asserts.CompileSlotRule("iface", m["iface"].(map[string]interface{}))
  1673  	c.Assert(err, IsNil)
  1674  
  1675  	c.Check(rule.AllowInstallation[0].OnClassic, DeepEquals, &asserts.OnClassicConstraint{})
  1676  
  1677  	m, err = asserts.ParseHeaders([]byte(`iface:
  1678    allow-installation:
  1679      on-classic: true`))
  1680  	c.Assert(err, IsNil)
  1681  
  1682  	rule, err = asserts.CompileSlotRule("iface", m["iface"].(map[string]interface{}))
  1683  	c.Assert(err, IsNil)
  1684  
  1685  	c.Check(rule.AllowInstallation[0].OnClassic, DeepEquals, &asserts.OnClassicConstraint{Classic: true})
  1686  
  1687  	m, err = asserts.ParseHeaders([]byte(`iface:
  1688    allow-installation:
  1689      on-classic:
  1690        - ubuntu
  1691        - debian`))
  1692  	c.Assert(err, IsNil)
  1693  
  1694  	rule, err = asserts.CompileSlotRule("iface", m["iface"].(map[string]interface{}))
  1695  	c.Assert(err, IsNil)
  1696  
  1697  	c.Check(rule.AllowInstallation[0].OnClassic, DeepEquals, &asserts.OnClassicConstraint{Classic: true, SystemIDs: []string{"ubuntu", "debian"}})
  1698  }
  1699  
  1700  func (s *plugSlotRulesSuite) TestCompileSlotRuleInstallationConstraintsDeviceScope(c *C) {
  1701  	m, err := asserts.ParseHeaders([]byte(`iface:
  1702    allow-installation: true`))
  1703  	c.Assert(err, IsNil)
  1704  
  1705  	rule, err := asserts.CompileSlotRule("iface", m["iface"].(map[string]interface{}))
  1706  	c.Assert(err, IsNil)
  1707  
  1708  	c.Check(rule.AllowInstallation[0].DeviceScope, IsNil)
  1709  
  1710  	tests := []struct {
  1711  		rule     string
  1712  		expected asserts.DeviceScopeConstraint
  1713  	}{
  1714  		{`iface:
  1715    allow-installation:
  1716      on-store:
  1717        - my-store`, asserts.DeviceScopeConstraint{Store: []string{"my-store"}}},
  1718  		{`iface:
  1719    allow-installation:
  1720      on-store:
  1721        - my-store
  1722        - other-store`, asserts.DeviceScopeConstraint{Store: []string{"my-store", "other-store"}}},
  1723  		{`iface:
  1724    allow-installation:
  1725      on-brand:
  1726        - my-brand
  1727        - s9zGdwb16ysLeRW6nRivwZS5r9puP8JT`, asserts.DeviceScopeConstraint{Brand: []string{"my-brand", "s9zGdwb16ysLeRW6nRivwZS5r9puP8JT"}}},
  1728  		{`iface:
  1729    allow-installation:
  1730      on-model:
  1731        - my-brand/bar
  1732        - s9zGdwb16ysLeRW6nRivwZS5r9puP8JT/baz`, asserts.DeviceScopeConstraint{Model: []string{"my-brand/bar", "s9zGdwb16ysLeRW6nRivwZS5r9puP8JT/baz"}}},
  1733  		{`iface:
  1734    allow-installation:
  1735      on-store:
  1736        - store1
  1737        - store2
  1738      on-brand:
  1739        - my-brand
  1740      on-model:
  1741        - my-brand/bar
  1742        - s9zGdwb16ysLeRW6nRivwZS5r9puP8JT/baz`, asserts.DeviceScopeConstraint{
  1743  			Store: []string{"store1", "store2"},
  1744  			Brand: []string{"my-brand"},
  1745  			Model: []string{"my-brand/bar", "s9zGdwb16ysLeRW6nRivwZS5r9puP8JT/baz"}}},
  1746  	}
  1747  
  1748  	for _, t := range tests {
  1749  		m, err = asserts.ParseHeaders([]byte(t.rule))
  1750  		c.Assert(err, IsNil)
  1751  
  1752  		rule, err = asserts.CompileSlotRule("iface", m["iface"].(map[string]interface{}))
  1753  		c.Assert(err, IsNil)
  1754  
  1755  		c.Check(rule.AllowInstallation[0].DeviceScope, DeepEquals, &t.expected)
  1756  	}
  1757  }
  1758  
  1759  func (s *plugSlotRulesSuite) TestCompileSlotRuleInstallationConstraintsSlotNames(c *C) {
  1760  	m, err := asserts.ParseHeaders([]byte(`iface:
  1761    allow-installation: true`))
  1762  	c.Assert(err, IsNil)
  1763  
  1764  	rule, err := asserts.CompileSlotRule("iface", m["iface"].(map[string]interface{}))
  1765  	c.Assert(err, IsNil)
  1766  
  1767  	c.Check(rule.AllowInstallation[0].SlotNames, IsNil)
  1768  
  1769  	tests := []struct {
  1770  		rule        string
  1771  		matching    []string
  1772  		notMatching []string
  1773  	}{
  1774  		{`iface:
  1775    allow-installation:
  1776      slot-names:
  1777        - foo`, []string{"foo"}, []string{"bar"}},
  1778  		{`iface:
  1779    allow-installation:
  1780      slot-names:
  1781        - foo
  1782        - bar`, []string{"foo", "bar"}, []string{"baz"}},
  1783  		{`iface:
  1784    allow-installation:
  1785      slot-names:
  1786        - foo[0-9]
  1787        - bar`, []string{"foo0", "foo1", "bar"}, []string{"baz", "fooo", "foo12"}},
  1788  	}
  1789  	for _, t := range tests {
  1790  		m, err = asserts.ParseHeaders([]byte(t.rule))
  1791  		c.Assert(err, IsNil)
  1792  
  1793  		rule, err = asserts.CompileSlotRule("iface", m["iface"].(map[string]interface{}))
  1794  		c.Assert(err, IsNil)
  1795  
  1796  		for _, matching := range t.matching {
  1797  			c.Check(rule.AllowInstallation[0].SlotNames.Check("slot name", matching, nil), IsNil)
  1798  		}
  1799  		for _, notMatching := range t.notMatching {
  1800  			c.Check(rule.AllowInstallation[0].SlotNames.Check("slot name", notMatching, nil), NotNil)
  1801  		}
  1802  	}
  1803  }
  1804  
  1805  func (s *plugSlotRulesSuite) TestCompileSlotRuleConnectionConstraintsIDConstraints(c *C) {
  1806  	rule, err := asserts.CompileSlotRule("iface", map[string]interface{}{
  1807  		"allow-connection": map[string]interface{}{
  1808  			"plug-snap-type":    []interface{}{"core", "kernel", "gadget", "app"},
  1809  			"plug-snap-id":      []interface{}{"snapidsnapidsnapidsnapidsnapid01", "snapidsnapidsnapidsnapidsnapid02"},
  1810  			"plug-publisher-id": []interface{}{"pubidpubidpubidpubidpubidpubid09", "canonical", "$SAME"},
  1811  		},
  1812  	})
  1813  	c.Assert(err, IsNil)
  1814  
  1815  	c.Assert(rule.AllowConnection, HasLen, 1)
  1816  	cstrs := rule.AllowConnection[0]
  1817  	c.Check(cstrs.PlugSnapTypes, DeepEquals, []string{"core", "kernel", "gadget", "app"})
  1818  	c.Check(cstrs.PlugSnapIDs, DeepEquals, []string{"snapidsnapidsnapidsnapidsnapid01", "snapidsnapidsnapidsnapidsnapid02"})
  1819  	c.Check(cstrs.PlugPublisherIDs, DeepEquals, []string{"pubidpubidpubidpubidpubidpubid09", "canonical", "$SAME"})
  1820  }
  1821  
  1822  func (s *plugSlotRulesSuite) TestCompileSlotRuleConnectionConstraintsOnClassic(c *C) {
  1823  	m, err := asserts.ParseHeaders([]byte(`iface:
  1824    allow-connection: true`))
  1825  	c.Assert(err, IsNil)
  1826  
  1827  	rule, err := asserts.CompileSlotRule("iface", m["iface"].(map[string]interface{}))
  1828  	c.Assert(err, IsNil)
  1829  
  1830  	c.Check(rule.AllowConnection[0].OnClassic, IsNil)
  1831  
  1832  	m, err = asserts.ParseHeaders([]byte(`iface:
  1833    allow-connection:
  1834      on-classic: false`))
  1835  	c.Assert(err, IsNil)
  1836  
  1837  	rule, err = asserts.CompileSlotRule("iface", m["iface"].(map[string]interface{}))
  1838  	c.Assert(err, IsNil)
  1839  
  1840  	c.Check(rule.AllowConnection[0].OnClassic, DeepEquals, &asserts.OnClassicConstraint{})
  1841  
  1842  	m, err = asserts.ParseHeaders([]byte(`iface:
  1843    allow-connection:
  1844      on-classic: true`))
  1845  	c.Assert(err, IsNil)
  1846  
  1847  	rule, err = asserts.CompileSlotRule("iface", m["iface"].(map[string]interface{}))
  1848  	c.Assert(err, IsNil)
  1849  
  1850  	c.Check(rule.AllowConnection[0].OnClassic, DeepEquals, &asserts.OnClassicConstraint{Classic: true})
  1851  
  1852  	m, err = asserts.ParseHeaders([]byte(`iface:
  1853    allow-connection:
  1854      on-classic:
  1855        - ubuntu
  1856        - debian`))
  1857  	c.Assert(err, IsNil)
  1858  
  1859  	rule, err = asserts.CompileSlotRule("iface", m["iface"].(map[string]interface{}))
  1860  	c.Assert(err, IsNil)
  1861  
  1862  	c.Check(rule.AllowConnection[0].OnClassic, DeepEquals, &asserts.OnClassicConstraint{Classic: true, SystemIDs: []string{"ubuntu", "debian"}})
  1863  }
  1864  
  1865  func (s *plugSlotRulesSuite) TestCompileSlotRuleConnectionConstraintsDeviceScope(c *C) {
  1866  	m, err := asserts.ParseHeaders([]byte(`iface:
  1867    allow-connection: true`))
  1868  	c.Assert(err, IsNil)
  1869  
  1870  	rule, err := asserts.CompileSlotRule("iface", m["iface"].(map[string]interface{}))
  1871  	c.Assert(err, IsNil)
  1872  
  1873  	c.Check(rule.AllowConnection[0].DeviceScope, IsNil)
  1874  
  1875  	tests := []struct {
  1876  		rule     string
  1877  		expected asserts.DeviceScopeConstraint
  1878  	}{
  1879  		{`iface:
  1880    allow-connection:
  1881      on-store:
  1882        - my-store`, asserts.DeviceScopeConstraint{Store: []string{"my-store"}}},
  1883  		{`iface:
  1884    allow-connection:
  1885      on-store:
  1886        - my-store
  1887        - other-store`, asserts.DeviceScopeConstraint{Store: []string{"my-store", "other-store"}}},
  1888  		{`iface:
  1889    allow-connection:
  1890      on-brand:
  1891        - my-brand
  1892        - s9zGdwb16ysLeRW6nRivwZS5r9puP8JT`, asserts.DeviceScopeConstraint{Brand: []string{"my-brand", "s9zGdwb16ysLeRW6nRivwZS5r9puP8JT"}}},
  1893  		{`iface:
  1894    allow-connection:
  1895      on-model:
  1896        - my-brand/bar
  1897        - s9zGdwb16ysLeRW6nRivwZS5r9puP8JT/baz`, asserts.DeviceScopeConstraint{Model: []string{"my-brand/bar", "s9zGdwb16ysLeRW6nRivwZS5r9puP8JT/baz"}}},
  1898  		{`iface:
  1899    allow-connection:
  1900      on-store:
  1901        - store1
  1902        - store2
  1903      on-brand:
  1904        - my-brand
  1905      on-model:
  1906        - my-brand/bar
  1907        - s9zGdwb16ysLeRW6nRivwZS5r9puP8JT/baz`, asserts.DeviceScopeConstraint{
  1908  			Store: []string{"store1", "store2"},
  1909  			Brand: []string{"my-brand"},
  1910  			Model: []string{"my-brand/bar", "s9zGdwb16ysLeRW6nRivwZS5r9puP8JT/baz"}}},
  1911  	}
  1912  
  1913  	for _, t := range tests {
  1914  		m, err = asserts.ParseHeaders([]byte(t.rule))
  1915  		c.Assert(err, IsNil)
  1916  
  1917  		rule, err = asserts.CompileSlotRule("iface", m["iface"].(map[string]interface{}))
  1918  		c.Assert(err, IsNil)
  1919  
  1920  		c.Check(rule.AllowConnection[0].DeviceScope, DeepEquals, &t.expected)
  1921  	}
  1922  }
  1923  
  1924  func (s *plugSlotRulesSuite) TestCompileSlotRuleConnectionConstraintsPlugNamesSlotNames(c *C) {
  1925  	m, err := asserts.ParseHeaders([]byte(`iface:
  1926    allow-connection: true`))
  1927  	c.Assert(err, IsNil)
  1928  
  1929  	rule, err := asserts.CompileSlotRule("iface", m["iface"].(map[string]interface{}))
  1930  	c.Assert(err, IsNil)
  1931  
  1932  	c.Check(rule.AllowConnection[0].PlugNames, IsNil)
  1933  	c.Check(rule.AllowConnection[0].SlotNames, IsNil)
  1934  
  1935  	tests := []struct {
  1936  		rule        string
  1937  		matching    []string
  1938  		notMatching []string
  1939  	}{
  1940  		{`iface:
  1941    allow-connection:
  1942      plug-names:
  1943        - Pfoo
  1944      slot-names:
  1945        - Sfoo`, []string{"foo"}, []string{"bar"}},
  1946  		{`iface:
  1947    allow-connection:
  1948      plug-names:
  1949        - Pfoo
  1950        - Pbar
  1951      slot-names:
  1952        - Sfoo
  1953        - Sbar`, []string{"foo", "bar"}, []string{"baz"}},
  1954  		{`iface:
  1955    allow-connection:
  1956      plug-names:
  1957        - Pfoo[0-9]
  1958        - Pbar
  1959      slot-names:
  1960        - Sfoo[0-9]
  1961        - Sbar`, []string{"foo0", "foo1", "bar"}, []string{"baz", "fooo", "foo12"}},
  1962  	}
  1963  	for _, t := range tests {
  1964  		m, err = asserts.ParseHeaders([]byte(t.rule))
  1965  		c.Assert(err, IsNil)
  1966  
  1967  		rule, err = asserts.CompileSlotRule("iface", m["iface"].(map[string]interface{}))
  1968  		c.Assert(err, IsNil)
  1969  
  1970  		for _, matching := range t.matching {
  1971  			c.Check(rule.AllowConnection[0].PlugNames.Check("plug name", "P"+matching, nil), IsNil)
  1972  
  1973  			c.Check(rule.AllowConnection[0].SlotNames.Check("slot name", "S"+matching, nil), IsNil)
  1974  		}
  1975  
  1976  		for _, notMatching := range t.notMatching {
  1977  			c.Check(rule.AllowConnection[0].SlotNames.Check("plug name", "P"+notMatching, nil), NotNil)
  1978  
  1979  			c.Check(rule.AllowConnection[0].SlotNames.Check("slot name", "S"+notMatching, nil), NotNil)
  1980  		}
  1981  	}
  1982  }
  1983  
  1984  func (s *plugSlotRulesSuite) TestCompileSlotRuleConnectionConstraintsSideArityConstraints(c *C) {
  1985  	m, err := asserts.ParseHeaders([]byte(`iface:
  1986    allow-auto-connection: true`))
  1987  	c.Assert(err, IsNil)
  1988  
  1989  	rule, err := asserts.CompileSlotRule("iface", m["iface"].(map[string]interface{}))
  1990  	c.Assert(err, IsNil)
  1991  
  1992  	// defaults
  1993  	c.Check(rule.AllowAutoConnection[0].SlotsPerPlug, Equals, asserts.SideArityConstraint{N: 1})
  1994  	c.Check(rule.AllowAutoConnection[0].PlugsPerSlot.Any(), Equals, true)
  1995  
  1996  	c.Check(rule.AllowConnection[0].SlotsPerPlug.Any(), Equals, true)
  1997  	c.Check(rule.AllowConnection[0].PlugsPerSlot.Any(), Equals, true)
  1998  
  1999  	// test that the arity constraints get normalized away to any
  2000  	// under allow-connection
  2001  	// see https://forum.snapcraft.io/t/plug-slot-declaration-rules-greedy-plugs/12438
  2002  	allowConnTests := []string{
  2003  		`iface:
  2004    allow-connection:
  2005      slots-per-plug: 1
  2006      plugs-per-slot: 2`,
  2007  		`iface:
  2008    allow-connection:
  2009      slots-per-plug: *
  2010      plugs-per-slot: 1`,
  2011  		`iface:
  2012    allow-connection:
  2013      slots-per-plug: 2
  2014      plugs-per-slot: *`,
  2015  	}
  2016  
  2017  	for _, t := range allowConnTests {
  2018  		m, err = asserts.ParseHeaders([]byte(t))
  2019  		c.Assert(err, IsNil)
  2020  
  2021  		rule, err = asserts.CompileSlotRule("iface", m["iface"].(map[string]interface{}))
  2022  		c.Assert(err, IsNil)
  2023  
  2024  		c.Check(rule.AllowConnection[0].SlotsPerPlug.Any(), Equals, true)
  2025  		c.Check(rule.AllowConnection[0].PlugsPerSlot.Any(), Equals, true)
  2026  	}
  2027  
  2028  	// test that under allow-auto-connection:
  2029  	// slots-per-plug can be * (any) or otherwise gets normalized to 1
  2030  	// plugs-per-slot gets normalized to any (*)
  2031  	// see https://forum.snapcraft.io/t/plug-slot-declaration-rules-greedy-plugs/12438
  2032  	allowAutoConnTests := []struct {
  2033  		rule         string
  2034  		slotsPerPlug asserts.SideArityConstraint
  2035  	}{
  2036  		{`iface:
  2037    allow-auto-connection:
  2038      slots-per-plug: 1
  2039      plugs-per-slot: 2`, sideArityOne},
  2040  		{`iface:
  2041    allow-auto-connection:
  2042      slots-per-plug: *
  2043      plugs-per-slot: 1`, sideArityAny},
  2044  		{`iface:
  2045    allow-auto-connection:
  2046      slots-per-plug: 2
  2047      plugs-per-slot: *`, sideArityOne},
  2048  	}
  2049  
  2050  	for _, t := range allowAutoConnTests {
  2051  		m, err = asserts.ParseHeaders([]byte(t.rule))
  2052  		c.Assert(err, IsNil)
  2053  
  2054  		rule, err = asserts.CompileSlotRule("iface", m["iface"].(map[string]interface{}))
  2055  		c.Assert(err, IsNil)
  2056  
  2057  		c.Check(rule.AllowAutoConnection[0].SlotsPerPlug, Equals, t.slotsPerPlug)
  2058  		c.Check(rule.AllowAutoConnection[0].PlugsPerSlot.Any(), Equals, true)
  2059  	}
  2060  }
  2061  
  2062  func (s *plugSlotRulesSuite) TestCompileSlotRuleErrors(c *C) {
  2063  	tests := []struct {
  2064  		stanza string
  2065  		err    string
  2066  	}{
  2067  		{`iface: foo`, `slot rule for interface "iface" must be a map or one of the shortcuts 'true' or 'false'`},
  2068  		{`iface:
  2069    - allow`, `slot rule for interface "iface" must be a map or one of the shortcuts 'true' or 'false'`},
  2070  		{`iface:
  2071    allow-installation: foo`, `allow-installation in slot rule for interface "iface" must be a map or one of the shortcuts 'true' or 'false'`},
  2072  		{`iface:
  2073    deny-installation: foo`, `deny-installation in slot rule for interface "iface" must be a map or one of the shortcuts 'true' or 'false'`},
  2074  		{`iface:
  2075    allow-connection: foo`, `allow-connection in slot rule for interface "iface" must be a map or one of the shortcuts 'true' or 'false'`},
  2076  		{`iface:
  2077    allow-connection:
  2078      - foo`, `alternative 1 of allow-connection in slot rule for interface "iface" must be a map`},
  2079  		{`iface:
  2080    allow-connection:
  2081      - true`, `alternative 1 of allow-connection in slot rule for interface "iface" must be a map`},
  2082  		{`iface:
  2083    allow-installation:
  2084      slot-attributes:
  2085        a1: [`, `cannot compile slot-attributes in allow-installation in slot rule for interface "iface": cannot compile "a1" constraint .*`},
  2086  		{`iface:
  2087    allow-connection:
  2088      plug-attributes:
  2089        a2: [`, `cannot compile plug-attributes in allow-connection in slot rule for interface "iface": cannot compile "a2" constraint .*`},
  2090  		{`iface:
  2091    allow-connection:
  2092      plug-snap-id:
  2093        -
  2094          foo: 1`, `plug-snap-id in allow-connection in slot rule for interface "iface" must be a list of strings`},
  2095  		{`iface:
  2096    allow-connection:
  2097      plug-snap-id:
  2098        - foo`, `plug-snap-id in allow-connection in slot rule for interface "iface" contains an invalid element: "foo"`},
  2099  		{`iface:
  2100    allow-connection:
  2101      plug-snap-type:
  2102        - foo`, `plug-snap-type in allow-connection in slot rule for interface "iface" contains an invalid element: "foo"`},
  2103  		{`iface:
  2104    allow-connection:
  2105      plug-snap-type:
  2106        - xapp`, `plug-snap-type in allow-connection in slot rule for interface "iface" contains an invalid element: "xapp"`},
  2107  		{`iface:
  2108    allow-connection:
  2109      on-classic:
  2110        x: 1`, `on-classic in allow-connection in slot rule for interface \"iface\" must be 'true', 'false' or a list of operating system IDs`},
  2111  		{`iface:
  2112    allow-connection:
  2113      on-classic:
  2114        - zoom!`, `on-classic in allow-connection in slot rule for interface \"iface\" contains an invalid element: \"zoom!\"`},
  2115  		{`iface:
  2116    allow-connection:
  2117      plug-snap-ids:
  2118        - foo`, `allow-connection in slot rule for interface "iface" must specify at least one of plug-names, slot-names, plug-attributes, slot-attributes, plug-snap-type, plug-publisher-id, plug-snap-id, slots-per-plug, plugs-per-slot, on-classic, on-store, on-brand, on-model`},
  2119  		{`iface:
  2120    deny-connection:
  2121      plug-snap-ids:
  2122        - foo`, `deny-connection in slot rule for interface "iface" must specify at least one of plug-names, slot-names, plug-attributes, slot-attributes, plug-snap-type, plug-publisher-id, plug-snap-id, slots-per-plug, plugs-per-slot, on-classic, on-store, on-brand, on-model`},
  2123  		{`iface:
  2124    allow-auto-connection:
  2125      plug-snap-ids:
  2126        - foo`, `allow-auto-connection in slot rule for interface "iface" must specify at least one of plug-names, slot-names, plug-attributes, slot-attributes, plug-snap-type, plug-publisher-id, plug-snap-id, slots-per-plug, plugs-per-slot, on-classic, on-store, on-brand, on-model`},
  2127  		{`iface:
  2128    deny-auto-connection:
  2129      plug-snap-ids:
  2130        - foo`, `deny-auto-connection in slot rule for interface "iface" must specify at least one of plug-names, slot-names, plug-attributes, slot-attributes, plug-snap-type, plug-publisher-id, plug-snap-id, slots-per-plug, plugs-per-slot, on-classic, on-store, on-brand, on-model`},
  2131  		{`iface:
  2132    allow-connect: true`, `slot rule for interface "iface" must specify at least one of allow-installation, deny-installation, allow-connection, deny-connection, allow-auto-connection, deny-auto-connection`},
  2133  		{`iface:
  2134    allow-installation:
  2135      on-store: true`, `on-store in allow-installation in slot rule for interface \"iface\" must be a list of strings`},
  2136  		{`iface:
  2137    allow-installation:
  2138      on-store: store1`, `on-store in allow-installation in slot rule for interface \"iface\" must be a list of strings`},
  2139  		{`iface:
  2140    allow-installation:
  2141      on-store:
  2142        - zoom!`, `on-store in allow-installation in slot rule for interface \"iface\" contains an invalid element: \"zoom!\"`},
  2143  		{`iface:
  2144    allow-connection:
  2145      on-brand: true`, `on-brand in allow-connection in slot rule for interface \"iface\" must be a list of strings`},
  2146  		{`iface:
  2147    allow-connection:
  2148      on-brand: brand1`, `on-brand in allow-connection in slot rule for interface \"iface\" must be a list of strings`},
  2149  		{`iface:
  2150    allow-connection:
  2151      on-brand:
  2152        - zoom!`, `on-brand in allow-connection in slot rule for interface \"iface\" contains an invalid element: \"zoom!\"`},
  2153  		{`iface:
  2154    allow-auto-connection:
  2155      on-model: true`, `on-model in allow-auto-connection in slot rule for interface \"iface\" must be a list of strings`},
  2156  		{`iface:
  2157    allow-auto-connection:
  2158      on-model: foo/bar`, `on-model in allow-auto-connection in slot rule for interface \"iface\" must be a list of strings`},
  2159  		{`iface:
  2160    allow-auto-connection:
  2161      on-model:
  2162        - foo//bar`, `on-model in allow-auto-connection in slot rule for interface \"iface\" contains an invalid element: \"foo//bar"`},
  2163  		{`iface:
  2164    allow-installation:
  2165      slots-per-plug: 1`, `allow-installation in slot rule for interface "iface" cannot specify a slots-per-plug constraint, they apply only to allow-\*connection`},
  2166  		{`iface:
  2167    deny-auto-connection:
  2168      slots-per-plug: 1`, `deny-auto-connection in slot rule for interface "iface" cannot specify a slots-per-plug constraint, they apply only to allow-\*connection`},
  2169  		{`iface:
  2170    allow-auto-connection:
  2171      plugs-per-slot: any`, `plugs-per-slot in allow-auto-connection in slot rule for interface "iface" must be an integer >=1 or \*`},
  2172  		{`iface:
  2173    allow-auto-connection:
  2174      slots-per-plug: 00`, `slots-per-plug in allow-auto-connection in slot rule for interface "iface" has invalid prefix zeros: 00`},
  2175  		{`iface:
  2176    allow-auto-connection:
  2177      slots-per-plug: 99999999999999999999`, `slots-per-plug in allow-auto-connection in slot rule for interface "iface" is out of range: 99999999999999999999`},
  2178  		{`iface:
  2179    allow-auto-connection:
  2180      slots-per-plug: 0`, `slots-per-plug in allow-auto-connection in slot rule for interface "iface" must be an integer >=1 or \*`},
  2181  		{`iface:
  2182    allow-auto-connection:
  2183      slots-per-plug:
  2184        what: 1`, `slots-per-plug in allow-auto-connection in slot rule for interface "iface" must be an integer >=1 or \*`},
  2185  		{`iface:
  2186    allow-auto-connection:
  2187      plug-names: true`, `plug-names constraints must be a list of regexps and special \$ values`},
  2188  		{`iface:
  2189    allow-auto-connection:
  2190      slot-names: true`, `slot-names constraints must be a list of regexps and special \$ values`},
  2191  	}
  2192  
  2193  	for _, t := range tests {
  2194  		m, err := asserts.ParseHeaders([]byte(t.stanza))
  2195  		c.Assert(err, IsNil, Commentf(t.stanza))
  2196  		_, err = asserts.CompileSlotRule("iface", m["iface"])
  2197  		c.Check(err, ErrorMatches, t.err, Commentf(t.stanza))
  2198  	}
  2199  }
  2200  
  2201  func (s *plugSlotRulesSuite) TestSlotRuleFeatures(c *C) {
  2202  	combos := []struct {
  2203  		subrule             string
  2204  		constraintsPrefixes []string
  2205  	}{
  2206  		{"allow-installation", []string{"slot-"}},
  2207  		{"deny-installation", []string{"slot-"}},
  2208  		{"allow-connection", []string{"plug-", "slot-"}},
  2209  		{"deny-connection", []string{"plug-", "slot-"}},
  2210  		{"allow-auto-connection", []string{"plug-", "slot-"}},
  2211  		{"deny-auto-connection", []string{"plug-", "slot-"}},
  2212  	}
  2213  
  2214  	for _, combo := range combos {
  2215  		for _, attrConstrPrefix := range combo.constraintsPrefixes {
  2216  			attrConstraintMap := map[string]interface{}{
  2217  				"a": "ATTR",
  2218  			}
  2219  			ruleMap := map[string]interface{}{
  2220  				combo.subrule: map[string]interface{}{
  2221  					attrConstrPrefix + "attributes": attrConstraintMap,
  2222  				},
  2223  			}
  2224  
  2225  			rule, err := asserts.CompileSlotRule("iface", ruleMap)
  2226  			c.Assert(err, IsNil)
  2227  			c.Check(asserts.RuleFeature(rule, "dollar-attr-constraints"), Equals, false, Commentf("%v", ruleMap))
  2228  
  2229  			c.Check(asserts.RuleFeature(rule, "device-scope-constraints"), Equals, false, Commentf("%v", ruleMap))
  2230  
  2231  			attrConstraintMap["a"] = "$PLUG(a)"
  2232  			rule, err = asserts.CompileSlotRule("iface", ruleMap)
  2233  			c.Assert(err, IsNil)
  2234  			c.Check(asserts.RuleFeature(rule, "dollar-attr-constraints"), Equals, true, Commentf("%v", ruleMap))
  2235  
  2236  			c.Check(asserts.RuleFeature(rule, "device-scope-constraints"), Equals, false, Commentf("%v", ruleMap))
  2237  		}
  2238  
  2239  		for deviceScopeConstr, value := range deviceScopeConstrs {
  2240  			ruleMap := map[string]interface{}{
  2241  				combo.subrule: map[string]interface{}{
  2242  					deviceScopeConstr: value,
  2243  				},
  2244  			}
  2245  
  2246  			rule, err := asserts.CompileSlotRule("iface", ruleMap)
  2247  			c.Assert(err, IsNil)
  2248  			c.Check(asserts.RuleFeature(rule, "device-scope-constraints"), Equals, true, Commentf("%v", ruleMap))
  2249  		}
  2250  
  2251  		for _, nameConstrPrefix := range combo.constraintsPrefixes {
  2252  			ruleMap := map[string]interface{}{
  2253  				combo.subrule: map[string]interface{}{
  2254  					nameConstrPrefix + "names": []interface{}{"foo"},
  2255  				},
  2256  			}
  2257  
  2258  			rule, err := asserts.CompileSlotRule("iface", ruleMap)
  2259  			c.Assert(err, IsNil)
  2260  			c.Check(asserts.RuleFeature(rule, "name-constraints"), Equals, true, Commentf("%v", ruleMap))
  2261  		}
  2262  
  2263  	}
  2264  }
  2265  
  2266  func (s *plugSlotRulesSuite) TestValidOnStoreBrandModel(c *C) {
  2267  	tests := []struct {
  2268  		constr string
  2269  		value  string
  2270  		valid  bool
  2271  	}{
  2272  		{"on-store", "", false},
  2273  		{"on-store", "foo", true},
  2274  		{"on-store", "F_o-O88", true},
  2275  		{"on-store", "foo!", false},
  2276  		{"on-store", "foo.", false},
  2277  		{"on-store", "foo/", false},
  2278  		{"on-brand", "", false},
  2279  		// custom set brands (length 2-28)
  2280  		{"on-brand", "dwell", true},
  2281  		{"on-brand", "Dwell", false},
  2282  		{"on-brand", "dwell-88", true},
  2283  		{"on-brand", "dwell_88", false},
  2284  		{"on-brand", "dwell.88", false},
  2285  		{"on-brand", "dwell:88", false},
  2286  		{"on-brand", "dwell!88", false},
  2287  		{"on-brand", "a", false},
  2288  		{"on-brand", "ab", true},
  2289  		{"on-brand", "0123456789012345678901234567", true},
  2290  		// snappy id brands (fixed length 32)
  2291  		{"on-brand", "01234567890123456789012345678", false},
  2292  		{"on-brand", "012345678901234567890123456789", false},
  2293  		{"on-brand", "0123456789012345678901234567890", false},
  2294  		{"on-brand", "01234567890123456789012345678901", true},
  2295  		{"on-brand", "abcdefghijklmnopqrstuvwxyz678901", true},
  2296  		{"on-brand", "ABCDEFGHIJKLMNOPQRSTUVWCYZ678901", true},
  2297  		{"on-brand", "ABCDEFGHIJKLMNOPQRSTUVWCYZ678901X", false},
  2298  		{"on-brand", "ABCDEFGHIJKLMNOPQ!STUVWCYZ678901", false},
  2299  		{"on-brand", "ABCDEFGHIJKLMNOPQ_STUVWCYZ678901", false},
  2300  		{"on-brand", "ABCDEFGHIJKLMNOPQ-STUVWCYZ678901", false},
  2301  		{"on-model", "", false},
  2302  		{"on-model", "/", false},
  2303  		{"on-model", "dwell/dwell1", true},
  2304  		{"on-model", "dwell", false},
  2305  		{"on-model", "dwell/", false},
  2306  		{"on-model", "dwell//dwell1", false},
  2307  		{"on-model", "dwell/-dwell1", false},
  2308  		{"on-model", "dwell/dwell1-", false},
  2309  		{"on-model", "dwell/dwell1-23", true},
  2310  		{"on-model", "dwell/dwell1!", false},
  2311  		{"on-model", "dwell/dwe_ll1", false},
  2312  		{"on-model", "dwell/dwe.ll1", false},
  2313  	}
  2314  
  2315  	check := func(constr, value string, valid bool) {
  2316  		ruleMap := map[string]interface{}{
  2317  			"allow-auto-connection": map[string]interface{}{
  2318  				constr: []interface{}{value},
  2319  			},
  2320  		}
  2321  
  2322  		_, err := asserts.CompilePlugRule("iface", ruleMap)
  2323  		if valid {
  2324  			c.Check(err, IsNil, Commentf("%v", ruleMap))
  2325  		} else {
  2326  			c.Check(err, ErrorMatches, fmt.Sprintf(`%s in allow-auto-connection in plug rule for interface "iface" contains an invalid element: %q`, constr, value), Commentf("%v", ruleMap))
  2327  		}
  2328  	}
  2329  
  2330  	for _, t := range tests {
  2331  		check(t.constr, t.value, t.valid)
  2332  
  2333  		if t.constr == "on-brand" {
  2334  			// reuse and double check all brands also in the context of on-model!
  2335  
  2336  			check("on-model", t.value+"/foo", t.valid)
  2337  		}
  2338  	}
  2339  }