You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mynewt.apache.org by cc...@apache.org on 2017/08/04 19:28:15 UTC

[mynewt-newtmgr] 05/06: nmxact - Restructure ble code

This is an automated email from the ASF dual-hosted git repository.

ccollins pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/mynewt-newtmgr.git

commit 9141ee90c9f45c11182040218ce9e971173f8e1a
Author: Christopher Collins <cc...@apache.org>
AuthorDate: Wed Aug 2 16:21:22 2017 -0700

    nmxact - Restructure ble code
    
    The BLE nmxact code had become basically unreadable due to changing
    requirements and an attempt to provide unrealistic and unnecessary
    thread-safety guarantees.  This commit simplifies the BLE code
    considerably.
---
 newtmgr/bll/bll_oic_sesn.go          | 135 ++++--
 newtmgr/bll/bll_plain_sesn.go        |   4 +-
 newtmgr/cli/getres.go                |   7 +-
 nmxact/bledefs/bledefs.go            |  29 +-
 nmxact/example/ble_loop/ble_loop.go  |  23 +-
 nmxact/example/ble_scan/ble_scan.go  |   2 +-
 nmxact/mgmt/transceiver.go           | 202 +++++++++
 nmxact/nmble/ble_act.go              |  95 ++++
 nmxact/nmble/ble_advertiser.go       |   9 +-
 nmxact/nmble/ble_coap_sesn.go        | 165 -------
 nmxact/nmble/ble_fsm.go              | 824 -----------------------------------
 nmxact/nmble/ble_oic_sesn.go         | 174 --------
 nmxact/nmble/ble_plain_sesn.go       | 175 --------
 nmxact/nmble/ble_proto.go            | 155 +++++--
 nmxact/nmble/ble_scanner.go          |   4 +-
 nmxact/nmble/ble_sesn.go             | 342 +++++++++++++++
 nmxact/nmble/ble_util.go             | 116 ++++-
 nmxact/nmble/ble_xport.go            |  13 +-
 nmxact/nmble/conn.go                 | 713 ++++++++++++++++++++++++++++++
 nmxact/nmble/dispatch.go             |  10 +-
 nmxact/nmble/profile.go              |  82 ++++
 nmxact/nmble/transceiver.go          | 256 -----------
 nmxact/nmserial/serial_oic_sesn.go   |   4 +-
 nmxact/nmserial/serial_plain_sesn.go |   4 +-
 nmxact/nmxutil/nmxutil.go            |   7 +
 nmxact/oic/oic.go                    |   8 -
 nmxact/oic/resmgr.go                 |   8 +-
 nmxact/omp/frag.go                   |  51 ---
 nmxact/sesn/sesn.go                  |  12 +-
 nmxact/sesn/sesn_cfg.go              |  29 ++
 nmxact/sesn/sesn_util.go             |   6 +-
 nmxact/udp/udp_oic_sesn.go           |   4 +-
 nmxact/udp/udp_plain_sesn.go         |   4 +-
 nmxact/xact/getres.go                |   2 +-
 34 files changed, 1881 insertions(+), 1793 deletions(-)

