You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mynewt.apache.org by st...@apache.org on 2015/12/15 07:46:00 UTC

[2/2] incubator-mynewt-newt git commit: interim commit. adding newtmgr to fetch data remotely from mynewt instances.

interim commit.   adding newtmgr to fetch data remotely from mynewt instances.


Project: http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/commit/1938fa43
Tree: http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/tree/1938fa43
Diff: http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/diff/1938fa43

Branch: refs/heads/master
Commit: 1938fa4307af0c20799e291707065079aef6c4be
Parents: 5785e36
Author: Sterling Hughes <st...@apache.org>
Authored: Thu Dec 10 13:43:54 2015 -0800
Committer: Sterling Hughes <st...@apache.org>
Committed: Mon Dec 14 22:45:37 2015 -0800

----------------------------------------------------------------------
 newtmgr/cli/cp.go               | 145 +++++++++++++++++++++
 newtmgr/newtmgr.go              | 226 ++++++++++++++++++++++++++++++++
 newtmgr/protocol/cmdrunner.go   |  69 ++++++++++
 newtmgr/protocol/nmgr.go        |  88 +++++++++++++
 newtmgr/protocol/stats.go       |  28 ++++
 newtmgr/transport/conn.go       |  71 ++++++++++
 newtmgr/transport/connserial.go | 132 +++++++++++++++++++
 util/cfgdb.go                   | 246 +++++++++++++++++++++++++++++++++++
 util/util.go                    | 129 ++++++++++++++++++
 9 files changed, 1134 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/blob/1938fa43/newtmgr/cli/cp.go
