Repository: gopcua/opcua
Pull request #147 (2019-03-15 05:51:17)

Consolidate datatypes and services in ua package
This patch moves the code from `datatypes` and `services` into the `ua` package.

The distinction between datatypes and services is somewhat arbitrary and makes writing code a bit more difficult. For example, you have to know that `DiagnosticInfo` is a data type but `UserIdentityToken` belongs to services. Also, `datatypes` and `services` are long package names (and plural) and we need them everywhere. 

Additionally, I want to resurrect the code generator for the services and enums now that we can decode them all with the reflection codec.



Patch:
diff --git a/client.go b/client.go
index d550e32..ec89ce6 100644
--- a/client.go
+++ b/client.go
@@ -5,8 +5,7 @@ import (
 	"fmt"
 	"time"
 
-	uad "github.com/gopcua/opcua/datatypes"
-	uas "github.com/gopcua/opcua/services"
+	"github.com/gopcua/opcua/ua"
 	"github.com/gopcua/opcua/uacp"
 	"github.com/gopcua/opcua/uasc"
 )
@@ -44,7 +43,7 @@ func (c *Client) Open() error {
 	// todo(fs): this should probably be configurable.
 	sessionCfg := uasc.NewClientSessionConfig(
 		[]string{"en-US"},
-		uad.NewAnonymousIdentityToken("open62541-anonymous-policy"),
+		ua.NewAnonymousIdentityToken("open62541-anonymous-policy"),
 	)
 
 	session := uasc.NewSession(sechan, sessionCfg)
@@ -67,15 +66,15 @@ func (c *Client) Close() error {
 
 // Node returns a node object which accesses its attributes
 // through this client connection.
-func (c *Client) Node(id *uad.NodeID) *Node {
+func (c *Client) Node(id *ua.NodeID) *Node {
 	return &Node{ID: id, c: c}
 }
 
 // Read executes a synchronous read request.
-func (c *Client) Read(req *uas.ReadRequest) (*uas.ReadResponse, error) {
-	var res *uas.ReadResponse
+func (c *Client) Read(req *ua.ReadRequest) (*ua.ReadResponse, error) {
+	var res *ua.ReadResponse
 	err := c.sechan.Send(req, func(v interface{}) error {
-		r, ok := v.(*uas.ReadResponse)
+		r, ok := v.(*ua.ReadResponse)
 		if !ok {
 			return fmt.Errorf("invalid response: %T", v)
 		}
@@ -86,10 +85,10 @@ func (c *Client) Read(req *uas.ReadRequest) (*uas.ReadResponse, error) {
 }
 
 // Browse executes a synchronous browse request.
-func (c *Client) Browse(req *uas.BrowseRequest) (*uas.BrowseResponse, error) {
-	var res *uas.BrowseResponse
+func (c *Client) Browse(req *ua.BrowseRequest) (*ua.BrowseResponse, error) {
+	var res *ua.BrowseResponse
 	err := c.sechan.Send(req, func(v interface{}) error {
-		r, ok := v.(*uas.BrowseResponse)
+		r, ok := v.(*ua.BrowseResponse)
 		if !ok {
 			return fmt.Errorf("invalid response: %T", v)
 		}
@@ -102,21 +101,21 @@ func (c *Client) Browse(req *uas.BrowseRequest) (*uas.BrowseResponse, error) {
 // todo(fs): this is not done yet since we need to be able to register
 // todo(fs): monitored items.
 type Subscription struct {
-	res *uas.CreateSubscriptionResponse
+	res *ua.CreateSubscriptionResponse
 }
 
 // todo(fs): return subscription object with channel
 func (c *Client) Subscribe(intv time.Duration) (*Subscription, error) {
-	req := &uas.CreateSubscriptionRequest{
+	req := &ua.CreateSubscriptionRequest{
 		RequestedPublishingInterval: float64(intv / time.Millisecond),
 		RequestedLifetimeCount:      60,
 		RequestedMaxKeepAliveCount:  20,
 		PublishingEnabled:           true,
 	}
 
-	var res *uas.CreateSubscriptionResponse
+	var res *ua.CreateSubscriptionResponse
 	err := c.sechan.Send(req, func(v interface{}) error {
-		r, ok := v.(*uas.CreateSubscriptionResponse)
+		r, ok := v.(*ua.CreateSubscriptionResponse)
 		if !ok {
 			return fmt.Errorf("invalid response: %T", v)
 		}
diff --git a/datatypes/doc.go b/datatypes/doc.go
deleted file mode 100644
index e05d83a..0000000
--- a/datatypes/doc.go
+++ /dev/null
@@ -1,8 +0,0 @@
-// Copyright 2018-2019 opcua authors. All rights reserved.
-// Use of this source code is governed by a MIT-style license that can be
-// found in the LICENSE file.
-
-/*
-Package datatypes defines the structures, decoders and encoder for built-in data types described in Part 6 Section 5 Data encoding.
-*/
-package datatypes
diff --git a/examples/browse/browse.go b/examples/browse/browse.go
index 19bff97..ec625ff 100644
--- a/examples/browse/browse.go
+++ b/examples/browse/browse.go
@@ -10,8 +10,8 @@ import (
 	"log"
 
 	"github.com/gopcua/opcua"
-	uad "github.com/gopcua/opcua/datatypes"
 	uid "github.com/gopcua/opcua/id"
+	"github.com/gopcua/opcua/ua"
 )
 
 func join(a, b string) string {
@@ -35,7 +35,7 @@ func browse(n *opcua.Node, path string, level int) ([]string, error) {
 	}
 	path = join(path, browseName.Name)
 
-	typeDefs := uad.NewTwoByteNodeID(uid.HasTypeDefinition)
+	typeDefs := ua.NewTwoByteNodeID(uid.HasTypeDefinition)
 	refs, err := n.References(typeDefs)
 	if err != nil {
 		return nil, err
@@ -55,7 +55,7 @@ func main() {
 	}
 	defer c.Close()
 
-	root := c.Node(uad.NewStringNodeID(1, "Root"))
+	root := c.Node(ua.NewStringNodeID(1, "Root"))
 
 	nodeList, err := browse(root, "", 0)
 	if err != nil {
diff --git a/examples/datetime/datetime.go b/examples/datetime/datetime.go
index 2df3a31..cb6759d 100644
--- a/examples/datetime/datetime.go
+++ b/examples/datetime/datetime.go
@@ -9,7 +9,7 @@ import (
 	"log"
 
 	"github.com/gopcua/opcua"
-	uad "github.com/gopcua/opcua/datatypes"
+	"github.com/gopcua/opcua/ua"
 )
 
 func main() {
@@ -22,7 +22,7 @@ func main() {
 	}
 	defer c.Close()
 
-	v, err := c.Node(uad.NewNumericNodeID(0, 2258)).Value()
+	v, err := c.Node(ua.NewNumericNodeID(0, 2258)).Value()
 	if err != nil {
 		log.Fatal(err)
 	}
diff --git a/node.go b/node.go
index 329eb7f..75fa9b6 100644
--- a/node.go
+++ b/node.go
@@ -3,8 +3,7 @@ package opcua
 import (
 	"time"
 
-	uad "github.com/gopcua/opcua/datatypes"
-	uas "github.com/gopcua/opcua/services"
+	"github.com/gopcua/opcua/ua"
 )
 
 // Node is a high-level object to interact with a node in the
@@ -12,47 +11,47 @@ import (
 // access and manipulate the common attributes of a node.
 type Node struct {
 	// ID is the node id of the node.
-	ID *uad.NodeID
+	ID *ua.NodeID
 
 	c *Client
 }
 
 // NodeClass returns the node class attribute.
-func (a *Node) NodeClass() (uas.NodeClass, error) {
-	v, err := a.Attribute(uad.IntegerIDNodeClass)
+func (a *Node) NodeClass() (ua.NodeClass, error) {
+	v, err := a.Attribute(ua.IntegerIDNodeClass)
 	if err != nil {
 		return 0, err
 	}
-	return uas.NodeClass(v.Int()), nil
+	return ua.NodeClass(v.Int()), nil
 }
 
 // BrowseName returns the browse name of the node.
-func (a *Node) BrowseName() (*uad.QualifiedName, error) {
-	v, err := a.Attribute(uad.IntegerIDBrowseName)
+func (a *Node) BrowseName() (*ua.QualifiedName, error) {
+	v, err := a.Attribute(ua.IntegerIDBrowseName)
 	if err != nil {
 		return nil, err
 	}
-	return v.Value.(*uad.QualifiedName), nil
+	return v.Value.(*ua.QualifiedName), nil
 }
 
 // DisplayName returns the display name of the node.
-func (a *Node) DisplayName() (*uad.LocalizedText, error) {
-	v, err := a.Attribute(uad.IntegerIDDisplayName)
+func (a *Node) DisplayName() (*ua.LocalizedText, error) {
+	v, err := a.Attribute(ua.IntegerIDDisplayName)
 	if err != nil {
 		return nil, err
 	}
-	return v.Value.(*uad.LocalizedText), nil
+	return v.Value.(*ua.LocalizedText), nil
 }
 
 // Value returns the value of the node.
-func (a *Node) Value() (*uad.Variant, error) {
-	return a.Attribute(uad.IntegerIDValue)
+func (a *Node) Value() (*ua.Variant, error) {
+	return a.Attribute(ua.IntegerIDValue)
 }
 
 // Attribute returns the attribute of the node. with the given id.
-func (a *Node) Attribute(attrID uint32) (*uad.Variant, error) {
-	rv := &uad.ReadValueID{NodeID: a.ID, AttributeID: attrID, DataEncoding: &uad.QualifiedName{}}
-	req := &uas.ReadRequest{NodesToRead: []*uad.ReadValueID{rv}}
+func (a *Node) Attribute(attrID uint32) (*ua.Variant, error) {
+	rv := &ua.ReadValueID{NodeID: a.ID, AttributeID: attrID, DataEncoding: &ua.QualifiedName{}}
+	req := &ua.ReadRequest{NodesToRead: []*ua.ReadValueID{rv}}
 	res, err := a.c.Read(req)
 	if err != nil {
 		return nil, err
@@ -66,23 +65,23 @@ func (a *Node) Attribute(attrID uint32) (*uad.Variant, error) {
 // References retrns all references for the node.
 // todo(fs): this is not complete since it only returns the
 // todo(fs): top-level reference at this point.
-func (a *Node) References(refs *uad.NodeID) (*uas.BrowseResponse, error) {
-	desc := &uas.BrowseDescription{
+func (a *Node) References(refs *ua.NodeID) (*ua.BrowseResponse, error) {
+	desc := &ua.BrowseDescription{
 		NodeID:          a.ID,
-		Direction:       uas.BrowseDirectionBoth,
+		Direction:       ua.BrowseDirectionBoth,
 		ReferenceTypeID: refs,
 		IncludeSubtypes: true,
-		NodeClassMask:   uas.NodeClassAll,
-		ResultMask:      uas.BrowseResultMaskAll,
+		NodeClassMask:   ua.NodeClassAll,
+		ResultMask:      ua.BrowseResultMaskAll,
 	}
 
-	req := &uas.BrowseRequest{
-		View: &uas.ViewDescription{
-			ViewID:    uad.NewTwoByteNodeID(0),
+	req := &ua.BrowseRequest{
+		View: &ua.ViewDescription{
+			ViewID:    ua.NewTwoByteNodeID(0),
 			Timestamp: time.Now(),
 		},
 		RequestedMaxReferencesPerNode: 1000,
-		NodesToBrowse:                 []*uas.BrowseDescription{desc},
+		NodesToBrowse:                 []*ua.BrowseDescription{desc},
 	}
 
 	return a.c.Browse(req)
diff --git a/services/doc.go b/services/doc.go
deleted file mode 100644
index 8448fd6..0000000
--- a/services/doc.go
+++ /dev/null
@@ -1,8 +0,0 @@
-// Copyright 2018-2019 opcua authors. All rights reserved.
-// Use of this source code is governed by a MIT-style license that can be
-// found in the LICENSE file.
-
-/*
-Package services provides encoding/decoding feature for Services in OPC UA Binary Protocol.
-*/
-package services
diff --git a/services/activate-session-request.go b/ua/activate-session-request.go
similarity index 83%
rename from services/activate-session-request.go
rename to ua/activate-session-request.go
index bca96d8..ccd13c6 100644
--- a/services/activate-session-request.go
+++ b/ua/activate-session-request.go
@@ -2,11 +2,7 @@
 // Use of this source code is governed by a MIT-style license that can be
 // found in the LICENSE file.
 
-package services
-
-import (
-	"github.com/gopcua/opcua/datatypes"
-)
+package ua
 
 // ActivateSessionRequest is used by the Client to specify the identity of the user
 // associated with the Session. This Service request shall be issued by the Client
@@ -25,18 +21,18 @@ type ActivateSessionRequest struct {
 	ClientSignature            *SignatureData
 	ClientSoftwareCertificates []*SignedSoftwareCertificate
 	LocaleIDs                  []string
-	UserIdentityToken          *datatypes.ExtensionObject
+	UserIdentityToken          *ExtensionObject
 	UserTokenSignature         *SignatureData
 }
 
 // NewActivateSessionRequest creates a new ActivateSessionRequest.
-func NewActivateSessionRequest(reqHeader *RequestHeader, sig *SignatureData, locales []string, userToken datatypes.UserIdentityToken, tokenSig *SignatureData) *ActivateSessionRequest {
+func NewActivateSessionRequest(reqHeader *RequestHeader, sig *SignatureData, locales []string, userToken UserIdentityToken, tokenSig *SignatureData) *ActivateSessionRequest {
 	return &ActivateSessionRequest{
 		RequestHeader:              reqHeader,
 		ClientSignature:            sig,
 		ClientSoftwareCertificates: nil,
 		LocaleIDs:                  locales,
-		UserIdentityToken:          datatypes.NewExtensionObject(0x01, userToken),
+		UserIdentityToken:          NewExtensionObject(0x01, userToken),
 		UserTokenSignature:         tokenSig,
 	}
 }
diff --git a/services/activate-session-response.go b/ua/activate-session-response.go
similarity index 88%
rename from services/activate-session-response.go
rename to ua/activate-session-response.go
index 63a9cd4..3512856 100644
--- a/services/activate-session-response.go
+++ b/ua/activate-session-response.go
@@ -2,11 +2,7 @@
 // Use of this source code is governed by a MIT-style license that can be
 // found in the LICENSE file.
 
-package services
-
-import (
-	"github.com/gopcua/opcua/datatypes"
-)
+package ua
 
 // ActivateSessionResponse is used by the Server to answer to the ActivateSessionRequest.
 // Once used, a serverNonce cannot be used again. For that reason, the Server returns a new
@@ -26,11 +22,11 @@ type ActivateSessionResponse struct {
 	ResponseHeader  *ResponseHeader
 	ServerNonce     []byte
 	Results         []uint32
-	DiagnosticInfos []*datatypes.DiagnosticInfo
+	DiagnosticInfos []*DiagnosticInfo
 }
 
 // NewActivateSessionResponse creates a new NewActivateSessionResponse.
-func NewActivateSessionResponse(resHeader *ResponseHeader, nonce []byte, results []uint32, diags []*datatypes.DiagnosticInfo) *ActivateSessionResponse {
+func NewActivateSessionResponse(resHeader *ResponseHeader, nonce []byte, results []uint32, diags []*DiagnosticInfo) *ActivateSessionResponse {
 	return &ActivateSessionResponse{
 		ResponseHeader:  resHeader,
 		ServerNonce:     nonce,
diff --git a/services/additional-header.go b/ua/additional-header.go
similarity index 79%
rename from services/additional-header.go
rename to ua/additional-header.go
index 653b203..cc23e57 100644
--- a/services/additional-header.go
+++ b/ua/additional-header.go
@@ -2,19 +2,17 @@
 // Use of this source code is governed by a MIT-style license that can be
 // found in the LICENSE file.
 
-package services
-
-import "github.com/gopcua/opcua/datatypes"
+package ua
 
 // AdditionalHeader represents the AdditionalHeader.
 // TODO: add body handling.
 type AdditionalHeader struct {
-	TypeID       *datatypes.ExpandedNodeID
+	TypeID       *ExpandedNodeID
 	EncodingMask uint8
 }
 
 // NewAdditionalHeader creates a new AdditionalHeader.
-func NewAdditionalHeader(typeID *datatypes.ExpandedNodeID, mask uint8) *AdditionalHeader {
+func NewAdditionalHeader(typeID *ExpandedNodeID, mask uint8) *AdditionalHeader {
 	return &AdditionalHeader{
 		TypeID:       typeID,
 		EncodingMask: mask,
@@ -24,7 +22,7 @@ func NewAdditionalHeader(typeID *datatypes.ExpandedNodeID, mask uint8) *Addition
 // NewNullAdditionalHeader creates a new AdditionalHeader without meaningful values.
 func NewNullAdditionalHeader() *AdditionalHeader {
 	return &AdditionalHeader{
-		TypeID:       datatypes.NewTwoByteExpandedNodeID(0),
+		TypeID:       NewTwoByteExpandedNodeID(0),
 		EncodingMask: 0x00,
 	}
 }
diff --git a/services/application-description.go b/ua/application-description.go
similarity index 89%
rename from services/application-description.go
rename to ua/application-description.go
index c32bf8a..ec2ed1f 100644
--- a/services/application-description.go
+++ b/ua/application-description.go
@@ -2,12 +2,10 @@
 // Use of this source code is governed by a MIT-style license that can be
 // found in the LICENSE file.
 
-package services
+package ua
 
 import (
 	"fmt"
-
-	"github.com/gopcua/opcua/datatypes"
 )
 
 // ApplicationType definitions.
@@ -26,7 +24,7 @@ const (
 type ApplicationDescription struct {
 	ApplicationURI      string
 	ProductURI          string
-	ApplicationName     *datatypes.LocalizedText
+	ApplicationName     *LocalizedText
 	ApplicationType     uint32
 	GatewayServerURI    string
 	DiscoveryProfileURI string
@@ -38,7 +36,7 @@ func NewApplicationDescription(appURI, prodURI, appName string, appType uint32,
 	return &ApplicationDescription{
 		ApplicationURI:      appURI,
 		ProductURI:          prodURI,
-		ApplicationName:     datatypes.NewLocalizedText("", appName),
+		ApplicationName:     NewLocalizedText("", appName),
 		ApplicationType:     appType,
 		GatewayServerURI:    gwURI,
 		DiscoveryProfileURI: profileURI,
diff --git a/services/cancel-request.go b/ua/cancel-request.go
similarity index 97%
rename from services/cancel-request.go
rename to ua/cancel-request.go
index 6286b3d..54ad1d6 100644
--- a/services/cancel-request.go
+++ b/ua/cancel-request.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a MIT-style license that can be
 // found in the LICENSE file.
 
-package services
+package ua
 
 // CancelRequest is used to cancel outstanding Service requests. Successfully cancelled service
 // requests shall respond with Bad_RequestCancelledByClient.
diff --git a/services/cancel-response.go b/ua/cancel-response.go
similarity index 97%
rename from services/cancel-response.go
rename to ua/cancel-response.go
index eed5365..146b0a2 100644
--- a/services/cancel-response.go
+++ b/ua/cancel-response.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a MIT-style license that can be
 // found in the LICENSE file.
 
-package services
+package ua
 
 // CancelResponse is used to cancel outstanding Service requests. Successfully cancelled service
 // requests shall respond with Bad_RequestCancelledByClient.
diff --git a/services/channel-security-token.go b/ua/channel-security-token.go
similarity index 99%
rename from services/channel-security-token.go
rename to ua/channel-security-token.go
index 1098531..4e693e5 100644
--- a/services/channel-security-token.go
+++ b/ua/channel-security-token.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a MIT-style license that can be
 // found in the LICENSE file.
 
-package services
+package ua
 
 import (
 	"encoding/binary"
diff --git a/services/close-secure-channel-request.go b/ua/close-secure-channel-request.go
similarity index 97%
rename from services/close-secure-channel-request.go
rename to ua/close-secure-channel-request.go
index 47435e2..93b49fd 100644
--- a/services/close-secure-channel-request.go
+++ b/ua/close-secure-channel-request.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a MIT-style license that can be
 // found in the LICENSE file.
 
-package services
+package ua
 
 // CloseSecureChannelRequest represents an CloseSecureChannelRequest.
 // This Service is used to terminate a SecureChannel.
diff --git a/services/close-secure-channel-response.go b/ua/close-secure-channel-response.go
similarity index 97%
rename from services/close-secure-channel-response.go
rename to ua/close-secure-channel-response.go
index 593b369..9c3dc76 100644
--- a/services/close-secure-channel-response.go
+++ b/ua/close-secure-channel-response.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a MIT-style license that can be
 // found in the LICENSE file.
 
-package services
+package ua
 
 // CloseSecureChannelResponse represents an CloseSecureChannelResponse.
 // This Service is used to terminate a SecureChannel.
diff --git a/services/close-session-request.go b/ua/close-session-request.go
similarity index 97%
rename from services/close-session-request.go
rename to ua/close-session-request.go
index 2c5fb91..f675e4b 100644
--- a/services/close-session-request.go
+++ b/ua/close-session-request.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a MIT-style license that can be
 // found in the LICENSE file.
 
-package services
+package ua
 
 // CloseSessionRequest represents an CloseSessionRequest.
 // This Service is used to terminate a Session.
diff --git a/services/close-session-response.go b/ua/close-session-response.go
similarity index 97%
rename from services/close-session-response.go
rename to ua/close-session-response.go
index 3b5f262..70ecc0a 100644
--- a/services/close-session-response.go
+++ b/ua/close-session-response.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a MIT-style license that can be
 // found in the LICENSE file.
 
-package services
+package ua
 
 // CloseSessionResponse represents an CloseSessionResponse.
 // This Service is used to terminate a Session.
diff --git a/services/create-session-request.go b/ua/create-session-request.go
similarity index 98%
rename from services/create-session-request.go
rename to ua/create-session-request.go
index 7b0a1db..c1ac04a 100644
--- a/services/create-session-request.go
+++ b/ua/create-session-request.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a MIT-style license that can be
 // found in the LICENSE file.
 
-package services
+package ua
 
 // CreateSessionRequest represents a CreateSessionRequest.
 //
diff --git a/services/create-session-response.go b/ua/create-session-response.go
similarity index 78%
rename from services/create-session-response.go
rename to ua/create-session-response.go
index 985c0ec..a13f0b6 100644
--- a/services/create-session-response.go
+++ b/ua/create-session-response.go
@@ -2,19 +2,15 @@
 // Use of this source code is governed by a MIT-style license that can be
 // found in the LICENSE file.
 
-package services
-
-import (
-	"github.com/gopcua/opcua/datatypes"
-)
+package ua
 
 // CreateSessionResponse represents a CreateSessionResponse.
 //
 // Specification: Part4, 5.6.2
 type CreateSessionResponse struct {
 	*ResponseHeader
-	SessionID                  *datatypes.NodeID
-	AuthenticationToken        *datatypes.NodeID
+	SessionID                  *NodeID
+	AuthenticationToken        *NodeID
 	RevisedSessionTimeout      uint64
 	ServerNonce                []byte
 	ServerCertificate          []byte
@@ -25,7 +21,7 @@ type CreateSessionResponse struct {
 }
 
 // NewCreateSessionResponse creates a new NewCreateSessionResponse with the given parameters.
-func NewCreateSessionResponse(resHeader *ResponseHeader, sessionID, authToken *datatypes.NodeID, timeout uint64, nonce, cert []byte, svrSignature *SignatureData, maxRespSize uint32, endpoints ...*EndpointDescription) *CreateSessionResponse {
+func NewCreateSessionResponse(resHeader *ResponseHeader, sessionID, authToken *NodeID, timeout uint64, nonce, cert []byte, svrSignature *SignatureData, maxRespSize uint32, endpoints ...*EndpointDescription) *CreateSessionResponse {
 	return &CreateSessionResponse{
 		ResponseHeader:             resHeader,
 		SessionID:                  sessionID,
diff --git a/services/create-subscription-request.go b/ua/create-subscription-request.go
similarity index 98%
rename from services/create-subscription-request.go
rename to ua/create-subscription-request.go
index 47e10d6..635211c 100644
--- a/services/create-subscription-request.go
+++ b/ua/create-subscription-request.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a MIT-style license that can be
 // found in the LICENSE file.
 
-package services
+package ua
 
 // CreateSubscriptionRequest is used to create a Subscription. Subscriptions monitor a set of MonitoredItems for
 // Notifications and return them to the Client in response to Publish requests.
diff --git a/services/create-subscription-response.go b/ua/create-subscription-response.go
similarity index 92%
rename from services/create-subscription-response.go
rename to ua/create-subscription-response.go
index d59830b..e3b3947 100644
--- a/services/create-subscription-response.go
+++ b/ua/create-subscription-response.go
@@ -1,4 +1,4 @@
-package services
+package ua
 
 type CreateSubscriptionResponse struct {
 	ResponseHeader            *ResponseHeader
diff --git a/datatypes/data-value.go b/ua/data-value.go
similarity index 97%
rename from datatypes/data-value.go
rename to ua/data-value.go
index 77e4c7d..69ec283 100644
--- a/datatypes/data-value.go
+++ b/ua/data-value.go
@@ -2,12 +2,10 @@
 // Use of this source code is governed by a MIT-style license that can be
 // found in the LICENSE file.
 
-package datatypes
+package ua
 
 import (
 	"time"
-
-	"github.com/gopcua/opcua/ua"
 )
 
 // DataValue is always preceded by a mask that indicates which fields are present in the stream.
@@ -56,7 +54,7 @@ func NewDataValue(hasValue, hasStatus, hasSrcTs, hasSrcPs, hasSvrTs, hasSvrPs bo
 }
 
 func (d *DataValue) Decode(b []byte) (int, error) {
-	buf := ua.NewBuffer(b)
+	buf := NewBuffer(b)
 	d.EncodingMask = buf.ReadByte()
 	if d.HasValue() {
 		d.Value = new(Variant)
@@ -81,7 +79,7 @@ func (d *DataValue) Decode(b []byte) (int, error) {
 }
 
 func (d *DataValue) Encode() ([]byte, error) {
-	buf := ua.NewBuffer(nil)
+	buf := NewBuffer(nil)
 	buf.WriteUint8(d.EncodingMask)
 	if d.HasValue() {
 		buf.WriteStruct(d.Value)
diff --git a/datatypes/data.go b/ua/data.go
similarity index 94%
rename from datatypes/data.go
rename to ua/data.go
index bf0dd94..79a52aa 100644
--- a/datatypes/data.go
+++ b/ua/data.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a MIT-style license that can be
 // found in the LICENSE file.
 
-package datatypes
+package ua
 
 // Data is an interface to handle any kind of OPC UA data types.
 type Data interface {
diff --git a/datatypes/datatypes.go b/ua/datatypes.go
similarity index 97%
rename from datatypes/datatypes.go
rename to ua/datatypes.go
index f0f17c6..902644f 100644
--- a/datatypes/datatypes.go
+++ b/ua/datatypes.go
@@ -1,4 +1,4 @@
-package datatypes
+package ua
 
 const (
 	TypeBoolean         = 1
diff --git a/datatypes/diagnostic-info.go b/ua/diagnostic-info.go
similarity index 98%
rename from datatypes/diagnostic-info.go
rename to ua/diagnostic-info.go
index b653bc7..18b8b78 100644
--- a/datatypes/diagnostic-info.go
+++ b/ua/diagnostic-info.go
@@ -2,12 +2,10 @@
 // Use of this source code is governed by a MIT-style license that can be
 // found in the LICENSE file.
 
-package datatypes
+package ua
 
 import (
 	"fmt"
-
-	"github.com/gopcua/opcua/ua"
 )
 
 // DiagnosticInfo represents the DiagnosticInfo.
@@ -67,7 +65,7 @@ func NewNullDiagnosticInfo() *DiagnosticInfo {
 }
 
 func (d *DiagnosticInfo) Decode(b []byte) (int, error) {
-	buf := ua.NewBuffer(b)
+	buf := NewBuffer(b)
 	d.EncodingMask = buf.ReadByte()
 	if d.HasSymbolicID() {
 		d.SymbolicID = buf.ReadInt32()
@@ -95,7 +93,7 @@ func (d *DiagnosticInfo) Decode(b []byte) (int, error) {
 }
 
 func (d *DiagnosticInfo) Encode() ([]byte, error) {
-	buf := ua.NewBuffer(nil)
+	buf := NewBuffer(nil)
 	buf.WriteByte(d.EncodingMask)
 	if d.HasSymbolicID() {
 		buf.WriteInt32(d.SymbolicID)
diff --git a/ua/doc.go b/ua/doc.go
new file mode 100644
index 0000000..7ee9da7
--- /dev/null
+++ b/ua/doc.go
@@ -0,0 +1,8 @@
+// Copyright 2018-2019 opcua authors. All rights reserved.
+// Use of this source code is governed by a MIT-style license that can be
+// found in the LICENSE file.
+
+// Package ua defines the structures, decoders and encoder
+// for built-in data types described in Part 6 Section 5 Data encoding
+// and for services in OPC UA Binary Protocol.
+package ua
diff --git a/services/endpoint-description.go b/ua/endpoint-description.go
similarity index 98%
rename from services/endpoint-description.go
rename to ua/endpoint-description.go
index e03a93c..7a080e7 100644
--- a/services/endpoint-description.go
+++ b/ua/endpoint-description.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a MIT-style license that can be
 // found in the LICENSE file.
 
-package services
+package ua
 
 import (
 	"fmt"
diff --git a/services/enums.go b/ua/enums.go
similarity index 98%
rename from services/enums.go
rename to ua/enums.go
index eb17e30..a650f82 100644
--- a/services/enums.go
+++ b/ua/enums.go
@@ -1,4 +1,4 @@
-package services
+package ua
 
 type BrowseDirection uint32
 
diff --git a/datatypes/expanded-node-id.go b/ua/expanded-node-id.go
similarity index 95%
rename from datatypes/expanded-node-id.go
rename to ua/expanded-node-id.go
index c820994..07e5def 100644
--- a/datatypes/expanded-node-id.go
+++ b/ua/expanded-node-id.go
@@ -2,9 +2,7 @@
 // Use of this source code is governed by a MIT-style license that can be
 // found in the LICENSE file.
 
-package datatypes
-
-import "github.com/gopcua/opcua/ua"
+package ua
 
 // ExpandedNodeID extends the NodeID structure by allowing the NamespaceURI to be
 // explicitly specified instead of using the NamespaceIndex. The NamespaceURI is optional.
@@ -50,7 +48,7 @@ func NewFourByteExpandedNodeID(ns uint8, id uint16) *ExpandedNodeID {
 }
 
 func (e *ExpandedNodeID) Decode(b []byte) (int, error) {
-	buf := ua.NewBuffer(b)
+	buf := NewBuffer(b)
 	e.NodeID = new(NodeID)
 	buf.ReadStruct(e.NodeID)
 	if e.HasNamespaceURI() {
@@ -63,7 +61,7 @@ func (e *ExpandedNodeID) Decode(b []byte) (int, error) {
 }
 
 func (e *ExpandedNodeID) Encode() ([]byte, error) {
-	buf := ua.NewBuffer(nil)
+	buf := NewBuffer(nil)
 	buf.WriteStruct(e.NodeID)
 	if e.HasNamespaceURI() {
 		buf.WriteString(e.NamespaceURI)
diff --git a/datatypes/extension-object.go b/ua/extension-object.go
similarity index 94%
rename from datatypes/extension-object.go
rename to ua/extension-object.go
index e70d115..34ac149 100644
--- a/datatypes/extension-object.go
+++ b/ua/extension-object.go
@@ -2,13 +2,12 @@
 // Use of this source code is governed by a MIT-style license that can be
 // found in the LICENSE file.
 
-package datatypes
+package ua
 
 import (
 	"fmt"
 
 	"github.com/gopcua/opcua/id"
-	"github.com/gopcua/opcua/ua"
 )
 
 // ExtensionObject is encoded as sequence of bytes prefixed by the NodeId of its DataTypeEncoding
@@ -33,7 +32,7 @@ func NewExtensionObject(mask uint8, extParam ExtensionObjectValue) *ExtensionObj
 }
 
 func (e *ExtensionObject) Decode(b []byte) (int, error) {
-	buf := ua.NewBuffer(b)
+	buf := NewBuffer(b)
 	e.TypeID = new(ExpandedNodeID)
 	buf.ReadStruct(e.TypeID)
 	e.EncodingMask = buf.ReadByte()
@@ -51,7 +50,7 @@ func (e *ExtensionObject) Decode(b []byte) (int, error) {
 }
 
 func (e *ExtensionObject) Encode() ([]byte, error) {
-	buf := ua.NewBuffer(nil)
+	buf := NewBuffer(nil)
 	buf.WriteStruct(e.TypeID)
 	buf.WriteByte(e.EncodingMask)
 	if e.Value == nil {
@@ -68,8 +67,8 @@ func (e *ExtensionObject) Encode() ([]byte, error) {
 
 // ExtensionObjectValue represents the value in ExtensionObject.
 type ExtensionObjectValue interface {
-	ua.BinaryDecoder
-	ua.BinaryEncoder
+	BinaryDecoder
+	BinaryEncoder
 	Type() int
 }
 
diff --git a/services/find-servers-on-network-request.go b/ua/find-servers-on-network-request.go
similarity index 99%
rename from services/find-servers-on-network-request.go
rename to ua/find-servers-on-network-request.go
index 47eedad..b2701d7 100644
--- a/services/find-servers-on-network-request.go
+++ b/ua/find-servers-on-network-request.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a MIT-style license that can be
 // found in the LICENSE file.
 
-package services
+package ua
 
 // FindServersOnNetworkRequest returns the Servers known to a Discovery Server. Unlike FindServers, this Service is
 // only implemented by Discovery Servers.
diff --git a/services/find-servers-on-network-response.go b/ua/find-servers-on-network-response.go
similarity index 98%
rename from services/find-servers-on-network-response.go
rename to ua/find-servers-on-network-response.go
index 71262c7..19d70f8 100644
--- a/services/find-servers-on-network-response.go
+++ b/ua/find-servers-on-network-response.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a MIT-style license that can be
 // found in the LICENSE file.
 
-package services
+package ua
 
 import (
 	"time"
diff --git a/services/find-servers-request.go b/ua/find-servers-request.go
similarity index 98%
rename from services/find-servers-request.go
rename to ua/find-servers-request.go
index af52cdc..ec62410 100644
--- a/services/find-servers-request.go
+++ b/ua/find-servers-request.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a MIT-style license that can be
 // found in the LICENSE file.
 
-package services
+package ua
 
 // FindServersRequest returns the Servers known to a Server or Discovery Server. The behaviour of
 // Discovery Servers is described in detail in Part 12.
diff --git a/services/find-servers-response.go b/ua/find-servers-response.go
similarity index 97%
rename from services/find-servers-response.go
rename to ua/find-servers-response.go
index 827a9d5..71a01a2 100644
--- a/services/find-servers-response.go
+++ b/ua/find-servers-response.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a MIT-style license that can be
 // found in the LICENSE file.
 
-package services
+package ua
 
 // FindServersResponse returns the Servers known to a Server or Discovery Server. The behaviour of
 // Discovery Servers is described in detail in Part 12.
diff --git a/services/get-endpoints-request.go b/ua/get-endpoints-request.go
similarity index 98%
rename from services/get-endpoints-request.go
rename to ua/get-endpoints-request.go
index 958c8fd..2a6a92e 100644
--- a/services/get-endpoints-request.go
+++ b/ua/get-endpoints-request.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a MIT-style license that can be
 // found in the LICENSE file.
 
-package services
+package ua
 
 // GetEndpointsRequest represents an GetEndpointsRequest.
 // This Service returns the Endpoints supported by a Server and all of the configuration information
diff --git a/services/get-endpoints-response.go b/ua/get-endpoints-response.go
similarity index 97%
rename from services/get-endpoints-response.go
rename to ua/get-endpoints-response.go
index 0e9008e..09bb462 100644
--- a/services/get-endpoints-response.go
+++ b/ua/get-endpoints-response.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a MIT-style license that can be
 // found in the LICENSE file.
 
-package services
+package ua
 
 // GetEndpointsResponse represents an GetEndpointsResponse.
 type GetEndpointsResponse struct {
diff --git a/datatypes/guid.go b/ua/guid.go
similarity index 94%
rename from datatypes/guid.go
rename to ua/guid.go
index 04da2fb..a58a82f 100644
--- a/datatypes/guid.go
+++ b/ua/guid.go
@@ -2,15 +2,13 @@
 // Use of this source code is governed by a MIT-style license that can be
 // found in the LICENSE file.
 
-package datatypes
+package ua
 
 import (
 	"encoding/binary"
 	"encoding/hex"
 	"fmt"
 	"strings"
-
-	"github.com/gopcua/opcua/ua"
 )
 
 // GUID represents GUID in binary stream. It is a 16-byte globally unique identifier.
@@ -45,7 +43,7 @@ func NewGUID(guid string) *GUID {
 }
 
 func (g *GUID) Decode(b []byte) (int, error) {
-	buf := ua.NewBuffer(b)
+	buf := NewBuffer(b)
 	g.Data1 = buf.ReadUint32()
 	g.Data2 = buf.ReadUint16()
 	g.Data3 = buf.ReadUint16()
@@ -54,7 +52,7 @@ func (g *GUID) Decode(b []byte) (int, error) {
 }
 
 func (g *GUID) Encode() ([]byte, error) {
-	buf := ua.NewBuffer(nil)
+	buf := NewBuffer(nil)
 	buf.WriteUint32(g.Data1)
 	buf.WriteUint16(g.Data2)
 	buf.WriteUint16(g.Data3)
diff --git a/datatypes/localized-text.go b/ua/localized-text.go
similarity index 95%
rename from datatypes/localized-text.go
rename to ua/localized-text.go
index acc61aa..3d1621d 100644
--- a/datatypes/localized-text.go
+++ b/ua/localized-text.go
@@ -2,13 +2,12 @@
 // Use of this source code is governed by a MIT-style license that can be
 // found in the LICENSE file.
 
-package datatypes
+package ua
 
 import (
 	"fmt"
 
 	"github.com/gopcua/opcua/id"
-	"github.com/gopcua/opcua/ua"
 )
 
 // LocalizedText represents a LocalizedText.
@@ -39,7 +38,7 @@ func NewLocalizedText(locale, text string) *LocalizedText {
 }
 
 func (m *LocalizedText) Decode(b []byte) (int, error) {
-	buf := ua.NewBuffer(b)
+	buf := NewBuffer(b)
 	m.EncodingMask = buf.ReadByte()
 	if m.HasLocale() {
 		m.Locale = buf.ReadString()
@@ -51,7 +50,7 @@ func (m *LocalizedText) Decode(b []byte) (int, error) {
 }
 
 func (m *LocalizedText) Encode() ([]byte, error) {
-	buf := ua.NewBuffer(nil)
+	buf := NewBuffer(nil)
 	buf.WriteUint8(m.EncodingMask)
 	if m.HasLocale() {
 		buf.WriteString(m.Locale)
diff --git a/datatypes/node-id.go b/ua/node-id.go
similarity index 98%
rename from datatypes/node-id.go
rename to ua/node-id.go
index 0543383..9300392 100644
--- a/datatypes/node-id.go
+++ b/ua/node-id.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a MIT-style license that can be
 // found in the LICENSE file.
 
-package datatypes
+package ua
 
 import (
 	"encoding/base64"
@@ -10,8 +10,6 @@ import (
 	"math"
 	"strconv"
 	"strings"
-
-	"github.com/gopcua/opcua/ua"
 )
 
 // NodeID type definitions.
@@ -358,7 +356,7 @@ func (n *NodeID) String() string {
 }
 
 func (n *NodeID) Decode(b []byte) (int, error) {
-	buf := ua.NewBuffer(b)
+	buf := NewBuffer(b)
 
 	n.mask = buf.ReadByte()
 	typ := n.mask & 0xf
@@ -395,7 +393,7 @@ func (n *NodeID) Decode(b []byte) (int, error) {
 }
 
 func (n *NodeID) Encode() ([]byte, error) {
-	buf := ua.NewBuffer(nil)
+	buf := NewBuffer(nil)
 	buf.WriteByte(n.mask)
 
 	switch n.Type() {
diff --git a/services/open-secure-channel-request.go b/ua/open-secure-channel-request.go
similarity index 98%
rename from services/open-secure-channel-request.go
rename to ua/open-secure-channel-request.go
index 8a974a0..d66f253 100644
--- a/services/open-secure-channel-request.go
+++ b/ua/open-secure-channel-request.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a MIT-style license that can be
 // found in the LICENSE file.
 
-package services
+package ua
 
 // SecurityTokenRequestType definitions.
 //
diff --git a/services/open-secure-channel-response.go b/ua/open-secure-channel-response.go
similarity index 97%
rename from services/open-secure-channel-response.go
rename to ua/open-secure-channel-response.go
index f9307c9..7344d53 100644
--- a/services/open-secure-channel-response.go
+++ b/ua/open-secure-channel-response.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a MIT-style license that can be
 // found in the LICENSE file.
 
-package services
+package ua
 
 // OpenSecureChannelResponse represents an OpenSecureChannelResponse.
 type OpenSecureChannelResponse struct {
diff --git a/datatypes/qualified-name.go b/ua/qualified-name.go
similarity index 97%
rename from datatypes/qualified-name.go
rename to ua/qualified-name.go
index b40de82..24aa64c 100644
--- a/datatypes/qualified-name.go
+++ b/ua/qualified-name.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a MIT-style license that can be
 // found in the LICENSE file.
 
-package datatypes
+package ua
 
 // QualifiedName contains a qualified name. It is, for example, used as BrowseName.
 // The name part of the QualifiedName is restricted to 512 characters.
diff --git a/services/read-request.go b/ua/read-request.go
similarity index 95%
rename from services/read-request.go
rename to ua/read-request.go
index 1eb19f3..dd714ab 100644
--- a/services/read-request.go
+++ b/ua/read-request.go
@@ -2,11 +2,7 @@
 // Use of this source code is governed by a MIT-style license that can be
 // found in the LICENSE file.
 
-package services
-
-import (
-	"github.com/gopcua/opcua/datatypes"
-)
+package ua
 
 // TimestampsToReturn is an enumeration that specifies the Timestamp Attributes to be
 // transmitted for MonitoredItems or Nodes in Read and HistoryRead.
@@ -74,11 +70,11 @@ type ReadRequest struct {
 
 	// List of Nodes and their Attributes to read. For each entry in this list,
 	// a StatusCode is returned, and if it indicates success, the Attribute Value is also returned.
-	NodesToRead []*datatypes.ReadValueID
+	NodesToRead []*ReadValueID
 }
 
 // NewReadRequest creates a new ReadRequest.
-func NewReadRequest(reqHeader *RequestHeader, maxAge uint64, tsRet TimestampsToReturn, nodes ...*datatypes.ReadValueID) *ReadRequest {
+func NewReadRequest(reqHeader *RequestHeader, maxAge uint64, tsRet TimestampsToReturn, nodes ...*ReadValueID) *ReadRequest {
 	return &ReadRequest{
 		RequestHeader:      reqHeader,
 		MaxAge:             maxAge,
diff --git a/services/read-response.go b/ua/read-response.go
similarity index 63%
rename from services/read-response.go
rename to ua/read-response.go
index f3cd170..5522099 100644
--- a/services/read-response.go
+++ b/ua/read-response.go
@@ -2,23 +2,19 @@
 // Use of this source code is governed by a MIT-style license that can be
 // found in the LICENSE file.
 
-package services
-
-import (
-	"github.com/gopcua/opcua/datatypes"
-)
+package ua
 
 // ReadResponse represents the response to a ReadRequest.
 //
 // Specification Part 4, 5.10.2.2
 type ReadResponse struct {
 	ResponseHeader  *ResponseHeader
-	Results         []*datatypes.DataValue
-	DiagnosticInfos []*datatypes.DiagnosticInfo
+	Results         []*DataValue
+	DiagnosticInfos []*DiagnosticInfo
 }
 
 // NewReadResponse creates a new ReadResponse.
-func NewReadResponse(resHeader *ResponseHeader, diag []*datatypes.DiagnosticInfo, results ...*datatypes.DataValue) *ReadResponse {
+func NewReadResponse(resHeader *ResponseHeader, diag []*DiagnosticInfo, results ...*DataValue) *ReadResponse {
 	return &ReadResponse{
 		ResponseHeader:  resHeader,
 		Results:         results,
diff --git a/datatypes/read-value-id.go b/ua/read-value-id.go
similarity index 95%
rename from datatypes/read-value-id.go
rename to ua/read-value-id.go
index dd24b1f..b4be33c 100644
--- a/datatypes/read-value-id.go
+++ b/ua/read-value-id.go
@@ -2,9 +2,7 @@
 // Use of this source code is governed by a MIT-style license that can be
 // found in the LICENSE file.
 
-package datatypes
-
-import "github.com/gopcua/opcua/ua"
+package ua
 
 // IntegerID is a UInt32 that is used as an identifier, such as a handle.
 // All values, except for 0, are valid.
@@ -67,7 +65,7 @@ func NewReadValueID(nodeID *NodeID, attrID uint32, idxRange string, qIdx uint16,
 }
 
 func (v *ReadValueID) Decode(b []byte) (int, error) {
-	buf := ua.NewBuffer(b)
+	buf := NewBuffer(b)
 	v.NodeID = new(NodeID)
 	buf.ReadStruct(v.NodeID)
 	v.AttributeID = buf.ReadUint32()
@@ -78,7 +76,7 @@ func (v *ReadValueID) Decode(b []byte) (int, error) {
 }
 
 func (v *ReadValueID) Encode() ([]byte, error) {
-	buf := ua.NewBuffer(nil)
+	buf := NewBuffer(nil)
 	buf.WriteStruct(v.NodeID)
 	buf.WriteUint32(v.AttributeID)
 	buf.WriteString(v.IndexRange)
diff --git a/services/request-header.go b/ua/request-header.go
similarity index 96%
rename from services/request-header.go
rename to ua/request-header.go
index 41970f4..44de1e9 100644
--- a/services/request-header.go
+++ b/ua/request-header.go
@@ -2,20 +2,18 @@
 // Use of this source code is governed by a MIT-style license that can be
 // found in the LICENSE file.
 
-package services
+package ua
 
 import (
 	"fmt"
 	"time"
-
-	"github.com/gopcua/opcua/datatypes"
 )
 
 // RequestHeader represents a Request Header in each services.
 //
 // Specification: Part 4, 7.28
 type RequestHeader struct {
-	AuthenticationToken *datatypes.NodeID
+	AuthenticationToken *NodeID
 	Timestamp           time.Time
 	RequestHandle       uint32
 	ReturnDiagnostics   uint32
@@ -25,7 +23,7 @@ type RequestHeader struct {
 }
 
 // NewRequestHeader creates a new RequestHeader.
-func NewRequestHeader(authToken *datatypes.NodeID, timestamp time.Time, handle, diag, timeout uint32, auditID string, additionalHeader *AdditionalHeader) *RequestHeader {
+func NewRequestHeader(authToken *NodeID, timestamp time.Time, handle, diag, timeout uint32, auditID string, additionalHeader *AdditionalHeader) *RequestHeader {
 	return &RequestHeader{
 		AuthenticationToken: authToken,
 		Timestamp:           timestamp,
diff --git a/services/response-header.go b/ua/response-header.go
similarity index 80%
rename from services/response-header.go
rename to ua/response-header.go
index bef223d..dd97961 100644
--- a/services/response-header.go
+++ b/ua/response-header.go
@@ -2,13 +2,11 @@
 // Use of this source code is governed by a MIT-style license that can be
 // found in the LICENSE file.
 
-package services
+package ua
 
 import (
 	"fmt"
 	"time"
-
-	"github.com/gopcua/opcua/datatypes"
 )
 
 // ResponseHeader represents a Response Header in each services.
@@ -18,13 +16,13 @@ type ResponseHeader struct {
 	Timestamp          time.Time
 	RequestHandle      uint32
 	ServiceResult      uint32
-	ServiceDiagnostics *datatypes.DiagnosticInfo
+	ServiceDiagnostics *DiagnosticInfo
 	StringTable        []string
 	AdditionalHeader   *AdditionalHeader
 }
 
 // NewResponseHeader creates a new ResponseHeader.
-func NewResponseHeader(timestamp time.Time, handle, code uint32, diag *datatypes.DiagnosticInfo, strs []string, additionalHeader *AdditionalHeader) *ResponseHeader {
+func NewResponseHeader(timestamp time.Time, handle, code uint32, diag *DiagnosticInfo, strs []string, additionalHeader *AdditionalHeader) *ResponseHeader {
 	r := &ResponseHeader{
 		Timestamp:          timestamp,
 		RequestHandle:      handle,
@@ -34,7 +32,7 @@ func NewResponseHeader(timestamp time.Time, handle, code uint32, diag *datatypes
 		AdditionalHeader:   additionalHeader,
 	}
 	if diag == nil {
-		r.ServiceDiagnostics = datatypes.NewNullDiagnosticInfo()
+		r.ServiceDiagnostics = NewNullDiagnosticInfo()
 	}
 
 	return r
diff --git a/services/servers-on-network.go b/ua/servers-on-network.go
similarity index 97%
rename from services/servers-on-network.go
rename to ua/servers-on-network.go
index 112f6c8..908d47a 100644
--- a/services/servers-on-network.go
+++ b/ua/servers-on-network.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a MIT-style license that can be
 // found in the LICENSE file.
 
-package services
+package ua
 
 // ServersOnNetwork is a DNS service record that meet criteria specified in the request.
 // This list is empty if no Servers meet the criteria.
diff --git a/services/service.go b/ua/service.go
similarity index 93%
rename from services/service.go
rename to ua/service.go
index b18c2d1..e244bd5 100644
--- a/services/service.go
+++ b/ua/service.go
@@ -2,16 +2,14 @@
 // Use of this source code is governed by a MIT-style license that can be
 // found in the LICENSE file.
 
-package services
+package ua
 
 import (
 	"fmt"
 	"reflect"
 
-	"github.com/gopcua/opcua/datatypes"
 	"github.com/gopcua/opcua/errors"
 	"github.com/gopcua/opcua/id"
-	"github.com/gopcua/opcua/ua"
 )
 
 func init() {
@@ -66,8 +64,8 @@ func TypeID(v interface{}) uint16 {
 	return serviceTypeID[reflect.TypeOf(v)]
 }
 
-func Decode(b []byte) (*datatypes.ExpandedNodeID, interface{}, error) {
-	typeID := new(datatypes.ExpandedNodeID)
+func DecodeService(b []byte) (*ExpandedNodeID, interface{}, error) {
+	typeID := new(ExpandedNodeID)
 	n, err := typeID.Decode(b)
 	if err != nil {
 		return nil, nil, err
@@ -81,6 +79,6 @@ func Decode(b []byte) (*datatypes.ExpandedNodeID, interface{}, error) {
 	}
 
 	v := reflect.New(typ.Elem()).Interface()
-	_, err = ua.Decode(b, v)
+	_, err = Decode(b, v)
 	return typeID, v, err
 }
diff --git a/services/signature-data.go b/ua/signature-data.go
similarity index 93%
rename from services/signature-data.go
rename to ua/signature-data.go
index a4208f4..7b3b893 100644
--- a/services/signature-data.go
+++ b/ua/signature-data.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a MIT-style license that can be
 // found in the LICENSE file.
 
-package services
+package ua
 
 import (
 	"fmt"
@@ -35,7 +35,7 @@ func NewSignatureDataFrom(cert, nonce []byte) *SignatureData {
 	return NewSignatureData("", nil)
 }
 
-// datatypes.String returns SignatureData in string.
+//String returns SignatureData in string.
 func (s *SignatureData) String() string {
 	return fmt.Sprintf("%s, %x", s.Algorithm, s.Signature)
 }
diff --git a/services/signed-software-certificate.go b/ua/signed-software-certificate.go
similarity index 89%
rename from services/signed-software-certificate.go
rename to ua/signed-software-certificate.go
index 3a671f5..b903ba0 100644
--- a/services/signed-software-certificate.go
+++ b/ua/signed-software-certificate.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a MIT-style license that can be
 // found in the LICENSE file.
 
-package services
+package ua
 
 import (
 	"fmt"
@@ -24,7 +24,7 @@ func NewSignedSoftwareCertificate(cert, signature []byte) *SignedSoftwareCertifi
 	}
 }
 
-// datatypes.ByteString returns SignedSoftwareCertificate in string.
+//ByteString returns SignedSoftwareCertificate in string.
 func (s *SignedSoftwareCertificate) String() string {
 	return fmt.Sprintf("%x, %x", s.CertificateData, s.Signature)
 }
diff --git a/datatypes/status-code.go b/ua/status-code.go
similarity index 57%
rename from datatypes/status-code.go
rename to ua/status-code.go
index c2e8bc5..b329618 100644
--- a/datatypes/status-code.go
+++ b/ua/status-code.go
@@ -1,3 +1,3 @@
-package datatypes
+package ua
 
 type StatusCode uint32
diff --git a/datatypes/user-identity-token.go b/ua/user-identity-token.go
similarity index 96%
rename from datatypes/user-identity-token.go
rename to ua/user-identity-token.go
index 69fa37e..511d4ff 100644
--- a/datatypes/user-identity-token.go
+++ b/ua/user-identity-token.go
@@ -2,11 +2,10 @@
 // Use of this source code is governed by a MIT-style license that can be
 // found in the LICENSE file.
 
-package datatypes
+package ua
 
 import (
 	"github.com/gopcua/opcua/id"
-	"github.com/gopcua/opcua/ua"
 )
 
 // UserIdentityToken is an interface to handle all types of UserIdentityToken types as one type.
@@ -29,13 +28,13 @@ func NewAnonymousIdentityToken(policyID string) *AnonymousIdentityToken {
 }
 
 func (t *AnonymousIdentityToken) Decode(b []byte) (int, error) {
-	buf := ua.NewBuffer(b)
+	buf := NewBuffer(b)
 	t.PolicyID = buf.ReadString()
 	return buf.Pos(), buf.Error()
 }
 
 func (t *AnonymousIdentityToken) Encode() ([]byte, error) {
-	buf := ua.NewBuffer(nil)
+	buf := NewBuffer(nil)
 	buf.WriteString(t.PolicyID)
 	return buf.Bytes(), buf.Error()
 }
@@ -88,7 +87,7 @@ func NewUserNameIdentityToken(policyID, username string, password []byte, alg st
 }
 
 func (t *UserNameIdentityToken) Decode(b []byte) (int, error) {
-	buf := ua.NewBuffer(b)
+	buf := NewBuffer(b)
 	t.PolicyID = buf.ReadString()
 	t.UserName = buf.ReadString()
 	t.Password = buf.ReadBytes()
@@ -97,7 +96,7 @@ func (t *UserNameIdentityToken) Decode(b []byte) (int, error) {
 }
 
 func (t *UserNameIdentityToken) Encode() ([]byte, error) {
-	buf := ua.NewBuffer(nil)
+	buf := NewBuffer(nil)
 	buf.WriteString(t.PolicyID)
 	buf.WriteString(t.UserName)
 	buf.WriteByteString(t.Password)
@@ -135,14 +134,14 @@ func NewX509IdentityToken(policyID, cert string) *X509IdentityToken {
 }
 
 func (t *X509IdentityToken) Decode(b []byte) (int, error) {
-	buf := ua.NewBuffer(b)
+	buf := NewBuffer(b)
 	t.PolicyID = buf.ReadString()
 	t.CertificateData = buf.ReadString()
 	return buf.Pos(), buf.Error()
 }
 
 func (t *X509IdentityToken) Encode() ([]byte, error) {
-	buf := ua.NewBuffer(nil)
+	buf := NewBuffer(nil)
 	buf.WriteString(t.PolicyID)
 	buf.WriteString(t.CertificateData)
 	return buf.Bytes(), buf.Error()
@@ -195,7 +194,7 @@ func NewIssuedIdentityToken(policyID string, tokenData []byte, alg string) *Issu
 }
 
 func (t *IssuedIdentityToken) Decode(b []byte) (int, error) {
-	buf := ua.NewBuffer(b)
+	buf := NewBuffer(b)
 	t.PolicyID = buf.ReadString()
 	t.TokenData = buf.ReadBytes()
 	t.EncryptionAlgorithm = buf.ReadString()
@@ -203,7 +202,7 @@ func (t *IssuedIdentityToken) Decode(b []byte) (int, error) {
 }
 
 func (t *IssuedIdentityToken) Encode() ([]byte, error) {
-	buf := ua.NewBuffer(nil)
+	buf := NewBuffer(nil)
 	buf.WriteString(t.PolicyID)
 	buf.WriteByteString(t.TokenData)
 	buf.WriteString(t.EncryptionAlgorithm)
diff --git a/services/user-token-policy.go b/ua/user-token-policy.go
similarity index 98%
rename from services/user-token-policy.go
rename to ua/user-token-policy.go
index c842c63..336958b 100644
--- a/services/user-token-policy.go
+++ b/ua/user-token-policy.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a MIT-style license that can be
 // found in the LICENSE file.
 
-package services
+package ua
 
 import (
 	"fmt"
diff --git a/datatypes/variant.go b/ua/variant.go
similarity index 98%
rename from datatypes/variant.go
rename to ua/variant.go
index 0305003..f2752fc 100644
--- a/datatypes/variant.go
+++ b/ua/variant.go
@@ -2,13 +2,11 @@
 // Use of this source code is governed by a MIT-style license that can be
 // found in the LICENSE file.
 
-package datatypes
+package ua
 
 import (
 	"fmt"
 	"time"
-
-	"github.com/gopcua/opcua/ua"
 )
 
 // Variant is a union of the built-in types.
@@ -73,7 +71,7 @@ func (m *Variant) HasArrayValues() bool {
 }
 
 func (m *Variant) Decode(b []byte) (int, error) {
-	buf := ua.NewBuffer(b)
+	buf := NewBuffer(b)
 
 	m.EncodingMask = buf.ReadByte()
 
@@ -177,7 +175,7 @@ func (m *Variant) Decode(b []byte) (int, error) {
 }
 
 func (m *Variant) Encode() ([]byte, error) {
-	buf := ua.NewBuffer(nil)
+	buf := NewBuffer(nil)
 
 	buf.WriteByte(m.EncodingMask)
 
diff --git a/services/view-service.go b/ua/view-service.go
similarity index 65%
rename from services/view-service.go
rename to ua/view-service.go
index c2acb09..ff3e3c8 100644
--- a/services/view-service.go
+++ b/ua/view-service.go
@@ -1,9 +1,7 @@
-package services
+package ua
 
 import (
 	"time"
-
-	uad "github.com/gopcua/opcua/datatypes"
 )
 
 type BrowseRequest struct {
@@ -16,36 +14,36 @@ type BrowseRequest struct {
 type BrowseResponse struct {
 	ResponseHeader  *ResponseHeader
 	Results         []*BrowseResult
-	DiagnosticInfos []*uad.DiagnosticInfo
+	DiagnosticInfos []*DiagnosticInfo
 }
 
 type BrowseDescription struct {
-	NodeID          *uad.NodeID
+	NodeID          *NodeID
 	Direction       BrowseDirection
-	ReferenceTypeID *uad.NodeID
+	ReferenceTypeID *NodeID
 	IncludeSubtypes bool
 	NodeClassMask   NodeClass
 	ResultMask      BrowseResultMask
 }
 
 type BrowseResult struct {
-	StatusCode        uad.StatusCode
+	StatusCode        StatusCode
 	ContinuationPoint []byte
 	References        []*ReferenceDescription
 }
 
 type ReferenceDescription struct {
-	ReferenceTypeID *uad.NodeID
+	ReferenceTypeID *NodeID
 	IsForward       bool
-	NodeID          *uad.ExpandedNodeID
-	BrowseName      *uad.QualifiedName
-	DisplayName     *uad.LocalizedText
+	NodeID          *ExpandedNodeID
+	BrowseName      *QualifiedName
+	DisplayName     *LocalizedText
 	NodeClass       NodeClass
-	TypeDefinition  *uad.ExpandedNodeID
+	TypeDefinition  *ExpandedNodeID
 }
 
 type ViewDescription struct {
-	ViewID      *uad.NodeID
+	ViewID      *NodeID
 	Timestamp   time.Time // UtcTime
 	ViewVersion uint32
 }
diff --git a/services/write-request.go b/ua/write-request.go
similarity index 78%
rename from services/write-request.go
rename to ua/write-request.go
index 300cc3d..d28dfbe 100644
--- a/services/write-request.go
+++ b/ua/write-request.go
@@ -2,11 +2,7 @@
 // Use of this source code is governed by a MIT-style license that can be
 // found in the LICENSE file.
 
-package services
-
-import (
-	"github.com/gopcua/opcua/datatypes"
-)
+package ua
 
 // WriteRequest is used to write values to one or more Attributes of one or more Nodes. For
 // constructed Attribute values whose elements are indexed, such as an array, this Service allows
@@ -16,11 +12,11 @@ import (
 // Specification: Part 4, 5.10.4
 type WriteRequest struct {
 	RequestHeader *RequestHeader
-	NodesToWrite  []*datatypes.WriteValue
+	NodesToWrite  []*WriteValue
 }
 
 // NewWriteRequest creates a new WriteRequest.
-func NewWriteRequest(reqHeader *RequestHeader, nodes ...*datatypes.WriteValue) *WriteRequest {
+func NewWriteRequest(reqHeader *RequestHeader, nodes ...*WriteValue) *WriteRequest {
 	return &WriteRequest{
 		RequestHeader: reqHeader,
 		NodesToWrite:  nodes,
diff --git a/services/write-response.go b/ua/write-response.go
similarity index 77%
rename from services/write-response.go
rename to ua/write-response.go
index a2612fc..a29f7ba 100644
--- a/services/write-response.go
+++ b/ua/write-response.go
@@ -2,11 +2,7 @@
 // Use of this source code is governed by a MIT-style license that can be
 // found in the LICENSE file.
 
-package services
-
-import (
-	"github.com/gopcua/opcua/datatypes"
-)
+package ua
 
 // WriteResponse is used to write values to one or more Attributes of one or more Nodes. For
 // constructed Attribute values whose elements are indexed, such as an array, this Service allows
@@ -17,11 +13,11 @@ import (
 type WriteResponse struct {
 	ResponseHeader  *ResponseHeader
 	Results         []uint32
-	DiagnosticInfos []*datatypes.DiagnosticInfo
+	DiagnosticInfos []*DiagnosticInfo
 }
 
 // NewWriteResponse creates a new WriteResponse.
-func NewWriteResponse(resHeader *ResponseHeader, diags []*datatypes.DiagnosticInfo, results ...uint32) *WriteResponse {
+func NewWriteResponse(resHeader *ResponseHeader, diags []*DiagnosticInfo, results ...uint32) *WriteResponse {
 	return &WriteResponse{
 		ResponseHeader:  resHeader,
 		Results:         results,
diff --git a/datatypes/write-value.go b/ua/write-value.go
similarity index 91%
rename from datatypes/write-value.go
rename to ua/write-value.go
index 12843f1..4493e2f 100644
--- a/datatypes/write-value.go
+++ b/ua/write-value.go
@@ -2,9 +2,7 @@
 // Use of this source code is governed by a MIT-style license that can be
 // found in the LICENSE file.
 
-package datatypes
-
-import "github.com/gopcua/opcua/ua"
+package ua
 
 // WriteValue is a set of Node and Attribute to write.
 //
@@ -27,7 +25,7 @@ func NewWriteValue(node *NodeID, attr uint32, idxRange string, value *DataValue)
 }
 
 func (v *WriteValue) Decode(b []byte) (int, error) {
-	buf := ua.NewBuffer(b)
+	buf := NewBuffer(b)
 	v.NodeID = new(NodeID)
 	buf.ReadStruct(v.NodeID)
 	v.AttributeID = buf.ReadUint32()
@@ -38,7 +36,7 @@ func (v *WriteValue) Decode(b []byte) (int, error) {
 }
 
 func (v *WriteValue) Encode() ([]byte, error) {
-	buf := ua.NewBuffer(nil)
+	buf := NewBuffer(nil)
 	buf.WriteStruct(v.NodeID)
 	buf.WriteUint32(v.AttributeID)
 	buf.WriteString(v.IndexRange)
diff --git a/datatypes/xml-element.go b/ua/xml-element.go
similarity index 57%
rename from datatypes/xml-element.go
rename to ua/xml-element.go
index c2b8f13..b57a8ee 100644
--- a/datatypes/xml-element.go
+++ b/ua/xml-element.go
@@ -1,3 +1,3 @@
-package datatypes
+package ua
 
 type XmlElement string
diff --git a/uasc/config.go b/uasc/config.go
index f5f6405..366af22 100644
--- a/uasc/config.go
+++ b/uasc/config.go
@@ -9,9 +9,8 @@ import (
 	"encoding/binary"
 	"time"
 
-	"github.com/gopcua/opcua/datatypes"
 	"github.com/gopcua/opcua/errors"
-	"github.com/gopcua/opcua/services"
+	"github.com/gopcua/opcua/ua"
 )
 
 // Config represents a configuration which UASC client/server has in common.
@@ -117,7 +116,7 @@ func NewClientConfigSecurityNone(reqID, lifetime uint32) *Config {
 	return &Config{
 		SecurityPolicyURI: "http://opcfoundation.org/UA/SecurityPolicy#None",
 		RequestID:         reqID,
-		SecurityMode:      services.SecModeNone,
+		SecurityMode:      ua.SecModeNone,
 		Lifetime:          lifetime,
 	}
 }
@@ -128,7 +127,7 @@ func NewClientConfigSecurityNone(reqID, lifetime uint32) *Config {
 func NewClientConfigSignBasic256Sha256(cert, thumbprint []byte, reqID, lifetime uint32) *Config {
 	return NewClientConfig(
 		"http://opcfoundation.org/UA/SecurityPolicy#Basic256Sha256",
-		cert, thumbprint, reqID, services.SecModeSign, lifetime,
+		cert, thumbprint, reqID, ua.SecModeSign, lifetime,
 	)
 }
 
@@ -137,7 +136,7 @@ func NewClientConfigSignBasic256Sha256(cert, thumbprint []byte, reqID, lifetime
 func NewClientConfigSignAndEncryptBasic256Sha256(cert, thumbprint []byte, reqID, lifetime uint32) *Config {
 	return NewClientConfig(
 		"http://opcfoundation.org/UA/SecurityPolicy#Basic256Sha256",
-		cert, thumbprint, reqID, services.SecModeSignAndEncrypt, lifetime,
+		cert, thumbprint, reqID, ua.SecModeSignAndEncrypt, lifetime,
 	)
 }
 
@@ -146,7 +145,7 @@ func NewClientConfigSignAndEncryptBasic256Sha256(cert, thumbprint []byte, reqID,
 func NewClientConfigSignAes128Sha256RsaOaep(cert, thumbprint []byte, reqID, lifetime uint32) *Config {
 	return NewClientConfig(
 		"http://opcfoundation.org/UA/SecurityPolicy#Aes128_Sha256_RsaOaep",
-		cert, thumbprint, reqID, services.SecModeSign, lifetime,
+		cert, thumbprint, reqID, ua.SecModeSign, lifetime,
 	)
 }
 
@@ -155,7 +154,7 @@ func NewClientConfigSignAes128Sha256RsaOaep(cert, thumbprint []byte, reqID, life
 func NewClientConfigSignAndEncryptAes128Sha256RsaOaep(cert, thumbprint []byte, reqID, lifetime uint32) *Config {
 	return NewClientConfig(
 		"http://opcfoundation.org/UA/SecurityPolicy#Aes128_Sha256_RsaOaep",
-		cert, thumbprint, reqID, services.SecModeSignAndEncrypt, lifetime,
+		cert, thumbprint, reqID, ua.SecModeSignAndEncrypt, lifetime,
 	)
 }
 
@@ -164,7 +163,7 @@ func NewClientConfigSignAndEncryptAes128Sha256RsaOaep(cert, thumbprint []byte, r
 func NewClientConfigSignPubSubAes128CTR(cert, thumbprint []byte, reqID, lifetime uint32) *Config {
 	return NewClientConfig(
 		"http://opcfoundation.org/UA/SecurityPolicy#PubSub_Aes128_CTR",
-		cert, thumbprint, reqID, services.SecModeSign, lifetime,
+		cert, thumbprint, reqID, ua.SecModeSign, lifetime,
 	)
 }
 
@@ -173,7 +172,7 @@ func NewClientConfigSignPubSubAes128CTR(cert, thumbprint []byte, reqID, lifetime
 func NewClientConfigSignAndEncryptPubSubAes128CTR(cert, thumbprint []byte, reqID, lifetime uint32) *Config {
 	return NewClientConfig(
 		"http://opcfoundation.org/UA/SecurityPolicy#PubSub_Aes128_CTR",
-		cert, thumbprint, reqID, services.SecModeSignAndEncrypt, lifetime,
+		cert, thumbprint, reqID, ua.SecModeSignAndEncrypt, lifetime,
 	)
 }
 
@@ -182,7 +181,7 @@ func NewClientConfigSignAndEncryptPubSubAes128CTR(cert, thumbprint []byte, reqID
 func NewClientConfigSignPubSubAes256CTR(cert, thumbprint []byte, reqID, lifetime uint32) *Config {
 	return NewClientConfig(
 		"http://opcfoundation.org/UA/SecurityPolicy#PubSub_Aes256_CTR",
-		cert, thumbprint, reqID, services.SecModeSign, lifetime,
+		cert, thumbprint, reqID, ua.SecModeSign, lifetime,
 	)
 }
 
@@ -191,7 +190,7 @@ func NewClientConfigSignPubSubAes256CTR(cert, thumbprint []byte, reqID, lifetime
 func NewClientConfigSignAndEncryptPubSubAes256CTR(cert, thumbprint []byte, reqID, lifetime uint32) *Config {
 	return NewClientConfig(
 		"http://opcfoundation.org/UA/SecurityPolicy#PubSub_Aes256_CTR",
-		cert, thumbprint, reqID, services.SecModeSignAndEncrypt, lifetime,
+		cert, thumbprint, reqID, ua.SecModeSignAndEncrypt, lifetime,
 	)
 }
 */
@@ -225,11 +224,11 @@ func (c *Config) validate(appType string) error {
 }
 
 func (c *Config) validateClientConfig() error {
-	if c.SecurityMode == services.SecModeSignAndEncrypt && (c.Certificate == nil || c.Thumbprint == nil) {
+	if c.SecurityMode == ua.SecModeSignAndEncrypt && (c.Certificate == nil || c.Thumbprint == nil) {
 		return errors.New("Certificate, Thumbprint is required when using SignAndEncrypt")
 	}
 
-	if c.SecurityMode == services.SecModeNone {
+	if c.SecurityMode == ua.SecModeNone {
 		c.Certificate = nil
 		c.Thumbprint = nil
 	}
@@ -237,7 +236,7 @@ func (c *Config) validateClientConfig() error {
 }
 
 func (c *Config) validateServerConfig() error {
-	if c.SecurityMode == services.SecModeNone {
+	if c.SecurityMode == ua.SecModeNone {
 		c.Certificate = nil
 		c.Thumbprint = nil
 	}
@@ -248,11 +247,11 @@ func (c *Config) validateServerConfig() error {
 type SessionConfig struct {
 	// AuthenticationToken is the secret Session identifier used to verify that the request is
 	// associated with the Session. The SessionAuthenticationToken type is defined in 7.31.
-	AuthenticationToken *datatypes.NodeID
+	AuthenticationToken *ua.NodeID
 
 	// ClientDescription is the information that describes the Client application.
 	// The type ApplicationDescription is defined in 7.1.
-	ClientDescription *services.ApplicationDescription
+	ClientDescription *ua.ApplicationDescription
 
 	// ServerEndpoints is the list of Endpoints that the Server supports.
 	// The Server shall return a set of EndpointDescriptions available for the serverUri
@@ -263,7 +262,7 @@ type SessionConfig struct {
 	// securityMode, securityPolicyUri, userIdentityTokens, transportProfileUri and
 	// securityLevel with all other parameters set to null. Only the recommended
 	// parameters shall be verified by the client.
-	ServerEndpoints []*services.EndpointDescription
+	ServerEndpoints []*ua.EndpointDescription
 
 	// LocaleIDs is the list of locale ids in priority order for localized strings. The first
 	// LocaleId in the list has the highest priority. If the Server returns a localized string
@@ -283,13 +282,13 @@ type SessionConfig struct {
 	// The UserIdentityToken is an extensible parameter type defined in 7.36.
 	// The EndpointDescription specifies what UserIdentityTokens the Server shall accept.
 	// Null or empty user token shall always be interpreted as anonymous.
-	UserIdentityToken datatypes.UserIdentityToken
+	UserIdentityToken ua.UserIdentityToken
 
 	// If the Client specified a user identity token that supports digital signatures, then it
 	// shall create a signature and pass it as this parameter. Otherwise the parameter is null.
 	// The SignatureAlgorithm depends on the identity token type.
 	// The SignatureData type is defined in 7.32.
-	UserTokenSignature *services.SignatureData
+	UserTokenSignature *ua.SignatureData
 
 	// If Session works as a client, SessionTimeout is the requested maximum number of milliseconds
 	// that a Session should remain open without activity. If the Client fails to issue a Service
@@ -302,26 +301,28 @@ type SessionConfig struct {
 	// mySignature is is the client/serverSignature expected to receive from the other endpoint.
 	// This parameter is automatically calculated and kept temporarily until being used to verify
 	// received client/serverSignature.
-	mySignature *services.SignatureData
+	// todo(fs): temp disable until the security code is resurrected. keep golangcibot happy
+	// mySignature *ua.SignatureData
 
 	// signatureToSend is the client/serverSignature defined in Part4, Table 15 and Table 17.
 	// This parameter is automatically calculated and kept temporarily until it is sent in next message.
-	signatureToSend *services.SignatureData
+	// todo(fs): temp disable until the security code is resurrected. keep golangcibot happy
+	// signatureToSend *ua.SignatureData
 }
 
 // NewClientSessionConfig creates a SessionConfig for client.
-func NewClientSessionConfig(locales []string, userToken datatypes.UserIdentityToken) *SessionConfig {
+func NewClientSessionConfig(locales []string, userToken ua.UserIdentityToken) *SessionConfig {
 	return &SessionConfig{
 		SessionTimeout: 0xffff,
-		ClientDescription: &services.ApplicationDescription{
+		ClientDescription: &ua.ApplicationDescription{
 			ApplicationURI:  "urn:gopcua:client",
 			ProductURI:      "urn:gopcua",
-			ApplicationName: datatypes.NewLocalizedText("", "gopcua - OPC UA implementation in pure Golang"),
-			ApplicationType: services.AppTypeClient,
+			ApplicationName: ua.NewLocalizedText("", "gopcua - OPC UA implementation in pure Golang"),
+			ApplicationType: ua.AppTypeClient,
 		},
 		LocaleIDs:          locales,
 		UserIdentityToken:  userToken,
-		UserTokenSignature: services.NewSignatureData("", nil),
+		UserTokenSignature: ua.NewSignatureData("", nil),
 	}
 }
 
@@ -332,17 +333,17 @@ func NewServerSessionConfig(secChan *SecureChannel) *SessionConfig {
 		binary.LittleEndian.PutUint16(rawToken, uint16(time.Now().UnixNano()))
 	}
 	return &SessionConfig{
-		AuthenticationToken: datatypes.NewFourByteNodeID(0, binary.LittleEndian.Uint16(rawToken)),
+		AuthenticationToken: ua.NewFourByteNodeID(0, binary.LittleEndian.Uint16(rawToken)),
 		SessionTimeout:      0xffff,
-		ServerEndpoints: []*services.EndpointDescription{
-			services.NewEndpointDescription(
-				secChan.LocalEndpoint(), services.NewApplicationDescription(
+		ServerEndpoints: []*ua.EndpointDescription{
+			ua.NewEndpointDescription(
+				secChan.LocalEndpoint(), ua.NewApplicationDescription(
 					"urn:gopcua:client", "urn:gopcua", "gopcua - OPC UA implementation in pure Golang",
-					services.AppTypeServer, "", "", []string{""},
+					ua.AppTypeServer, "", "", []string{""},
 				),
 				secChan.cfg.Certificate, secChan.cfg.SecurityMode, secChan.cfg.SecurityPolicyURI,
-				[]*services.UserTokenPolicy{
-					&services.UserTokenPolicy{},
+				[]*ua.UserTokenPolicy{
+					&ua.UserTokenPolicy{},
 				},
 				"", 0,
 			),
diff --git a/uasc/message.go b/uasc/message.go
index a407eb3..c6d36ba 100644
--- a/uasc/message.go
+++ b/uasc/message.go
@@ -5,10 +5,8 @@
 package uasc
 
 import (
-	"github.com/gopcua/opcua/datatypes"
 	"github.com/gopcua/opcua/errors"
 	"github.com/gopcua/opcua/id"
-	"github.com/gopcua/opcua/services"
 	"github.com/gopcua/opcua/ua"
 )
 
@@ -62,7 +60,7 @@ func (m *MessageChunk) Decode(b []byte) (int, error) {
 // Message represents a OPC UA Secure Conversation message.
 type Message struct {
 	*MessageHeader
-	TypeID  *datatypes.ExpandedNodeID
+	TypeID  *ua.ExpandedNodeID
 	Service interface{}
 }
 
@@ -85,7 +83,7 @@ func NewMessage(srv interface{}, typeID uint16, cfg *Config) *Message {
 				AsymmetricSecurityHeader: NewAsymmetricSecurityHeader(cfg.SecurityPolicyURI, cfg.Certificate, cfg.Thumbprint),
 				SequenceHeader:           NewSequenceHeader(cfg.SequenceNumber, cfg.RequestID),
 			},
-			TypeID:  datatypes.NewFourByteExpandedNodeID(0, typeID),
+			TypeID:  ua.NewFourByteExpandedNodeID(0, typeID),
 			Service: srv,
 		}
 
@@ -96,7 +94,7 @@ func NewMessage(srv interface{}, typeID uint16, cfg *Config) *Message {
 				SymmetricSecurityHeader: NewSymmetricSecurityHeader(cfg.SecurityTokenID),
 				SequenceHeader:          NewSequenceHeader(cfg.SequenceNumber, cfg.RequestID),
 			},
-			TypeID:  datatypes.NewFourByteExpandedNodeID(0, typeID),
+			TypeID:  ua.NewFourByteExpandedNodeID(0, typeID),
 			Service: srv,
 		}
 
@@ -107,7 +105,7 @@ func NewMessage(srv interface{}, typeID uint16, cfg *Config) *Message {
 				SymmetricSecurityHeader: NewSymmetricSecurityHeader(cfg.SecurityTokenID),
 				SequenceHeader:          NewSequenceHeader(cfg.SequenceNumber, cfg.RequestID),
 			},
-			TypeID:  datatypes.NewFourByteExpandedNodeID(0, typeID),
+			TypeID:  ua.NewFourByteExpandedNodeID(0, typeID),
 			Service: srv,
 		}
 	}
@@ -119,7 +117,7 @@ func (m *Message) Decode(b []byte) (int, error) {
 	if err != nil {
 		return n, err
 	}
-	m.TypeID, m.Service, err = services.Decode(b[n:])
+	m.TypeID, m.Service, err = ua.DecodeService(b[n:])
 	return len(b), err
 }
 
diff --git a/uasc/secure_channel.go b/uasc/secure_channel.go
index ff8826b..602352a 100644
--- a/uasc/secure_channel.go
+++ b/uasc/secure_channel.go
@@ -11,8 +11,7 @@ import (
 	"sync/atomic"
 	"time"
 
-	"github.com/gopcua/opcua/datatypes"
-	"github.com/gopcua/opcua/services"
+	"github.com/gopcua/opcua/ua"
 	"github.com/gopcua/opcua/uacp"
 )
 
@@ -37,7 +36,7 @@ type SecureChannel struct {
 	cfg *Config
 
 	// reqhdr is the header for the next request.
-	reqhdr *services.RequestHeader
+	reqhdr *ua.RequestHeader
 
 	// quit signals the termination of the recv loop.
 	quit chan struct{}
@@ -65,11 +64,11 @@ func NewSecureChannel(c *uacp.Conn, cfg *Config) *SecureChannel {
 	// always reset the secure channel id
 	cfg.SecureChannelID = 0
 
-	reqhdr := &services.RequestHeader{
-		AuthenticationToken: datatypes.NewTwoByteNodeID(0),
+	reqhdr := &ua.RequestHeader{
+		AuthenticationToken: ua.NewTwoByteNodeID(0),
 		Timestamp:           time.Now(),
 		TimeoutHint:         0xffff,
-		AdditionalHeader:    services.NewNullAdditionalHeader(),
+		AdditionalHeader:    ua.NewNullAdditionalHeader(),
 	}
 
 	return &SecureChannel{
@@ -106,16 +105,16 @@ func (s *SecureChannel) openSecureChannel() error {
 		return err
 	}
 
-	req := &services.OpenSecureChannelRequest{
+	req := &ua.OpenSecureChannelRequest{
 		ClientProtocolVersion:    0,
-		SecurityTokenRequestType: services.ReqTypeIssue,
+		SecurityTokenRequestType: ua.ReqTypeIssue,
 		MessageSecurityMode:      s.cfg.SecurityMode,
 		ClientNonce:              nonce,
 		RequestedLifetime:        s.cfg.Lifetime,
 	}
 
 	return s.Send(req, func(v interface{}) error {
-		resp, ok := v.(*services.OpenSecureChannelResponse)
+		resp, ok := v.(*ua.OpenSecureChannelResponse)
 		if !ok {
 			return fmt.Errorf("got %T, want OpenSecureChannelResponse", req)
 		}
@@ -127,7 +126,7 @@ func (s *SecureChannel) openSecureChannel() error {
 
 // closeSecureChannel sends CloseSecureChannelRequest on top of UASC to SecureChannel.
 func (s *SecureChannel) closeSecureChannel() error {
-	req := &services.CloseSecureChannelRequest{
+	req := &ua.CloseSecureChannelRequest{
 		SecureChannelID: s.cfg.SecureChannelID,
 	}
 
@@ -157,7 +156,7 @@ func (s *SecureChannel) Send(svc interface{}, h func(interface{}) error) error {
 // SendAsync sends the service request and returns a channel which will receive the
 // response when it arrives.
 func (s *SecureChannel) SendAsync(svc interface{}) (resp chan Response, err error) {
-	typeID := services.TypeID(svc)
+	typeID := ua.TypeID(svc)
 	if typeID == 0 {
 		return nil, fmt.Errorf("unknown service %T. Did you call register?", svc)
 	}
@@ -303,7 +302,7 @@ func (s *SecureChannel) recv() {
 			// and subsequently remove it and the TypeID from all service
 			// structs and tests. We also need to add a deadline to all
 			// handlers and check them periodically to time them out.
-			_, svc, err := services.Decode(b)
+			_, svc, err := ua.DecodeService(b)
 			if err != nil {
 				s.notifyCaller(reqid, nil, err)
 				continue
diff --git a/uasc/session.go b/uasc/session.go
index 0b02a30..4108dd2 100644
--- a/uasc/session.go
+++ b/uasc/session.go
@@ -5,8 +5,7 @@ import (
 	"fmt"
 	"time"
 
-	"github.com/gopcua/opcua/datatypes"
-	"github.com/gopcua/opcua/services"
+	"github.com/gopcua/opcua/ua"
 )
 
 type Session struct {
@@ -18,11 +17,11 @@ type Session struct {
 	// mySignature is is the client/serverSignature expected to receive from the other endpoint.
 	// This parameter is automatically calculated and kept temporarily until being used to verify
 	// received client/serverSignature.
-	mySignature *services.SignatureData
+	mySignature *ua.SignatureData
 
 	// signatureToSend is the client/serverSignature defined in Part4, Table 15 and Table 17.
 	// This parameter is automatically calculated and kept temporarily until it is sent in next message.
-	signatureToSend *services.SignatureData
+	signatureToSend *ua.SignatureData
 }
 
 func NewSession(sechan *SecureChannel, cfg *SessionConfig) *Session {
@@ -46,7 +45,7 @@ func (s *Session) createSession() error {
 		return err
 	}
 
-	req := &services.CreateSessionRequest{
+	req := &ua.CreateSessionRequest{
 		ClientDescription: s.cfg.ClientDescription,
 		EndpointURL:       s.sechan.EndpointURL,
 		SessionName:       fmt.Sprintf("gopcua-%d", time.Now().UnixNano()),
@@ -55,7 +54,7 @@ func (s *Session) createSession() error {
 	}
 
 	return s.sechan.Send(req, func(v interface{}) error {
-		resp, ok := v.(*services.CreateSessionResponse)
+		resp, ok := v.(*ua.CreateSessionResponse)
 		if !ok {
 			return fmt.Errorf("invalid response. Got %T, want CreateSessionResponse", v)
 		}
@@ -63,25 +62,25 @@ func (s *Session) createSession() error {
 		s.sechan.reqhdr.AuthenticationToken = resp.AuthenticationToken
 		s.cfg.ServerEndpoints = resp.ServerEndpoints
 		s.cfg.SessionTimeout = resp.RevisedSessionTimeout
-		s.signatureToSend = services.NewSignatureDataFrom(resp.ServerCertificate, resp.ServerNonce)
+		s.signatureToSend = ua.NewSignatureDataFrom(resp.ServerCertificate, resp.ServerNonce)
 		s.maxRequestMessageSize = resp.MaxRequestMessageSize
 
 		// keep SignatureData to verify serverSignature in CreateSessionResponse.
-		s.mySignature = services.NewSignatureDataFrom(s.sechan.cfg.Certificate, nonce)
+		s.mySignature = ua.NewSignatureDataFrom(s.sechan.cfg.Certificate, nonce)
 		return nil
 	})
 }
 
 func (s *Session) activateSession() error {
-	req := &services.ActivateSessionRequest{
+	req := &ua.ActivateSessionRequest{
 		ClientSignature:            s.signatureToSend,
 		ClientSoftwareCertificates: nil,
 		LocaleIDs:                  s.cfg.LocaleIDs,
-		UserIdentityToken:          datatypes.NewExtensionObject(1, s.cfg.UserIdentityToken),
+		UserIdentityToken:          ua.NewExtensionObject(1, s.cfg.UserIdentityToken),
 		UserTokenSignature:         s.cfg.UserTokenSignature,
 	}
 	return s.sechan.Send(req, func(v interface{}) error {
-		resp, ok := v.(*services.ActivateSessionResponse)
+		resp, ok := v.(*ua.ActivateSessionResponse)
 		if !ok {
 			return fmt.Errorf("invalid response. Got %T, want ActivateSessionResponse", v)
 		}


Test patch:
diff --git a/conn_test.go b/conn_test.go
index 2b84ae8..a7153ac 100644
--- a/conn_test.go
+++ b/conn_test.go
@@ -5,7 +5,7 @@ import (
 	"testing"
 	"time"
 
-	uad "github.com/gopcua/opcua/datatypes"
+	"github.com/gopcua/opcua/ua"
 	"github.com/gopcua/opcua/uacp"
 	"github.com/gopcua/opcua/uasc"
 )
@@ -43,7 +43,7 @@ func TestClientRead(t *testing.T) {
 	}
 	defer c.Close()
 
-	v, err := c.Node(uad.NewNumericNodeID(0, 2258)).Value()
+	v, err := c.Node(ua.NewNumericNodeID(0, 2258)).Value()
 	if err != nil {
 		t.Fatal(err)
 	}
diff --git a/services/activate-session-request_test.go b/ua/activate-session-request_test.go
similarity index 83%
rename from services/activate-session-request_test.go
rename to ua/activate-session-request_test.go
index 1c26bb6..999527b 100644
--- a/services/activate-session-request_test.go
+++ b/ua/activate-session-request_test.go
@@ -2,25 +2,21 @@
 // Use of this source code is governed by a MIT-style license that can be
 // found in the LICENSE file.
 
-package services
+package ua
 
 import (
 	"testing"
-
-	"github.com/gopcua/opcua/datatypes"
-	"github.com/gopcua/opcua/utils/codectest"
 )
 
-
 func TestActivateSessionRequest(t *testing.T) {
-	cases := []codectest.Case{
+	cases := []CodecTestCase{
 		{
 			Name: "normal",
 			Struct: NewActivateSessionRequest(
 				NewNullRequestHeader(),
 				NewSignatureData("", nil),
 				nil,
-				datatypes.NewAnonymousIdentityToken("anonymous"),
+				NewAnonymousIdentityToken("anonymous"),
 				NewSignatureData("", nil),
 			),
 			Bytes: flatten(
@@ -48,5 +44,5 @@ func TestActivateSessionRequest(t *testing.T) {
 				}),
 		},
 	}
-	codectest.Run(t, cases)
+	RunCodecTest(t, cases)
 }
diff --git a/services/activate-session-response_test.go b/ua/activate-session-response_test.go
similarity index 90%
rename from services/activate-session-response_test.go
rename to ua/activate-session-response_test.go
index 8f49390..2a97b1b 100644
--- a/services/activate-session-response_test.go
+++ b/ua/activate-session-response_test.go
@@ -2,16 +2,14 @@
 // Use of this source code is governed by a MIT-style license that can be
 // found in the LICENSE file.
 
-package services
+package ua
 
 import (
 	"testing"
-
-	"github.com/gopcua/opcua/utils/codectest"
 )
 
 func TestActivateSessionResponse(t *testing.T) {
-	cases := []codectest.Case{
+	cases := []CodecTestCase{
 		{ // Without dummy nonce, results nor diags
 			Name: "nothing",
 			Struct: NewActivateSessionResponse(
@@ -51,5 +49,5 @@ func TestActivateSessionResponse(t *testing.T) {
 				}),
 		},
 	}
-	codectest.Run(t, cases)
+	RunCodecTest(t, cases)
 }
diff --git a/services/additional-header_test.go b/ua/additional-header_test.go
similarity index 63%
rename from services/additional-header_test.go
rename to ua/additional-header_test.go
index 0e1ae24..1d26e6f 100644
--- a/services/additional-header_test.go
+++ b/ua/additional-header_test.go
@@ -2,23 +2,20 @@
 // Use of this source code is governed by a MIT-style license that can be
 // found in the LICENSE file.
 
-package services
+package ua
 
 import (
 	"testing"
-
-	"github.com/gopcua/opcua/datatypes"
-	"github.com/gopcua/opcua/utils/codectest"
 )
 
 func TestAdditionalHeader(t *testing.T) {
-	cases := []codectest.Case{
+	cases := []CodecTestCase{
 		{
 			Name: "no-body",
 			Struct: NewAdditionalHeader(
-				datatypes.NewExpandedNodeID(
+				NewExpandedNodeID(
 					false, false,
-					datatypes.NewTwoByteNodeID(255),
+					NewTwoByteNodeID(255),
 					"", 0,
 				),
 				0x00,
@@ -26,5 +23,5 @@ func TestAdditionalHeader(t *testing.T) {
 			Bytes: []byte{0x00, 0xff, 0x00},
 		},
 	}
-	codectest.Run(t, cases)
+	RunCodecTest(t, cases)
 }
diff --git a/services/application-description_test.go b/ua/application-description_test.go
similarity index 95%
rename from services/application-description_test.go
rename to ua/application-description_test.go
index 7787e10..48b48c9 100644
--- a/services/application-description_test.go
+++ b/ua/application-description_test.go
@@ -2,16 +2,14 @@
 // Use of this source code is governed by a MIT-style license that can be
 // found in the LICENSE file.
 
-package services
+package ua
 
 import (
 	"testing"
-
-	"github.com/gopcua/opcua/utils/codectest"
 )
 
 func TestApplicationDescription(t *testing.T) {
-	cases := []codectest.Case{
+	cases := []CodecTestCase{
 		{
 			Name: "Normal",
 			Struct: NewApplicationDescription(
@@ -44,11 +42,11 @@ func TestApplicationDescription(t *testing.T) {
 			},
 		},
 	}
-	codectest.Run(t, cases)
+	RunCodecTest(t, cases)
 }
 
 func TestApplicationDescriptionArray(t *testing.T) {
-	cases := []codectest.Case{
+	cases := []CodecTestCase{
 		{
 			Name: "Normal",
 			Struct: []*ApplicationDescription{
@@ -111,5 +109,5 @@ func TestApplicationDescriptionArray(t *testing.T) {
 			},
 		},
 	}
-	codectest.Run(t, cases)
+	RunCodecTest(t, cases)
 }
diff --git a/services/cancel-request_test.go b/ua/cancel-request_test.go
similarity index 85%
rename from services/cancel-request_test.go
rename to ua/cancel-request_test.go
index 0d45cf2..5bc5759 100644
--- a/services/cancel-request_test.go
+++ b/ua/cancel-request_test.go
@@ -2,23 +2,20 @@
 // Use of this source code is governed by a MIT-style license that can be
 // found in the LICENSE file.
 
-package services
+package ua
 
 import (
 	"testing"
 	"time"
-
-	"github.com/gopcua/opcua/datatypes"
-	"github.com/gopcua/opcua/utils/codectest"
 )
 
 func TestCancelRequest(t *testing.T) {
-	cases := []codectest.Case{
+	cases := []CodecTestCase{
 		{
 			Name: "normal",
 			Struct: NewCancelRequest(
 				NewRequestHeader(
-					datatypes.NewOpaqueNodeID(0x00, []byte{
+					NewOpaqueNodeID(0x00, []byte{
 						0x08, 0x22, 0x87, 0x62, 0xba, 0x81, 0xe1, 0x11,
 						0xa6, 0x43, 0xf8, 0x77, 0x7b, 0xc6, 0x2f, 0xc8,
 					}),
@@ -49,5 +46,5 @@ func TestCancelRequest(t *testing.T) {
 			},
 		},
 	}
-	codectest.Run(t, cases)
+	RunCodecTest(t, cases)
 }
diff --git a/services/cancel-response_test.go b/ua/cancel-response_test.go
similarity index 87%
rename from services/cancel-response_test.go
rename to ua/cancel-response_test.go
index e5ad3f5..719538f 100644
--- a/services/cancel-response_test.go
+++ b/ua/cancel-response_test.go
@@ -2,17 +2,15 @@
 // Use of this source code is governed by a MIT-style license that can be
 // found in the LICENSE file.
 
-package services
+package ua
 
 import (
 	"testing"
 	"time"
-
-	"github.com/gopcua/opcua/utils/codectest"
 )
 
 func TestCancelResponse(t *testing.T) {
-	cases := []codectest.Case{
+	cases := []CodecTestCase{
 		{
 			Name: "normal",
 			Struct: NewCancelResponse(
@@ -40,5 +38,5 @@ func TestCancelResponse(t *testing.T) {
 			},
 		},
 	}
-	codectest.Run(t, cases)
+	RunCodecTest(t, cases)
 }
diff --git a/services/channel-security-token_test.go b/ua/channel-security-token_test.go
similarity index 84%
rename from services/channel-security-token_test.go
rename to ua/channel-security-token_test.go
index c4075f7..aca46a8 100644
--- a/services/channel-security-token_test.go
+++ b/ua/channel-security-token_test.go
@@ -2,17 +2,15 @@
 // Use of this source code is governed by a MIT-style license that can be
 // found in the LICENSE file.
 
-package services
+package ua
 
 import (
 	"testing"
 	"time"
-
-	"github.com/gopcua/opcua/utils/codectest"
 )
 
 func TestChannelSecurityToken(t *testing.T) {
-	cases := []codectest.Case{
+	cases := []CodecTestCase{
 		{
 			Name: "Normal",
 			Struct: NewChannelSecurityToken(
@@ -30,5 +28,5 @@ func TestChannelSecurityToken(t *testing.T) {
 			},
 		},
 	}
-	codectest.Run(t, cases)
+	RunCodecTest(t, cases)
 }
diff --git a/services/close-secure-channel-request_test.go b/ua/close-secure-channel-request_test.go
similarity index 85%
rename from services/close-secure-channel-request_test.go
rename to ua/close-secure-channel-request_test.go
index b15969a..c0036a9 100644
--- a/services/close-secure-channel-request_test.go
+++ b/ua/close-secure-channel-request_test.go
@@ -2,23 +2,20 @@
 // Use of this source code is governed by a MIT-style license that can be
 // found in the LICENSE file.
 
-package services
+package ua
 
 import (
 	"testing"
 	"time"
-
-	"github.com/gopcua/opcua/datatypes"
-	"github.com/gopcua/opcua/utils/codectest"
 )
 
 func TestCloseSecureChannelRequest(t *testing.T) {
-	cases := []codectest.Case{
+	cases := []CodecTestCase{
 		{
 			Name: "normal",
 			Struct: NewCloseSecureChannelRequest(
 				NewRequestHeader(
-					datatypes.NewOpaqueNodeID(0x00, []byte{
+					NewOpaqueNodeID(0x00, []byte{
 						0x08, 0x22, 0x87, 0x62, 0xba, 0x81, 0xe1, 0x11,
 						0xa6, 0x43, 0xf8, 0x77, 0x7b, 0xc6, 0x2f, 0xc8,
 					}),
@@ -49,5 +46,5 @@ func TestCloseSecureChannelRequest(t *testing.T) {
 			},
 		},
 	}
-	codectest.Run(t, cases)
+	RunCodecTest(t, cases)
 }
diff --git a/services/close-secure-channel-response_test.go b/ua/close-secure-channel-response_test.go
similarity index 87%
rename from services/close-secure-channel-response_test.go
rename to ua/close-secure-channel-response_test.go
index 496eaa0..9030a76 100644
--- a/services/close-secure-channel-response_test.go
+++ b/ua/close-secure-channel-response_test.go
@@ -2,17 +2,15 @@
 // Use of this source code is governed by a MIT-style license that can be
 // found in the LICENSE file.
 
-package services
+package ua
 
 import (
 	"testing"
 	"time"
-
-	"github.com/gopcua/opcua/utils/codectest"
 )
 
 func TestCloseSecureChannelResponse(t *testing.T) {
-	cases := []codectest.Case{
+	cases := []CodecTestCase{
 		{
 			Name: "normal",
 			Struct: NewCloseSecureChannelResponse(
@@ -37,5 +35,5 @@ func TestCloseSecureChannelResponse(t *testing.T) {
 			},
 		},
 	}
-	codectest.Run(t, cases)
+	RunCodecTest(t, cases)
 }
diff --git a/services/close-session-request_test.go b/ua/close-session-request_test.go
similarity index 85%
rename from services/close-session-request_test.go
rename to ua/close-session-request_test.go
index 52804ab..45b084d 100644
--- a/services/close-session-request_test.go
+++ b/ua/close-session-request_test.go
@@ -2,23 +2,20 @@
 // Use of this source code is governed by a MIT-style license that can be
 // found in the LICENSE file.
 
-package services
+package ua
 
 import (
 	"testing"
 	"time"
-
-	"github.com/gopcua/opcua/datatypes"
-	"github.com/gopcua/opcua/utils/codectest"
 )
 
 func TestCloseSessionRequest(t *testing.T) {
-	cases := []codectest.Case{
+	cases := []CodecTestCase{
 		{
 			Name: "normal",
 			Struct: NewCloseSessionRequest(
 				NewRequestHeader(
-					datatypes.NewOpaqueNodeID(0x00, []byte{
+					NewOpaqueNodeID(0x00, []byte{
 						0x08, 0x22, 0x87, 0x62, 0xba, 0x81, 0xe1, 0x11,
 						0xa6, 0x43, 0xf8, 0x77, 0x7b, 0xc6, 0x2f, 0xc8,
 					}),
@@ -50,5 +47,5 @@ func TestCloseSessionRequest(t *testing.T) {
 			},
 		},
 	}
-	codectest.Run(t, cases)
+	RunCodecTest(t, cases)
 }
diff --git a/services/close-session-response_test.go b/ua/close-session-response_test.go
similarity index 87%
rename from services/close-session-response_test.go
rename to ua/close-session-response_test.go
index 0111dee..e3378a1 100644
--- a/services/close-session-response_test.go
+++ b/ua/close-session-response_test.go
@@ -2,17 +2,15 @@
 // Use of this source code is governed by a MIT-style license that can be
 // found in the LICENSE file.
 
-package services
+package ua
 
 import (
 	"testing"
 	"time"
-
-	"github.com/gopcua/opcua/utils/codectest"
 )
 
 func TestCloseSessionResponse(t *testing.T) {
-	cases := []codectest.Case{
+	cases := []CodecTestCase{
 		{ // Without dummy nonce, results nor diags
 			Name: "nothing",
 			Struct: NewCloseSessionResponse(
@@ -37,5 +35,5 @@ func TestCloseSessionResponse(t *testing.T) {
 			},
 		},
 	}
-	codectest.Run(t, cases)
+	RunCodecTest(t, cases)
 }
diff --git a/utils/codectest/testcase.go b/ua/codectest.go
similarity index 84%
rename from utils/codectest/testcase.go
rename to ua/codectest.go
index 1c57e12..78352cf 100644
--- a/utils/codectest/testcase.go
+++ b/ua/codectest.go
@@ -1,16 +1,15 @@
-package codectest
+package ua
 
 import (
 	"reflect"
 	"testing"
 
 	"github.com/pascaldekloe/goe/verify"
-	"github.com/gopcua/opcua/ua"
 )
 
 // Case describes a test case for a encoding and decoding an
 // object from bytes.
-type Case struct {
+type CodecTestCase struct {
 	Name   string
 	Struct interface{}
 	Bytes  []byte
@@ -18,7 +17,7 @@ type Case struct {
 
 // Run tests encoding, decoding and length calclulation for the given
 // object.
-func Run(t *testing.T, cases []Case) {
+func RunCodecTest(t *testing.T, cases []CodecTestCase) {
 	t.Helper()
 
 	for _, c := range cases {
@@ -36,7 +35,7 @@ func Run(t *testing.T, cases []Case) {
 					t.Fatalf("%T is not a pointer or a slice", c.Struct)
 				}
 
-				if _, err := ua.Decode(c.Bytes, v.Interface()); err != nil {
+				if _, err := Decode(c.Bytes, v.Interface()); err != nil {
 					t.Fatal(err)
 				}
 
@@ -48,7 +47,7 @@ func Run(t *testing.T, cases []Case) {
 			})
 
 			t.Run("encode", func(t *testing.T) {
-				b, err := ua.Encode(c.Struct)
+				b, err := Encode(c.Struct)
 				if err != nil {
 					t.Fatal(err)
 				}
diff --git a/services/create-session-request_test.go b/ua/create-session-request_test.go
similarity index 92%
rename from services/create-session-request_test.go
rename to ua/create-session-request_test.go
index a2726d4..747dd86 100644
--- a/services/create-session-request_test.go
+++ b/ua/create-session-request_test.go
@@ -2,23 +2,20 @@
 // Use of this source code is governed by a MIT-style license that can be
 // found in the LICENSE file.
 
-package services
+package ua
 
 import (
 	"testing"
 	"time"
-
-	"github.com/gopcua/opcua/datatypes"
-	"github.com/gopcua/opcua/utils/codectest"
 )
 
 func TestCreateSessionRequest(t *testing.T) {
-	cases := []codectest.Case{
+	cases := []CodecTestCase{
 		{
 			Name: "normal",
 			Struct: NewCreateSessionRequest(
 				NewRequestHeader(
-					datatypes.NewOpaqueNodeID(0x00, []byte{
+					NewOpaqueNodeID(0x00, []byte{
 						0x08, 0x22, 0x87, 0x62, 0xba, 0x81, 0xe1, 0x11,
 						0xa6, 0x43, 0xf8, 0x77, 0x7b, 0xc6, 0x2f, 0xc8,
 					}),
@@ -81,5 +78,5 @@ func TestCreateSessionRequest(t *testing.T) {
 			},
 		},
 	}
-	codectest.Run(t, cases)
+	RunCodecTest(t, cases)
 }
diff --git a/services/create-session-response_test.go b/ua/create-session-response_test.go
similarity index 95%
rename from services/create-session-response_test.go
rename to ua/create-session-response_test.go
index fb2a28b..fd551e6 100644
--- a/services/create-session-response_test.go
+++ b/ua/create-session-response_test.go
@@ -2,27 +2,24 @@
 // Use of this source code is governed by a MIT-style license that can be
 // found in the LICENSE file.
 
-package services
+package ua
 
 import (
 	"testing"
 	"time"
-
-	"github.com/gopcua/opcua/datatypes"
-	"github.com/gopcua/opcua/utils/codectest"
 )
 
 func TestCreateSessionResponse(t *testing.T) {
-	cases := []codectest.Case{
+	cases := []CodecTestCase{
 		{
 			Name: "normal",
 			Struct: NewCreateSessionResponse(
 				NewResponseHeader(
 					time.Date(2018, time.August, 10, 23, 0, 0, 0, time.UTC),
-					1, 0, datatypes.NewNullDiagnosticInfo(), []string{}, NewNullAdditionalHeader(),
+					1, 0, NewNullDiagnosticInfo(), []string{}, NewNullAdditionalHeader(),
 				),
-				datatypes.NewNumericNodeID(0, 1),
-				datatypes.NewOpaqueNodeID(0, []byte{
+				NewNumericNodeID(0, 1),
+				NewOpaqueNodeID(0, []byte{
 					0x08, 0x22, 0x87, 0x62, 0xba, 0x81, 0xe1, 0x11,
 					0xa6, 0x43, 0xf8, 0x77, 0x7b, 0xc6, 0x2f, 0xc8,
 				}),
@@ -195,5 +192,5 @@ func TestCreateSessionResponse(t *testing.T) {
 			},
 		},
 	}
-	codectest.Run(t, cases)
+	RunCodecTest(t, cases)
 }
diff --git a/services/create-subscription-request_test.go b/ua/create-subscription-request_test.go
similarity index 84%
rename from services/create-subscription-request_test.go
rename to ua/create-subscription-request_test.go
index d2ce57c..997f9a3 100644
--- a/services/create-subscription-request_test.go
+++ b/ua/create-subscription-request_test.go
@@ -2,23 +2,20 @@
 // Use of this source code is governed by a MIT-style license that can be
 // found in the LICENSE file.
 
-package services
+package ua
 
 import (
 	"testing"
 	"time"
-
-	"github.com/gopcua/opcua/datatypes"
-	"github.com/gopcua/opcua/utils/codectest"
 )
 
 func TestCreateSubscriptionRequest(t *testing.T) {
-	cases := []codectest.Case{
+	cases := []CodecTestCase{
 		{
 			Name: "normal",
 			Struct: &CreateSubscriptionRequest{
 				RequestHeader: &RequestHeader{
-					AuthenticationToken: datatypes.NewOpaqueNodeID(0, []byte{
+					AuthenticationToken: NewOpaqueNodeID(0, []byte{
 						0xfe, 0x8d, 0x87, 0x79, 0xf7, 0x03, 0x27, 0x77,
 						0xc5, 0x03, 0xa1, 0x09, 0x50, 0x29, 0x27, 0x60,
 					}),
@@ -26,7 +23,7 @@ func TestCreateSubscriptionRequest(t *testing.T) {
 					RequestHandle: 1003429,
 					TimeoutHint:   10000,
 					AdditionalHeader: &AdditionalHeader{
-						TypeID:       datatypes.NewTwoByteExpandedNodeID(0),
+						TypeID:       NewTwoByteExpandedNodeID(0),
 						EncodingMask: 0x00,
 					},
 					Timestamp: time.Date(2018, time.August, 10, 23, 0, 0, 0, time.UTC),
@@ -52,5 +49,5 @@ func TestCreateSubscriptionRequest(t *testing.T) {
 			},
 		},
 	}
-	codectest.Run(t, cases)
+	RunCodecTest(t, cases)
 }
diff --git a/services/create-subscription-response_test.go b/ua/create-subscription-response_test.go
similarity index 82%
rename from services/create-subscription-response_test.go
rename to ua/create-subscription-response_test.go
index 095374e..5447e92 100644
--- a/services/create-subscription-response_test.go
+++ b/ua/create-subscription-response_test.go
@@ -2,24 +2,21 @@
 // Use of this source code is governed by a MIT-style license that can be
 // found in the LICENSE file.
 
-package services
+package ua
 
 import (
 	"testing"
 	"time"
-
-	"github.com/gopcua/opcua/datatypes"
-	"github.com/gopcua/opcua/utils/codectest"
 )
 
 func TestCreateSubscriptionResponse(t *testing.T) {
-	cases := []codectest.Case{
+	cases := []CodecTestCase{
 		{
 			Name: "normal",
 			Struct: &CreateSubscriptionResponse{
 				ResponseHeader: NewResponseHeader(
 					time.Date(2018, time.August, 10, 23, 0, 0, 0, time.UTC),
-					1, 0, datatypes.NewNullDiagnosticInfo(), []string{}, NewNullAdditionalHeader(),
+					1, 0, NewNullDiagnosticInfo(), []string{}, NewNullAdditionalHeader(),
 				),
 
 				SubscriptionID:            1,
@@ -51,5 +48,5 @@ func TestCreateSubscriptionResponse(t *testing.T) {
 			},
 		},
 	}
-	codectest.Run(t, cases)
+	RunCodecTest(t, cases)
 }
diff --git a/datatypes/data-value_test.go b/ua/data-value_test.go
similarity index 92%
rename from datatypes/data-value_test.go
rename to ua/data-value_test.go
index f25ea71..2150fa0 100644
--- a/datatypes/data-value_test.go
+++ b/ua/data-value_test.go
@@ -2,17 +2,15 @@
 // Use of this source code is governed by a MIT-style license that can be
 // found in the LICENSE file.
 
-package datatypes
+package ua
 
 import (
 	"testing"
 	"time"
-
-	"github.com/gopcua/opcua/utils/codectest"
 )
 
 func TestDataValue(t *testing.T) {
-	cases := []codectest.Case{
+	cases := []CodecTestCase{
 		{
 			Name: "value only",
 			Struct: &DataValue{
@@ -48,11 +46,11 @@ func TestDataValue(t *testing.T) {
 			},
 		},
 	}
-	codectest.Run(t, cases)
+	RunCodecTest(t, cases)
 }
 
 func TestDataValueArray(t *testing.T) {
-	cases := []codectest.Case{
+	cases := []CodecTestCase{
 		{
 			Name: "value only and value, source timestamp, server timestamp",
 			Struct: []*DataValue{
@@ -89,5 +87,5 @@ func TestDataValueArray(t *testing.T) {
 			},
 		},
 	}
-	codectest.Run(t, cases)
+	RunCodecTest(t, cases)
 }
diff --git a/datatypes/diagnostic-info_test.go b/ua/diagnostic-info_test.go
similarity index 95%
rename from datatypes/diagnostic-info_test.go
rename to ua/diagnostic-info_test.go
index 454edfd..4fef9d9 100644
--- a/datatypes/diagnostic-info_test.go
+++ b/ua/diagnostic-info_test.go
@@ -2,16 +2,14 @@
 // Use of this source code is governed by a MIT-style license that can be
 // found in the LICENSE file.
 
-package datatypes
+package ua
 
 import (
 	"testing"
-
-	"github.com/gopcua/opcua/utils/codectest"
 )
 
 func TestDiagnosticInfo(t *testing.T) {
-	cases := []codectest.Case{
+	cases := []CodecTestCase{
 		{
 			Name:   "Nothing",
 			Struct: NewNullDiagnosticInfo(),
@@ -127,11 +125,11 @@ func TestDiagnosticInfo(t *testing.T) {
 			},
 		},
 	}
-	codectest.Run(t, cases)
+	RunCodecTest(t, cases)
 }
 
 func TestDiagnosticInfoArray(t *testing.T) {
-	cases := []codectest.Case{
+	cases := []CodecTestCase{
 		{
 			Name:   "Nothing",
 			Struct: []*DiagnosticInfo{},
@@ -179,5 +177,5 @@ func TestDiagnosticInfoArray(t *testing.T) {
 			},
 		},
 	}
-	codectest.Run(t, cases)
+	RunCodecTest(t, cases)
 }
diff --git a/services/endpoint-description_test.go b/ua/endpoint-description_test.go
similarity index 98%
rename from services/endpoint-description_test.go
rename to ua/endpoint-description_test.go
index 79fc376..0ccf06f 100644
--- a/services/endpoint-description_test.go
+++ b/ua/endpoint-description_test.go
@@ -2,16 +2,14 @@
 // Use of this source code is governed by a MIT-style license that can be
 // found in the LICENSE file.
 
-package services
+package ua
 
 import (
 	"testing"
-
-	"github.com/gopcua/opcua/utils/codectest"
 )
 
 func TestEndpointDescription(t *testing.T) {
-	cases := []codectest.Case{
+	cases := []CodecTestCase{
 		{
 			Name: "Normal",
 			Struct: NewEndpointDescription(
@@ -93,11 +91,11 @@ func TestEndpointDescription(t *testing.T) {
 			},
 		},
 	}
-	codectest.Run(t, cases)
+	RunCodecTest(t, cases)
 }
 
 func TestEndpointDescriptionArray(t *testing.T) {
-	cases := []codectest.Case{
+	cases := []CodecTestCase{
 		{
 			Name: "Normal",
 			Struct: []*EndpointDescription{
@@ -258,5 +256,5 @@ func TestEndpointDescriptionArray(t *testing.T) {
 			},
 		},
 	}
-	codectest.Run(t, cases)
+	RunCodecTest(t, cases)
 }
diff --git a/datatypes/expanded-node-id_test.go b/ua/expanded-node-id_test.go
similarity index 90%
rename from datatypes/expanded-node-id_test.go
rename to ua/expanded-node-id_test.go
index 19cea92..b55925e 100644
--- a/datatypes/expanded-node-id_test.go
+++ b/ua/expanded-node-id_test.go
@@ -2,16 +2,14 @@
 // Use of this source code is governed by a MIT-style license that can be
 // found in the LICENSE file.
 
-package datatypes
+package ua
 
 import (
 	"testing"
-
-	"github.com/gopcua/opcua/utils/codectest"
 )
 
 func TestExpandedNodeID(t *testing.T) {
-	cases := []codectest.Case{
+	cases := []CodecTestCase{
 		{
 			Name: "Without optional fields",
 			Struct: NewExpandedNodeID(
@@ -59,5 +57,5 @@ func TestExpandedNodeID(t *testing.T) {
 			},
 		},
 	}
-	codectest.Run(t, cases)
+	RunCodecTest(t, cases)
 }
diff --git a/datatypes/extension-object_test.go b/ua/extension-object_test.go
similarity index 84%
rename from datatypes/extension-object_test.go
rename to ua/extension-object_test.go
index f288632..ef10b8c 100644
--- a/datatypes/extension-object_test.go
+++ b/ua/extension-object_test.go
@@ -2,16 +2,14 @@
 // Use of this source code is governed by a MIT-style license that can be
 // found in the LICENSE file.
 
-package datatypes
+package ua
 
 import (
 	"testing"
-
-	"github.com/gopcua/opcua/utils/codectest"
 )
 
 func TestExtensionObject(t *testing.T) {
-	cases := []codectest.Case{
+	cases := []CodecTestCase{
 		{
 			Name: "anonymous-user-identity-token",
 			Struct: NewExtensionObject(
@@ -29,5 +27,5 @@ func TestExtensionObject(t *testing.T) {
 			},
 		},
 	}
-	codectest.Run(t, cases)
+	RunCodecTest(t, cases)
 }
diff --git a/services/find-servers-on-network-request_test.go b/ua/find-servers-on-network-request_test.go
similarity index 88%
rename from services/find-servers-on-network-request_test.go
rename to ua/find-servers-on-network-request_test.go
index 4371a8a..b7141f9 100644
--- a/services/find-servers-on-network-request_test.go
+++ b/ua/find-servers-on-network-request_test.go
@@ -2,23 +2,20 @@
 // Use of this source code is governed by a MIT-style license that can be
 // found in the LICENSE file.
 
-package services
+package ua
 
 import (
 	"testing"
 	"time"
-
-	"github.com/gopcua/opcua/datatypes"
-	"github.com/gopcua/opcua/utils/codectest"
 )
 
 func TestFindServersOnNetworkRequest(t *testing.T) {
-	cases := []codectest.Case{
+	cases := []CodecTestCase{
 		{
 			Name: "normal",
 			Struct: NewFindServersOnNetworkRequest(
 				NewRequestHeader(
-					datatypes.NewOpaqueNodeID(0x00, []byte{
+					NewOpaqueNodeID(0x00, []byte{
 						0x08, 0x22, 0x87, 0x62, 0xba, 0x81, 0xe1, 0x11,
 						0xa6, 0x43, 0xf8, 0x77, 0x7b, 0xc6, 0x2f, 0xc8,
 					}),
@@ -61,5 +58,5 @@ func TestFindServersOnNetworkRequest(t *testing.T) {
 			},
 		},
 	}
-	codectest.Run(t, cases)
+	RunCodecTest(t, cases)
 }
diff --git a/services/find-servers-on-network-response_test.go b/ua/find-servers-on-network-response_test.go
similarity index 91%
rename from services/find-servers-on-network-response_test.go
rename to ua/find-servers-on-network-response_test.go
index cbad4e1..f2ff2e5 100644
--- a/services/find-servers-on-network-response_test.go
+++ b/ua/find-servers-on-network-response_test.go
@@ -2,24 +2,21 @@
 // Use of this source code is governed by a MIT-style license that can be
 // found in the LICENSE file.
 
-package services
+package ua
 
 import (
 	"testing"
 	"time"
-
-	"github.com/gopcua/opcua/datatypes"
-	"github.com/gopcua/opcua/utils/codectest"
 )
 
 func TestFindServersOnNetworkResponse(t *testing.T) {
-	cases := []codectest.Case{
+	cases := []CodecTestCase{
 		{
 			Name: "single-server",
 			Struct: NewFindServersOnNetworkResponse(
 				NewResponseHeader(
 					time.Date(2018, time.August, 10, 23, 0, 0, 0, time.UTC),
-					1, 0, datatypes.NewNullDiagnosticInfo(), []string{}, NewNullAdditionalHeader(),
+					1, 0, NewNullDiagnosticInfo(), []string{}, NewNullAdditionalHeader(),
 				),
 				time.Date(2018, time.August, 10, 23, 0, 0, 0, time.UTC),
 				NewServersOnNetwork(
@@ -67,7 +64,7 @@ func TestFindServersOnNetworkResponse(t *testing.T) {
 			Struct: NewFindServersOnNetworkResponse(
 				NewResponseHeader(
 					time.Date(2018, time.August, 10, 23, 0, 0, 0, time.UTC),
-					1, 0, datatypes.NewNullDiagnosticInfo(), []string{}, NewNullAdditionalHeader(),
+					1, 0, NewNullDiagnosticInfo(), []string{}, NewNullAdditionalHeader(),
 				),
 				time.Date(2018, time.August, 10, 23, 0, 0, 0, time.UTC),
 				NewServersOnNetwork(
@@ -131,5 +128,5 @@ func TestFindServersOnNetworkResponse(t *testing.T) {
 			},
 		},
 	}
-	codectest.Run(t, cases)
+	RunCodecTest(t, cases)
 }
diff --git a/services/find-servers-request_test.go b/ua/find-servers-request_test.go
similarity index 89%
rename from services/find-servers-request_test.go
rename to ua/find-servers-request_test.go
index 3c9acf9..6573fc8 100644
--- a/services/find-servers-request_test.go
+++ b/ua/find-servers-request_test.go
@@ -2,23 +2,20 @@
 // Use of this source code is governed by a MIT-style license that can be
 // found in the LICENSE file.
 
-package services
+package ua
 
 import (
 	"testing"
 	"time"
-
-	"github.com/gopcua/opcua/datatypes"
-	"github.com/gopcua/opcua/utils/codectest"
 )
 
 func TestFindServersRequest(t *testing.T) {
-	cases := []codectest.Case{
+	cases := []CodecTestCase{
 		{
 			Name: "no-locales, no-uris",
 			Struct: NewFindServersRequest(
 				NewRequestHeader(
-					datatypes.NewOpaqueNodeID(0x00, []byte{
+					NewOpaqueNodeID(0x00, []byte{
 						0x08, 0x22, 0x87, 0x62, 0xba, 0x81, 0xe1, 0x11,
 						0xa6, 0x43, 0xf8, 0x77, 0x7b, 0xc6, 0x2f, 0xc8,
 					}),
@@ -64,5 +61,5 @@ func TestFindServersRequest(t *testing.T) {
 			},
 		},
 	}
-	codectest.Run(t, cases)
+	RunCodecTest(t, cases)
 }
diff --git a/services/find-servers-response_test.go b/ua/find-servers-response_test.go
similarity index 92%
rename from services/find-servers-response_test.go
rename to ua/find-servers-response_test.go
index 8e9b809..94d758e 100644
--- a/services/find-servers-response_test.go
+++ b/ua/find-servers-response_test.go
@@ -2,24 +2,21 @@
 // Use of this source code is governed by a MIT-style license that can be
 // found in the LICENSE file.
 
-package services
+package ua
 
 import (
 	"testing"
 	"time"
-
-	"github.com/gopcua/opcua/datatypes"
-	"github.com/gopcua/opcua/utils/codectest"
 )
 
 func TestFindServersResponse(t *testing.T) {
-	cases := []codectest.Case{
+	cases := []CodecTestCase{
 		{
 			Name: "normal",
 			Struct: NewFindServersResponse(
 				NewResponseHeader(
 					time.Date(2018, time.August, 10, 23, 0, 0, 0, time.UTC),
-					1, 0, datatypes.NewNullDiagnosticInfo(), []string{}, NewNullAdditionalHeader(),
+					1, 0, NewNullDiagnosticInfo(), []string{}, NewNullAdditionalHeader(),
 				),
 				NewApplicationDescription(
 					"app-uri",
@@ -93,5 +90,5 @@ func TestFindServersResponse(t *testing.T) {
 			},
 		},
 	}
-	codectest.Run(t, cases)
+	RunCodecTest(t, cases)
 }
diff --git a/services/get-endpoints-request_test.go b/ua/get-endpoints-request_test.go
similarity index 88%
rename from services/get-endpoints-request_test.go
rename to ua/get-endpoints-request_test.go
index 1a10411..b58beca 100644
--- a/services/get-endpoints-request_test.go
+++ b/ua/get-endpoints-request_test.go
@@ -2,23 +2,20 @@
 // Use of this source code is governed by a MIT-style license that can be
 // found in the LICENSE file.
 
-package services
+package ua
 
 import (
 	"testing"
 	"time"
-
-	"github.com/gopcua/opcua/datatypes"
-	"github.com/gopcua/opcua/utils/codectest"
 )
 
 func TestGetEndpointsRequest(t *testing.T) {
-	cases := []codectest.Case{
+	cases := []CodecTestCase{
 		{
 			Name: "normal",
 			Struct: NewGetEndpointsRequest(
 				NewRequestHeader(
-					datatypes.NewOpaqueNodeID(0x00, []byte{
+					NewOpaqueNodeID(0x00, []byte{
 						0x08, 0x22, 0x87, 0x62, 0xba, 0x81, 0xe1, 0x11,
 						0xa6, 0x43, 0xf8, 0x77, 0x7b, 0xc6, 0x2f, 0xc8,
 					}),
@@ -60,5 +57,5 @@ func TestGetEndpointsRequest(t *testing.T) {
 			},
 		},
 	}
-	codectest.Run(t, cases)
+	RunCodecTest(t, cases)
 }
diff --git a/services/get-endpoints-response_test.go b/ua/get-endpoints-response_test.go
similarity index 96%
rename from services/get-endpoints-response_test.go
rename to ua/get-endpoints-response_test.go
index c1d2318..ca15a47 100644
--- a/services/get-endpoints-response_test.go
+++ b/ua/get-endpoints-response_test.go
@@ -2,24 +2,21 @@
 // Use of this source code is governed by a MIT-style license that can be
 // found in the LICENSE file.
 
-package services
+package ua
 
 import (
 	"testing"
 	"time"
-
-	"github.com/gopcua/opcua/datatypes"
-	"github.com/gopcua/opcua/utils/codectest"
 )
 
 func TestGetEndpointsResponse(t *testing.T) {
-	cases := []codectest.Case{
+	cases := []CodecTestCase{
 		{
 			Name: "normal",
 			Struct: NewGetEndpointsResponse(
 				NewResponseHeader(
 					time.Date(2018, time.August, 10, 23, 0, 0, 0, time.UTC),
-					1, 0, datatypes.NewNullDiagnosticInfo(), []string{}, NewNullAdditionalHeader(),
+					1, 0, NewNullDiagnosticInfo(), []string{}, NewNullAdditionalHeader(),
 				),
 				NewEndpointDescription(
 					"ep-url",
@@ -191,5 +188,5 @@ func TestGetEndpointsResponse(t *testing.T) {
 			},
 		},
 	}
-	codectest.Run(t, cases)
+	RunCodecTest(t, cases)
 }
diff --git a/datatypes/guid_test.go b/ua/guid_test.go
similarity index 79%
rename from datatypes/guid_test.go
rename to ua/guid_test.go
index f780454..0efa6f5 100644
--- a/datatypes/guid_test.go
+++ b/ua/guid_test.go
@@ -2,16 +2,14 @@
 // Use of this source code is governed by a MIT-style license that can be
 // found in the LICENSE file.
 
-package datatypes
+package ua
 
 import (
 	"testing"
-
-	"github.com/gopcua/opcua/utils/codectest"
 )
 
 func TestGUID(t *testing.T) {
-	cases := []codectest.Case{
+	cases := []CodecTestCase{
 		{
 			Name:   "ok",
 			Struct: NewGUID("AAAABBBB-CCDD-EEFF-0101-0123456789AB"),
@@ -21,5 +19,5 @@ func TestGUID(t *testing.T) {
 			},
 		},
 	}
-	codectest.Run(t, cases)
+	RunCodecTest(t, cases)
 }
diff --git a/datatypes/localized-text_test.go b/ua/localized-text_test.go
similarity index 88%
rename from datatypes/localized-text_test.go
rename to ua/localized-text_test.go
index 1c2b99c..67228d4 100644
--- a/datatypes/localized-text_test.go
+++ b/ua/localized-text_test.go
@@ -2,16 +2,14 @@
 // Use of this source code is governed by a MIT-style license that can be
 // found in the LICENSE file.
 
-package datatypes
+package ua
 
 import (
 	"testing"
-
-	"github.com/gopcua/opcua/utils/codectest"
 )
 
 func TestLocalizedText(t *testing.T) {
-	cases := []codectest.Case{
+	cases := []CodecTestCase{
 		{
 			Name:   "nothing",
 			Struct: NewLocalizedText("", ""),
@@ -44,5 +42,5 @@ func TestLocalizedText(t *testing.T) {
 			},
 		},
 	}
-	codectest.Run(t, cases)
+	RunCodecTest(t, cases)
 }
diff --git a/datatypes/node-id_test.go b/ua/node-id_test.go
similarity index 98%
rename from datatypes/node-id_test.go
rename to ua/node-id_test.go
index 43b8a58..58a19d5 100644
--- a/datatypes/node-id_test.go
+++ b/ua/node-id_test.go
@@ -2,20 +2,17 @@
 // Use of this source code is governed by a MIT-style license that can be
 // found in the LICENSE file.
 
-package datatypes
+package ua
 
 import (
 	"encoding/base64"
 	"errors"
 	"reflect"
 	"testing"
-
-	"github.com/gopcua/opcua/ua"
-	"github.com/gopcua/opcua/utils/codectest"
 )
 
 func TestNodeID(t *testing.T) {
-	cases := []codectest.Case{
+	cases := []CodecTestCase{
 		{
 			Name:   "TwoByte",
 			Struct: NewTwoByteNodeID(0xff),
@@ -92,7 +89,7 @@ func TestNodeID(t *testing.T) {
 			},
 		},
 	}
-	codectest.Run(t, cases)
+	RunCodecTest(t, cases)
 }
 
 func BenchmarkReflectDecode(b *testing.B) {
@@ -109,7 +106,7 @@ func BenchmarkReflectDecode(b *testing.B) {
 
 	for i := 0; i < b.N; i++ {
 		v := new(NodeID)
-		if _, err := ua.Decode(data, v); err != nil {
+		if _, err := Decode(data, v); err != nil {
 			b.Fatal(err)
 		}
 	}
diff --git a/services/open-secure-channel-request_test.go b/ua/open-secure-channel-request_test.go
similarity index 87%
rename from services/open-secure-channel-request_test.go
rename to ua/open-secure-channel-request_test.go
index a09915c..8a6ddb7 100644
--- a/services/open-secure-channel-request_test.go
+++ b/ua/open-secure-channel-request_test.go
@@ -2,23 +2,20 @@
 // Use of this source code is governed by a MIT-style license that can be
 // found in the LICENSE file.
 
-package services
+package ua
 
 import (
 	"testing"
 	"time"
-
-	"github.com/gopcua/opcua/datatypes"
-	"github.com/gopcua/opcua/utils/codectest"
 )
 
 func TestOpenSecureChannelRequest(t *testing.T) {
-	cases := []codectest.Case{
+	cases := []CodecTestCase{
 		{
 			Name: "normal",
 			Struct: NewOpenSecureChannelRequest(
 				NewRequestHeader(
-					datatypes.NewOpaqueNodeID(0x00, []byte{
+					NewOpaqueNodeID(0x00, []byte{
 						0x08, 0x22, 0x87, 0x62, 0xba, 0x81, 0xe1, 0x11,
 						0xa6, 0x43, 0xf8, 0x77, 0x7b, 0xc6, 0x2f, 0xc8,
 					}),
@@ -61,5 +58,5 @@ func TestOpenSecureChannelRequest(t *testing.T) {
 			},
 		},
 	}
-	codectest.Run(t, cases)
+	RunCodecTest(t, cases)
 }
diff --git a/services/open-secure-channel-response_test.go b/ua/open-secure-channel-response_test.go
similarity index 82%
rename from services/open-secure-channel-response_test.go
rename to ua/open-secure-channel-response_test.go
index c5d62c9..0496e29 100644
--- a/services/open-secure-channel-response_test.go
+++ b/ua/open-secure-channel-response_test.go
@@ -2,24 +2,21 @@
 // Use of this source code is governed by a MIT-style license that can be
 // found in the LICENSE file.
 
-package services
+package ua
 
 import (
 	"testing"
 	"time"
-
-	"github.com/gopcua/opcua/datatypes"
-	"github.com/gopcua/opcua/utils/codectest"
 )
 
 func TestOpenSecureChannelResponse(t *testing.T) {
-	cases := []codectest.Case{
+	cases := []CodecTestCase{
 		{
 			Name: "normal",
 			Struct: NewOpenSecureChannelResponse(
 				NewResponseHeader(
 					time.Date(2018, time.August, 10, 23, 0, 0, 0, time.UTC),
-					1, 0, datatypes.NewNullDiagnosticInfo(), []string{}, NewNullAdditionalHeader(),
+					1, 0, NewNullDiagnosticInfo(), []string{}, NewNullAdditionalHeader(),
 				),
 				0,
 				NewChannelSecurityToken(
@@ -51,5 +48,5 @@ func TestOpenSecureChannelResponse(t *testing.T) {
 			},
 		},
 	}
-	codectest.Run(t, cases)
+	RunCodecTest(t, cases)
 }
diff --git a/datatypes/qualified-name_test.go b/ua/qualified-name_test.go
similarity index 84%
rename from datatypes/qualified-name_test.go
rename to ua/qualified-name_test.go
index 9a707aa..8109543 100644
--- a/datatypes/qualified-name_test.go
+++ b/ua/qualified-name_test.go
@@ -2,16 +2,14 @@
 // Use of this source code is governed by a MIT-style license that can be
 // found in the LICENSE file.
 
-package datatypes
+package ua
 
 import (
 	"testing"
-
-	"github.com/gopcua/opcua/utils/codectest"
 )
 
 func TestQualifiedName(t *testing.T) {
-	cases := []codectest.Case{
+	cases := []CodecTestCase{
 		{
 			Name:   "normal",
 			Struct: NewQualifiedName(1, "foobar"),
@@ -34,5 +32,5 @@ func TestQualifiedName(t *testing.T) {
 			},
 		},
 	}
-	codectest.Run(t, cases)
+	RunCodecTest(t, cases)
 }
diff --git a/services/read-request_test.go b/ua/read-request_test.go
similarity index 82%
rename from services/read-request_test.go
rename to ua/read-request_test.go
index edcd686..ed6dbfb 100644
--- a/services/read-request_test.go
+++ b/ua/read-request_test.go
@@ -2,23 +2,20 @@
 // Use of this source code is governed by a MIT-style license that can be
 // found in the LICENSE file.
 
-package services
+package ua
 
 import (
 	"testing"
 	"time"
-
-	"github.com/gopcua/opcua/datatypes"
-	"github.com/gopcua/opcua/utils/codectest"
 )
 
 func TestReadRequest(t *testing.T) {
-	cases := []codectest.Case{
+	cases := []CodecTestCase{
 		{
 			Name: "normal",
 			Struct: NewReadRequest(
 				NewRequestHeader(
-					datatypes.NewOpaqueNodeID(0x00, []byte{
+					NewOpaqueNodeID(0x00, []byte{
 						0x08, 0x22, 0x87, 0x62, 0xba, 0x81, 0xe1, 0x11,
 						0xa6, 0x43, 0xf8, 0x77, 0x7b, 0xc6, 0x2f, 0xc8,
 					}),
@@ -26,9 +23,9 @@ func TestReadRequest(t *testing.T) {
 					1, 0, 0, "", NewNullAdditionalHeader(),
 				),
 				0, TimestampsToReturnBoth,
-				datatypes.NewReadValueID(
-					datatypes.NewFourByteNodeID(0, 2256),
-					datatypes.IntegerIDValue,
+				NewReadValueID(
+					NewFourByteNodeID(0, 2256),
+					IntegerIDValue,
 					"", 0, "",
 				),
 			),
@@ -60,5 +57,5 @@ func TestReadRequest(t *testing.T) {
 			},
 		},
 	}
-	codectest.Run(t, cases)
+	RunCodecTest(t, cases)
 }
diff --git a/services/read-response_test.go b/ua/read-response_test.go
similarity index 71%
rename from services/read-response_test.go
rename to ua/read-response_test.go
index aedae29..a427cd4 100644
--- a/services/read-response_test.go
+++ b/ua/read-response_test.go
@@ -2,31 +2,28 @@
 // Use of this source code is governed by a MIT-style license that can be
 // found in the LICENSE file.
 
-package services
+package ua
 
 import (
 	"testing"
 	"time"
-
-	"github.com/gopcua/opcua/datatypes"
-	"github.com/gopcua/opcua/utils/codectest"
 )
 
 func TestReadResponse(t *testing.T) {
-	cases := []codectest.Case{
+	cases := []CodecTestCase{
 		{
 			Name: "read response with single float value",
 			Struct: NewReadResponse(
 				NewResponseHeader(
 					time.Date(2018, time.August, 10, 23, 0, 0, 0, time.UTC),
-					1, 0, datatypes.NewNullDiagnosticInfo(), []string{}, NewNullAdditionalHeader(),
+					1, 0, NewNullDiagnosticInfo(), []string{}, NewNullAdditionalHeader(),
 				),
-				[]*datatypes.DiagnosticInfo{
-					datatypes.NewNullDiagnosticInfo(),
+				[]*DiagnosticInfo{
+					NewNullDiagnosticInfo(),
 				},
-				datatypes.NewDataValue(
+				NewDataValue(
 					true, false, false, false, false, false,
-					datatypes.MustVariant(float32(2.5001559257507324)),
+					MustVariant(float32(2.5001559257507324)),
 					0, time.Time{}, 0, time.Time{}, 0,
 				),
 			),
@@ -53,5 +50,5 @@ func TestReadResponse(t *testing.T) {
 			},
 		},
 	}
-	codectest.Run(t, cases)
+	RunCodecTest(t, cases)
 }
diff --git a/datatypes/read-value-id_test.go b/ua/read-value-id_test.go
similarity index 93%
rename from datatypes/read-value-id_test.go
rename to ua/read-value-id_test.go
index 7ac167f..d055159 100644
--- a/datatypes/read-value-id_test.go
+++ b/ua/read-value-id_test.go
@@ -2,16 +2,14 @@
 // Use of this source code is governed by a MIT-style license that can be
 // found in the LICENSE file.
 
-package datatypes
+package ua
 
 import (
 	"testing"
-
-	"github.com/gopcua/opcua/utils/codectest"
 )
 
 func TestReadValueID(t *testing.T) {
-	cases := []codectest.Case{
+	cases := []CodecTestCase{
 		{
 			Name: "Normal",
 			Struct: NewReadValueID(
@@ -34,11 +32,11 @@ func TestReadValueID(t *testing.T) {
 			},
 		},
 	}
-	codectest.Run(t, cases)
+	RunCodecTest(t, cases)
 }
 
 func TestReadValueIDArray(t *testing.T) {
-	cases := []codectest.Case{
+	cases := []CodecTestCase{
 		{
 			Name:   "empty",
 			Struct: []*ReadValueID{},
@@ -111,5 +109,5 @@ func TestReadValueIDArray(t *testing.T) {
 			},
 		},
 	}
-	codectest.Run(t, cases)
+	RunCodecTest(t, cases)
 }
diff --git a/services/request-header_test.go b/ua/request-header_test.go
similarity index 83%
rename from services/request-header_test.go
rename to ua/request-header_test.go
index 7edd8bc..16c6e4c 100644
--- a/services/request-header_test.go
+++ b/ua/request-header_test.go
@@ -2,19 +2,16 @@
 // Use of this source code is governed by a MIT-style license that can be
 // found in the LICENSE file.
 
-package services
+package ua
 
 import (
 	"testing"
 	"time"
-
-	"github.com/gopcua/opcua/datatypes"
-	"github.com/gopcua/opcua/utils/codectest"
 )
 
 func NewNullRequestHeader() *RequestHeader {
 	return &RequestHeader{
-		AuthenticationToken: datatypes.NewTwoByteNodeID(0),
+		AuthenticationToken: NewTwoByteNodeID(0),
 		Timestamp:           time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC),
 		AdditionalHeader:    NewNullAdditionalHeader(),
 	}
@@ -38,7 +35,7 @@ var nullRequestHeaderBytes = []byte{
 }
 
 func TestRequestHeader(t *testing.T) {
-	cases := []codectest.Case{
+	cases := []CodecTestCase{
 		{
 			Struct: NewNullRequestHeader(),
 			Bytes:  nullRequestHeaderBytes,
@@ -46,16 +43,16 @@ func TestRequestHeader(t *testing.T) {
 		{
 			Struct: func() *RequestHeader {
 				r := NewRequestHeader(
-					datatypes.NewFourByteNodeID(0, 33008),
+					NewFourByteNodeID(0, 33008),
 					time.Date(2018, time.August, 10, 23, 0, 0, 0, time.UTC),
 					1,
 					0,
 					0,
 					"foobar",
 					NewAdditionalHeader(
-						datatypes.NewExpandedNodeID(
+						NewExpandedNodeID(
 							false, false,
-							datatypes.NewTwoByteNodeID(255),
+							NewTwoByteNodeID(255),
 							"", 0,
 						),
 						0x00,
@@ -83,5 +80,5 @@ func TestRequestHeader(t *testing.T) {
 			},
 		},
 	}
-	codectest.Run(t, cases)
+	RunCodecTest(t, cases)
 }
diff --git a/services/response-header_test.go b/ua/response-header_test.go
similarity index 83%
rename from services/response-header_test.go
rename to ua/response-header_test.go
index 21fd7f6..f3f7614 100644
--- a/services/response-header_test.go
+++ b/ua/response-header_test.go
@@ -2,20 +2,17 @@
 // Use of this source code is governed by a MIT-style license that can be
 // found in the LICENSE file.
 
-package services
+package ua
 
 import (
 	"testing"
 	"time"
-
-	"github.com/gopcua/opcua/datatypes"
-	"github.com/gopcua/opcua/utils/codectest"
 )
 
 func NewNullResponseHeader() *ResponseHeader {
 	return &ResponseHeader{
 		Timestamp:          time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC),
-		ServiceDiagnostics: datatypes.NewNullDiagnosticInfo(),
+		ServiceDiagnostics: NewNullDiagnosticInfo(),
 		AdditionalHeader:   NewNullAdditionalHeader(),
 	}
 }
@@ -36,7 +33,7 @@ var nullResponseHeaderBytes = []byte{
 }
 
 func TestResponseHeader(t *testing.T) {
-	cases := []codectest.Case{
+	cases := []CodecTestCase{
 		{
 			Struct: NewNullResponseHeader(),
 			Bytes:  nullResponseHeaderBytes,
@@ -46,15 +43,15 @@ func TestResponseHeader(t *testing.T) {
 				time.Date(2018, time.August, 10, 23, 0, 0, 0, time.UTC),
 				1,
 				0x00000000,
-				datatypes.NewDiagnosticInfo(
+				NewDiagnosticInfo(
 					false, false, false, false, false, false, false,
 					0, 0, 0, 0, "", 0, nil,
 				),
 				[]string{"foo", "bar"},
 				NewAdditionalHeader(
-					datatypes.NewExpandedNodeID(
+					NewExpandedNodeID(
 						false, false,
-						datatypes.NewTwoByteNodeID(255),
+						NewTwoByteNodeID(255),
 						"", 0,
 					),
 					0x00,
@@ -78,5 +75,5 @@ func TestResponseHeader(t *testing.T) {
 			},
 		},
 	}
-	codectest.Run(t, cases)
+	RunCodecTest(t, cases)
 }
diff --git a/services/servers-on-network_test.go b/ua/servers-on-network_test.go
similarity index 94%
rename from services/servers-on-network_test.go
rename to ua/servers-on-network_test.go
index 553487f..f0fb63c 100644
--- a/services/servers-on-network_test.go
+++ b/ua/servers-on-network_test.go
@@ -2,16 +2,14 @@
 // Use of this source code is governed by a MIT-style license that can be
 // found in the LICENSE file.
 
-package services
+package ua
 
 import (
 	"testing"
-
-	"github.com/gopcua/opcua/utils/codectest"
 )
 
 func TestDecodeServersOnNetwork(t *testing.T) {
-	cases := []codectest.Case{
+	cases := []CodecTestCase{
 		{
 			Name: "single-cap",
 			Struct: NewServersOnNetwork(
@@ -61,11 +59,11 @@ func TestDecodeServersOnNetwork(t *testing.T) {
 			},
 		},
 	}
-	codectest.Run(t, cases)
+	RunCodecTest(t, cases)
 }
 
 func TestServersOnNetworkArray(t *testing.T) {
-	cases := []codectest.Case{
+	cases := []CodecTestCase{
 		{
 			Name: "normal",
 			Struct: []*ServersOnNetwork{
@@ -114,5 +112,5 @@ func TestServersOnNetworkArray(t *testing.T) {
 			},
 		},
 	}
-	codectest.Run(t, cases)
+	RunCodecTest(t, cases)
 }
diff --git a/services/service_test.go b/ua/service_test.go
similarity index 87%
rename from services/service_test.go
rename to ua/service_test.go
index 24d3a94..080223a 100644
--- a/services/service_test.go
+++ b/ua/service_test.go
@@ -1,4 +1,4 @@
-package services
+package ua
 
 func flatten(b ...[]byte) []byte {
 	var x []byte
diff --git a/services/signature-data_test.go b/ua/signature-data_test.go
similarity index 85%
rename from services/signature-data_test.go
rename to ua/signature-data_test.go
index e996c03..39c4c7e 100644
--- a/services/signature-data_test.go
+++ b/ua/signature-data_test.go
@@ -2,16 +2,14 @@
 // Use of this source code is governed by a MIT-style license that can be
 // found in the LICENSE file.
 
-package services
+package ua
 
 import (
 	"testing"
-
-	"github.com/gopcua/opcua/utils/codectest"
 )
 
 func TestSignatureData(t *testing.T) {
-	cases := []codectest.Case{
+	cases := []CodecTestCase{
 		{
 			Name:   "empty",
 			Struct: NewSignatureData("", nil),
@@ -33,5 +31,5 @@ func TestSignatureData(t *testing.T) {
 			},
 		},
 	}
-	codectest.Run(t, cases)
+	RunCodecTest(t, cases)
 }
diff --git a/services/signed-software-certificate_test.go b/ua/signed-software-certificate_test.go
similarity index 90%
rename from services/signed-software-certificate_test.go
rename to ua/signed-software-certificate_test.go
index b66fe1d..b09ff53 100644
--- a/services/signed-software-certificate_test.go
+++ b/ua/signed-software-certificate_test.go
@@ -2,16 +2,14 @@
 // Use of this source code is governed by a MIT-style license that can be
 // found in the LICENSE file.
 
-package services
+package ua
 
 import (
 	"testing"
-
-	"github.com/gopcua/opcua/utils/codectest"
 )
 
 func TestSignedSoftwareCertificate(t *testing.T) {
-	cases := []codectest.Case{
+	cases := []CodecTestCase{
 		{
 			Name:   "empty",
 			Struct: NewSignedSoftwareCertificate(nil, nil),
@@ -33,11 +31,11 @@ func TestSignedSoftwareCertificate(t *testing.T) {
 			},
 		},
 	}
-	codectest.Run(t, cases)
+	RunCodecTest(t, cases)
 }
 
 func TestSignedSoftwareCertificateArray(t *testing.T) {
-	cases := []codectest.Case{
+	cases := []CodecTestCase{
 		{
 			Name:   "empty",
 			Struct: []*SignedSoftwareCertificate{},
@@ -66,5 +64,5 @@ func TestSignedSoftwareCertificateArray(t *testing.T) {
 			},
 		},
 	}
-	codectest.Run(t, cases)
+	RunCodecTest(t, cases)
 }
diff --git a/datatypes/uint32_test.go b/ua/uint32_test.go
similarity index 88%
rename from datatypes/uint32_test.go
rename to ua/uint32_test.go
index 0a9932d..b802588 100644
--- a/datatypes/uint32_test.go
+++ b/ua/uint32_test.go
@@ -2,16 +2,14 @@
 // Use of this source code is governed by a MIT-style license that can be
 // found in the LICENSE file.
 
-package datatypes
+package ua
 
 import (
 	"testing"
-
-	"github.com/gopcua/opcua/utils/codectest"
 )
 
 func TestUint32Array(t *testing.T) {
-	cases := []codectest.Case{
+	cases := []CodecTestCase{
 		{
 			Name:   "No contents",
 			Struct: []uint32{},
@@ -47,5 +45,5 @@ func TestUint32Array(t *testing.T) {
 			},
 		},
 	}
-	codectest.Run(t, cases)
+	RunCodecTest(t, cases)
 }
diff --git a/datatypes/user-identity-token_test.go b/ua/user-identity-token_test.go
similarity index 86%
rename from datatypes/user-identity-token_test.go
rename to ua/user-identity-token_test.go
index 60cdfac..154312c 100644
--- a/datatypes/user-identity-token_test.go
+++ b/ua/user-identity-token_test.go
@@ -2,16 +2,14 @@
 // Use of this source code is governed by a MIT-style license that can be
 // found in the LICENSE file.
 
-package datatypes
+package ua
 
 import (
 	"testing"
-
-	"github.com/gopcua/opcua/utils/codectest"
 )
 
 func TestAnonymousIdentityToken(t *testing.T) {
-	cases := []codectest.Case{
+	cases := []CodecTestCase{
 		{
 			Name:   "normal",
 			Struct: NewAnonymousIdentityToken("anonymous"),
@@ -21,11 +19,11 @@ func TestAnonymousIdentityToken(t *testing.T) {
 			},
 		},
 	}
-	codectest.Run(t, cases)
+	RunCodecTest(t, cases)
 }
 
 func TestUserNameIdentityToken(t *testing.T) {
-	cases := []codectest.Case{
+	cases := []CodecTestCase{
 		{
 			Name:   "normal",
 			Struct: NewUserNameIdentityToken("username", "user", []byte{0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64}, "plain"),
@@ -41,11 +39,11 @@ func TestUserNameIdentityToken(t *testing.T) {
 			},
 		},
 	}
-	codectest.Run(t, cases)
+	RunCodecTest(t, cases)
 }
 
 func TestX509IdentityToken(t *testing.T) {
-	cases := []codectest.Case{
+	cases := []CodecTestCase{
 		{
 			Name:   "normal",
 			Struct: NewX509IdentityToken("x509", "certificate"),
@@ -57,11 +55,11 @@ func TestX509IdentityToken(t *testing.T) {
 			},
 		},
 	}
-	codectest.Run(t, cases)
+	RunCodecTest(t, cases)
 }
 
 func TestIssuedIdentityToken(t *testing.T) {
-	cases := []codectest.Case{
+	cases := []CodecTestCase{
 		{
 			Name:   "normal",
 			Struct: NewIssuedIdentityToken("issued", []byte{0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64}, "plain"),
@@ -75,5 +73,5 @@ func TestIssuedIdentityToken(t *testing.T) {
 			},
 		},
 	}
-	codectest.Run(t, cases)
+	RunCodecTest(t, cases)
 }
diff --git a/services/user-token-policy_test.go b/ua/user-token-policy_test.go
similarity index 92%
rename from services/user-token-policy_test.go
rename to ua/user-token-policy_test.go
index e62cf47..a874749 100644
--- a/services/user-token-policy_test.go
+++ b/ua/user-token-policy_test.go
@@ -2,16 +2,14 @@
 // Use of this source code is governed by a MIT-style license that can be
 // found in the LICENSE file.
 
-package services
+package ua
 
 import (
 	"testing"
-
-	"github.com/gopcua/opcua/utils/codectest"
 )
 
 func TestUserTokenPolicy(t *testing.T) {
-	cases := []codectest.Case{
+	cases := []CodecTestCase{
 		{
 			Struct: NewUserTokenPolicy(
 				"1", UserTokenAnonymous,
@@ -31,11 +29,11 @@ func TestUserTokenPolicy(t *testing.T) {
 			},
 		},
 	}
-	codectest.Run(t, cases)
+	RunCodecTest(t, cases)
 }
 
 func TestUserTokenPolicyArray(t *testing.T) {
-	cases := []codectest.Case{
+	cases := []CodecTestCase{
 		{
 			Struct: []*UserTokenPolicy{
 				NewUserTokenPolicy(
@@ -73,5 +71,5 @@ func TestUserTokenPolicyArray(t *testing.T) {
 			},
 		},
 	}
-	codectest.Run(t, cases)
+	RunCodecTest(t, cases)
 }
diff --git a/datatypes/variant_test.go b/ua/variant_test.go
similarity index 98%
rename from datatypes/variant_test.go
rename to ua/variant_test.go
index f593977..5d4b053 100644
--- a/datatypes/variant_test.go
+++ b/ua/variant_test.go
@@ -2,17 +2,15 @@
 // Use of this source code is governed by a MIT-style license that can be
 // found in the LICENSE file.
 
-package datatypes
+package ua
 
 import (
 	"testing"
 	"time"
-
-	"github.com/gopcua/opcua/utils/codectest"
 )
 
 func TestVariant(t *testing.T) {
-	cases := []codectest.Case{
+	cases := []CodecTestCase{
 		{
 			Name:   "boolean",
 			Struct: MustVariant(false),
@@ -306,5 +304,5 @@ func TestVariant(t *testing.T) {
 			},
 		},
 	}
-	codectest.Run(t, cases)
+	RunCodecTest(t, cases)
 }
diff --git a/services/write-request_test.go b/ua/write-request_test.go
similarity index 82%
rename from services/write-request_test.go
rename to ua/write-request_test.go
index 138fe62..873e9c2 100644
--- a/services/write-request_test.go
+++ b/ua/write-request_test.go
@@ -2,36 +2,33 @@
 // Use of this source code is governed by a MIT-style license that can be
 // found in the LICENSE file.
 
-package services
+package ua
 
 import (
 	"testing"
 	"time"
-
-	"github.com/gopcua/opcua/datatypes"
-	"github.com/gopcua/opcua/utils/codectest"
 )
 
 func TestWriteRequest(t *testing.T) {
-	cases := []codectest.Case{
+	cases := []CodecTestCase{
 		{
 			Name: "single-writevalue",
 			Struct: NewWriteRequest(
 				NewRequestHeader(
-					datatypes.NewOpaqueNodeID(0x00, []byte{
+					NewOpaqueNodeID(0x00, []byte{
 						0x08, 0x22, 0x87, 0x62, 0xba, 0x81, 0xe1, 0x11,
 						0xa6, 0x43, 0xf8, 0x77, 0x7b, 0xc6, 0x2f, 0xc8,
 					}),
 					time.Date(2018, time.August, 10, 23, 0, 0, 0, time.UTC),
 					1, 0, 0, "", NewNullAdditionalHeader(),
 				),
-				datatypes.NewWriteValue(
-					datatypes.NewFourByteNodeID(0, 2256),
-					datatypes.IntegerIDValue,
+				NewWriteValue(
+					NewFourByteNodeID(0, 2256),
+					IntegerIDValue,
 					"",
-					datatypes.NewDataValue(
+					NewDataValue(
 						true, false, true, false, true, false,
-						datatypes.MustVariant(float32(2.50017)),
+						MustVariant(float32(2.50017)),
 						0,
 						time.Date(2018, time.September, 17, 14, 28, 29, 112000000, time.UTC),
 						0,
@@ -77,20 +74,20 @@ func TestWriteRequest(t *testing.T) {
 			Name: "multiple-writevalue",
 			Struct: NewWriteRequest(
 				NewRequestHeader(
-					datatypes.NewOpaqueNodeID(0x00, []byte{
+					NewOpaqueNodeID(0x00, []byte{
 						0x08, 0x22, 0x87, 0x62, 0xba, 0x81, 0xe1, 0x11,
 						0xa6, 0x43, 0xf8, 0x77, 0x7b, 0xc6, 0x2f, 0xc8,
 					}),
 					time.Date(2018, time.August, 10, 23, 0, 0, 0, time.UTC),
 					1, 0, 0, "", NewNullAdditionalHeader(),
 				),
-				datatypes.NewWriteValue(
-					datatypes.NewFourByteNodeID(0, 2256),
-					datatypes.IntegerIDValue,
+				NewWriteValue(
+					NewFourByteNodeID(0, 2256),
+					IntegerIDValue,
 					"",
-					datatypes.NewDataValue(
+					NewDataValue(
 						true, false, true, false, true, false,
-						datatypes.MustVariant(float32(2.50017)),
+						MustVariant(float32(2.50017)),
 						0,
 						time.Date(2018, time.September, 17, 14, 28, 29, 112000000, time.UTC),
 						0,
@@ -98,13 +95,13 @@ func TestWriteRequest(t *testing.T) {
 						0,
 					),
 				),
-				datatypes.NewWriteValue(
-					datatypes.NewFourByteNodeID(0, 2256),
-					datatypes.IntegerIDValue,
+				NewWriteValue(
+					NewFourByteNodeID(0, 2256),
+					IntegerIDValue,
 					"",
-					datatypes.NewDataValue(
+					NewDataValue(
 						true, false, true, false, true, false,
-						datatypes.MustVariant(float32(2.50017)),
+						MustVariant(float32(2.50017)),
 						0,
 						time.Date(2018, time.September, 17, 14, 28, 29, 112000000, time.UTC),
 						0,
@@ -157,5 +154,5 @@ func TestWriteRequest(t *testing.T) {
 			},
 		},
 	}
-	codectest.Run(t, cases)
+	RunCodecTest(t, cases)
 }
diff --git a/services/write-response_test.go b/ua/write-response_test.go
similarity index 83%
rename from services/write-response_test.go
rename to ua/write-response_test.go
index c162d0b..ebcbb7d 100644
--- a/services/write-response_test.go
+++ b/ua/write-response_test.go
@@ -2,25 +2,23 @@
 // Use of this source code is governed by a MIT-style license that can be
 // found in the LICENSE file.
 
-package services
+package ua
 
 import (
 	"testing"
 	"time"
 
-	"github.com/gopcua/opcua/datatypes"
 	"github.com/gopcua/opcua/status"
-	"github.com/gopcua/opcua/utils/codectest"
 )
 
 func TestWriteResponse(t *testing.T) {
-	cases := []codectest.Case{
+	cases := []CodecTestCase{
 		{
 			Name: "single-result",
 			Struct: NewWriteResponse(
 				NewResponseHeader(
 					time.Date(2018, time.August, 10, 23, 0, 0, 0, time.UTC),
-					1, 0, datatypes.NewNullDiagnosticInfo(), []string{}, NewNullAdditionalHeader(),
+					1, 0, NewNullDiagnosticInfo(), []string{}, NewNullAdditionalHeader(),
 				),
 				nil,
 				0,
@@ -50,7 +48,7 @@ func TestWriteResponse(t *testing.T) {
 			Struct: NewWriteResponse(
 				NewResponseHeader(
 					time.Date(2018, time.August, 10, 23, 0, 0, 0, time.UTC),
-					1, 0, datatypes.NewNullDiagnosticInfo(), []string{}, NewNullAdditionalHeader(),
+					1, 0, NewNullDiagnosticInfo(), []string{}, NewNullAdditionalHeader(),
 				),
 				nil,
 				0, status.BadUserAccessDenied,
@@ -77,5 +75,5 @@ func TestWriteResponse(t *testing.T) {
 			},
 		},
 	}
-	codectest.Run(t, cases)
+	RunCodecTest(t, cases)
 }
diff --git a/datatypes/write-value_test.go b/ua/write-value_test.go
similarity index 93%
rename from datatypes/write-value_test.go
rename to ua/write-value_test.go
index ece42ab..354a1f0 100644
--- a/datatypes/write-value_test.go
+++ b/ua/write-value_test.go
@@ -2,17 +2,15 @@
 // Use of this source code is governed by a MIT-style license that can be
 // found in the LICENSE file.
 
-package datatypes
+package ua
 
 import (
 	"testing"
 	"time"
-
-	"github.com/gopcua/opcua/utils/codectest"
 )
 
 func TestWriteValue(t *testing.T) {
-	cases := []codectest.Case{
+	cases := []CodecTestCase{
 		{
 			Name: "normal",
 			Struct: NewWriteValue(
@@ -44,11 +42,11 @@ func TestWriteValue(t *testing.T) {
 		},
 	}
 
-	codectest.Run(t, cases)
+	RunCodecTest(t, cases)
 }
 
 func TestWriteValueArray(t *testing.T) {
-	cases := []codectest.Case{
+	cases := []CodecTestCase{
 		{
 			Name: "normal",
 			Struct: []*WriteValue{
@@ -107,5 +105,5 @@ func TestWriteValueArray(t *testing.T) {
 			},
 		},
 	}
-	codectest.Run(t, cases)
+	RunCodecTest(t, cases)
 }
diff --git a/uacp/acknowledge_test.go b/uacp/acknowledge_test.go
index ce839ff..9ca14bc 100644
--- a/uacp/acknowledge_test.go
+++ b/uacp/acknowledge_test.go
@@ -7,11 +7,11 @@ package uacp
 import (
 	"testing"
 
-	"github.com/gopcua/opcua/utils/codectest"
+	"github.com/gopcua/opcua/ua"
 )
 
 func TestAcknowledge(t *testing.T) {
-	cases := []codectest.Case{
+	cases := []ua.CodecTestCase{
 		{
 			Struct: NewAcknowledge(
 				0,     //Version
@@ -33,5 +33,5 @@ func TestAcknowledge(t *testing.T) {
 			},
 		},
 	}
-	codectest.Run(t, cases)
+	ua.RunCodecTest(t, cases)
 }
diff --git a/uacp/error_test.go b/uacp/error_test.go
index 09c5491..33c0e63 100644
--- a/uacp/error_test.go
+++ b/uacp/error_test.go
@@ -7,11 +7,11 @@ package uacp
 import (
 	"testing"
 
-	"github.com/gopcua/opcua/utils/codectest"
+	"github.com/gopcua/opcua/ua"
 )
 
 func TestError(t *testing.T) {
-	cases := []codectest.Case{
+	cases := []ua.CodecTestCase{
 		{
 			Struct: NewError(
 				BadSecureChannelClosed, // Error
@@ -25,5 +25,5 @@ func TestError(t *testing.T) {
 			},
 		},
 	}
-	codectest.Run(t, cases)
+	ua.RunCodecTest(t, cases)
 }
diff --git a/uacp/generic_test.go b/uacp/generic_test.go
index e164814..591fa74 100644
--- a/uacp/generic_test.go
+++ b/uacp/generic_test.go
@@ -9,7 +9,7 @@ import (
 )
 
 func TestGeneric(t *testing.T) {
-	// cases := []codectest.Case{
+	// cases := []ua.CodecTestCase{
 	// 	{
 	// 		Struct: NewGeneric(
 	// 			"XXX",
@@ -23,5 +23,5 @@ func TestGeneric(t *testing.T) {
 	// 		},
 	// 	},
 	// }
-	// codectest.Run(t, cases)
+	// ua.RunCodecTest(t, cases)
 }
diff --git a/uacp/header_test.go b/uacp/header_test.go
index 168a6b4..1f16c04 100644
--- a/uacp/header_test.go
+++ b/uacp/header_test.go
@@ -7,7 +7,7 @@ package uacp
 // todo(fs): this test should be removed since the header is now sent in conn.go
 // todo(fs): and is no longer part of the message itself.
 // func TestHeader(t *testing.T) {
-// 	cases := []codectest.Case{
+// 	cases := []ua.CodecTestCase{
 // 		{
 // 			Struct: &Header{
 // 				MessageType: MessageTypeHello,
@@ -24,5 +24,5 @@ package uacp
 // 			},
 // 		},
 // 	}
-// 	codectest.Run(t, cases)
+// 	ua.RunCodecTest(t, cases)
 // }
diff --git a/uacp/hello_test.go b/uacp/hello_test.go
index b7b5c7e..d43a4e8 100644
--- a/uacp/hello_test.go
+++ b/uacp/hello_test.go
@@ -7,11 +7,11 @@ package uacp
 import (
 	"testing"
 
-	"github.com/gopcua/opcua/utils/codectest"
+	"github.com/gopcua/opcua/ua"
 )
 
 func TestHello(t *testing.T) {
-	cases := []codectest.Case{
+	cases := []ua.CodecTestCase{
 		{
 			Struct: NewHello(
 				0,                                        // Version
@@ -41,5 +41,5 @@ func TestHello(t *testing.T) {
 			},
 		},
 	}
-	codectest.Run(t, cases)
+	ua.RunCodecTest(t, cases)
 }
diff --git a/uacp/reverse-hello_test.go b/uacp/reverse-hello_test.go
index cfeda75..67a89fa 100644
--- a/uacp/reverse-hello_test.go
+++ b/uacp/reverse-hello_test.go
@@ -7,11 +7,11 @@ package uacp
 import (
 	"testing"
 
-	"github.com/gopcua/opcua/utils/codectest"
+	"github.com/gopcua/opcua/ua"
 )
 
 func TestReverseHello(t *testing.T) {
-	cases := []codectest.Case{
+	cases := []ua.CodecTestCase{
 		{
 			Name: "Normal",
 			Struct: NewReverseHello(
@@ -36,5 +36,5 @@ func TestReverseHello(t *testing.T) {
 			},
 		},
 	}
-	codectest.Run(t, cases)
+	ua.RunCodecTest(t, cases)
 }
diff --git a/uacp/uacp_test.go b/uacp/uacp_test.go
index 63cd1b5..2e8e70c 100644
--- a/uacp/uacp_test.go
+++ b/uacp/uacp_test.go
@@ -7,11 +7,11 @@ package uacp
 import (
 	"testing"
 
-	"github.com/gopcua/opcua/utils/codectest"
+	"github.com/gopcua/opcua/ua"
 )
 
 func TestUACPMessage(t *testing.T) {
-	cases := []codectest.Case{
+	cases := []ua.CodecTestCase{
 		{
 			Name: "Hello",
 			Struct: NewHello(
@@ -112,5 +112,5 @@ func TestUACPMessage(t *testing.T) {
 		// 	},
 		// },
 	}
-	codectest.Run(t, cases)
+	ua.RunCodecTest(t, cases)
 }
diff --git a/uasc/asymmetric-security-header_test.go b/uasc/asymmetric-security-header_test.go
index 3efa2b0..5c03015 100644
--- a/uasc/asymmetric-security-header_test.go
+++ b/uasc/asymmetric-security-header_test.go
@@ -7,11 +7,11 @@ package uasc
 import (
 	"testing"
 
-	"github.com/gopcua/opcua/utils/codectest"
+	"github.com/gopcua/opcua/ua"
 )
 
 func TestAsymmetricSecurityHeader(t *testing.T) {
-	cases := []codectest.Case{
+	cases := []ua.CodecTestCase{
 		{
 			Name: "normal",
 			Struct: NewAsymmetricSecurityHeader(
@@ -151,5 +151,5 @@ func TestAsymmetricSecurityHeader(t *testing.T) {
 			},
 		},
 	}
-	codectest.Run(t, cases)
+	ua.RunCodecTest(t, cases)
 }
diff --git a/uasc/header_test.go b/uasc/header_test.go
index 52af6e7..a969760 100644
--- a/uasc/header_test.go
+++ b/uasc/header_test.go
@@ -7,11 +7,11 @@ package uasc
 import (
 	"testing"
 
-	"github.com/gopcua/opcua/utils/codectest"
+	"github.com/gopcua/opcua/ua"
 )
 
 func TestHeader(t *testing.T) {
-	cases := []codectest.Case{
+	cases := []ua.CodecTestCase{
 		{
 			Name: "normal",
 			Struct: &Header{
@@ -32,5 +32,5 @@ func TestHeader(t *testing.T) {
 			},
 		},
 	}
-	codectest.Run(t, cases)
+	ua.RunCodecTest(t, cases)
 }
diff --git a/uasc/message_test.go b/uasc/message_test.go
index 6499dbc..f9ad8be 100644
--- a/uasc/message_test.go
+++ b/uasc/message_test.go
@@ -10,25 +10,23 @@ import (
 
 	"github.com/gopcua/opcua/id"
 
-	"github.com/gopcua/opcua/datatypes"
-	"github.com/gopcua/opcua/services"
-	"github.com/gopcua/opcua/utils/codectest"
+	"github.com/gopcua/opcua/ua"
 )
 
 func TestMessage(t *testing.T) {
-	cases := []codectest.Case{
+	cases := []ua.CodecTestCase{
 		{
 			Name: "OPN",
 			Struct: func() interface{} {
 				m := NewMessage(
-					services.NewOpenSecureChannelRequest(
-						services.NewRequestHeader(
-							datatypes.NewTwoByteNodeID(0),
+					ua.NewOpenSecureChannelRequest(
+						ua.NewRequestHeader(
+							ua.NewTwoByteNodeID(0),
 							time.Date(2018, time.August, 10, 23, 0, 0, 0, time.UTC),
-							1, 0x03ff, 0, "", services.NewNullAdditionalHeader(),
+							1, 0x03ff, 0, "", ua.NewNullAdditionalHeader(),
 						),
-						0, services.ReqTypeIssue,
-						services.SecModeNone, 6000000, nil,
+						0, ua.ReqTypeIssue,
+						ua.SecModeNone, 6000000, nil,
 					),
 					id.OpenSecureChannelRequest_Encoding_DefaultBinary,
 					&Config{
@@ -112,11 +110,11 @@ func TestMessage(t *testing.T) {
 			Name: "MSG",
 			Struct: func() interface{} {
 				m := NewMessage(
-					services.NewGetEndpointsRequest(
-						services.NewRequestHeader(
-							datatypes.NewTwoByteNodeID(0),
+					ua.NewGetEndpointsRequest(
+						ua.NewRequestHeader(
+							ua.NewTwoByteNodeID(0),
 							time.Date(2018, time.August, 10, 23, 0, 0, 0, time.UTC),
-							1, 0x03ff, 0, "", services.NewNullAdditionalHeader(),
+							1, 0x03ff, 0, "", ua.NewNullAdditionalHeader(),
 						),
 						"opc.tcp://wow.its.easy:11111/UA/Server",
 						nil, nil,
@@ -178,11 +176,11 @@ func TestMessage(t *testing.T) {
 			Name: "CLO",
 			Struct: func() interface{} {
 				m := NewMessage(
-					services.NewCloseSecureChannelRequest(
-						services.NewRequestHeader(
-							datatypes.NewTwoByteNodeID(0),
+					ua.NewCloseSecureChannelRequest(
+						ua.NewRequestHeader(
+							ua.NewTwoByteNodeID(0),
 							time.Date(2018, time.August, 10, 23, 0, 0, 0, time.UTC),
-							1, 0x03ff, 0, "", services.NewNullAdditionalHeader(),
+							1, 0x03ff, 0, "", ua.NewNullAdditionalHeader(),
 						),
 						1,
 					),
@@ -232,5 +230,5 @@ func TestMessage(t *testing.T) {
 			},
 		},
 	}
-	codectest.Run(t, cases)
+	ua.RunCodecTest(t, cases)
 }
diff --git a/uasc/sequence-header_test.go b/uasc/sequence-header_test.go
index 5bf14a9..61cc881 100644
--- a/uasc/sequence-header_test.go
+++ b/uasc/sequence-header_test.go
@@ -7,11 +7,11 @@ package uasc
 import (
 	"testing"
 
-	"github.com/gopcua/opcua/utils/codectest"
+	"github.com/gopcua/opcua/ua"
 )
 
 func TestSequenceHeader(t *testing.T) {
-	cases := []codectest.Case{
+	cases := []ua.CodecTestCase{
 		{
 			Name: "normal",
 			Struct: NewSequenceHeader(
@@ -39,5 +39,5 @@ func TestSequenceHeader(t *testing.T) {
 			},
 		},
 	}
-	codectest.Run(t, cases)
+	ua.RunCodecTest(t, cases)
 }
diff --git a/uasc/symmetric-security-header_test.go b/uasc/symmetric-security-header_test.go
index b0d95eb..752060d 100644
--- a/uasc/symmetric-security-header_test.go
+++ b/uasc/symmetric-security-header_test.go
@@ -7,11 +7,11 @@ package uasc
 import (
 	"testing"
 
-	"github.com/gopcua/opcua/utils/codectest"
+	"github.com/gopcua/opcua/ua"
 )
 
 func TestSymmetricSecurityHeader(t *testing.T) {
-	cases := []codectest.Case{
+	cases := []ua.CodecTestCase{
 		{
 			Name: "normal",
 			Struct: NewSymmetricSecurityHeader(
@@ -32,5 +32,5 @@ func TestSymmetricSecurityHeader(t *testing.T) {
 			},
 		},
 	}
-	codectest.Run(t, cases)
+	ua.RunCodecTest(t, cases)
 }