]> Cypherpunks repositories - gostls13.git/commitdiff
asn1: add big support.
authorAdam Langley <agl@golang.org>
Fri, 20 May 2011 17:20:08 +0000 (10:20 -0700)
committerAdam Langley <agl@golang.org>
Fri, 20 May 2011 17:20:08 +0000 (10:20 -0700)
Initially I wanted to minimise dependencies but it's become clear that
big int support in ASN.1 is a common need and that it should be part
of the core.

R=bradfitz
CC=golang-dev
https://golang.org/cl/4550063

src/pkg/asn1/asn1.go
src/pkg/asn1/asn1_test.go
src/pkg/asn1/common.go
src/pkg/asn1/marshal.go

index e7a46196cfa3b6bef5d1daf092b54b9c6a271a7b..2650ef2a2685204748b29698a99c56578eeda808 100644 (file)
@@ -20,6 +20,7 @@ package asn1
 // everything by any means.
 
 import (
+       "big"
        "fmt"
        "os"
        "reflect"
@@ -88,6 +89,27 @@ func parseInt(bytes []byte) (int, os.Error) {
        return int(ret64), nil
 }
 
+var bigOne = big.NewInt(1)
+
+// parseBigInt treats the given bytes as a big-endian, signed integer and returns
+// the result.
+func parseBigInt(bytes []byte) *big.Int {
+       ret := new(big.Int)
+       if len(bytes) > 0 && bytes[0]&0x80 == 0x80 {
+               // This is a negative number.
+               notBytes := make([]byte, len(bytes))
+               for i := range notBytes {
+                       notBytes[i] = ^bytes[i]
+               }
+               ret.SetBytes(notBytes)
+               ret.Add(ret, bigOne)
+               ret.Neg(ret)
+               return ret
+       }
+       ret.SetBytes(bytes)
+       return ret
+}
+
 // BIT STRING
 
 // BitString is the structure to use when you want an ASN.1 BIT STRING type. A
@@ -425,6 +447,7 @@ var (
        timeType             = reflect.TypeOf(&time.Time{})
        rawValueType         = reflect.TypeOf(RawValue{})
        rawContentsType      = reflect.TypeOf(RawContent(nil))
+       bigIntType           = reflect.TypeOf(new(big.Int))
 )
 
 // invalidLength returns true iff offset + length > sliceLength, or if the
@@ -639,6 +662,10 @@ func parseField(v reflect.Value, bytes []byte, initOffset int, params fieldParam
        case flagType:
                v.SetBool(true)
                return
+       case bigIntType:
+               parsedInt := parseBigInt(innerBytes)
+               v.Set(reflect.ValueOf(parsedInt))
+               return
        }
        switch val := v; val.Kind() {
        case reflect.Bool:
index 78f56280524b1e9de43939c04aad679699076075..463dbe0264920678a395c50ceef7426c1805310a 100644 (file)
@@ -42,6 +42,33 @@ func TestParseInt64(t *testing.T) {
        }
 }
 
+var bigIntTests = []struct {
+       in     []byte
+       base10 string
+}{
+       {[]byte{0xff}, "-1"},
+       {[]byte{0x00}, "0"},
+       {[]byte{0x01}, "1"},
+       {[]byte{0x00, 0xff}, "255"},
+       {[]byte{0xff, 0x00}, "-256"},
+       {[]byte{0x01, 0x00}, "256"},
+}
+
+func TestParseBigInt(t *testing.T) {
+       for i, test := range bigIntTests {
+               ret := parseBigInt(test.in)
+               if ret.String() != test.base10 {
+                       t.Errorf("#%d: bad result from %x, got %s want %s", i, test.in, ret.String(), test.base10)
+               }
+               fw := newForkableWriter()
+               marshalBigInt(fw, ret)
+               result := fw.Bytes()
+               if !bytes.Equal(result, test.in) {
+                       t.Errorf("#%d: got %x from marshaling %s, want %x", i, result, ret, test.in)
+               }
+       }
+}
+
 type bitStringTest struct {
        in        []byte
        ok        bool
index 0e6abc46b88804bb59f17834b4a3d7ab732ba1f1..9db887e251ba66b65c072b1a8d70fe3a5cfd15be 100644 (file)
@@ -132,6 +132,8 @@ func getUniversalType(t reflect.Type) (tagNumber int, isCompound, ok bool) {
                return tagUTCTime, false, true
        case enumeratedType:
                return tagEnum, false, true
+       case bigIntType:
+               return tagInteger, false, true
        }
        switch t.Kind() {
        case reflect.Bool:
index fc7c337f1ab09b05b7eae597f979c5c3cf9230f1..771ac28243183c137ba0369106da82550e3941eb 100644 (file)
@@ -5,6 +5,7 @@
 package asn1
 
 import (
+       "big"
        "bytes"
        "fmt"
        "io"
@@ -125,6 +126,43 @@ func int64Length(i int64) (numBytes int) {
        return
 }
 
+func marshalBigInt(out *forkableWriter, n *big.Int) (err os.Error) {
+       if n.Sign() < 0 {
+               // A negative number has to be converted to two's-complement
+               // form. So we'll subtract 1 and invert. If the
+               // most-significant-bit isn't set then we'll need to pad the
+               // beginning with 0xff in order to keep the number negative.
+               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 {
+               // Zero is written as a single 0 zero rather than no bytes.
+               err = out.WriteByte(0x00)
+       } else {
+               bytes := n.Bytes()
+               if len(bytes) > 0 && bytes[0]&0x80 != 0 {
+                       // We'll have to pad this with 0x00 in order to stop it
+                       // looking like a negative number.
+                       err = out.WriteByte(0)
+                       if err != nil {
+                               return
+                       }
+               }
+               _, err = out.Write(bytes)
+       }
+       return
+}
+
 func marshalLength(out *forkableWriter, i int) (err os.Error) {
        n := lengthLength(i)
 
@@ -334,6 +372,8 @@ func marshalBody(out *forkableWriter, value reflect.Value, params fieldParameter
                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() {
@@ -420,6 +460,9 @@ func marshalField(out *forkableWriter, v reflect.Value, params fieldParameters)
 
        if v.Type() == rawValueType {
                rv := v.Interface().(RawValue)
+               if rv.Class == 0 && rv.Tag == 0 && len(rv.Bytes) == 0 && params.optional {
+                       return
+               }
                err = marshalTagAndLength(out, tagAndLength{rv.Class, rv.Tag, len(rv.Bytes), rv.IsCompound})
                if err != nil {
                        return