----------------------------------------------------------------------
diff --git a/newtmgr/cli/cp.go b/newtmgr/cli/cp.go
new file mode 100644
index 0000000..88f1c04
--- /dev/null
+++ b/newtmgr/cli/cp.go
@@ -0,0 +1,145 @@
+/*
+ Copyright 2015 Runtime Inc.
+ Licensed 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 cli
+
+import (
+	"git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/util"
+	"github.com/mitchellh/go-homedir"
+	"log"
+)
+
+type CpMgr struct {
+	cDb *util.CfgDb
+}
+
+type ConnProfile struct {
+	Name       string
+	Type       string
+	ConnString string
+}
+
+func NewCpMgr() (*CpMgr, error) {
+	cpm := &CpMgr{}
+
+	if err := cpm.Init(); err != nil {
+		return nil, err
+	}
+
+	return cpm, nil
+}
+
+func (cpm *CpMgr) Init() error {
+	var err error
+
+	dir, err := homedir.Dir()
+	if err != nil {
+		return util.NewNewtError(err.Error())
+	}
+
+	cpm.cDb, err = util.NewCfgDb("cp", dir+"/.newtmgr.cp.db")
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (cpm *CpMgr) GetConnProfileList() ([]*ConnProfile, error) {
+	log.Printf("[DEBUG] Getting list of connection profiles")
+	cpMap, err := cpm.cDb.GetSect("conn_profile_list")
+	if err != nil {
+		return nil, err
+	}
+
+	cpList := make([]*ConnProfile, 0)
+
+	for _, profileName := range cpMap {
+		cp, err := cpm.GetConnProfile(profileName)
+		if err != nil {
+			return nil, err
+		}
+
+		cpList = append(cpList, cp)
+	}
+
+	return cpList, nil
+}
+
+func (cpm *CpMgr) DeleteConnProfile(name string) error {
+	if err := cpm.cDb.DeleteSect("_conn_profile_" + name); err != nil {
+		return err
+	}
+
+	if err := cpm.cDb.DeleteKey("conn_profile_list", name); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (cpm *CpMgr) AddConnProfile(cp *ConnProfile) error {
+	sect := "_conn_profile_" + cp.Name
+	cDb := cpm.cDb
+
+	// First serialize the conn profile into the configuration database
+	cDb.SetKey(sect, "name", cp.Name)
+	cDb.SetKey(sect, "type", cp.Type)
+	cDb.SetKey(sect, "connstring", cp.ConnString)
+
+	// Then write the ConnProfile to the ConnProfileList
+	cDb.SetKey("conn_profile_list", cp.Name, cp.Name)
+
+	return nil
+}
+
+func (cpm *CpMgr) GetConnProfile(pName string) (*ConnProfile, error) {
+	// Each section is a connection profile, key values are the contents
+	// of that section.
+	sectName := "_conn_profile_" + pName
+
+	cpVals, err := cpm.cDb.GetSect(sectName)
+	if err != nil {
+		return nil, err
+	}
+
+	cp, err := NewConnProfile(pName)
+	if err != nil {
+		return nil, err
+	}
+
+	for k, v := range cpVals {
+		switch k {
+		case "name":
+			cp.Name = v
+		case "type":
+			cp.Type = v
+		case "connstring":
+			cp.ConnString = v
+		default:
+			return nil, util.NewNewtError(
+				"Invalid key " + k + " with val " + v)
+		}
+	}
+
+	return cp, nil
+}
+
+func NewConnProfile(pName string) (*ConnProfile, error) {
+	cp := &ConnProfile{}
+	cp.Name = pName
+
+	return cp, nil
+}

http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/blob/1938fa43/newtmgr/newtmgr.go
----------------------------------------------------------------------
diff --git a/newtmgr/newtmgr.go b/newtmgr/newtmgr.go
new file mode 100644
index 0000000..02f0ffa
--- /dev/null
+++ b/newtmgr/newtmgr.go
@@ -0,0 +1,226 @@
+/*
+ Copyright 2015 Runtime Inc.
+ Licensed 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 main
+
+import (
+	"fmt"
+	"git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/newtmgr/cli"
+	"git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/newtmgr/protocol"
+	"git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/newtmgr/transport"
+	"git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/util"
+	"github.com/spf13/cobra"
+	"os"
+	"strings"
+)
+
+var ConnProfileName string
+
+func nmUsage(cmd *cobra.Command, err error) {
+	if err != nil {
+		sErr := err.(*util.NewtError)
+		fmt.Fprintf(os.Stderr, "[DEBUG] %s", sErr.StackTrace)
+	}
+
+	if cmd != nil {
+		cmd.Help()
+	}
+
+	os.Exit(1)
+}
+
+func connProfileAddCmd(cmd *cobra.Command, args []string) {
+	cpm, err := cli.NewCpMgr()
+	if err != nil {
+		nmUsage(cmd, err)
+	}
+
+	name := args[0]
+	cp, err := cli.NewConnProfile(name)
+	if err != nil {
+		nmUsage(cmd, err)
+	}
+
+	for _, vdef := range args[1:] {
+		s := strings.Split(vdef, "=")
+		switch s[0] {
+		case "name":
+			cp.Name = s[1]
+		case "type":
+			cp.Type = s[1]
+		case "connstring":
+			cp.ConnString = s[1]
+		default:
+			nmUsage(cmd, util.NewNewtError("Unknown variable "+s[0]))
+		}
+	}
+
+	if err := cpm.AddConnProfile(cp); err != nil {
+		nmUsage(cmd, err)
+	}
+
+	fmt.Printf("Connection profile %s successfully added\n", name)
+}
+
+func connProfileShowCmd(cmd *cobra.Command, args []string) {
+	cpm, err := cli.NewCpMgr()
+	if err != nil {
+		nmUsage(cmd, err)
+	}
+
+	name := ""
+	if len(args) > 0 {
+		name = args[0]
+	}
+
+	cpList, err := cpm.GetConnProfileList()
+	if err != nil {
+		nmUsage(cmd, err)
+	}
+
+	found := false
+	for _, cp := range cpList {
+		// Print out the connection profile, if name is "" or name
+		// matches cp.Name
+		if name != "" && cp.Name != name {
+			continue
+		}
+
+		if !found {
+			found = true
+			fmt.Printf("Connection profiles: \n")
+		}
+		fmt.Printf("  %s: type=%s, connstring='%s'\n", cp.Name, cp.Type,
+			cp.ConnString)
+	}
+
+	if !found {
+		if name == "" {
+			fmt.Printf("No connection profiles found!\n")
+		} else {
+			fmt.Printf("No connection profiles found matching %s\n", name)
+		}
+	}
+}
+
+func connProfileDelCmd(cmd *cobra.Command, args []string) {
+	cpm, err := cli.NewCpMgr()
+	if err != nil {
+		nmUsage(cmd, err)
+	}
+
+	name := args[0]
+
+	if err := cpm.DeleteConnProfile(name); err != nil {
+		nmUsage(cmd, err)
+	}
+
+	fmt.Printf("Connection profile %s successfully deleted.\n", name)
+}
+
+func connProfileCmd() *cobra.Command {
+	cpCmd := &cobra.Command{
+		Use:   "conn",
+		Short: "Manage newtmgr connection profiles",
+		Run: func(cmd *cobra.Command, args []string) {
+			cmd.Help()
+		},
+	}
+
+	addCmd := &cobra.Command{
+		Use:   "add",
+		Short: "Add a newtmgr connection profile",
+		Run:   connProfileAddCmd,
+	}
+	cpCmd.AddCommand(addCmd)
+
+	deleCmd := &cobra.Command{
+		Use:   "delete",
+		Short: "Delete a newtmgr connection profile",
+		Run:   connProfileDelCmd,
+	}
+	cpCmd.AddCommand(deleCmd)
+
+	showCmd := &cobra.Command{
+		Use:   "show",
+		Short: "Show newtmgr connection profiles",
+		Run:   connProfileShowCmd,
+	}
+	cpCmd.AddCommand(showCmd)
+
+	return cpCmd
+}
+
+func statShowCmd(cmd *cobra.Command, args []string) {
+	cpm, err := cli.NewCpMgr()
+	if err != nil {
+		nmUsage(cmd, err)
+	}
+
+	cp, err := cpm.GetConnProfile(ConnProfileName)
+	if err != nil {
+		nmUsage(cmd, err)
+	}
+
+	conn, err := transport.NewConn(cp)
+	if err != nil {
+		nmUsage(cmd, err)
+	}
+
+	runner, err := protocol.NewCmdRunner(conn)
+	if err != nil {
+		nmUsage(cmd, err)
+	}
+
+	stats, err := runner.Read(protocol.Stats)
+	if err != nil {
+		nmUsage(cmd, err)
+	}
+
+	stats.Display()
+}
+
+func statCmd() *cobra.Command {
+	statCmd := &cobra.Command{
+		Use:   "stat",
+		Short: "Display statistics from a remote newtmgr device",
+		Run:   statShowCmd,
+	}
+
+	return statCmd
+}
+
+func parseCmds() *cobra.Command {
+	nmCmd := &cobra.Command{
+		Use:   "newtmgr",
+		Short: "Newtmgr helps you manage remote instances of the Mynewt OS.",
+		Run: func(cmd *cobra.Command, args []string) {
+			cmd.Help()
+		},
+	}
+
+	nmCmd.PersistentFlags().StringVar(&ConnProfileName, "conn", "c", "",
+		"connection profile to use.")
+
+	nmCmd.AddCommand(connProfileCmd())
+	nmCmd.AddCommand(statCmd())
+
+	return nmCmd
+}
+
+func main() {
+	cmd := parseCmds()
+	cmd.Execute()
+}

http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/blob/1938fa43/newtmgr/protocol/cmdrunner.go
----------------------------------------------------------------------
diff --git a/newtmgr/protocol/cmdrunner.go b/newtmgr/protocol/cmdrunner.go
new file mode 100644
index 0000000..1451f5c
--- /dev/null
+++ b/newtmgr/protocol/cmdrunner.go
@@ -0,0 +1,69 @@
+/*
+ Copyright 2015 Runtime Inc.
+ Licensed 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 protocol
+
+import (
+	"git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/newtmgr/protocol"
+	"git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/newtmgr/transport"
+)
+
+type CmdRunner struct {
+	conn *transport.Conn
+}
+
+func (cr *CmdRunner) ReadReq() (*NmgrReq, error) {
+	pkt, err := cr.conn.ReadPacket()
+	if err != nil {
+		return nil, err
+	}
+
+	nmr, err := DeserializeNmgrReq(pkt.GetBytes())
+	if err != nil {
+		return nil, err
+	}
+
+	return nmr, nil
+}
+
+func (cr *CmdRunner) WriteReq(nmr *NmgrReq) error {
+	data := []byte{}
+
+	data, err := nmr.SerializeRequest(data)
+	if err != nil {
+		return err
+	}
+
+	pkt, err := NewPacket(len(data))
+	if err != nil {
+		return err
+	}
+
+	pkt.AddBytes(data)
+
+	if err := cr.conn.Write(pkt); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func NewCmdRunner(conn *transport.Conn) (*CmdRunner, error) {
+	cmd := &CmdRunner{
+		conn: conn,
+	}
+
+	return cmd, nil
+}

http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/blob/1938fa43/newtmgr/protocol/nmgr.go
----------------------------------------------------------------------
diff --git a/newtmgr/protocol/nmgr.go b/newtmgr/protocol/nmgr.go
new file mode 100644
index 0000000..1e5e910
--- /dev/null
+++ b/newtmgr/protocol/nmgr.go
@@ -0,0 +1,88 @@
+/*
+ Copyright 2015 Runtime Inc.
+ Licensed 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 protocol
+
+import (
+	"encoding/binary"
+)
+
+type NmgrReq struct {
+	Op    uint8
+	Flags uint8
+	Len   uint16
+	Group uint16
+	Id    uint16
+	Data  []byte
+}
+
+const (
+	NMGR_OP_READ      = 0
+	NMGR_OP_READ_RSP  = 1
+	NMGR_OP_WRITE     = 2
+	NMGR_OP_WRITE_RSP = 3
+)
+
+func NewNmgrReq() (*NmgrReq, error) {
+	nmr := &NmgrReq{}
+	nmr.Data = []byte{}
+
+	return nmr, nil
+}
+
+func DeserializeNmgrReq(data []byte) (*NmgrReq, error) {
+	if len(data) < 8 {
+		return nil, util.NewNewtError("Newtmgr request buffer too small " +
+			len(data) + " bytes.")
+	}
+
+	nmr := &NmgrReq{}
+
+	nmr.Op = uint8(data[0])
+	nmr.Flags = uint8(data[1])
+	nmr.Len = binary.BigEndian.Uint16(data[2:4])
+	nmr.Group = binary.BigEndian.Uint16(data[4:6])
+	nmr.Id = binary.BigEndian.Uint16(data[6:8])
+
+	data = data[8:]
+	if int(nmr.Len) != len(data) {
+		return nil, util.NewNewtError("Newtmgr request length doesn't " +
+			"match data length.")
+	}
+
+	copy(nmr.Data, data)
+
+	return nmr, nil
+}
+
+func (nmr *NmgrReq) SerializeRequest(data []byte) ([]byte, error) {
+	u16b := []byte{}
+
+	data = append(data, nmr.Op.(byte))
+	data = append(data, nmr.Flags.(byte))
+
+	binary.BigEndian.PutUint16(u16b, nmr.Len)
+	data = append(data, u16b...)
+
+	binary.BigEndian.PutUint16(u16b, nmr.Group)
+	data = append(data, u16b...)
+
+	binary.BigEndian.PutUint16(u16b, nmr.Id)
+	data = append(data, u16b...)
+
+	data = append(data, nmr.Data...)
+
+	return data, nil
+}

http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/blob/1938fa43/newtmgr/protocol/stats.go
----------------------------------------------------------------------
diff --git a/newtmgr/protocol/stats.go b/newtmgr/protocol/stats.go
new file mode 100644
index 0000000..f1d5f21
--- /dev/null
+++ b/newtmgr/protocol/stats.go
@@ -0,0 +1,28 @@
+/*
+ Copyright 2015 Runtime Inc.
+ Licensed 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 protocol
+
+var Stats, _ = NewStats()
+
+type Stats struct {
+}
+
+func NewStats() (*Stats, error) {
+	s := &Stats{}
+	return s, nil
+}
+
+func (s *Stats) EncodeRequest()

http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/blob/1938fa43/newtmgr/transport/conn.go
----------------------------------------------------------------------
diff --git a/newtmgr/transport/conn.go b/newtmgr/transport/conn.go
new file mode 100644
index 0000000..37d57d8
--- /dev/null
+++ b/newtmgr/transport/conn.go
@@ -0,0 +1,71 @@
+/*
+ Copyright 2015 Runtime Inc.
+ Licensed 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 transport
+
+import (
+	"bytes"
+	"git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/newtmgr/cli"
+	"git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/util"
+)
+
+type Conn interface {
+	Open(cp *ConnProfile) error
+	ReadPacket() (*Packet, error)
+	WritePacket(pkt *Packet) error
+}
+
+type Packet struct {
+	expectedLen uint16
+	readLen     uint16
+	buffer      *bytes.Buffer
+}
+
+func NewPacket(expectedLen uint16) (*Packet, error) {
+	pkt := &Packet{
+		expectedLen: expectedLen,
+		buffer:      bytes.NewBuffer([]byte{}),
+	}
+
+	return pkt, nil
+}
+
+func (pkt *Packet) AddBytes(bytes []byte) bool {
+	pkt.buffer.Write(bytes)
+	if pkt.buffer.Len() >= int(pkt.expectedLen) {
+		return true
+	} else {
+		return false
+	}
+}
+
+func (pkt *Packet) GetBytes() []byte {
+	return pkt.buffer.Bytes()
+}
+
+func NewConn(cp *cli.ConnProfile) (Conn, error) {
+	// Based on ConnProfile, instantiate the right type of conn object, that
+	// implements the conn interface.
+	var c Conn
+	switch cp.Type {
+	case "serial":
+		c = &ConnSerial{}
+	default:
+		return nil, util.NewNewtError("Invalid conn profile " + cp.Type +
+			" not implemented")
+	}
+
+	return c, nil
+}

http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/blob/1938fa43/newtmgr/transport/connserial.go
----------------------------------------------------------------------
diff --git a/newtmgr/transport/connserial.go b/newtmgr/transport/connserial.go
new file mode 100644
index 0000000..09ece25
--- /dev/null
+++ b/newtmgr/transport/connserial.go
@@ -0,0 +1,132 @@
+/*
+ Copyright 2015 Runtime Inc.
+ Licensed 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 transport
+
+import (
+	"bufio"
+	"encoding/base64"
+	"encoding/binary"
+	"git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/newtmgr/cli"
+	"git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/util"
+	"github.com/jacobsa/go-serial/serial"
+	"io"
+)
+
+type ConnSerial struct {
+	cp            *cli.ConnProfile
+	currentPacket *Packet
+
+	scanner       *bufio.Scanner
+	serialChannel io.ReadWriteCloser
+}
+
+func (cs *ConnSerial) Open(cp *cli.ConnProfile) error {
+	var err error
+
+	opts := serial.OpenOptions{
+		PortName:        cp.ConnString,
+		BaudRate:        9600,
+		DataBits:        8,
+		StopBits:        1,
+		MinimumReadSize: 1,
+	}
+
+	cs.serialChannel, err = serial.Open(opts)
+	if err != nil {
+		return util.NewNewtError(err.Error())
+	}
+	defer cs.serialChannel.Close()
+
+	// Most of the reading will be done line by line, use the
+	// bufio.Scanner to do this
+	cs.scanner = bufio.NewScanner(cs.serialChannel)
+
+	return nil
+}
+
+func (cs *ConnSerial) ReadPacket() (*Packet, error) {
+	scanner := cs.scanner
+	for scanner.Scan() {
+		line := scanner.Text()
+
+		if len(line) < 2 || ((line[0] != 4 || line[1] != 20) &&
+			(line[0] != 6 || line[1] != 9)) {
+			continue
+		}
+
+		base64Data := line[2:]
+
+		data, err := base64.StdEncoding.DecodeString(base64Data)
+		if err != nil {
+			return nil, util.NewNewtError("Couldn't decode base64 string: " +
+				line)
+		}
+
+		if line[0] == 4 && line[1] == 20 {
+			if len(data) < 2 {
+				continue
+			}
+
+			pktLen := binary.LittleEndian.Uint16(data[0:2])
+			cs.currentPacket, err = NewPacket(pktLen)
+			if err != nil {
+				return nil, err
+			}
+			data = data[2:]
+		}
+
+		full := cs.currentPacket.AddBytes(data)
+		if full {
+			pkt := cs.currentPacket
+			cs.currentPacket = nil
+			return pkt, nil
+		}
+	}
+
+	return nil, nil
+}
+
+func (cs *ConnSerial) WritePacket(pkt *Packet) error {
+	data := pkt.GetBytes()
+	dLen := uint16(len(data))
+
+	base64Data := []byte{}
+	pktData := []byte{}
+
+	binary.LittleEndian.PutUint16(pktData, dLen)
+	pktData = append(pktData, data...)
+
+	base64.StdEncoding.Encode(base64Data, pktData)
+
+	written := 0
+	totlen := len(base64Data)
+	for written < totlen {
+		if written == 0 {
+			cs.serialChannel.Write([]byte{4, 20})
+		} else {
+			cs.serialChannel.Write([]byte{6, 9})
+		}
+
+		writeLen := util.Min(122, totlen)
+		writeBytes := base64Data[:writeLen]
+		cs.serialChannel.Write(writeBytes)
+		cs.serialChannel.Write([]byte{'\n'})
+
+		written += writeLen
+	}
+
+	return nil
+}

http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/blob/1938fa43/util/cfgdb.go
----------------------------------------------------------------------
diff --git a/util/cfgdb.go b/util/cfgdb.go
new file mode 100644
index 0000000..e9f079d
--- /dev/null
+++ b/util/cfgdb.go
@@ -0,0 +1,246 @@
+/*
+ Copyright 2015 Runtime Inc.
+ Licensed 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 util
+
+import (
+	"database/sql"
+	"fmt"
+	_ "github.com/mattn/go-sqlite3"
+	"log"
+)
+
+type CfgDb struct {
+	DbPath   string
+	DbPrefix string
+	Config   map[string]map[string]string
+	db       *sql.DB
+}
+
+func NewCfgDb(prefix string, dbPath string) (*CfgDb, error) {
+	c := &CfgDb{}
+
+	if err := c.Init(prefix, dbPath); err != nil {
+		return nil, err
+	}
+
+	return c, nil
+}
+
+func (c *CfgDb) createDb(db *sql.DB, prefix string) error {
+	query := `
+	CREATE TABLE IF NOT EXISTS %s_cfg (
+		cfg_name VARCHAR(255) NOT NULL,
+		key VARCHAR(255) NOT NULL,
+		value TEXT
+	)
+	`
+	query = fmt.Sprintf(query, prefix)
+
+	if _, err := db.Exec(query); err != nil {
+		return NewNewtError(err.Error())
+	}
+
+	return nil
+}
+
+func (c *CfgDb) Init(prefix string, dbPath string) error {
+	c.DbPrefix = prefix
+	c.DbPath = dbPath
+	c.Config = make(map[string]map[string]string)
+
+	db, err := sql.Open("sqlite3", dbPath)
+	if err != nil {
+		return err
+	}
+
+	if err = c.createDb(db, prefix); err != nil {
+		return err
+	}
+	c.db = db
+
+	log.Printf("[DEBUG] Reading config from %s for %s", dbPath, prefix)
+
+	rows, err := db.Query(fmt.Sprintf("SELECT * FROM %s_cfg", prefix))
+	if err != nil {
+		return NewNewtError(err.Error())
+	}
+	defer rows.Close()
+
+	for rows.Next() {
+		var cName sql.NullString
+		var cKey sql.NullString
+		var cVal sql.NullString
+
+		if err := rows.Scan(&cName, &cKey, &cVal); err != nil {
+			return NewNewtError(err.Error())
+		}
+
+		log.Printf("[DEBUG] Setting sect %s, key %s to val %s", cName.String,
+			cKey.String, cVal.String)
+
+		if _, ok := c.Config[cName.String]; !ok {
+			c.Config[cName.String] = make(map[string]string)
+		}
+
+		c.Config[cName.String][cKey.String] = cVal.String
+	}
+
+	return nil
+}
+
+func (c *CfgDb) GetKey(sect string, key string) (string, error) {
+	sMap, ok := c.Config[sect]
+	if !ok {
+		return "", NewNewtError("No configuration section " + sect + " exists")
+	}
+
+	val, ok := sMap[key]
+	if !ok {
+		return "", NewNewtError("No configuration variable " + key +
+			" in sect " + sect + "exists")
+	}
+
+	return val, nil
+}
+
+func (c *CfgDb) GetSect(sect string) (map[string]string, error) {
+	sMap, ok := c.Config[sect]
+	if !ok {
+		return nil, NewNewtError("No configuration section " + sect + "exists")
+	}
+	return sMap, nil
+}
+
+func (c *CfgDb) DeleteSect(sect string) error {
+	log.Printf("[DEBUG] Deleting sect %s", sect)
+
+	tx, err := c.db.Begin()
+	if err != nil {
+		return NewNewtError(err.Error())
+	}
+
+	stmt, err := tx.Prepare(fmt.Sprintf(
+		"DELETE FROM %s_cfg WHERE cfg_name=?", c.DbPrefix))
+	if err != nil {
+		return NewNewtError(err.Error())
+	}
+	defer stmt.Close()
+
+	r, err := stmt.Exec(sect)
+	if err != nil {
+		return err
+	}
+
+	tx.Commit()
+
+	if naffected, err := r.RowsAffected(); naffected > 0 && err == nil {
+		log.Printf("[DEBUG] Successfully deleted sect %s from db %s",
+			sect, c.DbPath)
+	} else {
+		log.Printf("[DEBUG] Sect %s not found in db %s.  Delete "+
+			"successful", sect, c.DbPath)
+	}
+
+	return nil
+}
+
+func (c *CfgDb) DeleteKey(sect string, key string) error {
+	log.Printf("[DEBUG] Deleting sect %s, key %s", sect, key)
+
+	tx, err := c.db.Begin()
+	if err != nil {
+		return NewNewtError(err.Error())
+	}
+
+	stmt, err := tx.Prepare(fmt.Sprintf(
+		"DELETE FROM %s_cfg WHERE cfg_name=? AND key=?", c.DbPrefix))
+	if err != nil {
+		return NewNewtError(err.Error())
+	}
+	defer stmt.Close()
+
+	r, err := stmt.Exec(sect, key)
+	if err != nil {
+		return err
+	}
+
+	tx.Commit()
+
+	if naffected, err := r.RowsAffected(); naffected > 0 && err == nil {
+		log.Printf("[DEBUG] Successfully deleted sect %s, key %s from db %s",
+			sect, key, c.DbPath)
+	} else {
+		log.Printf("[DEBUG] Sect %s, key %s not found in db %s.  Delete "+
+			"successful", sect, key, c.DbPath)
+	}
+
+	return nil
+}
+
+func (c *CfgDb) SetKey(sect string, key string, val string) error {
+	if _, ok := c.Config[sect]; !ok {
+		log.Printf("[DEBUG] Section %s doesn't exist, creating it!", sect)
+		c.Config[sect] = make(map[string]string)
+	}
+	c.Config[sect][key] = val
+
+	log.Printf("[DEBUG] Storing value %s in section %s, key %s",
+		val, sect, key)
+
+	tx, err := c.db.Begin()
+	if err != nil {
+		return NewNewtError(err.Error())
+	}
+
+	stmt, err := tx.Prepare(fmt.Sprintf(
+		"UPDATE %s_cfg SET value=? WHERE cfg_name=? AND key=?", c.DbPrefix))
+	if err != nil {
+		return NewNewtError(err.Error())
+	}
+	defer stmt.Close()
+
+	r, err := stmt.Exec(val, sect, key)
+	if err != nil {
+		return NewNewtError(err.Error())
+	}
+
+	// If update succeeded, then exit out.
+	if naffected, err := r.RowsAffected(); naffected > 0 && err == nil {
+		tx.Commit()
+		log.Printf("[DEBUG] Sect %s, key %s successfully updated to %s",
+			sect, key, val)
+		return nil
+	}
+
+	// Otherwise, insert a new row.
+	stmt, err = tx.Prepare(fmt.Sprintf("INSERT INTO %s_cfg VALUES (?, ?, ?)",
+		c.DbPrefix))
+	if err != nil {
+		return NewNewtError(err.Error())
+	}
+	defer stmt.Close()
+
+	if _, err = stmt.Exec(sect, key, val); err != nil {
+		return NewNewtError(err.Error())
+	}
+
+	tx.Commit()
+
+	log.Printf("[DEBUG] Section %s, key %s successfully created.  Value set"+
+		"to %s", sect, key, val)
+
+	return nil
+}

http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/blob/1938fa43/util/util.go
----------------------------------------------------------------------
diff --git a/util/util.go b/util/util.go
new file mode 100644
index 0000000..c1037b2
--- /dev/null
+++ b/util/util.go
@@ -0,0 +1,129 @@
+/*
+ Copyright 2015 Runtime Inc.
+ Licensed 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 util
+
+import (
+	"github.com/hashicorp/logutils"
+	"github.com/spf13/viper"
+	"log"
+	"os"
+	"runtime"
+	"strings"
+)
+
+var Logger *log.Logger
+var Verbosity int
+var OK_STRING = " ok!\n"
+
+func ParseEqualsPair(v string) (string, string, error) {
+	s := strings.Split(v, "=")
+	return s[0], s[1], nil
+}
+
+type NewtError struct {
+	Text       string
+	StackTrace []byte
+}
+
+const (
+	VERBOSITY_SILENT  = 0
+	VERBOSITY_QUIET   = 1
+	VERBOSITY_DEFAULT = 2
+	VERBOSITY_VERBOSE = 3
+)
+
+func (se *NewtError) Error() string {
+	return se.Text + "\n" + string(se.StackTrace)
+}
+
+func NewNewtError(msg string) *NewtError {
+	err := &NewtError{
+		Text:       msg,
+		StackTrace: make([]byte, 1<<16),
+	}
+
+	runtime.Stack(err.StackTrace, true)
+
+	return err
+}
+
+func NewtErrorNoTrace(msg string) *NewtError {
+	return &NewtError{
+		Text:       msg,
+		StackTrace: nil,
+	}
+}
+
+func Min(x, y int) int {
+	if x < y {
+		return x
+	}
+	return y
+}
+
+func Max(x, y int) int {
+	if x > y {
+		return x
+	}
+	return y
+}
+
+// Initialize the CLI module
+func Init(level string, silent bool, quiet bool, verbose bool) {
+	if level == "" {
+		level = "WARN"
+	}
+
+	filter := &logutils.LevelFilter{
+		Levels: []logutils.LogLevel{"DEBUG", "VERBOSE", "INFO",
+			"WARN", "ERROR"},
+		MinLevel: logutils.LogLevel(level),
+		Writer:   os.Stderr,
+	}
+
+	log.SetOutput(filter)
+
+	if silent {
+		Verbosity = VERBOSITY_SILENT
+	} else if quiet {
+		Verbosity = VERBOSITY_QUIET
+	} else if verbose {
+		Verbosity = VERBOSITY_VERBOSE
+	} else {
+		Verbosity = VERBOSITY_DEFAULT
+	}
+}
+
+func CheckBoolMap(mapVar map[string]bool, item string) bool {
+	v, ok := mapVar[item]
+	return v && ok
+}
+
+// Read in the configuration file specified by name, in path
+// return a new viper config object if successful, and error if not
+func ReadConfig(path string, name string) (*viper.Viper, error) {
+	v := viper.New()
+	v.SetConfigType("yaml")
+	v.SetConfigName(name)
+	v.AddConfigPath(path)
+
+	err := v.ReadInConfig()
+	if err != nil {
+		return nil, NewNewtError(err.Error())
+	} else {
+		return v, nil
+	}
+}