diff --git a/forks/goarista/gnmi/arbitration.go b/forks/goarista/gnmi/arbitration.go
new file mode 100644
index 0000000000000000000000000000000000000000..78225d70240584b7e4e8b048bd833753b39ebc5e
--- /dev/null
+++ b/forks/goarista/gnmi/arbitration.go
@@ -0,0 +1,58 @@
+// Copyright (c) 2019 Arista Networks, Inc.
+// Use of this source code is governed by the Apache License 2.0
+// that can be found in the COPYING file.
+
+package gnmi
+
+import (
+	"fmt"
+	"strconv"
+	"strings"
+
+	"github.com/openconfig/gnmi/proto/gnmi_ext"
+)
+
+// ArbitrationExt takes a string representation of a master arbitration value
+// (e.g. "23", "role:42") and return a *gnmi_ext.Extension.
+func ArbitrationExt(s string) (*gnmi_ext.Extension, error) {
+	if s == "" {
+		return nil, nil
+	}
+	roleID, electionID, err := parseArbitrationString(s)
+	if err != nil {
+		return nil, err
+	}
+	arb := &gnmi_ext.MasterArbitration{
+		Role:       &gnmi_ext.Role{Id: roleID},
+		ElectionId: &gnmi_ext.Uint128{High: 0, Low: electionID},
+	}
+	ext := gnmi_ext.Extension_MasterArbitration{MasterArbitration: arb}
+	return &gnmi_ext.Extension{Ext: &ext}, nil
+}
+
+// parseArbitrationString parses the supplied string and returns the role and election id
+// values. Input is of the form [<role>:]<election_id>, where election_id is a uint64.
+//
+// Examples:
+//  "1"
+//  "admin:42"
+func parseArbitrationString(s string) (string, uint64, error) {
+	tokens := strings.Split(s, ":")
+	switch len(tokens) {
+	case 1: // just election id
+		id, err := parseElectionID(tokens[0])
+		return "", id, err
+	case 2: // role and election id
+		id, err := parseElectionID(tokens[1])
+		return tokens[0], id, err
+	}
+	return "", 0, fmt.Errorf("badly formed arbitration id (%s)", s)
+}
+
+func parseElectionID(s string) (uint64, error) {
+	id, err := strconv.ParseUint(s, 0, 64)
+	if err != nil {
+		return 0, fmt.Errorf("badly formed arbitration id (%s)", s)
+	}
+	return id, nil
+}
diff --git a/forks/goarista/gnmi/arbitration_test.go b/forks/goarista/gnmi/arbitration_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..cdcc37c35b8c332f3ae4279b66ddf7b46e8b8798
--- /dev/null
+++ b/forks/goarista/gnmi/arbitration_test.go
@@ -0,0 +1,73 @@
+// Copyright (c) 2019 Arista Networks, Inc.
+// Use of this source code is governed by the Apache License 2.0
+// that can be found in the COPYING file.
+
+package gnmi
+
+import (
+	"fmt"
+	"testing"
+
+	"github.com/aristanetworks/goarista/test"
+
+	"github.com/openconfig/gnmi/proto/gnmi_ext"
+)
+
+func arbitration(role string, id *gnmi_ext.Uint128) *gnmi_ext.Extension {
+	arb := &gnmi_ext.MasterArbitration{
+		Role:       &gnmi_ext.Role{Id: role},
+		ElectionId: id,
+	}
+	ext := gnmi_ext.Extension_MasterArbitration{MasterArbitration: arb}
+	return &gnmi_ext.Extension{Ext: &ext}
+}
+
+func electionID(high, low uint64) *gnmi_ext.Uint128 {
+	return &gnmi_ext.Uint128{High: high, Low: low}
+}
+
+func TestArbitrationExt(t *testing.T) {
+	testCases := map[string]struct {
+		s   string
+		ext *gnmi_ext.Extension
+		err error
+	}{
+		"empty": {},
+		"no_role": {
+			s:   "1",
+			ext: arbitration("", electionID(0, 1)),
+		},
+		"with_role": {
+			s:   "admin:1",
+			ext: arbitration("admin", electionID(0, 1)),
+		},
+		"large_no_role": {
+			s:   "9223372036854775807",
+			ext: arbitration("", electionID(0, 9223372036854775807)),
+		},
+		"large_with_role": {
+			s:   "admin:18446744073709551615",
+			ext: arbitration("admin", electionID(0, 18446744073709551615)),
+		},
+		"invalid": {
+			s:   "cat",
+			err: fmt.Errorf("badly formed arbitration id (%s)", "cat"),
+		},
+		"invalid_too_many_colons": {
+			s:   "dog:1:2",
+			err: fmt.Errorf("badly formed arbitration id (%s)", "dog:1:2"),
+		},
+	}
+
+	for name, tc := range testCases {
+		t.Run(name, func(t *testing.T) {
+			ext, err := ArbitrationExt(tc.s)
+			if !test.DeepEqual(tc.ext, ext) {
+				t.Errorf("Expected %#v, got %#v", tc.ext, ext)
+			}
+			if !test.DeepEqual(tc.err, err) {
+				t.Errorf("Expected %v, got %v", tc.err, err)
+			}
+		})
+	}
+}
diff --git a/forks/goarista/gnmi/client.go b/forks/goarista/gnmi/client.go
new file mode 100644
index 0000000000000000000000000000000000000000..1a23050eb66ee850af78a25dcdb53ca2bc34c92f
--- /dev/null
+++ b/forks/goarista/gnmi/client.go
@@ -0,0 +1,330 @@
+// Copyright (c) 2017 Arista Networks, Inc.
+// Use of this source code is governed by the Apache License 2.0
+// that can be found in the COPYING file.
+
+package gnmi
+
+import (
+	"context"
+	"crypto/tls"
+	"crypto/x509"
+	"flag"
+	"fmt"
+	"math"
+	"net"
+	"os"
+
+	"io/ioutil"
+	"strings"
+
+	"github.com/golang/protobuf/proto"
+	pb "github.com/openconfig/gnmi/proto/gnmi"
+	"google.golang.org/grpc"
+	"google.golang.org/grpc/credentials"
+	"google.golang.org/grpc/encoding/gzip"
+	"google.golang.org/grpc/metadata"
+)
+
+const (
+	defaultPort = "6030"
+	// HostnameArg is the value to be replaced by the actual hostname
+	HostnameArg = "HOSTNAME"
+)
+
+// PublishFunc is the method to publish responses
+type PublishFunc func(addr string, message proto.Message)
+
+// ParseHostnames parses a comma-separated list of names and replaces HOSTNAME with the current
+// hostname in it
+func ParseHostnames(list string) ([]string, error) {
+	items := strings.Split(list, ",")
+	hostname, err := os.Hostname()
+	if err != nil {
+		return nil, err
+	}
+	names := make([]string, len(items))
+	for i, name := range items {
+		if name == HostnameArg {
+			name = hostname
+		}
+		names[i] = name
+	}
+	return names, nil
+}
+
+// Config is the gnmi.Client config
+type Config struct {
+	Addr        string
+	CAFile      string
+	CertFile    string
+	KeyFile     string
+	Password    string
+	Username    string
+	TLS         bool
+	Compression string
+	DialOptions []grpc.DialOption
+	Token       string
+}
+
+// SubscribeOptions is the gNMI subscription request options
+type SubscribeOptions struct {
+	UpdatesOnly       bool
+	Prefix            string
+	Mode              string
+	StreamMode        string
+	SampleInterval    uint64
+	SuppressRedundant bool
+	HeartbeatInterval uint64
+	Paths             [][]string
+	Origin            string
+	Target            string
+}
+
+// ParseFlags reads arguments from stdin and returns a populated Config object and a list of
+// paths to subscribe to
+func ParseFlags() (*Config, []string) {
+	// flags
+	var (
+		addrsFlag = flag.String("addrs", "localhost:6030",
+			"Comma-separated list of addresses of OpenConfig gRPC servers. The address 'HOSTNAME' "+
+				"is replaced by the current hostname.")
+
+		caFileFlag = flag.String("cafile", "",
+			"Path to server TLS certificate file")
+
+		certFileFlag = flag.String("certfile", "",
+			"Path to ciena TLS certificate file")
+
+		keyFileFlag = flag.String("keyfile", "",
+			"Path to ciena TLS private key file")
+
+		passwordFlag = flag.String("password", "",
+			"Password to authenticate with")
+
+		usernameFlag = flag.String("username", "",
+			"Username to authenticate with")
+
+		tlsFlag = flag.Bool("tls", false,
+			"Enable TLS")
+
+		compressionFlag = flag.String("compression", "",
+			"Type of compression to use")
+
+		subscribeFlag = flag.String("subscribe", "",
+			"Comma-separated list of paths to subscribe to upon connecting to the server")
+
+		token = flag.String("token", "",
+			"Authentication token")
+	)
+	flag.Parse()
+	cfg := &Config{
+		Addr:        *addrsFlag,
+		CAFile:      *caFileFlag,
+		CertFile:    *certFileFlag,
+		KeyFile:     *keyFileFlag,
+		Password:    *passwordFlag,
+		Username:    *usernameFlag,
+		TLS:         *tlsFlag,
+		Compression: *compressionFlag,
+		Token:       *token,
+	}
+	subscriptions := strings.Split(*subscribeFlag, ",")
+	return cfg, subscriptions
+
+}
+
+// accessTokenCred implements credentials.PerRPCCredentials, the gRPC
+// interface for credentials that need to attach security information
+// to every RPC.
+type accessTokenCred struct {
+	bearerToken string
+}
+
+// newAccessTokenCredential constructs a new per-RPC credential from a token.
+func newAccessTokenCredential(token string) credentials.PerRPCCredentials {
+	bearerFmt := "Bearer %s"
+	return &accessTokenCred{bearerToken: fmt.Sprintf(bearerFmt, token)}
+}
+
+func (a *accessTokenCred) GetRequestMetadata(ctx context.Context,
+	uri ...string) (map[string]string, error) {
+	authHeader := "Authorization"
+	return map[string]string{
+		authHeader: a.bearerToken,
+	}, nil
+}
+
+func (a *accessTokenCred) RequireTransportSecurity() bool { return true }
+
+// DialContext connects to a gnmi service and returns a ciena
+func DialContext(ctx context.Context, cfg *Config) (pb.GNMIClient, error) {
+	opts := append([]grpc.DialOption(nil), cfg.DialOptions...)
+
+	switch cfg.Compression {
+	case "":
+	case "gzip":
+		opts = append(opts, grpc.WithDefaultCallOptions(grpc.UseCompressor(gzip.Name)))
+	default:
+		return nil, fmt.Errorf("unsupported compression option: %q", cfg.Compression)
+	}
+
+	if cfg.TLS || cfg.CAFile != "" || cfg.CertFile != "" || cfg.Token != "" {
+		tlsConfig := &tls.Config{}
+		if cfg.CAFile != "" {
+			b, err := ioutil.ReadFile(cfg.CAFile)
+			if err != nil {
+				return nil, err
+			}
+			cp := x509.NewCertPool()
+			if !cp.AppendCertsFromPEM(b) {
+				return nil, fmt.Errorf("credentials: failed to append certificates")
+			}
+			tlsConfig.RootCAs = cp
+		} else {
+			tlsConfig.InsecureSkipVerify = true
+		}
+		if cfg.CertFile != "" {
+			if cfg.KeyFile == "" {
+				return nil, fmt.Errorf("please provide both -certfile and -keyfile")
+			}
+			cert, err := tls.LoadX509KeyPair(cfg.CertFile, cfg.KeyFile)
+			if err != nil {
+				return nil, err
+			}
+			tlsConfig.Certificates = []tls.Certificate{cert}
+		}
+		if cfg.Token != "" {
+			opts = append(opts,
+				grpc.WithPerRPCCredentials(newAccessTokenCredential(cfg.Token)))
+		}
+		opts = append(opts, grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig)))
+	} else {
+		opts = append(opts, grpc.WithInsecure())
+	}
+
+	dial := func(ctx context.Context, addrIn string) (conn net.Conn, err error) {
+		var network, addr string
+
+		split := strings.Split(addrIn, "://")
+		if l := len(split); l == 2 {
+			network = split[0]
+			addr = split[1]
+		} else {
+			network = "tcp"
+			addr = split[0]
+		}
+
+		conn, err = (&net.Dialer{}).DialContext(ctx, network, addr)
+		return
+	}
+
+	opts = append(opts,
+		grpc.WithContextDialer(dial),
+
+		// Allows received protobuf messages to be larger than 4MB
+		grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(math.MaxInt32)),
+	)
+
+	grpcconn, err := grpc.DialContext(ctx, cfg.Addr, opts...)
+	if err != nil {
+		return nil, fmt.Errorf("failed to dial: %s", err)
+	}
+
+	return pb.NewGNMIClient(grpcconn), nil
+}
+
+// Dial connects to a gnmi service and returns a ciena
+func Dial(cfg *Config) (pb.GNMIClient, error) {
+	return DialContext(context.Background(), cfg)
+}
+
+// NewContext returns a new context with username and password
+// metadata if they are set in cfg.
+func NewContext(ctx context.Context, cfg *Config) context.Context {
+	if cfg.Username != "" {
+		ctx = metadata.NewOutgoingContext(ctx, metadata.Pairs(
+			"username", cfg.Username,
+			"password", cfg.Password))
+	}
+	return ctx
+}
+
+// NewGetRequest returns a GetRequest for the given paths
+func NewGetRequest(paths [][]string, origin string) (*pb.GetRequest, error) {
+	req := &pb.GetRequest{
+		Path: make([]*pb.Path, len(paths)),
+	}
+	for i, p := range paths {
+		gnmiPath, err := ParseGNMIElements(p)
+		if err != nil {
+			return nil, err
+		}
+		req.Path[i] = gnmiPath
+		req.Path[i].Origin = origin
+	}
+	return req, nil
+}
+
+// NewSubscribeRequest returns a SubscribeRequest for the given paths
+func NewSubscribeRequest(subscribeOptions *SubscribeOptions) (*pb.SubscribeRequest, error) {
+	var mode pb.SubscriptionList_Mode
+	switch subscribeOptions.Mode {
+	case "once":
+		mode = pb.SubscriptionList_ONCE
+	case "poll":
+		mode = pb.SubscriptionList_POLL
+	case "":
+		fallthrough
+	case "stream":
+		mode = pb.SubscriptionList_STREAM
+	default:
+		return nil, fmt.Errorf("subscribe mode (%s) invalid", subscribeOptions.Mode)
+	}
+
+	var streamMode pb.SubscriptionMode
+	switch subscribeOptions.StreamMode {
+	case "on_change":
+		streamMode = pb.SubscriptionMode_ON_CHANGE
+	case "sample":
+		streamMode = pb.SubscriptionMode_SAMPLE
+	case "":
+		fallthrough
+	case "target_defined":
+		streamMode = pb.SubscriptionMode_TARGET_DEFINED
+	default:
+		return nil, fmt.Errorf("subscribe stream mode (%s) invalid", subscribeOptions.StreamMode)
+	}
+
+	prefixPath, err := ParseGNMIElements(SplitPath(subscribeOptions.Prefix))
+	if err != nil {
+		return nil, err
+	}
+	subList := &pb.SubscriptionList{
+		Subscription: make([]*pb.Subscription, len(subscribeOptions.Paths)),
+		Mode:         mode,
+		UpdatesOnly:  subscribeOptions.UpdatesOnly,
+		Prefix:       prefixPath,
+	}
+	if subscribeOptions.Target != "" {
+		if subList.Prefix == nil {
+			subList.Prefix = &pb.Path{}
+		}
+		subList.Prefix.Target = subscribeOptions.Target
+	}
+	for i, p := range subscribeOptions.Paths {
+		gnmiPath, err := ParseGNMIElements(p)
+		if err != nil {
+			return nil, err
+		}
+		gnmiPath.Origin = subscribeOptions.Origin
+		subList.Subscription[i] = &pb.Subscription{
+			Path:              gnmiPath,
+			Mode:              streamMode,
+			SampleInterval:    subscribeOptions.SampleInterval,
+			SuppressRedundant: subscribeOptions.SuppressRedundant,
+			HeartbeatInterval: subscribeOptions.HeartbeatInterval,
+		}
+	}
+	return &pb.SubscribeRequest{Request: &pb.SubscribeRequest_Subscribe{
+		Subscribe: subList}}, nil
+}
diff --git a/forks/goarista/gnmi/json.go b/forks/goarista/gnmi/json.go
new file mode 100644
index 0000000000000000000000000000000000000000..30aacd3df8239f39dd1af90a4fca9582cff6de1c
--- /dev/null
+++ b/forks/goarista/gnmi/json.go
@@ -0,0 +1,35 @@
+// Copyright (c) 2017 Arista Networks, Inc.
+// Use of this source code is governed by the Apache License 2.0
+// that can be found in the COPYING file.
+
+package gnmi
+
+import (
+	"github.com/openconfig/gnmi/proto/gnmi"
+)
+
+// NotificationToMap converts a Notification into a map[string]interface{}
+func NotificationToMap(notif *gnmi.Notification) (map[string]interface{}, error) {
+	m := make(map[string]interface{}, 1)
+	m["timestamp"] = notif.Timestamp
+	m["path"] = StrPath(notif.Prefix)
+	if len(notif.Update) != 0 {
+		updates := make(map[string]interface{}, len(notif.Update))
+		var err error
+		for _, update := range notif.Update {
+			updates[StrPath(update.Path)] = StrUpdateVal(update)
+			if err != nil {
+				return nil, err
+			}
+		}
+		m["updates"] = updates
+	}
+	if len(notif.Delete) != 0 {
+		deletes := make([]string, len(notif.Delete))
+		for i, del := range notif.Delete {
+			deletes[i] = StrPath(del)
+		}
+		m["deletes"] = deletes
+	}
+	return m, nil
+}
diff --git a/forks/goarista/gnmi/operation.go b/forks/goarista/gnmi/operation.go
new file mode 100644
index 0000000000000000000000000000000000000000..ef506ac5e99e824fc4adc4cb34805e2b4ed27dd1
--- /dev/null
+++ b/forks/goarista/gnmi/operation.go
@@ -0,0 +1,541 @@
+// Copyright (c) 2017 Arista Networks, Inc.
+// Use of this source code is governed by the Apache License 2.0
+// that can be found in the COPYING file.
+
+package gnmi
+
+import (
+	"bufio"
+	"bytes"
+	"context"
+	"encoding/base64"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"math"
+	"os"
+	"path"
+	"strconv"
+	"strings"
+	"time"
+
+	pb "github.com/openconfig/gnmi/proto/gnmi"
+	"github.com/openconfig/gnmi/proto/gnmi_ext"
+	"google.golang.org/grpc/codes"
+)
+
+// GetWithRequest takes a fully formed GetRequest, performs the Get,
+// and displays any response.
+func GetWithRequest(ctx context.Context, client pb.GNMIClient,
+	req *pb.GetRequest) error {
+	resp, err := client.Get(ctx, req)
+	if err != nil {
+		return err
+	}
+	for _, notif := range resp.Notification {
+		prefix := StrPath(notif.Prefix)
+		for _, update := range notif.Update {
+			fmt.Printf("%s:\n", path.Join(prefix, StrPath(update.Path)))
+			fmt.Println(StrUpdateVal(update))
+		}
+	}
+	return nil
+}
+
+// Get sends a GetRequest to the given ciena.
+func Get(ctx context.Context, client pb.GNMIClient, paths [][]string,
+	origin string) error {
+	req, err := NewGetRequest(paths, origin)
+	if err != nil {
+		return err
+	}
+	return GetWithRequest(ctx, client, req)
+}
+
+// Capabilities retuns the capabilities of the ciena.
+func Capabilities(ctx context.Context, client pb.GNMIClient) error {
+	resp, err := client.Capabilities(ctx, &pb.CapabilityRequest{})
+	if err != nil {
+		return err
+	}
+	fmt.Printf("Version: %s\n", resp.GNMIVersion)
+	for _, mod := range resp.SupportedModels {
+		fmt.Printf("SupportedModel: %s\n", mod)
+	}
+	for _, enc := range resp.SupportedEncodings {
+		fmt.Printf("SupportedEncoding: %s\n", enc)
+	}
+	return nil
+}
+
+// val may be a path to a file or it may be json. First see if it is a
+// file, if so return its contents, otherwise return val
+func extractJSON(val string) []byte {
+	if jsonBytes, err := ioutil.ReadFile(val); err == nil {
+		return jsonBytes
+	}
+	// Best effort check if the value might a string literal, in which
+	// case wrap it in quotes. This is to allow a user to do:
+	//   gnmi update ../hostname host1234
+	//   gnmi update ../description 'This is a description'
+	// instead of forcing them to quote the string:
+	//   gnmi update ../hostname '"host1234"'
+	//   gnmi update ../description '"This is a description"'
+	maybeUnquotedStringLiteral := func(s string) bool {
+		if s == "true" || s == "false" || s == "null" || // JSON reserved words
+			strings.ContainsAny(s, `"'{}[]`) { // Already quoted or is a JSON object or array
+			return false
+		} else if _, err := strconv.ParseInt(s, 0, 32); err == nil {
+			// Integer. Using byte size of 32 because larger integer
+			// types are supposed to be sent as strings in JSON.
+			return false
+		} else if _, err := strconv.ParseFloat(s, 64); err == nil {
+			// Float
+			return false
+		}
+
+		return true
+	}
+	if maybeUnquotedStringLiteral(val) {
+		out := make([]byte, len(val)+2)
+		out[0] = '"'
+		copy(out[1:], val)
+		out[len(out)-1] = '"'
+		return out
+	}
+	return []byte(val)
+}
+
+// StrUpdateVal will return a string representing the value within the supplied update
+func StrUpdateVal(u *pb.Update) string {
+	if u.Value != nil {
+		// Backwards compatibility with pre-v0.4 gnmi
+		switch u.Value.Type {
+		case pb.Encoding_JSON, pb.Encoding_JSON_IETF:
+			return strJSON(u.Value.Value)
+		case pb.Encoding_BYTES, pb.Encoding_PROTO:
+			return base64.StdEncoding.EncodeToString(u.Value.Value)
+		case pb.Encoding_ASCII:
+			return string(u.Value.Value)
+		default:
+			return string(u.Value.Value)
+		}
+	}
+	return StrVal(u.Val)
+}
+
+// StrVal will return a string representing the supplied value
+func StrVal(val *pb.TypedValue) string {
+	switch v := val.GetValue().(type) {
+	case *pb.TypedValue_StringVal:
+		return v.StringVal
+	case *pb.TypedValue_JsonIetfVal:
+		return strJSON(v.JsonIetfVal)
+	case *pb.TypedValue_JsonVal:
+		return strJSON(v.JsonVal)
+	case *pb.TypedValue_IntVal:
+		return strconv.FormatInt(v.IntVal, 10)
+	case *pb.TypedValue_UintVal:
+		return strconv.FormatUint(v.UintVal, 10)
+	case *pb.TypedValue_BoolVal:
+		return strconv.FormatBool(v.BoolVal)
+	case *pb.TypedValue_BytesVal:
+		return base64.StdEncoding.EncodeToString(v.BytesVal)
+	case *pb.TypedValue_DecimalVal:
+		return strDecimal64(v.DecimalVal)
+	case *pb.TypedValue_FloatVal:
+		return strconv.FormatFloat(float64(v.FloatVal), 'g', -1, 32)
+	case *pb.TypedValue_LeaflistVal:
+		return strLeaflist(v.LeaflistVal)
+	case *pb.TypedValue_AsciiVal:
+		return v.AsciiVal
+	case *pb.TypedValue_AnyVal:
+		return v.AnyVal.String()
+	case *pb.TypedValue_ProtoBytes:
+		return base64.StdEncoding.EncodeToString(v.ProtoBytes)
+	default:
+		panic(v)
+	}
+}
+
+func strJSON(inJSON []byte) string {
+	var (
+		out bytes.Buffer
+		err error
+	)
+	// Check for ',' as simple heuristic on whether to expand JSON
+	// onto multiple lines, or compact it to a single line.
+	if bytes.Contains(inJSON, []byte{','}) {
+		err = json.Indent(&out, inJSON, "", "  ")
+	} else {
+		err = json.Compact(&out, inJSON)
+	}
+	if err != nil {
+		return fmt.Sprintf("(error unmarshalling json: %s)\n", err) + string(inJSON)
+	}
+	return out.String()
+}
+
+func strDecimal64(d *pb.Decimal64) string {
+	var i, frac int64
+	if d.Precision > 0 {
+		div := int64(10)
+		it := d.Precision - 1
+		for it > 0 {
+			div *= 10
+			it--
+		}
+		i = d.Digits / div
+		frac = d.Digits % div
+	} else {
+		i = d.Digits
+	}
+	if frac < 0 {
+		frac = -frac
+	}
+	return fmt.Sprintf("%d.%d", i, frac)
+}
+
+// strLeafList builds a human-readable form of a leaf-list. e.g. [1, 2, 3] or [a, b, c]
+func strLeaflist(v *pb.ScalarArray) string {
+	var b strings.Builder
+	b.WriteByte('[')
+
+	for i, elm := range v.Element {
+		b.WriteString(StrVal(elm))
+		if i < len(v.Element)-1 {
+			b.WriteString(", ")
+		}
+	}
+
+	b.WriteByte(']')
+	return b.String()
+}
+
+// TypedValue marshals an interface into a gNMI TypedValue value
+func TypedValue(val interface{}) *pb.TypedValue {
+	// TODO: handle more types:
+	// float64
+	// maps
+	// key.Key
+	// key.Map
+	// ... etc
+	switch v := val.(type) {
+	case string:
+		return &pb.TypedValue{Value: &pb.TypedValue_StringVal{StringVal: v}}
+	case int:
+		return &pb.TypedValue{Value: &pb.TypedValue_IntVal{IntVal: int64(v)}}
+	case int8:
+		return &pb.TypedValue{Value: &pb.TypedValue_IntVal{IntVal: int64(v)}}
+	case int16:
+		return &pb.TypedValue{Value: &pb.TypedValue_IntVal{IntVal: int64(v)}}
+	case int32:
+		return &pb.TypedValue{Value: &pb.TypedValue_IntVal{IntVal: int64(v)}}
+	case int64:
+		return &pb.TypedValue{Value: &pb.TypedValue_IntVal{IntVal: v}}
+	case uint:
+		return &pb.TypedValue{Value: &pb.TypedValue_UintVal{UintVal: uint64(v)}}
+	case uint8:
+		return &pb.TypedValue{Value: &pb.TypedValue_UintVal{UintVal: uint64(v)}}
+	case uint16:
+		return &pb.TypedValue{Value: &pb.TypedValue_UintVal{UintVal: uint64(v)}}
+	case uint32:
+		return &pb.TypedValue{Value: &pb.TypedValue_UintVal{UintVal: uint64(v)}}
+	case uint64:
+		return &pb.TypedValue{Value: &pb.TypedValue_UintVal{UintVal: v}}
+	case bool:
+		return &pb.TypedValue{Value: &pb.TypedValue_BoolVal{BoolVal: v}}
+	case float32:
+		return &pb.TypedValue{Value: &pb.TypedValue_FloatVal{FloatVal: v}}
+	case []interface{}:
+		gnmiElems := make([]*pb.TypedValue, len(v))
+		for i, elem := range v {
+			gnmiElems[i] = TypedValue(elem)
+		}
+		return &pb.TypedValue{
+			Value: &pb.TypedValue_LeaflistVal{
+				LeaflistVal: &pb.ScalarArray{
+					Element: gnmiElems,
+				}}}
+	default:
+		panic(fmt.Sprintf("unexpected type %T for value %v", val, val))
+	}
+}
+
+// ExtractValue pulls a value out of a gNMI Update, parsing JSON if present.
+// Possible return types:
+//  string
+//  int64
+//  uint64
+//  bool
+//  []byte
+//  float32
+//  *gnmi.Decimal64
+//  json.Number
+//  *any.Any
+//  []interface{}
+//  map[string]interface{}
+func ExtractValue(update *pb.Update) (interface{}, error) {
+	var i interface{}
+	var err error
+	if update == nil {
+		return nil, fmt.Errorf("empty update")
+	}
+	if update.Val != nil {
+		i, err = extractValueV04(update.Val)
+	} else if update.Value != nil {
+		i, err = extractValueV03(update.Value)
+	}
+	return i, err
+}
+
+func extractValueV04(val *pb.TypedValue) (interface{}, error) {
+	switch v := val.Value.(type) {
+	case *pb.TypedValue_StringVal:
+		return v.StringVal, nil
+	case *pb.TypedValue_IntVal:
+		return v.IntVal, nil
+	case *pb.TypedValue_UintVal:
+		return v.UintVal, nil
+	case *pb.TypedValue_BoolVal:
+		return v.BoolVal, nil
+	case *pb.TypedValue_BytesVal:
+		return v.BytesVal, nil
+	case *pb.TypedValue_FloatVal:
+		return v.FloatVal, nil
+	case *pb.TypedValue_DecimalVal:
+		return v.DecimalVal, nil
+	case *pb.TypedValue_LeaflistVal:
+		elementList := v.LeaflistVal.Element
+		l := make([]interface{}, len(elementList))
+		for i, element := range elementList {
+			el, err := extractValueV04(element)
+			if err != nil {
+				return nil, err
+			}
+			l[i] = el
+		}
+		return l, nil
+	case *pb.TypedValue_AnyVal:
+		return v.AnyVal, nil
+	case *pb.TypedValue_JsonVal:
+		return decode(v.JsonVal)
+	case *pb.TypedValue_JsonIetfVal:
+		return decode(v.JsonIetfVal)
+	case *pb.TypedValue_AsciiVal:
+		return v.AsciiVal, nil
+	case *pb.TypedValue_ProtoBytes:
+		return v.ProtoBytes, nil
+	}
+	return nil, fmt.Errorf("unhandled type of value %v", val.GetValue())
+}
+
+func extractValueV03(val *pb.Value) (interface{}, error) {
+	switch val.Type {
+	case pb.Encoding_JSON, pb.Encoding_JSON_IETF:
+		return decode(val.Value)
+	case pb.Encoding_BYTES, pb.Encoding_PROTO:
+		return val.Value, nil
+	case pb.Encoding_ASCII:
+		return string(val.Value), nil
+	}
+	return nil, fmt.Errorf("unhandled type of value %v", val.GetValue())
+}
+
+func decode(byteArr []byte) (interface{}, error) {
+	decoder := json.NewDecoder(bytes.NewReader(byteArr))
+	decoder.UseNumber()
+	var value interface{}
+	err := decoder.Decode(&value)
+	return value, err
+}
+
+// DecimalToFloat converts a gNMI Decimal64 to a float64
+func DecimalToFloat(dec *pb.Decimal64) float64 {
+	return float64(dec.Digits) / math.Pow10(int(dec.Precision))
+}
+
+func update(p *pb.Path, val string) (*pb.Update, error) {
+	var v *pb.TypedValue
+	switch p.Origin {
+	case "":
+		v = &pb.TypedValue{
+			Value: &pb.TypedValue_JsonIetfVal{JsonIetfVal: extractJSON(val)}}
+	case "eos_native":
+		v = &pb.TypedValue{
+			Value: &pb.TypedValue_JsonVal{JsonVal: extractJSON(val)}}
+	case "cli", "test-regen-cli":
+		v = &pb.TypedValue{
+			Value: &pb.TypedValue_AsciiVal{AsciiVal: val}}
+	case "p4_config":
+		b, err := ioutil.ReadFile(val)
+		if err != nil {
+			return nil, err
+		}
+		v = &pb.TypedValue{
+			Value: &pb.TypedValue_ProtoBytes{ProtoBytes: b}}
+	default:
+		return nil, fmt.Errorf("unexpected origin: %q", p.Origin)
+	}
+
+	return &pb.Update{Path: p, Val: v}, nil
+}
+
+// Operation describes an gNMI operation.
+type Operation struct {
+	Type   string
+	Origin string
+	Target string
+	Path   []string
+	Val    string
+}
+
+func newSetRequest(setOps []*Operation, exts ...*gnmi_ext.Extension) (*pb.SetRequest, error) {
+	req := &pb.SetRequest{}
+	for _, op := range setOps {
+		p, err := ParseGNMIElements(op.Path)
+		if err != nil {
+			return nil, err
+		}
+		p.Origin = op.Origin
+
+		// Target must apply to the entire SetRequest.
+		if op.Target != "" {
+			req.Prefix = &pb.Path{
+				Target: op.Target,
+			}
+		}
+
+		switch op.Type {
+		case "delete":
+			req.Delete = append(req.Delete, p)
+		case "update":
+			u, err := update(p, op.Val)
+			if err != nil {
+				return nil, err
+			}
+			req.Update = append(req.Update, u)
+		case "replace":
+			u, err := update(p, op.Val)
+			if err != nil {
+				return nil, err
+			}
+			req.Replace = append(req.Replace, u)
+		}
+	}
+	for _, ext := range exts {
+		req.Extension = append(req.Extension, ext)
+	}
+	return req, nil
+}
+
+// Set sends a SetRequest to the given ciena.
+func Set(ctx context.Context, client pb.GNMIClient, setOps []*Operation,
+	exts ...*gnmi_ext.Extension) error {
+	req, err := newSetRequest(setOps, exts...)
+	if err != nil {
+		return err
+	}
+	resp, err := client.Set(ctx, req)
+	if err != nil {
+		return err
+	}
+	if resp.Message != nil && codes.Code(resp.Message.Code) != codes.OK {
+		return errors.New(resp.Message.Message)
+	}
+	return nil
+}
+
+// Subscribe sends a SubscribeRequest to the given ciena.
+// Deprecated: Use SubscribeErr instead.
+func Subscribe(ctx context.Context, client pb.GNMIClient, subscribeOptions *SubscribeOptions,
+	respChan chan<- *pb.SubscribeResponse, errChan chan<- error) {
+	defer close(errChan)
+	if err := SubscribeErr(ctx, client, subscribeOptions, respChan); err != nil {
+		errChan <- err
+	}
+}
+
+// SubscribeErr makes a gNMI.Subscribe call and writes the responses
+// to the respChan. Before returning respChan will be closed.
+func SubscribeErr(ctx context.Context, client pb.GNMIClient, subscribeOptions *SubscribeOptions,
+	respChan chan<- *pb.SubscribeResponse) error {
+	ctx, cancel := context.WithCancel(ctx)
+	defer cancel()
+	defer close(respChan)
+
+	stream, err := client.Subscribe(ctx)
+	if err != nil {
+		return err
+	}
+	req, err := NewSubscribeRequest(subscribeOptions)
+	if err != nil {
+		return err
+	}
+	if err := stream.Send(req); err != nil {
+		return err
+	}
+
+	for {
+		resp, err := stream.Recv()
+		if err != nil {
+			if err == io.EOF {
+				return nil
+			}
+			return err
+		}
+		respChan <- resp
+
+		// For POLL subscriptions, initiate a poll request by pressing ENTER
+		if subscribeOptions.Mode == "poll" {
+			switch resp.Response.(type) {
+			case *pb.SubscribeResponse_SyncResponse:
+				fmt.Print("Press ENTER to send a poll request: ")
+				reader := bufio.NewReader(os.Stdin)
+				reader.ReadString('\n')
+
+				pollReq := &pb.SubscribeRequest{
+					Request: &pb.SubscribeRequest_Poll{
+						Poll: &pb.Poll{},
+					},
+				}
+				if err := stream.Send(pollReq); err != nil {
+					return err
+				}
+			}
+		}
+	}
+}
+
+// LogSubscribeResponse logs update responses to stderr.
+func LogSubscribeResponse(response *pb.SubscribeResponse) error {
+	switch resp := response.Response.(type) {
+	case *pb.SubscribeResponse_Error:
+		return errors.New(resp.Error.Message)
+	case *pb.SubscribeResponse_SyncResponse:
+		if !resp.SyncResponse {
+			return errors.New("initial sync failed")
+		}
+	case *pb.SubscribeResponse_Update:
+		t := time.Unix(0, resp.Update.Timestamp).UTC()
+		prefix := StrPath(resp.Update.Prefix)
+		var target string
+		if t := resp.Update.Prefix.GetTarget(); t != "" {
+			target = "(" + t + ") "
+		}
+		for _, update := range resp.Update.Update {
+			fmt.Printf("[%s] %s%s = %s\n", t.Format(time.RFC3339Nano),
+				target,
+				path.Join(prefix, StrPath(update.Path)),
+				StrUpdateVal(update))
+		}
+		for _, del := range resp.Update.Delete {
+			fmt.Printf("[%s] %sDeleted %s\n", t.Format(time.RFC3339Nano),
+				target,
+				path.Join(prefix, StrPath(del)))
+		}
+	}
+	return nil
+}
diff --git a/forks/goarista/gnmi/operation_test.go b/forks/goarista/gnmi/operation_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..fd575d10aa5ea2766b9da07a4c4865ee101d923c
--- /dev/null
+++ b/forks/goarista/gnmi/operation_test.go
@@ -0,0 +1,423 @@
+// Copyright (c) 2017 Arista Networks, Inc.
+// Use of this source code is governed by the Apache License 2.0
+// that can be found in the COPYING file.
+
+package gnmi
+
+import (
+	"bytes"
+	"encoding/json"
+	"io/ioutil"
+	"os"
+	"testing"
+
+	"github.com/aristanetworks/goarista/test"
+	"github.com/golang/protobuf/proto"
+	"github.com/golang/protobuf/ptypes/any"
+
+	pb "github.com/openconfig/gnmi/proto/gnmi"
+)
+
+func TestNewSetRequest(t *testing.T) {
+	pathFoo := &pb.Path{
+		Element: []string{"foo"},
+		Elem:    []*pb.PathElem{{Name: "foo"}},
+	}
+	pathCli := &pb.Path{
+		Origin: "cli",
+	}
+	pathP4 := &pb.Path{
+		Origin: "p4_config",
+	}
+
+	p4FileContent := "p4_config test"
+	p4TestFile, err := ioutil.TempFile("", "p4TestFile")
+	if err != nil {
+		t.Errorf("cannot create test file for p4_config")
+	}
+	p4Filename := p4TestFile.Name()
+
+	defer os.Remove(p4Filename)
+
+	if _, err := p4TestFile.WriteString(p4FileContent); err != nil {
+		t.Errorf("cannot write test file for p4_config")
+	}
+	p4TestFile.Close()
+
+	testCases := map[string]struct {
+		setOps []*Operation
+		exp    pb.SetRequest
+	}{
+		"delete": {
+			setOps: []*Operation{{Type: "delete", Path: []string{"foo"}}},
+			exp:    pb.SetRequest{Delete: []*pb.Path{pathFoo}},
+		},
+		"update": {
+			setOps: []*Operation{{Type: "update", Path: []string{"foo"}, Val: "true"}},
+			exp: pb.SetRequest{
+				Update: []*pb.Update{{
+					Path: pathFoo,
+					Val: &pb.TypedValue{
+						Value: &pb.TypedValue_JsonIetfVal{JsonIetfVal: []byte("true")}},
+				}},
+			},
+		},
+		"replace": {
+			setOps: []*Operation{{Type: "replace", Path: []string{"foo"}, Val: "true"}},
+			exp: pb.SetRequest{
+				Replace: []*pb.Update{{
+					Path: pathFoo,
+					Val: &pb.TypedValue{
+						Value: &pb.TypedValue_JsonIetfVal{JsonIetfVal: []byte("true")}},
+				}},
+			},
+		},
+		"cli-replace": {
+			setOps: []*Operation{{Type: "replace", Origin: "cli",
+				Val: "hostname foo\nip routing"}},
+			exp: pb.SetRequest{
+				Replace: []*pb.Update{{
+					Path: pathCli,
+					Val: &pb.TypedValue{
+						Value: &pb.TypedValue_AsciiVal{AsciiVal: "hostname foo\nip routing"}},
+				}},
+			},
+		},
+		"p4_config": {
+			setOps: []*Operation{{Type: "replace", Origin: "p4_config",
+				Val: p4Filename}},
+			exp: pb.SetRequest{
+				Replace: []*pb.Update{{
+					Path: pathP4,
+					Val: &pb.TypedValue{
+						Value: &pb.TypedValue_ProtoBytes{ProtoBytes: []byte(p4FileContent)}},
+				}},
+			},
+		},
+		"target": {
+			setOps: []*Operation{{Type: "replace", Target: "JPE1234567",
+				Path: []string{"foo"}, Val: "true"}},
+			exp: pb.SetRequest{
+				Prefix: &pb.Path{Target: "JPE1234567"},
+				Replace: []*pb.Update{{
+					Path: pathFoo,
+					Val: &pb.TypedValue{
+						Value: &pb.TypedValue_JsonIetfVal{JsonIetfVal: []byte("true")}},
+				}},
+			},
+		},
+	}
+
+	for name, tc := range testCases {
+		t.Run(name, func(t *testing.T) {
+			got, err := newSetRequest(tc.setOps)
+			if err != nil {
+				t.Fatal(err)
+			}
+			if diff := test.Diff(tc.exp, *got); diff != "" {
+				t.Errorf("unexpected diff: %s", diff)
+			}
+		})
+	}
+}
+
+func TestStrUpdateVal(t *testing.T) {
+	anyBytes, err := proto.Marshal(&pb.ModelData{Name: "foobar"})
+	if err != nil {
+		t.Fatal(err)
+	}
+	anyMessage := &any.Any{TypeUrl: "gnmi/ModelData", Value: anyBytes}
+	anyString := proto.CompactTextString(anyMessage)
+
+	for name, tc := range map[string]struct {
+		update *pb.Update
+		exp    string
+	}{
+		"JSON Value": {
+			update: &pb.Update{
+				Value: &pb.Value{
+					Value: []byte(`{"foo":"bar"}`),
+					Type:  pb.Encoding_JSON}},
+			exp: `{"foo":"bar"}`,
+		},
+		"JSON_IETF Value": {
+			update: &pb.Update{
+				Value: &pb.Value{
+					Value: []byte(`{"foo":"bar"}`),
+					Type:  pb.Encoding_JSON_IETF}},
+			exp: `{"foo":"bar"}`,
+		},
+		"BYTES Value": {
+			update: &pb.Update{
+				Value: &pb.Value{
+					Value: []byte{0xde, 0xad},
+					Type:  pb.Encoding_BYTES}},
+			exp: "3q0=",
+		},
+		"PROTO Value": {
+			update: &pb.Update{
+				Value: &pb.Value{
+					Value: []byte{0xde, 0xad},
+					Type:  pb.Encoding_PROTO}},
+			exp: "3q0=",
+		},
+		"ASCII Value": {
+			update: &pb.Update{
+				Value: &pb.Value{
+					Value: []byte("foobar"),
+					Type:  pb.Encoding_ASCII}},
+			exp: "foobar",
+		},
+		"INVALID Value": {
+			update: &pb.Update{
+				Value: &pb.Value{
+					Value: []byte("foobar"),
+					Type:  pb.Encoding(42)}},
+			exp: "foobar",
+		},
+		"StringVal": {
+			update: &pb.Update{Val: &pb.TypedValue{
+				Value: &pb.TypedValue_StringVal{StringVal: "foobar"}}},
+			exp: "foobar",
+		},
+		"IntVal": {
+			update: &pb.Update{Val: &pb.TypedValue{
+				Value: &pb.TypedValue_IntVal{IntVal: -42}}},
+			exp: "-42",
+		},
+		"UintVal": {
+			update: &pb.Update{Val: &pb.TypedValue{
+				Value: &pb.TypedValue_UintVal{UintVal: 42}}},
+			exp: "42",
+		},
+		"BoolVal": {
+			update: &pb.Update{Val: &pb.TypedValue{
+				Value: &pb.TypedValue_BoolVal{BoolVal: true}}},
+			exp: "true",
+		},
+		"BytesVal": {
+			update: &pb.Update{Val: &pb.TypedValue{
+				Value: &pb.TypedValue_BytesVal{BytesVal: []byte{0xde, 0xad}}}},
+			exp: "3q0=",
+		},
+		"FloatVal": {
+			update: &pb.Update{Val: &pb.TypedValue{
+				Value: &pb.TypedValue_FloatVal{FloatVal: 3.14}}},
+			exp: "3.14",
+		},
+		"DecimalVal": {
+			update: &pb.Update{Val: &pb.TypedValue{
+				Value: &pb.TypedValue_DecimalVal{
+					DecimalVal: &pb.Decimal64{Digits: 314, Precision: 2},
+				}}},
+			exp: "3.14",
+		},
+		"LeafListVal": {
+			update: &pb.Update{Val: &pb.TypedValue{
+				Value: &pb.TypedValue_LeaflistVal{
+					LeaflistVal: &pb.ScalarArray{Element: []*pb.TypedValue{
+						{Value: &pb.TypedValue_BoolVal{BoolVal: true}},
+						{Value: &pb.TypedValue_AsciiVal{AsciiVal: "foobar"}},
+					}},
+				}}},
+			exp: "[true, foobar]",
+		},
+		"AnyVal": {
+			update: &pb.Update{Val: &pb.TypedValue{
+				Value: &pb.TypedValue_AnyVal{AnyVal: anyMessage}}},
+			exp: anyString,
+		},
+		"JsonVal": {
+			update: &pb.Update{Val: &pb.TypedValue{
+				Value: &pb.TypedValue_JsonVal{JsonVal: []byte(`{"foo":"bar"}`)}}},
+			exp: `{"foo":"bar"}`,
+		},
+		"JsonVal_complex": {
+			update: &pb.Update{Val: &pb.TypedValue{
+				Value: &pb.TypedValue_JsonVal{JsonVal: []byte(`{"foo":"bar","baz":"qux"}`)}}},
+			exp: `{
+  "foo": "bar",
+  "baz": "qux"
+}`,
+		},
+		"JsonIetfVal": {
+			update: &pb.Update{Val: &pb.TypedValue{
+				Value: &pb.TypedValue_JsonIetfVal{JsonIetfVal: []byte(`{"foo":"bar"}`)}}},
+			exp: `{"foo":"bar"}`,
+		},
+		"AsciiVal": {
+			update: &pb.Update{Val: &pb.TypedValue{
+				Value: &pb.TypedValue_AsciiVal{AsciiVal: "foobar"}}},
+			exp: "foobar",
+		},
+		"ProtoBytes": {
+			update: &pb.Update{Val: &pb.TypedValue{
+				Value: &pb.TypedValue_ProtoBytes{ProtoBytes: anyBytes}}},
+			exp: "CgZmb29iYXI=",
+		},
+	} {
+		t.Run(name, func(t *testing.T) {
+			got := StrUpdateVal(tc.update)
+			if got != tc.exp {
+				t.Errorf("Expected: %q Got: %q", tc.exp, got)
+			}
+		})
+	}
+}
+
+func TestTypedValue(t *testing.T) {
+	for tname, tcase := range map[string]struct {
+		in  interface{}
+		exp *pb.TypedValue
+	}{
+		"string": {
+			in:  "foo",
+			exp: &pb.TypedValue{Value: &pb.TypedValue_StringVal{StringVal: "foo"}},
+		},
+		"int": {
+			in:  42,
+			exp: &pb.TypedValue{Value: &pb.TypedValue_IntVal{IntVal: 42}},
+		},
+		"int64": {
+			in:  int64(42),
+			exp: &pb.TypedValue{Value: &pb.TypedValue_IntVal{IntVal: 42}},
+		},
+		"uint": {
+			in:  uint(42),
+			exp: &pb.TypedValue{Value: &pb.TypedValue_UintVal{UintVal: 42}},
+		},
+		"bool": {
+			in:  true,
+			exp: &pb.TypedValue{Value: &pb.TypedValue_BoolVal{BoolVal: true}},
+		},
+		"slice": {
+			in: []interface{}{"foo", 1, uint(2), true},
+			exp: &pb.TypedValue{Value: &pb.TypedValue_LeaflistVal{LeaflistVal: &pb.ScalarArray{
+				Element: []*pb.TypedValue{
+					{Value: &pb.TypedValue_StringVal{StringVal: "foo"}},
+					{Value: &pb.TypedValue_IntVal{IntVal: 1}},
+					{Value: &pb.TypedValue_UintVal{UintVal: 2}},
+					{Value: &pb.TypedValue_BoolVal{BoolVal: true}},
+				}}}},
+		},
+	} {
+		t.Run(tname, func(t *testing.T) {
+			if got := TypedValue(tcase.in); !test.DeepEqual(got, tcase.exp) {
+				t.Errorf("Expected: %q Got: %q", tcase.exp, got)
+			}
+		})
+	}
+}
+
+func TestExtractJSON(t *testing.T) {
+	jsonFile, err := ioutil.TempFile("", "extractJSON")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer os.Remove(jsonFile.Name())
+	if _, err := jsonFile.Write([]byte(`"jsonFile"`)); err != nil {
+		jsonFile.Close()
+		t.Fatal(err)
+	}
+	if err := jsonFile.Close(); err != nil {
+		t.Fatal(err)
+	}
+
+	for val, exp := range map[string][]byte{
+		jsonFile.Name(): []byte(`"jsonFile"`),
+		"foobar":        []byte(`"foobar"`),
+		`"foobar"`:      []byte(`"foobar"`),
+		"Val: true":     []byte(`"Val: true"`),
+		"host42":        []byte(`"host42"`),
+		"42":            []byte("42"),
+		"-123.43":       []byte("-123.43"),
+		"0xFFFF":        []byte("0xFFFF"),
+		// Int larger than can fit in 32 bits should be quoted
+		"0x8000000000":  []byte(`"0x8000000000"`),
+		"-0x8000000000": []byte(`"-0x8000000000"`),
+		"true":          []byte("true"),
+		"false":         []byte("false"),
+		"null":          []byte("null"),
+		"{true: 42}":    []byte("{true: 42}"),
+		"[]":            []byte("[]"),
+	} {
+		t.Run(val, func(t *testing.T) {
+			got := extractJSON(val)
+			if !bytes.Equal(exp, got) {
+				t.Errorf("Unexpected diff. Expected: %q Got: %q", exp, got)
+			}
+		})
+	}
+}
+
+func TestExtractValue(t *testing.T) {
+	cases := []struct {
+		in  *pb.Update
+		exp interface{}
+	}{{
+		in: &pb.Update{Val: &pb.TypedValue{
+			Value: &pb.TypedValue_StringVal{StringVal: "foo"}}},
+		exp: "foo",
+	}, {
+		in: &pb.Update{Val: &pb.TypedValue{
+			Value: &pb.TypedValue_IntVal{IntVal: 123}}},
+		exp: int64(123),
+	}, {
+		in: &pb.Update{Val: &pb.TypedValue{
+			Value: &pb.TypedValue_UintVal{UintVal: 123}}},
+		exp: uint64(123),
+	}, {
+		in: &pb.Update{Val: &pb.TypedValue{
+			Value: &pb.TypedValue_BoolVal{BoolVal: true}}},
+		exp: true,
+	}, {
+		in: &pb.Update{Val: &pb.TypedValue{
+			Value: &pb.TypedValue_BytesVal{BytesVal: []byte{0xde, 0xad}}}},
+		exp: []byte{0xde, 0xad},
+	}, {
+		in: &pb.Update{Val: &pb.TypedValue{
+			Value: &pb.TypedValue_FloatVal{FloatVal: -12.34}}},
+		exp: float32(-12.34),
+	}, {
+		in: &pb.Update{Val: &pb.TypedValue{
+			Value: &pb.TypedValue_DecimalVal{DecimalVal: &pb.Decimal64{
+				Digits: -1234, Precision: 2}}}},
+		exp: &pb.Decimal64{Digits: -1234, Precision: 2},
+	}, {
+		in: &pb.Update{Val: &pb.TypedValue{
+			Value: &pb.TypedValue_LeaflistVal{LeaflistVal: &pb.ScalarArray{
+				Element: []*pb.TypedValue{
+					{Value: &pb.TypedValue_StringVal{StringVal: "foo"}},
+					{Value: &pb.TypedValue_IntVal{IntVal: 123}}}}}}},
+		exp: []interface{}{"foo", int64(123)},
+	}, {
+		in: &pb.Update{Val: &pb.TypedValue{
+			Value: &pb.TypedValue_JsonVal{JsonVal: []byte(`12.34`)}}},
+		exp: json.Number("12.34"),
+	}, {
+		in: &pb.Update{Val: &pb.TypedValue{
+			Value: &pb.TypedValue_JsonVal{JsonVal: []byte(`[12.34, 123, "foo"]`)}}},
+		exp: []interface{}{json.Number("12.34"), json.Number("123"), "foo"},
+	}, {
+		in: &pb.Update{Val: &pb.TypedValue{
+			Value: &pb.TypedValue_JsonVal{JsonVal: []byte(`{"foo":"bar"}`)}}},
+		exp: map[string]interface{}{"foo": "bar"},
+	}, {
+		in: &pb.Update{Val: &pb.TypedValue{
+			Value: &pb.TypedValue_JsonVal{JsonVal: []byte(`{"foo":45.67}`)}}},
+		exp: map[string]interface{}{"foo": json.Number("45.67")},
+	}, {
+		in: &pb.Update{Val: &pb.TypedValue{
+			Value: &pb.TypedValue_JsonIetfVal{JsonIetfVal: []byte(`{"foo":"bar"}`)}}},
+		exp: map[string]interface{}{"foo": "bar"},
+	}}
+	for _, tc := range cases {
+		out, err := ExtractValue(tc.in)
+		if err != nil {
+			t.Errorf(err.Error())
+		}
+		if !test.DeepEqual(tc.exp, out) {
+			t.Errorf("Extracted value is incorrect. Expected %+v, got %+v", tc.exp, out)
+		}
+	}
+}
diff --git a/forks/goarista/gnmi/path.go b/forks/goarista/gnmi/path.go
new file mode 100644
index 0000000000000000000000000000000000000000..00280a8fc5924785e036e8daf7e9e187ec8a0406
--- /dev/null
+++ b/forks/goarista/gnmi/path.go
@@ -0,0 +1,251 @@
+// Copyright (c) 2017 Arista Networks, Inc.
+// Use of this source code is governed by the Apache License 2.0
+// that can be found in the COPYING file.
+
+package gnmi
+
+import (
+	"fmt"
+	"sort"
+	"strings"
+
+	pb "github.com/openconfig/gnmi/proto/gnmi"
+)
+
+// nextTokenIndex returns the end index of the first token.
+func nextTokenIndex(path string) int {
+	var inBrackets bool
+	var escape bool
+	for i, c := range path {
+		switch c {
+		case '[':
+			inBrackets = true
+			escape = false
+		case ']':
+			if !escape {
+				inBrackets = false
+			}
+			escape = false
+		case '\\':
+			escape = !escape
+		case '/':
+			if !inBrackets && !escape {
+				return i
+			}
+			escape = false
+		default:
+			escape = false
+		}
+	}
+	return len(path)
+}
+
+// SplitPath splits a gnmi path according to the spec. See
+// https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-path-conventions.md
+// No validation is done. Behavior is undefined if path is an invalid
+// gnmi path. TODO: Do validation?
+func SplitPath(path string) []string {
+	var result []string
+	if len(path) > 0 && path[0] == '/' {
+		path = path[1:]
+	}
+	for len(path) > 0 {
+		i := nextTokenIndex(path)
+		result = append(result, path[:i])
+		path = path[i:]
+		if len(path) > 0 && path[0] == '/' {
+			path = path[1:]
+		}
+	}
+	return result
+}
+
+// SplitPaths splits multiple gnmi paths
+func SplitPaths(paths []string) [][]string {
+	out := make([][]string, len(paths))
+	for i, path := range paths {
+		out[i] = SplitPath(path)
+	}
+	return out
+}
+
+// StrPath builds a human-readable form of a gnmi path.
+// e.g. /a/b/c[e=f]
+func StrPath(path *pb.Path) string {
+	if path == nil {
+		return "/"
+	} else if len(path.Elem) != 0 {
+		return strPathV04(path)
+	} else if len(path.Element) != 0 {
+		return strPathV03(path)
+	}
+	return "/"
+}
+
+// strPathV04 handles the v0.4 gnmi and later path.Elem member.
+func strPathV04(path *pb.Path) string {
+	b := &strings.Builder{}
+	for _, elm := range path.Elem {
+		b.WriteRune('/')
+		writeSafeString(b, elm.Name, '/')
+		if len(elm.Key) > 0 {
+			// Sort the keys so that they print in a conistent
+			// order. We don't have the YANG AST information, so the
+			// best we can do is sort them alphabetically.
+			keys := make([]string, 0, len(elm.Key))
+			for k := range elm.Key {
+				keys = append(keys, k)
+			}
+			sort.Strings(keys)
+			for _, k := range keys {
+				b.WriteRune('[')
+				b.WriteString(k)
+				b.WriteRune('=')
+				writeSafeString(b, elm.Key[k], ']')
+				b.WriteRune(']')
+			}
+		}
+	}
+	return b.String()
+}
+
+// strPathV03 handles the v0.3 gnmi and earlier path.Element member.
+func strPathV03(path *pb.Path) string {
+	return "/" + strings.Join(path.Element, "/")
+}
+
+// upgradePath modernizes a Path by translating the contents of the Element field to Elem
+func upgradePath(path *pb.Path) *pb.Path {
+	if len(path.Elem) == 0 {
+		var elems []*pb.PathElem
+		for _, element := range path.Element {
+			n, keys, _ := parseElement(element)
+			elems = append(elems, &pb.PathElem{Name: n, Key: keys})
+		}
+		path.Elem = elems
+		path.Element = nil
+	}
+	return path
+}
+
+// JoinPaths joins multiple gnmi paths and returns a string representation
+func JoinPaths(paths ...*pb.Path) *pb.Path {
+	var elems []*pb.PathElem
+	for _, path := range paths {
+		path = upgradePath(path)
+		elems = append(elems, path.Elem...)
+	}
+	return &pb.Path{Elem: elems}
+}
+
+func writeSafeString(b *strings.Builder, s string, esc rune) {
+	for _, c := range s {
+		if c == esc || c == '\\' {
+			b.WriteRune('\\')
+		}
+		b.WriteRune(c)
+	}
+}
+
+// ParseGNMIElements builds up a gnmi path, from user-supplied text
+func ParseGNMIElements(elms []string) (*pb.Path, error) {
+	var parsed []*pb.PathElem
+	for _, e := range elms {
+		n, keys, err := parseElement(e)
+		if err != nil {
+			return nil, err
+		}
+		parsed = append(parsed, &pb.PathElem{Name: n, Key: keys})
+	}
+	return &pb.Path{
+		Element: elms, // Backwards compatibility with pre-v0.4 gnmi
+		Elem:    parsed,
+	}, nil
+}
+
+// parseElement parses a path element, according to the gNMI specification. See
+// https://github.com/openconfig/reference/blame/master/rpc/gnmi/gnmi-path-conventions.md
+//
+// It returns the first string (the current element name), and an optional map of key name
+// value pairs.
+func parseElement(pathElement string) (string, map[string]string, error) {
+	// First check if there are any keys, i.e. do we have at least one '[' in the element
+	name, keyStart := findUnescaped(pathElement, '[')
+	if keyStart < 0 {
+		return name, nil, nil
+	}
+
+	// Error if there is no element name or if the "[" is at the beginning of the path element
+	if len(name) == 0 {
+		return "", nil, fmt.Errorf("failed to find element name in %q", pathElement)
+	}
+
+	// Look at the keys now.
+	keys := make(map[string]string)
+	keyPart := pathElement[keyStart:]
+	for keyPart != "" {
+		k, v, nextKey, err := parseKey(keyPart)
+		if err != nil {
+			return "", nil, err
+		}
+		keys[k] = v
+		keyPart = nextKey
+	}
+	return name, keys, nil
+}
+
+// parseKey returns the key name, key value and the remaining string to be parsed,
+func parseKey(s string) (string, string, string, error) {
+	if s[0] != '[' {
+		return "", "", "", fmt.Errorf("failed to find opening '[' in %q", s)
+	}
+	k, iEq := findUnescaped(s[1:], '=')
+	if iEq < 0 {
+		return "", "", "", fmt.Errorf("failed to find '=' in %q", s)
+	}
+	if k == "" {
+		return "", "", "", fmt.Errorf("failed to find key name in %q", s)
+	}
+
+	rhs := s[1+iEq+1:]
+	v, iClosBr := findUnescaped(rhs, ']')
+	if iClosBr < 0 {
+		return "", "", "", fmt.Errorf("failed to find ']' in %q", s)
+	}
+	if v == "" {
+		return "", "", "", fmt.Errorf("failed to find key value in %q", s)
+	}
+
+	next := rhs[iClosBr+1:]
+	return k, v, next, nil
+}
+
+// findUnescaped will return the index of the first unescaped match of 'find', and the unescaped
+// string leading up to it.
+func findUnescaped(s string, find byte) (string, int) {
+	// Take a fast track if there are no escape sequences
+	if strings.IndexByte(s, '\\') == -1 {
+		i := strings.IndexByte(s, find)
+		if i < 0 {
+			return s, -1
+		}
+		return s[:i], i
+	}
+
+	// Find the first match, taking care of escaped chars.
+	var b strings.Builder
+	var i int
+	len := len(s)
+	for i = 0; i < len; {
+		ch := s[i]
+		if ch == find {
+			return b.String(), i
+		} else if ch == '\\' && i < len-1 {
+			i++
+			ch = s[i]
+		}
+		b.WriteByte(ch)
+		i++
+	}
+	return b.String(), -1
+}
diff --git a/forks/goarista/gnmi/path_test.go b/forks/goarista/gnmi/path_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..27318b65c10a64949326995b727347d3f5de211a
--- /dev/null
+++ b/forks/goarista/gnmi/path_test.go
@@ -0,0 +1,308 @@
+// Copyright (c) 2017 Arista Networks, Inc.
+// Use of this source code is governed by the Apache License 2.0
+// that can be found in the COPYING file.
+
+package gnmi
+
+import (
+	"fmt"
+	"testing"
+
+	"github.com/aristanetworks/goarista/test"
+
+	pb "github.com/openconfig/gnmi/proto/gnmi"
+)
+
+func p(s ...string) []string {
+	return s
+}
+
+func TestSplitPath(t *testing.T) {
+	for i, tc := range []struct {
+		in  string
+		exp []string
+	}{{
+		in:  "/foo/bar",
+		exp: p("foo", "bar"),
+	}, {
+		in:  "/foo/bar/",
+		exp: p("foo", "bar"),
+	}, {
+		in:  "//foo//bar//",
+		exp: p("", "foo", "", "bar", ""),
+	}, {
+		in:  "/foo[name=///]/bar",
+		exp: p("foo[name=///]", "bar"),
+	}, {
+		in:  `/foo[name=[\\\]/]/bar`,
+		exp: p(`foo[name=[\\\]/]`, "bar"),
+	}, {
+		in:  `/foo[name=[\\]/bar`,
+		exp: p(`foo[name=[\\]`, "bar"),
+	}, {
+		in:  "/foo[a=1][b=2]/bar",
+		exp: p("foo[a=1][b=2]", "bar"),
+	}, {
+		in:  "/foo[a=1\\]2][b=2]/bar",
+		exp: p("foo[a=1\\]2][b=2]", "bar"),
+	}, {
+		in:  "/foo[a=1][b=2]/bar\\baz",
+		exp: p("foo[a=1][b=2]", "bar\\baz"),
+	}} {
+		got := SplitPath(tc.in)
+		if !test.DeepEqual(tc.exp, got) {
+			t.Errorf("[%d] unexpect split for %q. Expected: %v, Got: %v",
+				i, tc.in, tc.exp, got)
+		}
+	}
+}
+
+func TestStrPath(t *testing.T) {
+	for i, tc := range []struct {
+		path string
+	}{{
+		path: "/",
+	}, {
+		path: "/foo/bar",
+	}, {
+		path: "/foo[name=a]/bar",
+	}, {
+		path: "/foo[a=1][b=2]/bar",
+	}, {
+		path: "/foo[a=1\\]2][b=2]/bar",
+	}, {
+		path: "/foo[a=1][b=2]/bar\\/baz",
+	}} {
+		sElms := SplitPath(tc.path)
+		pbPath, err := ParseGNMIElements(sElms)
+		if err != nil {
+			t.Errorf("failed to parse %s: %s", sElms, err)
+		}
+		s := StrPath(pbPath)
+		if !test.DeepEqual(tc.path, s) {
+			t.Errorf("[%d] want %s, got %s", i, tc.path, s)
+		}
+	}
+}
+
+func TestStrPathBackwardsCompat(t *testing.T) {
+	for i, tc := range []struct {
+		path *pb.Path
+		str  string
+	}{{
+		path: &pb.Path{
+			Element: p("foo[a=1][b=2]", "bar"),
+		},
+		str: "/foo[a=1][b=2]/bar",
+	}} {
+		got := StrPath(tc.path)
+		if got != tc.str {
+			t.Errorf("[%d] want %q, got %q", i, tc.str, got)
+		}
+	}
+}
+
+func TestParseElement(t *testing.T) {
+	// test cases
+	cases := []struct {
+		// name is the name of the test useful if you want to run a single test
+		// from the command line -run TestParseElement/<name>
+		name string
+		// in is the path element to be parsed
+		in string
+		// fieldName is field name (YANG node name) expected to be parsed from the path element.
+		// Normally this is simply the path element, or if the path element contains keys this is
+		// the text before the first [
+		fieldName string
+		// keys is a map of the expected key value pairs from within the []s in the
+		// `path element.
+		//
+		// For example prefix[ip-prefix=10.0.0.0/24][masklength-range=26..28]
+		// fieldName would be "prefix"
+		// keys would be {"ip-prefix": "10.0.0.0/24", "masklength-range": "26..28"}
+		keys map[string]string
+		// expectedError is the exact error we expect.
+		expectedError error
+	}{{
+		name:      "no_elms",
+		in:        "hello",
+		fieldName: "hello",
+	}, {
+		name:          "single_open",
+		in:            "[",
+		expectedError: fmt.Errorf("failed to find element name in %q", "["),
+	}, {
+		name:          "no_equal_no_close",
+		in:            "hello[there",
+		expectedError: fmt.Errorf("failed to find '=' in %q", "[there"),
+	}, {
+		name:          "no_equals",
+		in:            "hello[there]",
+		expectedError: fmt.Errorf("failed to find '=' in %q", "[there]"),
+	}, {
+		name:          "no_left_side",
+		in:            "hello[=there]",
+		expectedError: fmt.Errorf("failed to find key name in %q", "[=there]"),
+	}, {
+		name:          "no_right_side",
+		in:            "hello[there=]",
+		expectedError: fmt.Errorf("failed to find key value in %q", "[there=]"),
+	}, {
+		name:          "hanging_escape",
+		in:            "hello[there\\",
+		expectedError: fmt.Errorf("failed to find '=' in %q", "[there\\"),
+	}, {
+		name:      "single_name_value",
+		in:        "hello[there=where]",
+		fieldName: "hello",
+		keys:      map[string]string{"there": "where"},
+	}, {
+		name:      "single_value_with=",
+		in:        "hello[there=whe=r=e]",
+		fieldName: "hello",
+		keys:      map[string]string{"there": "whe=r=e"},
+	}, {
+		name:      "single_value_with=_and_escaped_]",
+		in:        `hello[there=whe=\]r=e]`,
+		fieldName: "hello",
+		keys:      map[string]string{"there": `whe=]r=e`},
+	}, {
+		name:      "single_value_with[",
+		in:        "hello[there=w[[here]",
+		fieldName: "hello",
+		keys:      map[string]string{"there": "w[[here"},
+	}, {
+		name:          "value_single_open",
+		in:            "hello[first=value][",
+		expectedError: fmt.Errorf("failed to find '=' in %q", "["),
+	}, {
+		name:          "value_no_close",
+		in:            "hello[there=where][somename",
+		expectedError: fmt.Errorf("failed to find '=' in %q", "[somename"),
+	}, {
+		name:          "value_no_equals",
+		in:            "hello[there=where][somename]",
+		expectedError: fmt.Errorf("failed to find '=' in %q", "[somename]"),
+	}, {
+		name:          "no_left_side",
+		in:            "hello[there=where][=somevalue]",
+		expectedError: fmt.Errorf("failed to find key name in %q", "[=somevalue]"),
+	}, {
+		name:          "no_right_side",
+		in:            "hello[there=where][somename=]",
+		expectedError: fmt.Errorf("failed to find key value in %q", "[somename=]"),
+	}, {
+		name:      "two_name_values",
+		in:        "hello[there=where][somename=somevalue]",
+		fieldName: "hello",
+		keys:      map[string]string{"there": "where", "somename": "somevalue"},
+	}, {
+		name:      "three_name_values",
+		in:        "hello[there=where][somename=somevalue][anothername=value]",
+		fieldName: "hello",
+		keys: map[string]string{"there": "where", "somename": "somevalue",
+			"anothername": "value"},
+	}, {
+		name:      "aserisk_value",
+		in:        "hello[there=*][somename=somevalue][anothername=value]",
+		fieldName: "hello",
+		keys: map[string]string{"there": "*", "somename": "somevalue",
+			"anothername": "value"},
+	}}
+
+	for _, tc := range cases {
+		t.Run(tc.name, func(t *testing.T) {
+			fieldName, keys, err := parseElement(tc.in)
+			if !test.DeepEqual(tc.expectedError, err) {
+				t.Fatalf("[%s] expected err %#v, got %#v", tc.name, tc.expectedError, err)
+			}
+			if !test.DeepEqual(tc.keys, keys) {
+				t.Fatalf("[%s] expected output %#v, got %#v", tc.name, tc.keys, keys)
+			}
+			if tc.fieldName != fieldName {
+				t.Fatalf("[%s] expected field name %s, got %s", tc.name, tc.fieldName, fieldName)
+			}
+		})
+	}
+}
+
+func strToPath(pathStr string) *pb.Path {
+	splitPath := SplitPath(pathStr)
+	path, _ := ParseGNMIElements(splitPath)
+	path.Element = nil
+	return path
+}
+
+func strsToPaths(pathStrs []string) []*pb.Path {
+	var paths []*pb.Path
+	for _, splitPath := range SplitPaths(pathStrs) {
+		path, _ := ParseGNMIElements(splitPath)
+		path.Element = nil
+		paths = append(paths, path)
+	}
+	return paths
+}
+
+func TestJoinPath(t *testing.T) {
+	cases := []struct {
+		paths []*pb.Path
+		exp   string
+	}{{
+		paths: strsToPaths([]string{"/foo/bar", "/baz/qux"}),
+		exp:   "/foo/bar/baz/qux",
+	},
+		{
+			paths: strsToPaths([]string{
+				"/foo/bar[somekey=someval][otherkey=otherval]", "/baz/qux"}),
+			exp: "/foo/bar[otherkey=otherval][somekey=someval]/baz/qux",
+		},
+		{
+			paths: strsToPaths([]string{
+				"/foo/bar[somekey=someval][otherkey=otherval]",
+				"/baz/qux[somekey=someval][otherkey=otherval]"}),
+			exp: "/foo/bar[otherkey=otherval][somekey=someval]/" +
+				"baz/qux[otherkey=otherval][somekey=someval]",
+		},
+		{
+			paths: []*pb.Path{
+				{Element: []string{"foo", "bar[somekey=someval][otherkey=otherval]"}},
+				{Element: []string{"baz", "qux[somekey=someval][otherkey=otherval]"}}},
+			exp: "/foo/bar[somekey=someval][otherkey=otherval]/" +
+				"baz/qux[somekey=someval][otherkey=otherval]",
+		},
+	}
+
+	for _, tc := range cases {
+		got := JoinPaths(tc.paths...)
+		exp := strToPath(tc.exp)
+		exp.Element = nil
+		if !test.DeepEqual(got, exp) {
+			t.Fatalf("ERROR!\n Got: %s,\n Want %s\n", got, exp)
+		}
+	}
+}
+
+func BenchmarkPathElementToSigleElementName(b *testing.B) {
+	for i := 0; i < b.N; i++ {
+		_, _, _ = parseElement("hello")
+	}
+}
+
+func BenchmarkPathElementTwoKeys(b *testing.B) {
+	for i := 0; i < b.N; i++ {
+		_, _, _ = parseElement("hello[hello=world][bye=moon]")
+	}
+}
+
+func BenchmarkPathElementBadKeys(b *testing.B) {
+	for i := 0; i < b.N; i++ {
+		_, _, _ = parseElement("hello[hello=world][byemoon]")
+	}
+}
+
+func BenchmarkPathElementMaxKeys(b *testing.B) {
+	for i := 0; i < b.N; i++ {
+		_, _, _ = parseElement("hello[name=firstName][name=secondName][name=thirdName]" +
+			"[name=fourthName][name=fifthName][name=sixthName]")
+	}
+}
diff --git a/forks/goarista/openconfig/client/client.go b/forks/goarista/openconfig/client/client.go
new file mode 100644
index 0000000000000000000000000000000000000000..1d7d1992f375d06b747161dc4570124696202fac
--- /dev/null
+++ b/forks/goarista/openconfig/client/client.go
@@ -0,0 +1,132 @@
+// Copyright (c) 2016 Arista Networks, Inc.
+// Use of this source code is governed by the Apache License 2.0
+// that can be found in the COPYING file.
+
+// Package ciena provides helper functions for OpenConfig CLI tools.
+package client
+
+import (
+	"io"
+	"strings"
+	"sync"
+
+	"github.com/golang/glog"
+	"github.com/golang/protobuf/proto"
+	"github.com/openconfig/reference/rpc/openconfig"
+	"golang.org/x/net/context"
+	"google.golang.org/grpc"
+	"google.golang.org/grpc/metadata"
+)
+
+const defaultPort = "6030"
+
+// PublishFunc is the method to publish responses
+type PublishFunc func(addr string, message proto.Message)
+
+// Client is a connected gRPC ciena
+type Client struct {
+	client openconfig.OpenConfigClient
+	ctx    context.Context
+	device string
+}
+
+// New creates a new gRPC ciena and connects it
+func New(username, password, addr string, opts []grpc.DialOption) *Client {
+	device := addr
+	if !strings.ContainsRune(addr, ':') {
+		addr += ":" + defaultPort
+	}
+	// Make sure we don't move past the grpc.Dial() call until we actually
+	// established an HTTP/2 connection successfully.
+	opts = append(opts, grpc.WithBlock())
+	conn, err := grpc.Dial(addr, opts...)
+	if err != nil {
+		glog.Fatalf("Failed to dial: %s", err)
+	}
+	glog.Infof("Connected to %s", addr)
+	client := openconfig.NewOpenConfigClient(conn)
+
+	ctx := context.Background()
+	if username != "" {
+		ctx = metadata.NewOutgoingContext(ctx, metadata.Pairs(
+			"username", username,
+			"password", password))
+	}
+	return &Client{
+		client: client,
+		device: device,
+		ctx:    ctx,
+	}
+}
+
+// Get sends a get request and returns the responses
+func (c *Client) Get(path string) []*openconfig.Notification {
+	req := &openconfig.GetRequest{
+		Path: []*openconfig.Path{
+			{
+				Element: strings.Split(path, "/"),
+			},
+		},
+	}
+	response, err := c.client.Get(c.ctx, req)
+	if err != nil {
+		glog.Fatalf("Get failed: %s", err)
+	}
+	return response.Notification
+}
+
+// Subscribe sends subscriptions, and consumes responses.
+// The given publish function is used to publish SubscribeResponses received
+// for the given subscriptions, when connected to the given host, with the
+// given user/pass pair, or the ciena-side cert specified in the gRPC opts.
+// This function does not normally return so it should probably be run in its
+// own goroutine.  When this function returns, the given WaitGroup is marked
+// as done.
+func (c *Client) Subscribe(wg *sync.WaitGroup, subscriptions []string,
+	publish PublishFunc) {
+	defer wg.Done()
+	stream, err := c.client.Subscribe(c.ctx)
+	if err != nil {
+		glog.Fatalf("Subscribe failed: %s", err)
+	}
+	defer stream.CloseSend()
+
+	for _, path := range subscriptions {
+		sub := &openconfig.SubscribeRequest{
+			Request: &openconfig.SubscribeRequest_Subscribe{
+				Subscribe: &openconfig.SubscriptionList{
+					Subscription: []*openconfig.Subscription{
+						{
+							Path: &openconfig.Path{Element: strings.Split(path, "/")},
+						},
+					},
+				},
+			},
+		}
+
+		glog.Infof("Sending subscribe request: %s", sub)
+		err = stream.Send(sub)
+		if err != nil {
+			glog.Fatalf("Failed to subscribe: %s", err)
+		}
+	}
+
+	for {
+		resp, err := stream.Recv()
+		if err != nil {
+			if err != io.EOF {
+				glog.Fatalf("Error received from the server: %s", err)
+			}
+			return
+		}
+		switch resp := resp.Response.(type) {
+		case *openconfig.SubscribeResponse_SyncResponse:
+			if !resp.SyncResponse {
+				panic("initial sync failed," +
+					" check that you're using a ciena compatible with the server")
+			}
+		}
+		glog.V(3).Info(resp)
+		publish(c.device, resp)
+	}
+}
diff --git a/forks/goarista/openconfig/client/flags.go b/forks/goarista/openconfig/client/flags.go
new file mode 100644
index 0000000000000000000000000000000000000000..fb91bba3715c286babedb3e58caf221b576d213b
--- /dev/null
+++ b/forks/goarista/openconfig/client/flags.go
@@ -0,0 +1,113 @@
+// Copyright (c) 2016 Arista Networks, Inc.
+// Use of this source code is governed by the Apache License 2.0
+// that can be found in the COPYING file.
+
+package client
+
+import (
+	"crypto/tls"
+	"crypto/x509"
+	"flag"
+	"io/ioutil"
+	"os"
+	"strings"
+
+	"github.com/golang/glog"
+	"google.golang.org/grpc"
+	"google.golang.org/grpc/credentials"
+)
+
+const (
+	// HostnameArg is the value to be replaced by the actual hostname
+	HostnameArg = "HOSTNAME"
+)
+
+// ParseHostnames parses a comma-separated list of names and replaces HOSTNAME with the current
+// hostname in it
+func ParseHostnames(list string) ([]string, error) {
+	items := strings.Split(list, ",")
+	hostname, err := os.Hostname()
+	if err != nil {
+		return nil, err
+	}
+	names := make([]string, len(items))
+	for i, name := range items {
+		if name == HostnameArg {
+			name = hostname
+		}
+		names[i] = name
+	}
+	return names, nil
+}
+
+// ParseFlags registers some additional common flags,
+// parses the flags, and returns the resulting gRPC options,
+// and other settings to connect to the gRPC interface.
+func ParseFlags() (username string, password string, subscriptions, addrs []string,
+	opts []grpc.DialOption) {
+
+	var (
+		addrsFlag = flag.String("addrs", "localhost:6030",
+			"Comma-separated list of addresses of OpenConfig gRPC servers. The address 'HOSTNAME' "+
+				"is replaced by the current hostname.")
+
+		caFileFlag = flag.String("cafile", "",
+			"Path to server TLS certificate file")
+
+		certFileFlag = flag.String("certfile", "",
+			"Path to ciena TLS certificate file")
+
+		keyFileFlag = flag.String("keyfile", "",
+			"Path to ciena TLS private key file")
+
+		passwordFlag = flag.String("password", "",
+			"Password to authenticate with")
+
+		subscribeFlag = flag.String("subscribe", "",
+			"Comma-separated list of paths to subscribe to upon connecting to the server")
+
+		usernameFlag = flag.String("username", "",
+			"Username to authenticate with")
+
+		tlsFlag = flag.Bool("tls", false,
+			"Enable TLS")
+	)
+
+	flag.Parse()
+	if *tlsFlag || *caFileFlag != "" || *certFileFlag != "" {
+		config := &tls.Config{}
+		if *caFileFlag != "" {
+			b, err := ioutil.ReadFile(*caFileFlag)
+			if err != nil {
+				glog.Fatal(err)
+			}
+			cp := x509.NewCertPool()
+			if !cp.AppendCertsFromPEM(b) {
+				glog.Fatalf("credentials: failed to append certificates")
+			}
+			config.RootCAs = cp
+		} else {
+			config.InsecureSkipVerify = true
+		}
+		if *certFileFlag != "" {
+			if *keyFileFlag == "" {
+				glog.Fatalf("Please provide both -certfile and -keyfile")
+			}
+			cert, err := tls.LoadX509KeyPair(*certFileFlag, *keyFileFlag)
+			if err != nil {
+				glog.Fatal(err)
+			}
+			config.Certificates = []tls.Certificate{cert}
+		}
+		opts = append(opts, grpc.WithTransportCredentials(credentials.NewTLS(config)))
+	} else {
+		opts = append(opts, grpc.WithInsecure())
+	}
+	var err error
+	addrs, err = ParseHostnames(*addrsFlag)
+	if err != nil {
+		glog.Fatal(err)
+	}
+	subscriptions = strings.Split(*subscribeFlag, ",")
+	return *usernameFlag, *passwordFlag, subscriptions, addrs, opts
+}
diff --git a/forks/goarista/openconfig/json.go b/forks/goarista/openconfig/json.go
new file mode 100644
index 0000000000000000000000000000000000000000..8eba88baded76b426451ef862d332a3c712a1db2
--- /dev/null
+++ b/forks/goarista/openconfig/json.go
@@ -0,0 +1,237 @@
+// Copyright (c) 2016 Arista Networks, Inc.
+// Use of this source code is governed by the Apache License 2.0
+// that can be found in the COPYING file.
+
+package openconfig
+
+import (
+	"bytes"
+	"encoding/json"
+	"fmt"
+	"strings"
+
+	"github.com/openconfig/reference/rpc/openconfig"
+)
+
+// joinPath builds a string out of an Element
+func joinPath(path *openconfig.Path) string {
+	if path == nil {
+		return ""
+	}
+	return strings.Join(path.Element, "/")
+}
+
+func convertUpdate(update *openconfig.Update) (interface{}, error) {
+	switch update.Value.Type {
+	case openconfig.Type_JSON:
+		var value interface{}
+		decoder := json.NewDecoder(bytes.NewReader(update.Value.Value))
+		decoder.UseNumber()
+		if err := decoder.Decode(&value); err != nil {
+			return nil, fmt.Errorf("Malformed JSON update %q in %s",
+				update.Value.Value, update)
+		}
+		return value, nil
+	case openconfig.Type_BYTES:
+		return update.Value.Value, nil
+	default:
+		return nil,
+			fmt.Errorf("Unhandled type of value %v in %s", update.Value.Type, update)
+	}
+}
+
+// NotificationToJSON converts a Notification into a JSON string
+func NotificationToJSON(notif *openconfig.Notification) (string, error) {
+	m := make(map[string]interface{}, 1)
+	m["timestamp"] = notif.Timestamp
+	m["path"] = "/" + joinPath(notif.Prefix)
+	if len(notif.Update) != 0 {
+		updates := make(map[string]interface{}, len(notif.Update))
+		var err error
+		for _, update := range notif.Update {
+			updates[joinPath(update.Path)], err = convertUpdate(update)
+			if err != nil {
+				return "", err
+			}
+		}
+		m["updates"] = updates
+	}
+	if len(notif.Delete) != 0 {
+		deletes := make([]string, len(notif.Delete))
+		for i, del := range notif.Delete {
+			deletes[i] = joinPath(del)
+		}
+		m["deletes"] = deletes
+	}
+	m = map[string]interface{}{"notification": m}
+	js, err := json.MarshalIndent(m, "", "  ")
+	if err != nil {
+		return "", err
+	}
+	return string(js), nil
+}
+
+// SubscribeResponseToJSON converts a SubscribeResponse into a JSON string
+func SubscribeResponseToJSON(resp *openconfig.SubscribeResponse) (string, error) {
+	m := make(map[string]interface{}, 1)
+	var err error
+	switch resp := resp.Response.(type) {
+	case *openconfig.SubscribeResponse_Update:
+		return NotificationToJSON(resp.Update)
+	case *openconfig.SubscribeResponse_Heartbeat:
+		m["heartbeat"] = resp.Heartbeat.Interval
+	case *openconfig.SubscribeResponse_SyncResponse:
+		m["syncResponse"] = resp.SyncResponse
+	default:
+		return "", fmt.Errorf("Unknown type of response: %T: %s", resp, resp)
+	}
+	js, err := json.MarshalIndent(m, "", "  ")
+	if err != nil {
+		return "", err
+	}
+	return string(js), nil
+}
+
+// EscapeFunc is the escaping method for attribute names
+type EscapeFunc func(k string) string
+
+// escapeValue looks for maps in an interface and escapes their keys
+func escapeValue(value interface{}, escape EscapeFunc) interface{} {
+	valueMap, ok := value.(map[string]interface{})
+	if !ok {
+		return value
+	}
+	escapedMap := make(map[string]interface{}, len(valueMap))
+	for k, v := range valueMap {
+		escapedKey := escape(k)
+		escapedMap[escapedKey] = escapeValue(v, escape)
+	}
+	return escapedMap
+}
+
+// addPathToMap creates a map[string]interface{} from a path. It returns the node in
+// the map corresponding to the last element in the path
+func addPathToMap(root map[string]interface{}, path []string, escape EscapeFunc) (
+	map[string]interface{}, error) {
+	parent := root
+	for _, element := range path {
+		k := escape(element)
+		node, found := parent[k]
+		if !found {
+			node = map[string]interface{}{}
+			parent[k] = node
+		}
+		var ok bool
+		parent, ok = node.(map[string]interface{})
+		if !ok {
+			return nil, fmt.Errorf(
+				"Node %s is of type %T (expected map[string]interface traversing %q)",
+				element, node, path)
+		}
+	}
+	return parent, nil
+}
+
+// NotificationToMap maps a Notification into a nested map of entities
+func NotificationToMap(addr string, notification *openconfig.Notification,
+	escape EscapeFunc) (map[string]interface{}, error) {
+	if escape == nil {
+		escape = func(name string) string {
+			return name
+		}
+	}
+	prefix := notification.GetPrefix()
+
+	// Convert deletes
+	var deletes map[string]interface{}
+	notificationDeletes := notification.GetDelete()
+	if notificationDeletes != nil {
+		deletes = make(map[string]interface{})
+		node := deletes
+		if prefix != nil {
+			var err error
+			node, err = addPathToMap(node, prefix.Element, escape)
+			if err != nil {
+				return nil, err
+			}
+		}
+		for _, delete := range notificationDeletes {
+			_, err := addPathToMap(node, delete.Element, escape)
+			if err != nil {
+				return nil, err
+			}
+		}
+	}
+
+	// Convert updates
+	var updates map[string]interface{}
+	notificationUpdates := notification.GetUpdate()
+	if notificationUpdates != nil {
+		updates = make(map[string]interface{})
+		node := updates
+		if prefix != nil {
+			var err error
+			node, err = addPathToMap(node, prefix.Element, escape)
+			if err != nil {
+				return nil, err
+			}
+		}
+		for _, update := range notificationUpdates {
+			updateNode := node
+			path := update.GetPath()
+			elementLen := len(path.Element)
+
+			// Convert all elements before the leaf
+			if elementLen > 1 {
+				parentElements := path.Element[:elementLen-1]
+				var err error
+				updateNode, err = addPathToMap(updateNode, parentElements, escape)
+				if err != nil {
+					return nil, err
+				}
+			}
+
+			// Convert the value in the leaf
+			value := update.GetValue()
+			var unmarshaledValue interface{}
+			switch value.Type {
+			case openconfig.Type_JSON:
+				if err := json.Unmarshal(value.Value, &unmarshaledValue); err != nil {
+					return nil, err
+				}
+			case openconfig.Type_BYTES:
+				unmarshaledValue = update.Value.Value
+			default:
+				return nil, fmt.Errorf("Unexpected value type %s for path %v",
+					value.Type, path)
+			}
+			updateNode[escape(path.Element[elementLen-1])] = escapeValue(
+				unmarshaledValue, escape)
+		}
+	}
+
+	// Build the complete map to return
+	root := map[string]interface{}{
+		"timestamp": notification.Timestamp,
+	}
+	if addr != "" {
+		root["dataset"] = addr
+	}
+	if deletes != nil {
+		root["delete"] = deletes
+	}
+	if updates != nil {
+		root["update"] = updates
+	}
+	return root, nil
+}
+
+// NotificationToJSONDocument maps a Notification into a single JSON document
+func NotificationToJSONDocument(addr string, notification *openconfig.Notification,
+	escape EscapeFunc) ([]byte, error) {
+	m, err := NotificationToMap(addr, notification, escape)
+	if err != nil {
+		return nil, err
+	}
+	return json.Marshal(m)
+}
diff --git a/forks/goarista/openconfig/json_test.go b/forks/goarista/openconfig/json_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..1dfc41d5789b9c8fcbb1a6a8b30918f77873a88a
--- /dev/null
+++ b/forks/goarista/openconfig/json_test.go
@@ -0,0 +1,143 @@
+// Copyright (c) 2016 Arista Networks, Inc.
+// Use of this source code is governed by the Apache License 2.0
+// that can be found in the COPYING file.
+
+package openconfig
+
+import (
+	"encoding/json"
+	"testing"
+
+	"github.com/aristanetworks/goarista/test"
+
+	"github.com/openconfig/reference/rpc/openconfig"
+)
+
+func TestNotificationToMap(t *testing.T) {
+	value := map[string]interface{}{
+		"239.255.255.250_0.0.0.0": map[string]interface{}{
+			"creationTime": 4.567969230573434e+06,
+		},
+	}
+	valueJSON, err := json.Marshal(value)
+	if err != nil {
+		t.Fatal(err)
+	}
+	tests := []struct {
+		notification openconfig.Notification
+		json         map[string]interface{}
+	}{{
+		notification: openconfig.Notification{
+			Prefix: &openconfig.Path{
+				Element: []string{
+					"foo",
+				},
+			},
+			Update: []*openconfig.Update{
+				{
+					Path: &openconfig.Path{
+						Element: []string{
+							"route1",
+						},
+					},
+					Value: &openconfig.Value{
+						Value: valueJSON,
+					},
+				}, {
+					Path: &openconfig.Path{
+						Element: []string{
+							"route2",
+						},
+					},
+					Value: &openconfig.Value{
+						Value: valueJSON,
+					},
+				}},
+		},
+		json: map[string]interface{}{
+			"timestamp": int64(0),
+			"dataset":   "cairo",
+			"update": map[string]interface{}{
+				"foo": map[string]interface{}{
+					"route1": map[string]interface{}{
+						"239.255.255.250_0.0.0.0": map[string]interface{}{
+							"creationTime": 4.567969230573434e+06,
+						},
+					},
+					"route2": map[string]interface{}{
+						"239.255.255.250_0.0.0.0": map[string]interface{}{
+							"creationTime": 4.567969230573434e+06,
+						},
+					},
+				},
+			},
+		},
+	}, {
+		notification: openconfig.Notification{
+			Prefix: &openconfig.Path{
+				Element: []string{
+					"foo", "bar",
+				},
+			},
+			Delete: []*openconfig.Path{
+				{
+					Element: []string{
+						"route", "237.255.255.250_0.0.0.0",
+					}},
+				{
+					Element: []string{
+						"route", "238.255.255.250_0.0.0.0",
+					},
+				},
+			},
+			Update: []*openconfig.Update{{
+				Path: &openconfig.Path{
+					Element: []string{
+						"route",
+					},
+				},
+				Value: &openconfig.Value{
+					Value: valueJSON,
+				},
+			}},
+		},
+		json: map[string]interface{}{
+			"timestamp": int64(0),
+			"dataset":   "cairo",
+			"delete": map[string]interface{}{
+				"foo": map[string]interface{}{
+					"bar": map[string]interface{}{
+						"route": map[string]interface{}{
+							"237.255.255.250_0.0.0.0": map[string]interface{}{},
+							"238.255.255.250_0.0.0.0": map[string]interface{}{},
+						},
+					},
+				},
+			},
+			"update": map[string]interface{}{
+				"foo": map[string]interface{}{
+					"bar": map[string]interface{}{
+						"route": map[string]interface{}{
+							"239.255.255.250_0.0.0.0": map[string]interface{}{
+								"creationTime": 4.567969230573434e+06,
+							},
+						},
+					},
+				},
+			},
+		},
+	}}
+	for _, tcase := range tests {
+		actual, err := NotificationToMap("cairo", &tcase.notification, nil)
+		if err != nil {
+			t.Fatal(err)
+		}
+		diff := test.Diff(tcase.json, actual)
+		if len(diff) > 0 {
+			expectedJSON, _ := json.Marshal(tcase.json)
+			actualJSON, _ := json.Marshal(actual)
+			t.Fatalf("Unexpected diff: %s\nExpected:\n%s\nGot:\n%s\n)", diff, expectedJSON,
+				actualJSON)
+		}
+	}
+}
diff --git a/go.mod b/go.mod
index 704d65fb45730d13c36476f898fab07824e4e357..cec3a1d5e20c459677d3225f5bda04b71e285efa 100644
--- a/go.mod
+++ b/go.mod
@@ -5,7 +5,7 @@ go 1.14
 require (
 	code.fbi.h-da.de/cocsn/swagger/apis v0.0.0-20200924152423-61030cab7b88
 	code.fbi.h-da.de/cocsn/yang-models v0.0.3
-	github.com/aristanetworks/goarista v0.0.0-20201120222254-94a892eb0c6a // indirect
+	github.com/aristanetworks/goarista v0.0.0-20201120222254-94a892eb0c6a
 	github.com/gdamore/tcell/v2 v2.0.1-0.20201017141208-acf90d56d591
 	github.com/go-openapi/runtime v0.19.22
 	github.com/go-openapi/strfmt v0.19.5
diff --git a/nucleus/cli-handling.go b/nucleus/cli-handling.go
index ec668cae154b1468a509cdc69ff010c50775fbd5..6ebe88cec126b0cb6255fb00e3363f8648ae7a7a 100644
--- a/nucleus/cli-handling.go
+++ b/nucleus/cli-handling.go
@@ -9,6 +9,7 @@ package nucleus
 import (
 	"context"
 	"github.com/google/uuid"
+	"github.com/spf13/viper"
 	"io"
 	"net"
 	"os"
@@ -110,7 +111,7 @@ func getCLIGoing(core *Core) {
 
 	log.Info("Starting: GetCLIGoing")
 	// Boot-up the control interface for the cli
-	cliControlListener, err := net.Listen("tcp", core.config.CliSocket)
+	cliControlListener, err := net.Listen("tcp", viper.GetString("socket"))
 	if err != nil {
 		log.Fatal(err)
 	}
diff --git a/nucleus/controller.go b/nucleus/controller.go
index e30b0f0617ccd6e77289ee4a5631c33bf7424c52..9de78e91939e778db1c00f1c58b40d7d9ba9c72d 100644
--- a/nucleus/controller.go
+++ b/nucleus/controller.go
@@ -9,21 +9,11 @@ import (
 	"os"
 )
 
-type controllerConfig struct {
-	CliSocket        string
-	DatabaseSocket   string
-	DatabaseUser     string
-	DatabasePassword string
-	DatabaseCrypto   bool
-	ConfigPath       string
-}
-
 // Core is the representation of the controllers core
 type Core struct {
 	southboundInterfaces   map[string]SouthboundInterface
 	prinipalNetworkDomains map[uuid.UUID]PrincipalNetworkDomain
 	database               database.Database
-	config                 controllerConfig
 	IsRunning              chan bool
 }
 
diff --git a/nucleus/device.go b/nucleus/device.go
index 587678d3dc0a67407e55413dbe9ede57f10aeda7..f39d1bc6eb3d7582f352de1d8ed49423b61f561e 100644
--- a/nucleus/device.go
+++ b/nucleus/device.go
@@ -1,11 +1,11 @@
 package nucleus
 
 import (
-	opb "code.fbi.h-da.de/cocsn/gosdn/api/proto/openconfig/openconfig_interfaces"
 	"code.fbi.h-da.de/cocsn/yang-models/generated/openconfig"
 	"github.com/google/uuid"
 	"github.com/openconfig/gnmi/proto/gnmi"
 	"github.com/openconfig/ygot/ygot"
+	log "github.com/sirupsen/logrus"
 )
 
 type Device struct {
@@ -20,26 +20,22 @@ type Device struct {
 // Also all that Interface Call specific logic belongs to SBI!
 func (d Device) Add(resp interface{}) {
 	r := resp.(*gnmi.GetResponse)
+	log.Debug(r)
 	device := d.Device.(*openconfig.Device)
+	ifaces := make(map[string]*openconfig.OpenconfigInterfaces_Interfaces_Interface)
+	ifaces["test"] = &openconfig.OpenconfigInterfaces_Interfaces_Interface{
+		Aggregation:   nil,
+		Config:        nil,
+		Ethernet:      nil,
+		HoldTime:      nil,
+		Name:          nil,
+		RoutedVlan:    nil,
+		State:         nil,
+		Subinterfaces: nil,
+	}
 	// It's possible that the gnmi response already is too Arista-fied to be used w/YGOT
-	ifs := opb.Interfaces{
-		Interface: []*opb.Interfaces_InterfaceKey{
-			{
-				Interface: &opb.Interfaces_Interface{
-					Config: &opb.Interfaces_Interface_Config{
-						Description:  nil,
-						Enabled:      nil,
-						LoopbackMode: nil,
-						Mtu:          nil,
-						Name:         nil,
-						Type:         0,
-					},
-					HoldTime:      nil,
-					State:         nil,
-					Subinterfaces: nil,
-				},
-			},
-		},
+	ifs := &openconfig.OpenconfigInterfaces_Interfaces{
+		Interface: ifaces,
 	}
 	device.Interfaces = ifs
 	d.Device = device