Skip to content

Commit

Permalink
Detect GUID Encoding based on reasonable timestamp (#59)
Browse files Browse the repository at this point in the history
  • Loading branch information
majst01 authored Feb 20, 2024
1 parent 5e6661c commit a4e1cfa
Show file tree
Hide file tree
Showing 6 changed files with 85 additions and 190 deletions.
8 changes: 4 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,21 @@ require (
github.com/avast/retry-go/v4 v4.5.1
github.com/creack/pty v1.1.21
github.com/gliderlabs/ssh v0.3.6
github.com/google/uuid v1.5.0
github.com/google/uuid v1.6.0
github.com/sethvargo/go-password v0.2.0
github.com/stmcginnis/gofish v0.15.0
github.com/stretchr/testify v1.8.4
github.com/vmware/goipmi v0.0.0-20181114221114-2333cd82d702
golang.org/x/net v0.20.0
golang.org/x/net v0.21.0
)

require (
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/crypto v0.18.0 // indirect
golang.org/x/sys v0.16.0 // indirect
golang.org/x/crypto v0.19.0 // indirect
golang.org/x/sys v0.17.0 // indirect
golang.org/x/text v0.14.0 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
Expand Down
20 changes: 10 additions & 10 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gliderlabs/ssh v0.3.6 h1:ZzjlDa05TcFRICb3anf/dSPN3ewz1Zx6CMLPWgkm3b8=
github.com/gliderlabs/ssh v0.3.6/go.mod h1:zpHEXBstFnQYtGnB8k8kQLol82umzn/2/snG7alWVD8=
github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
Expand All @@ -28,14 +28,14 @@ github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcU
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/vmware/goipmi v0.0.0-20181114221114-2333cd82d702 h1:yx587LNBbOpIxzCBHBiI94Wx8ryIAFlu1w0lDwm64cA=
github.com/vmware/goipmi v0.0.0-20181114221114-2333cd82d702/go.mod h1:YiWonbS/PuCtti3wt9jl+FvNEJ7c0nvmjGoEYxdjyk0=
golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE=
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
Expand Down
100 changes: 49 additions & 51 deletions internal/uuid-endianness/uuid.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,16 @@ import (
"bytes"
"encoding/binary"
"fmt"
guuid "github.com/google/uuid"
"time"

"github.com/google/uuid"
)

// UuidSize is the size in bytes of a UUID object
const UuidSize = 16
// uuidSize is the size in bytes of a UUID object
const uuidSize = 16

// Uuid represents a UUID object as defined by RFC 4122.
type Uuid struct {
// uid represents a UUID object as defined by RFC 4122.
type uid struct {
TimeLow uint32
TimeMid uint16
TimeHiAndVersion uint16
Expand All @@ -20,65 +22,61 @@ type Uuid struct {
Node [6]byte
}

// String implements the fmt.Stringer interface
func (u Uuid) String() string {
return fmt.Sprintf("%08x-%04x-%04x-%02x%02x-%x", u.TimeLow, u.TimeMid, u.TimeHiAndVersion, u.ClockSeqHiAndRes, u.ClockSeqLow, u.Node)
}

func FromString(s string) (Uuid, error) {
var uid Uuid
u, err := guuid.Parse(s)
// Redfish API and probably the DHCP Request return the GUID of a machine encoded either
// in middleEndianBytes
// or as normal string
//
// To detect which one is actually in use, we extract the creation time of the uuid,
// if this time is somehow reasonable, we return the string of the GUID as we got it,
// we need to convert it to mixed endian
// https://en.wikipedia.org/wiki/Universally_unique_identifier#Encoding
func Convert(s string) (string, error) {
u, err := uuid.Parse(s)
if err != nil {
return uid, err
return "", err
}
if isNotMixedEncoded(u) {
return u.String(), nil
}

b, err := u.MarshalBinary()
if err != nil {
return uid, err
return "", err
}
return FromBytes(b)
}

// ToMiddleEndian encodes the UUID into a middle-endian UUID.
//
// A middle-endian encoded UUID represents a UUID where the first
// three groups are Little Endian encoded, while the rest of the
// groups are Big Endian encoded.
func (u Uuid) ToMiddleEndian() (Uuid, error) {
buf, err := u.MiddleEndianBytes()
uid, err := fromBytes(b)
if err != nil {
return Uuid{}, err
return "", err
}

return FromBytes(buf)
}

// BigEndianBytes returns the UUID encoded in Big Endian.
func (u Uuid) BigEndianBytes() ([]byte, error) {
buf := bytes.NewBuffer(make([]byte, 0, UuidSize))

err := binary.Write(buf, binary.BigEndian, u)
buf, err := uid.middleEndianBytes()
if err != nil {
return nil, err
return "", err
}

return buf.Bytes(), nil
uid, err = fromBytes(buf)
return fmt.Sprintf("%08x-%04x-%04x-%02x%02x-%x", uid.TimeLow, uid.TimeMid, uid.TimeHiAndVersion, uid.ClockSeqHiAndRes, uid.ClockSeqLow, uid.Node), err
}

// LittleEndianBytes returns the UUID encoded in Little Endian.
func (u Uuid) LittleEndianBytes() ([]byte, error) {
buf := bytes.NewBuffer(make([]byte, 0, UuidSize))
// thirtyYears is an educated guess for a plausible time stored in the uuid.
// RFC states that the time is stored in 100s of nanos since 15 Okt 1582 as defined in RFC 4122
// We check if this time is not more than 30 years apart from now.
// Also if the encoded time is in the future
// If the uid returned from the BMC is mixedEndian encoded, the time extracted is usually in the year 4000 or so.
const thirtyYears = 30 * 365 * 24 * time.Hour

err := binary.Write(buf, binary.LittleEndian, u)
if err != nil {
return nil, err
func isNotMixedEncoded(u uuid.UUID) bool {
if u.Version() != 1 {
return false
}

return buf.Bytes(), nil
uuidTime := time.Unix(u.Time().UnixTime())
if time.Time(uuidTime).Year() > time.Now().Year() {
return false
}
timeDistance := time.Since(uuidTime).Abs()
return timeDistance < thirtyYears
}

// MiddleEndianBytes returns the UUID encoded in Middle Endian.
func (u Uuid) MiddleEndianBytes() ([]byte, error) {
buf := bytes.NewBuffer(make([]byte, 0, UuidSize))
// middleEndianBytes returns the UUID encoded in Middle Endian.
func (u uid) middleEndianBytes() ([]byte, error) {
buf := bytes.NewBuffer(make([]byte, 0, uuidSize))

if err := binary.Write(buf, binary.LittleEndian, u.TimeLow); err != nil {
return nil, err
Expand Down Expand Up @@ -108,8 +106,8 @@ func (u Uuid) MiddleEndianBytes() ([]byte, error) {
}

// UuidFromBytes reads a UUID from a given byte slice.
func FromBytes(buf []byte) (Uuid, error) {
var u Uuid
func fromBytes(buf []byte) (uid, error) {
var u uid
reader := bytes.NewReader(buf)

err := binary.Read(reader, binary.BigEndian, &u)
Expand Down
93 changes: 19 additions & 74 deletions internal/uuid-endianness/uuid_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ package uuid

import (
"bytes"
"reflect"
"testing"

"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func ConvertUUID(s string) []byte {
func convertUUID(s string) []byte {
u := uuid.MustParse(s)
b, _ := u.MarshalBinary()
return b
Expand All @@ -28,19 +29,19 @@ func TestUuid(t *testing.T) {
expect: []byte{0x68, 0x5, 0x34, 0x99, 0x75, 0xf7, 0xe7, 0x11, 0x8c, 0x3f, 0x9a, 0x21, 0x4c, 0xf0, 0x93, 0xae}, // 68053499-75f7-e711-8c3f-9a214cf093ae
},
{
value: ConvertUUID("00ecd471-0771-e911-8000-efbeaddeefbe"),
expect: ConvertUUID("71d4ec00-7107-11e9-8000-efbeaddeefbe"),
value: convertUUID("00ecd471-0771-e911-8000-efbeaddeefbe"),
expect: convertUUID("71d4ec00-7107-11e9-8000-efbeaddeefbe"),
},
}

for i := range tests {
tt := tests[i]
value, err := FromBytes(tt.value)
value, err := fromBytes(tt.value)
if err != nil {
t.Fatalf("Cannot parse UUID %+v: %s", tt.value, err)
}

got, err := value.MiddleEndianBytes()
got, err := value.middleEndianBytes()
if err != nil {
t.Fatalf("Cannot convert to middle endian %+v: %s", value, err)
}
Expand All @@ -51,73 +52,17 @@ func TestUuid(t *testing.T) {
}
}

func TestFromString(t *testing.T) {
tests := []struct {
name string
input string
wantErr bool
}{
{
name: "creates uuid",
input: "00ecd471-0771-e911-8000-efbeaddeefbe",
wantErr: false,
},
{
name: "creates uuid",
input: "11ecd471-2771-e911-8333-efbeaddeefbe",
wantErr: false,
},
{
name: "creates uuid",
input: "11ecd471-2771-e911-8333-0000addeefbe",
wantErr: false,
},
}
for i := range tests {
tt := tests[i]
t.Run(tt.name, func(t *testing.T) {
got, err := FromString(tt.input)
if (err != nil) != tt.wantErr {
t.Errorf("FromString() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got.String(), tt.input) {
t.Errorf("FromString() = %v, want %v", got.String(), tt.input)
}
})
}
}
func TestUUIDConvert(t *testing.T) {
bad := "0060c2bd-3089-eb11-8000-7cc255106b08"
good := "bdc26000-8930-11eb-8000-7cc255106b08"

func TestUuid_ToMiddleEndian(t *testing.T) {
tests := []struct {
name string
input string
want string
wantErr bool
}{
{
name: "convert to mixed endian",
input: "00ecd471-0771-e911-8000-efbeaddeefbe",
want: "71d4ec00-7107-11e9-8000-efbeaddeefbe",
wantErr: false,
},
}
for i := range tests {
tt := tests[i]
t.Run(tt.name, func(t *testing.T) {
u, err := FromString(tt.input)
if err != nil {
t.Errorf("Uuid.FormatString() error = %v", err)
return
}
got, err := u.ToMiddleEndian()
if (err != nil) != tt.wantErr {
t.Errorf("Uuid.ToMiddleEndian() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got.String(), tt.want) {
t.Errorf("Uuid.ToMiddleEndian() = %v, want %v", got.String(), tt.want)
}
})
}
result, err := Convert(good)
require.NoError(t, err)

assert.Equal(t, good, result)

result, err = Convert(bad)
require.NoError(t, err)

assert.Equal(t, good, result)
}
13 changes: 3 additions & 10 deletions internal/vendors/supermicro/supermicro.go
Original file line number Diff line number Diff line change
Expand Up @@ -193,20 +193,13 @@ func (ob *outBand) UUID() (*uuid.UUID, error) {
return &us, nil
}

// Redfish returns the UUID in the wrong byte order
// we need to convert it to mixed endian
// https://en.wikipedia.org/wiki/Universally_unique_identifier#Encoding
raw, err := uuidendian.FromString(u)
// Redfish returns the UUID sometimes in mixed encoding
uid, err := uuidendian.Convert(u)
if err != nil {
return nil, err
}

mixed, err := raw.ToMiddleEndian()
if err != nil {
return nil, err
}

us, err := uuid.Parse(mixed.String())
us, err := uuid.Parse(uid)
if err != nil {
return nil, err
}
Expand Down
Loading

0 comments on commit a4e1cfa

Please sign in to comment.