mirror of
https://github.com/ergochat/ergo.git
synced 2024-11-27 06:19:23 +01:00
513 lines
12 KiB
Go
513 lines
12 KiB
Go
package ber
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"math"
|
|
"os"
|
|
"reflect"
|
|
)
|
|
|
|
// MaxPacketLengthBytes specifies the maximum allowed packet size when calling ReadPacket or DecodePacket. Set to 0 for
|
|
// no limit.
|
|
var MaxPacketLengthBytes int64 = math.MaxInt32
|
|
|
|
type Packet struct {
|
|
Identifier
|
|
Value interface{}
|
|
ByteValue []byte
|
|
Data *bytes.Buffer
|
|
Children []*Packet
|
|
Description string
|
|
}
|
|
|
|
type Identifier struct {
|
|
ClassType Class
|
|
TagType Type
|
|
Tag Tag
|
|
}
|
|
|
|
type Tag uint64
|
|
|
|
const (
|
|
TagEOC Tag = 0x00
|
|
TagBoolean Tag = 0x01
|
|
TagInteger Tag = 0x02
|
|
TagBitString Tag = 0x03
|
|
TagOctetString Tag = 0x04
|
|
TagNULL Tag = 0x05
|
|
TagObjectIdentifier Tag = 0x06
|
|
TagObjectDescriptor Tag = 0x07
|
|
TagExternal Tag = 0x08
|
|
TagRealFloat Tag = 0x09
|
|
TagEnumerated Tag = 0x0a
|
|
TagEmbeddedPDV Tag = 0x0b
|
|
TagUTF8String Tag = 0x0c
|
|
TagRelativeOID Tag = 0x0d
|
|
TagSequence Tag = 0x10
|
|
TagSet Tag = 0x11
|
|
TagNumericString Tag = 0x12
|
|
TagPrintableString Tag = 0x13
|
|
TagT61String Tag = 0x14
|
|
TagVideotexString Tag = 0x15
|
|
TagIA5String Tag = 0x16
|
|
TagUTCTime Tag = 0x17
|
|
TagGeneralizedTime Tag = 0x18
|
|
TagGraphicString Tag = 0x19
|
|
TagVisibleString Tag = 0x1a
|
|
TagGeneralString Tag = 0x1b
|
|
TagUniversalString Tag = 0x1c
|
|
TagCharacterString Tag = 0x1d
|
|
TagBMPString Tag = 0x1e
|
|
TagBitmask Tag = 0x1f // xxx11111b
|
|
|
|
// HighTag indicates the start of a high-tag byte sequence
|
|
HighTag Tag = 0x1f // xxx11111b
|
|
// HighTagContinueBitmask indicates the high-tag byte sequence should continue
|
|
HighTagContinueBitmask Tag = 0x80 // 10000000b
|
|
// HighTagValueBitmask obtains the tag value from a high-tag byte sequence byte
|
|
HighTagValueBitmask Tag = 0x7f // 01111111b
|
|
)
|
|
|
|
const (
|
|
// LengthLongFormBitmask is the mask to apply to the length byte to see if a long-form byte sequence is used
|
|
LengthLongFormBitmask = 0x80
|
|
// LengthValueBitmask is the mask to apply to the length byte to get the number of bytes in the long-form byte sequence
|
|
LengthValueBitmask = 0x7f
|
|
|
|
// LengthIndefinite is returned from readLength to indicate an indefinite length
|
|
LengthIndefinite = -1
|
|
)
|
|
|
|
var tagMap = map[Tag]string{
|
|
TagEOC: "EOC (End-of-Content)",
|
|
TagBoolean: "Boolean",
|
|
TagInteger: "Integer",
|
|
TagBitString: "Bit String",
|
|
TagOctetString: "Octet String",
|
|
TagNULL: "NULL",
|
|
TagObjectIdentifier: "Object Identifier",
|
|
TagObjectDescriptor: "Object Descriptor",
|
|
TagExternal: "External",
|
|
TagRealFloat: "Real (float)",
|
|
TagEnumerated: "Enumerated",
|
|
TagEmbeddedPDV: "Embedded PDV",
|
|
TagUTF8String: "UTF8 String",
|
|
TagRelativeOID: "Relative-OID",
|
|
TagSequence: "Sequence and Sequence of",
|
|
TagSet: "Set and Set OF",
|
|
TagNumericString: "Numeric String",
|
|
TagPrintableString: "Printable String",
|
|
TagT61String: "T61 String",
|
|
TagVideotexString: "Videotex String",
|
|
TagIA5String: "IA5 String",
|
|
TagUTCTime: "UTC Time",
|
|
TagGeneralizedTime: "Generalized Time",
|
|
TagGraphicString: "Graphic String",
|
|
TagVisibleString: "Visible String",
|
|
TagGeneralString: "General String",
|
|
TagUniversalString: "Universal String",
|
|
TagCharacterString: "Character String",
|
|
TagBMPString: "BMP String",
|
|
}
|
|
|
|
type Class uint8
|
|
|
|
const (
|
|
ClassUniversal Class = 0 // 00xxxxxxb
|
|
ClassApplication Class = 64 // 01xxxxxxb
|
|
ClassContext Class = 128 // 10xxxxxxb
|
|
ClassPrivate Class = 192 // 11xxxxxxb
|
|
ClassBitmask Class = 192 // 11xxxxxxb
|
|
)
|
|
|
|
var ClassMap = map[Class]string{
|
|
ClassUniversal: "Universal",
|
|
ClassApplication: "Application",
|
|
ClassContext: "Context",
|
|
ClassPrivate: "Private",
|
|
}
|
|
|
|
type Type uint8
|
|
|
|
const (
|
|
TypePrimitive Type = 0 // xx0xxxxxb
|
|
TypeConstructed Type = 32 // xx1xxxxxb
|
|
TypeBitmask Type = 32 // xx1xxxxxb
|
|
)
|
|
|
|
var TypeMap = map[Type]string{
|
|
TypePrimitive: "Primitive",
|
|
TypeConstructed: "Constructed",
|
|
}
|
|
|
|
var Debug bool = false
|
|
|
|
func PrintBytes(out io.Writer, buf []byte, indent string) {
|
|
data_lines := make([]string, (len(buf)/30)+1)
|
|
num_lines := make([]string, (len(buf)/30)+1)
|
|
|
|
for i, b := range buf {
|
|
data_lines[i/30] += fmt.Sprintf("%02x ", b)
|
|
num_lines[i/30] += fmt.Sprintf("%02d ", (i+1)%100)
|
|
}
|
|
|
|
for i := 0; i < len(data_lines); i++ {
|
|
out.Write([]byte(indent + data_lines[i] + "\n"))
|
|
out.Write([]byte(indent + num_lines[i] + "\n\n"))
|
|
}
|
|
}
|
|
|
|
func PrintPacket(p *Packet) {
|
|
printPacket(os.Stdout, p, 0, false)
|
|
}
|
|
|
|
func printPacket(out io.Writer, p *Packet, indent int, printBytes bool) {
|
|
indent_str := ""
|
|
|
|
for len(indent_str) != indent {
|
|
indent_str += " "
|
|
}
|
|
|
|
class_str := ClassMap[p.ClassType]
|
|
|
|
tagtype_str := TypeMap[p.TagType]
|
|
|
|
tag_str := fmt.Sprintf("0x%02X", p.Tag)
|
|
|
|
if p.ClassType == ClassUniversal {
|
|
tag_str = tagMap[p.Tag]
|
|
}
|
|
|
|
value := fmt.Sprint(p.Value)
|
|
description := ""
|
|
|
|
if p.Description != "" {
|
|
description = p.Description + ": "
|
|
}
|
|
|
|
fmt.Fprintf(out, "%s%s(%s, %s, %s) Len=%d %q\n", indent_str, description, class_str, tagtype_str, tag_str, p.Data.Len(), value)
|
|
|
|
if printBytes {
|
|
PrintBytes(out, p.Bytes(), indent_str)
|
|
}
|
|
|
|
for _, child := range p.Children {
|
|
printPacket(out, child, indent+1, printBytes)
|
|
}
|
|
}
|
|
|
|
// ReadPacket reads a single Packet from the reader
|
|
func ReadPacket(reader io.Reader) (*Packet, error) {
|
|
p, _, err := readPacket(reader)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return p, nil
|
|
}
|
|
|
|
func DecodeString(data []byte) string {
|
|
return string(data)
|
|
}
|
|
|
|
func ParseInt64(bytes []byte) (ret int64, err error) {
|
|
if len(bytes) > 8 {
|
|
// We'll overflow an int64 in this case.
|
|
err = fmt.Errorf("integer too large")
|
|
return
|
|
}
|
|
for bytesRead := 0; bytesRead < len(bytes); bytesRead++ {
|
|
ret <<= 8
|
|
ret |= int64(bytes[bytesRead])
|
|
}
|
|
|
|
// Shift up and down in order to sign extend the result.
|
|
ret <<= 64 - uint8(len(bytes))*8
|
|
ret >>= 64 - uint8(len(bytes))*8
|
|
return
|
|
}
|
|
|
|
func encodeInteger(i int64) []byte {
|
|
n := int64Length(i)
|
|
out := make([]byte, n)
|
|
|
|
var j int
|
|
for ; n > 0; n-- {
|
|
out[j] = (byte(i >> uint((n-1)*8)))
|
|
j++
|
|
}
|
|
|
|
return out
|
|
}
|
|
|
|
func int64Length(i int64) (numBytes int) {
|
|
numBytes = 1
|
|
|
|
for i > 127 {
|
|
numBytes++
|
|
i >>= 8
|
|
}
|
|
|
|
for i < -128 {
|
|
numBytes++
|
|
i >>= 8
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// DecodePacket decodes the given bytes into a single Packet
|
|
// If a decode error is encountered, nil is returned.
|
|
func DecodePacket(data []byte) *Packet {
|
|
p, _, _ := readPacket(bytes.NewBuffer(data))
|
|
|
|
return p
|
|
}
|
|
|
|
// DecodePacketErr decodes the given bytes into a single Packet
|
|
// If a decode error is encountered, nil is returned
|
|
func DecodePacketErr(data []byte) (*Packet, error) {
|
|
p, _, err := readPacket(bytes.NewBuffer(data))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return p, nil
|
|
}
|
|
|
|
// readPacket reads a single Packet from the reader, returning the number of bytes read
|
|
func readPacket(reader io.Reader) (*Packet, int, error) {
|
|
identifier, length, read, err := readHeader(reader)
|
|
if err != nil {
|
|
return nil, read, err
|
|
}
|
|
|
|
p := &Packet{
|
|
Identifier: identifier,
|
|
}
|
|
|
|
p.Data = new(bytes.Buffer)
|
|
p.Children = make([]*Packet, 0, 2)
|
|
p.Value = nil
|
|
|
|
if p.TagType == TypeConstructed {
|
|
// TODO: if universal, ensure tag type is allowed to be constructed
|
|
|
|
// Track how much content we've read
|
|
contentRead := 0
|
|
for {
|
|
if length != LengthIndefinite {
|
|
// End if we've read what we've been told to
|
|
if contentRead == length {
|
|
break
|
|
}
|
|
// Detect if a packet boundary didn't fall on the expected length
|
|
if contentRead > length {
|
|
return nil, read, fmt.Errorf("expected to read %d bytes, read %d", length, contentRead)
|
|
}
|
|
}
|
|
|
|
// Read the next packet
|
|
child, r, err := readPacket(reader)
|
|
if err != nil {
|
|
return nil, read, err
|
|
}
|
|
contentRead += r
|
|
read += r
|
|
|
|
// Test is this is the EOC marker for our packet
|
|
if isEOCPacket(child) {
|
|
if length == LengthIndefinite {
|
|
break
|
|
}
|
|
return nil, read, errors.New("eoc child not allowed with definite length")
|
|
}
|
|
|
|
// Append and continue
|
|
p.AppendChild(child)
|
|
}
|
|
return p, read, nil
|
|
}
|
|
|
|
if length == LengthIndefinite {
|
|
return nil, read, errors.New("indefinite length used with primitive type")
|
|
}
|
|
|
|
// Read definite-length content
|
|
if MaxPacketLengthBytes > 0 && int64(length) > MaxPacketLengthBytes {
|
|
return nil, read, fmt.Errorf("length %d greater than maximum %d", length, MaxPacketLengthBytes)
|
|
}
|
|
content := make([]byte, length, length)
|
|
if length > 0 {
|
|
_, err := io.ReadFull(reader, content)
|
|
if err != nil {
|
|
if err == io.EOF {
|
|
return nil, read, io.ErrUnexpectedEOF
|
|
}
|
|
return nil, read, err
|
|
}
|
|
read += length
|
|
}
|
|
|
|
if p.ClassType == ClassUniversal {
|
|
p.Data.Write(content)
|
|
p.ByteValue = content
|
|
|
|
switch p.Tag {
|
|
case TagEOC:
|
|
case TagBoolean:
|
|
val, _ := ParseInt64(content)
|
|
|
|
p.Value = val != 0
|
|
case TagInteger:
|
|
p.Value, _ = ParseInt64(content)
|
|
case TagBitString:
|
|
case TagOctetString:
|
|
// the actual string encoding is not known here
|
|
// (e.g. for LDAP content is already an UTF8-encoded
|
|
// string). Return the data without further processing
|
|
p.Value = DecodeString(content)
|
|
case TagNULL:
|
|
case TagObjectIdentifier:
|
|
case TagObjectDescriptor:
|
|
case TagExternal:
|
|
case TagRealFloat:
|
|
case TagEnumerated:
|
|
p.Value, _ = ParseInt64(content)
|
|
case TagEmbeddedPDV:
|
|
case TagUTF8String:
|
|
p.Value = DecodeString(content)
|
|
case TagRelativeOID:
|
|
case TagSequence:
|
|
case TagSet:
|
|
case TagNumericString:
|
|
case TagPrintableString:
|
|
p.Value = DecodeString(content)
|
|
case TagT61String:
|
|
case TagVideotexString:
|
|
case TagIA5String:
|
|
case TagUTCTime:
|
|
case TagGeneralizedTime:
|
|
case TagGraphicString:
|
|
case TagVisibleString:
|
|
case TagGeneralString:
|
|
case TagUniversalString:
|
|
case TagCharacterString:
|
|
case TagBMPString:
|
|
}
|
|
} else {
|
|
p.Data.Write(content)
|
|
}
|
|
|
|
return p, read, nil
|
|
}
|
|
|
|
func (p *Packet) Bytes() []byte {
|
|
var out bytes.Buffer
|
|
|
|
out.Write(encodeIdentifier(p.Identifier))
|
|
out.Write(encodeLength(p.Data.Len()))
|
|
out.Write(p.Data.Bytes())
|
|
|
|
return out.Bytes()
|
|
}
|
|
|
|
func (p *Packet) AppendChild(child *Packet) {
|
|
p.Data.Write(child.Bytes())
|
|
p.Children = append(p.Children, child)
|
|
}
|
|
|
|
func Encode(ClassType Class, TagType Type, Tag Tag, Value interface{}, Description string) *Packet {
|
|
p := new(Packet)
|
|
|
|
p.ClassType = ClassType
|
|
p.TagType = TagType
|
|
p.Tag = Tag
|
|
p.Data = new(bytes.Buffer)
|
|
|
|
p.Children = make([]*Packet, 0, 2)
|
|
|
|
p.Value = Value
|
|
p.Description = Description
|
|
|
|
if Value != nil {
|
|
v := reflect.ValueOf(Value)
|
|
|
|
if ClassType == ClassUniversal {
|
|
switch Tag {
|
|
case TagOctetString:
|
|
sv, ok := v.Interface().(string)
|
|
|
|
if ok {
|
|
p.Data.Write([]byte(sv))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return p
|
|
}
|
|
|
|
func NewSequence(Description string) *Packet {
|
|
return Encode(ClassUniversal, TypeConstructed, TagSequence, nil, Description)
|
|
}
|
|
|
|
func NewBoolean(ClassType Class, TagType Type, Tag Tag, Value bool, Description string) *Packet {
|
|
intValue := int64(0)
|
|
|
|
if Value {
|
|
intValue = 1
|
|
}
|
|
|
|
p := Encode(ClassType, TagType, Tag, nil, Description)
|
|
|
|
p.Value = Value
|
|
p.Data.Write(encodeInteger(intValue))
|
|
|
|
return p
|
|
}
|
|
|
|
func NewInteger(ClassType Class, TagType Type, Tag Tag, Value interface{}, Description string) *Packet {
|
|
p := Encode(ClassType, TagType, Tag, nil, Description)
|
|
|
|
p.Value = Value
|
|
switch v := Value.(type) {
|
|
case int:
|
|
p.Data.Write(encodeInteger(int64(v)))
|
|
case uint:
|
|
p.Data.Write(encodeInteger(int64(v)))
|
|
case int64:
|
|
p.Data.Write(encodeInteger(v))
|
|
case uint64:
|
|
// TODO : check range or add encodeUInt...
|
|
p.Data.Write(encodeInteger(int64(v)))
|
|
case int32:
|
|
p.Data.Write(encodeInteger(int64(v)))
|
|
case uint32:
|
|
p.Data.Write(encodeInteger(int64(v)))
|
|
case int16:
|
|
p.Data.Write(encodeInteger(int64(v)))
|
|
case uint16:
|
|
p.Data.Write(encodeInteger(int64(v)))
|
|
case int8:
|
|
p.Data.Write(encodeInteger(int64(v)))
|
|
case uint8:
|
|
p.Data.Write(encodeInteger(int64(v)))
|
|
default:
|
|
// TODO : add support for big.Int ?
|
|
panic(fmt.Sprintf("Invalid type %T, expected {u|}int{64|32|16|8}", v))
|
|
}
|
|
|
|
return p
|
|
}
|
|
|
|
func NewString(ClassType Class, TagType Type, Tag Tag, Value, Description string) *Packet {
|
|
p := Encode(ClassType, TagType, Tag, nil, Description)
|
|
|
|
p.Value = Value
|
|
p.Data.Write([]byte(Value))
|
|
|
|
return p
|
|
}
|