Skip to content

Commit 39796f6

Browse files
authored
Add OID Implementation (#43)
* Add object identifier encode decode * Add Object Identifier Test Signed-off-by: Lukas Bachschwell <[email protected]>
1 parent 5679dfd commit 39796f6

File tree

2 files changed

+191
-0
lines changed

2 files changed

+191
-0
lines changed

ber.go

+173
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import (
99
"math"
1010
"os"
1111
"reflect"
12+
"strconv"
13+
"strings"
1214
"time"
1315
"unicode/utf8"
1416
)
@@ -391,6 +393,10 @@ func readPacket(reader io.Reader) (*Packet, int, error) {
391393
p.Value = DecodeString(content)
392394
case TagNULL:
393395
case TagObjectIdentifier:
396+
oid, err := parseObjectIdentifier(content)
397+
if err == nil {
398+
p.Value = OIDToString(oid)
399+
}
394400
case TagObjectDescriptor:
395401
case TagExternal:
396402
case TagRealFloat:
@@ -406,6 +412,10 @@ func readPacket(reader io.Reader) (*Packet, int, error) {
406412
p.Value = val
407413
}
408414
case TagRelativeOID:
415+
oid, err := parseObjectIdentifier(content)
416+
if err == nil {
417+
p.Value = OIDToString(oid)
418+
}
409419
case TagSequence:
410420
case TagSet:
411421
case TagNumericString:
@@ -633,3 +643,166 @@ func NewReal(classType Class, tagType Type, tag Tag, value interface{}, descript
633643
}
634644
return p
635645
}
646+
647+
func NewOID(classType Class, tagType Type, tag Tag, value interface{}, description string) *Packet {
648+
p := Encode(classType, tagType, tag, nil, description)
649+
650+
switch v := value.(type) {
651+
case string:
652+
encoded, err := encodeOID(v)
653+
if err != nil {
654+
fmt.Printf("failed writing %v", err)
655+
return nil
656+
}
657+
p.Value = v
658+
p.Data.Write(encoded)
659+
// TODO: support []int already ?
660+
default:
661+
panic(fmt.Sprintf("Invalid type %T, expected float{64|32}", v))
662+
}
663+
return p
664+
}
665+
666+
// encodeOID takes a string representation of an OID and returns its DER-encoded byte slice along with any error.
667+
func encodeOID(oidString string) ([]byte, error) {
668+
// Convert the string representation to an asn1.ObjectIdentifier
669+
parts := strings.Split(oidString, ".")
670+
oid := make([]int, len(parts))
671+
for i, part := range parts {
672+
var val int
673+
if _, err := fmt.Sscanf(part, "%d", &val); err != nil {
674+
return nil, fmt.Errorf("invalid OID part '%s': %w", part, err)
675+
}
676+
oid[i] = val
677+
}
678+
if len(oid) < 2 || oid[0] > 2 || (oid[0] < 2 && oid[1] >= 40) {
679+
panic(fmt.Sprintf("invalid object identifier % d", oid)) // TODO: not elegant
680+
}
681+
encoded := make([]byte, 0)
682+
683+
encoded = appendBase128Int(encoded[:0], int64(oid[0]*40+oid[1]))
684+
for i := 2; i < len(oid); i++ {
685+
encoded = appendBase128Int(encoded, int64(oid[i]))
686+
}
687+
688+
return encoded, nil
689+
}
690+
691+
func appendBase128Int(dst []byte, n int64) []byte {
692+
l := base128IntLength(n)
693+
694+
for i := l - 1; i >= 0; i-- {
695+
o := byte(n >> uint(i*7))
696+
o &= 0x7f
697+
if i != 0 {
698+
o |= 0x80
699+
}
700+
701+
dst = append(dst, o)
702+
}
703+
704+
return dst
705+
}
706+
func base128IntLength(n int64) int {
707+
if n == 0 {
708+
return 1
709+
}
710+
711+
l := 0
712+
for i := n; i > 0; i >>= 7 {
713+
l++
714+
}
715+
716+
return l
717+
}
718+
719+
func OIDToString(oi []int) string {
720+
var s strings.Builder
721+
s.Grow(32)
722+
723+
buf := make([]byte, 0, 19)
724+
for i, v := range oi {
725+
if i > 0 {
726+
s.WriteByte('.')
727+
}
728+
s.Write(strconv.AppendInt(buf, int64(v), 10))
729+
}
730+
731+
return s.String()
732+
}
733+
734+
// parseObjectIdentifier parses an OBJECT IDENTIFIER from the given bytes and
735+
// returns it. An object identifier is a sequence of variable length integers
736+
// that are assigned in a hierarchy.
737+
func parseObjectIdentifier(bytes []byte) (s []int, err error) {
738+
if len(bytes) == 0 {
739+
err = fmt.Errorf("zero length OBJECT IDENTIFIER")
740+
return
741+
}
742+
743+
// In the worst case, we get two elements from the first byte (which is
744+
// encoded differently) and then every varint is a single byte long.
745+
s = make([]int, len(bytes)+1)
746+
747+
// The first varint is 40*value1 + value2:
748+
// According to this packing, value1 can take the values 0, 1 and 2 only.
749+
// When value1 = 0 or value1 = 1, then value2 is <= 39. When value1 = 2,
750+
// then there are no restrictions on value2.
751+
v, offset, err := parseBase128Int(bytes, 0)
752+
if err != nil {
753+
return
754+
}
755+
if v < 80 {
756+
s[0] = v / 40
757+
s[1] = v % 40
758+
} else {
759+
s[0] = 2
760+
s[1] = v - 80
761+
}
762+
763+
i := 2
764+
for ; offset < len(bytes); i++ {
765+
v, offset, err = parseBase128Int(bytes, offset)
766+
if err != nil {
767+
return
768+
}
769+
s[i] = v
770+
}
771+
s = s[0:i]
772+
return
773+
}
774+
775+
// parseBase128Int parses a base-128 encoded int from the given offset in the
776+
// given byte slice. It returns the value and the new offset.
777+
func parseBase128Int(bytes []byte, initOffset int) (ret, offset int, err error) {
778+
offset = initOffset
779+
var ret64 int64
780+
for shifted := 0; offset < len(bytes); shifted++ {
781+
// 5 * 7 bits per byte == 35 bits of data
782+
// Thus the representation is either non-minimal or too large for an int32
783+
if shifted == 5 {
784+
err = fmt.Errorf("base 128 integer too large")
785+
return
786+
}
787+
ret64 <<= 7
788+
b := bytes[offset]
789+
// integers should be minimally encoded, so the leading octet should
790+
// never be 0x80
791+
if shifted == 0 && b == 0x80 {
792+
err = fmt.Errorf("integer is not minimally encoded")
793+
return
794+
}
795+
ret64 |= int64(b & 0x7f)
796+
offset++
797+
if b&0x80 == 0 {
798+
ret = int(ret64)
799+
// Ensure that the returned value fits in an int on all platforms
800+
if ret64 > math.MaxInt32 {
801+
err = fmt.Errorf("base 128 integer too large")
802+
}
803+
return
804+
}
805+
}
806+
err = fmt.Errorf("truncated base 128 integer")
807+
return
808+
}

ber_test.go

+18
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,24 @@ func TestString(t *testing.T) {
100100
}
101101
}
102102

103+
func TestEncodeDecodeOID(t *testing.T) {
104+
for _, v := range []string{"0.1", "1.1", "2.3", "0.4", "0.4.5.1888", "0.10.5.1888.234.324234"} {
105+
enc, err := encodeOID(v)
106+
if err != nil {
107+
t.Errorf("error on encoding object identifier when encoding %s: %v", v, err)
108+
}
109+
parsed, err := parseObjectIdentifier(enc)
110+
if err != nil {
111+
t.Errorf("error on parsing object identifier when parsing %s: %v", v, err)
112+
}
113+
t.Log(enc)
114+
t.Log(OIDToString(parsed))
115+
if v != OIDToString(parsed) {
116+
t.Error("encoded object identifier did not match parsed")
117+
}
118+
}
119+
}
120+
103121
func TestSequenceAndAppendChild(t *testing.T) {
104122
values := []string{
105123
"HIC SVNT LEONES",

0 commit comments

Comments
 (0)