New Upstream Release - golang-gopkg-jcmturner-rpc.v0

Ready changes

Summary

Merged new upstream version: 2.0.3 (was: 0.0.2).

Resulting package

Built on 2023-01-27T13:48 (took 3m59s)

The resulting binary packages can be installed (if you have the apt repository enabled) by running one of:

apt install -t fresh-releases golang-gopkg-jcmturner-rpc.v0-dev

Lintian Result

Diff

diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml
new file mode 100644
index 0000000..9a4e68b
--- /dev/null
+++ b/.github/workflows/testing.yml
@@ -0,0 +1,67 @@
+name: v1
+on:
+  push:
+    paths-ignore:
+      - 'v[0-9]+/**'
+  pull_request:
+    paths-ignore:
+      - 'v[0-9]+/**'
+
+jobs:
+  build:
+    name: Tests
+    runs-on: ubuntu-latest
+    strategy:
+      matrix:
+        go: [ '1.13.x', '1.14.x', '1.15.x' ]
+    steps:
+      - name: Set up Go ${{ matrix.go }}
+        uses: actions/setup-go@v1
+        with:
+          go-version: ${{ matrix.go }}
+
+      - name: Checkout
+        uses: actions/checkout@v2
+        with:
+          ref: ${{ github.ref }}
+
+      - name: Test well formatted with gofmt
+        run: |
+          # Remove major version sub directories
+          find . -maxdepth 1 -type d -regex '\./v[0-9]+' | xargs -i rm -rf {}
+          GO_FILES=$(find . -iname '*.go' -type f | grep -v /vendor/)
+          test -z $(gofmt -s -d -l -e $GO_FILES | tee /dev/fd/2 | xargs | sed 's/\s//g')
+        id: gofmt
+
+      - name: Copy into GOPATH
+        run: |
+          # Default GOPATH=${HOME}/go
+          mkdir -p ${HOME}/go/src/github.com/${GITHUB_REPOSITORY}
+          cp -r $GITHUB_WORKSPACE/*  /home/runner/go/src/github.com/${GITHUB_REPOSITORY}
+        id: copyToGOPATH
+
+      - name: Get dependencies
+        run: |
+          cd ${HOME}/go/src/github.com/${GITHUB_REPOSITORY}
+          go get -v -t -d ./...
+        id: goGet
+
+      - name: Go vet
+        run: |
+          cd ${HOME}/go/src/github.com/${GITHUB_REPOSITORY}
+          go vet $(go list ./... | grep -E -v '/v[0-9]+' | grep -v /vendor/)
+        id: govet
+
+      - name: Unit tests
+        run: |
+          cd ${HOME}/go/src/github.com/${GITHUB_REPOSITORY}
+          go test -race $(go list ./... | grep -E -v '/v[0-9]+' | grep -v /vendor/)
+        id: unitTests
+
+      - name: Unit tests (32bit)
+        run: |
+          cd ${HOME}/go/src/github.com/${GITHUB_REPOSITORY}
+          go test $(go list ./... | grep -E -v '/v[0-9]+' | grep -v /vendor/)
+        env:
+          GOARCH: 386
+        id: unitTest32
\ No newline at end of file
diff --git a/.github/workflows/testingv2.yml b/.github/workflows/testingv2.yml
new file mode 100644
index 0000000..cb55bb4
--- /dev/null
+++ b/.github/workflows/testingv2.yml
@@ -0,0 +1,53 @@
+# Name of the workflow needs to match the name of the major version directory
+name: v2
+on:
+  push:
+    paths:
+      - 'v2/**'
+  pull_request:
+    paths:
+      - 'v2/**'
+
+jobs:
+  build:
+    name: Tests
+    runs-on: ubuntu-latest
+    strategy:
+      matrix:
+        go: [ '1.13.x', '1.14.x', '1.15.x' ]
+    steps:
+      - name: Set up Go ${{ matrix.go }}
+        uses: actions/setup-go@v1
+        with:
+          go-version: ${{ matrix.go }}
+
+      - name: Checkout
+        uses: actions/checkout@v2
+        with:
+          ref: ${{ github.ref }}
+
+      - name: Test well formatted with gofmt
+        run: |
+          GO_FILES=$(find ${GITHUB_WORKFLOW} -iname '*.go' -type f | grep -v /vendor/)
+          test -z $(gofmt -s -d -l -e $GO_FILES | tee /dev/fd/2 | xargs | sed 's/\s//g')
+        id: gofmt
+
+      - name: Go vet
+        run: |
+          cd ${GITHUB_WORKFLOW}
+          go vet ./...
+        id: govet
+
+      - name: Unit tests
+        run: |
+          cd ${GITHUB_WORKFLOW}
+          go test -race ./...
+        id: unitTests
+
+      - name: Unit tests (32bit)
+        run: |
+          cd ${GITHUB_WORKFLOW}
+          go test ./...
+        env:
+          GOARCH: 386
+        id: unitTest32
\ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index bb68b93..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,22 +0,0 @@
-language: go
-
-go:
-- 1.7.x
-- 1.8.x
-- 1.9.x
-- 1.10.x
-- master
-
-go_import_path: gopkg.in/jcmturner/rpc.v0
-
-before_script:
-- GO_FILES=$(find . -iname '*.go' -type f | grep -v /vendor/)
-
-script:
-- test -z $(gofmt -s -d -l -e $GO_FILES | tee /dev/fd/2 | xargs | sed 's/\s//g') # Fail if a .go file hasn't been formatted with gofmt
-- go vet ./...                             # go vet is the official Go static analyzer
-#- golint -set_exit_status $(go list ./...) # golint to be added
-- go test -v -race -tags="integration" ./... # Run all the tests with the race detector enabled and integration tests
-- GOARCH=386 go test -v -tags="integration" ./... # 32bit testing
-
-
diff --git a/README.md b/README.md
index da32796..43d30ff 100644
--- a/README.md
+++ b/README.md
@@ -1,16 +1,90 @@
 # RPC
-[![GoDoc](https://godoc.org/gopkg.in/jcmturner/rpc.v0?status.svg)](https://godoc.org/gopkg.in/jcmturner/rpc.v0) [![Go Report Card](https://goreportcard.com/badge/gopkg.in/jcmturner/rpc.v0)](https://goreportcard.com/report/gopkg.in/jcmturner/rpc.v0) [![Build Status](https://travis-ci.org/jcmturner/rpc.svg?branch=master)](https://travis-ci.org/jcmturner/rpc)
 
+This project has now been converted to use Go modules. Please refer to the latest major version sub directory.
+This follows the practice outlines at https://blog.golang.org/v2-go-modules
+
+[![Version](https://img.shields.io/github/v/release/jcmturner/rpc?label=Version&sort=semver)](https://github.com/jcmturner/rpc/releases)
 
-This project relates to [CDE 1.1: Remote Procedure Call](http://pubs.opengroup.org/onlinepubs/9629399/)
 
-It is a partial implementation that mainly focuses on marshaling NDR encoded byte streams into Go structures.
 
-## Unstable API
-Currently this library is at a v0 status to reflect there will be breaking changes in the API without major version revisions.
-Please consider this if you adopt this library in your project.
+This project relates to [CDE 1.1: Remote Procedure Call](http://pubs.opengroup.org/onlinepubs/9629399/)
+
+It is a partial implementation that mainly focuses on unmarshaling NDR encoded byte streams into Go structures.
 
 ## Help Wanted
-* Reference test vectors needed: It has been difficult to implement due to a lack of reference test byte streams in the 
+**Reference test vectors needed**: It has been difficult to implement due to a lack of reference test byte streams in the 
 standards documentation. Test driven development has been extremely challenging without these.
-If you are aware of and reference test vector sources for NDR encoding please let me know by raising an issue with the details. Thanks!
\ No newline at end of file
+If you are aware of and reference test vector sources for NDR encoding please let me know by raising an issue with the details. Thanks!
+
+## References
+* [Open Group RPC Publication](http://pubs.opengroup.org/onlinepubs/9629399/)
+* [Microsoft RPC Documentation](https://docs.microsoft.com/en-us/windows/desktop/Rpc/rpc-start-page)
+
+## NDR Decode Capability Checklist
+- [x] Format label
+- [x] Boolean
+- [x] Character
+- [x] Unsigned small integer
+- [x] Unsigned short integer
+- [x] Unsigned long integer
+- [x] Unsigned hyper integer
+- [x] Signed small integer
+- [x] Signed short integer
+- [x] Signed long integer
+- [x] Signed hyper integer
+- [x] Single float
+- [x] Double float
+- [x] Uni-dimensional fixed array
+- [x] Multi-dimensional fixed array
+- [x] Uni-dimensional conformant array
+- [x] Multi-dimensional conformant array
+- [x] Uni-dimensional conformant varying array
+- [x] Multi-dimensional conformant varying array
+- [x] Varying string
+- [x] Conformant varying string
+- [x] Array of strings
+- [x] Union
+- [x] Pipe
+
+## Structs from IDL
+[Interface Definition Language (IDL)](http://pubs.opengroup.org/onlinepubs/9629399/chap4.htm)
+
+### Is a field a pointer?
+
+### Is an array conformant and/or varying?
+An array is conformant if the IDL definition includes one of the following attributes:
+* min_is
+* max_is
+* size_is
+
+An array is varying if the IDL definition includes one of the following attributes: 
+* last_is
+* first_is 
+* length_is
+
+#### Examples:
+SubAuthority[] is conformant in the example below:
+```
+ typedef struct _RPC_SID {
+   unsigned char Revision;
+   unsigned char SubAuthorityCount;
+   RPC_SID_IDENTIFIER_AUTHORITY IdentifierAuthority;
+   [size_is(SubAuthorityCount)] unsigned long SubAuthority[];
+ } RPC_SID,
+  *PRPC_SID,
+  *PSID;
+```
+
+Buffer is a pointer to a conformant varying array in the example below:
+```
+ typedef struct _RPC_UNICODE_STRING {
+   unsigned short Length;
+   unsigned short MaximumLength;
+   [size_is(MaximumLength/2), length_is(Length/2)] 
+     WCHAR* Buffer;
+ } RPC_UNICODE_STRING,
+  *PRPC_UNICODE_STRING;
+```
+
+### Is a union encapsulated?
+
diff --git a/debian/changelog b/debian/changelog
index 7279ea7..491ed16 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+golang-gopkg-jcmturner-rpc.v0 (2.0.3-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Debian Janitor <janitor@jelmer.uk>  Fri, 27 Jan 2023 13:45:03 -0000
+
 golang-gopkg-jcmturner-rpc.v0 (0.0.2-2) unstable; urgency=medium
 
   * Reupload as source-only
diff --git a/examples/examples.go b/examples/examples.go
new file mode 100644
index 0000000..3ed9cac
--- /dev/null
+++ b/examples/examples.go
@@ -0,0 +1,43 @@
+// Package examples provides example decoding of NDR byte streams
+package examples
+
+import "github.com/jcmturner/rpc/mstypes"
+
+// KerbValidationInfo
+type KerbValidationInfo struct {
+	LogOnTime              mstypes.FileTime
+	LogOffTime             mstypes.FileTime
+	KickOffTime            mstypes.FileTime
+	PasswordLastSet        mstypes.FileTime
+	PasswordCanChange      mstypes.FileTime
+	PasswordMustChange     mstypes.FileTime
+	EffectiveName          mstypes.RPCUnicodeString
+	FullName               mstypes.RPCUnicodeString
+	LogonScript            mstypes.RPCUnicodeString
+	ProfilePath            mstypes.RPCUnicodeString
+	HomeDirectory          mstypes.RPCUnicodeString
+	HomeDirectoryDrive     mstypes.RPCUnicodeString
+	LogonCount             uint16
+	BadPasswordCount       uint16
+	UserID                 uint32
+	PrimaryGroupID         uint32
+	GroupCount             uint32
+	GroupIDs               []mstypes.GroupMembership `ndr:"pointer,conformant"`
+	UserFlags              uint32
+	UserSessionKey         mstypes.UserSessionKey
+	LogonServer            mstypes.RPCUnicodeString
+	LogonDomainName        mstypes.RPCUnicodeString
+	LogonDomainID          mstypes.RPCSID `ndr:"pointer"`
+	Reserved1              [2]uint32      // Has 2 elements
+	UserAccountControl     uint32
+	SubAuthStatus          uint32
+	LastSuccessfulILogon   mstypes.FileTime
+	LastFailedILogon       mstypes.FileTime
+	FailedILogonCount      uint32
+	Reserved3              uint32
+	SIDCount               uint32
+	ExtraSIDs              []mstypes.KerbSidAndAttributes `ndr:"pointer,conformant"`
+	ResourceGroupDomainSID mstypes.RPCSID                 `ndr:"pointer"`
+	ResourceGroupCount     uint32
+	ResourceGroupIDs       []mstypes.GroupMembership `ndr:"pointer,conformant"`
+}
diff --git a/examples/examples_test.go b/examples/examples_test.go
new file mode 100644
index 0000000..eb3df37
--- /dev/null
+++ b/examples/examples_test.go
@@ -0,0 +1,266 @@
+package examples
+
+import (
+	"bytes"
+	"encoding/hex"
+	"testing"
+	"time"
+
+	"github.com/jcmturner/rpc/mstypes"
+	"github.com/jcmturner/rpc/ndr"
+	"github.com/stretchr/testify/assert"
+)
+
+const (
+	KerbValidationInfoMS     = "01100800cccccccca00400000000000000000200d186660f656ac601ffffffffffffff7fffffffffffffff7f17d439fe784ac6011794a328424bc601175424977a81c60108000800040002002400240008000200120012000c0002000000000010000200000000001400020000000000180002005410000097792c00010200001a0000001c000200200000000000000000000000000000000000000016001800200002000a000c002400020028000200000000000000000010000000000000000000000000000000000000000000000000000000000000000d0000002c0002000000000000000000000000000400000000000000040000006c007a00680075001200000000000000120000004c0069007100690061006e00670028004c006100720072007900290020005a00680075000900000000000000090000006e0074006400730032002e0062006100740000000000000000000000000000000000000000000000000000000000000000000000000000001a00000061c433000700000009c32d00070000005eb4320007000000010200000700000097b92c00070000002bf1320007000000ce30330007000000a72e2e00070000002af132000700000098b92c000700000062c4330007000000940133000700000076c4330007000000aefe2d000700000032d22c00070000001608320007000000425b2e00070000005fb4320007000000ca9c35000700000085442d0007000000c2f0320007000000e9ea310007000000ed8e2e0007000000b6eb310007000000ab2e2e0007000000720e2e00070000000c000000000000000b0000004e0054004400450056002d00440043002d003000350000000600000000000000050000004e0054004400450056000000040000000104000000000005150000005951b81766725d2564633b0b0d0000003000020007000000340002000700002038000200070000203c000200070000204000020007000020440002000700002048000200070000204c000200070000205000020007000020540002000700002058000200070000205c00020007000020600002000700002005000000010500000000000515000000b9301b2eb7414c6c8c3b351501020000050000000105000000000005150000005951b81766725d2564633b0b74542f00050000000105000000000005150000005951b81766725d2564633b0be8383200050000000105000000000005150000005951b81766725d2564633b0bcd383200050000000105000000000005150000005951b81766725d2564633b0b5db43200050000000105000000000005150000005951b81766725d2564633b0b41163500050000000105000000000005150000005951b81766725d2564633b0be8ea3100050000000105000000000005150000005951b81766725d2564633b0bc1193200050000000105000000000005150000005951b81766725d2564633b0b29f13200050000000105000000000005150000005951b81766725d2564633b0b0f5f2e00050000000105000000000005150000005951b81766725d2564633b0b2f5b2e00050000000105000000000005150000005951b81766725d2564633b0bef8f3100050000000105000000000005150000005951b81766725d2564633b0b075f2e0000000000"
+	KerbValidationInfoGoKRB5 = "01100800cccccccc180200000000000000000200058e4fdd80c6d201ffffffffffffff7fffffffffffffff7fcc27969c39c6d201cce7ffc602c7d201ffffffffffffff7f12001200040002001600160008000200000000000c000200000000001000020000000000140002000000000018000200d80000005104000001020000050000001c000200200000000000000000000000000000000000000008000a002000020008000a00240002002800020000000000000000001002000000000000000000000000000000000000000000000000000000000000020000002c00020000000000000000000000000009000000000000000900000074006500730074007500730065007200310000000b000000000000000b000000540065007300740031002000550073006500720031000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000050000000102000007000000540400000700000055040000070000005b040000070000005c0400000700000005000000000000000400000041004400440043000500000000000000040000005400450053005400040000000104000000000005150000004c86cebca07160e63fdce8870200000030000200070000203400020007000020050000000105000000000005150000004c86cebca07160e63fdce8875a040000050000000105000000000005150000004c86cebca07160e63fdce8875704000000000000"
+	KerbValidationInfoTrust  = "01100800cccccccc000200000000000000000200c30bcc79e444d301ffffffffffffff7fffffffffffffff7fc764125a0842d301c7247c84d142d301ffffffffffffff7f12001200040002001600160008000200000000000c0002000000000010000200000000001400020000000000180002002e0000005204000001020000030000001c0002002002000000000000000000000000000000000000060008002000020008000a00240002002800020000000000000000001002000000000000000000000000000000000000000000000000000000000000010000002c00020034000200020000003800020009000000000000000900000074006500730074007500730065007200310000000b000000000000000b0000005400650073007400310020005500730065007200310000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000056040000070000000102000007000000550400000700000004000000000000000300000055004400430000000500000000000000040000005500530045005200040000000104000000000005150000002057308834e7d1d0a2fb0444010000003000020007000000010000000101000000000012010000000400000001040000000000051500000062dc8db6c8705249b5459e75020000005304000007000020540400000700002000000000"
+)
+
+func TestExample_KerbValidationInfo(t *testing.T) {
+	b, _ := hex.DecodeString(KerbValidationInfoMS)
+	k := new(KerbValidationInfo)
+	dec := ndr.NewDecoder(bytes.NewReader(b))
+	err := dec.Decode(k)
+	if err != nil {
+		t.Errorf("%v", err)
+	}
+	assert.Equal(t, time.Date(2006, 4, 28, 1, 42, 50, 925640100, time.UTC), k.LogOnTime.Time(), "LogOnTime not as expected")
+	assert.Equal(t, time.Date(2185, 7, 21, 23, 34, 33, 709551516, time.UTC), k.LogOffTime.Time(), "LogOffTime not as expected")
+	assert.Equal(t, time.Date(2185, 7, 21, 23, 34, 33, 709551516, time.UTC), k.KickOffTime.Time(), "KickOffTime not as expected")
+	assert.Equal(t, time.Date(2006, 3, 18, 10, 44, 54, 837147900, time.UTC), k.PasswordLastSet.Time(), "PasswordLastSet not as expected")
+	assert.Equal(t, time.Date(2006, 3, 19, 10, 44, 54, 837147900, time.UTC), k.PasswordCanChange.Time(), "PasswordCanChange not as expected")
+
+	assert.Equal(t, "lzhu", k.EffectiveName.String(), "EffectiveName not as expected")
+	assert.Equal(t, "Liqiang(Larry) Zhu", k.FullName.String(), "EffectiveName not as expected")
+	assert.Equal(t, "ntds2.bat", k.LogonScript.String(), "EffectiveName not as expected")
+	assert.Equal(t, "", k.ProfilePath.String(), "EffectiveName not as expected")
+	assert.Equal(t, "", k.HomeDirectory.String(), "EffectiveName not as expected")
+	assert.Equal(t, "", k.HomeDirectoryDrive.String(), "EffectiveName not as expected")
+
+	assert.Equal(t, uint16(4180), k.LogonCount, "LogonCount not as expected")
+	assert.Equal(t, uint16(0), k.BadPasswordCount, "BadPasswordCount not as expected")
+	assert.Equal(t, uint32(2914711), k.UserID, "UserID not as expected")
+	assert.Equal(t, uint32(513), k.PrimaryGroupID, "PrimaryGroupID not as expected")
+	assert.Equal(t, uint32(26), k.GroupCount, "GroupCount not as expected")
+
+	gids := []mstypes.GroupMembership{
+		{RelativeID: 3392609, Attributes: 7},
+		{RelativeID: 2999049, Attributes: 7},
+		{RelativeID: 3322974, Attributes: 7},
+		{RelativeID: 513, Attributes: 7},
+		{RelativeID: 2931095, Attributes: 7},
+		{RelativeID: 3338539, Attributes: 7},
+		{RelativeID: 3354830, Attributes: 7},
+		{RelativeID: 3026599, Attributes: 7},
+		{RelativeID: 3338538, Attributes: 7},
+		{RelativeID: 2931096, Attributes: 7},
+		{RelativeID: 3392610, Attributes: 7},
+		{RelativeID: 3342740, Attributes: 7},
+		{RelativeID: 3392630, Attributes: 7},
+		{RelativeID: 3014318, Attributes: 7},
+		{RelativeID: 2937394, Attributes: 7},
+		{RelativeID: 3278870, Attributes: 7},
+		{RelativeID: 3038018, Attributes: 7},
+		{RelativeID: 3322975, Attributes: 7},
+		{RelativeID: 3513546, Attributes: 7},
+		{RelativeID: 2966661, Attributes: 7},
+		{RelativeID: 3338434, Attributes: 7},
+		{RelativeID: 3271401, Attributes: 7},
+		{RelativeID: 3051245, Attributes: 7},
+		{RelativeID: 3271606, Attributes: 7},
+		{RelativeID: 3026603, Attributes: 7},
+		{RelativeID: 3018354, Attributes: 7},
+	}
+	assert.Equal(t, gids, k.GroupIDs, "GroupIDs not as expected")
+
+	assert.Equal(t, uint32(32), k.UserFlags, "UserFlags not as expected")
+
+	assert.Equal(t, mstypes.UserSessionKey{CypherBlock: [2]mstypes.CypherBlock{{Data: [8]byte{}}, {Data: [8]byte{}}}}, k.UserSessionKey, "UserSessionKey not as expected")
+
+	assert.Equal(t, "NTDEV-DC-05", k.LogonServer.Value, "LogonServer not as expected")
+	assert.Equal(t, "NTDEV", k.LogonDomainName.Value, "LogonDomainName not as expected")
+
+	assert.Equal(t, "S-1-5-21-397955417-626881126-188441444", k.LogonDomainID.String(), "LogonDomainID not as expected")
+
+	assert.Equal(t, uint32(16), k.UserAccountControl, "UserAccountControl not as expected")
+	assert.Equal(t, uint32(0), k.SubAuthStatus, "SubAuthStatus not as expected")
+	assert.Equal(t, time.Date(2185, 7, 21, 23, 34, 33, 709551616, time.UTC), k.LastSuccessfulILogon.Time(), "LastSuccessfulILogon not as expected")
+	assert.Equal(t, time.Date(2185, 7, 21, 23, 34, 33, 709551616, time.UTC), k.LastFailedILogon.Time(), "LastSuccessfulILogon not as expected")
+	assert.Equal(t, uint32(0), k.FailedILogonCount, "FailedILogonCount not as expected")
+
+	assert.Equal(t, uint32(13), k.SIDCount, "SIDCount not as expected")
+	assert.Equal(t, int(k.SIDCount), len(k.ExtraSIDs), "SIDCount and size of ExtraSIDs list are not the same")
+
+	var es = []struct {
+		sid  string
+		attr uint32
+	}{
+		{"S-1-5-21-773533881-1816936887-355810188-513", uint32(7)},
+		{"S-1-5-21-397955417-626881126-188441444-3101812", uint32(536870919)},
+		{"S-1-5-21-397955417-626881126-188441444-3291368", uint32(536870919)},
+		{"S-1-5-21-397955417-626881126-188441444-3291341", uint32(536870919)},
+		{"S-1-5-21-397955417-626881126-188441444-3322973", uint32(536870919)},
+		{"S-1-5-21-397955417-626881126-188441444-3479105", uint32(536870919)},
+		{"S-1-5-21-397955417-626881126-188441444-3271400", uint32(536870919)},
+		{"S-1-5-21-397955417-626881126-188441444-3283393", uint32(536870919)},
+		{"S-1-5-21-397955417-626881126-188441444-3338537", uint32(536870919)},
+		{"S-1-5-21-397955417-626881126-188441444-3038991", uint32(536870919)},
+		{"S-1-5-21-397955417-626881126-188441444-3037999", uint32(536870919)},
+		{"S-1-5-21-397955417-626881126-188441444-3248111", uint32(536870919)},
+	}
+	for i, s := range es {
+		assert.Equal(t, s.sid, k.ExtraSIDs[i].SID.String(), "ExtraSID SID value not as epxected")
+		assert.Equal(t, s.attr, k.ExtraSIDs[i].Attributes, "ExtraSID Attributes value not as epxected")
+	}
+
+	assert.Equal(t, uint8(0), k.ResourceGroupDomainSID.SubAuthorityCount, "ResourceGroupDomainSID not as expected")
+	assert.Equal(t, 0, len(k.ResourceGroupIDs), "ResourceGroupIDs not as expected")
+
+	b, _ = hex.DecodeString(KerbValidationInfoGoKRB5)
+	k2 := new(KerbValidationInfo)
+	dec = ndr.NewDecoder(bytes.NewReader(b))
+	err = dec.Decode(k2)
+	if err != nil {
+		t.Errorf("%v", err)
+	}
+
+	assert.Equal(t, time.Date(2017, 5, 6, 15, 53, 11, 825766900, time.UTC), k2.LogOnTime.Time(), "LogOnTime not as expected")
+	assert.Equal(t, time.Date(2185, 7, 21, 23, 34, 33, 709551516, time.UTC), k2.LogOffTime.Time(), "LogOffTime not as expected")
+	assert.Equal(t, time.Date(2185, 7, 21, 23, 34, 33, 709551516, time.UTC), k2.KickOffTime.Time(), "KickOffTime not as expected")
+	assert.Equal(t, time.Date(2017, 5, 6, 7, 23, 8, 968750000, time.UTC), k2.PasswordLastSet.Time(), "PasswordLastSet not as expected")
+	assert.Equal(t, time.Date(2017, 5, 7, 7, 23, 8, 968750000, time.UTC), k2.PasswordCanChange.Time(), "PasswordCanChange not as expected")
+
+	assert.Equal(t, "testuser1", k2.EffectiveName.String(), "EffectiveName not as expected")
+	assert.Equal(t, "Test1 User1", k2.FullName.String(), "EffectiveName not as expected")
+	assert.Equal(t, "", k2.LogonScript.String(), "EffectiveName not as expected")
+	assert.Equal(t, "", k2.ProfilePath.String(), "EffectiveName not as expected")
+	assert.Equal(t, "", k2.HomeDirectory.String(), "EffectiveName not as expected")
+	assert.Equal(t, "", k2.HomeDirectoryDrive.String(), "EffectiveName not as expected")
+
+	assert.Equal(t, uint16(216), k2.LogonCount, "LogonCount not as expected")
+	assert.Equal(t, uint16(0), k2.BadPasswordCount, "BadPasswordCount not as expected")
+	assert.Equal(t, uint32(1105), k2.UserID, "UserID not as expected")
+	assert.Equal(t, uint32(513), k2.PrimaryGroupID, "PrimaryGroupID not as expected")
+	assert.Equal(t, uint32(5), k2.GroupCount, "GroupCount not as expected")
+
+	gids = []mstypes.GroupMembership{
+		{RelativeID: 513, Attributes: 7},
+		{RelativeID: 1108, Attributes: 7},
+		{RelativeID: 1109, Attributes: 7},
+		{RelativeID: 1115, Attributes: 7},
+		{RelativeID: 1116, Attributes: 7},
+	}
+	assert.Equal(t, gids, k2.GroupIDs, "GroupIDs not as expected")
+
+	assert.Equal(t, uint32(32), k2.UserFlags, "UserFlags not as expected")
+
+	assert.Equal(t, mstypes.UserSessionKey{CypherBlock: [2]mstypes.CypherBlock{{Data: [8]byte{}}, {Data: [8]byte{}}}}, k2.UserSessionKey, "UserSessionKey not as expected")
+
+	assert.Equal(t, "ADDC", k2.LogonServer.String(), "LogonServer not as expected")
+	assert.Equal(t, "TEST", k2.LogonDomainName.String(), "LogonDomainName not as expected")
+
+	assert.Equal(t, "S-1-5-21-3167651404-3865080224-2280184895", k2.LogonDomainID.String(), "LogonDomainID not as expected")
+
+	assert.Equal(t, uint32(528), k2.UserAccountControl, "UserAccountControl not as expected")
+	assert.Equal(t, uint32(0), k2.SubAuthStatus, "SubAuthStatus not as expected")
+	assert.Equal(t, time.Date(2185, 7, 21, 23, 34, 33, 709551616, time.UTC), k2.LastSuccessfulILogon.Time(), "LastSuccessfulILogon not as expected")
+	assert.Equal(t, time.Date(2185, 7, 21, 23, 34, 33, 709551616, time.UTC), k2.LastFailedILogon.Time(), "LastSuccessfulILogon not as expected")
+	assert.Equal(t, uint32(0), k2.FailedILogonCount, "FailedILogonCount not as expected")
+
+	assert.Equal(t, uint32(2), k2.SIDCount, "SIDCount not as expected")
+	assert.Equal(t, int(k2.SIDCount), len(k2.ExtraSIDs), "SIDCount and size of ExtraSIDs list are not the same")
+
+	var es2 = []struct {
+		sid  string
+		attr uint32
+	}{
+		{"S-1-5-21-3167651404-3865080224-2280184895-1114", uint32(536870919)},
+		{"S-1-5-21-3167651404-3865080224-2280184895-1111", uint32(536870919)},
+	}
+	for i, s := range es2 {
+		assert.Equal(t, s.sid, k2.ExtraSIDs[i].SID.String(), "ExtraSID SID value not as expected")
+		assert.Equal(t, s.attr, k2.ExtraSIDs[i].Attributes, "ExtraSID Attributes value not as expected")
+	}
+
+	assert.Equal(t, uint8(0), k2.ResourceGroupDomainSID.SubAuthorityCount, "ResourceGroupDomainSID not as expected")
+	assert.Equal(t, 0, len(k2.ResourceGroupIDs), "ResourceGroupIDs not as expected")
+
+	b, _ = hex.DecodeString(KerbValidationInfoTrust)
+	k = new(KerbValidationInfo)
+	dec = ndr.NewDecoder(bytes.NewReader(b))
+	err = dec.Decode(k)
+	if err != nil {
+		t.Errorf("%v", err)
+	}
+	assert.Equal(t, time.Date(2017, 10, 14, 12, 03, 41, 52409900, time.UTC), k.LogOnTime.Time(), "LogOnTime not as expected")
+	assert.Equal(t, time.Date(2185, 7, 21, 23, 34, 33, 709551516, time.UTC), k.LogOffTime.Time(), "LogOffTime not as expected")
+	assert.Equal(t, time.Date(2185, 7, 21, 23, 34, 33, 709551516, time.UTC), k.KickOffTime.Time(), "KickOffTime not as expected")
+	assert.Equal(t, time.Date(2017, 10, 10, 20, 42, 56, 220282300, time.UTC), k.PasswordLastSet.Time(), "PasswordLastSet not as expected")
+	assert.Equal(t, time.Date(2017, 10, 11, 20, 42, 56, 220282300, time.UTC), k.PasswordCanChange.Time(), "PasswordCanChange not as expected")
+
+	assert.Equal(t, "testuser1", k.EffectiveName.String(), "EffectiveName not as expected")
+	assert.Equal(t, "Test1 User1", k.FullName.String(), "EffectiveName not as expected")
+	assert.Equal(t, "", k.LogonScript.String(), "EffectiveName not as expected")
+	assert.Equal(t, "", k.ProfilePath.String(), "EffectiveName not as expected")
+	assert.Equal(t, "", k.HomeDirectory.String(), "EffectiveName not as expected")
+	assert.Equal(t, "", k.HomeDirectoryDrive.String(), "EffectiveName not as expected")
+
+	assert.Equal(t, uint16(46), k.LogonCount, "LogonCount not as expected")
+	assert.Equal(t, uint16(0), k.BadPasswordCount, "BadPasswordCount not as expected")
+	assert.Equal(t, uint32(1106), k.UserID, "UserID not as expected")
+	assert.Equal(t, uint32(513), k.PrimaryGroupID, "PrimaryGroupID not as expected")
+	assert.Equal(t, uint32(3), k.GroupCount, "GroupCount not as expected")
+
+	gids = []mstypes.GroupMembership{
+		{RelativeID: 1110, Attributes: 7},
+		{RelativeID: 513, Attributes: 7},
+		{RelativeID: 1109, Attributes: 7},
+	}
+	assert.Equal(t, gids, k.GroupIDs, "GroupIDs not as expected")
+
+	assert.Equal(t, uint32(544), k.UserFlags, "UserFlags not as expected")
+
+	assert.Equal(t, mstypes.UserSessionKey{CypherBlock: [2]mstypes.CypherBlock{{Data: [8]byte{}}, {Data: [8]byte{}}}}, k.UserSessionKey, "UserSessionKey not as expected")
+
+	assert.Equal(t, "UDC", k.LogonServer.Value, "LogonServer not as expected")
+	assert.Equal(t, "USER", k.LogonDomainName.Value, "LogonDomainName not as expected")
+
+	assert.Equal(t, "S-1-5-21-2284869408-3503417140-1141177250", k.LogonDomainID.String(), "LogonDomainID not as expected")
+
+	assert.Equal(t, uint32(528), k.UserAccountControl, "UserAccountControl not as expected")
+	assert.Equal(t, uint32(0), k.SubAuthStatus, "SubAuthStatus not as expected")
+	assert.Equal(t, time.Date(2185, 7, 21, 23, 34, 33, 709551616, time.UTC), k.LastSuccessfulILogon.Time(), "LastSuccessfulILogon not as expected")
+	assert.Equal(t, time.Date(2185, 7, 21, 23, 34, 33, 709551616, time.UTC), k.LastFailedILogon.Time(), "LastSuccessfulILogon not as expected")
+	assert.Equal(t, uint32(0), k.FailedILogonCount, "FailedILogonCount not as expected")
+
+	assert.Equal(t, uint32(1), k.SIDCount, "SIDCount not as expected")
+	assert.Equal(t, int(k.SIDCount), len(k.ExtraSIDs), "SIDCount and size of ExtraSIDs list are not the same")
+
+	es = []struct {
+		sid  string
+		attr uint32
+	}{
+		{"S-1-18-1", uint32(7)},
+	}
+	for i, s := range es {
+		assert.Equal(t, s.sid, k.ExtraSIDs[i].SID.String(), "ExtraSID SID value not as epxected")
+		assert.Equal(t, s.attr, k.ExtraSIDs[i].Attributes, "ExtraSID Attributes value not as epxected")
+	}
+
+	assert.Equal(t, uint8(4), k.ResourceGroupDomainSID.SubAuthorityCount, "ResourceGroupDomainSID not as expected")
+	assert.Equal(t, "S-1-5-21-3062750306-1230139592-1973306805", k.ResourceGroupDomainSID.String(), "ResourceGroupDomainSID value not as expected")
+	assert.Equal(t, 2, len(k.ResourceGroupIDs), "ResourceGroupIDs not as expected")
+	rgids := []mstypes.GroupMembership{
+		{RelativeID: 1107, Attributes: 536870919},
+		{RelativeID: 1108, Attributes: 536870919},
+	}
+	assert.Equal(t, rgids, k.ResourceGroupIDs, "ResourceGroupIDs not as expected")
+	//groupSids := []string{"S-1-5-21-2284869408-3503417140-1141177250-1110",
+	//	"S-1-5-21-2284869408-3503417140-1141177250-513",
+	//	"S-1-5-21-2284869408-3503417140-1141177250-1109",
+	//	"S-1-18-1",
+	//	"S-1-5-21-3062750306-1230139592-1973306805-1107",
+	//	"S-1-5-21-3062750306-1230139592-1973306805-1108"}
+	//assert.Equal(t, groupSids, k.GetGroupMembershipSIDs(), "GroupMembershipSIDs not as expected")
+}
diff --git a/mstypes/claims.go b/mstypes/claims.go
new file mode 100644
index 0000000..c66348d
--- /dev/null
+++ b/mstypes/claims.go
@@ -0,0 +1,152 @@
+package mstypes
+
+import (
+	"bytes"
+	"encoding/hex"
+	"errors"
+	"fmt"
+
+	"github.com/jcmturner/rpc/ndr"
+	"golang.org/x/net/http2/hpack"
+)
+
+// Compression format assigned numbers. https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-xca/a8b7cb0a-92a6-4187-a23b-5e14273b96f8
+const (
+	CompressionFormatNone       uint16 = 0
+	CompressionFormatLZNT1      uint16 = 2 // LZNT1 aka ntfs compression
+	CompressionFormatXPress     uint16 = 3 // plain LZ77
+	CompressionFormatXPressHuff uint16 = 4 // LZ77+Huffman - The Huffman variant of the XPRESS compression format uses LZ77-style dictionary compression combined with Huffman coding.
+)
+
+// ClaimsSourceTypeAD https://msdn.microsoft.com/en-us/library/hh553809.aspx
+const ClaimsSourceTypeAD uint16 = 1
+
+// Claim Type assigned numbers
+const (
+	ClaimTypeIDInt64    uint16 = 1
+	ClaimTypeIDUInt64   uint16 = 2
+	ClaimTypeIDString   uint16 = 3
+	ClaimsTypeIDBoolean uint16 = 6
+)
+
+// ClaimsBlob implements https://msdn.microsoft.com/en-us/library/hh554119.aspx
+type ClaimsBlob struct {
+	Size        uint32
+	EncodedBlob EncodedBlob
+}
+
+// EncodedBlob are the bytes of the encoded Claims
+type EncodedBlob []byte
+
+// Size returns the size of the bytes of the encoded Claims
+func (b EncodedBlob) Size(c interface{}) int {
+	cb := c.(ClaimsBlob)
+	return int(cb.Size)
+}
+
+// ClaimsSetMetadata implements https://msdn.microsoft.com/en-us/library/hh554073.aspx
+type ClaimsSetMetadata struct {
+	ClaimsSetSize             uint32
+	ClaimsSetBytes            []byte `ndr:"pointer,conformant"`
+	CompressionFormat         uint16 // Enum see constants for options
+	UncompressedClaimsSetSize uint32
+	ReservedType              uint16
+	ReservedFieldSize         uint32
+	ReservedField             []byte `ndr:"pointer,conformant"`
+}
+
+// ClaimsSet reads the ClaimsSet type from the NDR encoded ClaimsSetBytes in the ClaimsSetMetadata
+func (m *ClaimsSetMetadata) ClaimsSet() (c ClaimsSet, err error) {
+	if len(m.ClaimsSetBytes) < 1 {
+		err = errors.New("no bytes available for ClaimsSet")
+		return
+	}
+	// TODO switch statement to decompress ClaimsSetBytes
+	switch m.CompressionFormat {
+	case CompressionFormatLZNT1:
+		s := hex.EncodeToString(m.ClaimsSetBytes)
+		err = fmt.Errorf("ClaimsSet compressed, format LZNT1 not currently supported: %s", s)
+		return
+	case CompressionFormatXPress:
+		s := hex.EncodeToString(m.ClaimsSetBytes)
+		err = fmt.Errorf("ClaimsSet compressed, format XPress not currently supported: %s", s)
+		return
+	case CompressionFormatXPressHuff:
+		var b []byte
+		buff := bytes.NewBuffer(b)
+		_, e := hpack.HuffmanDecode(buff, m.ClaimsSetBytes)
+		if e != nil {
+			err = fmt.Errorf("error deflating: %v", e)
+			return
+		}
+		m.ClaimsSetBytes = buff.Bytes()
+	}
+	dec := ndr.NewDecoder(bytes.NewReader(m.ClaimsSetBytes))
+	err = dec.Decode(&c)
+	return
+}
+
+// ClaimsSet implements https://msdn.microsoft.com/en-us/library/hh554122.aspx
+type ClaimsSet struct {
+	ClaimsArrayCount  uint32
+	ClaimsArrays      []ClaimsArray `ndr:"pointer,conformant"`
+	ReservedType      uint16
+	ReservedFieldSize uint32
+	ReservedField     []byte `ndr:"pointer,conformant"`
+}
+
+// ClaimsArray implements https://msdn.microsoft.com/en-us/library/hh536458.aspx
+type ClaimsArray struct {
+	ClaimsSourceType uint16
+	ClaimsCount      uint32
+	ClaimEntries     []ClaimEntry `ndr:"pointer,conformant"`
+}
+
+// ClaimEntry is a NDR union that implements https://msdn.microsoft.com/en-us/library/hh536374.aspx
+type ClaimEntry struct {
+	ID         string           `ndr:"pointer,conformant,varying"`
+	Type       uint16           `ndr:"unionTag"`
+	TypeInt64  ClaimTypeInt64   `ndr:"unionField"`
+	TypeUInt64 ClaimTypeUInt64  `ndr:"unionField"`
+	TypeString ClaimTypeString  `ndr:"unionField"`
+	TypeBool   ClaimTypeBoolean `ndr:"unionField"`
+}
+
+// SwitchFunc is the ClaimEntry union field selection function
+func (u ClaimEntry) SwitchFunc(_ interface{}) string {
+	switch u.Type {
+	case ClaimTypeIDInt64:
+		return "TypeInt64"
+	case ClaimTypeIDUInt64:
+		return "TypeUInt64"
+	case ClaimTypeIDString:
+		return "TypeString"
+	case ClaimsTypeIDBoolean:
+		return "TypeBool"
+	}
+	return ""
+}
+
+// ClaimTypeInt64 is a claim of type int64
+type ClaimTypeInt64 struct {
+	ValueCount uint32
+	Value      []int64 `ndr:"pointer,conformant"`
+}
+
+// ClaimTypeUInt64 is a claim of type uint64
+type ClaimTypeUInt64 struct {
+	ValueCount uint32
+	Value      []uint64 `ndr:"pointer,conformant"`
+}
+
+// ClaimTypeString is a claim of type string
+type ClaimTypeString struct {
+	ValueCount uint32
+	Value      []LPWSTR `ndr:"pointer,conformant"`
+}
+
+// ClaimTypeBoolean is a claim of type bool
+type ClaimTypeBoolean struct {
+	ValueCount uint32
+	Value      []bool `ndr:"pointer,conformant"`
+}
diff --git a/mstypes/claims_test.go b/mstypes/claims_test.go
new file mode 100644
index 0000000..c2a9d1f
--- /dev/null
+++ b/mstypes/claims_test.go
@@ -0,0 +1,158 @@
+package mstypes
+
+import (
+	"bytes"
+	"compress/flate"
+	"encoding/hex"
+	"io/ioutil"
+	"testing"
+	"unicode/utf8"
+
+	"github.com/jcmturner/rpc/ndr"
+	"github.com/stretchr/testify/assert"
+)
+
+const (
+	ClientClaimsInfoStr       = "01100800cccccccc000100000000000000000200d80000000400020000000000d8000000000000000000000000000000d800000001100800ccccccccc80000000000000000000200010000000400020000000000000000000000000001000000010000000100000008000200010000000c000200030003000100000010000200290000000000000029000000610064003a002f002f006500780074002f00730041004d004100630063006f0075006e0074004e0061006d0065003a0038003800640035006400390030003800350065006100350063003000630030000000000001000000140002000a000000000000000a00000074006500730074007500730065007200310000000000000000000000"
+	ClientClaimsInfoInt       = "01100800cccccccce00000000000000000000200b80000000400020000000000b8000000000000000000000000000000b800000001100800cccccccca80000000000000000000200010000000400020000000000000000000000000001000000010000000100000008000200010000000c0002000100010001000000100002002a000000000000002a000000610064003a002f002f006500780074002f006d007300440053002d0053007500700070006f00720074006500640045003a0038003800640035006400650061003800660031006100660035006600310039000000010000001c0000000000000000000000"
+	ClientClaimsInfoMulti     = "01100800cccccccc780100000000000000000200500100000400020000000000500100000000000000000000000000005001000001100800cccccccc400100000000000000000200010000000400020000000000000000000000000001000000010000000200000008000200020000000c000200010001000100000010000200140002000300030001000000180002002a000000000000002a000000610064003a002f002f006500780074002f006d007300440053002d0053007500700070006f00720074006500640045003a0038003800640035006400650061003800660031006100660035006600310039000000010000001c00000000000000290000000000000029000000610064003a002f002f006500780074002f00730041004d004100630063006f0075006e0074004e0061006d0065003a00380038006400350064003900300038003500650061003500630030006300300000000000010000001c0002000a000000000000000a000000740065007300740075007300650072003100000000000000"
+	ClientClaimsInfoMultiUint = "01100800ccccccccf00000000000000000000200c80000000400020000000000c8000000000000000000000000000000c800000001100800ccccccccb80000000000000000000200010000000400020000000000000000000000000001000000010000000100000008000200010000000c000200020002000400000010000200260000000000000026000000610064003a002f002f006500780074002f006f0062006a0065006300740043006c006100730073003a00380038006400350064006500370039003100650037006200320037006500360000000400000009000a000000000007000100000000000600010000000000000001000000000000000000"
+	ClientClaimsInfoMultiStr  = "01100800cccccccc480100000000000000000200200100000400020000000000200100000000000000000000000000002001000001100800cccccccc100100000000000000000200010000000400020000000000000000000000000001000000010000000100000008000200010000000c000200030003000400000010000200270000000000000027000000610064003a002f002f006500780074002f006f00740068006500720049007000500068006f006e0065003a003800380064003500640065003900660036006200340061006600390038003500000000000400000014000200180002001c000200200002000500000000000000050000007300740072003100000000000500000000000000050000007300740072003200000000000500000000000000050000007300740072003300000000000500000000000000050000007300740072003400000000000000000000000000"
+
+	ClaimsEntryIDStr            = "ad://ext/sAMAccountName:88d5d9085ea5c0c0"
+	ClaimsEntryValueStr         = "testuser1"
+	ClaimsEntryIDInt64          = "ad://ext/msDS-SupportedE:88d5dea8f1af5f19"
+	ClaimsEntryValueInt64 int64 = 28
+	ClaimsEntryIDUInt64         = "ad://ext/objectClass:88d5de791e7b27e6"
+
+	ClaimsSetBytesCompressionFormatXPressHuff = "738788888708080007000800080007000880088808088880886687888607080000808800800000000880000000000000806667080808787707767800080000000000000000000000000000000000000000000000080000000000000000000000000000000000080000000000000000000000000000000000000000000000000057000800800000007500000000000000050700000000000064760800080000008587007700000080650808000000000075888700000000700788000000000060677000000000007000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002e91f150e1ad792412496411f3904ff6027871529ef12043e4e79ab23c9f03aea65c0aca41e842b8d46f0321354538afe9f8c413b6e1a37377bca410ac8bc3b35398e51c0a290929e3ca764addf84e5ada9caa43c80c38de74d75cd0289a202641d26a950284dea25479c4376c3100720db619b9066d13c506c88a858a3305007490a40d7015a7528382a7c9ae54ab58204f01e1d8e044fee01925cbc46ad28cfa8d67c28e0216ce1de315aaaf43e4c88409002793b33a3823683680ce7d6606eca05f0cff9d06c88a0588dd5500d51de514570286fa148c007c699838d635b0b87ed420749011c94696fa202b002b0000"
+)
+
+func Test_TMP(t *testing.T) {
+	blz77huff, _ := hex.DecodeString("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000030230000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a8dc0000ff2601")
+	//b, _ := hex.DecodeString("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000050555555555555555555554544040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d8523ed794115be9195ff9d67cdf8d0400000000")
+	r := bytes.NewReader(blz77huff)
+	dr := flate.NewReader(r)
+	s, errr := ioutil.ReadAll(dr)
+	t.Logf("test: %s %v\n", string(s), errr)
+	ru, size := utf8.DecodeRuneInString("`")
+	t.Logf("%d %v\n", ru, size)
+}
+
+func Test_ClientClaimsInfoStr_Unmarshal(t *testing.T) {
+	b, _ := hex.DecodeString(ClientClaimsInfoStr)
+	m := new(ClaimsSetMetadata)
+	dec := ndr.NewDecoder(bytes.NewReader(b))
+	err := dec.Decode(m)
+	if err != nil {
+		t.Errorf("error decoding ClaimsSetMetadata %v", err)
+	}
+	k, err := m.ClaimsSet()
+	if err != nil {
+		t.Errorf("error retrieving ClaimsSet %v", err)
+	}
+	assert.Equal(t, uint32(1), k.ClaimsArrayCount, "claims array count not as expected")
+	assert.Equal(t, ClaimsSourceTypeAD, k.ClaimsArrays[0].ClaimsSourceType, "claims source type not as expected")
+	assert.Equal(t, uint32(1), k.ClaimsArrays[0].ClaimsCount, "claims count not as expected")
+	assert.Equal(t, uint16(3), k.ClaimsArrays[0].ClaimEntries[0].Type, "claims entry type not as expected")
+	assert.Equal(t, uint32(1), k.ClaimsArrays[0].ClaimEntries[0].TypeString.ValueCount, "claims value count not as expected")
+	assert.Equal(t, ClaimsEntryIDStr, k.ClaimsArrays[0].ClaimEntries[0].ID, "claims entry ID not as expected")
+	assert.Equal(t, []LPWSTR{{ClaimsEntryValueStr}}, k.ClaimsArrays[0].ClaimEntries[0].TypeString.Value, "claims value not as expected")
+	assert.Equal(t, CompressionFormatNone, m.CompressionFormat, "compression format not as expected")
+}
+
+func Test_ClientClaimsMultiValueUint_Unmarshal(t *testing.T) {
+	b, _ := hex.DecodeString(ClientClaimsInfoMultiUint)
+	m := new(ClaimsSetMetadata)
+	dec := ndr.NewDecoder(bytes.NewReader(b))
+	err := dec.Decode(m)
+	if err != nil {
+		t.Errorf("error decoding ClaimsSetMetadata %v", err)
+	}
+	k, err := m.ClaimsSet()
+	if err != nil {
+		t.Errorf("error retrieving ClaimsSet %v", err)
+	}
+
+	assert.Equal(t, uint32(1), k.ClaimsArrayCount, "claims array count not as expected")
+	assert.Equal(t, ClaimsSourceTypeAD, k.ClaimsArrays[0].ClaimsSourceType, "claims source type not as expected")
+	assert.Equal(t, uint32(1), k.ClaimsArrays[0].ClaimsCount, "claims count not as expected")
+	assert.Equal(t, ClaimTypeIDUInt64, k.ClaimsArrays[0].ClaimEntries[0].Type, "claims entry type not as expected")
+	assert.Equal(t, uint32(4), k.ClaimsArrays[0].ClaimEntries[0].TypeUInt64.ValueCount, "claims value count not as expected")
+	assert.Equal(t, ClaimsEntryIDUInt64, k.ClaimsArrays[0].ClaimEntries[0].ID, "claims entry ID not as expected")
+	assert.Equal(t, []uint64{655369, 65543, 65542, 65536}, k.ClaimsArrays[0].ClaimEntries[0].TypeUInt64.Value, "claims value not as expected")
+	assert.Equal(t, CompressionFormatNone, m.CompressionFormat, "compression format not as expected")
+}
+
+func Test_ClientClaimsInt_Unmarshal(t *testing.T) {
+	b, _ := hex.DecodeString(ClientClaimsInfoInt)
+	m := new(ClaimsSetMetadata)
+	dec := ndr.NewDecoder(bytes.NewReader(b))
+	err := dec.Decode(m)
+	if err != nil {
+		t.Errorf("error decoding ClaimsSetMetadata %v", err)
+	}
+	k, err := m.ClaimsSet()
+	if err != nil {
+		t.Errorf("error retrieving ClaimsSet %v", err)
+	}
+
+	assert.Equal(t, uint32(1), k.ClaimsArrayCount, "claims array count not as expected")
+	assert.Equal(t, ClaimsSourceTypeAD, k.ClaimsArrays[0].ClaimsSourceType, "claims source type not as expected")
+	assert.Equal(t, uint32(1), k.ClaimsArrays[0].ClaimsCount, "claims count not as expected")
+	assert.Equal(t, ClaimTypeIDInt64, k.ClaimsArrays[0].ClaimEntries[0].Type, "claims entry type not as expected")
+	assert.Equal(t, uint32(1), k.ClaimsArrays[0].ClaimEntries[0].TypeInt64.ValueCount, "claims value count not as expected")
+	assert.Equal(t, ClaimsEntryIDInt64, k.ClaimsArrays[0].ClaimEntries[0].ID, "claims entry ID not as expected")
+	assert.Equal(t, []int64{ClaimsEntryValueInt64}, k.ClaimsArrays[0].ClaimEntries[0].TypeInt64.Value, "claims value not as expected")
+	assert.Equal(t, CompressionFormatNone, m.CompressionFormat, "compression format not as expected")
+}
+
+func Test_ClientClaimsMultiValueStr_Unmarshal(t *testing.T) {
+	b, _ := hex.DecodeString(ClientClaimsInfoMultiStr)
+	m := new(ClaimsSetMetadata)
+	dec := ndr.NewDecoder(bytes.NewReader(b))
+	err := dec.Decode(m)
+	if err != nil {
+		t.Errorf("error decoding ClaimsSetMetadata %v", err)
+	}
+	k, err := m.ClaimsSet()
+	if err != nil {
+		t.Errorf("error retrieving ClaimsSet %v", err)
+	}
+
+	assert.Equal(t, uint32(1), k.ClaimsArrayCount, "claims array count not as expected")
+	assert.Equal(t, ClaimsSourceTypeAD, k.ClaimsArrays[0].ClaimsSourceType, "claims source type not as expected")
+	assert.Equal(t, uint32(1), k.ClaimsArrays[0].ClaimsCount, "claims count not as expected")
+	assert.Equal(t, ClaimTypeIDString, k.ClaimsArrays[0].ClaimEntries[0].Type, "claims entry type not as expected")
+	assert.Equal(t, uint32(4), k.ClaimsArrays[0].ClaimEntries[0].TypeString.ValueCount, "claims value count not as expected")
+	assert.Equal(t, "ad://ext/otherIpPhone:88d5de9f6b4af985", k.ClaimsArrays[0].ClaimEntries[0].ID, "claims entry ID not as expected")
+	assert.Equal(t, []LPWSTR{{"str1"}, {"str2"}, {"str3"}, {"str4"}}, k.ClaimsArrays[0].ClaimEntries[0].TypeString.Value, "claims value not as expected")
+	assert.Equal(t, CompressionFormatNone, m.CompressionFormat, "compression format not as expected")
+}
+
+func Test_ClientClaimsInfoMultiEntry_Unmarshal(t *testing.T) {
+	b, _ := hex.DecodeString(ClientClaimsInfoMulti)
+	m := new(ClaimsSetMetadata)
+	dec := ndr.NewDecoder(bytes.NewReader(b))
+	err := dec.Decode(m)
+	if err != nil {
+		t.Errorf("error decoding ClaimsSetMetadata %v", err)
+	}
+	k, err := m.ClaimsSet()
+	if err != nil {
+		t.Errorf("error retrieving ClaimsSet %v", err)
+	}
+
+	assert.Equal(t, uint32(1), k.ClaimsArrayCount, "claims array count not as expected")
+	assert.Equal(t, ClaimsSourceTypeAD, k.ClaimsArrays[0].ClaimsSourceType, "claims source type not as expected")
+	assert.Equal(t, uint32(2), k.ClaimsArrays[0].ClaimsCount, "claims count not as expected")
+	assert.Equal(t, uint16(1), k.ClaimsArrays[0].ClaimEntries[0].Type, "claims entry type not as expected")
+	assert.Equal(t, uint32(1), k.ClaimsArrays[0].ClaimEntries[0].TypeInt64.ValueCount, "claims value count not as expected")
+	assert.Equal(t, ClaimsEntryIDInt64, k.ClaimsArrays[0].ClaimEntries[0].ID, "claims entry ID not as expected")
+	assert.Equal(t, []int64{int64(28)}, k.ClaimsArrays[0].ClaimEntries[0].TypeInt64.Value, "claims value not as expected")
+	assert.Equal(t, uint16(3), k.ClaimsArrays[0].ClaimEntries[1].Type, "claims entry type not as expected")
+	assert.Equal(t, uint32(1), k.ClaimsArrays[0].ClaimEntries[1].TypeString.ValueCount, "claims value count not as expected")
+	assert.Equal(t, ClaimsEntryIDStr, k.ClaimsArrays[0].ClaimEntries[1].ID, "claims entry ID not as expected")
+	assert.Equal(t, []LPWSTR{{ClaimsEntryValueStr}}, k.ClaimsArrays[0].ClaimEntries[1].TypeString.Value, "claims value not as expected")
+	assert.Equal(t, CompressionFormatNone, m.CompressionFormat, "compression format not as expected")
+}
diff --git a/mstypes/common.go b/mstypes/common.go
new file mode 100644
index 0000000..fb6510d
--- /dev/null
+++ b/mstypes/common.go
@@ -0,0 +1,12 @@
+// Package mstypes provides implemnations of some Microsoft data types [MS-DTYP] https://msdn.microsoft.com/en-us/library/cc230283.aspx
+package mstypes
+
+// LPWSTR implements https://msdn.microsoft.com/en-us/library/cc230355.aspx
+type LPWSTR struct {
+	Value string `ndr:"pointer,conformant,varying"`
+}
+
+// String returns the string representation of LPWSTR data type.
+func (s *LPWSTR) String() string {
+	return s.Value
+}
diff --git a/mstypes/filetime.go b/mstypes/filetime.go
new file mode 100644
index 0000000..5cc952f
--- /dev/null
+++ b/mstypes/filetime.go
@@ -0,0 +1,52 @@
+// Package mstypes implements representations of Microsoft types
+package mstypes
+
+import (
+	"time"
+)
+
+/*
+FILETIME is a windows data structure.
+Ref: https://msdn.microsoft.com/en-us/library/windows/desktop/ms724284%28v=vs.85%29.aspx
+It contains two parts that are 32bit integers:
+	dwLowDateTime
+	dwHighDateTime
+We need to combine these two into one 64bit integer.
+This gives the number of 100 nano second period from January 1, 1601, Coordinated Universal Time (UTC)
+*/
+
+const unixEpochDiff = 116444736000000000
+
+// FileTime implements the Microsoft FILETIME type https://msdn.microsoft.com/en-us/library/cc230324.aspx
+type FileTime struct {
+	LowDateTime  uint32
+	HighDateTime uint32
+}
+
+// Time return a golang Time type from the FileTime
+func (ft FileTime) Time() time.Time {
+	ns := (ft.MSEpoch() - unixEpochDiff) * 100
+	return time.Unix(0, int64(ns)).UTC()
+}
+
+// MSEpoch returns the FileTime as a Microsoft epoch, the number of 100 nano second periods elapsed from January 1, 1601 UTC.
+func (ft FileTime) MSEpoch() int64 {
+	return (int64(ft.HighDateTime) << 32) + int64(ft.LowDateTime)
+}
+
+// Unix returns the FileTime as a Unix time, the number of seconds elapsed since January 1, 1970 UTC.
+func (ft FileTime) Unix() int64 {
+	return (ft.MSEpoch() - unixEpochDiff) / 10000000
+}
+
+// GetFileTime returns a FileTime type from the provided Golang Time type.
+func GetFileTime(t time.Time) FileTime {
+	ns := t.UnixNano()
+	fp := (ns / 100) + unixEpochDiff
+	hd := fp >> 32
+	ld := fp - (hd << 32)
+	return FileTime{
+		LowDateTime:  uint32(ld),
+		HighDateTime: uint32(hd),
+	}
+}
diff --git a/mstypes/filetime_test.go b/mstypes/filetime_test.go
new file mode 100644
index 0000000..93ddc43
--- /dev/null
+++ b/mstypes/filetime_test.go
@@ -0,0 +1,52 @@
+package mstypes
+
+import (
+	"bytes"
+	"encoding/hex"
+	"github.com/jcmturner/rpc/ndr"
+	"github.com/stretchr/testify/assert"
+	"testing"
+	"time"
+)
+
+const TestNDRHeader = "01100800cccccccca00400000000000000000200"
+
+func TestFileTime(t *testing.T) {
+	t.Parallel()
+	//2007-02-22 17:00:01.6382155
+	tt := time.Date(2007, 2, 22, 17, 0, 1, 638215500, time.UTC)
+	ft := GetFileTime(tt)
+	assert.Equal(t, tt.Unix(), ft.Unix(), "Unix epoch time not as expected")
+	assert.Equal(t, int64(128166372016382155), ft.MSEpoch(), "MSEpoch not as expected")
+	assert.Equal(t, tt, ft.Time(), "Golang time object returned from FileTime not as expected")
+}
+
+func TestDecodeFileTime(t *testing.T) {
+	var tests = []struct {
+		Hex      string
+		UnixNano int64
+	}{
+		{"d186660f656ac601", 1146188570925640100},
+		{"17d439fe784ac601", 1142678694837147900},
+		{"1794a328424bc601", 1142765094837147900},
+		{"175424977a81c601", 1148726694837147900},
+		{"058e4fdd80c6d201", 1494085991825766900},
+		{"cc27969c39c6d201", 1494055388968750000},
+		{"cce7ffc602c7d201", 1494141788968750000},
+		{"c30bcc79e444d301", 1507982621052409900},
+		{"c764125a0842d301", 1507668176220282300},
+		{"c7247c84d142d301", 1507754576220282300},
+	}
+
+	for i, test := range tests {
+		a := new(FileTime)
+		hexStr := TestNDRHeader + test.Hex
+		b, _ := hex.DecodeString(hexStr)
+		dec := ndr.NewDecoder(bytes.NewReader(b))
+		err := dec.Decode(a)
+		if err != nil {
+			t.Fatalf("test %d: %v", i+1, err)
+		}
+		assert.Equal(t, test.UnixNano, a.Time().UnixNano(), "Time value not as expected for test: %d", i+1)
+	}
+}
diff --git a/mstypes/group_membership.go b/mstypes/group_membership.go
new file mode 100644
index 0000000..7915137
--- /dev/null
+++ b/mstypes/group_membership.go
@@ -0,0 +1,19 @@
+package mstypes
+
+// GroupMembership implements https://msdn.microsoft.com/en-us/library/cc237945.aspx
+// RelativeID : A 32-bit unsigned integer that contains the RID of a particular group.
+// The possible values for the Attributes flags are identical to those specified in KERB_SID_AND_ATTRIBUTES
+type GroupMembership struct {
+	RelativeID uint32
+	Attributes uint32
+}
+
+// DomainGroupMembership implements https://msdn.microsoft.com/en-us/library/hh536344.aspx
+// DomainId: A SID structure that contains the SID for the domain.This member is used in conjunction with the GroupIds members to create group SIDs for the device.
+// GroupCount: A 32-bit unsigned integer that contains the number of groups within the domain to which the account belongs.
+// GroupIds: A pointer to a list of GROUP_MEMBERSHIP structures that contain the groups to which the account belongs in the domain. The number of groups in this list MUST be equal to GroupCount.
+type DomainGroupMembership struct {
+	DomainID   RPCSID `ndr:"pointer"`
+	GroupCount uint32
+	GroupIDs   []GroupMembership `ndr:"pointer,conformant"` // Size is value of GroupCount
+}
diff --git a/mstypes/kerb_sid_and_attributes.go b/mstypes/kerb_sid_and_attributes.go
new file mode 100644
index 0000000..61ac39b
--- /dev/null
+++ b/mstypes/kerb_sid_and_attributes.go
@@ -0,0 +1,23 @@
+package mstypes
+
+// Attributes of a security group membership and can be combined by using the bitwise OR operation.
+// They are used by an access check mechanism to specify whether the membership is to be used in an access check decision.
+const (
+	SEGroupMandatory        = 31
+	SEGroupEnabledByDefault = 30
+	SEGroupEnabled          = 29
+	SEGroupOwner            = 28
+	SEGroupResource         = 2
+	//All other bits MUST be set to zero and MUST be  ignored on receipt.
+)
+
+// KerbSidAndAttributes implements https://msdn.microsoft.com/en-us/library/cc237947.aspx
+type KerbSidAndAttributes struct {
+	SID        RPCSID `ndr:"pointer"` // A pointer to an RPC_SID structure.
+	Attributes uint32
+}
+
+// SetFlag sets a flag in a uint32 attribute value.
+func SetFlag(a *uint32, i uint) {
+	*a = *a | (1 << (31 - i))
+}
diff --git a/mstypes/reader.go b/mstypes/reader.go
new file mode 100644
index 0000000..24495bc
--- /dev/null
+++ b/mstypes/reader.go
@@ -0,0 +1,109 @@
+package mstypes
+
+import (
+	"bufio"
+	"encoding/binary"
+	"fmt"
+	"io"
+)
+
+// Byte sizes of primitive types
+const (
+	SizeBool   = 1
+	SizeChar   = 1
+	SizeUint8  = 1
+	SizeUint16 = 2
+	SizeUint32 = 4
+	SizeUint64 = 8
+	SizeEnum   = 2
+	SizeSingle = 4
+	SizeDouble = 8
+	SizePtr    = 4
+)
+
+// Reader reads simple byte stream data into a Go representations
+type Reader struct {
+	r *bufio.Reader // source of the data
+}
+
+// NewReader creates a new instance of a simple Reader.
+func NewReader(r io.Reader) *Reader {
+	reader := new(Reader)
+	reader.r = bufio.NewReader(r)
+	return reader
+}
+
+func (r *Reader) Read(p []byte) (n int, err error) {
+	return r.r.Read(p)
+}
+
+func (r *Reader) Uint8() (uint8, error) {
+	b, err := r.r.ReadByte()
+	if err != nil {
+		return uint8(0), err
+	}
+	return uint8(b), nil
+}
+
+func (r *Reader) Uint16() (uint16, error) {
+	b, err := r.ReadBytes(SizeUint16)
+	if err != nil {
+		return uint16(0), err
+	}
+	return binary.LittleEndian.Uint16(b), nil
+}
+
+func (r *Reader) Uint32() (uint32, error) {
+	b, err := r.ReadBytes(SizeUint32)
+	if err != nil {
+		return uint32(0), err
+	}
+	return binary.LittleEndian.Uint32(b), nil
+}
+
+func (r *Reader) Uint64() (uint64, error) {
+	b, err := r.ReadBytes(SizeUint64)
+	if err != nil {
+		return uint64(0), err
+	}
+	return binary.LittleEndian.Uint64(b), nil
+}
+
+func (r *Reader) FileTime() (f FileTime, err error) {
+	f.LowDateTime, err = r.Uint32()
+	if err != nil {
+		return
+	}
+	f.HighDateTime, err = r.Uint32()
+	if err != nil {
+		return
+	}
+	return
+}
+
+// UTF16String returns a string that is UTF16 encoded in a byte slice. n is the number of bytes representing the string
+func (r *Reader) UTF16String(n int) (str string, err error) {
+	//Length divided by 2 as each run is 16bits = 2bytes
+	s := make([]rune, n/2, n/2)
+	for i := 0; i < len(s); i++ {
+		var u uint16
+		u, err = r.Uint16()
+		if err != nil {
+			return
+		}
+		s[i] = rune(u)
+	}
+	str = string(s)
+	return
+}
+
+// readBytes returns a number of bytes from the NDR byte stream.
+func (r *Reader) ReadBytes(n int) ([]byte, error) {
+	//TODO make this take an int64 as input to allow for larger values on all systems?
+	b := make([]byte, n, n)
+	m, err := r.r.Read(b)
+	if err != nil || m != n {
+		return b, fmt.Errorf("error reading bytes from stream: %v", err)
+	}
+	return b, nil
+}
diff --git a/mstypes/rpc_unicode_string.go b/mstypes/rpc_unicode_string.go
new file mode 100644
index 0000000..4bf02e0
--- /dev/null
+++ b/mstypes/rpc_unicode_string.go
@@ -0,0 +1,13 @@
+package mstypes
+
+// RPCUnicodeString implements https://msdn.microsoft.com/en-us/library/cc230365.aspx
+type RPCUnicodeString struct {
+	Length        uint16 // The length, in bytes, of the string pointed to by the Buffer member, not including the terminating null character if any. The length MUST be a multiple of 2. The length SHOULD equal the entire size of the Buffer, in which case there is no terminating null character. Any method that accesses this structure MUST use the Length specified instead of relying on the presence or absence of a null character.
+	MaximumLength uint16 // The maximum size, in bytes, of the string pointed to by Buffer. The size MUST be a multiple of 2. If not, the size MUST be decremented by 1 prior to use. This value MUST not be less than Length.
+	Value         string `ndr:"pointer,conformant,varying"`
+}
+
+// String returns the RPCUnicodeString string value
+func (r *RPCUnicodeString) String() string {
+	return r.Value
+}
diff --git a/mstypes/rpc_unicode_string_test.go b/mstypes/rpc_unicode_string_test.go
new file mode 100644
index 0000000..4220c7e
--- /dev/null
+++ b/mstypes/rpc_unicode_string_test.go
@@ -0,0 +1,32 @@
+package mstypes
+
+import (
+	"bytes"
+	"encoding/hex"
+	"testing"
+
+	"github.com/jcmturner/rpc/ndr"
+	"github.com/stretchr/testify/assert"
+)
+
+const (
+	TestRPCUnicodeStringBytes = "1200120004000200" + "01000000" + "0900000000000000090000007400650073007400750073006500720031000000"
+	TestRPCUnicodeStringValue = "testuser1"
+)
+
+type TestRPCUnicodeString struct {
+	RPCStr     RPCUnicodeString
+	OtherValue uint32
+}
+
+func Test_RPCUnicodeString(t *testing.T) {
+	a := new(TestRPCUnicodeString)
+	hexStr := TestNDRHeader + TestRPCUnicodeStringBytes
+	b, _ := hex.DecodeString(hexStr)
+	dec := ndr.NewDecoder(bytes.NewReader(b))
+	err := dec.Decode(a)
+	if err != nil {
+		t.Fatal(err)
+	}
+	assert.Equal(t, TestRPCUnicodeStringValue, a.RPCStr.Value, "String value not as expected")
+}
diff --git a/mstypes/sid.go b/mstypes/sid.go
new file mode 100644
index 0000000..98a9c5a
--- /dev/null
+++ b/mstypes/sid.go
@@ -0,0 +1,32 @@
+package mstypes
+
+import (
+	"encoding/binary"
+	"encoding/hex"
+	"fmt"
+)
+
+// RPCSID implements https://msdn.microsoft.com/en-us/library/cc230364.aspx
+type RPCSID struct {
+	Revision            uint8    // An 8-bit unsigned integer that specifies the revision level of the SID. This value MUST be set to 0x01.
+	SubAuthorityCount   uint8    // An 8-bit unsigned integer that specifies the number of elements in the SubAuthority array. The maximum number of elements allowed is 15.
+	IdentifierAuthority [6]byte  // An RPC_SID_IDENTIFIER_AUTHORITY structure that indicates the authority under which the SID was created. It describes the entity that created the SID. The Identifier Authority value {0,0,0,0,0,5} denotes SIDs created by the NT SID authority.
+	SubAuthority        []uint32 `ndr:"conformant"` // A variable length array of unsigned 32-bit integers that uniquely identifies a principal relative to the IdentifierAuthority. Its length is determined by SubAuthorityCount.
+}
+
+// String returns the string representation of the RPC_SID.
+func (s *RPCSID) String() string {
+	var str string
+	b := append(make([]byte, 2, 2), s.IdentifierAuthority[:]...)
+	// For a strange reason this is read big endian: https://msdn.microsoft.com/en-us/library/dd302645.aspx
+	i := binary.BigEndian.Uint64(b)
+	if i >= 4294967296 {
+		str = fmt.Sprintf("S-1-0x%s", hex.EncodeToString(s.IdentifierAuthority[:]))
+	} else {
+		str = fmt.Sprintf("S-1-%d", i)
+	}
+	for _, sub := range s.SubAuthority {
+		str = fmt.Sprintf("%s-%d", str, sub)
+	}
+	return str
+}
diff --git a/mstypes/sid_test.go b/mstypes/sid_test.go
new file mode 100644
index 0000000..4de5c6d
--- /dev/null
+++ b/mstypes/sid_test.go
@@ -0,0 +1,51 @@
+package mstypes
+
+import (
+	"bytes"
+	"encoding/hex"
+	"testing"
+
+	"github.com/jcmturner/rpc/ndr"
+	"github.com/stretchr/testify/assert"
+)
+
+type testSIDStruct struct {
+	SID RPCSID `ndr:"pointer"`
+}
+
+func Test_RPCSIDDecode(t *testing.T) {
+	var tests = []struct {
+		Hex string
+		SID string
+	}{
+		{"040000000104000000000005150000005951b81766725d2564633b0b", "S-1-5-21-397955417-626881126-188441444"},
+		{"05000000010500000000000515000000b9301b2eb7414c6c8c3b351501020000", "S-1-5-21-773533881-1816936887-355810188-513"},
+		{"050000000105000000000005150000005951b81766725d2564633b0b74542f00", "S-1-5-21-397955417-626881126-188441444-3101812"},
+		{"050000000105000000000005150000005951b81766725d2564633b0be8383200", "S-1-5-21-397955417-626881126-188441444-3291368"},
+		{"050000000105000000000005150000005951b81766725d2564633b0b5db43200", "S-1-5-21-397955417-626881126-188441444-3322973"},
+		{"050000000105000000000005150000005951b81766725d2564633b0b41163500", "S-1-5-21-397955417-626881126-188441444-3479105"},
+		{"050000000105000000000005150000005951b81766725d2564633b0be8ea3100", "S-1-5-21-397955417-626881126-188441444-3271400"},
+		{"050000000105000000000005150000005951b81766725d2564633b0bc1193200", "S-1-5-21-397955417-626881126-188441444-3283393"},
+		{"050000000105000000000005150000005951b81766725d2564633b0b29f13200", "S-1-5-21-397955417-626881126-188441444-3338537"},
+		{"050000000105000000000005150000005951b81766725d2564633b0b0f5f2e00", "S-1-5-21-397955417-626881126-188441444-3038991"},
+		{"050000000105000000000005150000005951b81766725d2564633b0b2f5b2e00", "S-1-5-21-397955417-626881126-188441444-3037999"},
+		{"050000000105000000000005150000005951b81766725d2564633b0bef8f3100", "S-1-5-21-397955417-626881126-188441444-3248111"},
+		{"050000000105000000000005150000005951b81766725d2564633b0b075f2e00", "S-1-5-21-397955417-626881126-188441444-3038983"},
+		{"040000000104000000000005150000004c86cebca07160e63fdce887", "S-1-5-21-3167651404-3865080224-2280184895"},
+		{"050000000105000000000005150000004c86cebca07160e63fdce8875a040000", "S-1-5-21-3167651404-3865080224-2280184895-1114"},
+		{"050000000105000000000005150000004c86cebca07160e63fdce88757040000", "S-1-5-21-3167651404-3865080224-2280184895-1111"},
+	}
+
+	for i, test := range tests {
+		a := new(testSIDStruct)
+		hexStr := TestNDRHeader + "01020304" + test.Hex //The 01000000 is a dumby value for the pointer uint32
+		b, _ := hex.DecodeString(hexStr)
+		dec := ndr.NewDecoder(bytes.NewReader(b))
+		err := dec.Decode(a)
+		if err != nil {
+			t.Fatalf("test %d: %v", i+1, err)
+		}
+		assert.Equal(t, test.SID, a.SID.String(), "SID not as expected for test %d", i+1)
+
+	}
+}
diff --git a/mstypes/user_session_key.go b/mstypes/user_session_key.go
new file mode 100644
index 0000000..fcf0a5d
--- /dev/null
+++ b/mstypes/user_session_key.go
@@ -0,0 +1,11 @@
+package mstypes
+
+// CypherBlock implements https://msdn.microsoft.com/en-us/library/cc237040.aspx
+type CypherBlock struct {
+	Data [8]byte // size = 8
+}
+
+// UserSessionKey implements https://msdn.microsoft.com/en-us/library/cc237080.aspx
+type UserSessionKey struct {
+	CypherBlock [2]CypherBlock // size = 2
+}
diff --git a/ndr/arrays.go b/ndr/arrays.go
new file mode 100644
index 0000000..5e2def2
--- /dev/null
+++ b/ndr/arrays.go
@@ -0,0 +1,413 @@
+package ndr
+
+import (
+	"errors"
+	"fmt"
+	"reflect"
+	"strconv"
+)
+
+// intFromTag returns an int that is a value in a struct tag key/value pair
+func intFromTag(tag reflect.StructTag, key string) (int, error) {
+	ndrTag := parseTags(tag)
+	d := 1
+	if n, ok := ndrTag.Map[key]; ok {
+		i, err := strconv.Atoi(n)
+		if err != nil {
+			return d, fmt.Errorf("invalid dimensions tag [%s]: %v", n, err)
+		}
+		d = i
+	}
+	return d, nil
+}
+
+// parseDimensions returns the a slice of the size of each dimension and type of the member at the deepest level.
+func parseDimensions(v reflect.Value) (l []int, tb reflect.Type) {
+	if v.Kind() == reflect.Ptr {
+		v = v.Elem()
+	}
+	t := v.Type()
+	if t.Kind() == reflect.Ptr {
+		t = t.Elem()
+	}
+	if t.Kind() != reflect.Array && t.Kind() != reflect.Slice {
+		return
+	}
+	l = append(l, v.Len())
+	if t.Elem().Kind() == reflect.Array || t.Elem().Kind() == reflect.Slice {
+		// contains array or slice
+		var m []int
+		m, tb = parseDimensions(v.Index(0))
+		l = append(l, m...)
+	} else {
+		tb = t.Elem()
+	}
+	return
+}
+
+// sliceDimensions returns the count of dimensions a slice has.
+func sliceDimensions(t reflect.Type) (d int, tb reflect.Type) {
+	if t.Kind() == reflect.Ptr {
+		t = t.Elem()
+	}
+	if t.Kind() == reflect.Slice {
+		d++
+		var n int
+		n, tb = sliceDimensions(t.Elem())
+		d += n
+	} else {
+		tb = t
+	}
+	return
+}
+
+// makeSubSlices is a deep recursive creation/initialisation of multi-dimensional slices.
+// Takes the reflect.Value of the 1st dimension and a slice of the lengths of the sub dimensions
+func makeSubSlices(v reflect.Value, l []int) {
+	ty := v.Type().Elem()
+	if ty.Kind() != reflect.Slice {
+		return
+	}
+	for i := 0; i < v.Len(); i++ {
+		s := reflect.MakeSlice(ty, l[0], l[0])
+		v.Index(i).Set(s)
+		// Are there more sub dimensions?
+		if len(l) > 1 {
+			makeSubSlices(v.Index(i), l[1:])
+		}
+	}
+	return
+}
+
+// multiDimensionalIndexPermutations returns all the permutations of the indexes of a multi-dimensional slice.
+// The input is a slice of integers that indicates the max size/length of each dimension
+func multiDimensionalIndexPermutations(l []int) (ps [][]int) {
+	z := make([]int, len(l), len(l)) // The zeros permutation
+	ps = append(ps, z)
+	// for each dimension, in reverse
+	for i := len(l) - 1; i >= 0; i-- {
+		ws := make([][]int, len(ps))
+		copy(ws, ps)
+		//create a permutation for each of the iterations of the current dimension
+		for j := 1; j <= l[i]-1; j++ {
+			// For each existing permutation
+			for _, p := range ws {
+				np := make([]int, len(p), len(p))
+				copy(np, p)
+				np[i] = j
+				ps = append(ps, np)
+			}
+		}
+	}
+	return
+}
+
+// precedingMax reads off the next conformant max value
+func (dec *Decoder) precedingMax() uint32 {
+	m := dec.conformantMax[0]
+	dec.conformantMax = dec.conformantMax[1:]
+	return m
+}
+
+// fillFixedArray establishes if the fixed array is uni or multi dimensional and then fills it.
+func (dec *Decoder) fillFixedArray(v reflect.Value, tag reflect.StructTag, def *[]deferedPtr) error {
+	l, t := parseDimensions(v)
+	if t.Kind() == reflect.String {
+		tag = reflect.StructTag(subStringArrayTag)
+	}
+	if len(l) < 1 {
+		return errors.New("could not establish dimensions of fixed array")
+	}
+	if len(l) == 1 {
+		err := dec.fillUniDimensionalFixedArray(v, tag, def)
+		if err != nil {
+			return fmt.Errorf("could not fill uni-dimensional fixed array: %v", err)
+		}
+		return nil
+	}
+	// Fixed array is multidimensional
+	ps := multiDimensionalIndexPermutations(l[:len(l)-1])
+	for _, p := range ps {
+		// Get current multi-dimensional index to fill
+		a := v
+		for _, i := range p {
+			a = a.Index(i)
+		}
+		// fill with the last dimension array
+		err := dec.fillUniDimensionalFixedArray(a, tag, def)
+		if err != nil {
+			return fmt.Errorf("could not fill dimension %v of multi-dimensional fixed array: %v", p, err)
+		}
+	}
+	return nil
+}
+
+// readUniDimensionalFixedArray reads an array (not slice) from the byte stream.
+func (dec *Decoder) fillUniDimensionalFixedArray(v reflect.Value, tag reflect.StructTag, def *[]deferedPtr) error {
+	for i := 0; i < v.Len(); i++ {
+		err := dec.fill(v.Index(i), tag, def)
+		if err != nil {
+			return fmt.Errorf("could not fill index %d of fixed array: %v", i, err)
+		}
+	}
+	return nil
+}
+
+// fillConformantArray establishes if the conformant array is uni or multi dimensional and then fills the slice.
+func (dec *Decoder) fillConformantArray(v reflect.Value, tag reflect.StructTag, def *[]deferedPtr) error {
+	d, _ := sliceDimensions(v.Type())
+	if d > 1 {
+		err := dec.fillMultiDimensionalConformantArray(v, d, tag, def)
+		if err != nil {
+			return err
+		}
+	} else {
+		err := dec.fillUniDimensionalConformantArray(v, tag, def)
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+// fillUniDimensionalConformantArray fills the uni-dimensional slice value.
+func (dec *Decoder) fillUniDimensionalConformantArray(v reflect.Value, tag reflect.StructTag, def *[]deferedPtr) error {
+	m := dec.precedingMax()
+	n := int(m)
+	a := reflect.MakeSlice(v.Type(), n, n)
+	for i := 0; i < n; i++ {
+		err := dec.fill(a.Index(i), tag, def)
+		if err != nil {
+			return fmt.Errorf("could not fill index %d of uni-dimensional conformant array: %v", i, err)
+		}
+	}
+	v.Set(a)
+	return nil
+}
+
+// fillMultiDimensionalConformantArray fills the multi-dimensional slice value provided from conformant array data.
+// The number of dimensions must be specified. This must be less than or equal to the dimensions in the slice for this
+// method not to panic.
+func (dec *Decoder) fillMultiDimensionalConformantArray(v reflect.Value, d int, tag reflect.StructTag, def *[]deferedPtr) error {
+	// Read the max size of each dimensions from the ndr stream
+	l := make([]int, d, d)
+	for i := range l {
+		l[i] = int(dec.precedingMax())
+	}
+	// Initialise size of slices
+	//   Initialise the size of the 1st dimension
+	ty := v.Type()
+	v.Set(reflect.MakeSlice(ty, l[0], l[0]))
+	// Initialise the size of the other dimensions recursively
+	makeSubSlices(v, l[1:])
+
+	// Get all permutations of the indexes and go through each and fill
+	ps := multiDimensionalIndexPermutations(l)
+	for _, p := range ps {
+		// Get current multi-dimensional index to fill
+		a := v
+		for _, i := range p {
+			a = a.Index(i)
+		}
+		err := dec.fill(a, tag, def)
+		if err != nil {
+			return fmt.Errorf("could not fill index %v of slice: %v", p, err)
+		}
+	}
+	return nil
+}
+
+// fillVaryingArray establishes if the varying array is uni or multi dimensional and then fills the slice.
+func (dec *Decoder) fillVaryingArray(v reflect.Value, tag reflect.StructTag, def *[]deferedPtr) error {
+	d, t := sliceDimensions(v.Type())
+	if d > 1 {
+		err := dec.fillMultiDimensionalVaryingArray(v, t, d, tag, def)
+		if err != nil {
+			return err
+		}
+	} else {
+		err := dec.fillUniDimensionalVaryingArray(v, tag, def)
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+// fillUniDimensionalVaryingArray fills the uni-dimensional slice value.
+func (dec *Decoder) fillUniDimensionalVaryingArray(v reflect.Value, tag reflect.StructTag, def *[]deferedPtr) error {
+	o, err := dec.readUint32()
+	if err != nil {
+		return fmt.Errorf("could not read offset of uni-dimensional varying array: %v", err)
+	}
+	s, err := dec.readUint32()
+	if err != nil {
+		return fmt.Errorf("could not establish actual count of uni-dimensional varying array: %v", err)
+	}
+	t := v.Type()
+	// Total size of the array is the offset in the index being passed plus the actual count of elements being passed.
+	n := int(s + o)
+	a := reflect.MakeSlice(t, n, n)
+	// Populate the array starting at the offset specified
+	for i := int(o); i < n; i++ {
+		err := dec.fill(a.Index(i), tag, def)
+		if err != nil {
+			return fmt.Errorf("could not fill index %d of uni-dimensional varying array: %v", i, err)
+		}
+	}
+	v.Set(a)
+	return nil
+}
+
+// fillMultiDimensionalVaryingArray fills the multi-dimensional slice value provided from varying array data.
+// The number of dimensions must be specified. This must be less than or equal to the dimensions in the slice for this
+// method not to panic.
+func (dec *Decoder) fillMultiDimensionalVaryingArray(v reflect.Value, t reflect.Type, d int, tag reflect.StructTag, def *[]deferedPtr) error {
+	// Read the offset and actual count of each dimensions from the ndr stream
+	o := make([]int, d, d)
+	l := make([]int, d, d)
+	for i := range l {
+		off, err := dec.readUint32()
+		if err != nil {
+			return fmt.Errorf("could not read offset of dimension %d: %v", i+1, err)
+		}
+		o[i] = int(off)
+		s, err := dec.readUint32()
+		if err != nil {
+			return fmt.Errorf("could not read size of dimension %d: %v", i+1, err)
+		}
+		l[i] = int(s) + int(off)
+	}
+	// Initialise size of slices
+	//   Initialise the size of the 1st dimension
+	ty := v.Type()
+	v.Set(reflect.MakeSlice(ty, l[0], l[0]))
+	// Initialise the size of the other dimensions recursively
+	makeSubSlices(v, l[1:])
+
+	// Get all permutations of the indexes and go through each and fill
+	ps := multiDimensionalIndexPermutations(l)
+	for _, p := range ps {
+		// Get current multi-dimensional index to fill
+		a := v
+		var os bool // should this permutation be skipped due to the offset of any of the dimensions?
+		for i, j := range p {
+			if j < o[i] {
+				os = true
+				break
+			}
+			a = a.Index(j)
+		}
+		if os {
+			// This permutation should be skipped as it is less than the offset for one of the dimensions.
+			continue
+		}
+		err := dec.fill(a, tag, def)
+		if err != nil {
+			return fmt.Errorf("could not fill index %v of slice: %v", p, err)
+		}
+	}
+	return nil
+}
+
+// fillConformantVaryingArray establishes if the varying array is uni or multi dimensional and then fills the slice.
+func (dec *Decoder) fillConformantVaryingArray(v reflect.Value, tag reflect.StructTag, def *[]deferedPtr) error {
+	d, t := sliceDimensions(v.Type())
+	if d > 1 {
+		err := dec.fillMultiDimensionalConformantVaryingArray(v, t, d, tag, def)
+		if err != nil {
+			return err
+		}
+	} else {
+		err := dec.fillUniDimensionalConformantVaryingArray(v, tag, def)
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+// fillUniDimensionalConformantVaryingArray fills the uni-dimensional slice value.
+func (dec *Decoder) fillUniDimensionalConformantVaryingArray(v reflect.Value, tag reflect.StructTag, def *[]deferedPtr) error {
+	m := dec.precedingMax()
+	o, err := dec.readUint32()
+	if err != nil {
+		return fmt.Errorf("could not read offset of uni-dimensional conformant varying array: %v", err)
+	}
+	s, err := dec.readUint32()
+	if err != nil {
+		return fmt.Errorf("could not establish actual count of uni-dimensional conformant varying array: %v", err)
+	}
+	if m < o+s {
+		return errors.New("max count is less than the offset plus actual count")
+	}
+	t := v.Type()
+	n := int(s)
+	a := reflect.MakeSlice(t, n, n)
+	for i := int(o); i < n; i++ {
+		err := dec.fill(a.Index(i), tag, def)
+		if err != nil {
+			return fmt.Errorf("could not fill index %d of uni-dimensional conformant varying array: %v", i, err)
+		}
+	}
+	v.Set(a)
+	return nil
+}
+
+// fillMultiDimensionalConformantVaryingArray fills the multi-dimensional slice value provided from conformant varying array data.
+// The number of dimensions must be specified. This must be less than or equal to the dimensions in the slice for this
+// method not to panic.
+func (dec *Decoder) fillMultiDimensionalConformantVaryingArray(v reflect.Value, t reflect.Type, d int, tag reflect.StructTag, def *[]deferedPtr) error {
+	// Read the offset and actual count of each dimensions from the ndr stream
+	m := make([]int, d, d)
+	for i := range m {
+		m[i] = int(dec.precedingMax())
+	}
+	o := make([]int, d, d)
+	l := make([]int, d, d)
+	for i := range l {
+		off, err := dec.readUint32()
+		if err != nil {
+			return fmt.Errorf("could not read offset of dimension %d: %v", i+1, err)
+		}
+		o[i] = int(off)
+		s, err := dec.readUint32()
+		if err != nil {
+			return fmt.Errorf("could not read actual count of dimension %d: %v", i+1, err)
+		}
+		if m[i] < int(s)+int(off) {
+			m[i] = int(s) + int(off)
+		}
+		l[i] = int(s)
+	}
+	// Initialise size of slices
+	//   Initialise the size of the 1st dimension
+	ty := v.Type()
+	v.Set(reflect.MakeSlice(ty, m[0], m[0]))
+	// Initialise the size of the other dimensions recursively
+	makeSubSlices(v, m[1:])
+
+	// Get all permutations of the indexes and go through each and fill
+	ps := multiDimensionalIndexPermutations(m)
+	for _, p := range ps {
+		// Get current multi-dimensional index to fill
+		a := v
+		var os bool // should this permutation be skipped due to the offset of any of the dimensions or max is higher than the actual count being passed
+		for i, j := range p {
+			if j < o[i] || j >= l[i] {
+				os = true
+				break
+			}
+			a = a.Index(j)
+		}
+		if os {
+			// This permutation should be skipped as it is less than the offset for one of the dimensions.
+			continue
+		}
+		err := dec.fill(a, tag, def)
+		if err != nil {
+			return fmt.Errorf("could not fill index %v of slice: %v", p, err)
+		}
+	}
+	return nil
+}
diff --git a/ndr/arrays_test.go b/ndr/arrays_test.go
new file mode 100644
index 0000000..3f67310
--- /dev/null
+++ b/ndr/arrays_test.go
@@ -0,0 +1,227 @@
+package ndr
+
+import (
+	"bytes"
+	"encoding/hex"
+	"fmt"
+	"reflect"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+const TestHeader = "01100800cccccccca00400000000000000000200"
+
+func TestParseDimensions(t *testing.T) {
+	a := [2][2][2][]SimpleTest{}
+	l, ta := parseDimensions(reflect.ValueOf(a))
+	assert.Equal(t, 4, len(l), "dimension count not as expected")
+	assert.Equal(t, []int{2, 2, 2, 0}, l, "lengths list not as expected")
+	assert.Equal(t, "SimpleTest", ta.Name(), "type within array not as expected")
+}
+
+func TestMakeSubSlices(t *testing.T) {
+	l := []int{2, 5, 3, 1}
+	a := new([][][][]uint32)
+	v := reflect.ValueOf(a)
+	v = v.Elem()
+	ty := v.Type()
+	s := reflect.MakeSlice(ty, l[0], l[0])
+	v.Set(s)
+	makeSubSlices(v, l[1:])
+	assert.Equal(t, "[[[[0] [0] [0]] [[0] [0] [0]] [[0] [0] [0]] [[0] [0] [0]] [[0] [0] [0]]] [[[0] [0] [0]] [[0] [0] [0]] [[0] [0] [0]] [[0] [0] [0]] [[0] [0] [0]]]]", fmt.Sprintf("%v", *a))
+}
+
+func TestDimensionCountFromTag(t *testing.T) {
+	var a StructWithMultiDimensionalConformantSlice
+	v := reflect.ValueOf(a)
+	d, err := intFromTag(v.Type().Field(0).Tag, "test")
+	if err != nil {
+		t.Errorf("error getting dimensions from tag: %v", err)
+	}
+	assert.Equal(t, 3, d, "number of dimensions not as expected")
+}
+
+type StructWithArray struct {
+	A [4]uint32
+}
+
+type StructWithMultiDimArray struct {
+	A [2][3][2]uint32
+}
+
+type StructWithConformantSlice struct {
+	A []uint32 `ndr:"conformant"`
+}
+
+type StructWithVaryingSlice struct {
+	A []uint32 `ndr:"varying"`
+}
+
+type StructWithConformantVaryingSlice struct {
+	A []uint32 `ndr:"conformant,varying"`
+}
+
+type StructWithMultiDimensionalConformantSlice struct {
+	A [][][]uint32 `ndr:"conformant,test:3"`
+}
+
+type StructWithMultiDimensionalVaryingSlice struct {
+	A [][][]uint32 `ndr:"varying"`
+}
+
+type StructWithMultiDimensionalConformantVaryingSlice struct {
+	A [][][]uint32 `ndr:"conformant,varying"`
+}
+
+func TestReadUniDimensionalFixedArray(t *testing.T) {
+	hexStr := TestHeader + "01000000020000000300000004000000"
+	b, _ := hex.DecodeString(hexStr)
+	a := new(StructWithArray)
+	dec := NewDecoder(bytes.NewReader(b))
+	err := dec.Decode(a)
+	if err != nil {
+		t.Fatalf("%v", err)
+	}
+	for i := range a.A {
+		assert.Equal(t, uint32(i+1), a.A[i], "Value of index %d not as expected", i)
+	}
+}
+
+func TestReadMultiDimensionalFixedArray(t *testing.T) {
+	hexStr := TestHeader + "0100000002000000030000000400000005000000060000000700000008000000090000000a0000000b0000000c0000000d0000000e0000000f000000100000001100000012000000130000001400000015000000160000001700000018000000190000001a0000001b0000001c0000001d0000001e0000001f0000002000000021000000220000002300000024000000"
+	b, _ := hex.DecodeString(hexStr)
+	a := new(StructWithMultiDimArray)
+	dec := NewDecoder(bytes.NewReader(b))
+	err := dec.Decode(a)
+	if err != nil {
+		t.Fatalf("%v", err)
+	}
+	ar := [2][3][2]uint32{
+		{
+			{1, 2},
+			{3, 4},
+			{5, 6},
+		},
+		{
+			{7, 8},
+			{9, 10},
+			{11, 12},
+		},
+	}
+	assert.Equal(t, ar, a.A, "multi-dimensional fixed array not as expected")
+}
+
+func TestReadUniDimensionalConformantArray(t *testing.T) {
+	hexStr := TestHeader + "0400000001000000020000000300000004000000"
+	b, _ := hex.DecodeString(hexStr)
+	a := new(StructWithConformantSlice)
+	dec := NewDecoder(bytes.NewReader(b))
+	err := dec.Decode(a)
+	if err != nil {
+		t.Fatalf("%v", err)
+	}
+	for i := range a.A {
+		assert.Equal(t, uint32(i+1), a.A[i], "Value of index %d not as expected", i)
+	}
+}
+
+func TestReadMultiDimensionalConformantArray(t *testing.T) {
+	hexStr := TestHeader + "0200000003000000020000000100000002000000030000000400000005000000060000000700000008000000090000000a0000000b0000000c0000000d0000000e0000000f000000100000001100000012000000130000001400000015000000160000001700000018000000190000001a0000001b0000001c0000001d0000001e0000001f0000002000000021000000220000002300000024000000"
+	b, _ := hex.DecodeString(hexStr)
+	a := new(StructWithMultiDimensionalConformantSlice)
+	dec := NewDecoder(bytes.NewReader(b))
+	err := dec.Decode(a)
+	if err != nil {
+		t.Fatalf("%v", err)
+	}
+	ar := [][][]uint32{
+		{
+			{1, 2},
+			{3, 4},
+			{5, 6},
+		},
+		{
+			{7, 8},
+			{9, 10},
+			{11, 12},
+		},
+	}
+	assert.Equal(t, ar, a.A, "multi-dimensional conformant array not as expected")
+}
+
+func TestReadUniDimensionalVaryingArray(t *testing.T) {
+	hexStr := TestHeader + "000000000400000001000000020000000300000004000000"
+	b, _ := hex.DecodeString(hexStr)
+	a := new(StructWithVaryingSlice)
+	dec := NewDecoder(bytes.NewReader(b))
+	err := dec.Decode(a)
+	if err != nil {
+		t.Fatalf("%v", err)
+	}
+	for i := range a.A {
+		assert.Equal(t, uint32(i+1), a.A[i], "Value of index %d not as expected", i)
+	}
+}
+
+func TestReadMultiDimensionalVaryingArray(t *testing.T) {
+	hexStr := TestHeader + "0000000002000000000000000300000000000000020000000100000002000000030000000400000005000000060000000700000008000000090000000a0000000b0000000c0000000d0000000e0000000f000000100000001100000012000000130000001400000015000000160000001700000018000000190000001a0000001b0000001c0000001d0000001e0000001f0000002000000021000000220000002300000024000000"
+	b, _ := hex.DecodeString(hexStr)
+	a := new(StructWithMultiDimensionalVaryingSlice)
+	dec := NewDecoder(bytes.NewReader(b))
+	err := dec.Decode(a)
+	if err != nil {
+		t.Fatalf("%v", err)
+	}
+	ar := [][][]uint32{
+		{
+			{1, 2},
+			{3, 4},
+			{5, 6},
+		},
+		{
+			{7, 8},
+			{9, 10},
+			{11, 12},
+		},
+	}
+	assert.Equal(t, ar, a.A, "multi-dimensional conformant varying array not as expected")
+}
+
+func TestReadUniDimensionalConformantVaryingArray(t *testing.T) {
+	hexStr := TestHeader + "04000000000000000400000001000000020000000300000004000000"
+	b, _ := hex.DecodeString(hexStr)
+	a := new(StructWithConformantVaryingSlice)
+	dec := NewDecoder(bytes.NewReader(b))
+	err := dec.Decode(a)
+	if err != nil {
+		t.Fatalf("%v", err)
+	}
+	for i := range a.A {
+		assert.Equal(t, uint32(i+1), a.A[i], "Value of index %d not as expected", i)
+	}
+}
+
+func TestReadMultiDimensionalConformantVaryingArray(t *testing.T) {
+	hexStr := TestHeader + "0200000003000000020000000000000002000000000000000300000000000000020000000100000002000000030000000400000005000000060000000700000008000000090000000a0000000b0000000c0000000d0000000e0000000f000000100000001100000012000000130000001400000015000000160000001700000018000000190000001a0000001b0000001c0000001d0000001e0000001f0000002000000021000000220000002300000024000000"
+	b, _ := hex.DecodeString(hexStr)
+	a := new(StructWithMultiDimensionalConformantVaryingSlice)
+	dec := NewDecoder(bytes.NewReader(b))
+	err := dec.Decode(a)
+	if err != nil {
+		t.Fatalf("%v", err)
+	}
+	ar := [][][]uint32{
+		{
+			{1, 2},
+			{3, 4},
+			{5, 6},
+		},
+		{
+			{7, 8},
+			{9, 10},
+			{11, 12},
+		},
+	}
+	assert.Equal(t, ar, a.A, "multi-dimensional conformant varying array not as expected")
+}
diff --git a/ndr/decoder.go b/ndr/decoder.go
new file mode 100644
index 0000000..6157b4e
--- /dev/null
+++ b/ndr/decoder.go
@@ -0,0 +1,393 @@
+// Package ndr provides the ability to unmarshal NDR encoded byte steams into Go data structures
+package ndr
+
+import (
+	"bufio"
+	"fmt"
+	"io"
+	"reflect"
+	"strings"
+)
+
+// Struct tag values
+const (
+	TagConformant = "conformant"
+	TagVarying    = "varying"
+	TagPointer    = "pointer"
+	TagPipe       = "pipe"
+)
+
+// Decoder unmarshals NDR byte stream data into a Go struct representation
+type Decoder struct {
+	r             *bufio.Reader // source of the data
+	size          int           // initial size of bytes in buffer
+	ch            CommonHeader  // NDR common header
+	ph            PrivateHeader // NDR private header
+	conformantMax []uint32      // conformant max values that were moved to the beginning of the structure
+	s             interface{}   // pointer to the structure being populated
+	current       []string      // keeps track of the current field being populated
+}
+
+type deferedPtr struct {
+	v   reflect.Value
+	tag reflect.StructTag
+}
+
+// NewDecoder creates a new instance of a NDR Decoder.
+func NewDecoder(r io.Reader) *Decoder {
+	dec := new(Decoder)
+	dec.r = bufio.NewReader(r)
+	dec.r.Peek(int(commonHeaderBytes)) // For some reason an operation is needed on the buffer to initialise it so Buffered() != 0
+	dec.size = dec.r.Buffered()
+	return dec
+}
+
+// Decode unmarshals the NDR encoded bytes into the pointer of a struct provided.
+func (dec *Decoder) Decode(s interface{}) error {
+	dec.s = s
+	err := dec.readCommonHeader()
+	if err != nil {
+		return err
+	}
+	err = dec.readPrivateHeader()
+	if err != nil {
+		return err
+	}
+	_, err = dec.r.Discard(4) //The next 4 bytes are an RPC unique pointer referent. We just skip these.
+	if err != nil {
+		return Errorf("unable to process byte stream: %v", err)
+	}
+
+	return dec.process(s, reflect.StructTag(""))
+}
+
+func (dec *Decoder) process(s interface{}, tag reflect.StructTag) error {
+	// Scan for conformant fields as their max counts are moved to the beginning
+	// http://pubs.opengroup.org/onlinepubs/9629399/chap14.htm#tagfcjh_37
+	err := dec.scanConformantArrays(s, tag)
+	if err != nil {
+		return err
+	}
+	// Recursively fill the struct fields
+	var localDef []deferedPtr
+	err = dec.fill(s, tag, &localDef)
+	if err != nil {
+		return Errorf("could not decode: %v", err)
+	}
+	// Read any deferred referents associated with pointers
+	for _, p := range localDef {
+		err = dec.process(p.v, p.tag)
+		if err != nil {
+			return fmt.Errorf("could not decode deferred referent: %v", err)
+		}
+	}
+	return nil
+}
+
+// scanConformantArrays scans the structure for embedded conformant fields and captures the maximum element counts for
+// dimensions of the array that are moved to the beginning of the structure.
+func (dec *Decoder) scanConformantArrays(s interface{}, tag reflect.StructTag) error {
+	err := dec.conformantScan(s, tag)
+	if err != nil {
+		return fmt.Errorf("failed to scan for embedded conformant arrays: %v", err)
+	}
+	for i := range dec.conformantMax {
+		dec.conformantMax[i], err = dec.readUint32()
+		if err != nil {
+			return fmt.Errorf("could not read preceding conformant max count index %d: %v", i, err)
+		}
+	}
+	return nil
+}
+
+// conformantScan inspects the structure's fields for whether they are conformant.
+func (dec *Decoder) conformantScan(s interface{}, tag reflect.StructTag) error {
+	ndrTag := parseTags(tag)
+	if ndrTag.HasValue(TagPointer) {
+		return nil
+	}
+	v := getReflectValue(s)
+	switch v.Kind() {
+	case reflect.Struct:
+		for i := 0; i < v.NumField(); i++ {
+			err := dec.conformantScan(v.Field(i), v.Type().Field(i).Tag)
+			if err != nil {
+				return err
+			}
+		}
+	case reflect.String:
+		if !ndrTag.HasValue(TagConformant) {
+			break
+		}
+		dec.conformantMax = append(dec.conformantMax, uint32(0))
+	case reflect.Slice:
+		if !ndrTag.HasValue(TagConformant) {
+			break
+		}
+		d, t := sliceDimensions(v.Type())
+		for i := 0; i < d; i++ {
+			dec.conformantMax = append(dec.conformantMax, uint32(0))
+		}
+		// For string arrays there is a common max for the strings within the array.
+		if t.Kind() == reflect.String {
+			dec.conformantMax = append(dec.conformantMax, uint32(0))
+		}
+	}
+	return nil
+}
+
+func (dec *Decoder) isPointer(v reflect.Value, tag reflect.StructTag, def *[]deferedPtr) (bool, error) {
+	// Pointer so defer filling the referent
+	ndrTag := parseTags(tag)
+	if ndrTag.HasValue(TagPointer) {
+		p, err := dec.readUint32()
+		if err != nil {
+			return true, fmt.Errorf("could not read pointer: %v", err)
+		}
+		ndrTag.delete(TagPointer)
+		if p != 0 {
+			// if pointer is not zero add to the deferred items at end of stream
+			*def = append(*def, deferedPtr{v, ndrTag.StructTag()})
+		}
+		return true, nil
+	}
+	return false, nil
+}
+
+func getReflectValue(s interface{}) (v reflect.Value) {
+	if r, ok := s.(reflect.Value); ok {
+		v = r
+	} else {
+		if reflect.ValueOf(s).Kind() == reflect.Ptr {
+			v = reflect.ValueOf(s).Elem()
+		}
+	}
+	return
+}
+
+// fill populates fields with values from the NDR byte stream.
+func (dec *Decoder) fill(s interface{}, tag reflect.StructTag, localDef *[]deferedPtr) error {
+	v := getReflectValue(s)
+
+	//// Pointer so defer filling the referent
+	ptr, err := dec.isPointer(v, tag, localDef)
+	if err != nil {
+		return fmt.Errorf("could not process struct field(%s): %v", strings.Join(dec.current, "/"), err)
+	}
+	if ptr {
+		return nil
+	}
+
+	// Populate the value from the byte stream
+	switch v.Kind() {
+	case reflect.Struct:
+		dec.current = append(dec.current, v.Type().Name()) //Track the current field being filled
+		// in case struct is a union, track this and the selected union field for efficiency
+		var unionTag reflect.Value
+		var unionField string // field to fill if struct is a union
+		// Go through each field in the struct and recursively fill
+		for i := 0; i < v.NumField(); i++ {
+			fieldName := v.Type().Field(i).Name
+			dec.current = append(dec.current, fieldName) //Track the current field being filled
+			//fmt.Fprintf(os.Stderr, "DEBUG Decoding: %s\n", strings.Join(dec.current, "/"))
+			structTag := v.Type().Field(i).Tag
+			ndrTag := parseTags(structTag)
+
+			// Union handling
+			if !unionTag.IsValid() {
+				// Is this field a union tag?
+				unionTag = dec.isUnion(v.Field(i), structTag)
+			} else {
+				// What is the selected field value of the union if we don't already know
+				if unionField == "" {
+					unionField, err = unionSelectedField(v, unionTag)
+					if err != nil {
+						return fmt.Errorf("could not determine selected union value field for %s with discriminat"+
+							" tag %s: %v", v.Type().Name(), unionTag, err)
+					}
+				}
+				if ndrTag.HasValue(TagUnionField) && fieldName != unionField {
+					// is a union and this field has not been selected so will skip it.
+					dec.current = dec.current[:len(dec.current)-1] //This field has been skipped so remove it from the current field tracker
+					continue
+				}
+			}
+
+			// Check if field is a pointer
+			if v.Field(i).Type().Implements(reflect.TypeOf(new(RawBytes)).Elem()) &&
+				v.Field(i).Type().Kind() == reflect.Slice && v.Field(i).Type().Elem().Kind() == reflect.Uint8 {
+				//field is for rawbytes
+				structTag, err = addSizeToTag(v, v.Field(i), structTag)
+				if err != nil {
+					return fmt.Errorf("could not get rawbytes field(%s) size: %v", strings.Join(dec.current, "/"), err)
+				}
+				ptr, err := dec.isPointer(v.Field(i), structTag, localDef)
+				if err != nil {
+					return fmt.Errorf("could not process struct field(%s): %v", strings.Join(dec.current, "/"), err)
+				}
+				if !ptr {
+					err := dec.readRawBytes(v.Field(i), structTag)
+					if err != nil {
+						return fmt.Errorf("could not fill raw bytes struct field(%s): %v", strings.Join(dec.current, "/"), err)
+					}
+				}
+			} else {
+				err := dec.fill(v.Field(i), structTag, localDef)
+				if err != nil {
+					return fmt.Errorf("could not fill struct field(%s): %v", strings.Join(dec.current, "/"), err)
+				}
+			}
+			dec.current = dec.current[:len(dec.current)-1] //This field has been filled so remove it from the current field tracker
+		}
+		dec.current = dec.current[:len(dec.current)-1] //This field has been filled so remove it from the current field tracker
+	case reflect.Bool:
+		i, err := dec.readBool()
+		if err != nil {
+			return fmt.Errorf("could not fill %s: %v", v.Type().Name(), err)
+		}
+		v.Set(reflect.ValueOf(i))
+	case reflect.Uint8:
+		i, err := dec.readUint8()
+		if err != nil {
+			return fmt.Errorf("could not fill %s: %v", v.Type().Name(), err)
+		}
+		v.Set(reflect.ValueOf(i))
+	case reflect.Uint16:
+		i, err := dec.readUint16()
+		if err != nil {
+			return fmt.Errorf("could not fill %s: %v", v.Type().Name(), err)
+		}
+		v.Set(reflect.ValueOf(i))
+	case reflect.Uint32:
+		i, err := dec.readUint32()
+		if err != nil {
+			return fmt.Errorf("could not fill %s: %v", v.Type().Name(), err)
+		}
+		v.Set(reflect.ValueOf(i))
+	case reflect.Uint64:
+		i, err := dec.readUint64()
+		if err != nil {
+			return fmt.Errorf("could not fill %s: %v", v.Type().Name(), err)
+		}
+		v.Set(reflect.ValueOf(i))
+	case reflect.Int8:
+		i, err := dec.readInt8()
+		if err != nil {
+			return fmt.Errorf("could not fill %s: %v", v.Type().Name(), err)
+		}
+		v.Set(reflect.ValueOf(i))
+	case reflect.Int16:
+		i, err := dec.readInt16()
+		if err != nil {
+			return fmt.Errorf("could not fill %s: %v", v.Type().Name(), err)
+		}
+		v.Set(reflect.ValueOf(i))
+	case reflect.Int32:
+		i, err := dec.readInt32()
+		if err != nil {
+			return fmt.Errorf("could not fill %s: %v", v.Type().Name(), err)
+		}
+		v.Set(reflect.ValueOf(i))
+	case reflect.Int64:
+		i, err := dec.readInt64()
+		if err != nil {
+			return fmt.Errorf("could not fill %s: %v", v.Type().Name(), err)
+		}
+		v.Set(reflect.ValueOf(i))
+	case reflect.String:
+		ndrTag := parseTags(tag)
+		conformant := ndrTag.HasValue(TagConformant)
+		// strings are always varying so this is assumed without an explicit tag
+		var s string
+		var err error
+		if conformant {
+			s, err = dec.readConformantVaryingString(localDef)
+			if err != nil {
+				return fmt.Errorf("could not fill with conformant varying string: %v", err)
+			}
+		} else {
+			s, err = dec.readVaryingString(localDef)
+			if err != nil {
+				return fmt.Errorf("could not fill with varying string: %v", err)
+			}
+		}
+		v.Set(reflect.ValueOf(s))
+	case reflect.Float32:
+		i, err := dec.readFloat32()
+		if err != nil {
+			return fmt.Errorf("could not fill %v: %v", v.Type().Name(), err)
+		}
+		v.Set(reflect.ValueOf(i))
+	case reflect.Float64:
+		i, err := dec.readFloat64()
+		if err != nil {
+			return fmt.Errorf("could not fill %v: %v", v.Type().Name(), err)
+		}
+		v.Set(reflect.ValueOf(i))
+	case reflect.Array:
+		err := dec.fillFixedArray(v, tag, localDef)
+		if err != nil {
+			return err
+		}
+	case reflect.Slice:
+		if v.Type().Implements(reflect.TypeOf(new(RawBytes)).Elem()) && v.Type().Elem().Kind() == reflect.Uint8 {
+			//field is for rawbytes
+			err := dec.readRawBytes(v, tag)
+			if err != nil {
+				return fmt.Errorf("could not fill raw bytes struct field(%s): %v", strings.Join(dec.current, "/"), err)
+			}
+			break
+		}
+		ndrTag := parseTags(tag)
+		conformant := ndrTag.HasValue(TagConformant)
+		varying := ndrTag.HasValue(TagVarying)
+		if ndrTag.HasValue(TagPipe) {
+			err := dec.fillPipe(v, tag)
+			if err != nil {
+				return err
+			}
+			break
+		}
+		_, t := sliceDimensions(v.Type())
+		if t.Kind() == reflect.String && !ndrTag.HasValue(subStringArrayValue) {
+			// String array
+			err := dec.readStringsArray(v, tag, localDef)
+			if err != nil {
+				return err
+			}
+			break
+		}
+		// varying is assumed as fixed arrays use the Go array type rather than slice
+		if conformant && varying {
+			err := dec.fillConformantVaryingArray(v, tag, localDef)
+			if err != nil {
+				return err
+			}
+		} else if !conformant && varying {
+			err := dec.fillVaryingArray(v, tag, localDef)
+			if err != nil {
+				return err
+			}
+		} else {
+			//default to conformant and not varying
+			err := dec.fillConformantArray(v, tag, localDef)
+			if err != nil {
+				return err
+			}
+		}
+	default:
+		return fmt.Errorf("unsupported type")
+	}
+	return nil
+}
+
+// readBytes returns a number of bytes from the NDR byte stream.
+func (dec *Decoder) readBytes(n int) ([]byte, error) {
+	//TODO make this take an int64 as input to allow for larger values on all systems?
+	b := make([]byte, n, n)
+	m, err := dec.r.Read(b)
+	if err != nil || m != n {
+		return b, fmt.Errorf("error reading bytes from stream: %v", err)
+	}
+	return b, nil
+}
diff --git a/ndr/decoder_test.go b/ndr/decoder_test.go
new file mode 100644
index 0000000..3c08784
--- /dev/null
+++ b/ndr/decoder_test.go
@@ -0,0 +1,137 @@
+package ndr
+
+import (
+	"bytes"
+	"encoding/hex"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestReadCommonHeader(t *testing.T) {
+	var tests = []struct {
+		EncodedHex string
+		ExpectFail bool
+	}{
+		{"01100800cccccccc", false}, // Little Endian
+		{"01000008cccccccc", false}, // Big Endian have to change the bytes for the header size? This test vector was artificially created. Need proper test vector
+		//{"01100800cccccccc1802000000000000", false},
+		//{"01100800cccccccc0002000000000000", false},
+		//{"01100800cccccccc0001000000000000", false},
+		//{"01100800cccccccce000000000000000", false},
+		//{"01100800ccccccccf000000000000000", false},
+		//{"01100800cccccccc7801000000000000", false},
+		//{"01100800cccccccc4801000000000000", false},
+		//{"01100800ccccccccd001000000000000", false},
+		{"02100800cccccccc", true}, // Incorrect version
+		{"02100900cccccccc", true}, // Incorrect length
+
+	}
+
+	for i, test := range tests {
+		b, _ := hex.DecodeString(test.EncodedHex)
+		dec := NewDecoder(bytes.NewReader(b))
+		err := dec.readCommonHeader()
+		if err != nil && !test.ExpectFail {
+			t.Errorf("error reading common header of test %d: %v", i, err)
+		}
+		if err == nil && test.ExpectFail {
+			t.Errorf("expected failure on reading common header of test %d: %v", i, err)
+		}
+	}
+}
+
+func TestReadPrivateHeader(t *testing.T) {
+	var tests = []struct {
+		EncodedHex string
+		ExpectFail bool
+		Length     int
+	}{
+		{"01100800cccccccc1802000000000000", false, 536},
+		{"01100800cccccccc0002000000000000", false, 512},
+		{"01100800cccccccc0001000000000000", false, 256},
+		{"01100800ccccccccFF00000000000000", true, 255}, // Length not multiple of 8
+		{"01100800cccccccc00010000000000", true, 256},   // Too short
+
+	}
+
+	for i, test := range tests {
+		b, _ := hex.DecodeString(test.EncodedHex)
+		dec := NewDecoder(bytes.NewReader(b))
+		err := dec.readCommonHeader()
+		if err != nil {
+			t.Errorf("error reading common header of test %d: %v", i, err)
+		}
+		err = dec.readPrivateHeader()
+		if err != nil && !test.ExpectFail {
+			t.Errorf("error reading private header of test %d: %v", i, err)
+		}
+		if err == nil && test.ExpectFail {
+			t.Errorf("expected failure on reading private header of test %d: %v", i, err)
+		}
+		if dec.ph.ObjectBufferLength != uint32(test.Length) {
+			t.Errorf("Objectbuffer length expected %d actual %d", test.Length, dec.ph.ObjectBufferLength)
+		}
+	}
+}
+
+type SimpleTest struct {
+	A uint32
+	B uint32
+}
+
+func TestBasicDecode(t *testing.T) {
+	hexStr := "01100800cccccccca00400000000000000000200d186660f656ac601"
+	b, _ := hex.DecodeString(hexStr)
+	ft := new(SimpleTest)
+	dec := NewDecoder(bytes.NewReader(b))
+	err := dec.Decode(ft)
+	if err != nil {
+		t.Fatalf("error decoding: %v", err)
+	}
+	assert.Equal(t, uint32(258377425), ft.A, "Value of field A not as expected")
+	assert.Equal(t, uint32(29780581), ft.B, "Value of field B not as expected %d")
+}
+
+func TestBasicDecodeOverRun(t *testing.T) {
+	hexStr := "01100800cccccccca00400000000000000000200d186660f"
+	b, _ := hex.DecodeString(hexStr)
+	ft := new(SimpleTest)
+	dec := NewDecoder(bytes.NewReader(b))
+	err := dec.Decode(ft)
+	if err == nil {
+		t.Errorf("Expected error for trying to read more than the bytes we have")
+	}
+}
+
+type testEmbeddingPointer struct {
+	A testEmbeddedPointer `ndr:"pointer"`
+	B uint32              // 1
+}
+
+type testEmbeddedPointer struct {
+	C testEmbeddedPointer2 `ndr:"pointer"`
+	D uint32               `ndr:"pointer"` // 2
+	E uint32               // 3
+}
+
+type testEmbeddedPointer2 struct {
+	F uint32 `ndr:"pointer"` // 4
+	G uint32 // 5
+}
+
+func Test_EmbeddedPointers(t *testing.T) {
+	hexStr := TestHeader + "00040002" + "01000000" + "00040002" + "00040002" + "03000000" + "00040002" + "05000000" + "04000000" + "02000000"
+	b, _ := hex.DecodeString(hexStr)
+	ft := new(testEmbeddingPointer)
+	dec := NewDecoder(bytes.NewReader(b))
+	err := dec.Decode(ft)
+	if err != nil {
+		t.Fatalf("error decoding: %v", err)
+	}
+	assert.Equal(t, uint32(1), ft.B)
+	assert.Equal(t, uint32(2), ft.A.D)
+	assert.Equal(t, uint32(3), ft.A.E)
+	assert.Equal(t, uint32(4), ft.A.C.F)
+	assert.Equal(t, uint32(5), ft.A.C.G)
+}
diff --git a/ndr/error.go b/ndr/error.go
index 3b44ee4..9971194 100644
--- a/ndr/error.go
+++ b/ndr/error.go
@@ -9,5 +9,10 @@ type Malformed struct {
 
 // Error implements the error interface on the Malformed struct.
 func (e Malformed) Error() string {
-	return fmt.Sprintf("malformed NDR steam: %s", e.EText)
+	return fmt.Sprintf("malformed NDR stream: %s", e.EText)
+}
+
+// Errorf formats an error message into a malformed NDR error.
+func Errorf(format string, a ...interface{}) Malformed {
+	return Malformed{EText: fmt.Sprintf(format, a...)}
 }
diff --git a/ndr/header.go b/ndr/header.go
new file mode 100644
index 0000000..1970ddb
--- /dev/null
+++ b/ndr/header.go
@@ -0,0 +1,116 @@
+package ndr
+
+import (
+	"encoding/binary"
+	"fmt"
+)
+
+/*
+Serialization Version 1
+https://msdn.microsoft.com/en-us/library/cc243563.aspx
+
+Common Header - https://msdn.microsoft.com/en-us/library/cc243890.aspx
+8 bytes in total:
+- First byte - Version: Must equal 1
+- Second byte -  1st 4 bits: Endianess (0=Big; 1=Little); 2nd 4 bits: Character Encoding (0=ASCII; 1=EBCDIC)
+- 3rd - Floating point representation (This does not seem to be the case in examples for Microsoft test sources)
+- 4th - Common Header Length: Must equal 8
+- 5th - 8th - Filler: MUST be set to 0xcccccccc on marshaling, and SHOULD be ignored during unmarshaling.
+
+Private Header - https://msdn.microsoft.com/en-us/library/cc243919.aspx
+8 bytes in total:
+- First 4 bytes - Indicates the length of a serialized top-level type in the octet stream. It MUST include the padding length and exclude the header itself.
+- Second 4 bytes - Filler: MUST be set to 0 (zero) during marshaling, and SHOULD be ignored during unmarshaling.
+*/
+
+const (
+	protocolVersion   uint8  = 1
+	commonHeaderBytes uint16 = 8
+	bigEndian                = 0
+	littleEndian             = 1
+	ascii             uint8  = 0
+	ebcdic            uint8  = 1
+	ieee              uint8  = 0
+	vax               uint8  = 1
+	cray              uint8  = 2
+	ibm               uint8  = 3
+)
+
+// CommonHeader implements the NDR common header: https://msdn.microsoft.com/en-us/library/cc243889.aspx
+type CommonHeader struct {
+	Version             uint8
+	Endianness          binary.ByteOrder
+	CharacterEncoding   uint8
+	FloatRepresentation uint8
+	HeaderLength        uint16
+	Filler              []byte
+}
+
+// PrivateHeader implements the NDR private header: https://msdn.microsoft.com/en-us/library/cc243919.aspx
+type PrivateHeader struct {
+	ObjectBufferLength uint32
+	Filler             []byte
+}
+
+func (dec *Decoder) readCommonHeader() error {
+	// Version
+	vb, err := dec.r.ReadByte()
+	if err != nil {
+		return Malformed{EText: "could not read first byte of common header for version"}
+	}
+	dec.ch.Version = uint8(vb)
+	if dec.ch.Version != protocolVersion {
+		return Malformed{EText: fmt.Sprintf("byte stream does not indicate a RPC Type serialization of version %v", protocolVersion)}
+	}
+	// Read Endianness & Character Encoding
+	eb, err := dec.r.ReadByte()
+	if err != nil {
+		return Malformed{EText: "could not read second byte of common header for endianness"}
+	}
+	endian := int(eb >> 4 & 0xF)
+	if endian != 0 && endian != 1 {
+		return Malformed{EText: "common header does not indicate a valid endianness"}
+	}
+	dec.ch.CharacterEncoding = uint8(vb & 0xF)
+	if dec.ch.CharacterEncoding != 0 && dec.ch.CharacterEncoding != 1 {
+		return Malformed{EText: "common header does not indicate a valid character encoding"}
+	}
+	switch endian {
+	case littleEndian:
+		dec.ch.Endianness = binary.LittleEndian
+	case bigEndian:
+		dec.ch.Endianness = binary.BigEndian
+	}
+	// Common header length
+	lb, err := dec.readBytes(2)
+	if err != nil {
+		return Malformed{EText: fmt.Sprintf("could not read common header length: %v", err)}
+	}
+	dec.ch.HeaderLength = dec.ch.Endianness.Uint16(lb)
+	if dec.ch.HeaderLength != commonHeaderBytes {
+		return Malformed{EText: "common header does not indicate a valid length"}
+	}
+	// Filler bytes
+	dec.ch.Filler, err = dec.readBytes(4)
+	if err != nil {
+		return Malformed{EText: fmt.Sprintf("could not read common header filler: %v", err)}
+	}
+	return nil
+}
+
+func (dec *Decoder) readPrivateHeader() error {
+	// The next 8 bytes after the common header comprise the RPC type marshalling private header for constructed types.
+	err := binary.Read(dec.r, dec.ch.Endianness, &dec.ph.ObjectBufferLength)
+	if err != nil {
+		return Malformed{EText: "could not read private header object buffer length"}
+	}
+	if dec.ph.ObjectBufferLength%8 != 0 {
+		return Malformed{EText: "object buffer length not a multiple of 8"}
+	}
+	// Filler bytes
+	dec.ph.Filler, err = dec.readBytes(4)
+	if err != nil {
+		return Malformed{EText: fmt.Sprintf("could not read private header filler: %v", err)}
+	}
+	return nil
+}
diff --git a/ndr/ndr.go b/ndr/ndr.go
deleted file mode 100644
index 5b3e87a..0000000
--- a/ndr/ndr.go
+++ /dev/null
@@ -1,444 +0,0 @@
-// Package ndr is a partial implementation of NDR encoding: http://pubs.opengroup.org/onlinepubs/9629399/chap14.htm
-package ndr
-
-import (
-	"bytes"
-	"encoding/binary"
-	"fmt"
-	"math"
-)
-
-// Useful reference: https://docs.microsoft.com/en-us/windows/desktop/Rpc/rpc-ndr-engine
-
-/*
-Serialization Version 1
-https://msdn.microsoft.com/en-us/library/cc243563.aspx
-
-Common Header - https://msdn.microsoft.com/en-us/library/cc243890.aspx
-8 bytes in total:
-- First byte - Version: Must equal 1
-- Second byte -  1st 4 bits: Endianess (0=Big; 1=Little); 2nd 4 bits: Character Encoding (0=ASCII; 1=EBCDIC)
-- 3rd - Floating point representation
-- 4th - Common Header Length: Must equal 8
-- 5th - 8th - Filler: MUST be set to 0xcccccccc on marshaling, and SHOULD be ignored during unmarshaling.
-
-Private Header - https://msdn.microsoft.com/en-us/library/cc243919.aspx
-8 bytes in total:
-- First 4 bytes - Indicates the length of a serialized top-level type in the octet stream. It MUST include the padding length and exclude the header itself.
-- Second 4 bytes - Filler: MUST be set to 0 (zero) during marshaling, and SHOULD be ignored during unmarshaling.
-*/
-
-const (
-	protocolVersion    = 1
-	commonHeaderBytes  = 8
-	privateHeaderBytes = 8
-	bigEndian          = 0
-	littleEndian       = 1
-	ascii              = 0
-	ebcdic             = 1
-	ieee               = 0
-	vax                = 1
-	cray               = 2
-	ibm                = 3
-)
-
-// CommonHeader implements the NDR common header: https://msdn.microsoft.com/en-us/library/cc243889.aspx
-type CommonHeader struct {
-	Version           uint8
-	Endianness        binary.ByteOrder
-	CharacterEncoding uint8
-	//FloatRepresentation uint8
-	HeaderLength uint16
-	Filler       []byte
-}
-
-// PrivateHeader implements the NDR private header: https://msdn.microsoft.com/en-us/library/cc243919.aspx
-type PrivateHeader struct {
-	ObjectBufferLength uint32
-	Filler             []byte
-}
-
-// ReadHeaders processes the bytes to return the NDR Common and Private headers.
-func ReadHeaders(b *[]byte) (CommonHeader, PrivateHeader, int, error) {
-	ch, p, err := GetCommonHeader(b)
-	if err != nil {
-		return CommonHeader{}, PrivateHeader{}, 0, err
-	}
-	ph, err := GetPrivateHeader(b, &p, &ch.Endianness)
-	if err != nil {
-		return CommonHeader{}, PrivateHeader{}, 0, err
-	}
-	return ch, ph, p, err
-}
-
-// GetCommonHeader processes the bytes to return the NDR Common header.
-func GetCommonHeader(b *[]byte) (CommonHeader, int, error) {
-	//The first 8 bytes comprise the Common RPC Header for type marshalling.
-	if len(*b) < commonHeaderBytes {
-		return CommonHeader{}, 0, Malformed{EText: "Not enough bytes."}
-	}
-	if (*b)[0] != protocolVersion {
-		return CommonHeader{}, 0, Malformed{EText: fmt.Sprintf("Stream does not indicate a RPC Type serialization of version %v", protocolVersion)}
-	}
-	endian := int((*b)[1] >> 4 & 0xF)
-	if endian != 0 && endian != 1 {
-		return CommonHeader{}, 1, Malformed{EText: "Common header does not indicate a valid endianness"}
-	}
-	charEncoding := uint8((*b)[1] & 0xF)
-	if charEncoding != 0 && charEncoding != 1 {
-		return CommonHeader{}, 1, Malformed{EText: "Common header does not indicate a valid charater encoding"}
-	}
-	var bo binary.ByteOrder
-	switch endian {
-	case littleEndian:
-		bo = binary.LittleEndian
-	case bigEndian:
-		bo = binary.BigEndian
-	}
-	l := bo.Uint16((*b)[2:4])
-	if l != commonHeaderBytes {
-		return CommonHeader{}, 4, Malformed{EText: fmt.Sprintf("Common header does not indicate a valid length: %v instead of %v", uint8((*b)[3]), commonHeaderBytes)}
-	}
-
-	return CommonHeader{
-		Version:           uint8((*b)[0]),
-		Endianness:        bo,
-		CharacterEncoding: charEncoding,
-		//FloatRepresentation: uint8(b[2]),
-		HeaderLength: l,
-		Filler:       (*b)[4:8],
-	}, 8, nil
-}
-
-// GetPrivateHeader processes the bytes to return the NDR Private header.
-func GetPrivateHeader(b *[]byte, p *int, bo *binary.ByteOrder) (PrivateHeader, error) {
-	//The next 8 bytes comprise the RPC type marshalling private header for constructed types.
-	if len(*b) < (privateHeaderBytes) {
-		return PrivateHeader{}, Malformed{EText: "Not enough bytes."}
-	}
-	var l uint32
-	buf := bytes.NewBuffer((*b)[*p : *p+4])
-	binary.Read(buf, *bo, &l)
-	if l%8 != 0 {
-		return PrivateHeader{}, Malformed{EText: "Object buffer length not a multiple of 8"}
-	}
-	*p += 8
-	return PrivateHeader{
-		ObjectBufferLength: l,
-		Filler:             (*b)[4:8],
-	}, nil
-}
-
-// ReadUint8 reads bytes representing a thirty two bit integer.
-func ReadUint8(b *[]byte, p *int) (i uint8) {
-	if len((*b)[*p:]) < 1 {
-		return
-	}
-	ensureAlignment(p, 1)
-	i = uint8((*b)[*p])
-	*p++
-	return
-}
-
-// ReadUint16 reads bytes representing a thirty two bit integer.
-func ReadUint16(b *[]byte, p *int, e *binary.ByteOrder) (i uint16) {
-	if len((*b)[*p:]) < 2 {
-		return
-	}
-	ensureAlignment(p, 2)
-	i = (*e).Uint16((*b)[*p : *p+2])
-	*p += 2
-	return
-}
-
-// ReadUint32 reads bytes representing a thirty two bit integer.
-func ReadUint32(b *[]byte, p *int, e *binary.ByteOrder) (i uint32) {
-	if len((*b)[*p:]) < 4 {
-		return
-	}
-	ensureAlignment(p, 4)
-	i = (*e).Uint32((*b)[*p : *p+4])
-	*p += 4
-	return
-}
-
-// ReadUint64 reads bytes representing a thirty two bit integer.
-func ReadUint64(b *[]byte, p *int, e *binary.ByteOrder) (i uint64) {
-	if len((*b)[*p:]) < 8 {
-		return
-	}
-	ensureAlignment(p, 8)
-	i = (*e).Uint64((*b)[*p : *p+8])
-	*p += 8
-	return
-}
-
-// ReadBytes reads the number of bytes specified.
-func ReadBytes(b *[]byte, p *int, s int, e *binary.ByteOrder) (r []byte) {
-	if len((*b)[*p:]) < s {
-		return
-	}
-	buf := bytes.NewBuffer((*b)[*p : *p+s])
-	r = make([]byte, s)
-	binary.Read(buf, *e, &r)
-	*p += s
-	return r
-}
-
-// ReadBool reads bytes representing a boolean.
-func ReadBool(b *[]byte, p *int) bool {
-	if len((*b)[*p:]) < 1 {
-		return false
-	}
-	if ReadUint8(b, p) != 0 {
-		return true
-	}
-	return false
-}
-
-// ReadIEEEfloat32 reads bytes representing a IEEE formatted 32 bit float.
-func ReadIEEEfloat32(b *[]byte, p *int, e *binary.ByteOrder) float32 {
-	ensureAlignment(p, 4)
-	return math.Float32frombits(ReadUint32(b, p, e))
-}
-
-// ReadIEEEfloat64 reads bytes representing a IEEE formatted 64 bit float.
-func ReadIEEEfloat64(b *[]byte, p *int, e *binary.ByteOrder) float64 {
-	ensureAlignment(p, 8)
-	return math.Float64frombits(ReadUint64(b, p, e))
-}
-
-// Conformant - don't know the max count in advance
-// Varying - don't know the actual count in advance
-
-// ReadConformantVaryingString reads a Conformant and Varying String from the bytes slice.
-// A conformant and varying string is a string in which the maximum number of elements is not known beforehand and therefore is included in the representation of the string.
-// NDR represents a conformant and varying string as an ordered sequence of representations of the string elements, preceded by three unsigned long integers.
-// The first integer gives the maximum number of elements in the string, including the terminator.
-// The second integer gives the offset from the first index of the string to the first index of the actual subset being passed.
-// The third integer gives the actual number of elements being passed, including the terminator.
-func ReadConformantVaryingString(b *[]byte, p *int, e *binary.ByteOrder) (string, error) {
-	m := ReadUint32(b, p, e) // Max element count
-	o := ReadUint32(b, p, e) // Offset
-	a := ReadUint32(b, p, e) // Actual count
-	if a > (m-o) || o > m {
-		return "", Malformed{EText: fmt.Sprintf("Not enough bytes to read conformant varying string. Max: %d, Offset: %d, Actual: %d", m, o, a)}
-	}
-	//Unicode string so each element is 2 bytes
-	//move position based on the offset
-	if o > 0 {
-		*p += int(o * 2)
-	}
-	s := make([]rune, a, a)
-	for i := 0; i < len(s); i++ {
-		s[i] = rune(ReadUint16(b, p, e))
-	}
-	ensureAlignment(p, 4)
-	if len(s) > 0 {
-		// Remove any null terminator
-		if s[len(s)-1] == rune(0) {
-			s = s[:len(s)-1]
-		}
-	}
-	return string(s), nil
-}
-
-// NDR defines a special representation for an array whose elements are strings.
-// In the NDR representation of an array of strings, any conformance information (maximum element counts)
-// for the strings is removed from the string representations and included in the conformance information for the array,
-// but any variance information (offsets and actual element counts) for the strings remains with the string representations.
-//
-// If the strings are conformant or if any dimension of the array is conformant, then the representation contains maximum element counts for all dimensions of the array and for the strings.
-//
-// If the strings are non-conformant and the array is non-conformant, then the representation does not contain any maximum element counts.
-//
-// If any dimension of the array is varying, then the representation contains offsets and actual counts for all dimensions of the array.
-//
-// If the array is non-varying, then the representation does not contain any offsets or actual counts for the array, although it does contain offsets and actual counts for the strings.
-func ReadConformantVaryingStringArray(b *[]byte, p *int, e *binary.ByteOrder, n int) ([]string, error) {
-	// Read Max count for each dimension
-	sm := make([]int, n, n)
-	for i := range sm {
-		sm[i] = int(ReadUint32(b, p, e))
-	}
-	// max count for all the strings
-	m := int(ReadUint32(b, p, e))
-	// Read each elements header
-	h := make([]VaryingArrayHeader, n, n)
-	for i := range h {
-		// Offset for the dimension
-		h[i].Offset = int(ReadUint32(b, p, e))
-		// Actual count for the dimension
-		h[i].ActualCount = int(ReadUint32(b, p, e))
-	}
-	sa := make([]string, n, n)
-	for i := range sa {
-		o := int(ReadUint32(b, p, e)) // Offset
-		a := int(ReadUint32(b, p, e)) // Actual count
-		if a > (m-h[i].Offset) || h[i].Offset > m {
-			return sa, Malformed{EText: fmt.Sprintf("Not enough bytes to read conformant varying string. Max: %d, Offset: %d, Actual: %d", m, o, a)}
-		}
-		//Unicode string so each element is 2 bytes
-		//move position based on the offset
-		if o > 0 {
-			*p += int(o * 2)
-		}
-		s := make([]rune, a, a)
-		for i := 0; i < len(s); i++ {
-			s[i] = rune(ReadUint16(b, p, e))
-		}
-		ensureAlignment(p, 4)
-		if len(s) > 0 {
-			// Remove any null terminator
-			if s[len(s)-1] == rune(0) {
-				s = s[:len(s)-1]
-			}
-		}
-		sa[i] = string(s)
-	}
-	return sa, nil
-}
-
-type ConformantArrayHeader struct {
-	MaxCount int
-}
-
-type VaryingArrayHeader struct {
-	Offset      int
-	ActualCount int
-}
-
-type ConformantVaryingArrayHeader struct {
-	ConformantArrayHeader
-	VaryingArrayHeader
-}
-
-// ReadUniDimensionalConformantArrayHeader reads a UniDimensionalConformantArrayHeader from the bytes slice.
-func ReadUniDimensionalConformantArrayHeader(b *[]byte, p *int, e *binary.ByteOrder) (h ConformantArrayHeader, err error) {
-	if len((*b)[*p:]) < 4 {
-		err = Malformed{EText: "Not enough bytes to read uni-dimensional conformant array"}
-		return
-	}
-	// Max count int
-	h.MaxCount = int(ReadUint32(b, p, e))
-	return
-}
-
-// ReadMultiDimensionalConformantArrayHeader reads a MultiDimensionalConformantArrayHeader of n dimensions from the bytes slice.
-func ReadMultiDimensionalConformantArrayHeader(b *[]byte, p *int, e *binary.ByteOrder, n int) ([]ConformantArrayHeader, error) {
-	if len((*b)[*p:]) < n*4 {
-		return []ConformantArrayHeader{}, Malformed{EText: "Not enough bytes to read conformant array"}
-	}
-	h := make([]ConformantArrayHeader, n, n)
-	for i := range h {
-		// Max count int for that dimension
-		h[i].MaxCount = int(ReadUint32(b, p, e))
-	}
-	return h, nil
-}
-
-// ReadUniDimensionalVaryingArrayHeader reads a UniDimensionalVaryingArrayHeader from the bytes slice.
-func ReadUniDimensionalVaryingArrayHeader(b *[]byte, p *int, e *binary.ByteOrder) (h VaryingArrayHeader, err error) {
-	if len((*b)[*p:]) < 8 {
-		err = Malformed{EText: "Not enough bytes to read uni-dimensional varying array"}
-		return
-	}
-	h.Offset = int(ReadUint32(b, p, e))
-	h.ActualCount = int(ReadUint32(b, p, e))
-	return
-}
-
-// ReadMultiDimensionalVaryingArrayHeader reads a MultiDimensionalVaryingArrayHeader of n dimensions from the bytes slice.
-func ReadMultiDimensionalVaryingArrayHeader(b *[]byte, p *int, e *binary.ByteOrder, n int) ([]VaryingArrayHeader, error) {
-	if len((*b)[*p:]) < n*4*2 {
-		return []VaryingArrayHeader{}, Malformed{EText: "Not enough bytes to read varying array"}
-	}
-	h := make([]VaryingArrayHeader, n, n)
-	for i := range h {
-		// Offset for the dimension
-		h[i].Offset = int(ReadUint32(b, p, e))
-		// Actual count for the dimension
-		h[i].ActualCount = int(ReadUint32(b, p, e))
-	}
-	return h, nil
-}
-
-// ReadUniDimensionalConformantVaryingArrayHeader reads a UniDimensionalConformantVaryingArrayHeader from the bytes slice.
-func ReadUniDimensionalConformantVaryingArrayHeader(b *[]byte, p *int, e *binary.ByteOrder) (h ConformantVaryingArrayHeader, err error) {
-	if len((*b)[*p:]) < 12 {
-		err = Malformed{EText: "Not enough bytes to read uni-dimensional conformant varying array"}
-		return
-	}
-	h.MaxCount = int(ReadUint32(b, p, e))
-	h.Offset = int(ReadUint32(b, p, e))
-	h.ActualCount = int(ReadUint32(b, p, e))
-	if h.ActualCount > (h.MaxCount-h.Offset) || h.Offset > h.MaxCount {
-		err = Malformed{EText: fmt.Sprintf("Not enough bytes to read uni-dimensional conformant varying array. Max: %d, Offset: %d, Actual: %d", h.MaxCount, h.Offset, h.ActualCount)}
-	}
-	return
-}
-
-// ReadMultiDimensionalConformantVaryingArrayHeader reads a MultiDimensionalConformantVaryingArrayHeader of n dimensions from the bytes slice.
-func ReadMultiDimensionalConformantVaryingArrayHeader(b *[]byte, p *int, e *binary.ByteOrder, n int) ([]ConformantVaryingArrayHeader, error) {
-	if len((*b)[*p:]) < n*4*3 {
-		return []ConformantVaryingArrayHeader{}, Malformed{EText: "Not enough bytes to read conformant varying array"}
-	}
-	h := make([]ConformantVaryingArrayHeader, n, n)
-	for i := range h {
-		h[i].MaxCount = int(ReadUint32(b, p, e))
-	}
-	for i := range h {
-		h[i].Offset = int(ReadUint32(b, p, e))
-		h[i].ActualCount = int(ReadUint32(b, p, e))
-	}
-	return h, nil
-}
-
-func ensureAlignment(p *int, byteSize int) {
-	if byteSize > 0 {
-		if s := *p % byteSize; s != 0 {
-			*p += byteSize - s
-		}
-	}
-}
-
-// ReadUTF16String returns a string that is UTF16 encoded in a byte slice. n is the number of bytes representing the string
-func ReadUTF16String(n int, b *[]byte, p *int, e *binary.ByteOrder) string {
-	//Length divided by 2 as each run is 16bits = 2bytes
-	s := make([]rune, n/2, n/2)
-	for i := 0; i < len(s); i++ {
-		s[i] = rune(ReadUint16(b, p, e))
-	}
-	return string(s)
-}
-
-//func DebugByteSteamView(p int, b []byte) {
-//	fmt.Fprintf(os.Stderr, "Full %v\n", b)
-//	fmt.Fprintf(os.Stderr, "At pos %v\n", b[p:])
-//	fmt.Fprintln(os.Stderr, "uint32 view:")
-//	var e binary.ByteOrder = binary.LittleEndian
-//	var sl []int
-//	for p < len(b) {
-//		l := p
-//		i := ReadUint32(&b, &p, &e)
-//		if l+4 <= len(b) {
-//			fmt.Fprintf(os.Stderr, "%d:\t%v\t\t%d\n", l, b[l:l+4], i)
-//		} else {
-//			fmt.Fprintf(os.Stderr, "%d:\t%v\t\t%d\n", l, b[l:], i)
-//		}
-//
-//		sc := l - 8
-//		if ReadUint32(&b, &sc, &e) == i {
-//			//Possible str
-//			sc -= 4
-//			sl = append(sl, sc)
-//		}
-//	}
-//	for _, i := range sl {
-//		sc := i
-//		s, e := ReadConformantVaryingString(&b, &i, &e)
-//		if e == nil {
-//			fmt.Fprintf(os.Stderr, "Potential string at %d: %s\n", sc, s)
-//		}
-//	}
-//}
diff --git a/ndr/ndr_test.go b/ndr/ndr_test.go
deleted file mode 100644
index 4e2963a..0000000
--- a/ndr/ndr_test.go
+++ /dev/null
@@ -1 +0,0 @@
-package ndr
diff --git a/ndr/pipe.go b/ndr/pipe.go
new file mode 100644
index 0000000..5fd27da
--- /dev/null
+++ b/ndr/pipe.go
@@ -0,0 +1,31 @@
+package ndr
+
+import (
+	"fmt"
+	"reflect"
+)
+
+func (dec *Decoder) fillPipe(v reflect.Value, tag reflect.StructTag) error {
+	s, err := dec.readUint32() // read element count of first chunk
+	if err != nil {
+		return err
+	}
+	a := reflect.MakeSlice(v.Type(), 0, 0)
+	c := reflect.MakeSlice(v.Type(), int(s), int(s))
+	for s != 0 {
+		for i := 0; i < int(s); i++ {
+			err := dec.fill(c.Index(i), tag, &[]deferedPtr{})
+			if err != nil {
+				return fmt.Errorf("could not fill element %d of pipe: %v", i, err)
+			}
+		}
+		s, err = dec.readUint32() // read element count of first chunk
+		if err != nil {
+			return err
+		}
+		a = reflect.AppendSlice(a, c)
+		c = reflect.MakeSlice(v.Type(), int(s), int(s))
+	}
+	v.Set(a)
+	return nil
+}
diff --git a/ndr/pipe_test.go b/ndr/pipe_test.go
new file mode 100644
index 0000000..ed77be3
--- /dev/null
+++ b/ndr/pipe_test.go
@@ -0,0 +1,28 @@
+package ndr
+
+import (
+	"bytes"
+	"encoding/hex"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+const testPipe = "04000000010000000200000003000000040000000300000001000000020000000300000000000000"
+
+type structWithPipe struct {
+	A []uint32 `ndr:"pipe"`
+}
+
+func TestFillPipe(t *testing.T) {
+	hexStr := TestHeader + testPipe
+	b, _ := hex.DecodeString(hexStr)
+	a := new(structWithPipe)
+	dec := NewDecoder(bytes.NewReader(b))
+	err := dec.Decode(a)
+	if err != nil {
+		t.Fatalf("%v", err)
+	}
+	tp := []uint32{1, 2, 3, 4, 1, 2, 3}
+	assert.Equal(t, tp, a.A, "Value of pipe not as expected")
+}
diff --git a/ndr/primitives.go b/ndr/primitives.go
new file mode 100644
index 0000000..7eb1d1a
--- /dev/null
+++ b/ndr/primitives.go
@@ -0,0 +1,211 @@
+package ndr
+
+import (
+	"bytes"
+	"encoding/binary"
+	"math"
+)
+
+// Byte sizes of primitive types
+const (
+	SizeBool   = 1
+	SizeChar   = 1
+	SizeUint8  = 1
+	SizeUint16 = 2
+	SizeUint32 = 4
+	SizeUint64 = 8
+	SizeEnum   = 2
+	SizeSingle = 4
+	SizeDouble = 8
+	SizePtr    = 4
+)
+
+// Bool is an NDR Boolean which is a logical quantity that assumes one of two values: TRUE or FALSE.
+// NDR represents a Boolean as one octet.
+// It represents a value of FALSE as a zero octet, an octet in which every bit is reset.
+// It represents a value of TRUE as a non-zero octet, an octet in which one or more bits are set.
+
+// Char is an NDR character.
+// NDR represents a character as one octet.
+// Characters have two representation formats: ASCII and EBCDIC.
+
+// USmall is an unsigned 8 bit integer
+
+// UShort is an unsigned 16 bit integer
+
+// ULong is an unsigned 32 bit integer
+
+// UHyper is an unsigned 64 bit integer
+
+// Small is an signed 8 bit integer
+
+// Short is an signed 16 bit integer
+
+// Long is an signed 32 bit integer
+
+// Hyper is an signed 64 bit integer
+
+// Enum is the NDR representation of enumerated types as signed short integers (2 octets)
+
+// Single is an NDR defined single-precision floating-point data type
+
+// Double is an NDR defined double-precision floating-point data type
+
+// readBool reads a byte representing a boolean.
+// NDR represents a Boolean as one octet.
+// It represents a value of FALSE as a zero octet, an octet in which every bit is reset.
+// It represents a value of TRUE as a non-zero octet, an octet in which one or more bits are set.
+func (dec *Decoder) readBool() (bool, error) {
+	i, err := dec.readUint8()
+	if err != nil {
+		return false, err
+	}
+	if i != 0 {
+		return true, nil
+	}
+	return false, nil
+}
+
+// readChar reads bytes representing a 8bit ASCII integer cast to a rune.
+func (dec *Decoder) readChar() (rune, error) {
+	var r rune
+	a, err := dec.readUint8()
+	if err != nil {
+		return r, err
+	}
+	return rune(a), nil
+}
+
+// readUint8 reads bytes representing a 8bit unsigned integer.
+func (dec *Decoder) readUint8() (uint8, error) {
+	b, err := dec.r.ReadByte()
+	if err != nil {
+		return uint8(0), err
+	}
+	return uint8(b), nil
+}
+
+// readUint16 reads bytes representing a 16bit unsigned integer.
+func (dec *Decoder) readUint16() (uint16, error) {
+	dec.ensureAlignment(SizeUint16)
+	b, err := dec.readBytes(SizeUint16)
+	if err != nil {
+		return uint16(0), err
+	}
+	return dec.ch.Endianness.Uint16(b), nil
+}
+
+// readUint32 reads bytes representing a 32bit unsigned integer.
+func (dec *Decoder) readUint32() (uint32, error) {
+	dec.ensureAlignment(SizeUint32)
+	b, err := dec.readBytes(SizeUint32)
+	if err != nil {
+		return uint32(0), err
+	}
+	return dec.ch.Endianness.Uint32(b), nil
+}
+
+// readUint32 reads bytes representing a 32bit unsigned integer.
+func (dec *Decoder) readUint64() (uint64, error) {
+	dec.ensureAlignment(SizeUint64)
+	b, err := dec.readBytes(SizeUint64)
+	if err != nil {
+		return uint64(0), err
+	}
+	return dec.ch.Endianness.Uint64(b), nil
+}
+
+func (dec *Decoder) readInt8() (int8, error) {
+	dec.ensureAlignment(SizeUint8)
+	b, err := dec.readBytes(SizeUint8)
+	if err != nil {
+		return 0, err
+	}
+	var i int8
+	buf := bytes.NewReader(b)
+	err = binary.Read(buf, dec.ch.Endianness, &i)
+	if err != nil {
+		return 0, err
+	}
+	return i, nil
+}
+
+func (dec *Decoder) readInt16() (int16, error) {
+	dec.ensureAlignment(SizeUint16)
+	b, err := dec.readBytes(SizeUint16)
+	if err != nil {
+		return 0, err
+	}
+	var i int16
+	buf := bytes.NewReader(b)
+	err = binary.Read(buf, dec.ch.Endianness, &i)
+	if err != nil {
+		return 0, err
+	}
+	return i, nil
+}
+
+func (dec *Decoder) readInt32() (int32, error) {
+	dec.ensureAlignment(SizeUint32)
+	b, err := dec.readBytes(SizeUint32)
+	if err != nil {
+		return 0, err
+	}
+	var i int32
+	buf := bytes.NewReader(b)
+	err = binary.Read(buf, dec.ch.Endianness, &i)
+	if err != nil {
+		return 0, err
+	}
+	return i, nil
+}
+
+func (dec *Decoder) readInt64() (int64, error) {
+	dec.ensureAlignment(SizeUint64)
+	b, err := dec.readBytes(SizeUint64)
+	if err != nil {
+		return 0, err
+	}
+	var i int64
+	buf := bytes.NewReader(b)
+	err = binary.Read(buf, dec.ch.Endianness, &i)
+	if err != nil {
+		return 0, err
+	}
+	return i, nil
+}
+
+// https://en.wikipedia.org/wiki/IEEE_754-1985
+func (dec *Decoder) readFloat32() (f float32, err error) {
+	dec.ensureAlignment(SizeSingle)
+	b, err := dec.readBytes(SizeSingle)
+	if err != nil {
+		return
+	}
+	bits := dec.ch.Endianness.Uint32(b)
+	f = math.Float32frombits(bits)
+	return
+}
+
+func (dec *Decoder) readFloat64() (f float64, err error) {
+	dec.ensureAlignment(SizeDouble)
+	b, err := dec.readBytes(SizeDouble)
+	if err != nil {
+		return
+	}
+	bits := dec.ch.Endianness.Uint64(b)
+	f = math.Float64frombits(bits)
+	return
+}
+
+// NDR enforces NDR alignment of primitive data; that is, any primitive of size n octets is aligned at a octet stream
+// index that is a multiple of n. (In this version of NDR, n is one of {1, 2, 4, 8}.) An octet stream index indicates
+// the number of an octet in an octet stream when octets are numbered, beginning with 0, from the first octet in the
+// stream. Where necessary, an alignment gap, consisting of octets of unspecified value, precedes the representation
+// of a primitive. The gap is of the smallest size sufficient to align the primitive.
+func (dec *Decoder) ensureAlignment(n int) {
+	p := dec.size - dec.r.Buffered()
+	if s := p % n; s != 0 {
+		dec.r.Discard(n - s)
+	}
+}
diff --git a/ndr/primitives_test.go b/ndr/primitives_test.go
new file mode 100644
index 0000000..184b8d9
--- /dev/null
+++ b/ndr/primitives_test.go
@@ -0,0 +1,44 @@
+package ndr
+
+import (
+	"bufio"
+	"bytes"
+	"encoding/binary"
+	"encoding/hex"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestReadFloat32(t *testing.T) {
+	tests := []struct {
+		hexStr string
+		value  float32
+		order  binary.ByteOrder
+	}{
+		{"3E200000", 0.15625, binary.BigEndian},
+		{"00000000", 0.0, binary.BigEndian},
+		{"3F800000", 1.0, binary.BigEndian},
+		{"BF800000", -1.0, binary.BigEndian},
+		{"00000001", 1.4e-45, binary.BigEndian},
+		{"00400000", 5.877472e-39, binary.BigEndian},
+		{"007FFFFF", 1.1754942e-38, binary.BigEndian},
+		{"00800000", 1.1754944e-38, binary.BigEndian},
+		{"7F7FFFFF", 3.4028235e38, binary.BigEndian},
+		//TODO need some littleendian test vectors
+	}
+	for i, test := range tests {
+		b, _ := hex.DecodeString(test.hexStr)
+		//t.Logf("%s %08b\n", test.hexStr,b)
+		r := bufio.NewReader(bytes.NewReader(b))
+		dec := Decoder{
+			r:  r,
+			ch: CommonHeader{Endianness: test.order},
+		}
+		f, err := dec.readFloat32()
+		if err != nil {
+			t.Errorf("could not read float32 test %d: %v", i, err)
+		}
+		assert.Equal(t, test.value, f, "float32 not as expect for test %d: %s", i, test.hexStr)
+	}
+}
diff --git a/ndr/rawbytes.go b/ndr/rawbytes.go
new file mode 100644
index 0000000..9ee59fb
--- /dev/null
+++ b/ndr/rawbytes.go
@@ -0,0 +1,61 @@
+package ndr
+
+import (
+	"errors"
+	"fmt"
+	"reflect"
+	"strconv"
+)
+
+// type MyBytes []byte
+// implement RawBytes interface
+
+const (
+	sizeMethod = "Size"
+)
+
+// RawBytes interface should be implemented if reading just a number of bytes from the NDR stream
+type RawBytes interface {
+	Size(interface{}) int
+}
+
+func rawBytesSize(parent reflect.Value, v reflect.Value) (int, error) {
+	sf := v.MethodByName(sizeMethod)
+	if !sf.IsValid() {
+		return 0, fmt.Errorf("could not find a method called %s on the implementation of RawBytes", sizeMethod)
+	}
+	in := []reflect.Value{parent}
+	f := sf.Call(in)
+	if f[0].Kind() != reflect.Int {
+		return 0, errors.New("the RawBytes size function did not return an integer")
+	}
+	return int(f[0].Int()), nil
+}
+
+func addSizeToTag(parent reflect.Value, v reflect.Value, tag reflect.StructTag) (reflect.StructTag, error) {
+	size, err := rawBytesSize(parent, v)
+	if err != nil {
+		return tag, err
+	}
+	ndrTag := parseTags(tag)
+	ndrTag.Map["size"] = strconv.Itoa(size)
+	return ndrTag.StructTag(), nil
+}
+
+func (dec *Decoder) readRawBytes(v reflect.Value, tag reflect.StructTag) error {
+	ndrTag := parseTags(tag)
+	sizeStr, ok := ndrTag.Map["size"]
+	if !ok {
+		return errors.New("size tag not available")
+	}
+	size, err := strconv.Atoi(sizeStr)
+	if err != nil {
+		return fmt.Errorf("size not valid: %v", err)
+	}
+	b, err := dec.readBytes(size)
+	if err != nil {
+		return err
+	}
+	v.Set(reflect.ValueOf(b).Convert(v.Type()))
+	return nil
+}
diff --git a/ndr/strings.go b/ndr/strings.go
new file mode 100644
index 0000000..b7a910b
--- /dev/null
+++ b/ndr/strings.go
@@ -0,0 +1,70 @@
+package ndr
+
+import (
+	"fmt"
+	"reflect"
+)
+
+const (
+	subStringArrayTag   = `ndr:"varying,X-subStringArray"`
+	subStringArrayValue = "X-subStringArray"
+)
+
+func uint16SliceToString(a []uint16) string {
+	s := make([]rune, len(a), len(a))
+	for i := range s {
+		s[i] = rune(a[i])
+	}
+	if len(s) > 0 {
+		// Remove any null terminator
+		if s[len(s)-1] == rune(0) {
+			s = s[:len(s)-1]
+		}
+	}
+	return string(s)
+}
+
+func (dec *Decoder) readVaryingString(def *[]deferedPtr) (string, error) {
+	a := new([]uint16)
+	v := reflect.ValueOf(a)
+	var t reflect.StructTag
+	err := dec.fillUniDimensionalVaryingArray(v.Elem(), t, def)
+	if err != nil {
+		return "", err
+	}
+	s := uint16SliceToString(*a)
+	return s, nil
+}
+
+func (dec *Decoder) readConformantVaryingString(def *[]deferedPtr) (string, error) {
+	a := new([]uint16)
+	v := reflect.ValueOf(a)
+	var t reflect.StructTag
+	err := dec.fillUniDimensionalConformantVaryingArray(v.Elem(), t, def)
+	if err != nil {
+		return "", err
+	}
+	s := uint16SliceToString(*a)
+	return s, nil
+}
+
+func (dec *Decoder) readStringsArray(v reflect.Value, tag reflect.StructTag, def *[]deferedPtr) error {
+	d, _ := sliceDimensions(v.Type())
+	ndrTag := parseTags(tag)
+	var m []int
+	//var ms int
+	if ndrTag.HasValue(TagConformant) {
+		for i := 0; i < d; i++ {
+			m = append(m, int(dec.precedingMax()))
+		}
+		//common max size
+		_ = dec.precedingMax()
+		//ms = int(n)
+	}
+	tag = reflect.StructTag(subStringArrayTag)
+	err := dec.fillVaryingArray(v, tag, def)
+	if err != nil {
+		return fmt.Errorf("could not read string array: %v", err)
+	}
+	return nil
+}
diff --git a/ndr/strings_test.go b/ndr/strings_test.go
new file mode 100644
index 0000000..5892fc2
--- /dev/null
+++ b/ndr/strings_test.go
@@ -0,0 +1,240 @@
+package ndr
+
+import (
+	"bytes"
+	"encoding/binary"
+	"encoding/hex"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+const (
+	TestStr         = "hello world!"
+	TestStrUTF16Hex = "680065006c006c006f00200077006f0072006c00640021000000" // little endian format
+)
+
+type TestStructWithVaryingString struct {
+	A string `ndr:"varying"`
+}
+
+type TestStructWithConformantVaryingString struct {
+	A string `ndr:"conformant,varying"`
+}
+
+type TestStructWithConformantVaryingStringUniArray struct {
+	A []string `ndr:"conformant,varying"`
+}
+
+// Should not have to specify varying tag
+type TestStructWithNonConformantStringUniArray struct {
+	A []string
+}
+
+type TestStructWithConformantVaryingStringMultiArray struct {
+	A [][][]string `ndr:"conformant,varying"`
+}
+
+// Should not have to specify varying tag
+type TestStructWithNonConformantStringMultiArray struct {
+	A [][][]string
+}
+
+// Strings are always varying but the array may not be
+type TestStructWithFixedStringUniArray struct {
+	A [4]string
+}
+
+type TestStructWithFixedStringMultiArray struct {
+	A [2][3][2]string
+}
+
+func Test_uint16SliceToString(t *testing.T) {
+	b, _ := hex.DecodeString(TestStrUTF16Hex)
+	var u []uint16
+	for i := 0; i < len(b); i += 2 {
+		u = append(u, binary.LittleEndian.Uint16(b[i:i+2]))
+	}
+	s := uint16SliceToString(u)
+	assert.Equal(t, TestStr, s, "uint16SliceToString did not return as expected")
+}
+
+func Test_readVaryingString(t *testing.T) {
+	ac := make([]byte, 4, 4)
+	binary.LittleEndian.PutUint32(ac, uint32(len(TestStrUTF16Hex)/4))            // actual count of number of uint16 bytes
+	hexStr := TestHeader + "00000000" + hex.EncodeToString(ac) + TestStrUTF16Hex // header:offset(0):actual count:data
+	b, _ := hex.DecodeString(hexStr)
+	a := new(TestStructWithVaryingString)
+	dec := NewDecoder(bytes.NewReader(b))
+	err := dec.Decode(a)
+	if err != nil {
+		t.Fatalf("%v", err)
+	}
+	assert.Equal(t, TestStr, a.A, "value of decoded varying string not as expected")
+}
+
+func Test_readConformantVaryingString(t *testing.T) {
+	ac := make([]byte, 4, 4)
+	binary.LittleEndian.PutUint32(ac, uint32(len(TestStrUTF16Hex)/4))                                     // actual count of number of uint16 bytes
+	hexStr := TestHeader + hex.EncodeToString(ac) + "00000000" + hex.EncodeToString(ac) + TestStrUTF16Hex // header:max:offset(0):actual count:data
+	b, _ := hex.DecodeString(hexStr)
+	a := new(TestStructWithConformantVaryingString)
+	dec := NewDecoder(bytes.NewReader(b))
+	err := dec.Decode(a)
+	if err != nil {
+		t.Fatalf("%v", err)
+	}
+	assert.Equal(t, TestStr, a.A, "value of decoded varying string not as expected")
+}
+
+func Test_readConformantStringUniDimensionalArray(t *testing.T) {
+	ac := make([]byte, 4, 4)
+	binary.LittleEndian.PutUint32(ac, uint32(len(TestStrUTF16Hex)/4))                                                                             // actual count of number of uint16 bytes
+	hexStr := "00000000" + hex.EncodeToString(ac) + TestStrUTF16Hex                                                                               // offset(0):actual count:data
+	hexStr = TestHeader + "04000000" + hex.EncodeToString(ac) + "0000000004000000" + hexStr + "0000" + hexStr + "0000" + hexStr + "0000" + hexStr // header:1st dimension count(4):max for all strings:offset for 1st dim:actual for 1st dim:string array elements(4) with offset and actual counts. Need to include some bytes for alignment.
+	b, _ := hex.DecodeString(hexStr)
+	a := new(TestStructWithConformantVaryingStringUniArray)
+	dec := NewDecoder(bytes.NewReader(b))
+	err := dec.Decode(a)
+	if err != nil {
+		t.Fatalf("%v", err)
+	}
+	assert.Equal(t, 4, len(a.A), "length of string array not as expected")
+	for _, s := range a.A {
+		if s != TestStr {
+			t.Fatalf("string array does not contain the right values")
+		}
+	}
+}
+
+func Test_readConformantStringMultiDimensionalArray(t *testing.T) {
+	ac := make([]byte, 4, 4)
+	binary.LittleEndian.PutUint32(ac, uint32(len(TestStrUTF16Hex)/4)) // actual count of number of uint16 bytes
+	strb := "00000000" + hex.EncodeToString(ac) + TestStrUTF16Hex     // offset(0):actual count:data
+	var hexStr string
+	for i := 0; i < 12; i++ {
+		hexStr = hexStr + strb + "0000"
+	}
+	hexStr = TestHeader + "02000000" + "03000000" + "02000000" + hex.EncodeToString(ac) + "0000000002000000" + "0000000003000000" + "0000000002000000" + hexStr
+	b, _ := hex.DecodeString(hexStr)
+	a := new(TestStructWithConformantVaryingStringMultiArray)
+	dec := NewDecoder(bytes.NewReader(b))
+	err := dec.Decode(a)
+	if err != nil {
+		t.Fatalf("%v", err)
+	}
+	ar := [][][]string{
+		{
+			{TestStr, TestStr},
+			{TestStr, TestStr},
+			{TestStr, TestStr},
+		},
+		{
+			{TestStr, TestStr},
+			{TestStr, TestStr},
+			{TestStr, TestStr},
+		},
+	}
+	assert.Equal(t, ar, a.A, "fixed multi-dimensional string array not as expected")
+}
+
+func Test_readNonConformantStringUniDimensionalArray(t *testing.T) {
+	ac := make([]byte, 4, 4)
+	binary.LittleEndian.PutUint32(ac, uint32(len(TestStrUTF16Hex)/4))                                       // actual count of number of uint16 bytes
+	hexStr := "00000000" + hex.EncodeToString(ac) + TestStrUTF16Hex                                         // offset(0):actual count:data
+	hexStr = TestHeader + "0000000004000000" + hexStr + "0000" + hexStr + "0000" + hexStr + "0000" + hexStr // header:offset for 1st dim:actual for 1st dim:string array elements(4) with offset and actual counts. Need to include some bytes for alignment.
+	b, _ := hex.DecodeString(hexStr)
+	a := new(TestStructWithNonConformantStringUniArray)
+	dec := NewDecoder(bytes.NewReader(b))
+	err := dec.Decode(a)
+	if err != nil {
+		t.Fatalf("%v", err)
+	}
+	assert.Equal(t, 4, len(a.A), "length of string array not as expected")
+	for _, s := range a.A {
+		if s != TestStr {
+			t.Fatalf("string array does not contain the right values")
+		}
+	}
+}
+
+func Test_readNonConformantStringMultiDimensionalArray(t *testing.T) {
+	ac := make([]byte, 4, 4)
+	binary.LittleEndian.PutUint32(ac, uint32(len(TestStrUTF16Hex)/4)) // actual count of number of uint16 bytes
+	strb := "00000000" + hex.EncodeToString(ac) + TestStrUTF16Hex     // offset(0):actual count:data
+	var hexStr string
+	for i := 0; i < 12; i++ {
+		hexStr = hexStr + strb + "0000"
+	}
+	hexStr = TestHeader + "0000000002000000" + "0000000003000000" + "0000000002000000" + hexStr
+	b, _ := hex.DecodeString(hexStr)
+	a := new(TestStructWithNonConformantStringMultiArray)
+	dec := NewDecoder(bytes.NewReader(b))
+	err := dec.Decode(a)
+	if err != nil {
+		t.Fatalf("%v", err)
+	}
+	ar := [][][]string{
+		{
+			{TestStr, TestStr},
+			{TestStr, TestStr},
+			{TestStr, TestStr},
+		},
+		{
+			{TestStr, TestStr},
+			{TestStr, TestStr},
+			{TestStr, TestStr},
+		},
+	}
+	assert.Equal(t, ar, a.A, "fixed multi-dimensional string array not as expected")
+}
+
+func Test_readFixedStringUniDimensionalArray(t *testing.T) {
+	ac := make([]byte, 4, 4)
+	binary.LittleEndian.PutUint32(ac, uint32(len(TestStrUTF16Hex)/4))                  // actual count of number of uint16 bytes
+	hexStr := "00000000" + hex.EncodeToString(ac) + TestStrUTF16Hex                    // offset(0):actual count:data
+	hexStr = TestHeader + hexStr + "0000" + hexStr + "0000" + hexStr + "0000" + hexStr // header:offset for 1st dim:actual for 1st dim:string array elements(4) with offset and actual counts. Need to include some bytes for alignment.
+	b, _ := hex.DecodeString(hexStr)
+	a := new(TestStructWithFixedStringUniArray)
+	dec := NewDecoder(bytes.NewReader(b))
+	err := dec.Decode(a)
+	if err != nil {
+		t.Fatalf("%v", err)
+	}
+	for _, s := range a.A {
+		if s != TestStr {
+			t.Fatalf("string array does not contain the right values")
+		}
+	}
+}
+
+func Test_readFixedStringMultiDimensionalArray(t *testing.T) {
+	ac := make([]byte, 4, 4)
+	binary.LittleEndian.PutUint32(ac, uint32(len(TestStrUTF16Hex)/4)) // actual count of number of uint16 bytes
+	strb := "00000000" + hex.EncodeToString(ac) + TestStrUTF16Hex     // offset(0):actual count:data
+	var hexStr string
+	for i := 0; i < 12; i++ {
+		hexStr = hexStr + strb + "0000"
+	}
+	hexStr = TestHeader + hexStr
+	b, _ := hex.DecodeString(hexStr)
+	a := new(TestStructWithFixedStringMultiArray)
+	dec := NewDecoder(bytes.NewReader(b))
+	err := dec.Decode(a)
+	if err != nil {
+		t.Fatalf("%v", err)
+	}
+	ar := [2][3][2]string{
+		{
+			{TestStr, TestStr},
+			{TestStr, TestStr},
+			{TestStr, TestStr},
+		},
+		{
+			{TestStr, TestStr},
+			{TestStr, TestStr},
+			{TestStr, TestStr},
+		},
+	}
+	assert.Equal(t, ar, a.A, "fixed multi-dimensional string array not as expected")
+}
diff --git a/ndr/tags.go b/ndr/tags.go
new file mode 100644
index 0000000..01657e0
--- /dev/null
+++ b/ndr/tags.go
@@ -0,0 +1,69 @@
+package ndr
+
+import (
+	"fmt"
+	"reflect"
+	"strings"
+)
+
+const ndrNameSpace = "ndr"
+
+type tags struct {
+	Values []string
+	Map    map[string]string
+}
+
+// parse the struct field tags and extract the ndr related ones.
+// format of tag ndr:"value,key:value1,value2"
+func parseTags(st reflect.StructTag) tags {
+	s := st.Get(ndrNameSpace)
+	t := tags{
+		Values: []string{},
+		Map:    make(map[string]string),
+	}
+	if s != "" {
+		ndrTags := strings.Trim(s, `"`)
+		for _, tag := range strings.Split(ndrTags, ",") {
+			if strings.Contains(tag, ":") {
+				m := strings.SplitN(tag, ":", 2)
+				t.Map[m[0]] = m[1]
+			} else {
+				t.Values = append(t.Values, tag)
+			}
+		}
+	}
+	return t
+}
+
+func appendTag(t reflect.StructTag, s string) reflect.StructTag {
+	ts := t.Get(ndrNameSpace)
+	ts = fmt.Sprintf(`%s"%s,%s"`, ndrNameSpace, ts, s)
+	return reflect.StructTag(ts)
+}
+
+func (t *tags) StructTag() reflect.StructTag {
+	mv := t.Values
+	for key, val := range t.Map {
+		mv = append(mv, key+":"+val)
+	}
+	s := ndrNameSpace + ":" + `"` + strings.Join(mv, ",") + `"`
+	return reflect.StructTag(s)
+}
+
+func (t *tags) delete(s string) {
+	for i, x := range t.Values {
+		if x == s {
+			t.Values = append(t.Values[:i], t.Values[i+1:]...)
+		}
+	}
+	delete(t.Map, s)
+}
+
+func (t *tags) HasValue(s string) bool {
+	for _, v := range t.Values {
+		if v == s {
+			return true
+		}
+	}
+	return false
+}
diff --git a/ndr/tags_test.go b/ndr/tags_test.go
new file mode 100644
index 0000000..6ad7776
--- /dev/null
+++ b/ndr/tags_test.go
@@ -0,0 +1,37 @@
+package ndr
+
+import (
+	"reflect"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+type Test struct {
+	A int `ndr:"value"`
+	B int `ndr:"key:value"`
+	C int `ndr:"value1,key:value2"`
+	D int `dr:"value"`
+}
+
+func TestParseTags(t *testing.T) {
+	var test Test
+	tag0 := reflect.TypeOf(test).Field(0).Tag
+	tag1 := reflect.TypeOf(test).Field(1).Tag
+	tag2 := reflect.TypeOf(test).Field(2).Tag
+	tag3 := reflect.TypeOf(test).Field(3).Tag
+
+	tg0 := parseTags(tag0)
+	tg1 := parseTags(tag1)
+	tg2 := parseTags(tag2)
+	tg3 := parseTags(tag3)
+
+	assert.Equal(t, []string{"value"}, tg0.Values, "Values not as expected for test %d", 0)
+	assert.Equal(t, make(map[string]string), tg0.Map, "Map not as expected for test %d", 0)
+	assert.Equal(t, []string{}, tg1.Values, "Values not as expected for test %d", 1)
+	assert.Equal(t, map[string]string{"key": "value"}, tg1.Map, "Map not as expected for test %d", 1)
+	assert.Equal(t, []string{"value1"}, tg2.Values, "Values not as expected for test %d", 2)
+	assert.Equal(t, map[string]string{"key": "value2"}, tg2.Map, "Map not as expected for test %d", 2)
+	assert.Equal(t, []string{}, tg3.Values, "Values not as expected for test %d", 3)
+	assert.Equal(t, make(map[string]string), tg3.Map, "Map not as expected for test %d", 3)
+}
diff --git a/ndr/union.go b/ndr/union.go
new file mode 100644
index 0000000..6a657fa
--- /dev/null
+++ b/ndr/union.go
@@ -0,0 +1,57 @@
+package ndr
+
+import (
+	"errors"
+	"fmt"
+	"reflect"
+)
+
+// Union interface must be implemented by structs that will be unmarshaled into from the NDR byte stream union representation.
+// The union's discriminating tag will be passed to the SwitchFunc method.
+// The discriminating tag field must have the struct tag: `ndr:"unionTag"`
+// If the union is encapsulated the discriminating tag field must have the struct tag: `ndr:"encapsulated"`
+// The possible value fields that can be selected from must have the struct tag: `ndr:"unionField"`
+type Union interface {
+	SwitchFunc(t interface{}) string
+}
+
+// Union related constants such as struct tag values
+const (
+	unionSelectionFuncName = "SwitchFunc"
+	TagEncapsulated        = "encapsulated"
+	TagUnionTag            = "unionTag"
+	TagUnionField          = "unionField"
+)
+
+func (dec *Decoder) isUnion(field reflect.Value, tag reflect.StructTag) (r reflect.Value) {
+	ndrTag := parseTags(tag)
+	if !ndrTag.HasValue(TagUnionTag) {
+		return
+	}
+	r = field
+	// For a non-encapsulated union, the discriminant is marshalled into the transmitted data stream twice: once as the
+	// field or parameter, which is referenced by the switch_is construct, in the procedure argument list; and once as
+	// the first part of the union representation.
+	if !ndrTag.HasValue(TagEncapsulated) {
+		dec.r.Discard(int(r.Type().Size()))
+	}
+	return
+}
+
+// unionSelectedField returns the field name of which of the union values to fill
+func unionSelectedField(union, discriminant reflect.Value) (string, error) {
+	if !union.Type().Implements(reflect.TypeOf(new(Union)).Elem()) {
+		return "", errors.New("struct does not implement union interface")
+	}
+	args := []reflect.Value{discriminant}
+	// Call the SelectFunc of the union struct to find the name of the field to fill with the value selected.
+	sf := union.MethodByName(unionSelectionFuncName)
+	if !sf.IsValid() {
+		return "", fmt.Errorf("could not find a selection function called %s in the unions struct representation", unionSelectionFuncName)
+	}
+	f := sf.Call(args)
+	if f[0].Kind() != reflect.String || f[0].String() == "" {
+		return "", fmt.Errorf("the union select function did not return a string for the name of the field to fill")
+	}
+	return f[0].String(), nil
+}
diff --git a/ndr/union_test.go b/ndr/union_test.go
new file mode 100644
index 0000000..50013f6
--- /dev/null
+++ b/ndr/union_test.go
@@ -0,0 +1,104 @@
+package ndr
+
+import (
+	"bytes"
+	"encoding/hex"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+const (
+	testUnionSelected1Enc    = "0100000001"
+	testUnionSelected2Enc    = "020000000200"
+	testUnionSelected1NonEnc = "010000000100000001"
+	testUnionSelected2NonEnc = "02000000020000000200"
+)
+
+type testUnionEncapsulated struct {
+	Tag    uint32 `ndr:"unionTag,encapsulated"`
+	Value1 uint8  `ndr:"unionField"`
+	Value2 uint16 `ndr:"unionField"`
+}
+
+type testUnionNonEncapsulated struct {
+	Tag    uint32 `ndr:"unionTag"`
+	Value1 uint8  `ndr:"unionField"`
+	Value2 uint16 `ndr:"unionField"`
+}
+
+func (u testUnionEncapsulated) SwitchFunc(tag interface{}) string {
+	t := tag.(uint32)
+	switch t {
+	case 1:
+		return "Value1"
+	case 2:
+		return "Value2"
+	}
+	return ""
+}
+
+func (u testUnionNonEncapsulated) SwitchFunc(tag interface{}) string {
+	t := tag.(uint32)
+	switch t {
+	case 1:
+		return "Value1"
+	case 2:
+		return "Value2"
+	}
+	return ""
+}
+
+func Test_readUnionEncapsulated(t *testing.T) {
+	var tests = []struct {
+		Hex string
+		Tag uint32
+		V1  uint8
+		V2  uint16
+	}{
+		{testUnionSelected1Enc, uint32(1), uint8(1), uint16(0)},
+		{testUnionSelected2Enc, uint32(2), uint8(0), uint16(2)},
+	}
+
+	for i, test := range tests {
+		a := new(testUnionEncapsulated)
+		hexStr := TestHeader + test.Hex
+		b, _ := hex.DecodeString(hexStr)
+		dec := NewDecoder(bytes.NewReader(b))
+		err := dec.Decode(a)
+		if err != nil {
+			t.Fatalf("test %d: %v", i+1, err)
+		}
+		assert.Equal(t, test.Tag, a.Tag, "Tag value not as expected for test: %d", i+1)
+		assert.Equal(t, test.V1, a.Value1, "Value1 not as expected for test: %d", i+1)
+		assert.Equal(t, test.V2, a.Value2, "Value2 value not as expected for test: %d", i+1)
+
+	}
+}
+
+func Test_readUnionNonEncapsulated(t *testing.T) {
+	var tests = []struct {
+		Hex string
+		Tag uint32
+		V1  uint8
+		V2  uint16
+	}{
+		{testUnionSelected1NonEnc, uint32(1), uint8(1), uint16(0)},
+		{testUnionSelected2NonEnc, uint32(2), uint8(0), uint16(2)},
+	}
+
+	for i, test := range tests {
+		a := new(testUnionNonEncapsulated)
+		hexStr := TestHeader + test.Hex
+		b, _ := hex.DecodeString(hexStr)
+		dec := NewDecoder(bytes.NewReader(b))
+		err := dec.Decode(a)
+		if err != nil {
+			t.Fatalf("test %d: %v", i+1, err)
+		}
+		assert.Equal(t, test.Tag, a.Tag, "Tag value not as expected for test: %d", i+1)
+		assert.Equal(t, test.V1, a.Value1, "Value1 not as expected for test: %d", i+1)
+		assert.Equal(t, test.V2, a.Value2, "Value2 value not as expected for test: %d", i+1)
+
+	}
+}
diff --git a/v2/README.md b/v2/README.md
new file mode 100644
index 0000000..b39b0f2
--- /dev/null
+++ b/v2/README.md
@@ -0,0 +1,13 @@
+# RPC
+[![GoDoc](https://godoc.org/github.com/jcmturner/rpc/v2?status.svg)](https://godoc.org/github.com/jcmturner/rpc/v2) [![Go Report Card](https://goreportcard.com/badge/github.com/jcmturner/rpc/v2)](https://goreportcard.com/report/github.com/jcmturner/rpc/v2) 
+
+This project relates to [CDE 1.1: Remote Procedure Call](http://pubs.opengroup.org/onlinepubs/9629399/)
+
+It is a partial implementation that mainly focuses on unmarshaling NDR encoded byte streams into Go structures.
+
+v2 has been released to make use of Go modules
+
+Please import as below
+```
+import "github.com/jcmturner/rpc/v2/<sub package>"
+```
diff --git a/v2/examples/examples.go b/v2/examples/examples.go
new file mode 100644
index 0000000..b2172c0
--- /dev/null
+++ b/v2/examples/examples.go
@@ -0,0 +1,43 @@
+// Package examples provides example decoding of NDR byte streams
+package examples
+
+import "github.com/jcmturner/rpc/v2/mstypes"
+
+// KerbValidationInfo
+type KerbValidationInfo struct {
+	LogOnTime              mstypes.FileTime
+	LogOffTime             mstypes.FileTime
+	KickOffTime            mstypes.FileTime
+	PasswordLastSet        mstypes.FileTime
+	PasswordCanChange      mstypes.FileTime
+	PasswordMustChange     mstypes.FileTime
+	EffectiveName          mstypes.RPCUnicodeString
+	FullName               mstypes.RPCUnicodeString
+	LogonScript            mstypes.RPCUnicodeString
+	ProfilePath            mstypes.RPCUnicodeString
+	HomeDirectory          mstypes.RPCUnicodeString
+	HomeDirectoryDrive     mstypes.RPCUnicodeString
+	LogonCount             uint16
+	BadPasswordCount       uint16
+	UserID                 uint32
+	PrimaryGroupID         uint32
+	GroupCount             uint32
+	GroupIDs               []mstypes.GroupMembership `ndr:"pointer,conformant"`
+	UserFlags              uint32
+	UserSessionKey         mstypes.UserSessionKey
+	LogonServer            mstypes.RPCUnicodeString
+	LogonDomainName        mstypes.RPCUnicodeString
+	LogonDomainID          mstypes.RPCSID `ndr:"pointer"`
+	Reserved1              [2]uint32      // Has 2 elements
+	UserAccountControl     uint32
+	SubAuthStatus          uint32
+	LastSuccessfulILogon   mstypes.FileTime
+	LastFailedILogon       mstypes.FileTime
+	FailedILogonCount      uint32
+	Reserved3              uint32
+	SIDCount               uint32
+	ExtraSIDs              []mstypes.KerbSidAndAttributes `ndr:"pointer,conformant"`
+	ResourceGroupDomainSID mstypes.RPCSID                 `ndr:"pointer"`
+	ResourceGroupCount     uint32
+	ResourceGroupIDs       []mstypes.GroupMembership `ndr:"pointer,conformant"`
+}
diff --git a/v2/examples/examples_test.go b/v2/examples/examples_test.go
new file mode 100644
index 0000000..0a06170
--- /dev/null
+++ b/v2/examples/examples_test.go
@@ -0,0 +1,266 @@
+package examples
+
+import (
+	"bytes"
+	"encoding/hex"
+	"testing"
+	"time"
+
+	"github.com/jcmturner/rpc/v2/mstypes"
+	"github.com/jcmturner/rpc/v2/ndr"
+	"github.com/stretchr/testify/assert"
+)
+
+const (
+	KerbValidationInfoMS     = "01100800cccccccca00400000000000000000200d186660f656ac601ffffffffffffff7fffffffffffffff7f17d439fe784ac6011794a328424bc601175424977a81c60108000800040002002400240008000200120012000c0002000000000010000200000000001400020000000000180002005410000097792c00010200001a0000001c000200200000000000000000000000000000000000000016001800200002000a000c002400020028000200000000000000000010000000000000000000000000000000000000000000000000000000000000000d0000002c0002000000000000000000000000000400000000000000040000006c007a00680075001200000000000000120000004c0069007100690061006e00670028004c006100720072007900290020005a00680075000900000000000000090000006e0074006400730032002e0062006100740000000000000000000000000000000000000000000000000000000000000000000000000000001a00000061c433000700000009c32d00070000005eb4320007000000010200000700000097b92c00070000002bf1320007000000ce30330007000000a72e2e00070000002af132000700000098b92c000700000062c4330007000000940133000700000076c4330007000000aefe2d000700000032d22c00070000001608320007000000425b2e00070000005fb4320007000000ca9c35000700000085442d0007000000c2f0320007000000e9ea310007000000ed8e2e0007000000b6eb310007000000ab2e2e0007000000720e2e00070000000c000000000000000b0000004e0054004400450056002d00440043002d003000350000000600000000000000050000004e0054004400450056000000040000000104000000000005150000005951b81766725d2564633b0b0d0000003000020007000000340002000700002038000200070000203c000200070000204000020007000020440002000700002048000200070000204c000200070000205000020007000020540002000700002058000200070000205c00020007000020600002000700002005000000010500000000000515000000b9301b2eb7414c6c8c3b351501020000050000000105000000000005150000005951b81766725d2564633b0b74542f00050000000105000000000005150000005951b81766725d2564633b0be8383200050000000105000000000005150000005951b81766725d2564633b0bcd383200050000000105000000000005150000005951b81766725d2564633b0b5db43200050000000105000000000005150000005951b81766725d2564633b0b41163500050000000105000000000005150000005951b81766725d2564633b0be8ea3100050000000105000000000005150000005951b81766725d2564633b0bc1193200050000000105000000000005150000005951b81766725d2564633b0b29f13200050000000105000000000005150000005951b81766725d2564633b0b0f5f2e00050000000105000000000005150000005951b81766725d2564633b0b2f5b2e00050000000105000000000005150000005951b81766725d2564633b0bef8f3100050000000105000000000005150000005951b81766725d2564633b0b075f2e0000000000"
+	KerbValidationInfoGoKRB5 = "01100800cccccccc180200000000000000000200058e4fdd80c6d201ffffffffffffff7fffffffffffffff7fcc27969c39c6d201cce7ffc602c7d201ffffffffffffff7f12001200040002001600160008000200000000000c000200000000001000020000000000140002000000000018000200d80000005104000001020000050000001c000200200000000000000000000000000000000000000008000a002000020008000a00240002002800020000000000000000001002000000000000000000000000000000000000000000000000000000000000020000002c00020000000000000000000000000009000000000000000900000074006500730074007500730065007200310000000b000000000000000b000000540065007300740031002000550073006500720031000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000050000000102000007000000540400000700000055040000070000005b040000070000005c0400000700000005000000000000000400000041004400440043000500000000000000040000005400450053005400040000000104000000000005150000004c86cebca07160e63fdce8870200000030000200070000203400020007000020050000000105000000000005150000004c86cebca07160e63fdce8875a040000050000000105000000000005150000004c86cebca07160e63fdce8875704000000000000"
+	KerbValidationInfoTrust  = "01100800cccccccc000200000000000000000200c30bcc79e444d301ffffffffffffff7fffffffffffffff7fc764125a0842d301c7247c84d142d301ffffffffffffff7f12001200040002001600160008000200000000000c0002000000000010000200000000001400020000000000180002002e0000005204000001020000030000001c0002002002000000000000000000000000000000000000060008002000020008000a00240002002800020000000000000000001002000000000000000000000000000000000000000000000000000000000000010000002c00020034000200020000003800020009000000000000000900000074006500730074007500730065007200310000000b000000000000000b0000005400650073007400310020005500730065007200310000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000056040000070000000102000007000000550400000700000004000000000000000300000055004400430000000500000000000000040000005500530045005200040000000104000000000005150000002057308834e7d1d0a2fb0444010000003000020007000000010000000101000000000012010000000400000001040000000000051500000062dc8db6c8705249b5459e75020000005304000007000020540400000700002000000000"
+)
+
+func TestExample_KerbValidationInfo(t *testing.T) {
+	b, _ := hex.DecodeString(KerbValidationInfoMS)
+	k := new(KerbValidationInfo)
+	dec := ndr.NewDecoder(bytes.NewReader(b))
+	err := dec.Decode(k)
+	if err != nil {
+		t.Errorf("%v", err)
+	}
+	assert.Equal(t, time.Date(2006, 4, 28, 1, 42, 50, 925640100, time.UTC), k.LogOnTime.Time(), "LogOnTime not as expected")
+	assert.Equal(t, time.Date(2185, 7, 21, 23, 34, 33, 709551516, time.UTC), k.LogOffTime.Time(), "LogOffTime not as expected")
+	assert.Equal(t, time.Date(2185, 7, 21, 23, 34, 33, 709551516, time.UTC), k.KickOffTime.Time(), "KickOffTime not as expected")
+	assert.Equal(t, time.Date(2006, 3, 18, 10, 44, 54, 837147900, time.UTC), k.PasswordLastSet.Time(), "PasswordLastSet not as expected")
+	assert.Equal(t, time.Date(2006, 3, 19, 10, 44, 54, 837147900, time.UTC), k.PasswordCanChange.Time(), "PasswordCanChange not as expected")
+
+	assert.Equal(t, "lzhu", k.EffectiveName.String(), "EffectiveName not as expected")
+	assert.Equal(t, "Liqiang(Larry) Zhu", k.FullName.String(), "EffectiveName not as expected")
+	assert.Equal(t, "ntds2.bat", k.LogonScript.String(), "EffectiveName not as expected")
+	assert.Equal(t, "", k.ProfilePath.String(), "EffectiveName not as expected")
+	assert.Equal(t, "", k.HomeDirectory.String(), "EffectiveName not as expected")
+	assert.Equal(t, "", k.HomeDirectoryDrive.String(), "EffectiveName not as expected")
+
+	assert.Equal(t, uint16(4180), k.LogonCount, "LogonCount not as expected")
+	assert.Equal(t, uint16(0), k.BadPasswordCount, "BadPasswordCount not as expected")
+	assert.Equal(t, uint32(2914711), k.UserID, "UserID not as expected")
+	assert.Equal(t, uint32(513), k.PrimaryGroupID, "PrimaryGroupID not as expected")
+	assert.Equal(t, uint32(26), k.GroupCount, "GroupCount not as expected")
+
+	gids := []mstypes.GroupMembership{
+		{RelativeID: 3392609, Attributes: 7},
+		{RelativeID: 2999049, Attributes: 7},
+		{RelativeID: 3322974, Attributes: 7},
+		{RelativeID: 513, Attributes: 7},
+		{RelativeID: 2931095, Attributes: 7},
+		{RelativeID: 3338539, Attributes: 7},
+		{RelativeID: 3354830, Attributes: 7},
+		{RelativeID: 3026599, Attributes: 7},
+		{RelativeID: 3338538, Attributes: 7},
+		{RelativeID: 2931096, Attributes: 7},
+		{RelativeID: 3392610, Attributes: 7},
+		{RelativeID: 3342740, Attributes: 7},
+		{RelativeID: 3392630, Attributes: 7},
+		{RelativeID: 3014318, Attributes: 7},
+		{RelativeID: 2937394, Attributes: 7},
+		{RelativeID: 3278870, Attributes: 7},
+		{RelativeID: 3038018, Attributes: 7},
+		{RelativeID: 3322975, Attributes: 7},
+		{RelativeID: 3513546, Attributes: 7},
+		{RelativeID: 2966661, Attributes: 7},
+		{RelativeID: 3338434, Attributes: 7},
+		{RelativeID: 3271401, Attributes: 7},
+		{RelativeID: 3051245, Attributes: 7},
+		{RelativeID: 3271606, Attributes: 7},
+		{RelativeID: 3026603, Attributes: 7},
+		{RelativeID: 3018354, Attributes: 7},
+	}
+	assert.Equal(t, gids, k.GroupIDs, "GroupIDs not as expected")
+
+	assert.Equal(t, uint32(32), k.UserFlags, "UserFlags not as expected")
+
+	assert.Equal(t, mstypes.UserSessionKey{CypherBlock: [2]mstypes.CypherBlock{{Data: [8]byte{}}, {Data: [8]byte{}}}}, k.UserSessionKey, "UserSessionKey not as expected")
+
+	assert.Equal(t, "NTDEV-DC-05", k.LogonServer.Value, "LogonServer not as expected")
+	assert.Equal(t, "NTDEV", k.LogonDomainName.Value, "LogonDomainName not as expected")
+
+	assert.Equal(t, "S-1-5-21-397955417-626881126-188441444", k.LogonDomainID.String(), "LogonDomainID not as expected")
+
+	assert.Equal(t, uint32(16), k.UserAccountControl, "UserAccountControl not as expected")
+	assert.Equal(t, uint32(0), k.SubAuthStatus, "SubAuthStatus not as expected")
+	assert.Equal(t, time.Date(2185, 7, 21, 23, 34, 33, 709551616, time.UTC), k.LastSuccessfulILogon.Time(), "LastSuccessfulILogon not as expected")
+	assert.Equal(t, time.Date(2185, 7, 21, 23, 34, 33, 709551616, time.UTC), k.LastFailedILogon.Time(), "LastSuccessfulILogon not as expected")
+	assert.Equal(t, uint32(0), k.FailedILogonCount, "FailedILogonCount not as expected")
+
+	assert.Equal(t, uint32(13), k.SIDCount, "SIDCount not as expected")
+	assert.Equal(t, int(k.SIDCount), len(k.ExtraSIDs), "SIDCount and size of ExtraSIDs list are not the same")
+
+	var es = []struct {
+		sid  string
+		attr uint32
+	}{
+		{"S-1-5-21-773533881-1816936887-355810188-513", uint32(7)},
+		{"S-1-5-21-397955417-626881126-188441444-3101812", uint32(536870919)},
+		{"S-1-5-21-397955417-626881126-188441444-3291368", uint32(536870919)},
+		{"S-1-5-21-397955417-626881126-188441444-3291341", uint32(536870919)},
+		{"S-1-5-21-397955417-626881126-188441444-3322973", uint32(536870919)},
+		{"S-1-5-21-397955417-626881126-188441444-3479105", uint32(536870919)},
+		{"S-1-5-21-397955417-626881126-188441444-3271400", uint32(536870919)},
+		{"S-1-5-21-397955417-626881126-188441444-3283393", uint32(536870919)},
+		{"S-1-5-21-397955417-626881126-188441444-3338537", uint32(536870919)},
+		{"S-1-5-21-397955417-626881126-188441444-3038991", uint32(536870919)},
+		{"S-1-5-21-397955417-626881126-188441444-3037999", uint32(536870919)},
+		{"S-1-5-21-397955417-626881126-188441444-3248111", uint32(536870919)},
+	}
+	for i, s := range es {
+		assert.Equal(t, s.sid, k.ExtraSIDs[i].SID.String(), "ExtraSID SID value not as epxected")
+		assert.Equal(t, s.attr, k.ExtraSIDs[i].Attributes, "ExtraSID Attributes value not as epxected")
+	}
+
+	assert.Equal(t, uint8(0), k.ResourceGroupDomainSID.SubAuthorityCount, "ResourceGroupDomainSID not as expected")
+	assert.Equal(t, 0, len(k.ResourceGroupIDs), "ResourceGroupIDs not as expected")
+
+	b, _ = hex.DecodeString(KerbValidationInfoGoKRB5)
+	k2 := new(KerbValidationInfo)
+	dec = ndr.NewDecoder(bytes.NewReader(b))
+	err = dec.Decode(k2)
+	if err != nil {
+		t.Errorf("%v", err)
+	}
+
+	assert.Equal(t, time.Date(2017, 5, 6, 15, 53, 11, 825766900, time.UTC), k2.LogOnTime.Time(), "LogOnTime not as expected")
+	assert.Equal(t, time.Date(2185, 7, 21, 23, 34, 33, 709551516, time.UTC), k2.LogOffTime.Time(), "LogOffTime not as expected")
+	assert.Equal(t, time.Date(2185, 7, 21, 23, 34, 33, 709551516, time.UTC), k2.KickOffTime.Time(), "KickOffTime not as expected")
+	assert.Equal(t, time.Date(2017, 5, 6, 7, 23, 8, 968750000, time.UTC), k2.PasswordLastSet.Time(), "PasswordLastSet not as expected")
+	assert.Equal(t, time.Date(2017, 5, 7, 7, 23, 8, 968750000, time.UTC), k2.PasswordCanChange.Time(), "PasswordCanChange not as expected")
+
+	assert.Equal(t, "testuser1", k2.EffectiveName.String(), "EffectiveName not as expected")
+	assert.Equal(t, "Test1 User1", k2.FullName.String(), "EffectiveName not as expected")
+	assert.Equal(t, "", k2.LogonScript.String(), "EffectiveName not as expected")
+	assert.Equal(t, "", k2.ProfilePath.String(), "EffectiveName not as expected")
+	assert.Equal(t, "", k2.HomeDirectory.String(), "EffectiveName not as expected")
+	assert.Equal(t, "", k2.HomeDirectoryDrive.String(), "EffectiveName not as expected")
+
+	assert.Equal(t, uint16(216), k2.LogonCount, "LogonCount not as expected")
+	assert.Equal(t, uint16(0), k2.BadPasswordCount, "BadPasswordCount not as expected")
+	assert.Equal(t, uint32(1105), k2.UserID, "UserID not as expected")
+	assert.Equal(t, uint32(513), k2.PrimaryGroupID, "PrimaryGroupID not as expected")
+	assert.Equal(t, uint32(5), k2.GroupCount, "GroupCount not as expected")
+
+	gids = []mstypes.GroupMembership{
+		{RelativeID: 513, Attributes: 7},
+		{RelativeID: 1108, Attributes: 7},
+		{RelativeID: 1109, Attributes: 7},
+		{RelativeID: 1115, Attributes: 7},
+		{RelativeID: 1116, Attributes: 7},
+	}
+	assert.Equal(t, gids, k2.GroupIDs, "GroupIDs not as expected")
+
+	assert.Equal(t, uint32(32), k2.UserFlags, "UserFlags not as expected")
+
+	assert.Equal(t, mstypes.UserSessionKey{CypherBlock: [2]mstypes.CypherBlock{{Data: [8]byte{}}, {Data: [8]byte{}}}}, k2.UserSessionKey, "UserSessionKey not as expected")
+
+	assert.Equal(t, "ADDC", k2.LogonServer.String(), "LogonServer not as expected")
+	assert.Equal(t, "TEST", k2.LogonDomainName.String(), "LogonDomainName not as expected")
+
+	assert.Equal(t, "S-1-5-21-3167651404-3865080224-2280184895", k2.LogonDomainID.String(), "LogonDomainID not as expected")
+
+	assert.Equal(t, uint32(528), k2.UserAccountControl, "UserAccountControl not as expected")
+	assert.Equal(t, uint32(0), k2.SubAuthStatus, "SubAuthStatus not as expected")
+	assert.Equal(t, time.Date(2185, 7, 21, 23, 34, 33, 709551616, time.UTC), k2.LastSuccessfulILogon.Time(), "LastSuccessfulILogon not as expected")
+	assert.Equal(t, time.Date(2185, 7, 21, 23, 34, 33, 709551616, time.UTC), k2.LastFailedILogon.Time(), "LastSuccessfulILogon not as expected")
+	assert.Equal(t, uint32(0), k2.FailedILogonCount, "FailedILogonCount not as expected")
+
+	assert.Equal(t, uint32(2), k2.SIDCount, "SIDCount not as expected")
+	assert.Equal(t, int(k2.SIDCount), len(k2.ExtraSIDs), "SIDCount and size of ExtraSIDs list are not the same")
+
+	var es2 = []struct {
+		sid  string
+		attr uint32
+	}{
+		{"S-1-5-21-3167651404-3865080224-2280184895-1114", uint32(536870919)},
+		{"S-1-5-21-3167651404-3865080224-2280184895-1111", uint32(536870919)},
+	}
+	for i, s := range es2 {
+		assert.Equal(t, s.sid, k2.ExtraSIDs[i].SID.String(), "ExtraSID SID value not as expected")
+		assert.Equal(t, s.attr, k2.ExtraSIDs[i].Attributes, "ExtraSID Attributes value not as expected")
+	}
+
+	assert.Equal(t, uint8(0), k2.ResourceGroupDomainSID.SubAuthorityCount, "ResourceGroupDomainSID not as expected")
+	assert.Equal(t, 0, len(k2.ResourceGroupIDs), "ResourceGroupIDs not as expected")
+
+	b, _ = hex.DecodeString(KerbValidationInfoTrust)
+	k = new(KerbValidationInfo)
+	dec = ndr.NewDecoder(bytes.NewReader(b))
+	err = dec.Decode(k)
+	if err != nil {
+		t.Errorf("%v", err)
+	}
+	assert.Equal(t, time.Date(2017, 10, 14, 12, 03, 41, 52409900, time.UTC), k.LogOnTime.Time(), "LogOnTime not as expected")
+	assert.Equal(t, time.Date(2185, 7, 21, 23, 34, 33, 709551516, time.UTC), k.LogOffTime.Time(), "LogOffTime not as expected")
+	assert.Equal(t, time.Date(2185, 7, 21, 23, 34, 33, 709551516, time.UTC), k.KickOffTime.Time(), "KickOffTime not as expected")
+	assert.Equal(t, time.Date(2017, 10, 10, 20, 42, 56, 220282300, time.UTC), k.PasswordLastSet.Time(), "PasswordLastSet not as expected")
+	assert.Equal(t, time.Date(2017, 10, 11, 20, 42, 56, 220282300, time.UTC), k.PasswordCanChange.Time(), "PasswordCanChange not as expected")
+
+	assert.Equal(t, "testuser1", k.EffectiveName.String(), "EffectiveName not as expected")
+	assert.Equal(t, "Test1 User1", k.FullName.String(), "EffectiveName not as expected")
+	assert.Equal(t, "", k.LogonScript.String(), "EffectiveName not as expected")
+	assert.Equal(t, "", k.ProfilePath.String(), "EffectiveName not as expected")
+	assert.Equal(t, "", k.HomeDirectory.String(), "EffectiveName not as expected")
+	assert.Equal(t, "", k.HomeDirectoryDrive.String(), "EffectiveName not as expected")
+
+	assert.Equal(t, uint16(46), k.LogonCount, "LogonCount not as expected")
+	assert.Equal(t, uint16(0), k.BadPasswordCount, "BadPasswordCount not as expected")
+	assert.Equal(t, uint32(1106), k.UserID, "UserID not as expected")
+	assert.Equal(t, uint32(513), k.PrimaryGroupID, "PrimaryGroupID not as expected")
+	assert.Equal(t, uint32(3), k.GroupCount, "GroupCount not as expected")
+
+	gids = []mstypes.GroupMembership{
+		{RelativeID: 1110, Attributes: 7},
+		{RelativeID: 513, Attributes: 7},
+		{RelativeID: 1109, Attributes: 7},
+	}
+	assert.Equal(t, gids, k.GroupIDs, "GroupIDs not as expected")
+
+	assert.Equal(t, uint32(544), k.UserFlags, "UserFlags not as expected")
+
+	assert.Equal(t, mstypes.UserSessionKey{CypherBlock: [2]mstypes.CypherBlock{{Data: [8]byte{}}, {Data: [8]byte{}}}}, k.UserSessionKey, "UserSessionKey not as expected")
+
+	assert.Equal(t, "UDC", k.LogonServer.Value, "LogonServer not as expected")
+	assert.Equal(t, "USER", k.LogonDomainName.Value, "LogonDomainName not as expected")
+
+	assert.Equal(t, "S-1-5-21-2284869408-3503417140-1141177250", k.LogonDomainID.String(), "LogonDomainID not as expected")
+
+	assert.Equal(t, uint32(528), k.UserAccountControl, "UserAccountControl not as expected")
+	assert.Equal(t, uint32(0), k.SubAuthStatus, "SubAuthStatus not as expected")
+	assert.Equal(t, time.Date(2185, 7, 21, 23, 34, 33, 709551616, time.UTC), k.LastSuccessfulILogon.Time(), "LastSuccessfulILogon not as expected")
+	assert.Equal(t, time.Date(2185, 7, 21, 23, 34, 33, 709551616, time.UTC), k.LastFailedILogon.Time(), "LastSuccessfulILogon not as expected")
+	assert.Equal(t, uint32(0), k.FailedILogonCount, "FailedILogonCount not as expected")
+
+	assert.Equal(t, uint32(1), k.SIDCount, "SIDCount not as expected")
+	assert.Equal(t, int(k.SIDCount), len(k.ExtraSIDs), "SIDCount and size of ExtraSIDs list are not the same")
+
+	es = []struct {
+		sid  string
+		attr uint32
+	}{
+		{"S-1-18-1", uint32(7)},
+	}
+	for i, s := range es {
+		assert.Equal(t, s.sid, k.ExtraSIDs[i].SID.String(), "ExtraSID SID value not as epxected")
+		assert.Equal(t, s.attr, k.ExtraSIDs[i].Attributes, "ExtraSID Attributes value not as epxected")
+	}
+
+	assert.Equal(t, uint8(4), k.ResourceGroupDomainSID.SubAuthorityCount, "ResourceGroupDomainSID not as expected")
+	assert.Equal(t, "S-1-5-21-3062750306-1230139592-1973306805", k.ResourceGroupDomainSID.String(), "ResourceGroupDomainSID value not as expected")
+	assert.Equal(t, 2, len(k.ResourceGroupIDs), "ResourceGroupIDs not as expected")
+	rgids := []mstypes.GroupMembership{
+		{RelativeID: 1107, Attributes: 536870919},
+		{RelativeID: 1108, Attributes: 536870919},
+	}
+	assert.Equal(t, rgids, k.ResourceGroupIDs, "ResourceGroupIDs not as expected")
+	//groupSids := []string{"S-1-5-21-2284869408-3503417140-1141177250-1110",
+	//	"S-1-5-21-2284869408-3503417140-1141177250-513",
+	//	"S-1-5-21-2284869408-3503417140-1141177250-1109",
+	//	"S-1-18-1",
+	//	"S-1-5-21-3062750306-1230139592-1973306805-1107",
+	//	"S-1-5-21-3062750306-1230139592-1973306805-1108"}
+	//assert.Equal(t, groupSids, k.GetGroupMembershipSIDs(), "GroupMembershipSIDs not as expected")
+}
diff --git a/v2/go.mod b/v2/go.mod
new file mode 100644
index 0000000..30cd9a5
--- /dev/null
+++ b/v2/go.mod
@@ -0,0 +1,8 @@
+module github.com/jcmturner/rpc/v2
+
+go 1.13
+
+require (
+	github.com/stretchr/testify v1.4.0
+	golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa
+)
diff --git a/v2/go.sum b/v2/go.sum
new file mode 100644
index 0000000..ffee468
--- /dev/null
+++ b/v2/go.sum
@@ -0,0 +1,15 @@
+github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
+github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa h1:F+8P+gmewFQYRk6JoLQLwjBCTu3mcIURZfNkVweuRKA=
+golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
diff --git a/v2/mstypes/claims.go b/v2/mstypes/claims.go
new file mode 100644
index 0000000..b9f535f
--- /dev/null
+++ b/v2/mstypes/claims.go
@@ -0,0 +1,152 @@
+package mstypes
+
+import (
+	"bytes"
+	"encoding/hex"
+	"errors"
+	"fmt"
+
+	"github.com/jcmturner/rpc/v2/ndr"
+	"golang.org/x/net/http2/hpack"
+)
+
+// Compression format assigned numbers. https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-xca/a8b7cb0a-92a6-4187-a23b-5e14273b96f8
+const (
+	CompressionFormatNone       uint16 = 0
+	CompressionFormatLZNT1      uint16 = 2 // LZNT1 aka ntfs compression
+	CompressionFormatXPress     uint16 = 3 // plain LZ77
+	CompressionFormatXPressHuff uint16 = 4 // LZ77+Huffman - The Huffman variant of the XPRESS compression format uses LZ77-style dictionary compression combined with Huffman coding.
+)
+
+// ClaimsSourceTypeAD https://msdn.microsoft.com/en-us/library/hh553809.aspx
+const ClaimsSourceTypeAD uint16 = 1
+
+// Claim Type assigned numbers
+const (
+	ClaimTypeIDInt64    uint16 = 1
+	ClaimTypeIDUInt64   uint16 = 2
+	ClaimTypeIDString   uint16 = 3
+	ClaimsTypeIDBoolean uint16 = 6
+)
+
+// ClaimsBlob implements https://msdn.microsoft.com/en-us/library/hh554119.aspx
+type ClaimsBlob struct {
+	Size        uint32
+	EncodedBlob EncodedBlob
+}
+
+// EncodedBlob are the bytes of the encoded Claims
+type EncodedBlob []byte
+
+// Size returns the size of the bytes of the encoded Claims
+func (b EncodedBlob) Size(c interface{}) int {
+	cb := c.(ClaimsBlob)
+	return int(cb.Size)
+}
+
+// ClaimsSetMetadata implements https://msdn.microsoft.com/en-us/library/hh554073.aspx
+type ClaimsSetMetadata struct {
+	ClaimsSetSize             uint32
+	ClaimsSetBytes            []byte `ndr:"pointer,conformant"`
+	CompressionFormat         uint16 // Enum see constants for options
+	UncompressedClaimsSetSize uint32
+	ReservedType              uint16
+	ReservedFieldSize         uint32
+	ReservedField             []byte `ndr:"pointer,conformant"`
+}
+
+// ClaimsSet reads the ClaimsSet type from the NDR encoded ClaimsSetBytes in the ClaimsSetMetadata
+func (m *ClaimsSetMetadata) ClaimsSet() (c ClaimsSet, err error) {
+	if len(m.ClaimsSetBytes) < 1 {
+		err = errors.New("no bytes available for ClaimsSet")
+		return
+	}
+	// TODO switch statement to decompress ClaimsSetBytes
+	switch m.CompressionFormat {
+	case CompressionFormatLZNT1:
+		s := hex.EncodeToString(m.ClaimsSetBytes)
+		err = fmt.Errorf("ClaimsSet compressed, format LZNT1 not currently supported: %s", s)
+		return
+	case CompressionFormatXPress:
+		s := hex.EncodeToString(m.ClaimsSetBytes)
+		err = fmt.Errorf("ClaimsSet compressed, format XPress not currently supported: %s", s)
+		return
+	case CompressionFormatXPressHuff:
+		var b []byte
+		buff := bytes.NewBuffer(b)
+		_, e := hpack.HuffmanDecode(buff, m.ClaimsSetBytes)
+		if e != nil {
+			err = fmt.Errorf("error deflating: %v", e)
+			return
+		}
+		m.ClaimsSetBytes = buff.Bytes()
+	}
+	dec := ndr.NewDecoder(bytes.NewReader(m.ClaimsSetBytes))
+	err = dec.Decode(&c)
+	return
+}
+
+// ClaimsSet implements https://msdn.microsoft.com/en-us/library/hh554122.aspx
+type ClaimsSet struct {
+	ClaimsArrayCount  uint32
+	ClaimsArrays      []ClaimsArray `ndr:"pointer,conformant"`
+	ReservedType      uint16
+	ReservedFieldSize uint32
+	ReservedField     []byte `ndr:"pointer,conformant"`
+}
+
+// ClaimsArray implements https://msdn.microsoft.com/en-us/library/hh536458.aspx
+type ClaimsArray struct {
+	ClaimsSourceType uint16
+	ClaimsCount      uint32
+	ClaimEntries     []ClaimEntry `ndr:"pointer,conformant"`
+}
+
+// ClaimEntry is a NDR union that implements https://msdn.microsoft.com/en-us/library/hh536374.aspx
+type ClaimEntry struct {
+	ID         string           `ndr:"pointer,conformant,varying"`
+	Type       uint16           `ndr:"unionTag"`
+	TypeInt64  ClaimTypeInt64   `ndr:"unionField"`
+	TypeUInt64 ClaimTypeUInt64  `ndr:"unionField"`
+	TypeString ClaimTypeString  `ndr:"unionField"`
+	TypeBool   ClaimTypeBoolean `ndr:"unionField"`
+}
+
+// SwitchFunc is the ClaimEntry union field selection function
+func (u ClaimEntry) SwitchFunc(_ interface{}) string {
+	switch u.Type {
+	case ClaimTypeIDInt64:
+		return "TypeInt64"
+	case ClaimTypeIDUInt64:
+		return "TypeUInt64"
+	case ClaimTypeIDString:
+		return "TypeString"
+	case ClaimsTypeIDBoolean:
+		return "TypeBool"
+	}
+	return ""
+}
+
+// ClaimTypeInt64 is a claim of type int64
+type ClaimTypeInt64 struct {
+	ValueCount uint32
+	Value      []int64 `ndr:"pointer,conformant"`
+}
+
+// ClaimTypeUInt64 is a claim of type uint64
+type ClaimTypeUInt64 struct {
+	ValueCount uint32
+	Value      []uint64 `ndr:"pointer,conformant"`
+}
+
+// ClaimTypeString is a claim of type string
+type ClaimTypeString struct {
+	ValueCount uint32
+	Value      []LPWSTR `ndr:"pointer,conformant"`
+}
+
+// ClaimTypeBoolean is a claim of type bool
+type ClaimTypeBoolean struct {
+	ValueCount uint32
+	Value      []bool `ndr:"pointer,conformant"`
+}
diff --git a/v2/mstypes/claims_test.go b/v2/mstypes/claims_test.go
new file mode 100644
index 0000000..21aaa10
--- /dev/null
+++ b/v2/mstypes/claims_test.go
@@ -0,0 +1,158 @@
+package mstypes
+
+import (
+	"bytes"
+	"compress/flate"
+	"encoding/hex"
+	"io/ioutil"
+	"testing"
+	"unicode/utf8"
+
+	"github.com/jcmturner/rpc/v2/ndr"
+	"github.com/stretchr/testify/assert"
+)
+
+const (
+	ClientClaimsInfoStr       = "01100800cccccccc000100000000000000000200d80000000400020000000000d8000000000000000000000000000000d800000001100800ccccccccc80000000000000000000200010000000400020000000000000000000000000001000000010000000100000008000200010000000c000200030003000100000010000200290000000000000029000000610064003a002f002f006500780074002f00730041004d004100630063006f0075006e0074004e0061006d0065003a0038003800640035006400390030003800350065006100350063003000630030000000000001000000140002000a000000000000000a00000074006500730074007500730065007200310000000000000000000000"
+	ClientClaimsInfoInt       = "01100800cccccccce00000000000000000000200b80000000400020000000000b8000000000000000000000000000000b800000001100800cccccccca80000000000000000000200010000000400020000000000000000000000000001000000010000000100000008000200010000000c0002000100010001000000100002002a000000000000002a000000610064003a002f002f006500780074002f006d007300440053002d0053007500700070006f00720074006500640045003a0038003800640035006400650061003800660031006100660035006600310039000000010000001c0000000000000000000000"
+	ClientClaimsInfoMulti     = "01100800cccccccc780100000000000000000200500100000400020000000000500100000000000000000000000000005001000001100800cccccccc400100000000000000000200010000000400020000000000000000000000000001000000010000000200000008000200020000000c000200010001000100000010000200140002000300030001000000180002002a000000000000002a000000610064003a002f002f006500780074002f006d007300440053002d0053007500700070006f00720074006500640045003a0038003800640035006400650061003800660031006100660035006600310039000000010000001c00000000000000290000000000000029000000610064003a002f002f006500780074002f00730041004d004100630063006f0075006e0074004e0061006d0065003a00380038006400350064003900300038003500650061003500630030006300300000000000010000001c0002000a000000000000000a000000740065007300740075007300650072003100000000000000"
+	ClientClaimsInfoMultiUint = "01100800ccccccccf00000000000000000000200c80000000400020000000000c8000000000000000000000000000000c800000001100800ccccccccb80000000000000000000200010000000400020000000000000000000000000001000000010000000100000008000200010000000c000200020002000400000010000200260000000000000026000000610064003a002f002f006500780074002f006f0062006a0065006300740043006c006100730073003a00380038006400350064006500370039003100650037006200320037006500360000000400000009000a000000000007000100000000000600010000000000000001000000000000000000"
+	ClientClaimsInfoMultiStr  = "01100800cccccccc480100000000000000000200200100000400020000000000200100000000000000000000000000002001000001100800cccccccc100100000000000000000200010000000400020000000000000000000000000001000000010000000100000008000200010000000c000200030003000400000010000200270000000000000027000000610064003a002f002f006500780074002f006f00740068006500720049007000500068006f006e0065003a003800380064003500640065003900660036006200340061006600390038003500000000000400000014000200180002001c000200200002000500000000000000050000007300740072003100000000000500000000000000050000007300740072003200000000000500000000000000050000007300740072003300000000000500000000000000050000007300740072003400000000000000000000000000"
+
+	ClaimsEntryIDStr            = "ad://ext/sAMAccountName:88d5d9085ea5c0c0"
+	ClaimsEntryValueStr         = "testuser1"
+	ClaimsEntryIDInt64          = "ad://ext/msDS-SupportedE:88d5dea8f1af5f19"
+	ClaimsEntryValueInt64 int64 = 28
+	ClaimsEntryIDUInt64         = "ad://ext/objectClass:88d5de791e7b27e6"
+
+	ClaimsSetBytesCompressionFormatXPressHuff = "738788888708080007000800080007000880088808088880886687888607080000808800800000000880000000000000806667080808787707767800080000000000000000000000000000000000000000000000080000000000000000000000000000000000080000000000000000000000000000000000000000000000000057000800800000007500000000000000050700000000000064760800080000008587007700000080650808000000000075888700000000700788000000000060677000000000007000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002e91f150e1ad792412496411f3904ff6027871529ef12043e4e79ab23c9f03aea65c0aca41e842b8d46f0321354538afe9f8c413b6e1a37377bca410ac8bc3b35398e51c0a290929e3ca764addf84e5ada9caa43c80c38de74d75cd0289a202641d26a950284dea25479c4376c3100720db619b9066d13c506c88a858a3305007490a40d7015a7528382a7c9ae54ab58204f01e1d8e044fee01925cbc46ad28cfa8d67c28e0216ce1de315aaaf43e4c88409002793b33a3823683680ce7d6606eca05f0cff9d06c88a0588dd5500d51de514570286fa148c007c699838d635b0b87ed420749011c94696fa202b002b0000"
+)
+
+func Test_TMP(t *testing.T) {
+	blz77huff, _ := hex.DecodeString("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000030230000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a8dc0000ff2601")
+	//b, _ := hex.DecodeString("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000050555555555555555555554544040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d8523ed794115be9195ff9d67cdf8d0400000000")
+	r := bytes.NewReader(blz77huff)
+	dr := flate.NewReader(r)
+	s, errr := ioutil.ReadAll(dr)
+	t.Logf("test: %s %v\n", string(s), errr)
+	ru, size := utf8.DecodeRuneInString("`")
+	t.Logf("%d %v\n", ru, size)
+}
+
+func Test_ClientClaimsInfoStr_Unmarshal(t *testing.T) {
+	b, _ := hex.DecodeString(ClientClaimsInfoStr)
+	m := new(ClaimsSetMetadata)
+	dec := ndr.NewDecoder(bytes.NewReader(b))
+	err := dec.Decode(m)
+	if err != nil {
+		t.Errorf("error decoding ClaimsSetMetadata %v", err)
+	}
+	k, err := m.ClaimsSet()
+	if err != nil {
+		t.Errorf("error retrieving ClaimsSet %v", err)
+	}
+	assert.Equal(t, uint32(1), k.ClaimsArrayCount, "claims array count not as expected")
+	assert.Equal(t, ClaimsSourceTypeAD, k.ClaimsArrays[0].ClaimsSourceType, "claims source type not as expected")
+	assert.Equal(t, uint32(1), k.ClaimsArrays[0].ClaimsCount, "claims count not as expected")
+	assert.Equal(t, uint16(3), k.ClaimsArrays[0].ClaimEntries[0].Type, "claims entry type not as expected")
+	assert.Equal(t, uint32(1), k.ClaimsArrays[0].ClaimEntries[0].TypeString.ValueCount, "claims value count not as expected")
+	assert.Equal(t, ClaimsEntryIDStr, k.ClaimsArrays[0].ClaimEntries[0].ID, "claims entry ID not as expected")
+	assert.Equal(t, []LPWSTR{{ClaimsEntryValueStr}}, k.ClaimsArrays[0].ClaimEntries[0].TypeString.Value, "claims value not as expected")
+	assert.Equal(t, CompressionFormatNone, m.CompressionFormat, "compression format not as expected")
+}
+
+func Test_ClientClaimsMultiValueUint_Unmarshal(t *testing.T) {
+	b, _ := hex.DecodeString(ClientClaimsInfoMultiUint)
+	m := new(ClaimsSetMetadata)
+	dec := ndr.NewDecoder(bytes.NewReader(b))
+	err := dec.Decode(m)
+	if err != nil {
+		t.Errorf("error decoding ClaimsSetMetadata %v", err)
+	}
+	k, err := m.ClaimsSet()
+	if err != nil {
+		t.Errorf("error retrieving ClaimsSet %v", err)
+	}
+
+	assert.Equal(t, uint32(1), k.ClaimsArrayCount, "claims array count not as expected")
+	assert.Equal(t, ClaimsSourceTypeAD, k.ClaimsArrays[0].ClaimsSourceType, "claims source type not as expected")
+	assert.Equal(t, uint32(1), k.ClaimsArrays[0].ClaimsCount, "claims count not as expected")
+	assert.Equal(t, ClaimTypeIDUInt64, k.ClaimsArrays[0].ClaimEntries[0].Type, "claims entry type not as expected")
+	assert.Equal(t, uint32(4), k.ClaimsArrays[0].ClaimEntries[0].TypeUInt64.ValueCount, "claims value count not as expected")
+	assert.Equal(t, ClaimsEntryIDUInt64, k.ClaimsArrays[0].ClaimEntries[0].ID, "claims entry ID not as expected")
+	assert.Equal(t, []uint64{655369, 65543, 65542, 65536}, k.ClaimsArrays[0].ClaimEntries[0].TypeUInt64.Value, "claims value not as expected")
+	assert.Equal(t, CompressionFormatNone, m.CompressionFormat, "compression format not as expected")
+}
+
+func Test_ClientClaimsInt_Unmarshal(t *testing.T) {
+	b, _ := hex.DecodeString(ClientClaimsInfoInt)
+	m := new(ClaimsSetMetadata)
+	dec := ndr.NewDecoder(bytes.NewReader(b))
+	err := dec.Decode(m)
+	if err != nil {
+		t.Errorf("error decoding ClaimsSetMetadata %v", err)
+	}
+	k, err := m.ClaimsSet()
+	if err != nil {
+		t.Errorf("error retrieving ClaimsSet %v", err)
+	}
+
+	assert.Equal(t, uint32(1), k.ClaimsArrayCount, "claims array count not as expected")
+	assert.Equal(t, ClaimsSourceTypeAD, k.ClaimsArrays[0].ClaimsSourceType, "claims source type not as expected")
+	assert.Equal(t, uint32(1), k.ClaimsArrays[0].ClaimsCount, "claims count not as expected")
+	assert.Equal(t, ClaimTypeIDInt64, k.ClaimsArrays[0].ClaimEntries[0].Type, "claims entry type not as expected")
+	assert.Equal(t, uint32(1), k.ClaimsArrays[0].ClaimEntries[0].TypeInt64.ValueCount, "claims value count not as expected")
+	assert.Equal(t, ClaimsEntryIDInt64, k.ClaimsArrays[0].ClaimEntries[0].ID, "claims entry ID not as expected")
+	assert.Equal(t, []int64{ClaimsEntryValueInt64}, k.ClaimsArrays[0].ClaimEntries[0].TypeInt64.Value, "claims value not as expected")
+	assert.Equal(t, CompressionFormatNone, m.CompressionFormat, "compression format not as expected")
+}
+
+func Test_ClientClaimsMultiValueStr_Unmarshal(t *testing.T) {
+	b, _ := hex.DecodeString(ClientClaimsInfoMultiStr)
+	m := new(ClaimsSetMetadata)
+	dec := ndr.NewDecoder(bytes.NewReader(b))
+	err := dec.Decode(m)
+	if err != nil {
+		t.Errorf("error decoding ClaimsSetMetadata %v", err)
+	}
+	k, err := m.ClaimsSet()
+	if err != nil {
+		t.Errorf("error retrieving ClaimsSet %v", err)
+	}
+
+	assert.Equal(t, uint32(1), k.ClaimsArrayCount, "claims array count not as expected")
+	assert.Equal(t, ClaimsSourceTypeAD, k.ClaimsArrays[0].ClaimsSourceType, "claims source type not as expected")
+	assert.Equal(t, uint32(1), k.ClaimsArrays[0].ClaimsCount, "claims count not as expected")
+	assert.Equal(t, ClaimTypeIDString, k.ClaimsArrays[0].ClaimEntries[0].Type, "claims entry type not as expected")
+	assert.Equal(t, uint32(4), k.ClaimsArrays[0].ClaimEntries[0].TypeString.ValueCount, "claims value count not as expected")
+	assert.Equal(t, "ad://ext/otherIpPhone:88d5de9f6b4af985", k.ClaimsArrays[0].ClaimEntries[0].ID, "claims entry ID not as expected")
+	assert.Equal(t, []LPWSTR{{"str1"}, {"str2"}, {"str3"}, {"str4"}}, k.ClaimsArrays[0].ClaimEntries[0].TypeString.Value, "claims value not as expected")
+	assert.Equal(t, CompressionFormatNone, m.CompressionFormat, "compression format not as expected")
+}
+
+func Test_ClientClaimsInfoMultiEntry_Unmarshal(t *testing.T) {
+	b, _ := hex.DecodeString(ClientClaimsInfoMulti)
+	m := new(ClaimsSetMetadata)
+	dec := ndr.NewDecoder(bytes.NewReader(b))
+	err := dec.Decode(m)
+	if err != nil {
+		t.Errorf("error decoding ClaimsSetMetadata %v", err)
+	}
+	k, err := m.ClaimsSet()
+	if err != nil {
+		t.Errorf("error retrieving ClaimsSet %v", err)
+	}
+
+	assert.Equal(t, uint32(1), k.ClaimsArrayCount, "claims array count not as expected")
+	assert.Equal(t, ClaimsSourceTypeAD, k.ClaimsArrays[0].ClaimsSourceType, "claims source type not as expected")
+	assert.Equal(t, uint32(2), k.ClaimsArrays[0].ClaimsCount, "claims count not as expected")
+	assert.Equal(t, uint16(1), k.ClaimsArrays[0].ClaimEntries[0].Type, "claims entry type not as expected")
+	assert.Equal(t, uint32(1), k.ClaimsArrays[0].ClaimEntries[0].TypeInt64.ValueCount, "claims value count not as expected")
+	assert.Equal(t, ClaimsEntryIDInt64, k.ClaimsArrays[0].ClaimEntries[0].ID, "claims entry ID not as expected")
+	assert.Equal(t, []int64{int64(28)}, k.ClaimsArrays[0].ClaimEntries[0].TypeInt64.Value, "claims value not as expected")
+	assert.Equal(t, uint16(3), k.ClaimsArrays[0].ClaimEntries[1].Type, "claims entry type not as expected")
+	assert.Equal(t, uint32(1), k.ClaimsArrays[0].ClaimEntries[1].TypeString.ValueCount, "claims value count not as expected")
+	assert.Equal(t, ClaimsEntryIDStr, k.ClaimsArrays[0].ClaimEntries[1].ID, "claims entry ID not as expected")
+	assert.Equal(t, []LPWSTR{{ClaimsEntryValueStr}}, k.ClaimsArrays[0].ClaimEntries[1].TypeString.Value, "claims value not as expected")
+	assert.Equal(t, CompressionFormatNone, m.CompressionFormat, "compression format not as expected")
+}
diff --git a/v2/mstypes/common.go b/v2/mstypes/common.go
new file mode 100644
index 0000000..fb6510d
--- /dev/null
+++ b/v2/mstypes/common.go
@@ -0,0 +1,12 @@
+// Package mstypes provides implemnations of some Microsoft data types [MS-DTYP] https://msdn.microsoft.com/en-us/library/cc230283.aspx
+package mstypes
+
+// LPWSTR implements https://msdn.microsoft.com/en-us/library/cc230355.aspx
+type LPWSTR struct {
+	Value string `ndr:"pointer,conformant,varying"`
+}
+
+// String returns the string representation of LPWSTR data type.
+func (s *LPWSTR) String() string {
+	return s.Value
+}
diff --git a/v2/mstypes/filetime.go b/v2/mstypes/filetime.go
new file mode 100644
index 0000000..5cc952f
--- /dev/null
+++ b/v2/mstypes/filetime.go
@@ -0,0 +1,52 @@
+// Package mstypes implements representations of Microsoft types
+package mstypes
+
+import (
+	"time"
+)
+
+/*
+FILETIME is a windows data structure.
+Ref: https://msdn.microsoft.com/en-us/library/windows/desktop/ms724284%28v=vs.85%29.aspx
+It contains two parts that are 32bit integers:
+	dwLowDateTime
+	dwHighDateTime
+We need to combine these two into one 64bit integer.
+This gives the number of 100 nano second period from January 1, 1601, Coordinated Universal Time (UTC)
+*/
+
+const unixEpochDiff = 116444736000000000
+
+// FileTime implements the Microsoft FILETIME type https://msdn.microsoft.com/en-us/library/cc230324.aspx
+type FileTime struct {
+	LowDateTime  uint32
+	HighDateTime uint32
+}
+
+// Time return a golang Time type from the FileTime
+func (ft FileTime) Time() time.Time {
+	ns := (ft.MSEpoch() - unixEpochDiff) * 100
+	return time.Unix(0, int64(ns)).UTC()
+}
+
+// MSEpoch returns the FileTime as a Microsoft epoch, the number of 100 nano second periods elapsed from January 1, 1601 UTC.
+func (ft FileTime) MSEpoch() int64 {
+	return (int64(ft.HighDateTime) << 32) + int64(ft.LowDateTime)
+}
+
+// Unix returns the FileTime as a Unix time, the number of seconds elapsed since January 1, 1970 UTC.
+func (ft FileTime) Unix() int64 {
+	return (ft.MSEpoch() - unixEpochDiff) / 10000000
+}
+
+// GetFileTime returns a FileTime type from the provided Golang Time type.
+func GetFileTime(t time.Time) FileTime {
+	ns := t.UnixNano()
+	fp := (ns / 100) + unixEpochDiff
+	hd := fp >> 32
+	ld := fp - (hd << 32)
+	return FileTime{
+		LowDateTime:  uint32(ld),
+		HighDateTime: uint32(hd),
+	}
+}
diff --git a/v2/mstypes/filetime_test.go b/v2/mstypes/filetime_test.go
new file mode 100644
index 0000000..7154184
--- /dev/null
+++ b/v2/mstypes/filetime_test.go
@@ -0,0 +1,52 @@
+package mstypes
+
+import (
+	"bytes"
+	"encoding/hex"
+	"github.com/jcmturner/rpc/v2/ndr"
+	"github.com/stretchr/testify/assert"
+	"testing"
+	"time"
+)
+
+const TestNDRHeader = "01100800cccccccca00400000000000000000200"
+
+func TestFileTime(t *testing.T) {
+	t.Parallel()
+	//2007-02-22 17:00:01.6382155
+	tt := time.Date(2007, 2, 22, 17, 0, 1, 638215500, time.UTC)
+	ft := GetFileTime(tt)
+	assert.Equal(t, tt.Unix(), ft.Unix(), "Unix epoch time not as expected")
+	assert.Equal(t, int64(128166372016382155), ft.MSEpoch(), "MSEpoch not as expected")
+	assert.Equal(t, tt, ft.Time(), "Golang time object returned from FileTime not as expected")
+}
+
+func TestDecodeFileTime(t *testing.T) {
+	var tests = []struct {
+		Hex      string
+		UnixNano int64
+	}{
+		{"d186660f656ac601", 1146188570925640100},
+		{"17d439fe784ac601", 1142678694837147900},
+		{"1794a328424bc601", 1142765094837147900},
+		{"175424977a81c601", 1148726694837147900},
+		{"058e4fdd80c6d201", 1494085991825766900},
+		{"cc27969c39c6d201", 1494055388968750000},
+		{"cce7ffc602c7d201", 1494141788968750000},
+		{"c30bcc79e444d301", 1507982621052409900},
+		{"c764125a0842d301", 1507668176220282300},
+		{"c7247c84d142d301", 1507754576220282300},
+	}
+
+	for i, test := range tests {
+		a := new(FileTime)
+		hexStr := TestNDRHeader + test.Hex
+		b, _ := hex.DecodeString(hexStr)
+		dec := ndr.NewDecoder(bytes.NewReader(b))
+		err := dec.Decode(a)
+		if err != nil {
+			t.Fatalf("test %d: %v", i+1, err)
+		}
+		assert.Equal(t, test.UnixNano, a.Time().UnixNano(), "Time value not as expected for test: %d", i+1)
+	}
+}
diff --git a/v2/mstypes/group_membership.go b/v2/mstypes/group_membership.go
new file mode 100644
index 0000000..7915137
--- /dev/null
+++ b/v2/mstypes/group_membership.go
@@ -0,0 +1,19 @@
+package mstypes
+
+// GroupMembership implements https://msdn.microsoft.com/en-us/library/cc237945.aspx
+// RelativeID : A 32-bit unsigned integer that contains the RID of a particular group.
+// The possible values for the Attributes flags are identical to those specified in KERB_SID_AND_ATTRIBUTES
+type GroupMembership struct {
+	RelativeID uint32
+	Attributes uint32
+}
+
+// DomainGroupMembership implements https://msdn.microsoft.com/en-us/library/hh536344.aspx
+// DomainId: A SID structure that contains the SID for the domain.This member is used in conjunction with the GroupIds members to create group SIDs for the device.
+// GroupCount: A 32-bit unsigned integer that contains the number of groups within the domain to which the account belongs.
+// GroupIds: A pointer to a list of GROUP_MEMBERSHIP structures that contain the groups to which the account belongs in the domain. The number of groups in this list MUST be equal to GroupCount.
+type DomainGroupMembership struct {
+	DomainID   RPCSID `ndr:"pointer"`
+	GroupCount uint32
+	GroupIDs   []GroupMembership `ndr:"pointer,conformant"` // Size is value of GroupCount
+}
diff --git a/v2/mstypes/kerb_sid_and_attributes.go b/v2/mstypes/kerb_sid_and_attributes.go
new file mode 100644
index 0000000..61ac39b
--- /dev/null
+++ b/v2/mstypes/kerb_sid_and_attributes.go
@@ -0,0 +1,23 @@
+package mstypes
+
+// Attributes of a security group membership and can be combined by using the bitwise OR operation.
+// They are used by an access check mechanism to specify whether the membership is to be used in an access check decision.
+const (
+	SEGroupMandatory        = 31
+	SEGroupEnabledByDefault = 30
+	SEGroupEnabled          = 29
+	SEGroupOwner            = 28
+	SEGroupResource         = 2
+	//All other bits MUST be set to zero and MUST be  ignored on receipt.
+)
+
+// KerbSidAndAttributes implements https://msdn.microsoft.com/en-us/library/cc237947.aspx
+type KerbSidAndAttributes struct {
+	SID        RPCSID `ndr:"pointer"` // A pointer to an RPC_SID structure.
+	Attributes uint32
+}
+
+// SetFlag sets a flag in a uint32 attribute value.
+func SetFlag(a *uint32, i uint) {
+	*a = *a | (1 << (31 - i))
+}
diff --git a/v2/mstypes/reader.go b/v2/mstypes/reader.go
new file mode 100644
index 0000000..24495bc
--- /dev/null
+++ b/v2/mstypes/reader.go
@@ -0,0 +1,109 @@
+package mstypes
+
+import (
+	"bufio"
+	"encoding/binary"
+	"fmt"
+	"io"
+)
+
+// Byte sizes of primitive types
+const (
+	SizeBool   = 1
+	SizeChar   = 1
+	SizeUint8  = 1
+	SizeUint16 = 2
+	SizeUint32 = 4
+	SizeUint64 = 8
+	SizeEnum   = 2
+	SizeSingle = 4
+	SizeDouble = 8
+	SizePtr    = 4
+)
+
+// Reader reads simple byte stream data into a Go representations
+type Reader struct {
+	r *bufio.Reader // source of the data
+}
+
+// NewReader creates a new instance of a simple Reader.
+func NewReader(r io.Reader) *Reader {
+	reader := new(Reader)
+	reader.r = bufio.NewReader(r)
+	return reader
+}
+
+func (r *Reader) Read(p []byte) (n int, err error) {
+	return r.r.Read(p)
+}
+
+func (r *Reader) Uint8() (uint8, error) {
+	b, err := r.r.ReadByte()
+	if err != nil {
+		return uint8(0), err
+	}
+	return uint8(b), nil
+}
+
+func (r *Reader) Uint16() (uint16, error) {
+	b, err := r.ReadBytes(SizeUint16)
+	if err != nil {
+		return uint16(0), err
+	}
+	return binary.LittleEndian.Uint16(b), nil
+}
+
+func (r *Reader) Uint32() (uint32, error) {
+	b, err := r.ReadBytes(SizeUint32)
+	if err != nil {
+		return uint32(0), err
+	}
+	return binary.LittleEndian.Uint32(b), nil
+}
+
+func (r *Reader) Uint64() (uint64, error) {
+	b, err := r.ReadBytes(SizeUint64)
+	if err != nil {
+		return uint64(0), err
+	}
+	return binary.LittleEndian.Uint64(b), nil
+}
+
+func (r *Reader) FileTime() (f FileTime, err error) {
+	f.LowDateTime, err = r.Uint32()
+	if err != nil {
+		return
+	}
+	f.HighDateTime, err = r.Uint32()
+	if err != nil {
+		return
+	}
+	return
+}
+
+// UTF16String returns a string that is UTF16 encoded in a byte slice. n is the number of bytes representing the string
+func (r *Reader) UTF16String(n int) (str string, err error) {
+	//Length divided by 2 as each run is 16bits = 2bytes
+	s := make([]rune, n/2, n/2)
+	for i := 0; i < len(s); i++ {
+		var u uint16
+		u, err = r.Uint16()
+		if err != nil {
+			return
+		}
+		s[i] = rune(u)
+	}
+	str = string(s)
+	return
+}
+
+// readBytes returns a number of bytes from the NDR byte stream.
+func (r *Reader) ReadBytes(n int) ([]byte, error) {
+	//TODO make this take an int64 as input to allow for larger values on all systems?
+	b := make([]byte, n, n)
+	m, err := r.r.Read(b)
+	if err != nil || m != n {
+		return b, fmt.Errorf("error reading bytes from stream: %v", err)
+	}
+	return b, nil
+}
diff --git a/v2/mstypes/rpc_unicode_string.go b/v2/mstypes/rpc_unicode_string.go
new file mode 100644
index 0000000..4bf02e0
--- /dev/null
+++ b/v2/mstypes/rpc_unicode_string.go
@@ -0,0 +1,13 @@
+package mstypes
+
+// RPCUnicodeString implements https://msdn.microsoft.com/en-us/library/cc230365.aspx
+type RPCUnicodeString struct {
+	Length        uint16 // The length, in bytes, of the string pointed to by the Buffer member, not including the terminating null character if any. The length MUST be a multiple of 2. The length SHOULD equal the entire size of the Buffer, in which case there is no terminating null character. Any method that accesses this structure MUST use the Length specified instead of relying on the presence or absence of a null character.
+	MaximumLength uint16 // The maximum size, in bytes, of the string pointed to by Buffer. The size MUST be a multiple of 2. If not, the size MUST be decremented by 1 prior to use. This value MUST not be less than Length.
+	Value         string `ndr:"pointer,conformant,varying"`
+}
+
+// String returns the RPCUnicodeString string value
+func (r *RPCUnicodeString) String() string {
+	return r.Value
+}
diff --git a/v2/mstypes/rpc_unicode_string_test.go b/v2/mstypes/rpc_unicode_string_test.go
new file mode 100644
index 0000000..c99619f
--- /dev/null
+++ b/v2/mstypes/rpc_unicode_string_test.go
@@ -0,0 +1,32 @@
+package mstypes
+
+import (
+	"bytes"
+	"encoding/hex"
+	"testing"
+
+	"github.com/jcmturner/rpc/v2/ndr"
+	"github.com/stretchr/testify/assert"
+)
+
+const (
+	TestRPCUnicodeStringBytes = "1200120004000200" + "01000000" + "0900000000000000090000007400650073007400750073006500720031000000"
+	TestRPCUnicodeStringValue = "testuser1"
+)
+
+type TestRPCUnicodeString struct {
+	RPCStr     RPCUnicodeString
+	OtherValue uint32
+}
+
+func Test_RPCUnicodeString(t *testing.T) {
+	a := new(TestRPCUnicodeString)
+	hexStr := TestNDRHeader + TestRPCUnicodeStringBytes
+	b, _ := hex.DecodeString(hexStr)
+	dec := ndr.NewDecoder(bytes.NewReader(b))
+	err := dec.Decode(a)
+	if err != nil {
+		t.Fatal(err)
+	}
+	assert.Equal(t, TestRPCUnicodeStringValue, a.RPCStr.Value, "String value not as expected")
+}
diff --git a/v2/mstypes/sid.go b/v2/mstypes/sid.go
new file mode 100644
index 0000000..8e34705
--- /dev/null
+++ b/v2/mstypes/sid.go
@@ -0,0 +1,36 @@
+package mstypes
+
+import (
+	"encoding/binary"
+	"encoding/hex"
+	"fmt"
+	"math"
+	"strings"
+)
+
+// RPCSID implements https://msdn.microsoft.com/en-us/library/cc230364.aspx
+type RPCSID struct {
+	Revision            uint8    // An 8-bit unsigned integer that specifies the revision level of the SID. This value MUST be set to 0x01.
+	SubAuthorityCount   uint8    // An 8-bit unsigned integer that specifies the number of elements in the SubAuthority array. The maximum number of elements allowed is 15.
+	IdentifierAuthority [6]byte  // An RPC_SID_IDENTIFIER_AUTHORITY structure that indicates the authority under which the SID was created. It describes the entity that created the SID. The Identifier Authority value {0,0,0,0,0,5} denotes SIDs created by the NT SID authority.
+	SubAuthority        []uint32 `ndr:"conformant"` // A variable length array of unsigned 32-bit integers that uniquely identifies a principal relative to the IdentifierAuthority. Its length is determined by SubAuthorityCount.
+}
+
+// String returns the string representation of the RPC_SID.
+func (s *RPCSID) String() string {
+	var strb strings.Builder
+	strb.WriteString("S-1-")
+
+	b := append(make([]byte, 2, 2), s.IdentifierAuthority[:]...)
+	// For a strange reason this is read big endian: https://msdn.microsoft.com/en-us/library/dd302645.aspx
+	i := binary.BigEndian.Uint64(b)
+	if i > math.MaxUint32 {
+		fmt.Fprintf(&strb, "0x%s", hex.EncodeToString(s.IdentifierAuthority[:]))
+	} else {
+		fmt.Fprintf(&strb, "%d", i)
+	}
+	for _, sub := range s.SubAuthority {
+		fmt.Fprintf(&strb, "-%d", sub)
+	}
+	return strb.String()
+}
diff --git a/v2/mstypes/sid_test.go b/v2/mstypes/sid_test.go
new file mode 100644
index 0000000..464be71
--- /dev/null
+++ b/v2/mstypes/sid_test.go
@@ -0,0 +1,51 @@
+package mstypes
+
+import (
+	"bytes"
+	"encoding/hex"
+	"testing"
+
+	"github.com/jcmturner/rpc/v2/ndr"
+	"github.com/stretchr/testify/assert"
+)
+
+type testSIDStruct struct {
+	SID RPCSID `ndr:"pointer"`
+}
+
+func Test_RPCSIDDecode(t *testing.T) {
+	var tests = []struct {
+		Hex string
+		SID string
+	}{
+		{"040000000104000000000005150000005951b81766725d2564633b0b", "S-1-5-21-397955417-626881126-188441444"},
+		{"05000000010500000000000515000000b9301b2eb7414c6c8c3b351501020000", "S-1-5-21-773533881-1816936887-355810188-513"},
+		{"050000000105000000000005150000005951b81766725d2564633b0b74542f00", "S-1-5-21-397955417-626881126-188441444-3101812"},
+		{"050000000105000000000005150000005951b81766725d2564633b0be8383200", "S-1-5-21-397955417-626881126-188441444-3291368"},
+		{"050000000105000000000005150000005951b81766725d2564633b0b5db43200", "S-1-5-21-397955417-626881126-188441444-3322973"},
+		{"050000000105000000000005150000005951b81766725d2564633b0b41163500", "S-1-5-21-397955417-626881126-188441444-3479105"},
+		{"050000000105000000000005150000005951b81766725d2564633b0be8ea3100", "S-1-5-21-397955417-626881126-188441444-3271400"},
+		{"050000000105000000000005150000005951b81766725d2564633b0bc1193200", "S-1-5-21-397955417-626881126-188441444-3283393"},
+		{"050000000105000000000005150000005951b81766725d2564633b0b29f13200", "S-1-5-21-397955417-626881126-188441444-3338537"},
+		{"050000000105000000000005150000005951b81766725d2564633b0b0f5f2e00", "S-1-5-21-397955417-626881126-188441444-3038991"},
+		{"050000000105000000000005150000005951b81766725d2564633b0b2f5b2e00", "S-1-5-21-397955417-626881126-188441444-3037999"},
+		{"050000000105000000000005150000005951b81766725d2564633b0bef8f3100", "S-1-5-21-397955417-626881126-188441444-3248111"},
+		{"050000000105000000000005150000005951b81766725d2564633b0b075f2e00", "S-1-5-21-397955417-626881126-188441444-3038983"},
+		{"040000000104000000000005150000004c86cebca07160e63fdce887", "S-1-5-21-3167651404-3865080224-2280184895"},
+		{"050000000105000000000005150000004c86cebca07160e63fdce8875a040000", "S-1-5-21-3167651404-3865080224-2280184895-1114"},
+		{"050000000105000000000005150000004c86cebca07160e63fdce88757040000", "S-1-5-21-3167651404-3865080224-2280184895-1111"},
+	}
+
+	for i, test := range tests {
+		a := new(testSIDStruct)
+		hexStr := TestNDRHeader + "01020304" + test.Hex //The 01000000 is a dumby value for the pointer uint32
+		b, _ := hex.DecodeString(hexStr)
+		dec := ndr.NewDecoder(bytes.NewReader(b))
+		err := dec.Decode(a)
+		if err != nil {
+			t.Fatalf("test %d: %v", i+1, err)
+		}
+		assert.Equal(t, test.SID, a.SID.String(), "SID not as expected for test %d", i+1)
+
+	}
+}
diff --git a/v2/mstypes/user_session_key.go b/v2/mstypes/user_session_key.go
new file mode 100644
index 0000000..fcf0a5d
--- /dev/null
+++ b/v2/mstypes/user_session_key.go
@@ -0,0 +1,11 @@
+package mstypes
+
+// CypherBlock implements https://msdn.microsoft.com/en-us/library/cc237040.aspx
+type CypherBlock struct {
+	Data [8]byte // size = 8
+}
+
+// UserSessionKey implements https://msdn.microsoft.com/en-us/library/cc237080.aspx
+type UserSessionKey struct {
+	CypherBlock [2]CypherBlock // size = 2
+}
diff --git a/v2/ndr/arrays.go b/v2/ndr/arrays.go
new file mode 100644
index 0000000..5e2def2
--- /dev/null
+++ b/v2/ndr/arrays.go
@@ -0,0 +1,413 @@
+package ndr
+
+import (
+	"errors"
+	"fmt"
+	"reflect"
+	"strconv"
+)
+
+// intFromTag returns an int that is a value in a struct tag key/value pair
+func intFromTag(tag reflect.StructTag, key string) (int, error) {
+	ndrTag := parseTags(tag)
+	d := 1
+	if n, ok := ndrTag.Map[key]; ok {
+		i, err := strconv.Atoi(n)
+		if err != nil {
+			return d, fmt.Errorf("invalid dimensions tag [%s]: %v", n, err)
+		}
+		d = i
+	}
+	return d, nil
+}
+
+// parseDimensions returns the a slice of the size of each dimension and type of the member at the deepest level.
+func parseDimensions(v reflect.Value) (l []int, tb reflect.Type) {
+	if v.Kind() == reflect.Ptr {
+		v = v.Elem()
+	}
+	t := v.Type()
+	if t.Kind() == reflect.Ptr {
+		t = t.Elem()
+	}
+	if t.Kind() != reflect.Array && t.Kind() != reflect.Slice {
+		return
+	}
+	l = append(l, v.Len())
+	if t.Elem().Kind() == reflect.Array || t.Elem().Kind() == reflect.Slice {
+		// contains array or slice
+		var m []int
+		m, tb = parseDimensions(v.Index(0))
+		l = append(l, m...)
+	} else {
+		tb = t.Elem()
+	}
+	return
+}
+
+// sliceDimensions returns the count of dimensions a slice has.
+func sliceDimensions(t reflect.Type) (d int, tb reflect.Type) {
+	if t.Kind() == reflect.Ptr {
+		t = t.Elem()
+	}
+	if t.Kind() == reflect.Slice {
+		d++
+		var n int
+		n, tb = sliceDimensions(t.Elem())
+		d += n
+	} else {
+		tb = t
+	}
+	return
+}
+
+// makeSubSlices is a deep recursive creation/initialisation of multi-dimensional slices.
+// Takes the reflect.Value of the 1st dimension and a slice of the lengths of the sub dimensions
+func makeSubSlices(v reflect.Value, l []int) {
+	ty := v.Type().Elem()
+	if ty.Kind() != reflect.Slice {
+		return
+	}
+	for i := 0; i < v.Len(); i++ {
+		s := reflect.MakeSlice(ty, l[0], l[0])
+		v.Index(i).Set(s)
+		// Are there more sub dimensions?
+		if len(l) > 1 {
+			makeSubSlices(v.Index(i), l[1:])
+		}
+	}
+	return
+}
+
+// multiDimensionalIndexPermutations returns all the permutations of the indexes of a multi-dimensional slice.
+// The input is a slice of integers that indicates the max size/length of each dimension
+func multiDimensionalIndexPermutations(l []int) (ps [][]int) {
+	z := make([]int, len(l), len(l)) // The zeros permutation
+	ps = append(ps, z)
+	// for each dimension, in reverse
+	for i := len(l) - 1; i >= 0; i-- {
+		ws := make([][]int, len(ps))
+		copy(ws, ps)
+		//create a permutation for each of the iterations of the current dimension
+		for j := 1; j <= l[i]-1; j++ {
+			// For each existing permutation
+			for _, p := range ws {
+				np := make([]int, len(p), len(p))
+				copy(np, p)
+				np[i] = j
+				ps = append(ps, np)
+			}
+		}
+	}
+	return
+}
+
+// precedingMax reads off the next conformant max value
+func (dec *Decoder) precedingMax() uint32 {
+	m := dec.conformantMax[0]
+	dec.conformantMax = dec.conformantMax[1:]
+	return m
+}
+
+// fillFixedArray establishes if the fixed array is uni or multi dimensional and then fills it.
+func (dec *Decoder) fillFixedArray(v reflect.Value, tag reflect.StructTag, def *[]deferedPtr) error {
+	l, t := parseDimensions(v)
+	if t.Kind() == reflect.String {
+		tag = reflect.StructTag(subStringArrayTag)
+	}
+	if len(l) < 1 {
+		return errors.New("could not establish dimensions of fixed array")
+	}
+	if len(l) == 1 {
+		err := dec.fillUniDimensionalFixedArray(v, tag, def)
+		if err != nil {
+			return fmt.Errorf("could not fill uni-dimensional fixed array: %v", err)
+		}
+		return nil
+	}
+	// Fixed array is multidimensional
+	ps := multiDimensionalIndexPermutations(l[:len(l)-1])
+	for _, p := range ps {
+		// Get current multi-dimensional index to fill
+		a := v
+		for _, i := range p {
+			a = a.Index(i)
+		}
+		// fill with the last dimension array
+		err := dec.fillUniDimensionalFixedArray(a, tag, def)
+		if err != nil {
+			return fmt.Errorf("could not fill dimension %v of multi-dimensional fixed array: %v", p, err)
+		}
+	}
+	return nil
+}
+
+// readUniDimensionalFixedArray reads an array (not slice) from the byte stream.
+func (dec *Decoder) fillUniDimensionalFixedArray(v reflect.Value, tag reflect.StructTag, def *[]deferedPtr) error {
+	for i := 0; i < v.Len(); i++ {
+		err := dec.fill(v.Index(i), tag, def)
+		if err != nil {
+			return fmt.Errorf("could not fill index %d of fixed array: %v", i, err)
+		}
+	}
+	return nil
+}
+
+// fillConformantArray establishes if the conformant array is uni or multi dimensional and then fills the slice.
+func (dec *Decoder) fillConformantArray(v reflect.Value, tag reflect.StructTag, def *[]deferedPtr) error {
+	d, _ := sliceDimensions(v.Type())
+	if d > 1 {
+		err := dec.fillMultiDimensionalConformantArray(v, d, tag, def)
+		if err != nil {
+			return err
+		}
+	} else {
+		err := dec.fillUniDimensionalConformantArray(v, tag, def)
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+// fillUniDimensionalConformantArray fills the uni-dimensional slice value.
+func (dec *Decoder) fillUniDimensionalConformantArray(v reflect.Value, tag reflect.StructTag, def *[]deferedPtr) error {
+	m := dec.precedingMax()
+	n := int(m)
+	a := reflect.MakeSlice(v.Type(), n, n)
+	for i := 0; i < n; i++ {
+		err := dec.fill(a.Index(i), tag, def)
+		if err != nil {
+			return fmt.Errorf("could not fill index %d of uni-dimensional conformant array: %v", i, err)
+		}
+	}
+	v.Set(a)
+	return nil
+}
+
+// fillMultiDimensionalConformantArray fills the multi-dimensional slice value provided from conformant array data.
+// The number of dimensions must be specified. This must be less than or equal to the dimensions in the slice for this
+// method not to panic.
+func (dec *Decoder) fillMultiDimensionalConformantArray(v reflect.Value, d int, tag reflect.StructTag, def *[]deferedPtr) error {
+	// Read the max size of each dimensions from the ndr stream
+	l := make([]int, d, d)
+	for i := range l {
+		l[i] = int(dec.precedingMax())
+	}
+	// Initialise size of slices
+	//   Initialise the size of the 1st dimension
+	ty := v.Type()
+	v.Set(reflect.MakeSlice(ty, l[0], l[0]))
+	// Initialise the size of the other dimensions recursively
+	makeSubSlices(v, l[1:])
+
+	// Get all permutations of the indexes and go through each and fill
+	ps := multiDimensionalIndexPermutations(l)
+	for _, p := range ps {
+		// Get current multi-dimensional index to fill
+		a := v
+		for _, i := range p {
+			a = a.Index(i)
+		}
+		err := dec.fill(a, tag, def)
+		if err != nil {
+			return fmt.Errorf("could not fill index %v of slice: %v", p, err)
+		}
+	}
+	return nil
+}
+
+// fillVaryingArray establishes if the varying array is uni or multi dimensional and then fills the slice.
+func (dec *Decoder) fillVaryingArray(v reflect.Value, tag reflect.StructTag, def *[]deferedPtr) error {
+	d, t := sliceDimensions(v.Type())
+	if d > 1 {
+		err := dec.fillMultiDimensionalVaryingArray(v, t, d, tag, def)
+		if err != nil {
+			return err
+		}
+	} else {
+		err := dec.fillUniDimensionalVaryingArray(v, tag, def)
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+// fillUniDimensionalVaryingArray fills the uni-dimensional slice value.
+func (dec *Decoder) fillUniDimensionalVaryingArray(v reflect.Value, tag reflect.StructTag, def *[]deferedPtr) error {
+	o, err := dec.readUint32()
+	if err != nil {
+		return fmt.Errorf("could not read offset of uni-dimensional varying array: %v", err)
+	}
+	s, err := dec.readUint32()
+	if err != nil {
+		return fmt.Errorf("could not establish actual count of uni-dimensional varying array: %v", err)
+	}
+	t := v.Type()
+	// Total size of the array is the offset in the index being passed plus the actual count of elements being passed.
+	n := int(s + o)
+	a := reflect.MakeSlice(t, n, n)
+	// Populate the array starting at the offset specified
+	for i := int(o); i < n; i++ {
+		err := dec.fill(a.Index(i), tag, def)
+		if err != nil {
+			return fmt.Errorf("could not fill index %d of uni-dimensional varying array: %v", i, err)
+		}
+	}
+	v.Set(a)
+	return nil
+}
+
+// fillMultiDimensionalVaryingArray fills the multi-dimensional slice value provided from varying array data.
+// The number of dimensions must be specified. This must be less than or equal to the dimensions in the slice for this
+// method not to panic.
+func (dec *Decoder) fillMultiDimensionalVaryingArray(v reflect.Value, t reflect.Type, d int, tag reflect.StructTag, def *[]deferedPtr) error {
+	// Read the offset and actual count of each dimensions from the ndr stream
+	o := make([]int, d, d)
+	l := make([]int, d, d)
+	for i := range l {
+		off, err := dec.readUint32()
+		if err != nil {
+			return fmt.Errorf("could not read offset of dimension %d: %v", i+1, err)
+		}
+		o[i] = int(off)
+		s, err := dec.readUint32()
+		if err != nil {
+			return fmt.Errorf("could not read size of dimension %d: %v", i+1, err)
+		}
+		l[i] = int(s) + int(off)
+	}
+	// Initialise size of slices
+	//   Initialise the size of the 1st dimension
+	ty := v.Type()
+	v.Set(reflect.MakeSlice(ty, l[0], l[0]))
+	// Initialise the size of the other dimensions recursively
+	makeSubSlices(v, l[1:])
+
+	// Get all permutations of the indexes and go through each and fill
+	ps := multiDimensionalIndexPermutations(l)
+	for _, p := range ps {
+		// Get current multi-dimensional index to fill
+		a := v
+		var os bool // should this permutation be skipped due to the offset of any of the dimensions?
+		for i, j := range p {
+			if j < o[i] {
+				os = true
+				break
+			}
+			a = a.Index(j)
+		}
+		if os {
+			// This permutation should be skipped as it is less than the offset for one of the dimensions.
+			continue
+		}
+		err := dec.fill(a, tag, def)
+		if err != nil {
+			return fmt.Errorf("could not fill index %v of slice: %v", p, err)
+		}
+	}
+	return nil
+}
+
+// fillConformantVaryingArray establishes if the varying array is uni or multi dimensional and then fills the slice.
+func (dec *Decoder) fillConformantVaryingArray(v reflect.Value, tag reflect.StructTag, def *[]deferedPtr) error {
+	d, t := sliceDimensions(v.Type())
+	if d > 1 {
+		err := dec.fillMultiDimensionalConformantVaryingArray(v, t, d, tag, def)
+		if err != nil {
+			return err
+		}
+	} else {
+		err := dec.fillUniDimensionalConformantVaryingArray(v, tag, def)
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+// fillUniDimensionalConformantVaryingArray fills the uni-dimensional slice value.
+func (dec *Decoder) fillUniDimensionalConformantVaryingArray(v reflect.Value, tag reflect.StructTag, def *[]deferedPtr) error {
+	m := dec.precedingMax()
+	o, err := dec.readUint32()
+	if err != nil {
+		return fmt.Errorf("could not read offset of uni-dimensional conformant varying array: %v", err)
+	}
+	s, err := dec.readUint32()
+	if err != nil {
+		return fmt.Errorf("could not establish actual count of uni-dimensional conformant varying array: %v", err)
+	}
+	if m < o+s {
+		return errors.New("max count is less than the offset plus actual count")
+	}
+	t := v.Type()
+	n := int(s)
+	a := reflect.MakeSlice(t, n, n)
+	for i := int(o); i < n; i++ {
+		err := dec.fill(a.Index(i), tag, def)
+		if err != nil {
+			return fmt.Errorf("could not fill index %d of uni-dimensional conformant varying array: %v", i, err)
+		}
+	}
+	v.Set(a)
+	return nil
+}
+
+// fillMultiDimensionalConformantVaryingArray fills the multi-dimensional slice value provided from conformant varying array data.
+// The number of dimensions must be specified. This must be less than or equal to the dimensions in the slice for this
+// method not to panic.
+func (dec *Decoder) fillMultiDimensionalConformantVaryingArray(v reflect.Value, t reflect.Type, d int, tag reflect.StructTag, def *[]deferedPtr) error {
+	// Read the offset and actual count of each dimensions from the ndr stream
+	m := make([]int, d, d)
+	for i := range m {
+		m[i] = int(dec.precedingMax())
+	}
+	o := make([]int, d, d)
+	l := make([]int, d, d)
+	for i := range l {
+		off, err := dec.readUint32()
+		if err != nil {
+			return fmt.Errorf("could not read offset of dimension %d: %v", i+1, err)
+		}
+		o[i] = int(off)
+		s, err := dec.readUint32()
+		if err != nil {
+			return fmt.Errorf("could not read actual count of dimension %d: %v", i+1, err)
+		}
+		if m[i] < int(s)+int(off) {
+			m[i] = int(s) + int(off)
+		}
+		l[i] = int(s)
+	}
+	// Initialise size of slices
+	//   Initialise the size of the 1st dimension
+	ty := v.Type()
+	v.Set(reflect.MakeSlice(ty, m[0], m[0]))
+	// Initialise the size of the other dimensions recursively
+	makeSubSlices(v, m[1:])
+
+	// Get all permutations of the indexes and go through each and fill
+	ps := multiDimensionalIndexPermutations(m)
+	for _, p := range ps {
+		// Get current multi-dimensional index to fill
+		a := v
+		var os bool // should this permutation be skipped due to the offset of any of the dimensions or max is higher than the actual count being passed
+		for i, j := range p {
+			if j < o[i] || j >= l[i] {
+				os = true
+				break
+			}
+			a = a.Index(j)
+		}
+		if os {
+			// This permutation should be skipped as it is less than the offset for one of the dimensions.
+			continue
+		}
+		err := dec.fill(a, tag, def)
+		if err != nil {
+			return fmt.Errorf("could not fill index %v of slice: %v", p, err)
+		}
+	}
+	return nil
+}
diff --git a/v2/ndr/arrays_test.go b/v2/ndr/arrays_test.go
new file mode 100644
index 0000000..3f67310
--- /dev/null
+++ b/v2/ndr/arrays_test.go
@@ -0,0 +1,227 @@
+package ndr
+
+import (
+	"bytes"
+	"encoding/hex"
+	"fmt"
+	"reflect"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+const TestHeader = "01100800cccccccca00400000000000000000200"
+
+func TestParseDimensions(t *testing.T) {
+	a := [2][2][2][]SimpleTest{}
+	l, ta := parseDimensions(reflect.ValueOf(a))
+	assert.Equal(t, 4, len(l), "dimension count not as expected")
+	assert.Equal(t, []int{2, 2, 2, 0}, l, "lengths list not as expected")
+	assert.Equal(t, "SimpleTest", ta.Name(), "type within array not as expected")
+}
+
+func TestMakeSubSlices(t *testing.T) {
+	l := []int{2, 5, 3, 1}
+	a := new([][][][]uint32)
+	v := reflect.ValueOf(a)
+	v = v.Elem()
+	ty := v.Type()
+	s := reflect.MakeSlice(ty, l[0], l[0])
+	v.Set(s)
+	makeSubSlices(v, l[1:])
+	assert.Equal(t, "[[[[0] [0] [0]] [[0] [0] [0]] [[0] [0] [0]] [[0] [0] [0]] [[0] [0] [0]]] [[[0] [0] [0]] [[0] [0] [0]] [[0] [0] [0]] [[0] [0] [0]] [[0] [0] [0]]]]", fmt.Sprintf("%v", *a))
+}
+
+func TestDimensionCountFromTag(t *testing.T) {
+	var a StructWithMultiDimensionalConformantSlice
+	v := reflect.ValueOf(a)
+	d, err := intFromTag(v.Type().Field(0).Tag, "test")
+	if err != nil {
+		t.Errorf("error getting dimensions from tag: %v", err)
+	}
+	assert.Equal(t, 3, d, "number of dimensions not as expected")
+}
+
+type StructWithArray struct {
+	A [4]uint32
+}
+
+type StructWithMultiDimArray struct {
+	A [2][3][2]uint32
+}
+
+type StructWithConformantSlice struct {
+	A []uint32 `ndr:"conformant"`
+}
+
+type StructWithVaryingSlice struct {
+	A []uint32 `ndr:"varying"`
+}
+
+type StructWithConformantVaryingSlice struct {
+	A []uint32 `ndr:"conformant,varying"`
+}
+
+type StructWithMultiDimensionalConformantSlice struct {
+	A [][][]uint32 `ndr:"conformant,test:3"`
+}
+
+type StructWithMultiDimensionalVaryingSlice struct {
+	A [][][]uint32 `ndr:"varying"`
+}
+
+type StructWithMultiDimensionalConformantVaryingSlice struct {
+	A [][][]uint32 `ndr:"conformant,varying"`
+}
+
+func TestReadUniDimensionalFixedArray(t *testing.T) {
+	hexStr := TestHeader + "01000000020000000300000004000000"
+	b, _ := hex.DecodeString(hexStr)
+	a := new(StructWithArray)
+	dec := NewDecoder(bytes.NewReader(b))
+	err := dec.Decode(a)
+	if err != nil {
+		t.Fatalf("%v", err)
+	}
+	for i := range a.A {
+		assert.Equal(t, uint32(i+1), a.A[i], "Value of index %d not as expected", i)
+	}
+}
+
+func TestReadMultiDimensionalFixedArray(t *testing.T) {
+	hexStr := TestHeader + "0100000002000000030000000400000005000000060000000700000008000000090000000a0000000b0000000c0000000d0000000e0000000f000000100000001100000012000000130000001400000015000000160000001700000018000000190000001a0000001b0000001c0000001d0000001e0000001f0000002000000021000000220000002300000024000000"
+	b, _ := hex.DecodeString(hexStr)
+	a := new(StructWithMultiDimArray)
+	dec := NewDecoder(bytes.NewReader(b))
+	err := dec.Decode(a)
+	if err != nil {
+		t.Fatalf("%v", err)
+	}
+	ar := [2][3][2]uint32{
+		{
+			{1, 2},
+			{3, 4},
+			{5, 6},
+		},
+		{
+			{7, 8},
+			{9, 10},
+			{11, 12},
+		},
+	}
+	assert.Equal(t, ar, a.A, "multi-dimensional fixed array not as expected")
+}
+
+func TestReadUniDimensionalConformantArray(t *testing.T) {
+	hexStr := TestHeader + "0400000001000000020000000300000004000000"
+	b, _ := hex.DecodeString(hexStr)
+	a := new(StructWithConformantSlice)
+	dec := NewDecoder(bytes.NewReader(b))
+	err := dec.Decode(a)
+	if err != nil {
+		t.Fatalf("%v", err)
+	}
+	for i := range a.A {
+		assert.Equal(t, uint32(i+1), a.A[i], "Value of index %d not as expected", i)
+	}
+}
+
+func TestReadMultiDimensionalConformantArray(t *testing.T) {
+	hexStr := TestHeader + "0200000003000000020000000100000002000000030000000400000005000000060000000700000008000000090000000a0000000b0000000c0000000d0000000e0000000f000000100000001100000012000000130000001400000015000000160000001700000018000000190000001a0000001b0000001c0000001d0000001e0000001f0000002000000021000000220000002300000024000000"
+	b, _ := hex.DecodeString(hexStr)
+	a := new(StructWithMultiDimensionalConformantSlice)
+	dec := NewDecoder(bytes.NewReader(b))
+	err := dec.Decode(a)
+	if err != nil {
+		t.Fatalf("%v", err)
+	}
+	ar := [][][]uint32{
+		{
+			{1, 2},
+			{3, 4},
+			{5, 6},
+		},
+		{
+			{7, 8},
+			{9, 10},
+			{11, 12},
+		},
+	}
+	assert.Equal(t, ar, a.A, "multi-dimensional conformant array not as expected")
+}
+
+func TestReadUniDimensionalVaryingArray(t *testing.T) {
+	hexStr := TestHeader + "000000000400000001000000020000000300000004000000"
+	b, _ := hex.DecodeString(hexStr)
+	a := new(StructWithVaryingSlice)
+	dec := NewDecoder(bytes.NewReader(b))
+	err := dec.Decode(a)
+	if err != nil {
+		t.Fatalf("%v", err)
+	}
+	for i := range a.A {
+		assert.Equal(t, uint32(i+1), a.A[i], "Value of index %d not as expected", i)
+	}
+}
+
+func TestReadMultiDimensionalVaryingArray(t *testing.T) {
+	hexStr := TestHeader + "0000000002000000000000000300000000000000020000000100000002000000030000000400000005000000060000000700000008000000090000000a0000000b0000000c0000000d0000000e0000000f000000100000001100000012000000130000001400000015000000160000001700000018000000190000001a0000001b0000001c0000001d0000001e0000001f0000002000000021000000220000002300000024000000"
+	b, _ := hex.DecodeString(hexStr)
+	a := new(StructWithMultiDimensionalVaryingSlice)
+	dec := NewDecoder(bytes.NewReader(b))
+	err := dec.Decode(a)
+	if err != nil {
+		t.Fatalf("%v", err)
+	}
+	ar := [][][]uint32{
+		{
+			{1, 2},
+			{3, 4},
+			{5, 6},
+		},
+		{
+			{7, 8},
+			{9, 10},
+			{11, 12},
+		},
+	}
+	assert.Equal(t, ar, a.A, "multi-dimensional conformant varying array not as expected")
+}
+
+func TestReadUniDimensionalConformantVaryingArray(t *testing.T) {
+	hexStr := TestHeader + "04000000000000000400000001000000020000000300000004000000"
+	b, _ := hex.DecodeString(hexStr)
+	a := new(StructWithConformantVaryingSlice)
+	dec := NewDecoder(bytes.NewReader(b))
+	err := dec.Decode(a)
+	if err != nil {
+		t.Fatalf("%v", err)
+	}
+	for i := range a.A {
+		assert.Equal(t, uint32(i+1), a.A[i], "Value of index %d not as expected", i)
+	}
+}
+
+func TestReadMultiDimensionalConformantVaryingArray(t *testing.T) {
+	hexStr := TestHeader + "0200000003000000020000000000000002000000000000000300000000000000020000000100000002000000030000000400000005000000060000000700000008000000090000000a0000000b0000000c0000000d0000000e0000000f000000100000001100000012000000130000001400000015000000160000001700000018000000190000001a0000001b0000001c0000001d0000001e0000001f0000002000000021000000220000002300000024000000"
+	b, _ := hex.DecodeString(hexStr)
+	a := new(StructWithMultiDimensionalConformantVaryingSlice)
+	dec := NewDecoder(bytes.NewReader(b))
+	err := dec.Decode(a)
+	if err != nil {
+		t.Fatalf("%v", err)
+	}
+	ar := [][][]uint32{
+		{
+			{1, 2},
+			{3, 4},
+			{5, 6},
+		},
+		{
+			{7, 8},
+			{9, 10},
+			{11, 12},
+		},
+	}
+	assert.Equal(t, ar, a.A, "multi-dimensional conformant varying array not as expected")
+}
diff --git a/v2/ndr/decoder.go b/v2/ndr/decoder.go
new file mode 100644
index 0000000..6157b4e
--- /dev/null
+++ b/v2/ndr/decoder.go
@@ -0,0 +1,393 @@
+// Package ndr provides the ability to unmarshal NDR encoded byte steams into Go data structures
+package ndr
+
+import (
+	"bufio"
+	"fmt"
+	"io"
+	"reflect"
+	"strings"
+)
+
+// Struct tag values
+const (
+	TagConformant = "conformant"
+	TagVarying    = "varying"
+	TagPointer    = "pointer"
+	TagPipe       = "pipe"
+)
+
+// Decoder unmarshals NDR byte stream data into a Go struct representation
+type Decoder struct {
+	r             *bufio.Reader // source of the data
+	size          int           // initial size of bytes in buffer
+	ch            CommonHeader  // NDR common header
+	ph            PrivateHeader // NDR private header
+	conformantMax []uint32      // conformant max values that were moved to the beginning of the structure
+	s             interface{}   // pointer to the structure being populated
+	current       []string      // keeps track of the current field being populated
+}
+
+type deferedPtr struct {
+	v   reflect.Value
+	tag reflect.StructTag
+}
+
+// NewDecoder creates a new instance of a NDR Decoder.
+func NewDecoder(r io.Reader) *Decoder {
+	dec := new(Decoder)
+	dec.r = bufio.NewReader(r)
+	dec.r.Peek(int(commonHeaderBytes)) // For some reason an operation is needed on the buffer to initialise it so Buffered() != 0
+	dec.size = dec.r.Buffered()
+	return dec
+}
+
+// Decode unmarshals the NDR encoded bytes into the pointer of a struct provided.
+func (dec *Decoder) Decode(s interface{}) error {
+	dec.s = s
+	err := dec.readCommonHeader()
+	if err != nil {
+		return err
+	}
+	err = dec.readPrivateHeader()
+	if err != nil {
+		return err
+	}
+	_, err = dec.r.Discard(4) //The next 4 bytes are an RPC unique pointer referent. We just skip these.
+	if err != nil {
+		return Errorf("unable to process byte stream: %v", err)
+	}
+
+	return dec.process(s, reflect.StructTag(""))
+}
+
+func (dec *Decoder) process(s interface{}, tag reflect.StructTag) error {
+	// Scan for conformant fields as their max counts are moved to the beginning
+	// http://pubs.opengroup.org/onlinepubs/9629399/chap14.htm#tagfcjh_37
+	err := dec.scanConformantArrays(s, tag)
+	if err != nil {
+		return err
+	}
+	// Recursively fill the struct fields
+	var localDef []deferedPtr
+	err = dec.fill(s, tag, &localDef)
+	if err != nil {
+		return Errorf("could not decode: %v", err)
+	}
+	// Read any deferred referents associated with pointers
+	for _, p := range localDef {
+		err = dec.process(p.v, p.tag)
+		if err != nil {
+			return fmt.Errorf("could not decode deferred referent: %v", err)
+		}
+	}
+	return nil
+}
+
+// scanConformantArrays scans the structure for embedded conformant fields and captures the maximum element counts for
+// dimensions of the array that are moved to the beginning of the structure.
+func (dec *Decoder) scanConformantArrays(s interface{}, tag reflect.StructTag) error {
+	err := dec.conformantScan(s, tag)
+	if err != nil {
+		return fmt.Errorf("failed to scan for embedded conformant arrays: %v", err)
+	}
+	for i := range dec.conformantMax {
+		dec.conformantMax[i], err = dec.readUint32()
+		if err != nil {
+			return fmt.Errorf("could not read preceding conformant max count index %d: %v", i, err)
+		}
+	}
+	return nil
+}
+
+// conformantScan inspects the structure's fields for whether they are conformant.
+func (dec *Decoder) conformantScan(s interface{}, tag reflect.StructTag) error {
+	ndrTag := parseTags(tag)
+	if ndrTag.HasValue(TagPointer) {
+		return nil
+	}
+	v := getReflectValue(s)
+	switch v.Kind() {
+	case reflect.Struct:
+		for i := 0; i < v.NumField(); i++ {
+			err := dec.conformantScan(v.Field(i), v.Type().Field(i).Tag)
+			if err != nil {
+				return err
+			}
+		}
+	case reflect.String:
+		if !ndrTag.HasValue(TagConformant) {
+			break
+		}
+		dec.conformantMax = append(dec.conformantMax, uint32(0))
+	case reflect.Slice:
+		if !ndrTag.HasValue(TagConformant) {
+			break
+		}
+		d, t := sliceDimensions(v.Type())
+		for i := 0; i < d; i++ {
+			dec.conformantMax = append(dec.conformantMax, uint32(0))
+		}
+		// For string arrays there is a common max for the strings within the array.
+		if t.Kind() == reflect.String {
+			dec.conformantMax = append(dec.conformantMax, uint32(0))
+		}
+	}
+	return nil
+}
+
+func (dec *Decoder) isPointer(v reflect.Value, tag reflect.StructTag, def *[]deferedPtr) (bool, error) {
+	// Pointer so defer filling the referent
+	ndrTag := parseTags(tag)
+	if ndrTag.HasValue(TagPointer) {
+		p, err := dec.readUint32()
+		if err != nil {
+			return true, fmt.Errorf("could not read pointer: %v", err)
+		}
+		ndrTag.delete(TagPointer)
+		if p != 0 {
+			// if pointer is not zero add to the deferred items at end of stream
+			*def = append(*def, deferedPtr{v, ndrTag.StructTag()})
+		}
+		return true, nil
+	}
+	return false, nil
+}
+
+func getReflectValue(s interface{}) (v reflect.Value) {
+	if r, ok := s.(reflect.Value); ok {
+		v = r
+	} else {
+		if reflect.ValueOf(s).Kind() == reflect.Ptr {
+			v = reflect.ValueOf(s).Elem()
+		}
+	}
+	return
+}
+
+// fill populates fields with values from the NDR byte stream.
+func (dec *Decoder) fill(s interface{}, tag reflect.StructTag, localDef *[]deferedPtr) error {
+	v := getReflectValue(s)
+
+	//// Pointer so defer filling the referent
+	ptr, err := dec.isPointer(v, tag, localDef)
+	if err != nil {
+		return fmt.Errorf("could not process struct field(%s): %v", strings.Join(dec.current, "/"), err)
+	}
+	if ptr {
+		return nil
+	}
+
+	// Populate the value from the byte stream
+	switch v.Kind() {
+	case reflect.Struct:
+		dec.current = append(dec.current, v.Type().Name()) //Track the current field being filled
+		// in case struct is a union, track this and the selected union field for efficiency
+		var unionTag reflect.Value
+		var unionField string // field to fill if struct is a union
+		// Go through each field in the struct and recursively fill
+		for i := 0; i < v.NumField(); i++ {
+			fieldName := v.Type().Field(i).Name
+			dec.current = append(dec.current, fieldName) //Track the current field being filled
+			//fmt.Fprintf(os.Stderr, "DEBUG Decoding: %s\n", strings.Join(dec.current, "/"))
+			structTag := v.Type().Field(i).Tag
+			ndrTag := parseTags(structTag)
+
+			// Union handling
+			if !unionTag.IsValid() {
+				// Is this field a union tag?
+				unionTag = dec.isUnion(v.Field(i), structTag)
+			} else {
+				// What is the selected field value of the union if we don't already know
+				if unionField == "" {
+					unionField, err = unionSelectedField(v, unionTag)
+					if err != nil {
+						return fmt.Errorf("could not determine selected union value field for %s with discriminat"+
+							" tag %s: %v", v.Type().Name(), unionTag, err)
+					}
+				}
+				if ndrTag.HasValue(TagUnionField) && fieldName != unionField {
+					// is a union and this field has not been selected so will skip it.
+					dec.current = dec.current[:len(dec.current)-1] //This field has been skipped so remove it from the current field tracker
+					continue
+				}
+			}
+
+			// Check if field is a pointer
+			if v.Field(i).Type().Implements(reflect.TypeOf(new(RawBytes)).Elem()) &&
+				v.Field(i).Type().Kind() == reflect.Slice && v.Field(i).Type().Elem().Kind() == reflect.Uint8 {
+				//field is for rawbytes
+				structTag, err = addSizeToTag(v, v.Field(i), structTag)
+				if err != nil {
+					return fmt.Errorf("could not get rawbytes field(%s) size: %v", strings.Join(dec.current, "/"), err)
+				}
+				ptr, err := dec.isPointer(v.Field(i), structTag, localDef)
+				if err != nil {
+					return fmt.Errorf("could not process struct field(%s): %v", strings.Join(dec.current, "/"), err)
+				}
+				if !ptr {
+					err := dec.readRawBytes(v.Field(i), structTag)
+					if err != nil {
+						return fmt.Errorf("could not fill raw bytes struct field(%s): %v", strings.Join(dec.current, "/"), err)
+					}
+				}
+			} else {
+				err := dec.fill(v.Field(i), structTag, localDef)
+				if err != nil {
+					return fmt.Errorf("could not fill struct field(%s): %v", strings.Join(dec.current, "/"), err)
+				}
+			}
+			dec.current = dec.current[:len(dec.current)-1] //This field has been filled so remove it from the current field tracker
+		}
+		dec.current = dec.current[:len(dec.current)-1] //This field has been filled so remove it from the current field tracker
+	case reflect.Bool:
+		i, err := dec.readBool()
+		if err != nil {
+			return fmt.Errorf("could not fill %s: %v", v.Type().Name(), err)
+		}
+		v.Set(reflect.ValueOf(i))
+	case reflect.Uint8:
+		i, err := dec.readUint8()
+		if err != nil {
+			return fmt.Errorf("could not fill %s: %v", v.Type().Name(), err)
+		}
+		v.Set(reflect.ValueOf(i))
+	case reflect.Uint16:
+		i, err := dec.readUint16()
+		if err != nil {
+			return fmt.Errorf("could not fill %s: %v", v.Type().Name(), err)
+		}
+		v.Set(reflect.ValueOf(i))
+	case reflect.Uint32:
+		i, err := dec.readUint32()
+		if err != nil {
+			return fmt.Errorf("could not fill %s: %v", v.Type().Name(), err)
+		}
+		v.Set(reflect.ValueOf(i))
+	case reflect.Uint64:
+		i, err := dec.readUint64()
+		if err != nil {
+			return fmt.Errorf("could not fill %s: %v", v.Type().Name(), err)
+		}
+		v.Set(reflect.ValueOf(i))
+	case reflect.Int8:
+		i, err := dec.readInt8()
+		if err != nil {
+			return fmt.Errorf("could not fill %s: %v", v.Type().Name(), err)
+		}
+		v.Set(reflect.ValueOf(i))
+	case reflect.Int16:
+		i, err := dec.readInt16()
+		if err != nil {
+			return fmt.Errorf("could not fill %s: %v", v.Type().Name(), err)
+		}
+		v.Set(reflect.ValueOf(i))
+	case reflect.Int32:
+		i, err := dec.readInt32()
+		if err != nil {
+			return fmt.Errorf("could not fill %s: %v", v.Type().Name(), err)
+		}
+		v.Set(reflect.ValueOf(i))
+	case reflect.Int64:
+		i, err := dec.readInt64()
+		if err != nil {
+			return fmt.Errorf("could not fill %s: %v", v.Type().Name(), err)
+		}
+		v.Set(reflect.ValueOf(i))
+	case reflect.String:
+		ndrTag := parseTags(tag)
+		conformant := ndrTag.HasValue(TagConformant)
+		// strings are always varying so this is assumed without an explicit tag
+		var s string
+		var err error
+		if conformant {
+			s, err = dec.readConformantVaryingString(localDef)
+			if err != nil {
+				return fmt.Errorf("could not fill with conformant varying string: %v", err)
+			}
+		} else {
+			s, err = dec.readVaryingString(localDef)
+			if err != nil {
+				return fmt.Errorf("could not fill with varying string: %v", err)
+			}
+		}
+		v.Set(reflect.ValueOf(s))
+	case reflect.Float32:
+		i, err := dec.readFloat32()
+		if err != nil {
+			return fmt.Errorf("could not fill %v: %v", v.Type().Name(), err)
+		}
+		v.Set(reflect.ValueOf(i))
+	case reflect.Float64:
+		i, err := dec.readFloat64()
+		if err != nil {
+			return fmt.Errorf("could not fill %v: %v", v.Type().Name(), err)
+		}
+		v.Set(reflect.ValueOf(i))
+	case reflect.Array:
+		err := dec.fillFixedArray(v, tag, localDef)
+		if err != nil {
+			return err
+		}
+	case reflect.Slice:
+		if v.Type().Implements(reflect.TypeOf(new(RawBytes)).Elem()) && v.Type().Elem().Kind() == reflect.Uint8 {
+			//field is for rawbytes
+			err := dec.readRawBytes(v, tag)
+			if err != nil {
+				return fmt.Errorf("could not fill raw bytes struct field(%s): %v", strings.Join(dec.current, "/"), err)
+			}
+			break
+		}
+		ndrTag := parseTags(tag)
+		conformant := ndrTag.HasValue(TagConformant)
+		varying := ndrTag.HasValue(TagVarying)
+		if ndrTag.HasValue(TagPipe) {
+			err := dec.fillPipe(v, tag)
+			if err != nil {
+				return err
+			}
+			break
+		}
+		_, t := sliceDimensions(v.Type())
+		if t.Kind() == reflect.String && !ndrTag.HasValue(subStringArrayValue) {
+			// String array
+			err := dec.readStringsArray(v, tag, localDef)
+			if err != nil {
+				return err
+			}
+			break
+		}
+		// varying is assumed as fixed arrays use the Go array type rather than slice
+		if conformant && varying {
+			err := dec.fillConformantVaryingArray(v, tag, localDef)
+			if err != nil {
+				return err
+			}
+		} else if !conformant && varying {
+			err := dec.fillVaryingArray(v, tag, localDef)
+			if err != nil {
+				return err
+			}
+		} else {
+			//default to conformant and not varying
+			err := dec.fillConformantArray(v, tag, localDef)
+			if err != nil {
+				return err
+			}
+		}
+	default:
+		return fmt.Errorf("unsupported type")
+	}
+	return nil
+}
+
+// readBytes returns a number of bytes from the NDR byte stream.
+func (dec *Decoder) readBytes(n int) ([]byte, error) {
+	//TODO make this take an int64 as input to allow for larger values on all systems?
+	b := make([]byte, n, n)
+	m, err := dec.r.Read(b)
+	if err != nil || m != n {
+		return b, fmt.Errorf("error reading bytes from stream: %v", err)
+	}
+	return b, nil
+}
diff --git a/v2/ndr/decoder_test.go b/v2/ndr/decoder_test.go
new file mode 100644
index 0000000..3c08784
--- /dev/null
+++ b/v2/ndr/decoder_test.go
@@ -0,0 +1,137 @@
+package ndr
+
+import (
+	"bytes"
+	"encoding/hex"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestReadCommonHeader(t *testing.T) {
+	var tests = []struct {
+		EncodedHex string
+		ExpectFail bool
+	}{
+		{"01100800cccccccc", false}, // Little Endian
+		{"01000008cccccccc", false}, // Big Endian have to change the bytes for the header size? This test vector was artificially created. Need proper test vector
+		//{"01100800cccccccc1802000000000000", false},
+		//{"01100800cccccccc0002000000000000", false},
+		//{"01100800cccccccc0001000000000000", false},
+		//{"01100800cccccccce000000000000000", false},
+		//{"01100800ccccccccf000000000000000", false},
+		//{"01100800cccccccc7801000000000000", false},
+		//{"01100800cccccccc4801000000000000", false},
+		//{"01100800ccccccccd001000000000000", false},
+		{"02100800cccccccc", true}, // Incorrect version
+		{"02100900cccccccc", true}, // Incorrect length
+
+	}
+
+	for i, test := range tests {
+		b, _ := hex.DecodeString(test.EncodedHex)
+		dec := NewDecoder(bytes.NewReader(b))
+		err := dec.readCommonHeader()
+		if err != nil && !test.ExpectFail {
+			t.Errorf("error reading common header of test %d: %v", i, err)
+		}
+		if err == nil && test.ExpectFail {
+			t.Errorf("expected failure on reading common header of test %d: %v", i, err)
+		}
+	}
+}
+
+func TestReadPrivateHeader(t *testing.T) {
+	var tests = []struct {
+		EncodedHex string
+		ExpectFail bool
+		Length     int
+	}{
+		{"01100800cccccccc1802000000000000", false, 536},
+		{"01100800cccccccc0002000000000000", false, 512},
+		{"01100800cccccccc0001000000000000", false, 256},
+		{"01100800ccccccccFF00000000000000", true, 255}, // Length not multiple of 8
+		{"01100800cccccccc00010000000000", true, 256},   // Too short
+
+	}
+
+	for i, test := range tests {
+		b, _ := hex.DecodeString(test.EncodedHex)
+		dec := NewDecoder(bytes.NewReader(b))
+		err := dec.readCommonHeader()
+		if err != nil {
+			t.Errorf("error reading common header of test %d: %v", i, err)
+		}
+		err = dec.readPrivateHeader()
+		if err != nil && !test.ExpectFail {
+			t.Errorf("error reading private header of test %d: %v", i, err)
+		}
+		if err == nil && test.ExpectFail {
+			t.Errorf("expected failure on reading private header of test %d: %v", i, err)
+		}
+		if dec.ph.ObjectBufferLength != uint32(test.Length) {
+			t.Errorf("Objectbuffer length expected %d actual %d", test.Length, dec.ph.ObjectBufferLength)
+		}
+	}
+}
+
+type SimpleTest struct {
+	A uint32
+	B uint32
+}
+
+func TestBasicDecode(t *testing.T) {
+	hexStr := "01100800cccccccca00400000000000000000200d186660f656ac601"
+	b, _ := hex.DecodeString(hexStr)
+	ft := new(SimpleTest)
+	dec := NewDecoder(bytes.NewReader(b))
+	err := dec.Decode(ft)
+	if err != nil {
+		t.Fatalf("error decoding: %v", err)
+	}
+	assert.Equal(t, uint32(258377425), ft.A, "Value of field A not as expected")
+	assert.Equal(t, uint32(29780581), ft.B, "Value of field B not as expected %d")
+}
+
+func TestBasicDecodeOverRun(t *testing.T) {
+	hexStr := "01100800cccccccca00400000000000000000200d186660f"
+	b, _ := hex.DecodeString(hexStr)
+	ft := new(SimpleTest)
+	dec := NewDecoder(bytes.NewReader(b))
+	err := dec.Decode(ft)
+	if err == nil {
+		t.Errorf("Expected error for trying to read more than the bytes we have")
+	}
+}
+
+type testEmbeddingPointer struct {
+	A testEmbeddedPointer `ndr:"pointer"`
+	B uint32              // 1
+}
+
+type testEmbeddedPointer struct {
+	C testEmbeddedPointer2 `ndr:"pointer"`
+	D uint32               `ndr:"pointer"` // 2
+	E uint32               // 3
+}
+
+type testEmbeddedPointer2 struct {
+	F uint32 `ndr:"pointer"` // 4
+	G uint32 // 5
+}
+
+func Test_EmbeddedPointers(t *testing.T) {
+	hexStr := TestHeader + "00040002" + "01000000" + "00040002" + "00040002" + "03000000" + "00040002" + "05000000" + "04000000" + "02000000"
+	b, _ := hex.DecodeString(hexStr)
+	ft := new(testEmbeddingPointer)
+	dec := NewDecoder(bytes.NewReader(b))
+	err := dec.Decode(ft)
+	if err != nil {
+		t.Fatalf("error decoding: %v", err)
+	}
+	assert.Equal(t, uint32(1), ft.B)
+	assert.Equal(t, uint32(2), ft.A.D)
+	assert.Equal(t, uint32(3), ft.A.E)
+	assert.Equal(t, uint32(4), ft.A.C.F)
+	assert.Equal(t, uint32(5), ft.A.C.G)
+}
diff --git a/v2/ndr/error.go b/v2/ndr/error.go
new file mode 100644
index 0000000..9971194
--- /dev/null
+++ b/v2/ndr/error.go
@@ -0,0 +1,18 @@
+package ndr
+
+import "fmt"
+
+// Malformed implements the error interface for malformed NDR encoding errors.
+type Malformed struct {
+	EText string
+}
+
+// Error implements the error interface on the Malformed struct.
+func (e Malformed) Error() string {
+	return fmt.Sprintf("malformed NDR stream: %s", e.EText)
+}
+
+// Errorf formats an error message into a malformed NDR error.
+func Errorf(format string, a ...interface{}) Malformed {
+	return Malformed{EText: fmt.Sprintf(format, a...)}
+}
diff --git a/v2/ndr/header.go b/v2/ndr/header.go
new file mode 100644
index 0000000..1970ddb
--- /dev/null
+++ b/v2/ndr/header.go
@@ -0,0 +1,116 @@
+package ndr
+
+import (
+	"encoding/binary"
+	"fmt"
+)
+
+/*
+Serialization Version 1
+https://msdn.microsoft.com/en-us/library/cc243563.aspx
+
+Common Header - https://msdn.microsoft.com/en-us/library/cc243890.aspx
+8 bytes in total:
+- First byte - Version: Must equal 1
+- Second byte -  1st 4 bits: Endianess (0=Big; 1=Little); 2nd 4 bits: Character Encoding (0=ASCII; 1=EBCDIC)
+- 3rd - Floating point representation (This does not seem to be the case in examples for Microsoft test sources)
+- 4th - Common Header Length: Must equal 8
+- 5th - 8th - Filler: MUST be set to 0xcccccccc on marshaling, and SHOULD be ignored during unmarshaling.
+
+Private Header - https://msdn.microsoft.com/en-us/library/cc243919.aspx
+8 bytes in total:
+- First 4 bytes - Indicates the length of a serialized top-level type in the octet stream. It MUST include the padding length and exclude the header itself.
+- Second 4 bytes - Filler: MUST be set to 0 (zero) during marshaling, and SHOULD be ignored during unmarshaling.
+*/
+
+const (
+	protocolVersion   uint8  = 1
+	commonHeaderBytes uint16 = 8
+	bigEndian                = 0
+	littleEndian             = 1
+	ascii             uint8  = 0
+	ebcdic            uint8  = 1
+	ieee              uint8  = 0
+	vax               uint8  = 1
+	cray              uint8  = 2
+	ibm               uint8  = 3
+)
+
+// CommonHeader implements the NDR common header: https://msdn.microsoft.com/en-us/library/cc243889.aspx
+type CommonHeader struct {
+	Version             uint8
+	Endianness          binary.ByteOrder
+	CharacterEncoding   uint8
+	FloatRepresentation uint8
+	HeaderLength        uint16
+	Filler              []byte
+}
+
+// PrivateHeader implements the NDR private header: https://msdn.microsoft.com/en-us/library/cc243919.aspx
+type PrivateHeader struct {
+	ObjectBufferLength uint32
+	Filler             []byte
+}
+
+func (dec *Decoder) readCommonHeader() error {
+	// Version
+	vb, err := dec.r.ReadByte()
+	if err != nil {
+		return Malformed{EText: "could not read first byte of common header for version"}
+	}
+	dec.ch.Version = uint8(vb)
+	if dec.ch.Version != protocolVersion {
+		return Malformed{EText: fmt.Sprintf("byte stream does not indicate a RPC Type serialization of version %v", protocolVersion)}
+	}
+	// Read Endianness & Character Encoding
+	eb, err := dec.r.ReadByte()
+	if err != nil {
+		return Malformed{EText: "could not read second byte of common header for endianness"}
+	}
+	endian := int(eb >> 4 & 0xF)
+	if endian != 0 && endian != 1 {
+		return Malformed{EText: "common header does not indicate a valid endianness"}
+	}
+	dec.ch.CharacterEncoding = uint8(vb & 0xF)
+	if dec.ch.CharacterEncoding != 0 && dec.ch.CharacterEncoding != 1 {
+		return Malformed{EText: "common header does not indicate a valid character encoding"}
+	}
+	switch endian {
+	case littleEndian:
+		dec.ch.Endianness = binary.LittleEndian
+	case bigEndian:
+		dec.ch.Endianness = binary.BigEndian
+	}
+	// Common header length
+	lb, err := dec.readBytes(2)
+	if err != nil {
+		return Malformed{EText: fmt.Sprintf("could not read common header length: %v", err)}
+	}
+	dec.ch.HeaderLength = dec.ch.Endianness.Uint16(lb)
+	if dec.ch.HeaderLength != commonHeaderBytes {
+		return Malformed{EText: "common header does not indicate a valid length"}
+	}
+	// Filler bytes
+	dec.ch.Filler, err = dec.readBytes(4)
+	if err != nil {
+		return Malformed{EText: fmt.Sprintf("could not read common header filler: %v", err)}
+	}
+	return nil
+}
+
+func (dec *Decoder) readPrivateHeader() error {
+	// The next 8 bytes after the common header comprise the RPC type marshalling private header for constructed types.
+	err := binary.Read(dec.r, dec.ch.Endianness, &dec.ph.ObjectBufferLength)
+	if err != nil {
+		return Malformed{EText: "could not read private header object buffer length"}
+	}
+	if dec.ph.ObjectBufferLength%8 != 0 {
+		return Malformed{EText: "object buffer length not a multiple of 8"}
+	}
+	// Filler bytes
+	dec.ph.Filler, err = dec.readBytes(4)
+	if err != nil {
+		return Malformed{EText: fmt.Sprintf("could not read private header filler: %v", err)}
+	}
+	return nil
+}
diff --git a/v2/ndr/pipe.go b/v2/ndr/pipe.go
new file mode 100644
index 0000000..5fd27da
--- /dev/null
+++ b/v2/ndr/pipe.go
@@ -0,0 +1,31 @@
+package ndr
+
+import (
+	"fmt"
+	"reflect"
+)
+
+func (dec *Decoder) fillPipe(v reflect.Value, tag reflect.StructTag) error {
+	s, err := dec.readUint32() // read element count of first chunk
+	if err != nil {
+		return err
+	}
+	a := reflect.MakeSlice(v.Type(), 0, 0)
+	c := reflect.MakeSlice(v.Type(), int(s), int(s))
+	for s != 0 {
+		for i := 0; i < int(s); i++ {
+			err := dec.fill(c.Index(i), tag, &[]deferedPtr{})
+			if err != nil {
+				return fmt.Errorf("could not fill element %d of pipe: %v", i, err)
+			}
+		}
+		s, err = dec.readUint32() // read element count of first chunk
+		if err != nil {
+			return err
+		}
+		a = reflect.AppendSlice(a, c)
+		c = reflect.MakeSlice(v.Type(), int(s), int(s))
+	}
+	v.Set(a)
+	return nil
+}
diff --git a/v2/ndr/pipe_test.go b/v2/ndr/pipe_test.go
new file mode 100644
index 0000000..ed77be3
--- /dev/null
+++ b/v2/ndr/pipe_test.go
@@ -0,0 +1,28 @@
+package ndr
+
+import (
+	"bytes"
+	"encoding/hex"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+const testPipe = "04000000010000000200000003000000040000000300000001000000020000000300000000000000"
+
+type structWithPipe struct {
+	A []uint32 `ndr:"pipe"`
+}
+
+func TestFillPipe(t *testing.T) {
+	hexStr := TestHeader + testPipe
+	b, _ := hex.DecodeString(hexStr)
+	a := new(structWithPipe)
+	dec := NewDecoder(bytes.NewReader(b))
+	err := dec.Decode(a)
+	if err != nil {
+		t.Fatalf("%v", err)
+	}
+	tp := []uint32{1, 2, 3, 4, 1, 2, 3}
+	assert.Equal(t, tp, a.A, "Value of pipe not as expected")
+}
diff --git a/v2/ndr/primitives.go b/v2/ndr/primitives.go
new file mode 100644
index 0000000..7eb1d1a
--- /dev/null
+++ b/v2/ndr/primitives.go
@@ -0,0 +1,211 @@
+package ndr
+
+import (
+	"bytes"
+	"encoding/binary"
+	"math"
+)
+
+// Byte sizes of primitive types
+const (
+	SizeBool   = 1
+	SizeChar   = 1
+	SizeUint8  = 1
+	SizeUint16 = 2
+	SizeUint32 = 4
+	SizeUint64 = 8
+	SizeEnum   = 2
+	SizeSingle = 4
+	SizeDouble = 8
+	SizePtr    = 4
+)
+
+// Bool is an NDR Boolean which is a logical quantity that assumes one of two values: TRUE or FALSE.
+// NDR represents a Boolean as one octet.
+// It represents a value of FALSE as a zero octet, an octet in which every bit is reset.
+// It represents a value of TRUE as a non-zero octet, an octet in which one or more bits are set.
+
+// Char is an NDR character.
+// NDR represents a character as one octet.
+// Characters have two representation formats: ASCII and EBCDIC.
+
+// USmall is an unsigned 8 bit integer
+
+// UShort is an unsigned 16 bit integer
+
+// ULong is an unsigned 32 bit integer
+
+// UHyper is an unsigned 64 bit integer
+
+// Small is an signed 8 bit integer
+
+// Short is an signed 16 bit integer
+
+// Long is an signed 32 bit integer
+
+// Hyper is an signed 64 bit integer
+
+// Enum is the NDR representation of enumerated types as signed short integers (2 octets)
+
+// Single is an NDR defined single-precision floating-point data type
+
+// Double is an NDR defined double-precision floating-point data type
+
+// readBool reads a byte representing a boolean.
+// NDR represents a Boolean as one octet.
+// It represents a value of FALSE as a zero octet, an octet in which every bit is reset.
+// It represents a value of TRUE as a non-zero octet, an octet in which one or more bits are set.
+func (dec *Decoder) readBool() (bool, error) {
+	i, err := dec.readUint8()
+	if err != nil {
+		return false, err
+	}
+	if i != 0 {
+		return true, nil
+	}
+	return false, nil
+}
+
+// readChar reads bytes representing a 8bit ASCII integer cast to a rune.
+func (dec *Decoder) readChar() (rune, error) {
+	var r rune
+	a, err := dec.readUint8()
+	if err != nil {
+		return r, err
+	}
+	return rune(a), nil
+}
+
+// readUint8 reads bytes representing a 8bit unsigned integer.
+func (dec *Decoder) readUint8() (uint8, error) {
+	b, err := dec.r.ReadByte()
+	if err != nil {
+		return uint8(0), err
+	}
+	return uint8(b), nil
+}
+
+// readUint16 reads bytes representing a 16bit unsigned integer.
+func (dec *Decoder) readUint16() (uint16, error) {
+	dec.ensureAlignment(SizeUint16)
+	b, err := dec.readBytes(SizeUint16)
+	if err != nil {
+		return uint16(0), err
+	}
+	return dec.ch.Endianness.Uint16(b), nil
+}
+
+// readUint32 reads bytes representing a 32bit unsigned integer.
+func (dec *Decoder) readUint32() (uint32, error) {
+	dec.ensureAlignment(SizeUint32)
+	b, err := dec.readBytes(SizeUint32)
+	if err != nil {
+		return uint32(0), err
+	}
+	return dec.ch.Endianness.Uint32(b), nil
+}
+
+// readUint32 reads bytes representing a 32bit unsigned integer.
+func (dec *Decoder) readUint64() (uint64, error) {
+	dec.ensureAlignment(SizeUint64)
+	b, err := dec.readBytes(SizeUint64)
+	if err != nil {
+		return uint64(0), err
+	}
+	return dec.ch.Endianness.Uint64(b), nil
+}
+
+func (dec *Decoder) readInt8() (int8, error) {
+	dec.ensureAlignment(SizeUint8)
+	b, err := dec.readBytes(SizeUint8)
+	if err != nil {
+		return 0, err
+	}
+	var i int8
+	buf := bytes.NewReader(b)
+	err = binary.Read(buf, dec.ch.Endianness, &i)
+	if err != nil {
+		return 0, err
+	}
+	return i, nil
+}
+
+func (dec *Decoder) readInt16() (int16, error) {
+	dec.ensureAlignment(SizeUint16)
+	b, err := dec.readBytes(SizeUint16)
+	if err != nil {
+		return 0, err
+	}
+	var i int16
+	buf := bytes.NewReader(b)
+	err = binary.Read(buf, dec.ch.Endianness, &i)
+	if err != nil {
+		return 0, err
+	}
+	return i, nil
+}
+
+func (dec *Decoder) readInt32() (int32, error) {
+	dec.ensureAlignment(SizeUint32)
+	b, err := dec.readBytes(SizeUint32)
+	if err != nil {
+		return 0, err
+	}
+	var i int32
+	buf := bytes.NewReader(b)
+	err = binary.Read(buf, dec.ch.Endianness, &i)
+	if err != nil {
+		return 0, err
+	}
+	return i, nil
+}
+
+func (dec *Decoder) readInt64() (int64, error) {
+	dec.ensureAlignment(SizeUint64)
+	b, err := dec.readBytes(SizeUint64)
+	if err != nil {
+		return 0, err
+	}
+	var i int64
+	buf := bytes.NewReader(b)
+	err = binary.Read(buf, dec.ch.Endianness, &i)
+	if err != nil {
+		return 0, err
+	}
+	return i, nil
+}
+
+// https://en.wikipedia.org/wiki/IEEE_754-1985
+func (dec *Decoder) readFloat32() (f float32, err error) {
+	dec.ensureAlignment(SizeSingle)
+	b, err := dec.readBytes(SizeSingle)
+	if err != nil {
+		return
+	}
+	bits := dec.ch.Endianness.Uint32(b)
+	f = math.Float32frombits(bits)
+	return
+}
+
+func (dec *Decoder) readFloat64() (f float64, err error) {
+	dec.ensureAlignment(SizeDouble)
+	b, err := dec.readBytes(SizeDouble)
+	if err != nil {
+		return
+	}
+	bits := dec.ch.Endianness.Uint64(b)
+	f = math.Float64frombits(bits)
+	return
+}
+
+// NDR enforces NDR alignment of primitive data; that is, any primitive of size n octets is aligned at a octet stream
+// index that is a multiple of n. (In this version of NDR, n is one of {1, 2, 4, 8}.) An octet stream index indicates
+// the number of an octet in an octet stream when octets are numbered, beginning with 0, from the first octet in the
+// stream. Where necessary, an alignment gap, consisting of octets of unspecified value, precedes the representation
+// of a primitive. The gap is of the smallest size sufficient to align the primitive.
+func (dec *Decoder) ensureAlignment(n int) {
+	p := dec.size - dec.r.Buffered()
+	if s := p % n; s != 0 {
+		dec.r.Discard(n - s)
+	}
+}
diff --git a/v2/ndr/primitives_test.go b/v2/ndr/primitives_test.go
new file mode 100644
index 0000000..184b8d9
--- /dev/null
+++ b/v2/ndr/primitives_test.go
@@ -0,0 +1,44 @@
+package ndr
+
+import (
+	"bufio"
+	"bytes"
+	"encoding/binary"
+	"encoding/hex"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestReadFloat32(t *testing.T) {
+	tests := []struct {
+		hexStr string
+		value  float32
+		order  binary.ByteOrder
+	}{
+		{"3E200000", 0.15625, binary.BigEndian},
+		{"00000000", 0.0, binary.BigEndian},
+		{"3F800000", 1.0, binary.BigEndian},
+		{"BF800000", -1.0, binary.BigEndian},
+		{"00000001", 1.4e-45, binary.BigEndian},
+		{"00400000", 5.877472e-39, binary.BigEndian},
+		{"007FFFFF", 1.1754942e-38, binary.BigEndian},
+		{"00800000", 1.1754944e-38, binary.BigEndian},
+		{"7F7FFFFF", 3.4028235e38, binary.BigEndian},
+		//TODO need some littleendian test vectors
+	}
+	for i, test := range tests {
+		b, _ := hex.DecodeString(test.hexStr)
+		//t.Logf("%s %08b\n", test.hexStr,b)
+		r := bufio.NewReader(bytes.NewReader(b))
+		dec := Decoder{
+			r:  r,
+			ch: CommonHeader{Endianness: test.order},
+		}
+		f, err := dec.readFloat32()
+		if err != nil {
+			t.Errorf("could not read float32 test %d: %v", i, err)
+		}
+		assert.Equal(t, test.value, f, "float32 not as expect for test %d: %s", i, test.hexStr)
+	}
+}
diff --git a/v2/ndr/rawbytes.go b/v2/ndr/rawbytes.go
new file mode 100644
index 0000000..9ee59fb
--- /dev/null
+++ b/v2/ndr/rawbytes.go
@@ -0,0 +1,61 @@
+package ndr
+
+import (
+	"errors"
+	"fmt"
+	"reflect"
+	"strconv"
+)
+
+// type MyBytes []byte
+// implement RawBytes interface
+
+const (
+	sizeMethod = "Size"
+)
+
+// RawBytes interface should be implemented if reading just a number of bytes from the NDR stream
+type RawBytes interface {
+	Size(interface{}) int
+}
+
+func rawBytesSize(parent reflect.Value, v reflect.Value) (int, error) {
+	sf := v.MethodByName(sizeMethod)
+	if !sf.IsValid() {
+		return 0, fmt.Errorf("could not find a method called %s on the implementation of RawBytes", sizeMethod)
+	}
+	in := []reflect.Value{parent}
+	f := sf.Call(in)
+	if f[0].Kind() != reflect.Int {
+		return 0, errors.New("the RawBytes size function did not return an integer")
+	}
+	return int(f[0].Int()), nil
+}
+
+func addSizeToTag(parent reflect.Value, v reflect.Value, tag reflect.StructTag) (reflect.StructTag, error) {
+	size, err := rawBytesSize(parent, v)
+	if err != nil {
+		return tag, err
+	}
+	ndrTag := parseTags(tag)
+	ndrTag.Map["size"] = strconv.Itoa(size)
+	return ndrTag.StructTag(), nil
+}
+
+func (dec *Decoder) readRawBytes(v reflect.Value, tag reflect.StructTag) error {
+	ndrTag := parseTags(tag)
+	sizeStr, ok := ndrTag.Map["size"]
+	if !ok {
+		return errors.New("size tag not available")
+	}
+	size, err := strconv.Atoi(sizeStr)
+	if err != nil {
+		return fmt.Errorf("size not valid: %v", err)
+	}
+	b, err := dec.readBytes(size)
+	if err != nil {
+		return err
+	}
+	v.Set(reflect.ValueOf(b).Convert(v.Type()))
+	return nil
+}
diff --git a/v2/ndr/strings.go b/v2/ndr/strings.go
new file mode 100644
index 0000000..b7a910b
--- /dev/null
+++ b/v2/ndr/strings.go
@@ -0,0 +1,70 @@
+package ndr
+
+import (
+	"fmt"
+	"reflect"
+)
+
+const (
+	subStringArrayTag   = `ndr:"varying,X-subStringArray"`
+	subStringArrayValue = "X-subStringArray"
+)
+
+func uint16SliceToString(a []uint16) string {
+	s := make([]rune, len(a), len(a))
+	for i := range s {
+		s[i] = rune(a[i])
+	}
+	if len(s) > 0 {
+		// Remove any null terminator
+		if s[len(s)-1] == rune(0) {
+			s = s[:len(s)-1]
+		}
+	}
+	return string(s)
+}
+
+func (dec *Decoder) readVaryingString(def *[]deferedPtr) (string, error) {
+	a := new([]uint16)
+	v := reflect.ValueOf(a)
+	var t reflect.StructTag
+	err := dec.fillUniDimensionalVaryingArray(v.Elem(), t, def)
+	if err != nil {
+		return "", err
+	}
+	s := uint16SliceToString(*a)
+	return s, nil
+}
+
+func (dec *Decoder) readConformantVaryingString(def *[]deferedPtr) (string, error) {
+	a := new([]uint16)
+	v := reflect.ValueOf(a)
+	var t reflect.StructTag
+	err := dec.fillUniDimensionalConformantVaryingArray(v.Elem(), t, def)
+	if err != nil {
+		return "", err
+	}
+	s := uint16SliceToString(*a)
+	return s, nil
+}
+
+func (dec *Decoder) readStringsArray(v reflect.Value, tag reflect.StructTag, def *[]deferedPtr) error {
+	d, _ := sliceDimensions(v.Type())
+	ndrTag := parseTags(tag)
+	var m []int
+	//var ms int
+	if ndrTag.HasValue(TagConformant) {
+		for i := 0; i < d; i++ {
+			m = append(m, int(dec.precedingMax()))
+		}
+		//common max size
+		_ = dec.precedingMax()
+		//ms = int(n)
+	}
+	tag = reflect.StructTag(subStringArrayTag)
+	err := dec.fillVaryingArray(v, tag, def)
+	if err != nil {
+		return fmt.Errorf("could not read string array: %v", err)
+	}
+	return nil
+}
diff --git a/v2/ndr/strings_test.go b/v2/ndr/strings_test.go
new file mode 100644
index 0000000..5892fc2
--- /dev/null
+++ b/v2/ndr/strings_test.go
@@ -0,0 +1,240 @@
+package ndr
+
+import (
+	"bytes"
+	"encoding/binary"
+	"encoding/hex"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+const (
+	TestStr         = "hello world!"
+	TestStrUTF16Hex = "680065006c006c006f00200077006f0072006c00640021000000" // little endian format
+)
+
+type TestStructWithVaryingString struct {
+	A string `ndr:"varying"`
+}
+
+type TestStructWithConformantVaryingString struct {
+	A string `ndr:"conformant,varying"`
+}
+
+type TestStructWithConformantVaryingStringUniArray struct {
+	A []string `ndr:"conformant,varying"`
+}
+
+// Should not have to specify varying tag
+type TestStructWithNonConformantStringUniArray struct {
+	A []string
+}
+
+type TestStructWithConformantVaryingStringMultiArray struct {
+	A [][][]string `ndr:"conformant,varying"`
+}
+
+// Should not have to specify varying tag
+type TestStructWithNonConformantStringMultiArray struct {
+	A [][][]string
+}
+
+// Strings are always varying but the array may not be
+type TestStructWithFixedStringUniArray struct {
+	A [4]string
+}
+
+type TestStructWithFixedStringMultiArray struct {
+	A [2][3][2]string
+}
+
+func Test_uint16SliceToString(t *testing.T) {
+	b, _ := hex.DecodeString(TestStrUTF16Hex)
+	var u []uint16
+	for i := 0; i < len(b); i += 2 {
+		u = append(u, binary.LittleEndian.Uint16(b[i:i+2]))
+	}
+	s := uint16SliceToString(u)
+	assert.Equal(t, TestStr, s, "uint16SliceToString did not return as expected")
+}
+
+func Test_readVaryingString(t *testing.T) {
+	ac := make([]byte, 4, 4)
+	binary.LittleEndian.PutUint32(ac, uint32(len(TestStrUTF16Hex)/4))            // actual count of number of uint16 bytes
+	hexStr := TestHeader + "00000000" + hex.EncodeToString(ac) + TestStrUTF16Hex // header:offset(0):actual count:data
+	b, _ := hex.DecodeString(hexStr)
+	a := new(TestStructWithVaryingString)
+	dec := NewDecoder(bytes.NewReader(b))
+	err := dec.Decode(a)
+	if err != nil {
+		t.Fatalf("%v", err)
+	}
+	assert.Equal(t, TestStr, a.A, "value of decoded varying string not as expected")
+}
+
+func Test_readConformantVaryingString(t *testing.T) {
+	ac := make([]byte, 4, 4)
+	binary.LittleEndian.PutUint32(ac, uint32(len(TestStrUTF16Hex)/4))                                     // actual count of number of uint16 bytes
+	hexStr := TestHeader + hex.EncodeToString(ac) + "00000000" + hex.EncodeToString(ac) + TestStrUTF16Hex // header:max:offset(0):actual count:data
+	b, _ := hex.DecodeString(hexStr)
+	a := new(TestStructWithConformantVaryingString)
+	dec := NewDecoder(bytes.NewReader(b))
+	err := dec.Decode(a)
+	if err != nil {
+		t.Fatalf("%v", err)
+	}
+	assert.Equal(t, TestStr, a.A, "value of decoded varying string not as expected")
+}
+
+func Test_readConformantStringUniDimensionalArray(t *testing.T) {
+	ac := make([]byte, 4, 4)
+	binary.LittleEndian.PutUint32(ac, uint32(len(TestStrUTF16Hex)/4))                                                                             // actual count of number of uint16 bytes
+	hexStr := "00000000" + hex.EncodeToString(ac) + TestStrUTF16Hex                                                                               // offset(0):actual count:data
+	hexStr = TestHeader + "04000000" + hex.EncodeToString(ac) + "0000000004000000" + hexStr + "0000" + hexStr + "0000" + hexStr + "0000" + hexStr // header:1st dimension count(4):max for all strings:offset for 1st dim:actual for 1st dim:string array elements(4) with offset and actual counts. Need to include some bytes for alignment.
+	b, _ := hex.DecodeString(hexStr)
+	a := new(TestStructWithConformantVaryingStringUniArray)
+	dec := NewDecoder(bytes.NewReader(b))
+	err := dec.Decode(a)
+	if err != nil {
+		t.Fatalf("%v", err)
+	}
+	assert.Equal(t, 4, len(a.A), "length of string array not as expected")
+	for _, s := range a.A {
+		if s != TestStr {
+			t.Fatalf("string array does not contain the right values")
+		}
+	}
+}
+
+func Test_readConformantStringMultiDimensionalArray(t *testing.T) {
+	ac := make([]byte, 4, 4)
+	binary.LittleEndian.PutUint32(ac, uint32(len(TestStrUTF16Hex)/4)) // actual count of number of uint16 bytes
+	strb := "00000000" + hex.EncodeToString(ac) + TestStrUTF16Hex     // offset(0):actual count:data
+	var hexStr string
+	for i := 0; i < 12; i++ {
+		hexStr = hexStr + strb + "0000"
+	}
+	hexStr = TestHeader + "02000000" + "03000000" + "02000000" + hex.EncodeToString(ac) + "0000000002000000" + "0000000003000000" + "0000000002000000" + hexStr
+	b, _ := hex.DecodeString(hexStr)
+	a := new(TestStructWithConformantVaryingStringMultiArray)
+	dec := NewDecoder(bytes.NewReader(b))
+	err := dec.Decode(a)
+	if err != nil {
+		t.Fatalf("%v", err)
+	}
+	ar := [][][]string{
+		{
+			{TestStr, TestStr},
+			{TestStr, TestStr},
+			{TestStr, TestStr},
+		},
+		{
+			{TestStr, TestStr},
+			{TestStr, TestStr},
+			{TestStr, TestStr},
+		},
+	}
+	assert.Equal(t, ar, a.A, "fixed multi-dimensional string array not as expected")
+}
+
+func Test_readNonConformantStringUniDimensionalArray(t *testing.T) {
+	ac := make([]byte, 4, 4)
+	binary.LittleEndian.PutUint32(ac, uint32(len(TestStrUTF16Hex)/4))                                       // actual count of number of uint16 bytes
+	hexStr := "00000000" + hex.EncodeToString(ac) + TestStrUTF16Hex                                         // offset(0):actual count:data
+	hexStr = TestHeader + "0000000004000000" + hexStr + "0000" + hexStr + "0000" + hexStr + "0000" + hexStr // header:offset for 1st dim:actual for 1st dim:string array elements(4) with offset and actual counts. Need to include some bytes for alignment.
+	b, _ := hex.DecodeString(hexStr)
+	a := new(TestStructWithNonConformantStringUniArray)
+	dec := NewDecoder(bytes.NewReader(b))
+	err := dec.Decode(a)
+	if err != nil {
+		t.Fatalf("%v", err)
+	}
+	assert.Equal(t, 4, len(a.A), "length of string array not as expected")
+	for _, s := range a.A {
+		if s != TestStr {
+			t.Fatalf("string array does not contain the right values")
+		}
+	}
+}
+
+func Test_readNonConformantStringMultiDimensionalArray(t *testing.T) {
+	ac := make([]byte, 4, 4)
+	binary.LittleEndian.PutUint32(ac, uint32(len(TestStrUTF16Hex)/4)) // actual count of number of uint16 bytes
+	strb := "00000000" + hex.EncodeToString(ac) + TestStrUTF16Hex     // offset(0):actual count:data
+	var hexStr string
+	for i := 0; i < 12; i++ {
+		hexStr = hexStr + strb + "0000"
+	}
+	hexStr = TestHeader + "0000000002000000" + "0000000003000000" + "0000000002000000" + hexStr
+	b, _ := hex.DecodeString(hexStr)
+	a := new(TestStructWithNonConformantStringMultiArray)
+	dec := NewDecoder(bytes.NewReader(b))
+	err := dec.Decode(a)
+	if err != nil {
+		t.Fatalf("%v", err)
+	}
+	ar := [][][]string{
+		{
+			{TestStr, TestStr},
+			{TestStr, TestStr},
+			{TestStr, TestStr},
+		},
+		{
+			{TestStr, TestStr},
+			{TestStr, TestStr},
+			{TestStr, TestStr},
+		},
+	}
+	assert.Equal(t, ar, a.A, "fixed multi-dimensional string array not as expected")
+}
+
+func Test_readFixedStringUniDimensionalArray(t *testing.T) {
+	ac := make([]byte, 4, 4)
+	binary.LittleEndian.PutUint32(ac, uint32(len(TestStrUTF16Hex)/4))                  // actual count of number of uint16 bytes
+	hexStr := "00000000" + hex.EncodeToString(ac) + TestStrUTF16Hex                    // offset(0):actual count:data
+	hexStr = TestHeader + hexStr + "0000" + hexStr + "0000" + hexStr + "0000" + hexStr // header:offset for 1st dim:actual for 1st dim:string array elements(4) with offset and actual counts. Need to include some bytes for alignment.
+	b, _ := hex.DecodeString(hexStr)
+	a := new(TestStructWithFixedStringUniArray)
+	dec := NewDecoder(bytes.NewReader(b))
+	err := dec.Decode(a)
+	if err != nil {
+		t.Fatalf("%v", err)
+	}
+	for _, s := range a.A {
+		if s != TestStr {
+			t.Fatalf("string array does not contain the right values")
+		}
+	}
+}
+
+func Test_readFixedStringMultiDimensionalArray(t *testing.T) {
+	ac := make([]byte, 4, 4)
+	binary.LittleEndian.PutUint32(ac, uint32(len(TestStrUTF16Hex)/4)) // actual count of number of uint16 bytes
+	strb := "00000000" + hex.EncodeToString(ac) + TestStrUTF16Hex     // offset(0):actual count:data
+	var hexStr string
+	for i := 0; i < 12; i++ {
+		hexStr = hexStr + strb + "0000"
+	}
+	hexStr = TestHeader + hexStr
+	b, _ := hex.DecodeString(hexStr)
+	a := new(TestStructWithFixedStringMultiArray)
+	dec := NewDecoder(bytes.NewReader(b))
+	err := dec.Decode(a)
+	if err != nil {
+		t.Fatalf("%v", err)
+	}
+	ar := [2][3][2]string{
+		{
+			{TestStr, TestStr},
+			{TestStr, TestStr},
+			{TestStr, TestStr},
+		},
+		{
+			{TestStr, TestStr},
+			{TestStr, TestStr},
+			{TestStr, TestStr},
+		},
+	}
+	assert.Equal(t, ar, a.A, "fixed multi-dimensional string array not as expected")
+}
diff --git a/v2/ndr/tags.go b/v2/ndr/tags.go
new file mode 100644
index 0000000..01657e0
--- /dev/null
+++ b/v2/ndr/tags.go
@@ -0,0 +1,69 @@
+package ndr
+
+import (
+	"fmt"
+	"reflect"
+	"strings"
+)
+
+const ndrNameSpace = "ndr"
+
+type tags struct {
+	Values []string
+	Map    map[string]string
+}
+
+// parse the struct field tags and extract the ndr related ones.
+// format of tag ndr:"value,key:value1,value2"
+func parseTags(st reflect.StructTag) tags {
+	s := st.Get(ndrNameSpace)
+	t := tags{
+		Values: []string{},
+		Map:    make(map[string]string),
+	}
+	if s != "" {
+		ndrTags := strings.Trim(s, `"`)
+		for _, tag := range strings.Split(ndrTags, ",") {
+			if strings.Contains(tag, ":") {
+				m := strings.SplitN(tag, ":", 2)
+				t.Map[m[0]] = m[1]
+			} else {
+				t.Values = append(t.Values, tag)
+			}
+		}
+	}
+	return t
+}
+
+func appendTag(t reflect.StructTag, s string) reflect.StructTag {
+	ts := t.Get(ndrNameSpace)
+	ts = fmt.Sprintf(`%s"%s,%s"`, ndrNameSpace, ts, s)
+	return reflect.StructTag(ts)
+}
+
+func (t *tags) StructTag() reflect.StructTag {
+	mv := t.Values
+	for key, val := range t.Map {
+		mv = append(mv, key+":"+val)
+	}
+	s := ndrNameSpace + ":" + `"` + strings.Join(mv, ",") + `"`
+	return reflect.StructTag(s)
+}
+
+func (t *tags) delete(s string) {
+	for i, x := range t.Values {
+		if x == s {
+			t.Values = append(t.Values[:i], t.Values[i+1:]...)
+		}
+	}
+	delete(t.Map, s)
+}
+
+func (t *tags) HasValue(s string) bool {
+	for _, v := range t.Values {
+		if v == s {
+			return true
+		}
+	}
+	return false
+}
diff --git a/v2/ndr/tags_test.go b/v2/ndr/tags_test.go
new file mode 100644
index 0000000..6ad7776
--- /dev/null
+++ b/v2/ndr/tags_test.go
@@ -0,0 +1,37 @@
+package ndr
+
+import (
+	"reflect"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+type Test struct {
+	A int `ndr:"value"`
+	B int `ndr:"key:value"`
+	C int `ndr:"value1,key:value2"`
+	D int `dr:"value"`
+}
+
+func TestParseTags(t *testing.T) {
+	var test Test
+	tag0 := reflect.TypeOf(test).Field(0).Tag
+	tag1 := reflect.TypeOf(test).Field(1).Tag
+	tag2 := reflect.TypeOf(test).Field(2).Tag
+	tag3 := reflect.TypeOf(test).Field(3).Tag
+
+	tg0 := parseTags(tag0)
+	tg1 := parseTags(tag1)
+	tg2 := parseTags(tag2)
+	tg3 := parseTags(tag3)
+
+	assert.Equal(t, []string{"value"}, tg0.Values, "Values not as expected for test %d", 0)
+	assert.Equal(t, make(map[string]string), tg0.Map, "Map not as expected for test %d", 0)
+	assert.Equal(t, []string{}, tg1.Values, "Values not as expected for test %d", 1)
+	assert.Equal(t, map[string]string{"key": "value"}, tg1.Map, "Map not as expected for test %d", 1)
+	assert.Equal(t, []string{"value1"}, tg2.Values, "Values not as expected for test %d", 2)
+	assert.Equal(t, map[string]string{"key": "value2"}, tg2.Map, "Map not as expected for test %d", 2)
+	assert.Equal(t, []string{}, tg3.Values, "Values not as expected for test %d", 3)
+	assert.Equal(t, make(map[string]string), tg3.Map, "Map not as expected for test %d", 3)
+}
diff --git a/v2/ndr/union.go b/v2/ndr/union.go
new file mode 100644
index 0000000..6a657fa
--- /dev/null
+++ b/v2/ndr/union.go
@@ -0,0 +1,57 @@
+package ndr
+
+import (
+	"errors"
+	"fmt"
+	"reflect"
+)
+
+// Union interface must be implemented by structs that will be unmarshaled into from the NDR byte stream union representation.
+// The union's discriminating tag will be passed to the SwitchFunc method.
+// The discriminating tag field must have the struct tag: `ndr:"unionTag"`
+// If the union is encapsulated the discriminating tag field must have the struct tag: `ndr:"encapsulated"`
+// The possible value fields that can be selected from must have the struct tag: `ndr:"unionField"`
+type Union interface {
+	SwitchFunc(t interface{}) string
+}
+
+// Union related constants such as struct tag values
+const (
+	unionSelectionFuncName = "SwitchFunc"
+	TagEncapsulated        = "encapsulated"
+	TagUnionTag            = "unionTag"
+	TagUnionField          = "unionField"
+)
+
+func (dec *Decoder) isUnion(field reflect.Value, tag reflect.StructTag) (r reflect.Value) {
+	ndrTag := parseTags(tag)
+	if !ndrTag.HasValue(TagUnionTag) {
+		return
+	}
+	r = field
+	// For a non-encapsulated union, the discriminant is marshalled into the transmitted data stream twice: once as the
+	// field or parameter, which is referenced by the switch_is construct, in the procedure argument list; and once as
+	// the first part of the union representation.
+	if !ndrTag.HasValue(TagEncapsulated) {
+		dec.r.Discard(int(r.Type().Size()))
+	}
+	return
+}
+
+// unionSelectedField returns the field name of which of the union values to fill
+func unionSelectedField(union, discriminant reflect.Value) (string, error) {
+	if !union.Type().Implements(reflect.TypeOf(new(Union)).Elem()) {
+		return "", errors.New("struct does not implement union interface")
+	}
+	args := []reflect.Value{discriminant}
+	// Call the SelectFunc of the union struct to find the name of the field to fill with the value selected.
+	sf := union.MethodByName(unionSelectionFuncName)
+	if !sf.IsValid() {
+		return "", fmt.Errorf("could not find a selection function called %s in the unions struct representation", unionSelectionFuncName)
+	}
+	f := sf.Call(args)
+	if f[0].Kind() != reflect.String || f[0].String() == "" {
+		return "", fmt.Errorf("the union select function did not return a string for the name of the field to fill")
+	}
+	return f[0].String(), nil
+}
diff --git a/v2/ndr/union_test.go b/v2/ndr/union_test.go
new file mode 100644
index 0000000..50013f6
--- /dev/null
+++ b/v2/ndr/union_test.go
@@ -0,0 +1,104 @@
+package ndr
+
+import (
+	"bytes"
+	"encoding/hex"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+const (
+	testUnionSelected1Enc    = "0100000001"
+	testUnionSelected2Enc    = "020000000200"
+	testUnionSelected1NonEnc = "010000000100000001"
+	testUnionSelected2NonEnc = "02000000020000000200"
+)
+
+type testUnionEncapsulated struct {
+	Tag    uint32 `ndr:"unionTag,encapsulated"`
+	Value1 uint8  `ndr:"unionField"`
+	Value2 uint16 `ndr:"unionField"`
+}
+
+type testUnionNonEncapsulated struct {
+	Tag    uint32 `ndr:"unionTag"`
+	Value1 uint8  `ndr:"unionField"`
+	Value2 uint16 `ndr:"unionField"`
+}
+
+func (u testUnionEncapsulated) SwitchFunc(tag interface{}) string {
+	t := tag.(uint32)
+	switch t {
+	case 1:
+		return "Value1"
+	case 2:
+		return "Value2"
+	}
+	return ""
+}
+
+func (u testUnionNonEncapsulated) SwitchFunc(tag interface{}) string {
+	t := tag.(uint32)
+	switch t {
+	case 1:
+		return "Value1"
+	case 2:
+		return "Value2"
+	}
+	return ""
+}
+
+func Test_readUnionEncapsulated(t *testing.T) {
+	var tests = []struct {
+		Hex string
+		Tag uint32
+		V1  uint8
+		V2  uint16
+	}{
+		{testUnionSelected1Enc, uint32(1), uint8(1), uint16(0)},
+		{testUnionSelected2Enc, uint32(2), uint8(0), uint16(2)},
+	}
+
+	for i, test := range tests {
+		a := new(testUnionEncapsulated)
+		hexStr := TestHeader + test.Hex
+		b, _ := hex.DecodeString(hexStr)
+		dec := NewDecoder(bytes.NewReader(b))
+		err := dec.Decode(a)
+		if err != nil {
+			t.Fatalf("test %d: %v", i+1, err)
+		}
+		assert.Equal(t, test.Tag, a.Tag, "Tag value not as expected for test: %d", i+1)
+		assert.Equal(t, test.V1, a.Value1, "Value1 not as expected for test: %d", i+1)
+		assert.Equal(t, test.V2, a.Value2, "Value2 value not as expected for test: %d", i+1)
+
+	}
+}
+
+func Test_readUnionNonEncapsulated(t *testing.T) {
+	var tests = []struct {
+		Hex string
+		Tag uint32
+		V1  uint8
+		V2  uint16
+	}{
+		{testUnionSelected1NonEnc, uint32(1), uint8(1), uint16(0)},
+		{testUnionSelected2NonEnc, uint32(2), uint8(0), uint16(2)},
+	}
+
+	for i, test := range tests {
+		a := new(testUnionNonEncapsulated)
+		hexStr := TestHeader + test.Hex
+		b, _ := hex.DecodeString(hexStr)
+		dec := NewDecoder(bytes.NewReader(b))
+		err := dec.Decode(a)
+		if err != nil {
+			t.Fatalf("test %d: %v", i+1, err)
+		}
+		assert.Equal(t, test.Tag, a.Tag, "Tag value not as expected for test: %d", i+1)
+		assert.Equal(t, test.V1, a.Value1, "Value1 not as expected for test: %d", i+1)
+		assert.Equal(t, test.V2, a.Value2, "Value2 value not as expected for test: %d", i+1)
+
+	}
+}

Debdiff

[The following lists of changes regard files as different if they have different names, permissions or owners.]

Files in second set of .debs but not in first

-rw-r--r--  root/root   /usr/share/gocode/src/gopkg.in/jcmturner/rpc.v0/examples/examples.go
-rw-r--r--  root/root   /usr/share/gocode/src/gopkg.in/jcmturner/rpc.v0/examples/examples_test.go
-rw-r--r--  root/root   /usr/share/gocode/src/gopkg.in/jcmturner/rpc.v0/mstypes/claims.go
-rw-r--r--  root/root   /usr/share/gocode/src/gopkg.in/jcmturner/rpc.v0/mstypes/claims_test.go
-rw-r--r--  root/root   /usr/share/gocode/src/gopkg.in/jcmturner/rpc.v0/mstypes/common.go
-rw-r--r--  root/root   /usr/share/gocode/src/gopkg.in/jcmturner/rpc.v0/mstypes/filetime.go
-rw-r--r--  root/root   /usr/share/gocode/src/gopkg.in/jcmturner/rpc.v0/mstypes/filetime_test.go
-rw-r--r--  root/root   /usr/share/gocode/src/gopkg.in/jcmturner/rpc.v0/mstypes/group_membership.go
-rw-r--r--  root/root   /usr/share/gocode/src/gopkg.in/jcmturner/rpc.v0/mstypes/kerb_sid_and_attributes.go
-rw-r--r--  root/root   /usr/share/gocode/src/gopkg.in/jcmturner/rpc.v0/mstypes/reader.go
-rw-r--r--  root/root   /usr/share/gocode/src/gopkg.in/jcmturner/rpc.v0/mstypes/rpc_unicode_string.go
-rw-r--r--  root/root   /usr/share/gocode/src/gopkg.in/jcmturner/rpc.v0/mstypes/rpc_unicode_string_test.go
-rw-r--r--  root/root   /usr/share/gocode/src/gopkg.in/jcmturner/rpc.v0/mstypes/sid.go
-rw-r--r--  root/root   /usr/share/gocode/src/gopkg.in/jcmturner/rpc.v0/mstypes/sid_test.go
-rw-r--r--  root/root   /usr/share/gocode/src/gopkg.in/jcmturner/rpc.v0/mstypes/user_session_key.go
-rw-r--r--  root/root   /usr/share/gocode/src/gopkg.in/jcmturner/rpc.v0/ndr/arrays.go
-rw-r--r--  root/root   /usr/share/gocode/src/gopkg.in/jcmturner/rpc.v0/ndr/arrays_test.go
-rw-r--r--  root/root   /usr/share/gocode/src/gopkg.in/jcmturner/rpc.v0/ndr/decoder.go
-rw-r--r--  root/root   /usr/share/gocode/src/gopkg.in/jcmturner/rpc.v0/ndr/decoder_test.go
-rw-r--r--  root/root   /usr/share/gocode/src/gopkg.in/jcmturner/rpc.v0/ndr/header.go
-rw-r--r--  root/root   /usr/share/gocode/src/gopkg.in/jcmturner/rpc.v0/ndr/pipe.go
-rw-r--r--  root/root   /usr/share/gocode/src/gopkg.in/jcmturner/rpc.v0/ndr/pipe_test.go
-rw-r--r--  root/root   /usr/share/gocode/src/gopkg.in/jcmturner/rpc.v0/ndr/primitives.go
-rw-r--r--  root/root   /usr/share/gocode/src/gopkg.in/jcmturner/rpc.v0/ndr/primitives_test.go
-rw-r--r--  root/root   /usr/share/gocode/src/gopkg.in/jcmturner/rpc.v0/ndr/rawbytes.go
-rw-r--r--  root/root   /usr/share/gocode/src/gopkg.in/jcmturner/rpc.v0/ndr/strings.go
-rw-r--r--  root/root   /usr/share/gocode/src/gopkg.in/jcmturner/rpc.v0/ndr/strings_test.go
-rw-r--r--  root/root   /usr/share/gocode/src/gopkg.in/jcmturner/rpc.v0/ndr/tags.go
-rw-r--r--  root/root   /usr/share/gocode/src/gopkg.in/jcmturner/rpc.v0/ndr/tags_test.go
-rw-r--r--  root/root   /usr/share/gocode/src/gopkg.in/jcmturner/rpc.v0/ndr/union.go
-rw-r--r--  root/root   /usr/share/gocode/src/gopkg.in/jcmturner/rpc.v0/ndr/union_test.go
-rw-r--r--  root/root   /usr/share/gocode/src/gopkg.in/jcmturner/rpc.v0/v2/examples/examples.go
-rw-r--r--  root/root   /usr/share/gocode/src/gopkg.in/jcmturner/rpc.v0/v2/examples/examples_test.go
-rw-r--r--  root/root   /usr/share/gocode/src/gopkg.in/jcmturner/rpc.v0/v2/mstypes/claims.go
-rw-r--r--  root/root   /usr/share/gocode/src/gopkg.in/jcmturner/rpc.v0/v2/mstypes/claims_test.go
-rw-r--r--  root/root   /usr/share/gocode/src/gopkg.in/jcmturner/rpc.v0/v2/mstypes/common.go
-rw-r--r--  root/root   /usr/share/gocode/src/gopkg.in/jcmturner/rpc.v0/v2/mstypes/filetime.go
-rw-r--r--  root/root   /usr/share/gocode/src/gopkg.in/jcmturner/rpc.v0/v2/mstypes/filetime_test.go
-rw-r--r--  root/root   /usr/share/gocode/src/gopkg.in/jcmturner/rpc.v0/v2/mstypes/group_membership.go
-rw-r--r--  root/root   /usr/share/gocode/src/gopkg.in/jcmturner/rpc.v0/v2/mstypes/kerb_sid_and_attributes.go
-rw-r--r--  root/root   /usr/share/gocode/src/gopkg.in/jcmturner/rpc.v0/v2/mstypes/reader.go
-rw-r--r--  root/root   /usr/share/gocode/src/gopkg.in/jcmturner/rpc.v0/v2/mstypes/rpc_unicode_string.go
-rw-r--r--  root/root   /usr/share/gocode/src/gopkg.in/jcmturner/rpc.v0/v2/mstypes/rpc_unicode_string_test.go
-rw-r--r--  root/root   /usr/share/gocode/src/gopkg.in/jcmturner/rpc.v0/v2/mstypes/sid.go
-rw-r--r--  root/root   /usr/share/gocode/src/gopkg.in/jcmturner/rpc.v0/v2/mstypes/sid_test.go
-rw-r--r--  root/root   /usr/share/gocode/src/gopkg.in/jcmturner/rpc.v0/v2/mstypes/user_session_key.go
-rw-r--r--  root/root   /usr/share/gocode/src/gopkg.in/jcmturner/rpc.v0/v2/ndr/arrays.go
-rw-r--r--  root/root   /usr/share/gocode/src/gopkg.in/jcmturner/rpc.v0/v2/ndr/arrays_test.go
-rw-r--r--  root/root   /usr/share/gocode/src/gopkg.in/jcmturner/rpc.v0/v2/ndr/decoder.go
-rw-r--r--  root/root   /usr/share/gocode/src/gopkg.in/jcmturner/rpc.v0/v2/ndr/decoder_test.go
-rw-r--r--  root/root   /usr/share/gocode/src/gopkg.in/jcmturner/rpc.v0/v2/ndr/error.go
-rw-r--r--  root/root   /usr/share/gocode/src/gopkg.in/jcmturner/rpc.v0/v2/ndr/header.go
-rw-r--r--  root/root   /usr/share/gocode/src/gopkg.in/jcmturner/rpc.v0/v2/ndr/pipe.go
-rw-r--r--  root/root   /usr/share/gocode/src/gopkg.in/jcmturner/rpc.v0/v2/ndr/pipe_test.go
-rw-r--r--  root/root   /usr/share/gocode/src/gopkg.in/jcmturner/rpc.v0/v2/ndr/primitives.go
-rw-r--r--  root/root   /usr/share/gocode/src/gopkg.in/jcmturner/rpc.v0/v2/ndr/primitives_test.go
-rw-r--r--  root/root   /usr/share/gocode/src/gopkg.in/jcmturner/rpc.v0/v2/ndr/rawbytes.go
-rw-r--r--  root/root   /usr/share/gocode/src/gopkg.in/jcmturner/rpc.v0/v2/ndr/strings.go
-rw-r--r--  root/root   /usr/share/gocode/src/gopkg.in/jcmturner/rpc.v0/v2/ndr/strings_test.go
-rw-r--r--  root/root   /usr/share/gocode/src/gopkg.in/jcmturner/rpc.v0/v2/ndr/tags.go
-rw-r--r--  root/root   /usr/share/gocode/src/gopkg.in/jcmturner/rpc.v0/v2/ndr/tags_test.go
-rw-r--r--  root/root   /usr/share/gocode/src/gopkg.in/jcmturner/rpc.v0/v2/ndr/union.go
-rw-r--r--  root/root   /usr/share/gocode/src/gopkg.in/jcmturner/rpc.v0/v2/ndr/union_test.go

Files in first set of .debs but not in second

-rw-r--r--  root/root   /usr/share/gocode/src/gopkg.in/jcmturner/rpc.v0/ndr/ndr.go
-rw-r--r--  root/root   /usr/share/gocode/src/gopkg.in/jcmturner/rpc.v0/ndr/ndr_test.go

No differences were encountered in the control files

More details

Full run details