gopkg.in/ubuntu-core/snappy.v0@v0.0.0-20210902073436-25a8614f10a6/asserts/ifacedecls_test.go (about)

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