gitee.com/mysnapcore/mysnapd@v0.1.0/asserts/ifacedecls_test.go (about)

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