github.com/kubiko/snapd@v0.0.0-20201013125620-d4f3094d9ddf/interfaces/builtin/serial_port_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2016 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 builtin_test
    21  
    22  import (
    23  	. "gopkg.in/check.v1"
    24  
    25  	"github.com/snapcore/snapd/interfaces"
    26  	"github.com/snapcore/snapd/interfaces/apparmor"
    27  	"github.com/snapcore/snapd/interfaces/builtin"
    28  	"github.com/snapcore/snapd/interfaces/hotplug"
    29  	"github.com/snapcore/snapd/interfaces/udev"
    30  	"github.com/snapcore/snapd/snap"
    31  	"github.com/snapcore/snapd/snap/snaptest"
    32  	"github.com/snapcore/snapd/testutil"
    33  )
    34  
    35  type SerialPortInterfaceSuite struct {
    36  	testutil.BaseTest
    37  	iface interfaces.Interface
    38  
    39  	// OS Snap
    40  	testSlot1            *interfaces.ConnectedSlot
    41  	testSlot1Info        *snap.SlotInfo
    42  	testSlot2            *interfaces.ConnectedSlot
    43  	testSlot2Info        *snap.SlotInfo
    44  	testSlot3            *interfaces.ConnectedSlot
    45  	testSlot3Info        *snap.SlotInfo
    46  	testSlot4            *interfaces.ConnectedSlot
    47  	testSlot4Info        *snap.SlotInfo
    48  	testSlot5            *interfaces.ConnectedSlot
    49  	testSlot5Info        *snap.SlotInfo
    50  	testSlot6            *interfaces.ConnectedSlot
    51  	testSlot6Info        *snap.SlotInfo
    52  	testSlot7            *interfaces.ConnectedSlot
    53  	testSlot7Info        *snap.SlotInfo
    54  	testSlot8            *interfaces.ConnectedSlot
    55  	testSlot8Info        *snap.SlotInfo
    56  	testSlot9            *interfaces.ConnectedSlot
    57  	testSlot9Info        *snap.SlotInfo
    58  	testSlotCleaned      *interfaces.ConnectedSlot
    59  	testSlotCleanedInfo  *snap.SlotInfo
    60  	missingPathSlot      *interfaces.ConnectedSlot
    61  	missingPathSlotInfo  *snap.SlotInfo
    62  	badPathSlot1         *interfaces.ConnectedSlot
    63  	badPathSlot1Info     *snap.SlotInfo
    64  	badPathSlot2         *interfaces.ConnectedSlot
    65  	badPathSlot2Info     *snap.SlotInfo
    66  	badPathSlot3         *interfaces.ConnectedSlot
    67  	badPathSlot3Info     *snap.SlotInfo
    68  	badPathSlot4         *interfaces.ConnectedSlot
    69  	badPathSlot4Info     *snap.SlotInfo
    70  	badPathSlot5         *interfaces.ConnectedSlot
    71  	badPathSlot5Info     *snap.SlotInfo
    72  	badPathSlot6         *interfaces.ConnectedSlot
    73  	badPathSlot6Info     *snap.SlotInfo
    74  	badPathSlot7         *interfaces.ConnectedSlot
    75  	badPathSlot7Info     *snap.SlotInfo
    76  	badPathSlot8         *interfaces.ConnectedSlot
    77  	badPathSlot8Info     *snap.SlotInfo
    78  	badPathSlot9         *interfaces.ConnectedSlot
    79  	badPathSlot9Info     *snap.SlotInfo
    80  	badPathSlot10        *interfaces.ConnectedSlot
    81  	badPathSlot10Info    *snap.SlotInfo
    82  	badPathSlot11        *interfaces.ConnectedSlot
    83  	badPathSlot11Info    *snap.SlotInfo
    84  	badInterfaceSlot     *interfaces.ConnectedSlot
    85  	badInterfaceSlotInfo *snap.SlotInfo
    86  
    87  	// Gadget Snap
    88  	testUDev1             *interfaces.ConnectedSlot
    89  	testUDev1Info         *snap.SlotInfo
    90  	testUDev2             *interfaces.ConnectedSlot
    91  	testUDev2Info         *snap.SlotInfo
    92  	testUDev3             *interfaces.ConnectedSlot
    93  	testUDev3Info         *snap.SlotInfo
    94  	testUDevBadValue1     *interfaces.ConnectedSlot
    95  	testUDevBadValue1Info *snap.SlotInfo
    96  	testUDevBadValue2     *interfaces.ConnectedSlot
    97  	testUDevBadValue2Info *snap.SlotInfo
    98  	testUDevBadValue3     *interfaces.ConnectedSlot
    99  	testUDevBadValue3Info *snap.SlotInfo
   100  	testUDevBadValue4     *interfaces.ConnectedSlot
   101  	testUDevBadValue4Info *snap.SlotInfo
   102  	testUDevBadValue5     *interfaces.ConnectedSlot
   103  	testUDevBadValue5Info *snap.SlotInfo
   104  
   105  	// Consuming Snap
   106  	testPlugPort1     *interfaces.ConnectedPlug
   107  	testPlugPort1Info *snap.PlugInfo
   108  	testPlugPort2     *interfaces.ConnectedPlug
   109  	testPlugPort2Info *snap.PlugInfo
   110  	testPlugPort3     *interfaces.ConnectedPlug
   111  	testPlugPort3Info *snap.PlugInfo
   112  }
   113  
   114  var _ = Suite(&SerialPortInterfaceSuite{
   115  	iface: builtin.MustInterface("serial-port"),
   116  })
   117  
   118  func (s *SerialPortInterfaceSuite) SetUpTest(c *C) {
   119  	osSnapInfo := snaptest.MockInfo(c, `
   120  name: ubuntu-core
   121  version: 0
   122  type: os
   123  slots:
   124      test-port-1:
   125          interface: serial-port
   126          path: /dev/ttyS0
   127      test-port-2:
   128          interface: serial-port
   129          path: /dev/ttyUSB927
   130      test-port-3:
   131          interface: serial-port
   132          path: /dev/ttyS42
   133      test-port-4:
   134          interface: serial-port
   135          path: /dev/ttyO0
   136      test-port-5:
   137          interface: serial-port
   138          path: /dev/ttyACM0
   139      test-port-6:
   140          interface: serial-port
   141          path: /dev/ttyAMA0
   142      test-port-7:
   143          interface: serial-port
   144          path: /dev/ttyXRUSB0
   145      test-port-8:
   146          interface: serial-port
   147          path: /dev/ttymxc2
   148      test-port-9:
   149          interface: serial-port
   150          path: /dev/ttySC0
   151      test-port-unclean:
   152          interface: serial-port
   153          path: /dev/./././ttyS1////
   154      missing-path: serial-port
   155      bad-path-1:
   156          interface: serial-port
   157          path: path
   158      bad-path-2:
   159          interface: serial-port
   160          path: /dev/tty
   161      bad-path-3:
   162          interface: serial-port
   163          path: /dev/tty0
   164      bad-path-4:
   165          interface: serial-port
   166          path: /dev/tty63
   167      bad-path-5:
   168          interface: serial-port
   169          path: /dev/ttyUSB
   170      bad-path-6:
   171          interface: serial-port
   172          path: /dev/usb
   173      bad-path-7:
   174          interface: serial-port
   175          path: /dev/ttyprintk
   176      bad-path-8:
   177          interface: serial-port
   178          path: /dev/ttyO
   179      bad-path-9:
   180          interface: serial-port
   181          path: /dev/ttyS
   182      bad-path-10:
   183          interface: serial-port
   184          path: /dev/ttySC
   185      bad-path-11:
   186          interface: serial-port
   187          path: /dev/ttyillegal0
   188      bad-interface: other-interface
   189  `, nil)
   190  	s.testSlot1Info = osSnapInfo.Slots["test-port-1"]
   191  	s.testSlot1 = interfaces.NewConnectedSlot(s.testSlot1Info, nil, nil)
   192  	s.testSlot2Info = osSnapInfo.Slots["test-port-2"]
   193  	s.testSlot2 = interfaces.NewConnectedSlot(s.testSlot2Info, nil, nil)
   194  	s.testSlot3Info = osSnapInfo.Slots["test-port-3"]
   195  	s.testSlot3 = interfaces.NewConnectedSlot(s.testSlot3Info, nil, nil)
   196  	s.testSlot4Info = osSnapInfo.Slots["test-port-4"]
   197  	s.testSlot4 = interfaces.NewConnectedSlot(s.testSlot4Info, nil, nil)
   198  	s.testSlot5Info = osSnapInfo.Slots["test-port-5"]
   199  	s.testSlot5 = interfaces.NewConnectedSlot(s.testSlot5Info, nil, nil)
   200  	s.testSlot6Info = osSnapInfo.Slots["test-port-6"]
   201  	s.testSlot6 = interfaces.NewConnectedSlot(s.testSlot6Info, nil, nil)
   202  	s.testSlot7Info = osSnapInfo.Slots["test-port-7"]
   203  	s.testSlot7 = interfaces.NewConnectedSlot(s.testSlot7Info, nil, nil)
   204  	s.testSlot8Info = osSnapInfo.Slots["test-port-8"]
   205  	s.testSlot8 = interfaces.NewConnectedSlot(s.testSlot8Info, nil, nil)
   206  	s.testSlot9Info = osSnapInfo.Slots["test-port-9"]
   207  	s.testSlot9 = interfaces.NewConnectedSlot(s.testSlot9Info, nil, nil)
   208  	s.testSlotCleanedInfo = osSnapInfo.Slots["test-port-unclean"]
   209  	s.testSlotCleaned = interfaces.NewConnectedSlot(s.testSlotCleanedInfo, nil, nil)
   210  	s.missingPathSlotInfo = osSnapInfo.Slots["missing-path"]
   211  	s.missingPathSlot = interfaces.NewConnectedSlot(s.missingPathSlotInfo, nil, nil)
   212  	s.badPathSlot1Info = osSnapInfo.Slots["bad-path-1"]
   213  	s.badPathSlot1 = interfaces.NewConnectedSlot(s.badPathSlot1Info, nil, nil)
   214  	s.badPathSlot2Info = osSnapInfo.Slots["bad-path-2"]
   215  	s.badPathSlot2 = interfaces.NewConnectedSlot(s.badPathSlot2Info, nil, nil)
   216  	s.badPathSlot3Info = osSnapInfo.Slots["bad-path-3"]
   217  	s.badPathSlot3 = interfaces.NewConnectedSlot(s.badPathSlot3Info, nil, nil)
   218  	s.badPathSlot4Info = osSnapInfo.Slots["bad-path-4"]
   219  	s.badPathSlot4 = interfaces.NewConnectedSlot(s.badPathSlot4Info, nil, nil)
   220  	s.badPathSlot5Info = osSnapInfo.Slots["bad-path-5"]
   221  	s.badPathSlot5 = interfaces.NewConnectedSlot(s.badPathSlot5Info, nil, nil)
   222  	s.badPathSlot6Info = osSnapInfo.Slots["bad-path-6"]
   223  	s.badPathSlot6 = interfaces.NewConnectedSlot(s.badPathSlot6Info, nil, nil)
   224  	s.badPathSlot7Info = osSnapInfo.Slots["bad-path-7"]
   225  	s.badPathSlot7 = interfaces.NewConnectedSlot(s.badPathSlot7Info, nil, nil)
   226  	s.badPathSlot8Info = osSnapInfo.Slots["bad-path-8"]
   227  	s.badPathSlot8 = interfaces.NewConnectedSlot(s.badPathSlot8Info, nil, nil)
   228  	s.badPathSlot9Info = osSnapInfo.Slots["bad-path-9"]
   229  	s.badPathSlot9 = interfaces.NewConnectedSlot(s.badPathSlot9Info, nil, nil)
   230  	s.badPathSlot10Info = osSnapInfo.Slots["bad-path-10"]
   231  	s.badPathSlot10 = interfaces.NewConnectedSlot(s.badPathSlot10Info, nil, nil)
   232  	s.badPathSlot11Info = osSnapInfo.Slots["bad-path-11"]
   233  	s.badPathSlot11 = interfaces.NewConnectedSlot(s.badPathSlot11Info, nil, nil)
   234  	s.badInterfaceSlotInfo = osSnapInfo.Slots["bad-interface"]
   235  	s.badInterfaceSlot = interfaces.NewConnectedSlot(s.badInterfaceSlotInfo, nil, nil)
   236  
   237  	gadgetSnapInfo := snaptest.MockInfo(c, `
   238  name: some-device
   239  version: 0
   240  type: gadget
   241  slots:
   242    test-udev-1:
   243        interface: serial-port
   244        usb-vendor: 0x0001
   245        usb-product: 0x0001
   246        path: /dev/serial-port-zigbee
   247    test-udev-2:
   248        interface: serial-port
   249        usb-vendor: 0xffff
   250        usb-product: 0xffff
   251        path: /dev/serial-port-mydevice
   252    test-udev-3:
   253        interface: serial-port
   254        usb-vendor: 0xabcd
   255        usb-product: 0x1234
   256        usb-interface-number: 0
   257        path: /dev/serial-port-myserial
   258    test-udev-bad-value-1:
   259        interface: serial-port
   260        usb-vendor: -1
   261        usb-product: 0xffff
   262        path: /dev/serial-port-mydevice
   263    test-udev-bad-value-2:
   264        interface: serial-port
   265        usb-vendor: 0x1234
   266        usb-product: 0x10000
   267        path: /dev/serial-port-mydevice
   268    test-udev-bad-value-3:
   269        interface: serial-port
   270        usb-vendor: 0x789a
   271        usb-product: 0x4321
   272        path: /dev/my-device
   273    test-udev-bad-value-4:
   274        interface: serial-port
   275        usb-vendor: 0x1234
   276        usb-product: 0x4321
   277        usb-interface-number: -1
   278        path: /dev/serial-port-mybadinterface
   279    test-udev-bad-value-5:
   280        interface: serial-port
   281        usb-vendor: 0x1234
   282        usb-product: 0x4321
   283        usb-interface-number: 32
   284        path: /dev/serial-port-overinterfacenumber
   285  `, nil)
   286  	s.testUDev1Info = gadgetSnapInfo.Slots["test-udev-1"]
   287  	s.testUDev1 = interfaces.NewConnectedSlot(s.testUDev1Info, nil, nil)
   288  	s.testUDev2Info = gadgetSnapInfo.Slots["test-udev-2"]
   289  	s.testUDev2 = interfaces.NewConnectedSlot(s.testUDev2Info, nil, nil)
   290  	s.testUDev3Info = gadgetSnapInfo.Slots["test-udev-3"]
   291  	s.testUDev3 = interfaces.NewConnectedSlot(s.testUDev3Info, nil, nil)
   292  	s.testUDevBadValue1Info = gadgetSnapInfo.Slots["test-udev-bad-value-1"]
   293  	s.testUDevBadValue1 = interfaces.NewConnectedSlot(s.testUDevBadValue1Info, nil, nil)
   294  	s.testUDevBadValue2Info = gadgetSnapInfo.Slots["test-udev-bad-value-2"]
   295  	s.testUDevBadValue3 = interfaces.NewConnectedSlot(s.testUDevBadValue2Info, nil, nil)
   296  	s.testUDevBadValue3Info = gadgetSnapInfo.Slots["test-udev-bad-value-3"]
   297  	s.testUDevBadValue3 = interfaces.NewConnectedSlot(s.testUDevBadValue3Info, nil, nil)
   298  	s.testUDevBadValue4Info = gadgetSnapInfo.Slots["test-udev-bad-value-4"]
   299  	s.testUDevBadValue4 = interfaces.NewConnectedSlot(s.testUDevBadValue4Info, nil, nil)
   300  	s.testUDevBadValue5Info = gadgetSnapInfo.Slots["test-udev-bad-value-5"]
   301  	s.testUDevBadValue5 = interfaces.NewConnectedSlot(s.testUDevBadValue5Info, nil, nil)
   302  
   303  	consumingSnapInfo := snaptest.MockInfo(c, `
   304  name: client-snap
   305  version: 0
   306  plugs:
   307      plug-for-port-1:
   308          interface: serial-port
   309      plug-for-port-2:
   310          interface: serial-port
   311      plug-for-port-3:
   312          interface: serial-port
   313  
   314  apps:
   315      app-accessing-1-port:
   316          command: foo
   317          plugs: [serial-port]
   318      app-accessing-2-ports:
   319          command: bar
   320          plugs: [plug-for-port-1, plug-for-port-2]
   321      app-accessing-3rd-port:
   322          command: foo
   323          plugs: [plug-for-port-3]
   324  `, nil)
   325  	s.testPlugPort1Info = consumingSnapInfo.Plugs["plug-for-port-1"]
   326  	s.testPlugPort1 = interfaces.NewConnectedPlug(s.testPlugPort1Info, nil, nil)
   327  	s.testPlugPort2Info = consumingSnapInfo.Plugs["plug-for-port-2"]
   328  	s.testPlugPort2 = interfaces.NewConnectedPlug(s.testPlugPort2Info, nil, nil)
   329  	s.testPlugPort3Info = consumingSnapInfo.Plugs["plug-for-port-3"]
   330  	s.testPlugPort3 = interfaces.NewConnectedPlug(s.testPlugPort3Info, nil, nil)
   331  }
   332  
   333  func (s *SerialPortInterfaceSuite) TestName(c *C) {
   334  	c.Assert(s.iface.Name(), Equals, "serial-port")
   335  }
   336  
   337  func (s *SerialPortInterfaceSuite) TestSanitizeCoreSnapSlots(c *C) {
   338  	for _, slot := range []*snap.SlotInfo{s.testSlot1Info, s.testSlot2Info, s.testSlot3Info, s.testSlot4Info, s.testSlot5Info, s.testSlot6Info, s.testSlot7Info, s.testSlot8Info} {
   339  		c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), IsNil)
   340  	}
   341  }
   342  
   343  func (s *SerialPortInterfaceSuite) TestSanitizeBadCoreSnapSlots(c *C) {
   344  	// Slots without the "path" attribute are rejected.
   345  	c.Assert(interfaces.BeforePrepareSlot(s.iface, s.missingPathSlotInfo), ErrorMatches, `serial-port slot must have a path attribute`)
   346  
   347  	// Slots with incorrect value of the "path" attribute are rejected.
   348  	for _, slot := range []*snap.SlotInfo{s.badPathSlot1Info, s.badPathSlot2Info, s.badPathSlot3Info, s.badPathSlot4Info, s.badPathSlot5Info, s.badPathSlot6Info, s.badPathSlot7Info, s.badPathSlot8Info, s.badPathSlot9Info, s.badPathSlot10Info, s.badPathSlot11Info} {
   349  		c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), ErrorMatches, "serial-port path attribute must be a valid device node")
   350  	}
   351  }
   352  
   353  func (s *SerialPortInterfaceSuite) TestSanitizeGadgetSnapSlots(c *C) {
   354  	c.Assert(interfaces.BeforePrepareSlot(s.iface, s.testUDev1Info), IsNil)
   355  	c.Assert(interfaces.BeforePrepareSlot(s.iface, s.testUDev2Info), IsNil)
   356  	c.Assert(interfaces.BeforePrepareSlot(s.iface, s.testUDev3Info), IsNil)
   357  }
   358  
   359  func (s *SerialPortInterfaceSuite) TestSanitizeBadGadgetSnapSlots(c *C) {
   360  	c.Assert(interfaces.BeforePrepareSlot(s.iface, s.testUDevBadValue1Info), ErrorMatches, "serial-port usb-vendor attribute not valid: -1")
   361  	c.Assert(interfaces.BeforePrepareSlot(s.iface, s.testUDevBadValue2Info), ErrorMatches, "serial-port usb-product attribute not valid: 65536")
   362  	c.Assert(interfaces.BeforePrepareSlot(s.iface, s.testUDevBadValue3Info), ErrorMatches, "serial-port path attribute specifies invalid symlink location")
   363  	c.Assert(interfaces.BeforePrepareSlot(s.iface, s.testUDevBadValue4Info), ErrorMatches, "serial-port usb-interface-number attribute cannot be negative or larger than 31")
   364  	c.Assert(interfaces.BeforePrepareSlot(s.iface, s.testUDevBadValue5Info), ErrorMatches, "serial-port usb-interface-number attribute cannot be negative or larger than 31")
   365  }
   366  
   367  func (s *SerialPortInterfaceSuite) TestPermanentSlotUDevSnippets(c *C) {
   368  	spec := &udev.Specification{}
   369  	for _, slot := range []*snap.SlotInfo{s.testSlot1Info, s.testSlot2Info, s.testSlot3Info, s.testSlot4Info} {
   370  		err := spec.AddPermanentSlot(s.iface, slot)
   371  		c.Assert(err, IsNil)
   372  		c.Assert(spec.Snippets(), HasLen, 0)
   373  	}
   374  
   375  	expectedSnippet1 := `# serial-port
   376  IMPORT{builtin}="usb_id"
   377  SUBSYSTEM=="tty", SUBSYSTEMS=="usb", ATTRS{idVendor}=="0001", ATTRS{idProduct}=="0001", SYMLINK+="serial-port-zigbee"`
   378  	err := spec.AddPermanentSlot(s.iface, s.testUDev1Info)
   379  	c.Assert(err, IsNil)
   380  	c.Assert(spec.Snippets(), HasLen, 1)
   381  	snippet := spec.Snippets()[0]
   382  	c.Assert(snippet, Equals, expectedSnippet1)
   383  
   384  	spec = &udev.Specification{}
   385  	expectedSnippet2 := `# serial-port
   386  IMPORT{builtin}="usb_id"
   387  SUBSYSTEM=="tty", SUBSYSTEMS=="usb", ATTRS{idVendor}=="ffff", ATTRS{idProduct}=="ffff", SYMLINK+="serial-port-mydevice"`
   388  	err = spec.AddPermanentSlot(s.iface, s.testUDev2Info)
   389  	c.Assert(err, IsNil)
   390  	c.Assert(spec.Snippets(), HasLen, 1)
   391  	snippet = spec.Snippets()[0]
   392  	c.Assert(snippet, Equals, expectedSnippet2)
   393  
   394  	spec = &udev.Specification{}
   395  	// The ENV{ID_USB_INTERFACE_NUM} is set to two hex digits
   396  	// For instance, the expectedSnippet3 is set to 00
   397  	expectedSnippet3 := `# serial-port
   398  IMPORT{builtin}="usb_id"
   399  SUBSYSTEM=="tty", SUBSYSTEMS=="usb", ATTRS{idVendor}=="abcd", ATTRS{idProduct}=="1234", ENV{ID_USB_INTERFACE_NUM}=="00", SYMLINK+="serial-port-myserial"`
   400  	err = spec.AddPermanentSlot(s.iface, s.testUDev3Info)
   401  	c.Assert(err, IsNil)
   402  	c.Assert(spec.Snippets(), HasLen, 1)
   403  	snippet = spec.Snippets()[0]
   404  	c.Assert(snippet, Equals, expectedSnippet3)
   405  }
   406  
   407  func (s *SerialPortInterfaceSuite) TestConnectedPlugUDevSnippets(c *C) {
   408  	// add the plug for the slot with just path
   409  	spec := &udev.Specification{}
   410  	err := spec.AddConnectedPlug(s.iface, s.testPlugPort1, s.testSlot1)
   411  	c.Assert(err, IsNil)
   412  	c.Assert(spec.Snippets(), HasLen, 2)
   413  	snippet := spec.Snippets()[0]
   414  	expectedSnippet1 := `# serial-port
   415  SUBSYSTEM=="tty", KERNEL=="ttyS0", TAG+="snap_client-snap_app-accessing-2-ports"`
   416  	c.Assert(snippet, Equals, expectedSnippet1)
   417  	extraSnippet := spec.Snippets()[1]
   418  	expectedExtraSnippet1 := `TAG=="snap_client-snap_app-accessing-2-ports", RUN+="/usr/lib/snapd/snap-device-helper $env{ACTION} snap_client-snap_app-accessing-2-ports $devpath $major:$minor"`
   419  	c.Assert(extraSnippet, Equals, expectedExtraSnippet1)
   420  
   421  	// add plug for the first slot with product and vendor ids
   422  	spec = &udev.Specification{}
   423  	err = spec.AddConnectedPlug(s.iface, s.testPlugPort1, s.testUDev1)
   424  	c.Assert(err, IsNil)
   425  	c.Assert(spec.Snippets(), HasLen, 2)
   426  	snippet = spec.Snippets()[0]
   427  	expectedSnippet2 := `# serial-port
   428  IMPORT{builtin}="usb_id"
   429  SUBSYSTEM=="tty", SUBSYSTEMS=="usb", ATTRS{idVendor}=="0001", ATTRS{idProduct}=="0001", TAG+="snap_client-snap_app-accessing-2-ports"`
   430  	c.Assert(snippet, Equals, expectedSnippet2)
   431  	extraSnippet = spec.Snippets()[1]
   432  	expectedExtraSnippet2 := `TAG=="snap_client-snap_app-accessing-2-ports", RUN+="/usr/lib/snapd/snap-device-helper $env{ACTION} snap_client-snap_app-accessing-2-ports $devpath $major:$minor"`
   433  	c.Assert(extraSnippet, Equals, expectedExtraSnippet2)
   434  
   435  	// add plug for the first slot with product and vendor ids
   436  	spec = &udev.Specification{}
   437  	err = spec.AddConnectedPlug(s.iface, s.testPlugPort2, s.testUDev2)
   438  	c.Assert(err, IsNil)
   439  	c.Assert(spec.Snippets(), HasLen, 2)
   440  	snippet = spec.Snippets()[0]
   441  	expectedSnippet3 := `# serial-port
   442  IMPORT{builtin}="usb_id"
   443  SUBSYSTEM=="tty", SUBSYSTEMS=="usb", ATTRS{idVendor}=="ffff", ATTRS{idProduct}=="ffff", TAG+="snap_client-snap_app-accessing-2-ports"`
   444  	c.Assert(snippet, Equals, expectedSnippet3)
   445  	extraSnippet = spec.Snippets()[1]
   446  	expectedExtraSnippet3 := `TAG=="snap_client-snap_app-accessing-2-ports", RUN+="/usr/lib/snapd/snap-device-helper $env{ACTION} snap_client-snap_app-accessing-2-ports $devpath $major:$minor"`
   447  	c.Assert(extraSnippet, Equals, expectedExtraSnippet3)
   448  
   449  	// add plug for the first slot with product and vendor ids and usb interface number
   450  	spec = &udev.Specification{}
   451  	err = spec.AddConnectedPlug(s.iface, s.testPlugPort2, s.testUDev3)
   452  	c.Assert(err, IsNil)
   453  	c.Assert(spec.Snippets(), HasLen, 2)
   454  	snippet = spec.Snippets()[0]
   455  	expectedSnippet4 := `# serial-port
   456  IMPORT{builtin}="usb_id"
   457  SUBSYSTEM=="tty", SUBSYSTEMS=="usb", ATTRS{idVendor}=="abcd", ATTRS{idProduct}=="1234", ENV{ID_USB_INTERFACE_NUM}=="00", TAG+="snap_client-snap_app-accessing-2-ports"`
   458  	c.Assert(snippet, Equals, expectedSnippet4)
   459  	extraSnippet = spec.Snippets()[1]
   460  	expectedExtraSnippet4 := `TAG=="snap_client-snap_app-accessing-2-ports", RUN+="/usr/lib/snapd/snap-device-helper $env{ACTION} snap_client-snap_app-accessing-2-ports $devpath $major:$minor"`
   461  	c.Assert(extraSnippet, Equals, expectedExtraSnippet4)
   462  }
   463  
   464  func (s *SerialPortInterfaceSuite) TestConnectedPlugAppArmorSnippets(c *C) {
   465  	checkConnectedPlugSnippet := func(plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot, expectedSnippet string) {
   466  		apparmorSpec := &apparmor.Specification{}
   467  		err := apparmorSpec.AddConnectedPlug(s.iface, plug, slot)
   468  		c.Assert(err, IsNil)
   469  
   470  		c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.client-snap.app-accessing-2-ports"})
   471  		snippet := apparmorSpec.SnippetForTag("snap.client-snap.app-accessing-2-ports")
   472  		c.Assert(snippet, DeepEquals, expectedSnippet)
   473  	}
   474  
   475  	expectedSnippet1 := `/dev/ttyS0 rwk,`
   476  	checkConnectedPlugSnippet(s.testPlugPort1, s.testSlot1, expectedSnippet1)
   477  	expectedSnippet2 := `/dev/ttyUSB927 rwk,`
   478  	checkConnectedPlugSnippet(s.testPlugPort1, s.testSlot2, expectedSnippet2)
   479  
   480  	expectedSnippet3 := `/dev/ttyS42 rwk,`
   481  	checkConnectedPlugSnippet(s.testPlugPort1, s.testSlot3, expectedSnippet3)
   482  
   483  	expectedSnippet4 := `/dev/ttyO0 rwk,`
   484  	checkConnectedPlugSnippet(s.testPlugPort1, s.testSlot4, expectedSnippet4)
   485  
   486  	expectedSnippet5 := `/dev/ttyACM0 rwk,`
   487  	checkConnectedPlugSnippet(s.testPlugPort1, s.testSlot5, expectedSnippet5)
   488  
   489  	expectedSnippet6 := `/dev/ttyAMA0 rwk,`
   490  	checkConnectedPlugSnippet(s.testPlugPort1, s.testSlot6, expectedSnippet6)
   491  
   492  	expectedSnippet7 := `/dev/ttyXRUSB0 rwk,`
   493  	checkConnectedPlugSnippet(s.testPlugPort1, s.testSlot7, expectedSnippet7)
   494  
   495  	expectedSnippet8 := `/dev/ttymxc2 rwk,`
   496  	checkConnectedPlugSnippet(s.testPlugPort1, s.testSlot8, expectedSnippet8)
   497  
   498  	expectedSnippet9 := `/dev/ttySC0 rwk,`
   499  	checkConnectedPlugSnippet(s.testPlugPort1, s.testSlot9, expectedSnippet9)
   500  
   501  	expectedSnippet10 := `/dev/tty[A-Z]*[0-9] rwk,`
   502  	checkConnectedPlugSnippet(s.testPlugPort1, s.testUDev1, expectedSnippet10)
   503  
   504  	expectedSnippet11 := `/dev/tty[A-Z]*[0-9] rwk,`
   505  	checkConnectedPlugSnippet(s.testPlugPort2, s.testUDev2, expectedSnippet11)
   506  
   507  	expectedSnippet12 := `/dev/tty[A-Z]*[0-9] rwk,`
   508  	checkConnectedPlugSnippet(s.testPlugPort2, s.testUDev3, expectedSnippet12)
   509  }
   510  
   511  func (s *SerialPortInterfaceSuite) TestConnectedPlugUDevSnippetsForPath(c *C) {
   512  	checkConnectedPlugSnippet := func(plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot, expectedSnippet string, expectedExtraSnippet string) {
   513  		udevSpec := &udev.Specification{}
   514  		err := udevSpec.AddConnectedPlug(s.iface, plug, slot)
   515  		c.Assert(err, IsNil)
   516  
   517  		c.Assert(udevSpec.Snippets(), HasLen, 2)
   518  		snippet := udevSpec.Snippets()[0]
   519  		c.Assert(snippet, Equals, expectedSnippet)
   520  		extraSnippet := udevSpec.Snippets()[1]
   521  		c.Assert(extraSnippet, Equals, expectedExtraSnippet)
   522  	}
   523  
   524  	// these have only path
   525  	expectedSnippet1 := `# serial-port
   526  SUBSYSTEM=="tty", KERNEL=="ttyS0", TAG+="snap_client-snap_app-accessing-3rd-port"`
   527  	expectedExtraSnippet1 := `TAG=="snap_client-snap_app-accessing-3rd-port", RUN+="/usr/lib/snapd/snap-device-helper $env{ACTION} snap_client-snap_app-accessing-3rd-port $devpath $major:$minor"`
   528  	checkConnectedPlugSnippet(s.testPlugPort3, s.testSlot1, expectedSnippet1, expectedExtraSnippet1)
   529  
   530  	expectedSnippet2 := `# serial-port
   531  SUBSYSTEM=="tty", KERNEL=="ttyUSB927", TAG+="snap_client-snap_app-accessing-3rd-port"`
   532  	expectedExtraSnippet2 := `TAG=="snap_client-snap_app-accessing-3rd-port", RUN+="/usr/lib/snapd/snap-device-helper $env{ACTION} snap_client-snap_app-accessing-3rd-port $devpath $major:$minor"`
   533  	checkConnectedPlugSnippet(s.testPlugPort3, s.testSlot2, expectedSnippet2, expectedExtraSnippet2)
   534  
   535  	expectedSnippet3 := `# serial-port
   536  SUBSYSTEM=="tty", KERNEL=="ttyS42", TAG+="snap_client-snap_app-accessing-3rd-port"`
   537  	expectedExtraSnippet3 := `TAG=="snap_client-snap_app-accessing-3rd-port", RUN+="/usr/lib/snapd/snap-device-helper $env{ACTION} snap_client-snap_app-accessing-3rd-port $devpath $major:$minor"`
   538  	checkConnectedPlugSnippet(s.testPlugPort3, s.testSlot3, expectedSnippet3, expectedExtraSnippet3)
   539  
   540  	expectedSnippet4 := `# serial-port
   541  SUBSYSTEM=="tty", KERNEL=="ttyO0", TAG+="snap_client-snap_app-accessing-3rd-port"`
   542  	expectedExtraSnippet4 := `TAG=="snap_client-snap_app-accessing-3rd-port", RUN+="/usr/lib/snapd/snap-device-helper $env{ACTION} snap_client-snap_app-accessing-3rd-port $devpath $major:$minor"`
   543  	checkConnectedPlugSnippet(s.testPlugPort3, s.testSlot4, expectedSnippet4, expectedExtraSnippet4)
   544  
   545  	expectedSnippet5 := `# serial-port
   546  SUBSYSTEM=="tty", KERNEL=="ttyACM0", TAG+="snap_client-snap_app-accessing-3rd-port"`
   547  	expectedExtraSnippet5 := `TAG=="snap_client-snap_app-accessing-3rd-port", RUN+="/usr/lib/snapd/snap-device-helper $env{ACTION} snap_client-snap_app-accessing-3rd-port $devpath $major:$minor"`
   548  	checkConnectedPlugSnippet(s.testPlugPort3, s.testSlot5, expectedSnippet5, expectedExtraSnippet5)
   549  
   550  	expectedSnippet6 := `# serial-port
   551  SUBSYSTEM=="tty", KERNEL=="ttyAMA0", TAG+="snap_client-snap_app-accessing-3rd-port"`
   552  	expectedExtraSnippet6 := `TAG=="snap_client-snap_app-accessing-3rd-port", RUN+="/usr/lib/snapd/snap-device-helper $env{ACTION} snap_client-snap_app-accessing-3rd-port $devpath $major:$minor"`
   553  	checkConnectedPlugSnippet(s.testPlugPort3, s.testSlot6, expectedSnippet6, expectedExtraSnippet6)
   554  
   555  	expectedSnippet7 := `# serial-port
   556  SUBSYSTEM=="tty", KERNEL=="ttyXRUSB0", TAG+="snap_client-snap_app-accessing-3rd-port"`
   557  	expectedExtraSnippet7 := `TAG=="snap_client-snap_app-accessing-3rd-port", RUN+="/usr/lib/snapd/snap-device-helper $env{ACTION} snap_client-snap_app-accessing-3rd-port $devpath $major:$minor"`
   558  	checkConnectedPlugSnippet(s.testPlugPort3, s.testSlot7, expectedSnippet7, expectedExtraSnippet7)
   559  
   560  	expectedSnippet8 := `# serial-port
   561  SUBSYSTEM=="tty", KERNEL=="ttymxc2", TAG+="snap_client-snap_app-accessing-3rd-port"`
   562  	expectedExtraSnippet8 := `TAG=="snap_client-snap_app-accessing-3rd-port", RUN+="/usr/lib/snapd/snap-device-helper $env{ACTION} snap_client-snap_app-accessing-3rd-port $devpath $major:$minor"`
   563  	checkConnectedPlugSnippet(s.testPlugPort3, s.testSlot8, expectedSnippet8, expectedExtraSnippet8)
   564  
   565  	expectedSnippet9 := `# serial-port
   566  SUBSYSTEM=="tty", KERNEL=="ttySC0", TAG+="snap_client-snap_app-accessing-3rd-port"`
   567  	expectedExtraSnippet9 := `TAG=="snap_client-snap_app-accessing-3rd-port", RUN+="/usr/lib/snapd/snap-device-helper $env{ACTION} snap_client-snap_app-accessing-3rd-port $devpath $major:$minor"`
   568  	checkConnectedPlugSnippet(s.testPlugPort3, s.testSlot9, expectedSnippet9, expectedExtraSnippet9)
   569  
   570  	// these have product and vendor ids
   571  	expectedSnippet10 := `# serial-port
   572  IMPORT{builtin}="usb_id"
   573  SUBSYSTEM=="tty", SUBSYSTEMS=="usb", ATTRS{idVendor}=="0001", ATTRS{idProduct}=="0001", TAG+="snap_client-snap_app-accessing-3rd-port"`
   574  	expectedExtraSnippet10 := `TAG=="snap_client-snap_app-accessing-3rd-port", RUN+="/usr/lib/snapd/snap-device-helper $env{ACTION} snap_client-snap_app-accessing-3rd-port $devpath $major:$minor"`
   575  	checkConnectedPlugSnippet(s.testPlugPort3, s.testUDev1, expectedSnippet10, expectedExtraSnippet10)
   576  
   577  	expectedSnippet11 := `# serial-port
   578  IMPORT{builtin}="usb_id"
   579  SUBSYSTEM=="tty", SUBSYSTEMS=="usb", ATTRS{idVendor}=="ffff", ATTRS{idProduct}=="ffff", TAG+="snap_client-snap_app-accessing-3rd-port"`
   580  	expectedExtraSnippet11 := `TAG=="snap_client-snap_app-accessing-3rd-port", RUN+="/usr/lib/snapd/snap-device-helper $env{ACTION} snap_client-snap_app-accessing-3rd-port $devpath $major:$minor"`
   581  	checkConnectedPlugSnippet(s.testPlugPort3, s.testUDev2, expectedSnippet11, expectedExtraSnippet11)
   582  }
   583  
   584  func (s *SerialPortInterfaceSuite) TestHotplugDeviceDetected(c *C) {
   585  	hotplugIface := s.iface.(hotplug.Definer)
   586  	di, err := hotplug.NewHotplugDeviceInfo(map[string]string{"DEVPATH": "/sys/foo/bar", "DEVNAME": "/dev/ttyUSB0", "ID_VENDOR_ID": "1234", "ID_MODEL_ID": "5678", "ACTION": "add", "SUBSYSTEM": "tty", "ID_BUS": "usb"})
   587  	c.Assert(err, IsNil)
   588  	proposedSlot, err := hotplugIface.HotplugDeviceDetected(di)
   589  	c.Assert(err, IsNil)
   590  	c.Assert(proposedSlot, DeepEquals, &hotplug.ProposedSlot{Attrs: map[string]interface{}{"path": "/dev/ttyUSB0", "usb-vendor": "1234", "usb-product": "5678"}})
   591  }
   592  
   593  func (s *SerialPortInterfaceSuite) TestHotplugDeviceDetectedNotSerialPort(c *C) {
   594  	hotplugIface := s.iface.(hotplug.Definer)
   595  	di, err := hotplug.NewHotplugDeviceInfo(map[string]string{"DEVPATH": "/sys/foo/bar", "DEVNAME": "/dev/other", "ID_VENDOR_ID": "1234", "ID_MODEL_ID": "5678", "ACTION": "add", "SUBSYSTEM": "tty", "ID_BUS": "usb"})
   596  	c.Assert(err, IsNil)
   597  	proposedSlot, err := hotplugIface.HotplugDeviceDetected(di)
   598  	c.Assert(err, IsNil)
   599  	c.Assert(proposedSlot, IsNil)
   600  }
   601  
   602  func (s *SerialPortInterfaceSuite) TestHotplugHandledByGadget(c *C) {
   603  	byGadgetPred := s.iface.(hotplug.HandledByGadgetPredicate)
   604  	di, err := hotplug.NewHotplugDeviceInfo(map[string]string{"DEVPATH": "/sys/foo/bar", "DEVNAME": "/dev/ttyXRUSB0", "ACTION": "add", "SUBSYSTEM": "tty", "ID_BUS": "usb"})
   605  	c.Assert(err, IsNil)
   606  
   607  	c.Assert(byGadgetPred.HandledByGadget(di, s.testSlot5Info), Equals, false)
   608  	// matching path /dev/ttyXRUSB0
   609  	c.Assert(byGadgetPred.HandledByGadget(di, s.testSlot7Info), Equals, true)
   610  
   611  	// matching on vendor, model, usb interface num
   612  	di, err = hotplug.NewHotplugDeviceInfo(map[string]string{"DEVPATH": "/sys/foo/bar", "DEVNAME": "/dev/path", "ID_VENDOR_ID": "abcd", "ID_MODEL_ID": "1234", "ID_USB_INTERFACE_NUM": "00", "ACTION": "add", "SUBSYSTEM": "tty", "ID_BUS": "usb"})
   613  	c.Assert(err, IsNil)
   614  	c.Assert(byGadgetPred.HandledByGadget(di, s.testUDev3Info), Equals, true)
   615  	// model doesn't match, everything else matches
   616  	di, err = hotplug.NewHotplugDeviceInfo(map[string]string{"DEVPATH": "/sys/foo/bar", "DEVNAME": "/dev/path", "ID_VENDOR_ID": "abcd", "ID_MODEL_ID": "ffff", "ID_USB_INTERFACE_NUM": "00", "ACTION": "add", "SUBSYSTEM": "tty", "ID_BUS": "usb"})
   617  	c.Assert(err, IsNil)
   618  	c.Assert(byGadgetPred.HandledByGadget(di, s.testUDev3Info), Equals, false)
   619  	// vendor doesn't match, everything else matches
   620  	di, err = hotplug.NewHotplugDeviceInfo(map[string]string{"DEVPATH": "/sys/foo/bar", "DEVNAME": "/dev/path", "ID_VENDOR_ID": "eeee", "ID_MODEL_ID": "1234", "ID_USB_INTERFACE_NUM": "00", "ACTION": "add", "SUBSYSTEM": "tty", "ID_BUS": "usb"})
   621  	c.Assert(err, IsNil)
   622  	c.Assert(byGadgetPred.HandledByGadget(di, s.testUDev3Info), Equals, false)
   623  	// usb interface doesn't match, everything else matches
   624  	di, err = hotplug.NewHotplugDeviceInfo(map[string]string{"DEVPATH": "/sys/foo/bar", "DEVNAME": "/dev/path", "ID_VENDOR_ID": "abcd", "ID_MODEL_ID": "1234", "ID_USB_INTERFACE_NUM": "ff", "ACTION": "add", "SUBSYSTEM": "tty", "ID_BUS": "usb"})
   625  	c.Assert(err, IsNil)
   626  	c.Assert(byGadgetPred.HandledByGadget(di, s.testUDev3Info), Equals, false)
   627  
   628  	// usb interface num is optional, match on vendor/model
   629  	di, err = hotplug.NewHotplugDeviceInfo(map[string]string{"DEVPATH": "/sys/foo/bar", "DEVNAME": "/dev/path", "ID_VENDOR_ID": "ffff", "ID_MODEL_ID": "ffff", "ACTION": "add", "SUBSYSTEM": "tty", "ID_BUS": "usb"})
   630  	c.Assert(err, IsNil)
   631  	c.Assert(byGadgetPred.HandledByGadget(di, s.testUDev2Info), Equals, true)
   632  }
   633  
   634  func (s *SerialPortInterfaceSuite) TestInterfaces(c *C) {
   635  	c.Check(builtin.Interfaces(), testutil.DeepContains, s.iface)
   636  }