package asn1
import (
"bytes"
"errors"
"fmt"
"io"
"math/big"
"reflect"
"time"
"unicode/utf8"
)
type forkableWriter struct {
*bytes .Buffer
pre, post *forkableWriter
}
func newForkableWriter() *forkableWriter {
return &forkableWriter {new (bytes .Buffer ), nil , nil }
}
func (f *forkableWriter ) fork () (pre , post *forkableWriter ) {
if f .pre != nil || f .post != nil {
panic ("have already forked" )
}
f .pre = newForkableWriter ()
f .post = newForkableWriter ()
return f .pre , f .post
}
func (f *forkableWriter ) Len () (l int ) {
l += f .Buffer .Len ()
if f .pre != nil {
l += f .pre .Len ()
}
if f .post != nil {
l += f .post .Len ()
}
return
}
func (f *forkableWriter ) writeTo (out io .Writer ) (n int , err error ) {
n , err = out .Write (f .Bytes ())
if err != nil {
return
}
var nn int
if f .pre != nil {
nn , err = f .pre .writeTo (out )
n += nn
if err != nil {
return
}
}
if f .post != nil {
nn , err = f .post .writeTo (out )
n += nn
}
return
}
func marshalBase128Int(out *forkableWriter , n int64 ) (err error ) {
if n == 0 {
err = out .WriteByte (0 )
return
}
l := 0
for i := n ; i > 0 ; i >>= 7 {
l ++
}
for i := l - 1 ; i >= 0 ; i -- {
o := byte (n >> uint (i *7 ))
o &= 0x7f
if i != 0 {
o |= 0x80
}
err = out .WriteByte (o )
if err != nil {
return
}
}
return nil
}
func marshalInt64(out *forkableWriter , i int64 ) (err error ) {
n := int64Length (i )
for ; n > 0 ; n -- {
err = out .WriteByte (byte (i >> uint ((n -1 )*8 )))
if err != nil {
return
}
}
return nil
}
func int64Length(i int64 ) (numBytes int ) {
numBytes = 1
for i > 127 {
numBytes ++
i >>= 8
}
for i < -128 {
numBytes ++
i >>= 8
}
return
}
func marshalBigInt(out *forkableWriter , n *big .Int ) (err error ) {
if n .Sign () < 0 {
nMinus1 := new (big .Int ).Neg (n )
nMinus1 .Sub (nMinus1 , bigOne )
bytes := nMinus1 .Bytes ()
for i := range bytes {
bytes [i ] ^= 0xff
}
if len (bytes ) == 0 || bytes [0 ]&0x80 == 0 {
err = out .WriteByte (0xff )
if err != nil {
return
}
}
_, err = out .Write (bytes )
} else if n .Sign () == 0 {
err = out .WriteByte (0x00 )
} else {
bytes := n .Bytes ()
if len (bytes ) > 0 && bytes [0 ]&0x80 != 0 {
err = out .WriteByte (0 )
if err != nil {
return
}
}
_, err = out .Write (bytes )
}
return
}
func marshalLength(out *forkableWriter , i int ) (err error ) {
n := lengthLength (i )
for ; n > 0 ; n -- {
err = out .WriteByte (byte (i >> uint ((n -1 )*8 )))
if err != nil {
return
}
}
return nil
}
func lengthLength(i int ) (numBytes int ) {
numBytes = 1
for i > 255 {
numBytes ++
i >>= 8
}
return
}
func marshalTagAndLength(out *forkableWriter , t tagAndLength ) (err error ) {
b := uint8 (t .class ) << 6
if t .isCompound {
b |= 0x20
}
if t .tag >= 31 {
b |= 0x1f
err = out .WriteByte (b )
if err != nil {
return
}
err = marshalBase128Int (out , int64 (t .tag ))
if err != nil {
return
}
} else {
b |= uint8 (t .tag )
err = out .WriteByte (b )
if err != nil {
return
}
}
if t .length >= 128 {
l := lengthLength (t .length )
err = out .WriteByte (0x80 | byte (l ))
if err != nil {
return
}
err = marshalLength (out , t .length )
if err != nil {
return
}
} else {
err = out .WriteByte (byte (t .length ))
if err != nil {
return
}
}
return nil
}
func marshalBitString(out *forkableWriter , b BitString ) (err error ) {
paddingBits := byte ((8 - b .BitLength %8 ) % 8 )
err = out .WriteByte (paddingBits )
if err != nil {
return
}
_, err = out .Write (b .Bytes )
return
}
func marshalObjectIdentifier(out *forkableWriter , oid []int ) (err error ) {
if len (oid ) < 2 || oid [0 ] > 2 || (oid [0 ] < 2 && oid [1 ] >= 40 ) {
return StructuralError {"invalid object identifier" }
}
err = marshalBase128Int (out , int64 (oid [0 ]*40 +oid [1 ]))
if err != nil {
return
}
for i := 2 ; i < len (oid ); i ++ {
err = marshalBase128Int (out , int64 (oid [i ]))
if err != nil {
return
}
}
return
}
func marshalPrintableString(out *forkableWriter , s string ) (err error ) {
b := []byte (s )
for _ , c := range b {
if !isPrintable (c ) {
return StructuralError {"PrintableString contains invalid character" }
}
}
_, err = out .Write (b )
return
}
func marshalIA5String(out *forkableWriter , s string ) (err error ) {
b := []byte (s )
for _ , c := range b {
if c > 127 {
return StructuralError {"IA5String contains invalid character" }
}
}
_, err = out .Write (b )
return
}
func marshalUTF8String(out *forkableWriter , s string ) (err error ) {
_, err = out .Write ([]byte (s ))
return
}
func marshalTwoDigits(out *forkableWriter , v int ) (err error ) {
err = out .WriteByte (byte ('0' + (v /10 )%10 ))
if err != nil {
return
}
return out .WriteByte (byte ('0' + v %10 ))
}
func marshalFourDigits(out *forkableWriter , v int ) (err error ) {
var bytes [4 ]byte
for i := range bytes {
bytes [3 -i ] = '0' + byte (v %10 )
v /= 10
}
_, err = out .Write (bytes [:])
return
}
func outsideUTCRange(t time .Time ) bool {
year := t .Year ()
return year < 1950 || year >= 2050
}
func marshalUTCTime(out *forkableWriter , t time .Time ) (err error ) {
year := t .Year ()
switch {
case 1950 <= year && year < 2000 :
err = marshalTwoDigits (out , year -1900 )
case 2000 <= year && year < 2050 :
err = marshalTwoDigits (out , year -2000 )
default :
return StructuralError {"cannot represent time as UTCTime" }
}
if err != nil {
return
}
return marshalTimeCommon (out , t )
}
func marshalGeneralizedTime(out *forkableWriter , t time .Time ) (err error ) {
year := t .Year ()
if year < 0 || year > 9999 {
return StructuralError {"cannot represent time as GeneralizedTime" }
}
if err = marshalFourDigits (out , year ); err != nil {
return
}
return marshalTimeCommon (out , t )
}
func marshalTimeCommon(out *forkableWriter , t time .Time ) (err error ) {
_ , month , day := t .Date ()
err = marshalTwoDigits (out , int (month ))
if err != nil {
return
}
err = marshalTwoDigits (out , day )
if err != nil {
return
}
hour , min , sec := t .Clock ()
err = marshalTwoDigits (out , hour )
if err != nil {
return
}
err = marshalTwoDigits (out , min )
if err != nil {
return
}
err = marshalTwoDigits (out , sec )
if err != nil {
return
}
_ , offset := t .Zone ()
switch {
case offset /60 == 0 :
err = out .WriteByte ('Z' )
return
case offset > 0 :
err = out .WriteByte ('+' )
case offset < 0 :
err = out .WriteByte ('-' )
}
if err != nil {
return
}
offsetMinutes := offset / 60
if offsetMinutes < 0 {
offsetMinutes = -offsetMinutes
}
err = marshalTwoDigits (out , offsetMinutes /60 )
if err != nil {
return
}
err = marshalTwoDigits (out , offsetMinutes %60 )
return
}
func stripTagAndLength(in []byte ) []byte {
_ , offset , err := parseTagAndLength (in , 0 )
if err != nil {
return in
}
return in [offset :]
}
func marshalBody(out *forkableWriter , value reflect .Value , params fieldParameters ) (err error ) {
switch value .Type () {
case flagType :
return nil
case timeType :
t := value .Interface ().(time .Time )
if params .timeType == TagGeneralizedTime || outsideUTCRange (t ) {
return marshalGeneralizedTime (out , t )
} else {
return marshalUTCTime (out , t )
}
case bitStringType :
return marshalBitString (out , value .Interface ().(BitString ))
case objectIdentifierType :
return marshalObjectIdentifier (out , value .Interface ().(ObjectIdentifier ))
case bigIntType :
return marshalBigInt (out , value .Interface ().(*big .Int ))
}
switch v := value ; v .Kind () {
case reflect .Bool :
if v .Bool () {
return out .WriteByte (255 )
} else {
return out .WriteByte (0 )
}
case reflect .Int , reflect .Int8 , reflect .Int16 , reflect .Int32 , reflect .Int64 :
return marshalInt64 (out , v .Int ())
case reflect .Struct :
t := v .Type ()
startingField := 0
if t .NumField () > 0 && t .Field (0 ).Type == rawContentsType {
s := v .Field (0 )
if s .Len () > 0 {
bytes := make ([]byte , s .Len ())
for i := 0 ; i < s .Len (); i ++ {
bytes [i ] = uint8 (s .Index (i ).Uint ())
}
_, err = out .Write (stripTagAndLength (bytes ))
return
} else {
startingField = 1
}
}
for i := startingField ; i < t .NumField (); i ++ {
var pre *forkableWriter
pre , out = out .fork ()
err = marshalField (pre , v .Field (i ), parseFieldParameters (t .Field (i ).Tag .Get ("asn1" )))
if err != nil {
return
}
}
return
case reflect .Slice :
sliceType := v .Type ()
if sliceType .Elem ().Kind () == reflect .Uint8 {
bytes := make ([]byte , v .Len ())
for i := 0 ; i < v .Len (); i ++ {
bytes [i ] = uint8 (v .Index (i ).Uint ())
}
_, err = out .Write (bytes )
return
}
params .explicit = false
params .tag = nil
for i := 0 ; i < v .Len (); i ++ {
var pre *forkableWriter
pre , out = out .fork ()
err = marshalField (pre , v .Index (i ), params )
if err != nil {
return
}
}
return
case reflect .String :
switch params .stringType {
case TagIA5String :
return marshalIA5String (out , v .String ())
case TagPrintableString :
return marshalPrintableString (out , v .String ())
default :
return marshalUTF8String (out , v .String ())
}
}
return StructuralError {"unknown Go type" }
}
func marshalField(out *forkableWriter , v reflect .Value , params fieldParameters ) (err error ) {
if !v .IsValid () {
return fmt .Errorf ("asn1: cannot marshal nil value" )
}
if v .Kind () == reflect .Interface && v .Type ().NumMethod () == 0 {
return marshalField (out , v .Elem (), params )
}
if v .Kind () == reflect .Slice && v .Len () == 0 && params .omitEmpty {
return
}
if params .optional && params .defaultValue != nil && canHaveDefaultValue (v .Kind ()) {
defaultValue := reflect .New (v .Type ()).Elem ()
defaultValue .SetInt (*params .defaultValue )
if reflect .DeepEqual (v .Interface (), defaultValue .Interface ()) {
return
}
}
if params .optional && params .defaultValue == nil {
if reflect .DeepEqual (v .Interface (), reflect .Zero (v .Type ()).Interface ()) {
return
}
}
if v .Type () == rawValueType {
rv := v .Interface ().(RawValue )
if len (rv .FullBytes ) != 0 {
_, err = out .Write (rv .FullBytes )
} else {
err = marshalTagAndLength (out , tagAndLength {rv .Class , rv .Tag , len (rv .Bytes ), rv .IsCompound })
if err != nil {
return
}
_, err = out .Write (rv .Bytes )
}
return
}
tag , isCompound , ok := getUniversalType (v .Type ())
if !ok {
err = StructuralError {fmt .Sprintf ("unknown Go type: %v" , v .Type ())}
return
}
class := ClassUniversal
if params .timeType != 0 && tag != TagUTCTime {
return StructuralError {"explicit time type given to non-time member" }
}
if params .stringType != 0 && !(tag == TagPrintableString || (v .Kind () == reflect .Slice && tag == 16 && v .Type ().Elem ().Kind () == reflect .String )) {
return StructuralError {"explicit string type given to non-string member" }
}
switch tag {
case TagPrintableString :
if params .stringType == 0 {
for _ , r := range v .String () {
if r >= utf8 .RuneSelf || !isPrintable (byte (r )) {
if !utf8 .ValidString (v .String ()) {
return errors .New ("asn1: string not valid UTF-8" )
}
tag = TagUTF8String
break
}
}
} else {
tag = params .stringType
}
case TagUTCTime :
if params .timeType == TagGeneralizedTime || outsideUTCRange (v .Interface ().(time .Time )) {
tag = TagGeneralizedTime
}
}
if params .set {
if tag != TagSequence {
return StructuralError {"non sequence tagged as set" }
}
tag = TagSet
}
tags , body := out .fork ()
err = marshalBody (body , v , params )
if err != nil {
return
}
bodyLen := body .Len ()
var explicitTag *forkableWriter
if params .explicit {
explicitTag , tags = tags .fork ()
}
if !params .explicit && params .tag != nil {
tag = *params .tag
class = ClassContextSpecific
}
err = marshalTagAndLength (tags , tagAndLength {class , tag , bodyLen , isCompound })
if err != nil {
return
}
if params .explicit {
err = marshalTagAndLength (explicitTag , tagAndLength {
class : ClassContextSpecific ,
tag : *params .tag ,
length : bodyLen + tags .Len (),
isCompound : true ,
})
}
return err
}
func Marshal (val interface {}) ([]byte , error ) {
var out bytes .Buffer
v := reflect .ValueOf (val )
f := newForkableWriter ()
err := marshalField (f , v , fieldParameters {})
if err != nil {
return nil , err
}
_, err = f .writeTo (&out )
return out .Bytes (), err
}
The pages are generated with Golds v0.6.7 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @Go100and1 (reachable from the left QR code) to get the latest news of Golds .