diff --git a/newtmgr/bll/bll_oic_sesn.go b/newtmgr/bll/bll_oic_sesn.go
index 28c4c53..fc042f5 100644
--- a/newtmgr/bll/bll_oic_sesn.go
+++ b/newtmgr/bll/bll_oic_sesn.go
@@ -41,12 +41,19 @@ import (
 type BllOicSesn struct {
 	cfg BllSesnCfg
 
-	cln       ble.Client
-	nmpReqChr *ble.Characteristic
-	nmpRspChr *ble.Characteristic
-	d         *omp.Dispatcher
-	mtx       sync.Mutex
-	attMtu    int
+	cln    ble.Client
+	d      *omp.Dispatcher
+	mtx    sync.Mutex
+	attMtu int
+
+	nmpReqChr     *ble.Characteristic
+	nmpRspChr     *ble.Characteristic
+	publicReqChr  *ble.Characteristic
+	publicRspChr  *ble.Characteristic
+	gwReqChr      *ble.Characteristic
+	gwRspChr      *ble.Characteristic
+	privateReqChr *ble.Characteristic
+	privateRspChr *ble.Characteristic
 }
 
 func NewBllOicSesn(cfg BllSesnCfg) *BllOicSesn {
@@ -89,43 +96,65 @@ func (bls *BllOicSesn) connect() error {
 	return nil
 }
 
-func (bls *BllOicSesn) discoverAll() error {
-	log.Debugf("Discovering profile")
-	p, err := bls.cln.DiscoverProfile(true)
-	if err != nil {
-		return err
-	}
+func findChr(profile *ble.Profile, svcUuid bledefs.BleUuid,
+	chrUuid bledefs.BleUuid) (*ble.Characteristic, error) {
 
-	ompUnsecSvcUuid, _ := ParseUuid(bledefs.OmpUnsecSvcUuid)
-	reqChrUuid, _ := ParseUuid(bledefs.OmpUnsecReqChrUuid)
-	rspChrUuid, _ := ParseUuid(bledefs.OmpUnsecRspChrUuid)
-
-	for _, s := range p.Services {
+	for _, s := range profile.Services {
 		uuid, err := UuidFromBllUuid(s.UUID)
 		if err != nil {
-			return err
+			return nil, err
 		}
 
-		if bledefs.CompareUuids(uuid, ompUnsecSvcUuid) == 0 {
+		if bledefs.CompareUuids(uuid, svcUuid) == 0 {
 			for _, c := range s.Characteristics {
 				uuid, err := UuidFromBllUuid(c.UUID)
 				if err != nil {
-					return err
+					return nil, err
 				}
 
-				if bledefs.CompareUuids(uuid, reqChrUuid) == 0 {
-					bls.nmpReqChr = c
-				}
-				if bledefs.CompareUuids(uuid, rspChrUuid) == 0 {
-					bls.nmpRspChr = c
+				if bledefs.CompareUuids(uuid, chrUuid) == 0 {
+					return c, nil
 				}
 			}
 		}
 	}
 
-	if bls.nmpReqChr == nil || bls.nmpRspChr == nil {
-		return fmt.Errorf(
-			"Peer doesn't support a suitable service / characteristic")
+	return nil, nil
+}
+
+func (bls *BllOicSesn) discoverAll() error {
+	log.Debugf("Discovering profile")
+	p, err := bls.cln.DiscoverProfile(true)
+	if err != nil {
+		return err
+	}
+
+	ompSvcUuid, _ := bledefs.ParseUuid(bledefs.OmpUnsecSvcUuid)
+	ompReqChrUuid, _ := bledefs.ParseUuid(bledefs.OmpUnsecReqChrUuid)
+	ompRspChrUuid, _ := bledefs.ParseUuid(bledefs.OmpUnsecRspChrUuid)
+
+	gwSvcUuid, _ := bledefs.ParseUuid(bledefs.GwSvcUuid)
+	gwReqChrUuid, _ := bledefs.ParseUuid(bledefs.GwReqChrUuid)
+	gwRspChrUuid, _ := bledefs.ParseUuid(bledefs.GwRspChrUuid)
+
+	bls.nmpReqChr, err = findChr(p, ompSvcUuid, ompReqChrUuid)
+	if err != nil {
+		return err
+	}
+
+	bls.nmpRspChr, err = findChr(p, ompSvcUuid, ompRspChrUuid)
+	if err != nil {
+		return err
+	}
+
+	bls.gwReqChr, err = findChr(p, gwSvcUuid, gwReqChrUuid)
+	if err != nil {
+		return err
+	}
+
+	bls.gwRspChr, err = findChr(p, gwSvcUuid, gwRspChrUuid)
+	if err != nil {
+		return err
 	}
 
 	return nil
@@ -138,8 +167,20 @@ func (bls *BllOicSesn) subscribe() error {
 		bls.d.Dispatch(data)
 	}
 
-	if err := bls.cln.Subscribe(bls.nmpRspChr, false, onNotify); err != nil {
-		return err
+	if bls.nmpRspChr != nil {
+		if err := bls.cln.Subscribe(bls.nmpRspChr, false,
+			onNotify); err != nil {
+
+			return err
+		}
+	}
+
+	if bls.gwRspChr != nil {
+		if err := bls.cln.Subscribe(bls.gwRspChr, false,
+			onNotify); err != nil {
+
+			return err
+		}
 	}
 
 	return nil
@@ -239,6 +280,11 @@ func (bls *BllOicSesn) TxNmpOnce(msg *nmp.NmpMsg, opt sesn.TxOptions) (
 			"Attempt to transmit over closed BLE session")
 	}
 
+	if bls.nmpReqChr == nil || bls.nmpRspChr == nil {
+		return nil, fmt.Errorf("Cannot send NMP request; peer doesn't " +
+			"support request or response characteristic")
+	}
+
 	b, err := bls.EncodeNmpMsg(msg)
 	if err != nil {
 		return nil, err
@@ -272,8 +318,31 @@ func (bls *BllOicSesn) TxNmpOnce(msg *nmp.NmpMsg, opt sesn.TxOptions) (
 	}
 }
 
-func (bls *BllOicSesn) GetResourceOnce(uri string, opt sesn.TxOptions) (
-	coap.COAPCode, []byte, error) {
+func (bls *BllOicSesn) resReqChr(resType sesn.ResourceType) (
+	*ble.Characteristic, error) {
+
+	m := map[sesn.ResourceType]*ble.Characteristic{
+		sesn.RES_TYPE_PUBLIC: bls.publicReqChr,
+		sesn.RES_TYPE_UNAUTH: bls.gwReqChr,
+		sesn.RES_TYPE_SECURE: bls.privateReqChr,
+	}
+
+	chr := m[resType]
+	if chr == nil {
+		return nil, fmt.Errorf("BLE session not configured with "+
+			"characteristic for %s resources", resType)
+	}
+
+	return chr, nil
+}
+
+func (bls *BllOicSesn) GetResourceOnce(resType sesn.ResourceType, uri string,
+	opt sesn.TxOptions) (coap.COAPCode, []byte, error) {
+
+	chr, err := bls.resReqChr(resType)
+	if err != nil {
+		return 0, nil, err
+	}
 
 	token := nmxutil.NextToken()
 
@@ -289,7 +358,7 @@ func (bls *BllOicSesn) GetResourceOnce(uri string, opt sesn.TxOptions) (
 	}
 
 	// Send request.
-	if err := bls.cln.WriteCharacteristic(bls.nmpReqChr, req, true); err != nil {
+	if err := bls.cln.WriteCharacteristic(chr, req, true); err != nil {
 		return 0, nil, err
 	}
 
diff --git a/newtmgr/bll/bll_plain_sesn.go b/newtmgr/bll/bll_plain_sesn.go
index 0808bb4..1f6dccc 100644
--- a/newtmgr/bll/bll_plain_sesn.go
+++ b/newtmgr/bll/bll_plain_sesn.go
@@ -251,8 +251,8 @@ func (bps *BllPlainSesn) TxNmpOnce(msg *nmp.NmpMsg, opt sesn.TxOptions) (
 	}
 }
 
-func (bps *BllPlainSesn) GetResourceOnce(uri string, opt sesn.TxOptions) (
-	coap.COAPCode, []byte, error) {
+func (bps *BllPlainSesn) GetResourceOnce(resType sesn.ResourceType, uri string,
+	opt sesn.TxOptions) (coap.COAPCode, []byte, error) {
 
 	return 0, nil,
 		fmt.Errorf("Resource API not supported by plain (non-OIC) session")
diff --git a/newtmgr/cli/getres.go b/newtmgr/cli/getres.go
index a70b922..049fe94 100644
--- a/newtmgr/cli/getres.go
+++ b/newtmgr/cli/getres.go
@@ -26,6 +26,7 @@ import (
 
 	"mynewt.apache.org/newt/util"
 	"mynewt.apache.org/newtmgr/newtmgr/nmutil"
+	"mynewt.apache.org/newtmgr/nmxact/sesn"
 	"mynewt.apache.org/newtmgr/nmxact/xact"
 )
 
@@ -34,7 +35,10 @@ func getResRunCmd(cmd *cobra.Command, args []string) {
 		nmUsage(cmd, nil)
 	}
 
-	// XXX: Parse type.
+	rt, err := sesn.ParseResType(args[0])
+	if err != nil {
+		nmUsage(cmd, err)
+	}
 
 	uri := args[1]
 
@@ -46,6 +50,7 @@ func getResRunCmd(cmd *cobra.Command, args []string) {
 	c := xact.NewGetResCmd()
 	c.SetTxOptions(nmutil.TxOptions())
 	c.Uri = uri
+	c.Typ = rt
 
 	res, err := c.Run(s)
 	if err != nil {
diff --git a/nmxact/bledefs/bledefs.go b/nmxact/bledefs/bledefs.go
index b5c103a..4f0b8c9 100644
--- a/nmxact/bledefs/bledefs.go
+++ b/nmxact/bledefs/bledefs.go
@@ -31,17 +31,23 @@ const BLE_ATT_ATTR_MAX_LEN = 512
 
 const BLE_ATT_MTU_DFLT = 23
 
-const NmpPlainSvcUuid = "8D53DC1D-1DB7-4CD3-868B-8A527460AA84"
-const NmpPlainChrUuid = "DA2E7828-FBCE-4E01-AE9E-261174997C48"
+const CccdUuid = 0x2902
 
-const OmpUnsecSvcUuid = "ADE3D529-C784-4F63-A987-EB69F70EE816"
-const OmpUnsecReqChrUuid = "AD7B334F-4637-4B86-90B6-9D787F03D218"
-const OmpUnsecRspChrUuid = "E9241982-4580-42C4-8831-95048216B256"
+const NmpPlainSvcUuid = "8d53dc1d-1db7-4cd3-868b-8a527460aa84"
+const NmpPlainChrUuid = "da2e7828-fbce-4e01-ae9e-261174997c48"
+
+const OmpUnsecSvcUuid = "ade3d529-c784-4f63-a987-eb69f70ee816"
+const OmpUnsecReqChrUuid = "ad7b334f-4637-4b86-90b6-9d787f03d218"
+const OmpUnsecRspChrUuid = "e9241982-4580-42c4-8831-95048216b256"
 
 const OmpSecSvcUuid = 0xfe18
 const OmpSecReqChrUuid = 0x1000
 const OmpSecRspChrUuid = 0x1001
 
+const GwSvcUuid = "0c08c213-98ed-4e43-a499-7e1137c39567"
+const GwReqChrUuid = "69b8a928-2ab2-487b-923e-54ce53a18bc1"
+const GwRspChrUuid = "bca10aea-5df1-4248-b72b-f52955ad9c88"
+
 type BleAddrType int
 
 const (
@@ -804,9 +810,12 @@ type BleChrId struct {
 }
 
 type BleMgmtChrs struct {
-	NmpReqChr     *BleChrId
-	NmpRspChr     *BleChrId
-	ResPublicChr  *BleChrId
-	ResGwChr      *BleChrId
-	ResPrivateChr *BleChrId
+	NmpReqChr        *BleChrId
+	NmpRspChr        *BleChrId
+	ResPublicReqChr  *BleChrId
+	ResPublicRspChr  *BleChrId
+	ResGwReqChr      *BleChrId
+	ResGwRspChr      *BleChrId
+	ResPrivateReqChr *BleChrId
+	ResPrivateRspChr *BleChrId
 }
diff --git a/nmxact/example/ble_loop/ble_loop.go b/nmxact/example/ble_loop/ble_loop.go
index 0e5bd99..0844e2d 100644
--- a/nmxact/example/ble_loop/ble_loop.go
+++ b/nmxact/example/ble_loop/ble_loop.go
@@ -28,6 +28,7 @@ import (
 
 	log "github.com/Sirupsen/logrus"
 
+	"mynewt.apache.org/newt/util"
 	"mynewt.apache.org/newtmgr/nmxact/bledefs"
 	"mynewt.apache.org/newtmgr/nmxact/nmble"
 	"mynewt.apache.org/newtmgr/nmxact/nmxutil"
@@ -53,22 +54,27 @@ func configExitHandler(x xport.Xport, s sesn.Sesn) {
 			s := <-sigChan
 			switch s {
 			case os.Interrupt, syscall.SIGTERM:
-				onExit()
-				os.Exit(0)
+				go func() {
+					onExit()
+					os.Exit(0)
+				}()
+
+			case syscall.SIGQUIT:
+				util.PrintStacks()
 			}
 		}
 	}()
 }
 
 func main() {
-	//nmxutil.SetLogLevel(log.DebugLevel)
-	nmxutil.SetLogLevel(log.InfoLevel)
+	nmxutil.SetLogLevel(log.DebugLevel)
+	//nmxutil.SetLogLevel(log.InfoLevel)
 
 	// Initialize the BLE transport.
 	params := nmble.NewXportCfg()
 	params.SockPath = "/tmp/blehostd-uds"
 	params.BlehostdPath = "blehostd"
-	params.DevPath = "/dev/cu.usbmodem142111"
+	params.DevPath = "/dev/cu.usbmodem142141"
 
 	x, err := nmble.NewBleXport(params)
 	if err != nil {
@@ -103,7 +109,7 @@ func main() {
 	//     * Plain NMP (not tunnelled over OIC).
 	//     * We use a random address.
 	sc := sesn.NewSesnCfg()
-	sc.MgmtProto = sesn.MGMT_PROTO_NMP
+	sc.MgmtProto = sesn.MGMT_PROTO_OMP
 	sc.Ble.OwnAddrType = bledefs.BLE_ADDR_TYPE_RANDOM
 	sc.PeerSpec.Ble = *dev
 
@@ -127,14 +133,11 @@ func main() {
 			if err := s.Open(); err != nil {
 				fmt.Fprintf(os.Stderr, "error starting BLE session: %s\n",
 					err.Error())
+				time.Sleep(time.Second)
 				continue
 			}
 		}
 
-		if err := s.Open(); err != nil {
-			s.Close()
-		}
-
 		// Send an echo command to the peer.
 		c := xact.NewEchoCmd()
 		c.Payload = "hello"
diff --git a/nmxact/example/ble_scan/ble_scan.go b/nmxact/example/ble_scan/ble_scan.go
index 3a01146..0f08a03 100644
--- a/nmxact/example/ble_scan/ble_scan.go
+++ b/nmxact/example/ble_scan/ble_scan.go
@@ -71,7 +71,7 @@ func main() {
 	params := nmble.NewXportCfg()
 	params.SockPath = "/tmp/blehostd-uds"
 	params.BlehostdPath = "blehostd"
-	params.DevPath = "/dev/cu.usbmodem142121"
+	params.DevPath = "/dev/cu.usbmodem142141"
 
 	x, err := nmble.NewBleXport(params)
 	if err != nil {
diff --git a/nmxact/mgmt/transceiver.go b/nmxact/mgmt/transceiver.go
new file mode 100644
index 0000000..d43f48e
--- /dev/null
+++ b/nmxact/mgmt/transceiver.go
@@ -0,0 +1,202 @@
+package mgmt
+
+import (
+	"encoding/hex"
+	"fmt"
+	"sync"
+	"time"
+
+	log "github.com/Sirupsen/logrus"
+	"github.com/runtimeco/go-coap"
+
+	"mynewt.apache.org/newtmgr/nmxact/nmp"
+	"mynewt.apache.org/newtmgr/nmxact/nmxutil"
+	"mynewt.apache.org/newtmgr/nmxact/oic"
+	"mynewt.apache.org/newtmgr/nmxact/omp"
+	"mynewt.apache.org/newtmgr/nmxact/sesn"
+)
+
+type TxFn func(req []byte) error
+
+type Transceiver struct {
+	// Only for plain NMP; nil for OMP transceivers.
+	nd *nmp.Dispatcher
+
+	// Used for OMP and CoAP resource requests.
+	od *omp.Dispatcher
+
+	wg sync.WaitGroup
+}
+
+func NewTransceiver(mgmtProto sesn.MgmtProto, logDepth int) (
+	*Transceiver, error) {
+
+	t := &Transceiver{}
+
+	if mgmtProto == sesn.MGMT_PROTO_NMP {
+		t.nd = nmp.NewDispatcher(logDepth)
+	}
+
+	od, err := omp.NewDispatcher(true, logDepth)
+	if err != nil {
+		return nil, err
+	}
+	t.od = od
+
+	return t, nil
+}
+
+func (t *Transceiver) txPlain(txCb TxFn, req *nmp.NmpMsg,
+	timeout time.Duration) (nmp.NmpRsp, error) {
+
+	nl, err := t.nd.AddListener(req.Hdr.Seq)
+	if err != nil {
+		return nil, err
+	}
+	defer t.nd.RemoveListener(req.Hdr.Seq)
+
+	b, err := nmp.EncodeNmpPlain(req)
+	if err != nil {
+		return nil, err
+	}
+
+	log.Debugf("Tx NMP request: %s", hex.Dump(b))
+
+	if err := txCb(b); err != nil {
+		return nil, err
+	}
+
+	// Now wait for NMP response.
+	for {
+		select {
+		case err := <-nl.ErrChan:
+			return nil, err
+		case rsp := <-nl.RspChan:
+			return rsp, nil
+		case <-nl.AfterTimeout(timeout):
+			return nil, nmxutil.NewRspTimeoutError("NMP timeout")
+		}
+	}
+}
+
+func (t *Transceiver) txOmp(txCb TxFn, req *nmp.NmpMsg, timeout time.Duration) (
+	nmp.NmpRsp, error) {
+
+	nl, err := t.od.AddNmpListener(req.Hdr.Seq)
+	if err != nil {
+		return nil, err
+	}
+	defer t.od.RemoveNmpListener(req.Hdr.Seq)
+
+	b, err := omp.EncodeOmpTcp(req)
+	if err != nil {
+		return nil, err
+	}
+
+	log.Debugf("Tx OMP request: %s", hex.Dump(b))
+
+	if err := txCb(b); err != nil {
+		return nil, err
+	}
+
+	// Now wait for NMP response.
+	for {
+		select {
+		case err := <-nl.ErrChan:
+			return nil, err
+		case rsp := <-nl.RspChan:
+			return rsp, nil
+		case <-nl.AfterTimeout(timeout):
+			return nil, nmxutil.NewRspTimeoutError("NMP timeout")
+		}
+	}
+}
+
+func (t *Transceiver) TxNmp(txCb TxFn, req *nmp.NmpMsg, timeout time.Duration) (
+	nmp.NmpRsp, error) {
+
+	if t.nd != nil {
+		return t.txPlain(txCb, req, timeout)
+	} else {
+		return t.txOmp(txCb, req, timeout)
+	}
+}
+
+func (t *Transceiver) TxOic(txCb TxFn, req coap.Message,
+	timeout time.Duration) (coap.Message, error) {
+
+	b, err := oic.Encode(req)
+	if err != nil {
+		return nil, err
+	}
+
+	var rspExpected bool
+	switch req.Type() {
+	case coap.Confirmable:
+		rspExpected = true
+	case coap.NonConfirmable:
+		rspExpected = req.Code() == coap.GET
+	default:
+		rspExpected = false
+	}
+
+	var ol *oic.Listener
+	if rspExpected {
+		ol, err = t.od.AddOicListener(req.Token())
+		if err != nil {
+			return nil, err
+		}
+		defer t.od.RemoveOicListener(req.Token())
+	}
+
+	log.Debugf("Tx OIC request: %s", hex.Dump(b))
+	if err := txCb(b); err != nil {
+		return nil, err
+	}
+
+	if !rspExpected {
+		return nil, nil
+	}
+
+	for {
+		select {
+		case err := <-ol.ErrChan:
+			return nil, err
+		case rsp := <-ol.RspChan:
+			return rsp, nil
+		case <-ol.AfterTimeout(timeout):
+			return nil, nmxutil.NewRspTimeoutError("OIC timeout")
+		}
+	}
+}
+
+func (t *Transceiver) DispatchNmpRsp(data []byte) {
+	if t.nd != nil {
+		t.nd.Dispatch(data)
+	} else {
+		t.od.Dispatch(data)
+	}
+}
+
+func (t *Transceiver) DispatchCoap(data []byte) {
+	t.od.Dispatch(data)
+}
+
+func (t *Transceiver) ErrorOne(seq uint8, err error) {
+	if t.nd != nil {
+		t.nd.ErrorOne(seq, err)
+	} else {
+		t.od.ErrorOneNmp(seq, err)
+	}
+}
+
+func (t *Transceiver) ErrorAll(err error) {
+	if t.nd != nil {
+		t.nd.ErrorAll(err)
+	}
+	t.od.ErrorAll(err)
+}
+
+func (t *Transceiver) AbortRx(seq uint8) {
+	t.ErrorOne(seq, fmt.Errorf("rx aborted"))
+}
diff --git a/nmxact/nmble/ble_act.go b/nmxact/nmble/ble_act.go
index 0712b47..bd97133 100644
--- a/nmxact/nmble/ble_act.go
+++ b/nmxact/nmble/ble_act.go
@@ -311,6 +311,101 @@ func discAllChrs(x *BleXport, bl *Listener, r *BleDiscAllChrsReq) (
 }
 
 // Blocking.
+func discAllDscs(x *BleXport, bl *Listener, r *BleDiscAllDscsReq) (
+	[]*BleDiscDsc, error) {
+
+	const rspType = MSG_TYPE_DISC_ALL_DSCS
+	const evtType = MSG_TYPE_DISC_DSC_EVT
+
+	j, err := json.Marshal(r)
+	if err != nil {
+		return nil, err
+	}
+
+	if err := x.Tx(j); err != nil {
+		return nil, err
+	}
+
+	dscs := []*BleDiscDsc{}
+	for {
+		select {
+		case err := <-bl.ErrChan:
+			return nil, err
+
+		case bm := <-bl.MsgChan:
+			switch msg := bm.(type) {
+			case *BleDiscAllDscsRsp:
+				bl.Acked = true
+				if msg.Status != 0 {
+					return nil, StatusError(MSG_OP_RSP, rspType, msg.Status)
+				}
+
+			case *BleDiscDscEvt:
+				switch msg.Status {
+				case 0:
+					dscs = append(dscs, &msg.Dsc)
+				case ERR_CODE_EDONE:
+					return dscs, nil
+				default:
+					return nil, StatusError(MSG_OP_EVT, evtType, msg.Status)
+				}
+
+			default:
+			}
+
+		case <-bl.AfterTimeout(x.RspTimeout()):
+			return nil, BhdTimeoutError(rspType, r.Seq)
+		}
+	}
+}
+
+// Blocking.
+func write(x *BleXport, bl *Listener, r *BleWriteReq) error {
+	const rspType = MSG_TYPE_WRITE_CMD
+	const evtType = MSG_TYPE_WRITE_ACK_EVT
+
+	j, err := json.Marshal(r)
+	if err != nil {
+		return err
+	}
+
+	if err := x.Tx(j); err != nil {
+		return err
+	}
+
+	for {
+		select {
+		case err := <-bl.ErrChan:
+			return err
+
+		case bm := <-bl.MsgChan:
+			switch msg := bm.(type) {
+			case *BleWriteRsp:
+				bl.Acked = true
+				if msg.Status != 0 {
+					return StatusError(MSG_OP_RSP, rspType, msg.Status)
+				} else {
+					return nil
+				}
+
+			case *BleDiscChrEvt:
+				switch msg.Status {
+				case 0:
+					return nil
+				default:
+					return StatusError(MSG_OP_EVT, evtType, msg.Status)
+				}
+
+			default:
+			}
+
+		case <-bl.AfterTimeout(x.RspTimeout()):
+			return BhdTimeoutError(rspType, r.Seq)
+		}
+	}
+}
+
+// Blocking.
 func writeCmd(x *BleXport, bl *Listener, r *BleWriteCmdReq) error {
 	const rspType = MSG_TYPE_WRITE_CMD
 
diff --git a/nmxact/nmble/ble_advertiser.go b/nmxact/nmble/ble_advertiser.go
index 6888009..c6b012d 100644
--- a/nmxact/nmble/ble_advertiser.go
+++ b/nmxact/nmble/ble_advertiser.go
@@ -122,12 +122,15 @@ func (a *Advertiser) stopAdvertising() error {
 func (a *Advertiser) buildSesn(cfg adv.Cfg, connHandle uint16, bl *Listener) (
 	sesn.Sesn, error) {
 
-	// XXX: Build different kinds of sessions.
-	s := NewBleCoapSesn(a.bx, cfg.Ble.SesnCfg)
-	if err := s.OpenConnected(connHandle, bl); err != nil {
+	s, err := NewBleSesn(a.bx, cfg.Ble.SesnCfg)
+	if err != nil {
 		return nil, err
 	}
 
+	//if err := s.OpenConnected(connHandle, bl); err != nil {
+	//return nil, err
+	//}
+
 	return s, nil
 }
 
diff --git a/nmxact/nmble/ble_coap_sesn.go b/nmxact/nmble/ble_coap_sesn.go
deleted file mode 100644
index 05c6016..0000000
--- a/nmxact/nmble/ble_coap_sesn.go
+++ /dev/null
@@ -1,165 +0,0 @@
-package nmble
-
-import (
-	"fmt"
-	"sync"
-	"time"
-
-	"github.com/runtimeco/go-coap"
-
-	"mynewt.apache.org/newt/util"
-	. "mynewt.apache.org/newtmgr/nmxact/bledefs"
-	"mynewt.apache.org/newtmgr/nmxact/nmp"
-	"mynewt.apache.org/newtmgr/nmxact/nmxutil"
-	"mynewt.apache.org/newtmgr/nmxact/oic"
-	"mynewt.apache.org/newtmgr/nmxact/omp"
-	"mynewt.apache.org/newtmgr/nmxact/sesn"
-)
-
-type BleCoapSesn struct {
-	bf           *BleFsm
-	d            *oic.Dispatcher
-	closeTimeout time.Duration
-	onCloseCb    sesn.OnCloseFn
-	wg           sync.WaitGroup
-
-	closeChan chan struct{}
-}
-
-func NewBleCoapSesn(bx *BleXport, cfg sesn.SesnCfg) *BleCoapSesn {
-	bcs := &BleCoapSesn{
-		closeTimeout: cfg.Ble.CloseTimeout,
-		onCloseCb:    cfg.OnCloseCb,
-	}
-
-	bcs.bf = NewBleFsm(BleFsmParams{
-		Bx:          bx,
-		OwnAddrType: cfg.Ble.OwnAddrType,
-		Central: BleFsmParamsCentral{
-			PeerDev:     cfg.PeerSpec.Ble,
-			ConnTries:   cfg.Ble.Central.ConnTries,
-			ConnTimeout: cfg.Ble.Central.ConnTimeout,
-		},
-
-		EncryptWhen: cfg.Ble.EncryptWhen,
-	})
-
-	return bcs
-}
-
-func (bcs *BleCoapSesn) AbortRx(seq uint8) error {
-	return fmt.Errorf("BleCoapSesn.AbortRx unimplemented")
-}
-
-func (bcs *BleCoapSesn) openInit() {
-	// This channel gets closed when the session closes.
-	bcs.closeChan = make(chan struct{})
-	bcs.d = oic.NewDispatcher(true, 3)
-
-	// Listen for disconnect in the background.
-	bcs.wg.Add(1)
-	go func() {
-		// If the session is being closed, unblock the close() call.
-		defer close(bcs.closeChan)
-
-		// Block until disconnect.
-		<-bcs.bf.DisconnectChan()
-		nmxutil.Assert(!bcs.IsOpen())
-		pd := bcs.bf.PrevDisconnect()
-
-		// Signal error to all listeners.
-		bcs.d.ErrorAll(pd.Err)
-		bcs.wg.Done()
-		bcs.wg.Wait()
-
-		// Only execute the client's disconnect callback if the disconnect was
-		// unsolicited.
-		if pd.Dt != FSM_DISCONNECT_TYPE_REQUESTED && bcs.onCloseCb != nil {
-			bcs.onCloseCb(bcs, pd.Err)
-		}
-	}()
-}
-
-func (bcs *BleCoapSesn) OpenConnected(
-	connHandle uint16, eventListener *Listener) error {
-
-	bcs.openInit()
-
-	if err := bcs.bf.StartConnected(connHandle, eventListener); err != nil {
-		close(bcs.closeChan)
-		return err
-	}
-	return nil
-}
-
-func (bcs *BleCoapSesn) Open() error {
-	bcs.openInit()
-
-	if err := bcs.bf.Start(); err != nil {
-		close(bcs.closeChan)
-		return err
-	}
-	return nil
-}
-
-func (bcs *BleCoapSesn) Close() error {
-	err := bcs.bf.Stop()
-	if err != nil {
-		return err
-	}
-
-	// Block until close completes.
-	<-bcs.closeChan
-	return nil
-}
-
-func (bcs *BleCoapSesn) IsOpen() bool {
-	return bcs.bf.IsOpen()
-}
-
-func (bcs *BleCoapSesn) EncodeNmpMsg(m *nmp.NmpMsg) ([]byte, error) {
-	return nil, fmt.Errorf("BleCoapSesn.EncodeNmpMsg unimplemented")
-}
-
-// Blocking.
-func (bcs *BleCoapSesn) TxNmpOnce(m *nmp.NmpMsg, opt sesn.TxOptions) (
-	nmp.NmpRsp, error) {
-
-	return nil, fmt.Errorf("BleCoapSesn.TxNmpOnce unimplemented")
-}
-
-func (bcs *BleCoapSesn) MtuIn() int {
-	return bcs.bf.attMtu -
-		NOTIFY_CMD_BASE_SZ -
-		omp.OMP_MSG_OVERHEAD -
-		nmp.NMP_HDR_SIZE
-}
-
-func (bcs *BleCoapSesn) MtuOut() int {
-	mtu := bcs.bf.attMtu -
-		WRITE_CMD_BASE_SZ -
-		omp.OMP_MSG_OVERHEAD -
-		nmp.NMP_HDR_SIZE
-	return util.IntMin(mtu, BLE_ATT_ATTR_MAX_LEN)
-}
-
-func (bcs *BleCoapSesn) ConnInfo() (BleConnDesc, error) {
-	return bcs.bf.connInfo()
-}
-
-func (bcs *BleCoapSesn) GetResourceOnce(uri string, opt sesn.TxOptions) (
-	coap.COAPCode, []byte, error) {
-
-	token := nmxutil.NextToken()
-	req, err := oic.CreateGet(true, uri, token)
-	if err != nil {
-		return 0, nil, err
-	}
-
-	rsp, err := bcs.bf.TxOic(req, oic.RES_TYPE_PUBLIC, opt.Timeout)
-	if err != nil {
-		return 0, nil, err
-	}
-
-	return rsp.Code(), rsp.Payload(), nil
-}
diff --git a/nmxact/nmble/ble_fsm.go b/nmxact/nmble/ble_fsm.go
deleted file mode 100644
index 8cd5c6f..0000000
--- a/nmxact/nmble/ble_fsm.go
+++ /dev/null
@@ -1,824 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *  http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-package nmble
-
-import (
-	"fmt"
-	"sync"
-	"sync/atomic"
-	"time"
-
-	log "github.com/Sirupsen/logrus"
-	"github.com/runtimeco/go-coap"
-
-	. "mynewt.apache.org/newtmgr/nmxact/bledefs"
-	"mynewt.apache.org/newtmgr/nmxact/nmp"
-	"mynewt.apache.org/newtmgr/nmxact/nmxutil"
-	"mynewt.apache.org/newtmgr/nmxact/oic"
-	"mynewt.apache.org/newtmgr/nmxact/sesn"
-)
-
-var nextId uint32
-
-func getNextId() uint32 {
-	return atomic.AddUint32(&nextId, 1) - 1
-}
-
-const DFLT_ATT_MTU = 23
-
-type BleSesnState int32
-
-const (
-	SESN_STATE_UNCONNECTED     BleSesnState = 0
-	SESN_STATE_CONNECTING                   = 1
-	SESN_STATE_EXCHANGE_MTU                 = 2
-	SESN_STATE_DISCOVER_SVC                 = 3
-	SESN_STATE_DISCOVER_CHR                 = 4
-	SESN_STATE_SECURITY                     = 5
-	SESN_STATE_SUBSCRIBE                    = 6
-	SESN_STATE_GET_INFO                     = 7
-	SESN_STATE_DONE                         = 8
-	SESN_STATE_TERMINATING                  = 9
-	SESN_STATE_CONN_CANCELLING              = 10
-)
-
-type BleFsmDisconnectType int
-
-const (
-	FSM_DISCONNECT_TYPE_UNOPENED BleFsmDisconnectType = iota
-	FSM_DISCONNECT_TYPE_IMMEDIATE_TIMEOUT
-	FSM_DISCONNECT_TYPE_OPENED
-	FSM_DISCONNECT_TYPE_REQUESTED
-)
-
-type BleDisconnectEntry struct {
-	Dt   BleFsmDisconnectType
-	Peer BleDev
-	Err  error
-}
-
-type BleFsmParamsCentral struct {
-	PeerDev     BleDev
-	ConnTries   int
-	ConnTimeout time.Duration
-}
-
-type BleFsmParams struct {
-	Bx          *BleXport
-	OwnAddrType BleAddrType
-	EncryptWhen BleEncryptWhen
-	Central     BleFsmParamsCentral
-	MgmtChrs    BleMgmtChrs
-	MgmtProto   sesn.MgmtProto
-}
-
-type FsmChr struct {
-	Uuid       BleUuid
-	DefHandle  uint16
-	ValHandle  uint16
-	Properties uint8
-}
-
-type FsmSvc struct {
-	Uuid        BleUuid
-	StartHandle uint16
-	EndHandle   uint16
-	Chrs        []FsmChr
-}
-
-type BleFsm struct {
-	params BleFsmParams
-
-	connHandle     uint16
-	connDesc       BleConnDesc
-	peerDev        BleDev
-	svcs           []FsmSvc
-	chrs           map[BleChrId]FsmChr
-	prevDisconnect BleDisconnectEntry
-	attMtu         int
-	state          BleSesnState
-	txvr           *Transceiver
-	rxer           *Receiver
-	errFunnel      nmxutil.ErrFunnel
-	id             uint32
-	wg             sync.WaitGroup
-
-	encBcast       nmxutil.Bcaster
-	disconnectChan chan struct{}
-	rxNmpChan      chan []byte
-}
-
-func NewBleFsm(p BleFsmParams) *BleFsm {
-	bf := &BleFsm{
-		params: p,
-
-		attMtu: DFLT_ATT_MTU,
-		id:     getNextId(),
-	}
-
-	bf.chrs = map[BleChrId]FsmChr{}
-
-	bf.rxer = NewReceiver(bf.id, p.Bx, 1)
-
-	bf.errFunnel.AccumDelay = 250 * time.Millisecond
-	bf.errFunnel.LessCb = fsmErrorLess
-
-	return bf
-}
-
-func (bf *BleFsm) disconnectError(reason int) error {
-	str := fmt.Sprintf("BLE peer disconnected; "+
-		"reason=\"%s\" (%d) peer=%s handle=%d",
-		ErrCodeToString(reason), reason, bf.peerDev.String(),
-		bf.connHandle)
-	return nmxutil.NewBleSesnDisconnectError(reason, str)
-}
-
-func (bf *BleFsm) closedError(msg string) error {
-	return nmxutil.NewSesnClosedError(
-		fmt.Sprintf("%s; state=%d", msg, bf.state))
-}
-
-func (bf *BleFsm) connInfo() (BleConnDesc, error) {
-	return ConnFindXact(bf.params.Bx, bf.connHandle)
-}
-
-func fsmErrorLess(a error, b error) bool {
-	aIsXport := nmxutil.IsXport(a)
-	bIsXport := nmxutil.IsXport(b)
-	aIsTerm := nmxutil.IsBleSesnDisconnect(a)
-	bIsTerm := nmxutil.IsBleSesnDisconnect(b)
-
-	if aIsXport {
-		return false
-	}
-
-	if aIsTerm {
-		return !bIsXport && !bIsTerm
-	}
-
-	return false
-}
-
-func calcDisconnectType(state BleSesnState) BleFsmDisconnectType {
-	switch state {
-	case SESN_STATE_EXCHANGE_MTU:
-		return FSM_DISCONNECT_TYPE_IMMEDIATE_TIMEOUT
-
-	case SESN_STATE_DONE:
-		return FSM_DISCONNECT_TYPE_OPENED
-
-	case SESN_STATE_TERMINATING, SESN_STATE_CONN_CANCELLING:
-		return FSM_DISCONNECT_TYPE_REQUESTED
-
-	default:
-		return FSM_DISCONNECT_TYPE_UNOPENED
-	}
-}
-
-func (bf *BleFsm) chrHandle(chrId *BleChrId, name string) (uint16, error) {
-	if chrId == nil {
-		return 0, fmt.Errorf("BLE session not configured with "+
-			"characteristic \"%s\"", name)
-	}
-
-	chr, ok := bf.chrs[*chrId]
-	if !ok {
-		return 0, fmt.Errorf("BLE peer does not support characteristic "+
-			"\"%s\" (svc=%s chr=%s)",
-			name, chrId.SvcUuid.String(), chrId.ChrUuid.String())
-	}
-
-	return chr.ValHandle, nil
-}
-
-func (bf *BleFsm) nmpReqHandle() (uint16, error) {
-	return bf.chrHandle(bf.params.MgmtChrs.NmpReqChr, "NMP request")
-}
-
-func (bf *BleFsm) nmpRspHandle() (uint16, error) {
-	return bf.chrHandle(bf.params.MgmtChrs.NmpRspChr, "NMP response")
-}
-
-func (bf *BleFsm) oicResHandle(resType oic.ResType) (uint16, error) {
-	switch resType {
-	case oic.RES_TYPE_PUBLIC:
-		return bf.chrHandle(bf.params.MgmtChrs.ResPublicChr,
-			"Public resource")
-	case oic.RES_TYPE_GW:
-		return bf.chrHandle(bf.params.MgmtChrs.ResGwChr,
-			"Gateway resource")
-	case oic.RES_TYPE_PRIVATE:
-		return bf.chrHandle(bf.params.MgmtChrs.ResPrivateChr,
-			"Private resource")
-
-	default:
-		return 0, fmt.Errorf("Invalid resource type: %#v", resType)
-	}
-}
-
-func (bf *BleFsm) shutdown(err error) {
-	bf.params.Bx.StopWaitingForMaster(bf, err)
-
-	bf.rxer.RemoveAll("shutdown")
-	if bf.txvr != nil {
-		bf.txvr.Stop(err)
-	}
-
-	bf.state = SESN_STATE_UNCONNECTED
-
-	// Wait for all listeners to get removed.
-	bf.rxer.WaitUntilNoListeners()
-	bf.wg.Wait()
-
-	close(bf.rxNmpChan)
-	close(bf.disconnectChan)
-}
-
-// Listens for an error in the state machine.  On error, the session is
-// considered disconnected and the error is reported to the client.
-func (bf *BleFsm) listenForError() {
-	bf.wg.Add(1)
-
-	go func() {
-		err := <-bf.errFunnel.Wait()
-
-		dt := calcDisconnectType(bf.state)
-		bf.prevDisconnect = BleDisconnectEntry{dt, bf.peerDev, err}
-
-		bf.wg.Done()
-		bf.shutdown(err)
-	}()
-}
-
-// Listens for events in the background.
-func (bf *BleFsm) eventListen(bl *Listener) error {
-	bf.wg.Add(1)
-
-	go func() {
-		defer bf.wg.Done()
-		defer bf.rxer.RemoveListener("connect", bl)
-
-		for {
-			select {
-			case err, ok := <-bl.ErrChan:
-				if ok {
-					bf.errFunnel.Insert(err)
-				}
-				return
-
-			case bm := <-bl.MsgChan:
-				switch msg := bm.(type) {
-				case *BleMtuChangeEvt:
-					if msg.Status != 0 {
-						err := StatusError(MSG_OP_EVT,
-							MSG_TYPE_MTU_CHANGE_EVT,
-							msg.Status)
-						log.Debugf(err.Error())
-					} else {
-						log.Debugf("BLE ATT MTU updated; from=%d to=%d",
-							bf.attMtu, msg.Mtu)
-						bf.attMtu = int(msg.Mtu)
-					}
-
-				case *BleEncChangeEvt:
-					var err error
-					if msg.Status != 0 {
-						err = StatusError(MSG_OP_EVT,
-							MSG_TYPE_ENC_CHANGE_EVT,
-							msg.Status)
-						log.Debugf(err.Error())
-					} else {
-						log.Debugf("Connection encrypted; conn_handle=%d",
-							msg.ConnHandle)
-					}
-
-					// Notify any listeners of the encryption change event.
-					bf.encBcast.SendAndClear(err)
-
-				case *BleDisconnectEvt:
-					err := bf.disconnectError(msg.Reason)
-					bf.errFunnel.Insert(err)
-					return
-
-				default:
-				}
-			}
-		}
-	}()
-	return nil
-}
-
-func (bf *BleFsm) connect() error {
-	bf.peerDev = bf.params.Central.PeerDev
-
-	r := NewBleConnectReq()
-	r.OwnAddrType = bf.params.OwnAddrType
-	r.PeerAddrType = bf.peerDev.AddrType
-	r.PeerAddr = bf.peerDev.Addr
-
-	// Initiating a connection requires dedicated master privileges.
-	if err := bf.params.Bx.AcquireMaster(bf); err != nil {
-		return err
-	}
-	defer bf.params.Bx.ReleaseMaster()
-
-	bl, err := bf.rxer.AddListener("connect", SeqKey(r.Seq))
-	if err != nil {
-		return err
-	}
-
-	// Connection operation now in progress.
-	bf.state = SESN_STATE_CONNECTING
-
-	// Tell blehostd to initiate connection.
-	if bf.connHandle, err = connect(bf.params.Bx, bl, r,
-		bf.params.Central.ConnTimeout); err != nil {
-
-		bhe := nmxutil.ToBleHost(err)
-		if bhe != nil && bhe.Status == ERR_CODE_EDONE {
-			// Already connected.
-			bf.rxer.RemoveListener("connect", bl)
-			return nil
-		} else if !nmxutil.IsXport(err) {
-			// The transport did not restart; always attempt to cancel the
-			// connect operation.  In most cases, the host has already stopped
-			// connecting and will respond with an "ealready" error that can be
-			// ignored.
-			if err := bf.connCancel(); err != nil {
-				log.Errorf("Failed to cancel connect in progress: %s",
-					err.Error())
-			}
-		}
-
-		bf.rxer.RemoveListener("connect", bl)
-		return err
-	}
-
-	// Listen for events in the background.
-	if err := bf.eventListen(bl); err != nil {
-		return err
-	}
-
-	return nil
-}
-
-func (bf *BleFsm) terminateSetState() error {
-	switch bf.state {
-	case SESN_STATE_UNCONNECTED,
-		SESN_STATE_CONNECTING,
-		SESN_STATE_CONN_CANCELLING:
-		return fmt.Errorf("BLE terminate failed; not connected")
-	case SESN_STATE_TERMINATING:
-		return fmt.Errorf(
-			"BLE terminate failed; session already being closed")
-	default:
-		bf.state = SESN_STATE_TERMINATING
-	}
-
-	return nil
-}
-
-func (bf *BleFsm) terminate() error {
-	if err := bf.terminateSetState(); err != nil {
-		return err
-	}
-
-	r := NewBleTerminateReq()
-	r.ConnHandle = bf.connHandle
-	r.HciReason = ERR_CODE_HCI_REM_USER_CONN_TERM
-
-	bl, err := bf.rxer.AddListener("terminate", SeqKey(r.Seq))
-	if err != nil {
-		return err
-	}
-	defer bf.rxer.RemoveListener("terminate", bl)
-
-	if err := terminate(bf.params.Bx, bl, r); err != nil {
-		return err
-	}
-
-	return nil
-}
-
-func (bf *BleFsm) connCancel() error {
-	r := NewBleConnCancelReq()
-
-	bl, err := bf.rxer.AddListener("conn-cancel", SeqKey(r.Seq))
-	if err != nil {
-		return err
-	}
-	defer bf.rxer.RemoveListener("conn-cancel", bl)
-
-	if err := connCancel(bf.params.Bx, bl, r); err != nil {
-		return err
-	}
-
-	return nil
-}
-
-func (bf *BleFsm) discAllSvcs() error {
-	r := NewBleDiscAllSvcsReq()
-	r.ConnHandle = bf.connHandle
-
-	bl, err := bf.rxer.AddListener("disc-all-svcs", SeqKey(r.Seq))
-	if err != nil {
-		return err
-	}
-	defer bf.rxer.RemoveListener("disc-all-svcs", bl)
-
-	svcs, err := discAllSvcs(bf.params.Bx, bl, r)
-	if err != nil {
-		return err
-	}
-
-	bf.svcs = nil
-	for _, s := range svcs {
-		bf.svcs = append(bf.svcs, FsmSvc{
-			Uuid:        s.Uuid,
-			StartHandle: uint16(s.StartHandle),
-			EndHandle:   uint16(s.EndHandle),
-		})
-	}
-
-	return nil
-}
-
-func (bf *BleFsm) encInitiate() error {
-	r := NewBleSecurityInitiateReq()
-	r.ConnHandle = bf.connHandle
-
-	bl, err := bf.rxer.AddListener("enc-initiate", SeqKey(r.Seq))
-	if err != nil {
-		return err
-	}
-	defer bf.rxer.RemoveListener("enc-initiate", bl)
-
-	// Initiate the encryption procedure.
-	if err := encInitiate(bf.params.Bx, bl, r); err != nil {
-		return err
-	}
-
-	// Block until the procedure completes.
-	itf := <-bf.encBcast.Listen()
-	return itf.(error)
-}
-
-func (bf *BleFsm) discAllChrsOnce(svc *FsmSvc) error {
-	r := NewBleDiscAllChrsReq()
-	r.ConnHandle = bf.connHandle
-	r.StartHandle = int(svc.StartHandle)
-	r.EndHandle = int(svc.EndHandle)
-
-	bl, err := bf.rxer.AddListener("disc-all-chrs", SeqKey(r.Seq))
-	if err != nil {
-		return err
-	}
-	defer bf.rxer.RemoveListener("disc-all-chrs", bl)
-
-	chrs, err := discAllChrs(bf.params.Bx, bl, r)
-	if err != nil {
-		return err
-	}
-
-	for _, c := range chrs {
-		fc := FsmChr{
-			Uuid:       c.Uuid,
-			DefHandle:  uint16(c.DefHandle),
-			ValHandle:  uint16(c.ValHandle),
-			Properties: uint8(c.Properties),
-		}
-		svc.Chrs = append(svc.Chrs, fc)
-		bf.chrs[BleChrId{svc.Uuid, c.Uuid}] = fc
-	}
-
-	return nil
-}
-
-func (bf *BleFsm) discAllChrs() error {
-	for _, s := range bf.svcs {
-		if err := bf.discAllChrsOnce(&s); err != nil {
-			return err
-		}
-	}
-
-	// Listen for NMP responses in the background.
-	attHandle, err := bf.nmpRspHandle()
-	if err != nil {
-		return err
-	}
-
-	txvr, err := NewTransceiver(bf.id, bf.connHandle, bf.params.MgmtProto,
-		bf.params.Bx, bf.rxer, 1)
-	if err != nil {
-		return err
-	}
-	bf.txvr = txvr
-
-	errChan, err := bf.txvr.NmpRspListen(attHandle)
-	if err != nil {
-		return err
-	}
-
-	bf.wg.Add(1)
-	go func() {
-		defer bf.wg.Done()
-
-		if err := <-errChan; err != nil {
-			bf.errFunnel.Insert(err)
-		}
-	}()
-
-	return nil
-}
-
-func (bf *BleFsm) exchangeMtu() error {
-	r := NewBleExchangeMtuReq()
-	r.ConnHandle = bf.connHandle
-
-	bl, err := bf.rxer.AddListener("exchange-mtu", SeqKey(r.Seq))
-	if err != nil {
-		return err
-	}
-	defer bf.rxer.RemoveListener("exchange-mtu", bl)
-
-	mtu, err := exchangeMtu(bf.params.Bx, bl, r)
-	if err != nil {
-		// If the operation failed because the peer already initiated the
-		// exchange, just pretend it was successful.
-		bhe := nmxutil.ToBleHost(err)
-		if bhe != nil &&
-			(bhe.Status == ERR_CODE_EALREADY ||
-				bhe.Status == ERR_CODE_ATT_BASE+ERR_CODE_ATT_REQ_NOT_SUPPORTED) {
-
-			return nil
-		}
-		return err
-	}
-
-	bf.attMtu = mtu
-	return nil
-}
-
-func (bf *BleFsm) subscribe() error {
-	rspHandle, err := bf.nmpRspHandle()
-	if err != nil {
-		return err
-	}
-
-	return bf.txvr.WriteCmd(rspHandle+1, []byte{1, 0}, "subscribe")
-}
-
-func (bf *BleFsm) shouldEncrypt() bool {
-	switch bf.params.EncryptWhen {
-	case BLE_ENCRYPT_NEVER:
-		return false
-
-	case BLE_ENCRYPT_ALWAYS:
-		return true
-
-	case BLE_ENCRYPT_PRIV_ONLY:
-		return bf.peerDev.AddrType == BLE_ADDR_TYPE_RPA_PUB ||
-			bf.peerDev.AddrType == BLE_ADDR_TYPE_RPA_RND
-
-	default:
-		log.Errorf("Invalid BleEncryptWhen value: %d",
-			bf.params.EncryptWhen)
-		return false
-	}
-}
-
-func (bf *BleFsm) executeState() (bool, error) {
-	switch bf.state {
-	case SESN_STATE_UNCONNECTED:
-		if err := bf.connect(); err != nil {
-			return false, err
-		}
-		bf.state = SESN_STATE_EXCHANGE_MTU
-
-	case SESN_STATE_EXCHANGE_MTU:
-		if err := bf.exchangeMtu(); err != nil {
-			bhe := nmxutil.ToBleHost(err)
-			retry := bhe != nil && bhe.Status == ERR_CODE_ENOTCONN
-			return retry, err
-		}
-		bf.state = SESN_STATE_DISCOVER_SVC
-
-	case SESN_STATE_DISCOVER_SVC:
-		if err := bf.discAllSvcs(); err != nil {
-			return false, err
-		}
-		bf.state = SESN_STATE_DISCOVER_CHR
-
-	case SESN_STATE_DISCOVER_CHR:
-		if err := bf.discAllChrs(); err != nil {
-			return false, err
-		}
-		bf.state = SESN_STATE_SECURITY
-
-	case SESN_STATE_SECURITY:
-		if bf.shouldEncrypt() {
-			if err := bf.encInitiate(); err != nil {
-				return false, err
-			}
-		}
-		bf.state = SESN_STATE_SUBSCRIBE
-
-	case SESN_STATE_SUBSCRIBE:
-		if err := bf.subscribe(); err != nil {
-			return false, err
-		}
-		bf.state = SESN_STATE_GET_INFO
-
-	case SESN_STATE_GET_INFO:
-		desc, err := bf.connInfo()
-		if err != nil {
-			return false, err
-		}
-		bf.connDesc = desc
-		bf.state = SESN_STATE_DONE
-
-	case SESN_STATE_DONE:
-		/* Open complete. */
-		return false, fmt.Errorf("BleFsm already done being opened")
-
-	default:
-		return false, fmt.Errorf("BleFsm already being opened")
-	}
-
-	return false, nil
-}
-
-func (bf *BleFsm) DisconnectChan() <-chan struct{} {
-	return bf.disconnectChan
-}
-
-func (bf *BleFsm) RxNmpChan() <-chan []byte {
-	return bf.rxNmpChan
-}
-
-func (bf *BleFsm) PrevDisconnect() BleDisconnectEntry {
-	return bf.prevDisconnect
-}
-
-func (bf *BleFsm) IsCentral() bool {
-	return bf.connDesc.Role == BLE_ROLE_MASTER
-}
-
-func (bf *BleFsm) startOnce() (bool, error) {
-	bf.disconnectChan = make(chan struct{})
-	bf.rxNmpChan = make(chan []byte)
-	bf.txvr = nil
-
-	bf.listenForError()
-
-	for {
-		retry, err := bf.executeState()
-		if err != nil {
-			// If stop fails, assume the connection wasn't established and
-			// force an error.
-			if bf.Stop() != nil {
-				bf.errFunnel.Insert(err)
-			}
-			<-bf.disconnectChan
-			return retry, err
-		} else if bf.state == SESN_STATE_DONE {
-			// We are fully connected.  Listen for errors in the background.
-			return false, nil
-		}
-	}
-}
-
-// @return bool                 Whether another start attempt should be made;
-//         error                The error that caused the start attempt to
-//                                  fail; nil on success.
-func (bf *BleFsm) Start() error {
-	var err error
-
-	if !bf.IsClosed() {
-		return nmxutil.NewSesnAlreadyOpenError(fmt.Sprintf(
-			"Attempt to open an already-open BLE session (state=%d)",
-			bf.state))
-	}
-
-	for i := 0; i < bf.params.Central.ConnTries; i++ {
-		var retry bool
-		retry, err = bf.startOnce()
-		if !retry {
-			break
-		}
-	}
-
-	if err != nil {
-		nmxutil.Assert(!bf.IsOpen())
-		nmxutil.Assert(bf.IsClosed())
-		return err
-	}
-
-	return nil
-}
-
-func (bf *BleFsm) StartConnected(
-	connHandle uint16, eventListener *Listener) error {
-
-	bf.connHandle = connHandle
-	if err := bf.eventListen(eventListener); err != nil {
-		return err
-	}
-
-	bf.state = SESN_STATE_GET_INFO
-
-	if _, err := bf.startOnce(); err != nil {
-		nmxutil.Assert(!bf.IsOpen())
-		nmxutil.Assert(bf.IsClosed())
-		return err
-	}
-
-	return nil
-}
-
-// @return bool                 true if stop complete;
-//                              false if disconnect is now pending.
-func (bf *BleFsm) Stop() error {
-	state := bf.state
-
-	switch state {
-	case SESN_STATE_UNCONNECTED,
-		SESN_STATE_TERMINATING,
-		SESN_STATE_CONN_CANCELLING:
-
-		return bf.closedError("Attempt to close an unopened BLE session")
-
-	case SESN_STATE_CONNECTING:
-		bf.connCancel()
-		bf.errFunnel.Insert(fmt.Errorf("Connection attempt cancelled"))
-		return nil
-
-	default:
-		if err := bf.terminate(); err != nil {
-			return err
-		}
-		return nil
-	}
-}
-
-func (bf *BleFsm) IsOpen() bool {
-	return bf.state == SESN_STATE_DONE
-}
-
-func (bf *BleFsm) IsClosed() bool {
-	return bf.state == SESN_STATE_UNCONNECTED
-}
-
-func (bf *BleFsm) TxNmp(req *nmp.NmpMsg, timeout time.Duration) (
-	nmp.NmpRsp, error) {
-
-	if !bf.IsOpen() {
-		return nil, bf.closedError(
-			"Attempt to transmit over closed BLE session")
-	}
-
-	attHandle, err := bf.nmpReqHandle()
-	if err != nil {
-		return nil, err
-	}
-
-	return bf.txvr.TxNmp(attHandle, req, timeout)
-}
-
-func (bf *BleFsm) TxOic(req coap.Message, resType oic.ResType,
-	timeout time.Duration) (coap.Message, error) {
-
-	if !bf.IsOpen() {
-		return nil, bf.closedError(
-			"Attempt to transmit over closed BLE session")
-	}
-
-	attHandle, err := bf.oicResHandle(resType)
-	if err != nil {
-		return nil, err
-	}
-
-	return bf.txvr.TxOic(attHandle, req, timeout)
-}
-
-func (bf *BleFsm) AbortNmpRx(seq uint8) error {
-	return bf.txvr.AbortNmpRx(seq)
-}
diff --git a/nmxact/nmble/ble_oic_sesn.go b/nmxact/nmble/ble_oic_sesn.go
deleted file mode 100644
index dc46be4..0000000
--- a/nmxact/nmble/ble_oic_sesn.go
+++ /dev/null
@@ -1,174 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *  http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-package nmble
-
-import (
-	"sync"
-	"time"
-
-	"github.com/runtimeco/go-coap"
-
-	"mynewt.apache.org/newt/util"
-	. "mynewt.apache.org/newtmgr/nmxact/bledefs"
-	"mynewt.apache.org/newtmgr/nmxact/nmp"
-	"mynewt.apache.org/newtmgr/nmxact/nmxutil"
-	"mynewt.apache.org/newtmgr/nmxact/oic"
-	"mynewt.apache.org/newtmgr/nmxact/omp"
-	"mynewt.apache.org/newtmgr/nmxact/sesn"
-)
-
-type BleOicSesn struct {
-	bf           *BleFsm
-	closeTimeout time.Duration
-	onCloseCb    sesn.OnCloseFn
-	wg           sync.WaitGroup
-	closeBlocker nmxutil.Blocker
-}
-
-func NewBleOicSesn(bx *BleXport, cfg sesn.SesnCfg) *BleOicSesn {
-	bos := &BleOicSesn{
-		closeTimeout: cfg.Ble.CloseTimeout,
-		onCloseCb:    cfg.OnCloseCb,
-	}
-
-	iotUuid, _ := ParseUuid(OmpUnsecSvcUuid)
-	reqChrUuid, _ := ParseUuid(OmpUnsecReqChrUuid)
-	rspChrUuid, _ := ParseUuid(OmpUnsecRspChrUuid)
-
-	bos.bf = NewBleFsm(BleFsmParams{
-		Bx:          bx,
-		OwnAddrType: cfg.Ble.OwnAddrType,
-		Central: BleFsmParamsCentral{
-			PeerDev:     cfg.PeerSpec.Ble,
-			ConnTries:   cfg.Ble.Central.ConnTries,
-			ConnTimeout: cfg.Ble.Central.ConnTimeout,
-		},
-		MgmtChrs: BleMgmtChrs{
-			NmpReqChr: &BleChrId{iotUuid, reqChrUuid},
-			NmpRspChr: &BleChrId{iotUuid, rspChrUuid},
-		},
-		MgmtProto:   sesn.MGMT_PROTO_OMP,
-		EncryptWhen: cfg.Ble.EncryptWhen,
-	})
-
-	return bos
-}
-
-func (bos *BleOicSesn) AbortRx(seq uint8) error {
-	return bos.bf.AbortNmpRx(seq)
-}
-
-func (bos *BleOicSesn) Open() error {
-	// Ensure subsequent calls to Close() block.
-	bos.closeBlocker.Block()
-
-	if err := bos.bf.Start(); err != nil {
-		if !nmxutil.IsSesnAlreadyOpen(err) {
-			bos.closeBlocker.Unblock()
-		}
-		return err
-	}
-
-	// Listen for disconnect in the background.
-	bos.wg.Add(1)
-	go func() {
-		// If the session is being closed, unblock the close() call.
-		defer bos.closeBlocker.Unblock()
-
-		// Block until disconnect.
-		<-bos.bf.DisconnectChan()
-		nmxutil.Assert(!bos.IsOpen())
-		pd := bos.bf.PrevDisconnect()
-
-		// Signal error to all listeners.
-		bos.wg.Done()
-		bos.wg.Wait()
-
-		// Only execute the client's disconnect callback if the disconnect was
-		// unsolicited.
-		if pd.Dt != FSM_DISCONNECT_TYPE_REQUESTED && bos.onCloseCb != nil {
-			bos.onCloseCb(bos, pd.Err)
-		}
-	}()
-
-	return nil
-}
-
-func (bos *BleOicSesn) Close() error {
-	err := bos.bf.Stop()
-	if err != nil {
-		return err
-	}
-
-	// Block until close completes.
-	bos.closeBlocker.Wait()
-	return nil
-}
-
-func (bos *BleOicSesn) IsOpen() bool {
-	return bos.bf.IsOpen()
-}
-
-func (bos *BleOicSesn) EncodeNmpMsg(m *nmp.NmpMsg) ([]byte, error) {
-	return omp.EncodeOmpTcp(m)
-}
-
-// Blocking.
-func (bos *BleOicSesn) TxNmpOnce(req *nmp.NmpMsg, opt sesn.TxOptions) (
-	nmp.NmpRsp, error) {
-
-	return bos.bf.TxNmp(req, opt.Timeout)
-}
-
-func (bos *BleOicSesn) MtuIn() int {
-	return bos.bf.attMtu -
-		NOTIFY_CMD_BASE_SZ -
-		omp.OMP_MSG_OVERHEAD -
-		nmp.NMP_HDR_SIZE
-}
-
-func (bos *BleOicSesn) MtuOut() int {
-	mtu := bos.bf.attMtu -
-		WRITE_CMD_BASE_SZ -
-		omp.OMP_MSG_OVERHEAD -
-		nmp.NMP_HDR_SIZE
-	return util.IntMin(mtu, BLE_ATT_ATTR_MAX_LEN)
-}
-
-func (bos *BleOicSesn) ConnInfo() (BleConnDesc, error) {
-	return bos.bf.connInfo()
-}
-
-func (bos *BleOicSesn) GetResourceOnce(uri string, opt sesn.TxOptions) (
-	coap.COAPCode, []byte, error) {
-
-	token := nmxutil.NextToken()
-	req, err := oic.CreateGet(true, uri, token)
-	if err != nil {
-		return 0, nil, err
-	}
-
-	rsp, err := bos.bf.TxOic(req, oic.RES_TYPE_PUBLIC, opt.Timeout)
-	if err != nil {
-		return 0, nil, err
-	}
-
-	return rsp.Code(), rsp.Payload(), nil
-}
diff --git a/nmxact/nmble/ble_plain_sesn.go b/nmxact/nmble/ble_plain_sesn.go
deleted file mode 100644
index 3802d7f..0000000
--- a/nmxact/nmble/ble_plain_sesn.go
+++ /dev/null
@@ -1,175 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *  http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-package nmble
-
-import (
-	"fmt"
-	"sync"
-	"time"
-
-	"github.com/runtimeco/go-coap"
-
-	"mynewt.apache.org/newt/util"
-	. "mynewt.apache.org/newtmgr/nmxact/bledefs"
-	"mynewt.apache.org/newtmgr/nmxact/nmp"
-	"mynewt.apache.org/newtmgr/nmxact/nmxutil"
-	"mynewt.apache.org/newtmgr/nmxact/sesn"
-)
-
-type BlePlainSesn struct {
-	bf           *BleFsm
-	d            *nmp.Dispatcher
-	closeTimeout time.Duration
-	onCloseCb    sesn.OnCloseFn
-	wg           sync.WaitGroup
-	closeBlocker nmxutil.Blocker
-}
-
-func NewBlePlainSesn(bx *BleXport, cfg sesn.SesnCfg) *BlePlainSesn {
-	bps := &BlePlainSesn{
-		closeTimeout: cfg.Ble.CloseTimeout,
-		onCloseCb:    cfg.OnCloseCb,
-	}
-
-	svcUuid, _ := ParseUuid(NmpPlainSvcUuid)
-	chrUuid, _ := ParseUuid(NmpPlainChrUuid)
-
-	bps.bf = NewBleFsm(BleFsmParams{
-		Bx:          bx,
-		OwnAddrType: cfg.Ble.OwnAddrType,
-		Central: BleFsmParamsCentral{
-			PeerDev:     cfg.PeerSpec.Ble,
-			ConnTries:   cfg.Ble.Central.ConnTries,
-			ConnTimeout: cfg.Ble.Central.ConnTimeout,
-		},
-		MgmtChrs: BleMgmtChrs{
-			NmpReqChr: &BleChrId{svcUuid, chrUuid},
-			NmpRspChr: &BleChrId{svcUuid, chrUuid},
-		},
-		EncryptWhen: cfg.Ble.EncryptWhen,
-	})
-
-	return bps
-}
-
-func (bps *BlePlainSesn) AbortRx(seq uint8) error {
-	return bps.d.ErrorOne(seq, fmt.Errorf("Rx aborted"))
-}
-
-func (bps *BlePlainSesn) Open() error {
-	// Ensure subsequent calls to Close() block.
-	bps.closeBlocker.Block()
-
-	if err := bps.bf.Start(); err != nil {
-		if !nmxutil.IsSesnAlreadyOpen(err) {
-			bps.closeBlocker.Unblock()
-		}
-		return err
-	}
-
-	bps.d = nmp.NewDispatcher(3)
-
-	// Listen for disconnect in the background.
-	bps.wg.Add(1)
-	go func() {
-		// If the session is being closed, unblock the close() call.
-		defer bps.closeBlocker.Unblock()
-
-		// Block until disconnect.
-		<-bps.bf.DisconnectChan()
-		nmxutil.Assert(!bps.IsOpen())
-
-		pd := bps.bf.PrevDisconnect()
-
-		// Signal error to all listeners.
-		bps.d.ErrorAll(pd.Err)
-		bps.wg.Done()
-		bps.wg.Wait()
-
-		// Only execute the client's disconnect callback if the disconnect was
-		// unsolicited.
-		if pd.Dt != FSM_DISCONNECT_TYPE_REQUESTED && bps.onCloseCb != nil {
-			bps.onCloseCb(bps, pd.Err)
-		}
-	}()
-
-	// Listen for NMP responses in the background.
-	bps.wg.Add(1)
-	go func() {
-		defer bps.wg.Done()
-
-		for {
-			data, ok := <-bps.bf.RxNmpChan()
-			if !ok {
-				// Disconnected.
-				return
-			} else {
-				bps.d.Dispatch(data)
-			}
-		}
-	}()
-
-	return nil
-}
-
-func (bps *BlePlainSesn) Close() error {
-	err := bps.bf.Stop()
-	if err != nil {
-		return err
-	}
-
-	// Block until close completes.
-	bps.closeBlocker.Wait()
-	return nil
-}
-
-func (bps *BlePlainSesn) IsOpen() bool {
-	return bps.bf.IsOpen()
-}
-
-func (bps *BlePlainSesn) EncodeNmpMsg(m *nmp.NmpMsg) ([]byte, error) {
-	return nmp.EncodeNmpPlain(m)
-}
-
-// Blocking.
-func (bps *BlePlainSesn) TxNmpOnce(req *nmp.NmpMsg, opt sesn.TxOptions) (
-	nmp.NmpRsp, error) {
-
-	return bps.bf.TxNmp(req, opt.Timeout)
-}
-
-func (bps *BlePlainSesn) MtuIn() int {
-	return bps.bf.attMtu - NOTIFY_CMD_BASE_SZ - nmp.NMP_HDR_SIZE
-}
-
-func (bps *BlePlainSesn) MtuOut() int {
-	mtu := bps.bf.attMtu - WRITE_CMD_BASE_SZ - nmp.NMP_HDR_SIZE
-	return util.IntMin(mtu, BLE_ATT_ATTR_MAX_LEN)
-}
-
-func (bps *BlePlainSesn) ConnInfo() (BleConnDesc, error) {
-	return bps.bf.connInfo()
-}
-
-func (bps *BlePlainSesn) GetResourceOnce(uri string, opt sesn.TxOptions) (
-	coap.COAPCode, []byte, error) {
-
-	return 0, nil, fmt.Errorf("BlePlainSesn.GetResourceOnce() unsupported")
-}
diff --git a/nmxact/nmble/ble_proto.go b/nmxact/nmble/ble_proto.go
index 82b73f5..61c086a 100644
--- a/nmxact/nmble/ble_proto.go
+++ b/nmxact/nmble/ble_proto.go
@@ -38,6 +38,8 @@ type BleBytes struct {
 	Bytes []byte
 }
 
+const BLE_CONN_HANDLE_NONE uint16 = 0xffff
+
 const BLE_SEQ_MIN BleSeq = 0
 const BLE_SEQ_EVT_MIN BleSeq = 0xffffff00
 const BLE_SEQ_NONE BleSeq = 0xffffffff
@@ -285,43 +287,45 @@ const (
 	MSG_TYPE_DISC_SVC_UUID             = 6
 	MSG_TYPE_DISC_ALL_CHRS             = 7
 	MSG_TYPE_DISC_CHR_UUID             = 8
-	MSG_TYPE_WRITE                     = 9
-	MSG_TYPE_WRITE_CMD                 = 10
-	MSG_TYPE_EXCHANGE_MTU              = 11
-	MSG_TYPE_GEN_RAND_ADDR             = 12
-	MSG_TYPE_SET_RAND_ADDR             = 13
-	MSG_TYPE_CONN_CANCEL               = 14
-	MSG_TYPE_SCAN                      = 15
-	MSG_TYPE_SCAN_CANCEL               = 16
-	MSG_TYPE_SET_PREFERRED_MTU         = 17
-	MSG_TYPE_SECURITY_INITIATE         = 18
-	MSG_TYPE_CONN_FIND                 = 19
-	MSG_TYPE_RESET                     = 20
-	MSG_TYPE_ADV_START                 = 21
-	MSG_TYPE_ADV_STOP                  = 22
-	MSG_TYPE_ADV_SET_DATA              = 23
-	MSG_TYPE_ADV_RSP_SET_DATA          = 24
-	MSG_TYPE_ADV_FIELDS                = 25
-	MSG_TYPE_CLEAR_SVCS                = 26
-	MSG_TYPE_ADD_SVCS                  = 27
-	MSG_TYPE_COMMIT_SVCS               = 28
-	MSG_TYPE_ACCESS_STATUS             = 29
-	MSG_TYPE_NOTIFY                    = 30
-	MSG_TYPE_FIND_CHR                  = 31
+	MSG_TYPE_DISC_ALL_DSCS             = 9
+	MSG_TYPE_WRITE                     = 10
+	MSG_TYPE_WRITE_CMD                 = 11
+	MSG_TYPE_EXCHANGE_MTU              = 12
+	MSG_TYPE_GEN_RAND_ADDR             = 13
+	MSG_TYPE_SET_RAND_ADDR             = 14
+	MSG_TYPE_CONN_CANCEL               = 15
+	MSG_TYPE_SCAN                      = 16
+	MSG_TYPE_SCAN_CANCEL               = 17
+	MSG_TYPE_SET_PREFERRED_MTU         = 18
+	MSG_TYPE_SECURITY_INITIATE         = 19
+	MSG_TYPE_CONN_FIND                 = 20
+	MSG_TYPE_RESET                     = 21
+	MSG_TYPE_ADV_START                 = 22
+	MSG_TYPE_ADV_STOP                  = 23
+	MSG_TYPE_ADV_SET_DATA              = 24
+	MSG_TYPE_ADV_RSP_SET_DATA          = 25
+	MSG_TYPE_ADV_FIELDS                = 26
+	MSG_TYPE_CLEAR_SVCS                = 27
+	MSG_TYPE_ADD_SVCS                  = 28
+	MSG_TYPE_COMMIT_SVCS               = 29
+	MSG_TYPE_ACCESS_STATUS             = 30
+	MSG_TYPE_NOTIFY                    = 31
+	MSG_TYPE_FIND_CHR                  = 32
 
 	MSG_TYPE_SYNC_EVT       = 2049
 	MSG_TYPE_CONNECT_EVT    = 2050
 	MSG_TYPE_DISCONNECT_EVT = 2051
 	MSG_TYPE_DISC_SVC_EVT   = 2052
 	MSG_TYPE_DISC_CHR_EVT   = 2053
-	MSG_TYPE_WRITE_ACK_EVT  = 2054
-	MSG_TYPE_NOTIFY_RX_EVT  = 2055
-	MSG_TYPE_MTU_CHANGE_EVT = 2056
-	MSG_TYPE_SCAN_EVT       = 2057
-	MSG_TYPE_SCAN_TMO_EVT   = 2058
-	MSG_TYPE_ENC_CHANGE_EVT = 2059
-	MSG_TYPE_RESET_EVT      = 2060
-	MSG_TYPE_ACCESS_EVT     = 2061
+	MSG_TYPE_DISC_DSC_EVT   = 2054
+	MSG_TYPE_WRITE_ACK_EVT  = 2055
+	MSG_TYPE_NOTIFY_RX_EVT  = 2056
+	MSG_TYPE_MTU_CHANGE_EVT = 2057
+	MSG_TYPE_SCAN_EVT       = 2058
+	MSG_TYPE_SCAN_TMO_EVT   = 2059
+	MSG_TYPE_ENC_CHANGE_EVT = 2060
+	MSG_TYPE_RESET_EVT      = 2061
+	MSG_TYPE_ACCESS_EVT     = 2062
 )
 
 var MsgOpStringMap = map[MsgOp]string{
@@ -337,8 +341,10 @@ var MsgTypeStringMap = map[MsgType]string{
 	MSG_TYPE_TERMINATE:         "terminate",
 	MSG_TYPE_DISC_ALL_SVCS:     "disc_all_svcs",
 	MSG_TYPE_DISC_SVC_UUID:     "disc_svc_uuid",
-	MSG_TYPE_DISC_CHR_UUID:     "disc_chr_uuid",
 	MSG_TYPE_DISC_ALL_CHRS:     "disc_all_chrs",
+	MSG_TYPE_DISC_CHR_UUID:     "disc_chr_uuid",
+	MSG_TYPE_DISC_ALL_DSCS:     "disc_all_dscs",
+	MSG_TYPE_WRITE:             "write",
 	MSG_TYPE_WRITE_CMD:         "write_cmd",
 	MSG_TYPE_EXCHANGE_MTU:      "exchange_mtu",
 	MSG_TYPE_GEN_RAND_ADDR:     "gen_rand_addr",
@@ -367,6 +373,8 @@ var MsgTypeStringMap = map[MsgType]string{
 	MSG_TYPE_DISCONNECT_EVT: "disconnect_evt",
 	MSG_TYPE_DISC_SVC_EVT:   "disc_svc_evt",
 	MSG_TYPE_DISC_CHR_EVT:   "disc_chr_evt",
+	MSG_TYPE_DISC_DSC_EVT:   "disc_dsc_evt",
+	MSG_TYPE_WRITE_ACK_EVT:  "write_ack_evt",
 	MSG_TYPE_NOTIFY_RX_EVT:  "notify_rx_evt",
 	MSG_TYPE_MTU_CHANGE_EVT: "mtu_change_evt",
 	MSG_TYPE_SCAN_EVT:       "scan_evt",
@@ -397,6 +405,11 @@ type BleDiscChr struct {
 	Uuid       BleUuid `json:"uuid"`
 }
 
+type BleDiscDsc struct {
+	Handle uint16  `json:"handle"`
+	Uuid   BleUuid `json:"uuid"`
+}
+
 type BleSyncReq struct {
 	// Header
 	Op   MsgOp   `json:"op"`
@@ -548,6 +561,28 @@ type BleDiscSvcEvt struct {
 	Svc    BleDiscSvc `json:"service"`
 }
 
+type BleDiscAllChrsReq struct {
+	// Header
+	Op   MsgOp   `json:"op"`
+	Type MsgType `json:"type"`
+	Seq  BleSeq  `json:"seq"`
+
+	// Mandatory
+	ConnHandle  uint16 `json:"conn_handle"`
+	StartHandle int    `json:"start_handle"`
+	EndHandle   int    `json:"end_handle"`
+}
+
+type BleDiscAllChrsRsp struct {
+	// Header
+	Op   MsgOp   `json:"op"`
+	Type MsgType `json:"type"`
+	Seq  BleSeq  `json:"seq"`
+
+	// Mandatory
+	Status int `json:"status"`
+}
+
 type BleDiscChrUuidReq struct {
 	// Header
 	Op   MsgOp   `json:"op"`
@@ -561,7 +596,28 @@ type BleDiscChrUuidReq struct {
 	Uuid        BleUuid `json:"chr_uuid"`
 }
 
-type BleDiscAllChrsReq struct {
+type BleDiscChrUuidRsp struct {
+	// Header
+	Op   MsgOp   `json:"op"`
+	Type MsgType `json:"type"`
+	Seq  BleSeq  `json:"seq"`
+
+	// Mandatory
+	Status int `json:"status"`
+}
+
+type BleDiscChrEvt struct {
+	// Header
+	Op   MsgOp   `json:"op"`
+	Type MsgType `json:"type"`
+	Seq  BleSeq  `json:"seq"`
+
+	// Mandatory
+	Status int        `json:"status"`
+	Chr    BleDiscChr `json:"characteristic"`
+}
+
+type BleDiscAllDscsReq struct {
 	// Header
 	Op   MsgOp   `json:"op"`
 	Type MsgType `json:"type"`
@@ -573,7 +629,7 @@ type BleDiscAllChrsReq struct {
 	EndHandle   int    `json:"end_handle"`
 }
 
-type BleDiscAllChrsRsp struct {
+type BleDiscAllDscsRsp struct {
 	// Header
 	Op   MsgOp   `json:"op"`
 	Type MsgType `json:"type"`
@@ -583,6 +639,18 @@ type BleDiscAllChrsRsp struct {
 	Status int `json:"status"`
 }
 
+type BleDiscDscEvt struct {
+	// Header
+	Op   MsgOp   `json:"op"`
+	Type MsgType `json:"type"`
+	Seq  BleSeq  `json:"seq"`
+
+	// Mandatory
+	Status       int        `json:"status"`
+	ChrDefHandle uint16     `json:"chr_def_handle"`
+	Dsc          BleDiscDsc `json:"descriptor"`
+}
+
 type BleErrRsp struct {
 	// Header
 	Op   MsgOp   `json:"op"`
@@ -604,7 +672,19 @@ type BleSyncRsp struct {
 	Synced bool `json:"synced"`
 }
 
-type BleDiscChrUuidRsp struct {
+type BleWriteReq struct {
+	// Header
+	Op   MsgOp   `json:"op"`
+	Type MsgType `json:"type"`
+	Seq  BleSeq  `json:"seq"`
+
+	// Mandatory
+	ConnHandle uint16   `json:"conn_handle"`
+	AttrHandle int      `json:"attr_handle"`
+	Data       BleBytes `json:"data"`
+}
+
+type BleWriteRsp struct {
 	// Header
 	Op   MsgOp   `json:"op"`
 	Type MsgType `json:"type"`
@@ -614,15 +694,14 @@ type BleDiscChrUuidRsp struct {
 	Status int `json:"status"`
 }
 
-type BleDiscChrEvt struct {
+type BleWriteAckEvt struct {
 	// Header
 	Op   MsgOp   `json:"op"`
 	Type MsgType `json:"type"`
 	Seq  BleSeq  `json:"seq"`
 
 	// Mandatory
-	Status int        `json:"status"`
-	Chr    BleDiscChr `json:"characteristic"`
+	Status int `json:"status"`
 }
 
 type BleWriteCmdReq struct {
diff --git a/nmxact/nmble/ble_scanner.go b/nmxact/nmble/ble_scanner.go
index 5e44d39..b93a5e9 100644
--- a/nmxact/nmble/ble_scanner.go
+++ b/nmxact/nmble/ble_scanner.go
@@ -40,7 +40,7 @@ type BleScanner struct {
 	bx           *BleXport
 	discoverer   *Discoverer
 	reportedDevs map[BleDev]string
-	bos          *BleOicSesn
+	bos          *BleSesn
 	enabled      bool
 
 	// Protects accesses to the reported devices map.
@@ -96,7 +96,7 @@ func (s *BleScanner) connect(dev BleDev) error {
 	}
 
 	s.mtx.Lock()
-	s.bos = session.(*BleOicSesn)
+	s.bos = session.(*BleSesn)
 	s.mtx.Unlock()
 
 	if err := s.bos.Open(); err != nil {
diff --git a/nmxact/nmble/ble_sesn.go b/nmxact/nmble/ble_sesn.go
new file mode 100644
index 0000000..0918e58
--- /dev/null
+++ b/nmxact/nmble/ble_sesn.go
@@ -0,0 +1,342 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package nmble
+
+import (
+	"fmt"
+	"sync"
+
+	"github.com/runtimeco/go-coap"
+
+	"mynewt.apache.org/newt/util"
+	. "mynewt.apache.org/newtmgr/nmxact/bledefs"
+	"mynewt.apache.org/newtmgr/nmxact/mgmt"
+	"mynewt.apache.org/newtmgr/nmxact/nmp"
+	"mynewt.apache.org/newtmgr/nmxact/nmxutil"
+	"mynewt.apache.org/newtmgr/nmxact/oic"
+	"mynewt.apache.org/newtmgr/nmxact/omp"
+	"mynewt.apache.org/newtmgr/nmxact/sesn"
+)
+
+var dummyNotifyListener = NewNotifyListener()
+
+type BleSesn struct {
+	cfg      sesn.SesnCfg
+	bx       *BleXport
+	conn     *Conn
+	mgmtChrs BleMgmtChrs
+	txvr     *mgmt.Transceiver
+
+	wg           sync.WaitGroup
+	closeBlocker nmxutil.Blocker
+	stopChan     chan struct{}
+}
+
+func (s *BleSesn) init() error {
+	s.conn = NewConn(s.bx)
+
+	s.stopChan = make(chan struct{})
+
+	txvr, err := mgmt.NewTransceiver(s.cfg.MgmtProto, 3)
+	if err != nil {
+		return err
+	}
+	s.txvr = txvr
+	return nil
+}
+
+func NewBleSesn(bx *BleXport, cfg sesn.SesnCfg) (*BleSesn, error) {
+	mgmtChrs, err := BuildMgmtChrs(cfg.MgmtProto)
+	if err != nil {
+		return nil, err
+	}
+
+	s := &BleSesn{
+		cfg:      cfg,
+		bx:       bx,
+		mgmtChrs: mgmtChrs,
+	}
+
+	if err := s.init(); err != nil {
+		return nil, err
+	}
+
+	return s, nil
+}
+
+func (s *BleSesn) AbortRx(seq uint8) error {
+	s.txvr.AbortRx(seq)
+	return nil
+}
+
+func (s *BleSesn) disconnectListen() {
+	// Listen for disconnect in the background.
+	s.wg.Add(1)
+	go func() {
+		// If the session is being closed, unblock the close() call.
+		defer s.closeBlocker.Unblock()
+
+		// Block until disconnect.
+		err := <-s.conn.DisconnectChan()
+		nmxutil.Assert(!s.IsOpen())
+
+		// Signal error to all listeners.
+		close(s.stopChan)
+		s.wg.Done()
+		s.wg.Wait()
+
+		if s.cfg.OnCloseCb != nil {
+			s.cfg.OnCloseCb(s, err)
+		}
+	}()
+}
+
+func (s *BleSesn) getChr(chrId *BleChrId) (*Characteristic, error) {
+	if chrId == nil {
+		return nil, fmt.Errorf("BLE session not configured with required "+
+			"characteristic: %s", *chrId)
+	}
+
+	chr := s.conn.Profile().FindChrByUuid(*chrId)
+	if chr == nil {
+		return nil, fmt.Errorf("BLE peer doesn't support required "+
+			"characteristic: %s", *chrId)
+	}
+
+	return chr, nil
+}
+
+func (s *BleSesn) createNotifyListener(chrId *BleChrId) *NotifyListener {
+	chr, _ := s.getChr(chrId)
+	if chr == nil {
+		return dummyNotifyListener
+	}
+
+	nl := s.conn.ListenForNotifications(chr)
+	if nl == nil {
+		return dummyNotifyListener
+	}
+
+	return nl
+}
+
+func (s *BleSesn) notifyListen() {
+	nmpRspNl := s.createNotifyListener(s.mgmtChrs.NmpRspChr)
+
+	s.wg.Add(1)
+	go func() {
+		defer s.wg.Done()
+
+		for {
+			select {
+			case err := <-nmpRspNl.ErrChan:
+				s.txvr.ErrorAll(err)
+				return
+
+			case n, ok := <-nmpRspNl.NotifyChan:
+				if ok {
+					s.txvr.DispatchNmpRsp(n.Data)
+				}
+
+			case <-s.stopChan:
+				return
+			}
+		}
+	}()
+}
+
+func (s *BleSesn) openOnce() (bool, error) {
+	if s.IsOpen() {
+		return false, nmxutil.NewSesnAlreadyOpenError(
+			"Attempt to open an already-open bll session")
+	}
+
+	if err := s.init(); err != nil {
+		return false, err
+	}
+
+	// Ensure subsequent calls to Close() block.
+	s.closeBlocker.Block()
+
+	// Listen for disconnect in the background.
+	s.disconnectListen()
+
+	if err := s.conn.Connect(
+		s.bx,
+		s.cfg.Ble.OwnAddrType,
+		s.cfg.PeerSpec.Ble,
+		s.cfg.Ble.Central.ConnTimeout); err != nil {
+
+		return false, err
+	}
+
+	if err := s.conn.ExchangeMtu(); err != nil {
+		bhdErr := nmxutil.ToBleHost(err)
+		retry := bhdErr != nil && bhdErr.Status == ERR_CODE_ENOTCONN
+		return retry, err
+	}
+
+	if err := s.conn.DiscoverSvcs(); err != nil {
+		return false, err
+	}
+
+	if chr, _ := s.getChr(s.mgmtChrs.NmpRspChr); chr != nil {
+		if chr.SubscribeType() != 0 {
+			if err := s.conn.Subscribe(chr); err != nil {
+				return false, err
+			}
+		}
+	}
+
+	// Listen for incoming notifications in the background.
+	s.notifyListen()
+
+	return false, nil
+}
+
+func (s *BleSesn) Open() error {
+	var err error
+	for i := 0; i < s.cfg.Ble.Central.ConnTries; i++ {
+		var retry bool
+
+		retry, err = s.openOnce()
+		if err != nil {
+			s.Close()
+		}
+
+		if !retry {
+			break
+		}
+	}
+
+	return err
+}
+
+func (s *BleSesn) OpenConnected(
+	connHandle uint16, eventListener *Listener) error {
+
+	if err := s.conn.Inherit(connHandle, eventListener); err != nil {
+		if !nmxutil.IsSesnAlreadyOpen(err) {
+			s.closeBlocker.Unblock()
+		}
+		return err
+	}
+
+	// Ensure subsequent calls to Close() block.
+	s.closeBlocker.Block()
+
+	// Listen for disconnect in the background.
+	s.disconnectListen()
+
+	// Listen for incoming notifications in the background.
+	s.notifyListen()
+
+	return nil
+}
+
+func (s *BleSesn) Close() error {
+	if err := s.conn.Stop(); err != nil {
+		return err
+	}
+
+	// Block until close completes.
+	s.closeBlocker.Wait()
+	return nil
+}
+
+func (s *BleSesn) IsOpen() bool {
+	return s.conn.IsConnected()
+}
+
+func (s *BleSesn) EncodeNmpMsg(m *nmp.NmpMsg) ([]byte, error) {
+	switch s.cfg.MgmtProto {
+	case sesn.MGMT_PROTO_NMP:
+		return nmp.EncodeNmpPlain(m)
+
+	case sesn.MGMT_PROTO_OMP:
+		return omp.EncodeOmpTcp(m)
+
+	default:
+		return nil,
+			fmt.Errorf("invalid management protocol: %+v", s.cfg.MgmtProto)
+	}
+}
+
+// Blocking.
+func (s *BleSesn) TxNmpOnce(req *nmp.NmpMsg, opt sesn.TxOptions) (
+	nmp.NmpRsp, error) {
+
+	chr, err := s.getChr(s.mgmtChrs.NmpReqChr)
+	if err != nil {
+		return nil, err
+	}
+
+	txRaw := func(b []byte) error {
+		return s.conn.WriteChrNoRsp(chr, b, "nmp")
+	}
+
+	return s.txvr.TxNmp(txRaw, req, opt.Timeout)
+}
+
+func (s *BleSesn) MtuIn() int {
+	return s.conn.AttMtu() -
+		NOTIFY_CMD_BASE_SZ -
+		omp.OMP_MSG_OVERHEAD -
+		nmp.NMP_HDR_SIZE
+}
+
+func (s *BleSesn) MtuOut() int {
+	mtu := s.conn.AttMtu() -
+		WRITE_CMD_BASE_SZ -
+		omp.OMP_MSG_OVERHEAD -
+		nmp.NMP_HDR_SIZE
+	return util.IntMin(mtu, BLE_ATT_ATTR_MAX_LEN)
+}
+
+func (s *BleSesn) ConnInfo() (BleConnDesc, error) {
+	return s.conn.ConnInfo(), nil
+}
+
+func (s *BleSesn) GetResourceOnce(resType sesn.ResourceType, uri string,
+	opt sesn.TxOptions) (coap.COAPCode, []byte, error) {
+
+	token := nmxutil.NextToken()
+	req, err := oic.CreateGet(true, uri, token)
+	if err != nil {
+		return 0, nil, err
+	}
+
+	chrId := ResChrIdLookup(s.mgmtChrs, resType)
+	chr, err := s.getChr(chrId)
+	if err != nil {
+		return 0, nil, err
+	}
+
+	txRaw := func(b []byte) error {
+		return s.conn.WriteChrNoRsp(chr, b, "oic")
+	}
+
+	rsp, err := s.txvr.TxOic(txRaw, req, opt.Timeout)
+	if err != nil {
+		return 0, nil, err
+	}
+
+	return rsp.Code(), rsp.Payload(), nil
+}
diff --git a/nmxact/nmble/ble_util.go b/nmxact/nmble/ble_util.go
index 6b6690d..2630536 100644
--- a/nmxact/nmble/ble_util.go
+++ b/nmxact/nmble/ble_util.go
@@ -26,9 +26,12 @@ import (
 	"time"
 
 	log "github.com/Sirupsen/logrus"
+	"github.com/runtimeco/go-coap"
 
 	. "mynewt.apache.org/newtmgr/nmxact/bledefs"
 	"mynewt.apache.org/newtmgr/nmxact/nmxutil"
+	"mynewt.apache.org/newtmgr/nmxact/oic"
+	"mynewt.apache.org/newtmgr/nmxact/sesn"
 )
 
 const WRITE_CMD_BASE_SZ = 3
@@ -194,6 +197,14 @@ func NewBleDiscAllChrsReq() *BleDiscAllChrsReq {
 	}
 }
 
+func NewBleDiscAllDscsReq() *BleDiscAllDscsReq {
+	return &BleDiscAllDscsReq{
+		Op:   MSG_OP_REQ,
+		Type: MSG_TYPE_DISC_ALL_DSCS,
+		Seq:  NextSeq(),
+	}
+}
+
 func NewBleExchangeMtuReq() *BleExchangeMtuReq {
 	return &BleExchangeMtuReq{
 		Op:   MSG_OP_REQ,
@@ -225,10 +236,14 @@ func NewBleWriteCmdReq() *BleWriteCmdReq {
 		Op:   MSG_OP_REQ,
 		Type: MSG_TYPE_WRITE_CMD,
 		Seq:  NextSeq(),
+	}
+}
 
-		ConnHandle: 0,
-		AttrHandle: 0,
-		Data:       BleBytes{},
+func NewBleWriteReq() *BleWriteReq {
+	return &BleWriteReq{
+		Op:   MSG_OP_REQ,
+		Type: MSG_TYPE_WRITE,
+		Seq:  NextSeq(),
 	}
 }
 
@@ -664,3 +679,98 @@ func GattService() BleSvc {
 		},
 	}
 }
+
+func GenCoapService(x *BleXport, svcUuid BleUuid, reqChrUuid BleUuid,
+	rspChrUuid BleUuid, resources []oic.Resource) (BleSvc, error) {
+
+	svr := NewBleOicSvr(x, svcUuid, rspChrUuid)
+	for _, r := range resources {
+		if err := svr.AddResource(r); err != nil {
+			return BleSvc{}, err
+		}
+	}
+
+	svc := BleSvc{
+		Uuid:    svcUuid,
+		SvcType: BLE_SVC_TYPE_PRIMARY,
+		Chrs: []BleChr{
+			BleChr{
+				Uuid:       reqChrUuid,
+				Flags:      BLE_GATT_F_WRITE_NO_RSP, /* XXX: Security */
+				MinKeySize: 0,
+				AccessCb: func(access BleGattAccess) (uint8, []byte) {
+					return svr.Rx(access), nil
+				},
+			},
+			BleChr{
+				Uuid:       rspChrUuid,
+				Flags:      BLE_GATT_F_NOTIFY, /* XXX: Security */
+				MinKeySize: 0,
+				AccessCb:   nil,
+			},
+		},
+	}
+
+	return svc, nil
+}
+
+func GwService(x *BleXport) (BleSvc, error) {
+	svcUuid, _ := ParseUuid(GwSvcUuid)
+	reqChrUuid, _ := ParseUuid(GwReqChrUuid)
+	rspChrUuid, _ := ParseUuid(GwRspChrUuid)
+
+	resources := []oic.Resource{
+		oic.Resource{
+			Name: "mynewt.yourmom",
+			ReadCb: func(uri string, data []byte) (coap.COAPCode, []byte) {
+				return coap.Content, []byte{1, 2, 3, 4}
+			},
+		},
+	}
+
+	return GenCoapService(x, svcUuid, reqChrUuid, rspChrUuid, resources)
+}
+
+func ResChrIdLookup(mgmtChrs BleMgmtChrs, resType sesn.ResourceType) *BleChrId {
+	m := map[sesn.ResourceType]*BleChrId{
+		sesn.RES_TYPE_PUBLIC:  mgmtChrs.ResPublicReqChr,
+		sesn.RES_TYPE_UNAUTH: mgmtChrs.ResGwReqChr,
+		sesn.RES_TYPE_SECURE: mgmtChrs.ResPrivateReqChr,
+	}
+
+	return m[resType]
+}
+
+func BuildMgmtChrs(mgmtProto sesn.MgmtProto) (BleMgmtChrs, error) {
+	mgmtChrs := BleMgmtChrs{}
+
+	nmpSvcUuid, _ := ParseUuid(NmpPlainSvcUuid)
+	nmpChrUuid, _ := ParseUuid(NmpPlainChrUuid)
+
+	ompSvcUuid, _ := ParseUuid(OmpUnsecSvcUuid)
+	ompReqChrUuid, _ := ParseUuid(OmpUnsecReqChrUuid)
+	ompRspChrUuid, _ := ParseUuid(OmpUnsecRspChrUuid)
+
+	gwSvcUuid, _ := ParseUuid(GwSvcUuid)
+	gwReqChrUuid, _ := ParseUuid(GwReqChrUuid)
+	gwRspChrUuid, _ := ParseUuid(GwRspChrUuid)
+
+	switch mgmtProto {
+	case sesn.MGMT_PROTO_NMP:
+		mgmtChrs.NmpReqChr = &BleChrId{nmpSvcUuid, nmpChrUuid}
+		mgmtChrs.NmpRspChr = &BleChrId{nmpSvcUuid, nmpChrUuid}
+
+	case sesn.MGMT_PROTO_OMP:
+		mgmtChrs.NmpReqChr = &BleChrId{ompSvcUuid, ompReqChrUuid}
+		mgmtChrs.NmpRspChr = &BleChrId{ompSvcUuid, ompRspChrUuid}
+
+	default:
+		return mgmtChrs,
+			fmt.Errorf("invalid management protocol: %+v", mgmtProto)
+	}
+
+	mgmtChrs.ResGwReqChr = &BleChrId{gwSvcUuid, gwReqChrUuid}
+	mgmtChrs.ResGwRspChr = &BleChrId{gwSvcUuid, gwRspChrUuid}
+
+	return mgmtChrs, nil
+}
diff --git a/nmxact/nmble/ble_xport.go b/nmxact/nmble/ble_xport.go
index f67354a..da7cf30 100644
--- a/nmxact/nmble/ble_xport.go
+++ b/nmxact/nmble/ble_xport.go
@@ -27,13 +27,11 @@ import (
 	"time"
 
 	log "github.com/Sirupsen/logrus"
-	"github.com/runtimeco/go-coap"
 
 	"mynewt.apache.org/newt/util/unixchild"
 	"mynewt.apache.org/newtmgr/nmxact/adv"
 	. "mynewt.apache.org/newtmgr/nmxact/bledefs"
 	"mynewt.apache.org/newtmgr/nmxact/nmxutil"
-	"mynewt.apache.org/newtmgr/nmxact/oic"
 	"mynewt.apache.org/newtmgr/nmxact/scan"
 	"mynewt.apache.org/newtmgr/nmxact/sesn"
 )
@@ -184,16 +182,7 @@ func (bx *BleXport) BuildAdvertiser() (adv.Advertiser, error) {
 }
 
 func (bx *BleXport) BuildSesn(cfg sesn.SesnCfg) (sesn.Sesn, error) {
-	switch cfg.MgmtProto {
-	case sesn.MGMT_PROTO_NMP:
-		return NewBlePlainSesn(bx, cfg), nil
-	case sesn.MGMT_PROTO_OMP:
-		return NewBleOicSesn(bx, cfg), nil
-	default:
-		return nil, fmt.Errorf(
-			"Invalid management protocol: %d; expected NMP or OMP",
-			cfg.MgmtProto)
-	}
+	return NewBleSesn(bx, cfg)
 }
 
 func (bx *BleXport) addSyncListener() (*Listener, error) {
diff --git a/nmxact/nmble/conn.go b/nmxact/nmble/conn.go
new file mode 100644
index 0000000..bf8fd10
--- /dev/null
+++ b/nmxact/nmble/conn.go
@@ -0,0 +1,713 @@
+package nmble
+
+import (
+	"fmt"
+	"sync"
+	"time"
+
+	log "github.com/Sirupsen/logrus"
+
+	. "mynewt.apache.org/newtmgr/nmxact/bledefs"
+	"mynewt.apache.org/newtmgr/nmxact/nmxutil"
+)
+
+type Notification struct {
+	Chr        *Characteristic
+	Data       []byte
+	Indication bool
+}
+
+type NotifyListener struct {
+	NotifyChan chan Notification
+	ErrChan    chan error
+}
+
+func NewNotifyListener() *NotifyListener {
+	return &NotifyListener{
+		NotifyChan: make(chan Notification),
+		ErrChan:    make(chan error),
+	}
+}
+
+type Conn struct {
+	bx      *BleXport
+	rxvr    *Receiver
+	attMtu  int
+	profile Profile
+	desc    BleConnDesc
+
+	connHandle     uint16
+	connecting     bool
+	disconnectChan chan error
+	wg             sync.WaitGroup
+
+	// Terminates all go routines.  Gets set to null after disconnect.
+	stopChan chan struct{}
+
+	notifyMap map[*Characteristic][](*NotifyListener)
+
+	// Protects:
+	// * connHandle
+	// * connecting
+	// * notifyMap
+	// * stopChan
+	mtx sync.Mutex
+}
+
+func NewConn(bx *BleXport) *Conn {
+	return &Conn{
+		bx:             bx,
+		rxvr:           NewReceiver(nmxutil.GetNextId(), bx, 1),
+		connHandle:     BLE_CONN_HANDLE_NONE,
+		attMtu:         BLE_ATT_MTU_DFLT,
+		disconnectChan: make(chan error, 1),
+		stopChan:       make(chan struct{}),
+		notifyMap:      map[*Characteristic][](*NotifyListener){},
+	}
+}
+
+func (c *Conn) DisconnectChan() <-chan error {
+	return c.disconnectChan
+}
+
+func (c *Conn) initiateShutdown() bool {
+	c.mtx.Lock()
+	defer c.mtx.Unlock()
+
+	if c.stopChan == nil {
+		return false
+	}
+
+	close(c.stopChan)
+	c.stopChan = nil
+
+	return true
+}
+
+func (c *Conn) abortNotifyListeners(err error) {
+	// No need to lock mutex; this should only be called after all go routines
+	// have terminated.
+	for _, nls := range c.notifyMap {
+		for _, nl := range nls {
+			nl.ErrChan <- err
+			close(nl.NotifyChan)
+			close(nl.ErrChan)
+		}
+	}
+}
+
+func (c *Conn) shutdown(err error) {
+	if !c.initiateShutdown() {
+		return
+	}
+
+	c.connecting = false
+	c.connHandle = BLE_CONN_HANDLE_NONE
+
+	c.bx.StopWaitingForMaster(c, err)
+
+	c.rxvr.RemoveAll("shutdown")
+	c.rxvr.WaitUntilNoListeners()
+
+	c.wg.Wait()
+
+	c.abortNotifyListeners(err)
+
+	c.disconnectChan <- err
+	close(c.disconnectChan)
+}
+
+func (c *Conn) newDisconnectError(reason int) error {
+	str := fmt.Sprintf("BLE peer disconnected; "+
+		"reason=\"%s\" (%d) connection=%s",
+		ErrCodeToString(reason), reason, c.desc.String())
+
+	return nmxutil.NewBleSesnDisconnectError(reason, str)
+}
+
+// Listens for events in the background.
+func (c *Conn) eventListen(bl *Listener) error {
+	c.wg.Add(1)
+
+	go func() {
+		defer c.wg.Done()
+		defer c.rxvr.RemoveListener("connect", bl)
+
+		for {
+			select {
+			case <-c.stopChan:
+				return
+
+			case err := <-bl.ErrChan:
+				go c.shutdown(err)
+				return
+
+			case bm := <-bl.MsgChan:
+				switch msg := bm.(type) {
+				case *BleMtuChangeEvt:
+					if msg.Status != 0 {
+						err := StatusError(MSG_OP_EVT,
+							MSG_TYPE_MTU_CHANGE_EVT,
+							msg.Status)
+						log.Debugf(err.Error())
+					} else {
+						log.Debugf("BLE ATT MTU updated; from=%d to=%d",
+							c.attMtu, msg.Mtu)
+						c.attMtu = int(msg.Mtu)
+					}
+
+				case *BleEncChangeEvt:
+					var err error
+					if msg.Status != 0 {
+						err = StatusError(MSG_OP_EVT,
+							MSG_TYPE_ENC_CHANGE_EVT,
+							msg.Status)
+						log.Debugf(err.Error())
+					} else {
+						log.Debugf("Connection encrypted; conn_handle=%d",
+							msg.ConnHandle)
+					}
+
+				case *BleDisconnectEvt:
+					go c.shutdown(c.newDisconnectError(msg.Reason))
+					return
+
+				default:
+				}
+			}
+		}
+	}()
+	return nil
+}
+
+func (c *Conn) rxNotify(msg *BleNotifyRxEvt) {
+	c.mtx.Lock()
+	defer c.mtx.Unlock()
+
+	chr := c.profile.FindChrByHandle(uint16(msg.AttrHandle))
+	if chr == nil {
+		return
+	}
+
+	nls := c.notifyMap[chr]
+	if nls == nil {
+		return
+	}
+
+	n := Notification{
+		Chr:        chr,
+		Data:       msg.Data.Bytes,
+		Indication: msg.Indication,
+	}
+	for _, nl := range nls {
+		nl.NotifyChan <- n
+	}
+}
+
+// Listens for incoming notifications and indications.
+func (c *Conn) notifyListen() error {
+	key := TchKey(MSG_TYPE_NOTIFY_RX_EVT, int(c.connHandle))
+	bl, err := c.rxvr.AddListener("notifications", key)
+	if err != nil {
+		return err
+	}
+
+	c.wg.Add(1)
+	go func() {
+		defer c.wg.Done()
+		defer c.rxvr.RemoveListener("notifications", bl)
+
+		for {
+			select {
+			case <-c.stopChan:
+				return
+
+			case <-bl.ErrChan:
+				return
+
+			case bm := <-bl.MsgChan:
+				switch msg := bm.(type) {
+				case *BleNotifyRxEvt:
+					c.rxNotify(msg)
+				}
+			}
+		}
+	}()
+
+	return nil
+}
+
+func (c *Conn) startConnecting() error {
+	c.mtx.Lock()
+	defer c.mtx.Unlock()
+
+	if c.stopChan == nil {
+		return fmt.Errorf("Attempt to re-use conn object")
+	}
+
+	if c.connHandle != BLE_CONN_HANDLE_NONE {
+		return nmxutil.NewSesnAlreadyOpenError(
+			"BLE connection already established")
+	}
+	if c.connecting {
+		return nmxutil.NewSesnAlreadyOpenError(
+			"BLE connection already being established")
+	}
+
+	c.connecting = true
+	return nil
+}
+
+func (c *Conn) stopConnecting() {
+	c.mtx.Lock()
+	defer c.mtx.Unlock()
+
+	c.connecting = false
+}
+
+func (c *Conn) finalizeConnection(connHandle uint16,
+	eventListener *Listener) error {
+
+	c.mtx.Lock()
+	c.connecting = false
+	c.connHandle = connHandle
+	c.mtx.Unlock()
+
+	// Listen for events in the background.
+	if err := c.eventListen(eventListener); err != nil {
+		return err
+	}
+
+	// Listen for notifications in the background.
+	if err := c.notifyListen(); err != nil {
+		return err
+	}
+
+	d, err := ConnFindXact(c.bx, c.connHandle)
+	if err != nil {
+		return err
+	}
+	c.desc = d
+
+	return nil
+}
+
+func (c *Conn) IsConnected() bool {
+	c.mtx.Lock()
+	defer c.mtx.Unlock()
+
+	return c.connHandle != BLE_CONN_HANDLE_NONE
+}
+
+func (c *Conn) Connect(bx *BleXport, ownAddrType BleAddrType, peer BleDev,
+	timeout time.Duration) error {
+
+	if err := c.startConnecting(); err != nil {
+		return err
+	}
+	defer c.stopConnecting()
+
+	r := NewBleConnectReq()
+	r.OwnAddrType = ownAddrType
+	r.PeerAddrType = peer.AddrType
+	r.PeerAddr = peer.Addr
+
+	// Initiating a connection requires dedicated master privileges.
+	if err := c.bx.AcquireMaster(c); err != nil {
+		return err
+	}
+	defer c.bx.ReleaseMaster()
+
+	bl, err := c.rxvr.AddListener("connect", SeqKey(r.Seq))
+	if err != nil {
+		return err
+	}
+
+	// Tell blehostd to initiate connection.
+	connHandle, err := connect(c.bx, bl, r, timeout)
+	if err != nil {
+		bhe := nmxutil.ToBleHost(err)
+		if bhe != nil && bhe.Status == ERR_CODE_EDONE {
+			// Already connected.
+			c.rxvr.RemoveListener("connect", bl)
+			return fmt.Errorf("Already connected to peer %s", peer)
+		} else if !nmxutil.IsXport(err) {
+			// The transport did not restart; always attempt to cancel the
+			// connect operation.  In most cases, the host has already stopped
+			// connecting and will respond with an "ealready" error that can be
+			// ignored.
+			if err := c.connCancel(); err != nil {
+				log.Errorf("Failed to cancel connect in progress: %s",
+					err.Error())
+			}
+		}
+
+		c.rxvr.RemoveListener("connect", bl)
+		return err
+	}
+
+	if err := c.finalizeConnection(connHandle, bl); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (c *Conn) Inherit(connHandle uint16, bl *Listener) error {
+	if err := c.startConnecting(); err != nil {
+		return err
+	}
+
+	if err := c.finalizeConnection(connHandle, bl); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (c *Conn) ConnInfo() BleConnDesc {
+	return c.desc
+}
+
+func (c *Conn) AttMtu() int {
+	return c.attMtu
+}
+
+func (c *Conn) discAllDscsOnce(startHandle uint16, endHandle uint16) (
+	[]*Descriptor, error) {
+
+	r := NewBleDiscAllDscsReq()
+	r.ConnHandle = c.connHandle
+	r.StartHandle = int(startHandle)
+	r.EndHandle = int(endHandle)
+
+	bl, err := c.rxvr.AddListener("disc-all-dscs", SeqKey(r.Seq))
+	if err != nil {
+		return nil, err
+	}
+	defer c.rxvr.RemoveListener("disc-all-dscs", bl)
+
+	rawDscs, err := discAllDscs(c.bx, bl, r)
+	if err != nil {
+		return nil, err
+	}
+
+	dscs := make([]*Descriptor, len(rawDscs))
+	for i, rd := range rawDscs {
+		dscs[i] = &Descriptor{
+			Uuid:   rd.Uuid,
+			Handle: uint16(rd.Handle),
+		}
+	}
+
+	return dscs, nil
+}
+
+func (c *Conn) discAllDscs(chrs []*Characteristic, svcEndHandle uint16) error {
+	for i, _ := range chrs {
+		chr := chrs[i]
+
+		// Only discover descriptors if this characteristic has a CCCD.
+		if chr.SubscribeType() != 0 {
+			var endHandle uint16
+			if i < len(chrs)-1 {
+				endHandle = chrs[i+1].DefHandle - 1
+			} else {
+				endHandle = svcEndHandle
+			}
+
+			dscs, err := c.discAllDscsOnce(chrs[i].ValHandle, endHandle)
+			if err != nil {
+				return err
+			}
+
+			chr.Dscs = dscs
+		}
+	}
+
+	return nil
+}
+
+func (c *Conn) discAllChrsOnce(svc Service) ([]*Characteristic, error) {
+	r := NewBleDiscAllChrsReq()
+	r.ConnHandle = c.connHandle
+	r.StartHandle = int(svc.StartHandle)
+	r.EndHandle = int(svc.EndHandle)
+
+	bl, err := c.rxvr.AddListener("disc-all-chrs", SeqKey(r.Seq))
+	if err != nil {
+		return nil, err
+	}
+	defer c.rxvr.RemoveListener("disc-all-chrs", bl)
+
+	rawChrs, err := discAllChrs(c.bx, bl, r)
+	if err != nil {
+		return nil, err
+	}
+
+	chrs := make([]*Characteristic, len(rawChrs))
+	for i, rc := range rawChrs {
+		chrs[i] = &Characteristic{
+			Uuid:       rc.Uuid,
+			DefHandle:  uint16(rc.DefHandle),
+			ValHandle:  uint16(rc.ValHandle),
+			Properties: BleChrFlags(rc.Properties),
+		}
+	}
+
+	if err := c.discAllDscs(chrs, svc.EndHandle); err != nil {
+		return nil, err
+	}
+
+	return chrs, nil
+}
+
+func (c *Conn) discAllChrs(svcs []Service) error {
+	for i, _ := range svcs {
+		chrs, err := c.discAllChrsOnce(svcs[i])
+		if err != nil {
+			return err
+		}
+
+		svcs[i].Chrs = chrs
+	}
+
+	return nil
+}
+
+func (c *Conn) discAllSvcs() ([]Service, error) {
+	r := NewBleDiscAllSvcsReq()
+	r.ConnHandle = c.connHandle
+
+	bl, err := c.rxvr.AddListener("disc-all-svcs", SeqKey(r.Seq))
+	if err != nil {
+		return nil, err
+	}
+	defer c.rxvr.RemoveListener("disc-all-svcs", bl)
+
+	rawSvcs, err := discAllSvcs(c.bx, bl, r)
+	if err != nil {
+		return nil, err
+	}
+
+	svcs := make([]Service, len(rawSvcs))
+	for i, rs := range rawSvcs {
+		svcs[i] = Service{
+			Uuid:        rs.Uuid,
+			StartHandle: uint16(rs.StartHandle),
+			EndHandle:   uint16(rs.EndHandle),
+		}
+	}
+
+	return svcs, nil
+}
+
+func (c *Conn) DiscoverSvcs() error {
+	svcs, err := c.discAllSvcs()
+	if err != nil {
+		return err
+	}
+
+	if err := c.discAllChrs(svcs); err != nil {
+		return err
+	}
+
+	c.profile.SetServices(svcs)
+
+	return nil
+}
+
+func (c *Conn) Profile() *Profile {
+	return &c.profile
+}
+
+func isExchangeMtuError(err error) bool {
+	if err == nil {
+		return false
+	}
+
+	// If the operation failed because the peer already initiated the
+	// exchange, just pretend it was successful.
+	bhe := nmxutil.ToBleHost(err)
+	if bhe == nil {
+		return true
+	}
+
+	switch bhe.Status {
+	case ERR_CODE_EALREADY:
+		return false
+	case ERR_CODE_ATT_BASE + ERR_CODE_ATT_REQ_NOT_SUPPORTED:
+		return false
+	default:
+		return true
+	}
+}
+
+func (c *Conn) ExchangeMtu() error {
+	r := NewBleExchangeMtuReq()
+	r.ConnHandle = c.connHandle
+
+	bl, err := c.rxvr.AddListener("exchange-mtu", SeqKey(r.Seq))
+	if err != nil {
+		return err
+	}
+	defer c.rxvr.RemoveListener("exchange-mtu", bl)
+
+	mtu, err := exchangeMtu(c.bx, bl, r)
+	if isExchangeMtuError(err) {
+		return err
+	}
+
+	c.attMtu = mtu
+	return nil
+}
+
+func (c *Conn) WriteHandle(handle uint16, payload []byte,
+	name string) error {
+
+	r := NewBleWriteReq()
+	r.ConnHandle = c.connHandle
+	r.AttrHandle = int(handle)
+	r.Data.Bytes = payload
+
+	bl, err := c.rxvr.AddListener(name, SeqKey(r.Seq))
+	if err != nil {
+		return err
+	}
+	defer c.rxvr.RemoveListener(name, bl)
+
+	if err := write(c.bx, bl, r); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (c *Conn) WriteHandleNoRsp(handle uint16, payload []byte,
+	name string) error {
+
+	r := NewBleWriteCmdReq()
+	r.ConnHandle = c.connHandle
+	r.AttrHandle = int(handle)
+	r.Data.Bytes = payload
+
+	bl, err := c.rxvr.AddListener(name, SeqKey(r.Seq))
+	if err != nil {
+		return err
+	}
+	defer c.rxvr.RemoveListener(name, bl)
+
+	if err := writeCmd(c.bx, bl, r); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (c *Conn) WriteChr(chr *Characteristic, payload []byte,
+	name string) error {
+
+	return c.WriteHandle(chr.ValHandle, payload, name)
+}
+
+func (c *Conn) WriteChrNoRsp(chr *Characteristic, payload []byte,
+	name string) error {
+
+	return c.WriteHandleNoRsp(chr.ValHandle, payload, name)
+}
+
+func (c *Conn) Subscribe(chr *Characteristic) error {
+	uuid := BleUuid{CccdUuid, [16]byte{}}
+	dsc := FindDscByUuid(chr, uuid)
+	if dsc == nil {
+		return fmt.Errorf("Cannot subscribe to characteristic %s; no CCCD",
+			chr.Uuid.String())
+	}
+
+	var payload []byte
+	switch chr.SubscribeType() {
+	case BLE_GATT_F_NOTIFY:
+		payload = []byte{1, 0}
+	case BLE_GATT_F_INDICATE:
+		payload = []byte{2, 0}
+	default:
+		return fmt.Errorf("Cannot subscribe to characteristic %s; "+
+			"properties indicate unsubscribable", chr.Uuid.String())
+	}
+
+	return c.WriteHandle(dsc.Handle, payload, "subscribe")
+}
+
+func (c *Conn) ListenForNotifications(chr *Characteristic) *NotifyListener {
+	c.mtx.Lock()
+	defer c.mtx.Unlock()
+
+	nl := NewNotifyListener()
+	slice := c.notifyMap[chr]
+
+	slice = append(slice, nl)
+	c.notifyMap[chr] = slice
+
+	return nl
+}
+
+func (c *Conn) terminate() error {
+	r := NewBleTerminateReq()
+	r.ConnHandle = c.connHandle
+	r.HciReason = ERR_CODE_HCI_REM_USER_CONN_TERM
+
+	bl, err := c.rxvr.AddListener("terminate", SeqKey(r.Seq))
+	if err != nil {
+		return err
+	}
+	defer c.rxvr.RemoveListener("terminate", bl)
+
+	if err := terminate(c.bx, bl, r); err != nil {
+		// Ignore ealready errors.
+		bhdErr := nmxutil.ToBleHost(err)
+		if bhdErr == nil || bhdErr.Status != ERR_CODE_EALREADY {
+			return err
+		}
+	}
+
+	return nil
+}
+
+func (c *Conn) connCancel() error {
+	r := NewBleConnCancelReq()
+
+	bl, err := c.rxvr.AddListener("conn-cancel", SeqKey(r.Seq))
+	if err != nil {
+		return err
+	}
+	defer c.rxvr.RemoveListener("conn-cancel", bl)
+
+	if err := connCancel(c.bx, bl, r); err != nil {
+		// Ignore ealready errors.
+		bhdErr := nmxutil.ToBleHost(err)
+		if bhdErr == nil || bhdErr.Status != ERR_CODE_EALREADY {
+			return err
+		}
+	}
+
+	return nil
+}
+
+func (c *Conn) Stop() error {
+	c.mtx.Lock()
+	defer c.mtx.Unlock()
+
+	var err error
+	if c.connHandle != BLE_CONN_HANDLE_NONE {
+		err = c.terminate()
+	} else if c.connecting {
+		err = c.connCancel()
+	}
+
+	if err != nil {
+		// Something went unexpectedly wrong.  Just force a shutdown.
+		go c.shutdown(err)
+	}
+
+	return nil
+}
diff --git a/nmxact/nmble/dispatch.go b/nmxact/nmble/dispatch.go
index b097078..ffdb1d4 100644
--- a/nmxact/nmble/dispatch.go
+++ b/nmxact/nmble/dispatch.go
@@ -61,6 +61,8 @@ func discAllSvcsRspCtor() Msg      { return &BleDiscAllSvcsRsp{} }
 func discSvcUuidRspCtor() Msg      { return &BleDiscSvcUuidRsp{} }
 func discAllChrsRspCtor() Msg      { return &BleDiscAllChrsRsp{} }
 func discChrUuidRspCtor() Msg      { return &BleDiscChrUuidRsp{} }
+func discAllDscsRspCtor() Msg      { return &BleDiscAllDscsRsp{} }
+func writeRspCtor() Msg            { return &BleWriteRsp{} }
 func writeCmdRspCtor() Msg         { return &BleWriteCmdRsp{} }
 func exchangeMtuRspCtor() Msg      { return &BleExchangeMtuRsp{} }
 func genRandAddrRspCtor() Msg      { return &BleGenRandAddrRsp{} }
@@ -89,6 +91,8 @@ func connectEvtCtor() Msg    { return &BleConnectEvt{} }
 func disconnectEvtCtor() Msg { return &BleDisconnectEvt{} }
 func discSvcEvtCtor() Msg    { return &BleDiscSvcEvt{} }
 func discChrEvtCtor() Msg    { return &BleDiscChrEvt{} }
+func discDscEvtCtor() Msg    { return &BleDiscDscEvt{} }
+func writeAckEvtCtor() Msg   { return &BleWriteAckEvt{} }
 func notifyRxEvtCtor() Msg   { return &BleNotifyRxEvt{} }
 func mtuChangeEvtCtor() Msg  { return &BleMtuChangeEvt{} }
 func scanEvtCtor() Msg       { return &BleScanEvt{} }
@@ -104,8 +108,10 @@ var msgCtorMap = map[OpTypePair]msgCtor{
 	{MSG_OP_RSP, MSG_TYPE_TERMINATE}:         terminateRspCtor,
 	{MSG_OP_RSP, MSG_TYPE_DISC_ALL_SVCS}:     discAllSvcsRspCtor,
 	{MSG_OP_RSP, MSG_TYPE_DISC_SVC_UUID}:     discSvcUuidRspCtor,
-	{MSG_OP_RSP, MSG_TYPE_DISC_CHR_UUID}:     discChrUuidRspCtor,
 	{MSG_OP_RSP, MSG_TYPE_DISC_ALL_CHRS}:     discAllChrsRspCtor,
+	{MSG_OP_RSP, MSG_TYPE_DISC_CHR_UUID}:     discChrUuidRspCtor,
+	{MSG_OP_RSP, MSG_TYPE_DISC_ALL_DSCS}:     discAllDscsRspCtor,
+	{MSG_OP_RSP, MSG_TYPE_WRITE}:             writeRspCtor,
 	{MSG_OP_RSP, MSG_TYPE_WRITE_CMD}:         writeCmdRspCtor,
 	{MSG_OP_RSP, MSG_TYPE_EXCHANGE_MTU}:      exchangeMtuRspCtor,
 	{MSG_OP_RSP, MSG_TYPE_GEN_RAND_ADDR}:     genRandAddrRspCtor,
@@ -134,6 +140,8 @@ var msgCtorMap = map[OpTypePair]msgCtor{
 	{MSG_OP_EVT, MSG_TYPE_DISCONNECT_EVT}: disconnectEvtCtor,
 	{MSG_OP_EVT, MSG_TYPE_DISC_SVC_EVT}:   discSvcEvtCtor,
 	{MSG_OP_EVT, MSG_TYPE_DISC_CHR_EVT}:   discChrEvtCtor,
+	{MSG_OP_EVT, MSG_TYPE_DISC_DSC_EVT}:   discDscEvtCtor,
+	{MSG_OP_EVT, MSG_TYPE_WRITE_ACK_EVT}:  writeAckEvtCtor,
 	{MSG_OP_EVT, MSG_TYPE_NOTIFY_RX_EVT}:  notifyRxEvtCtor,
 	{MSG_OP_EVT, MSG_TYPE_MTU_CHANGE_EVT}: mtuChangeEvtCtor,
 	{MSG_OP_EVT, MSG_TYPE_SCAN_EVT}:       scanEvtCtor,
diff --git a/nmxact/nmble/profile.go b/nmxact/nmble/profile.go
new file mode 100644
index 0000000..0e98740
--- /dev/null
+++ b/nmxact/nmble/profile.go
@@ -0,0 +1,82 @@
+package nmble
+
+import (
+	. "mynewt.apache.org/newtmgr/nmxact/bledefs"
+)
+
+type Descriptor struct {
+	Uuid     BleUuid
+	Handle   uint16
+	AttFlags BleAttFlags
+}
+
+type Characteristic struct {
+	Uuid       BleUuid
+	DefHandle  uint16
+	ValHandle  uint16
+	Properties BleChrFlags
+	Dscs       []*Descriptor
+}
+
+type Service struct {
+	Uuid        BleUuid
+	StartHandle uint16
+	EndHandle   uint16
+	Chrs        []*Characteristic
+}
+
+type Profile struct {
+	svcs  []Service
+	chrs  map[BleChrId]*Characteristic
+	attrs map[uint16]*Characteristic
+}
+
+func (c *Characteristic) SubscribeType() BleChrFlags {
+	if c.Properties&BLE_GATT_F_NOTIFY != 0 {
+		return BLE_GATT_F_NOTIFY
+	} else {
+		return c.Properties & BLE_GATT_F_INDICATE
+	}
+}
+
+func NewProfile() Profile {
+	return Profile{
+		chrs:  map[BleChrId]*Characteristic{},
+		attrs: map[uint16]*Characteristic{},
+	}
+}
+
+func (p *Profile) Services() []Service {
+	return p.svcs
+}
+
+func (p *Profile) SetServices(svcs []Service) {
+	p.svcs = svcs
+	p.chrs = map[BleChrId]*Characteristic{}
+	p.attrs = map[uint16]*Characteristic{}
+
+	for _, s := range svcs {
+		for _, c := range s.Chrs {
+			p.chrs[BleChrId{s.Uuid, c.Uuid}] = c
+			p.attrs[uint16(c.ValHandle)] = c
+		}
+	}
+}
+
+func (p *Profile) FindChrByUuid(id BleChrId) *Characteristic {
+	return p.chrs[id]
+}
+
+func (p *Profile) FindChrByHandle(handle uint16) *Characteristic {
+	return p.attrs[handle]
+}
+
+func FindDscByUuid(chr *Characteristic, uuid BleUuid) *Descriptor {
+	for _, d := range chr.Dscs {
+		if CompareUuids(uuid, d.Uuid) == 0 {
+			return d
+		}
+	}
+
+	return nil
+}
diff --git a/nmxact/nmble/transceiver.go b/nmxact/nmble/transceiver.go
deleted file mode 100644
index 34705b0..0000000
--- a/nmxact/nmble/transceiver.go
+++ /dev/null
@@ -1,256 +0,0 @@
-package nmble
-
-import (
-	"encoding/hex"
-	"fmt"
-	"sync"
-	"time"
-
-	log "github.com/Sirupsen/logrus"
-	"github.com/runtimeco/go-coap"
-
-	"mynewt.apache.org/newtmgr/nmxact/nmp"
-	"mynewt.apache.org/newtmgr/nmxact/nmxutil"
-	"mynewt.apache.org/newtmgr/nmxact/oic"
-	"mynewt.apache.org/newtmgr/nmxact/omp"
-	"mynewt.apache.org/newtmgr/nmxact/sesn"
-)
-
-type Transceiver struct {
-	connHandle uint16
-	bx         *BleXport
-	rxer       *Receiver
-	nd         *nmp.Dispatcher
-	od         *omp.Dispatcher
-
-	wg         sync.WaitGroup
-	nmpErrChan chan error
-}
-
-func NewTransceiver(id uint32, connHandle uint16, mgmtProto sesn.MgmtProto,
-	bx *BleXport, rxer *Receiver, logDepth int) (*Transceiver, error) {
-
-	t := &Transceiver{
-		connHandle: connHandle,
-		bx:         bx,
-		rxer:       rxer,
-	}
-
-	switch mgmtProto {
-	case sesn.MGMT_PROTO_NMP:
-		t.nd = nmp.NewDispatcher(logDepth)
-
-	case sesn.MGMT_PROTO_OMP:
-		od, err := omp.NewDispatcher(true, logDepth)
-		if err != nil {
-			return nil, err
-		}
-		t.od = od
-
-	default:
-		return nil,
-			fmt.Errorf("Invalid session management protocol: %#v", mgmtProto)
-	}
-
-	return t, nil
-}
-
-func (t *Transceiver) WriteCmd(attHandle uint16, payload []byte,
-	name string) error {
-
-	r := NewBleWriteCmdReq()
-	r.ConnHandle = t.connHandle
-	r.AttrHandle = int(attHandle)
-	r.Data.Bytes = payload
-
-	bl, err := t.rxer.AddListener(name, SeqKey(r.Seq))
-	if err != nil {
-		return err
-	}
-	defer t.rxer.RemoveListener(name, bl)
-
-	if err := writeCmd(t.bx, bl, r); err != nil {
-		return err
-	}
-
-	return nil
-}
-
-func (t *Transceiver) txPlain(attHandle uint16, req *nmp.NmpMsg,
-	timeout time.Duration) (nmp.NmpRsp, error) {
-
-	nl, err := t.nd.AddListener(req.Hdr.Seq)
-	if err != nil {
-		return nil, err
-	}
-	defer t.nd.RemoveListener(req.Hdr.Seq)
-
-	b, err := nmp.EncodeNmpPlain(req)
-	if err != nil {
-		return nil, err
-	}
-
-	log.Debugf("Tx NMP request: %s", hex.Dump(b))
-
-	if err := t.WriteCmd(attHandle, b, "nmp-rsp"); err != nil {
-		return nil, err
-	}
-
-	// Now wait for NMP response.
-	for {
-		select {
-		case err := <-nl.ErrChan:
-			return nil, err
-		case rsp := <-nl.RspChan:
-			return rsp, nil
-		case <-nl.AfterTimeout(timeout):
-			return nil, nmxutil.NewRspTimeoutError("NMP timeout")
-		}
-	}
-}
-
-func (t *Transceiver) txOmp(attHandle uint16, req *nmp.NmpMsg,
-	timeout time.Duration) (nmp.NmpRsp, error) {
-
-	nl, err := t.od.AddNmpListener(req.Hdr.Seq)
-	if err != nil {
-		return nil, err
-	}
-	defer t.od.RemoveNmpListener(req.Hdr.Seq)
-
-	b, err := omp.EncodeOmpTcp(req)
-	if err != nil {
-		return nil, err
-	}
-
-	log.Debugf("Tx OMP request: %s", hex.Dump(b))
-
-	if err := t.WriteCmd(attHandle, b, "omp-rsp"); err != nil {
-		return nil, err
-	}
-
-	// Now wait for NMP response.
-	for {
-		select {
-		case err := <-nl.ErrChan:
-			return nil, err
-		case rsp := <-nl.RspChan:
-			return rsp, nil
-		case <-nl.AfterTimeout(timeout):
-			return nil, nmxutil.NewRspTimeoutError("NMP timeout")
-		}
-	}
-}
-
-func (t *Transceiver) TxNmp(attHandle uint16, req *nmp.NmpMsg,
-	timeout time.Duration) (nmp.NmpRsp, error) {
-
-	if t.nd != nil {
-		return t.txPlain(attHandle, req, timeout)
-	} else {
-		return t.txOmp(attHandle, req, timeout)
-	}
-}
-
-func (t *Transceiver) TxOic(attHandle uint16, req coap.Message,
-	timeout time.Duration) (coap.Message, error) {
-
-	b, err := oic.Encode(req)
-	if err != nil {
-		return nil, err
-	}
-
-	// XXX: Only listen if message expects an ack.
-
-	ol, err := t.od.AddOicListener(req.Token())
-	if err != nil {
-		return nil, err
-	}
-	defer t.od.RemoveOicListener(req.Token())
-
-	log.Debugf("Tx OIC request: %s", hex.Dump(b))
-	if err := t.WriteCmd(attHandle, b, "oic-rsp"); err != nil {
-		return nil, err
-	}
-
-	for {
-		select {
-		case err := <-ol.ErrChan:
-			return nil, err
-		case rsp := <-ol.RspChan:
-			return rsp, nil
-		case <-ol.AfterTimeout(timeout):
-			return nil, nmxutil.NewRspTimeoutError("OIC timeout")
-		}
-	}
-}
-
-func (t *Transceiver) NmpRspListen(attHandle uint16) (<-chan error, error) {
-	if t.nmpErrChan != nil {
-		return t.nmpErrChan, nil
-	}
-
-	key := TchKey(MSG_TYPE_NOTIFY_RX_EVT, int(t.connHandle))
-	bl, err := t.rxer.AddListener("nmp-rsp", key)
-	if err != nil {
-		return nil, err
-	}
-
-	t.wg.Add(1)
-
-	t.nmpErrChan = make(chan error)
-
-	go func() {
-		defer t.wg.Done()
-		defer t.rxer.RemoveListener("nmp-rsp", bl)
-
-		for {
-			select {
-			case err, ok := <-bl.ErrChan:
-				if !ok {
-					return
-				}
-				t.nmpErrChan <- err
-
-			case bm := <-bl.MsgChan:
-				switch msg := bm.(type) {
-				case *BleNotifyRxEvt:
-					if msg.AttrHandle == int(attHandle) {
-						if t.nd != nil {
-							t.nd.Dispatch(msg.Data.Bytes)
-						} else {
-							t.od.Dispatch(msg.Data.Bytes)
-						}
-					} else {
-						// XXX: Forward non-management notifications elsewhere.
-					}
-				}
-			}
-		}
-	}()
-	return t.nmpErrChan, nil
-}
-
-func (t *Transceiver) Stop(err error) {
-	if t.nd != nil {
-		t.nd.ErrorAll(err)
-	} else {
-		t.od.ErrorAll(err)
-		t.od.Stop()
-	}
-
-	t.wg.Wait()
-
-	ch := t.nmpErrChan
-	if ch != nil {
-		close(ch)
-	}
-}
-
-func (t *Transceiver) AbortNmpRx(seq uint8) error {
-	return t.nd.ErrorOne(seq, fmt.Errorf("Rx aborted"))
-}
-
-func (t *Transceiver) AbortOmpRx(seq uint8) error {
-	return t.od.ErrorOneNmp(seq, fmt.Errorf("Rx aborted"))
-}
diff --git a/nmxact/nmserial/serial_oic_sesn.go b/nmxact/nmserial/serial_oic_sesn.go
index fd38477..8279065 100644
--- a/nmxact/nmserial/serial_oic_sesn.go
+++ b/nmxact/nmserial/serial_oic_sesn.go
@@ -152,8 +152,8 @@ func (sos *SerialOicSesn) TxNmpOnce(m *nmp.NmpMsg, opt sesn.TxOptions) (
 	}
 }
 
-func (sos *SerialOicSesn) GetResourceOnce(uri string, opt sesn.TxOptions) (
-	coap.COAPCode, []byte, error) {
+func (sos *SerialOicSesn) GetResourceOnce(resType sesn.ResourceType,
+	uri string, opt sesn.TxOptions) (coap.COAPCode, []byte, error) {
 
 	token := nmxutil.NextToken()
 
diff --git a/nmxact/nmserial/serial_plain_sesn.go b/nmxact/nmserial/serial_plain_sesn.go
index 6348410..5f219da 100644
--- a/nmxact/nmserial/serial_plain_sesn.go
+++ b/nmxact/nmserial/serial_plain_sesn.go
@@ -157,8 +157,8 @@ func (sps *SerialPlainSesn) TxNmpOnce(m *nmp.NmpMsg, opt sesn.TxOptions) (
 	}
 }
 
-func (sps *SerialPlainSesn) GetResourceOnce(uri string, opt sesn.TxOptions) (
-	coap.COAPCode, []byte, error) {
+func (sps *SerialPlainSesn) GetResourceOnce(resType sesn.ResourceType,
+	uri string, opt sesn.TxOptions) (coap.COAPCode, []byte, error) {
 
 	return 0, nil, fmt.Errorf("SerialPlainSesn.GetResourceOnce() unsupported")
 }
diff --git a/nmxact/nmxutil/nmxutil.go b/nmxact/nmxutil/nmxutil.go
index 72bd00e..d95c368 100644
--- a/nmxact/nmxutil/nmxutil.go
+++ b/nmxact/nmxutil/nmxutil.go
@@ -27,6 +27,7 @@ import (
 	"path"
 	"runtime"
 	"sync"
+	"sync/atomic"
 	"time"
 
 	log "github.com/Sirupsen/logrus"
@@ -115,6 +116,12 @@ func DecodeCborMap(cbor []byte) (map[string]interface{}, error) {
 	return m, nil
 }
 
+var nextId uint32
+
+func GetNextId() uint32 {
+	return atomic.AddUint32(&nextId, 1) - 1
+}
+
 func LogListener(parentLevel int, title string, extra string) {
 	_, file, line, _ := runtime.Caller(parentLevel)
 	file = path.Base(file)
diff --git a/nmxact/oic/oic.go b/nmxact/oic/oic.go
index 808eb49..f5052ed 100644
--- a/nmxact/oic/oic.go
+++ b/nmxact/oic/oic.go
@@ -26,14 +26,6 @@ import (
 	"github.com/runtimeco/go-coap"
 )
 
-type ResType int
-
-const (
-	RES_TYPE_PUBLIC ResType = iota
-	RES_TYPE_GW
-	RES_TYPE_PRIVATE
-)
-
 var messageIdMtx sync.Mutex
 var nextMessageId uint16
 
diff --git a/nmxact/oic/resmgr.go b/nmxact/oic/resmgr.go
index 9a1a651..467204d 100644
--- a/nmxact/oic/resmgr.go
+++ b/nmxact/oic/resmgr.go
@@ -7,8 +7,8 @@ import (
 	"github.com/runtimeco/go-coap"
 )
 
-type ResWriteFn func(data []byte) coap.COAPCode
-type ResReadFn func(data []byte) (coap.COAPCode, []byte)
+type ResWriteFn func(uri string, data []byte) coap.COAPCode
+type ResReadFn func(uri string, data []byte) (coap.COAPCode, []byte)
 
 type Resource struct {
 	Name    string
@@ -55,14 +55,14 @@ func (rm *ResMgr) Access(m coap.Message) (coap.COAPCode, []byte) {
 		if r.ReadCb == nil {
 			return coap.MethodNotAllowed, nil
 		} else {
-			return r.ReadCb(m.Payload())
+			return r.ReadCb(path, m.Payload())
 		}
 
 	case coap.PUT:
 		if r.WriteCb == nil {
 			return coap.MethodNotAllowed, nil
 		} else {
-			return r.WriteCb(m.Payload()), nil
+			return r.WriteCb(path, m.Payload()), nil
 		}
 
 	default:
diff --git a/nmxact/omp/frag.go b/nmxact/omp/frag.go
deleted file mode 100644
index 0ad8e91..0000000
--- a/nmxact/omp/frag.go
+++ /dev/null
@@ -1,51 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *  http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-package omp
-
-import (
-	log "github.com/Sirupsen/logrus"
-	"github.com/runtimeco/go-coap"
-)
-
-type Reassembler struct {
-	cur []byte
-}
-
-func NewReassembler() *Reassembler {
-	return &Reassembler{}
-}
-
-func (r *Reassembler) RxFrag(frag []byte) *coap.TcpMessage {
-	r.cur = append(r.cur, frag...)
-
-	var tm *coap.TcpMessage
-	var err error
-	tm, r.cur, err = coap.PullTcp(r.cur)
-	if err != nil {
-		log.Debugf("received invalid CoAP-TCP packet: %s", err.Error())
-		return nil
-	}
-
-	if tm != nil {
-		r.cur = nil
-	}
-
-	return tm
-}
diff --git a/nmxact/sesn/sesn.go b/nmxact/sesn/sesn.go
index 3b3a059..8db9438 100644
--- a/nmxact/sesn/sesn.go
+++ b/nmxact/sesn/sesn.go
@@ -27,14 +27,6 @@ import (
 	"mynewt.apache.org/newtmgr/nmxact/nmp"
 )
 
-type ResourceType int
-
-const (
-	RES_TYPE_PUBLIC ResourceType = iota
-	RES_TYPE_GATEWAY
-	RES_TYPE_PRIVATE
-)
-
 type TxOptions struct {
 	Timeout time.Duration
 	Tries   int
@@ -102,6 +94,8 @@ type Sesn interface {
 	//     * other error
 	TxNmpOnce(m *nmp.NmpMsg, opt TxOptions) (nmp.NmpRsp, error)
 
-	GetResourceOnce(uri string, opt TxOptions) (coap.COAPCode, []byte, error)
+	GetResourceOnce(resType ResourceType, uri string, opt TxOptions) (
+		coap.COAPCode, []byte, error)
+
 	//SetResource(uri string, value []byte, opt TxOptions) error
 }
diff --git a/nmxact/sesn/sesn_cfg.go b/nmxact/sesn/sesn_cfg.go
index 7c11d33..c3e84f4 100644
--- a/nmxact/sesn/sesn_cfg.go
+++ b/nmxact/sesn/sesn_cfg.go
@@ -20,6 +20,7 @@
 package sesn
 
 import (
+	"fmt"
 	"time"
 
 	"mynewt.apache.org/newtmgr/nmxact/bledefs"
@@ -32,6 +33,34 @@ const (
 	MGMT_PROTO_OMP
 )
 
+type ResourceType int
+
+const (
+	RES_TYPE_PUBLIC ResourceType = iota
+	RES_TYPE_UNAUTH
+	RES_TYPE_SECURE
+)
+
+var resTypeMap = map[ResourceType]string{
+	RES_TYPE_PUBLIC: "public",
+	RES_TYPE_UNAUTH: "unauth",
+	RES_TYPE_SECURE: "secure",
+}
+
+func (r ResourceType) String() string {
+	return resTypeMap[r]
+}
+
+func ParseResType(s string) (ResourceType, error) {
+	for r, n := range resTypeMap {
+		if s == n {
+			return r, nil
+		}
+	}
+
+	return ResourceType(0), fmt.Errorf("Unknown resource type: %s", s)
+}
+
 type OnCloseFn func(s Sesn, err error)
 
 type PeerSpec struct {
diff --git a/nmxact/sesn/sesn_util.go b/nmxact/sesn/sesn_util.go
index b964639..6399ee9 100644
--- a/nmxact/sesn/sesn_util.go
+++ b/nmxact/sesn/sesn_util.go
@@ -40,10 +40,12 @@ func TxNmp(s Sesn, m *nmp.NmpMsg, o TxOptions) (nmp.NmpRsp, error) {
 	}
 }
 
-func GetResource(s Sesn, uri string, o TxOptions) (coap.COAPCode, []byte, error) {
+func GetResource(s Sesn, resType ResourceType, uri string, o TxOptions) (
+	coap.COAPCode, []byte, error) {
+
 	retries := o.Tries - 1
 	for i := 0; ; i++ {
-		code, r, err := s.GetResourceOnce(uri, o)
+		code, r, err := s.GetResourceOnce(resType, uri, o)
 		if err == nil {
 			return code, r, nil
 		}
diff --git a/nmxact/udp/udp_oic_sesn.go b/nmxact/udp/udp_oic_sesn.go
index da1c68c..9690221 100644
--- a/nmxact/udp/udp_oic_sesn.go
+++ b/nmxact/udp/udp_oic_sesn.go
@@ -145,8 +145,8 @@ func (uos *UdpOicSesn) AbortRx(seq uint8) error {
 	return nil
 }
 
-func (uos *UdpOicSesn) GetResourceOnce(uri string, opt sesn.TxOptions) (
-	coap.COAPCode, []byte, error) {
+func (uos *UdpOicSesn) GetResourceOnce(resType sesn.ResourceType, uri string,
+	opt sesn.TxOptions) (coap.COAPCode, []byte, error) {
 
 	token := nmxutil.NextToken()
 
diff --git a/nmxact/udp/udp_plain_sesn.go b/nmxact/udp/udp_plain_sesn.go
index 9e0ce2d..4f79310 100644
--- a/nmxact/udp/udp_plain_sesn.go
+++ b/nmxact/udp/udp_plain_sesn.go
@@ -134,8 +134,8 @@ func (ups *UdpPlainSesn) AbortRx(seq uint8) error {
 	return ups.d.ErrorOne(seq, fmt.Errorf("Rx aborted"))
 }
 
-func (ups *UdpPlainSesn) GetResourceOnce(uri string, opt sesn.TxOptions) (
-	coap.COAPCode, []byte, error) {
+func (ups *UdpPlainSesn) GetResourceOnce(resType sesn.ResourceType,
+	uri string, opt sesn.TxOptions) (coap.COAPCode, []byte, error) {
 
 	return 0, nil, fmt.Errorf("UdpPlainSesn.GetResourceOnce() unsupported")
 }
diff --git a/nmxact/xact/getres.go b/nmxact/xact/getres.go
index 1bf85bc..e56067d 100644
--- a/nmxact/xact/getres.go
+++ b/nmxact/xact/getres.go
@@ -55,7 +55,7 @@ func (r *GetResResult) Status() int {
 }
 
 func (c *GetResCmd) Run(s sesn.Sesn) (Result, error) {
-	status, val, err := sesn.GetResource(s, c.Uri, c.TxOptions())
+	status, val, err := sesn.GetResource(s, c.Typ, c.Uri, c.TxOptions())
 	if err != nil {
 		return nil, err
 	}

-- 
To stop receiving notification emails like this one, please contact
"commits@mynewt.apache.org" <co...@mynewt.apache.org>.