Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add OID Implementation #43

Merged
merged 5 commits into from
Apr 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
173 changes: 173 additions & 0 deletions ber.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import (
"math"
"os"
"reflect"
"strconv"
"strings"
"time"
"unicode/utf8"
)
Expand Down Expand Up @@ -391,6 +393,10 @@ func readPacket(reader io.Reader) (*Packet, int, error) {
p.Value = DecodeString(content)
case TagNULL:
case TagObjectIdentifier:
oid, err := parseObjectIdentifier(content)
if err == nil {
p.Value = OIDToString(oid)
}
case TagObjectDescriptor:
case TagExternal:
case TagRealFloat:
Expand All @@ -406,6 +412,10 @@ func readPacket(reader io.Reader) (*Packet, int, error) {
p.Value = val
}
case TagRelativeOID:
oid, err := parseObjectIdentifier(content)
if err == nil {
p.Value = OIDToString(oid)
}
case TagSequence:
case TagSet:
case TagNumericString:
Expand Down Expand Up @@ -633,3 +643,166 @@ func NewReal(classType Class, tagType Type, tag Tag, value interface{}, descript
}
return p
}

func NewOID(classType Class, tagType Type, tag Tag, value interface{}, description string) *Packet {
p := Encode(classType, tagType, tag, nil, description)

switch v := value.(type) {
case string:
encoded, err := encodeOID(v)
if err != nil {
fmt.Printf("failed writing %v", err)
return nil
}
p.Value = v
p.Data.Write(encoded)
// TODO: support []int already ?
default:
panic(fmt.Sprintf("Invalid type %T, expected float{64|32}", v))
}
return p
}

// encodeOID takes a string representation of an OID and returns its DER-encoded byte slice along with any error.
func encodeOID(oidString string) ([]byte, error) {
// Convert the string representation to an asn1.ObjectIdentifier
parts := strings.Split(oidString, ".")
oid := make([]int, len(parts))
for i, part := range parts {
var val int
if _, err := fmt.Sscanf(part, "%d", &val); err != nil {
return nil, fmt.Errorf("invalid OID part '%s': %w", part, err)
}
oid[i] = val
}
if len(oid) < 2 || oid[0] > 2 || (oid[0] < 2 && oid[1] >= 40) {
panic(fmt.Sprintf("invalid object identifier % d", oid)) // TODO: not elegant
}
encoded := make([]byte, 0)

encoded = appendBase128Int(encoded[:0], int64(oid[0]*40+oid[1]))
for i := 2; i < len(oid); i++ {
encoded = appendBase128Int(encoded, int64(oid[i]))
}

return encoded, nil
}

func appendBase128Int(dst []byte, n int64) []byte {
l := base128IntLength(n)

for i := l - 1; i >= 0; i-- {
o := byte(n >> uint(i*7))
o &= 0x7f
if i != 0 {
o |= 0x80
}

dst = append(dst, o)
}

return dst
}
func base128IntLength(n int64) int {
if n == 0 {
return 1
}

l := 0
for i := n; i > 0; i >>= 7 {
l++
}

return l
}

func OIDToString(oi []int) string {
var s strings.Builder
s.Grow(32)

buf := make([]byte, 0, 19)
for i, v := range oi {
if i > 0 {
s.WriteByte('.')
}
s.Write(strconv.AppendInt(buf, int64(v), 10))
}

return s.String()
}

// parseObjectIdentifier parses an OBJECT IDENTIFIER from the given bytes and
// returns it. An object identifier is a sequence of variable length integers
// that are assigned in a hierarchy.
func parseObjectIdentifier(bytes []byte) (s []int, err error) {
if len(bytes) == 0 {
err = fmt.Errorf("zero length OBJECT IDENTIFIER")
return
}

// In the worst case, we get two elements from the first byte (which is
// encoded differently) and then every varint is a single byte long.
s = make([]int, len(bytes)+1)

// The first varint is 40*value1 + value2:
// According to this packing, value1 can take the values 0, 1 and 2 only.
// When value1 = 0 or value1 = 1, then value2 is <= 39. When value1 = 2,
// then there are no restrictions on value2.
v, offset, err := parseBase128Int(bytes, 0)
if err != nil {
return
}
if v < 80 {
s[0] = v / 40
s[1] = v % 40
} else {
s[0] = 2
s[1] = v - 80
}

i := 2
for ; offset < len(bytes); i++ {
v, offset, err = parseBase128Int(bytes, offset)
if err != nil {
return
}
s[i] = v
}
s = s[0:i]
return
}

// parseBase128Int parses a base-128 encoded int from the given offset in the
// given byte slice. It returns the value and the new offset.
func parseBase128Int(bytes []byte, initOffset int) (ret, offset int, err error) {
offset = initOffset
var ret64 int64
for shifted := 0; offset < len(bytes); shifted++ {
// 5 * 7 bits per byte == 35 bits of data
// Thus the representation is either non-minimal or too large for an int32
if shifted == 5 {
err = fmt.Errorf("base 128 integer too large")
return
}
ret64 <<= 7
b := bytes[offset]
// integers should be minimally encoded, so the leading octet should
// never be 0x80
if shifted == 0 && b == 0x80 {
err = fmt.Errorf("integer is not minimally encoded")
return
}
ret64 |= int64(b & 0x7f)
offset++
if b&0x80 == 0 {
ret = int(ret64)
// Ensure that the returned value fits in an int on all platforms
if ret64 > math.MaxInt32 {
err = fmt.Errorf("base 128 integer too large")
}
return
}
}
err = fmt.Errorf("truncated base 128 integer")
return
}
18 changes: 18 additions & 0 deletions ber_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,24 @@ func TestString(t *testing.T) {
}
}

func TestEncodeDecodeOID(t *testing.T) {
for _, v := range []string{"0.1", "1.1", "2.3", "0.4", "0.4.5.1888", "0.10.5.1888.234.324234"} {
enc, err := encodeOID(v)
if err != nil {
t.Errorf("error on encoding object identifier when encoding %s: %v", v, err)
}
parsed, err := parseObjectIdentifier(enc)
if err != nil {
t.Errorf("error on parsing object identifier when parsing %s: %v", v, err)
}
t.Log(enc)
t.Log(OIDToString(parsed))
if v != OIDToString(parsed) {
t.Error("encoded object identifier did not match parsed")
}
}
}

func TestSequenceAndAppendChild(t *testing.T) {
values := []string{
"HIC SVNT LEONES",
Expand Down
Loading