nef: treat Version as string

Following changes in C# code and simpilifying things a lot.
This commit is contained in:
Roman Khimov 2020-11-27 21:42:54 +03:00
parent e12c52f588
commit 4d0eaef510
2 changed files with 25 additions and 175 deletions

View file

@ -4,9 +4,6 @@ import (
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"errors" "errors"
"fmt"
"strconv"
"strings"
"github.com/nspcc-dev/neo-go/pkg/config" "github.com/nspcc-dev/neo-go/pkg/config"
"github.com/nspcc-dev/neo-go/pkg/crypto/hash" "github.com/nspcc-dev/neo-go/pkg/crypto/hash"
@ -21,7 +18,7 @@ import (
// +------------+-----------+------------------------------------------------------------+ // +------------+-----------+------------------------------------------------------------+
// | Magic | 4 bytes | Magic header | // | Magic | 4 bytes | Magic header |
// | Compiler | 32 bytes | Compiler used | // | Compiler | 32 bytes | Compiler used |
// | Version | 16 bytes | Compiler version (Major, Minor, Build, Version) | // | Version | 16 bytes | Compiler version |
// +------------+-----------+------------------------------------------------------------+ // +------------+-----------+------------------------------------------------------------+
// | Script | Var bytes | Var bytes for the payload | // | Script | Var bytes | Var bytes for the payload |
// +------------+-----------+------------------------------------------------------------+ // +------------+-----------+------------------------------------------------------------+
@ -35,6 +32,8 @@ const (
MaxScriptLength = 1024 * 1024 MaxScriptLength = 1024 * 1024
// compilerFieldSize is the length of `Compiler` File header field in bytes. // compilerFieldSize is the length of `Compiler` File header field in bytes.
compilerFieldSize = 32 compilerFieldSize = 32
// versionFieldSize is the length of `Version` File header field in bytes.
versionFieldSize = 16
) )
// File represents compiled contract file structure according to the NEF3 standard. // File represents compiled contract file structure according to the NEF3 standard.
@ -48,97 +47,26 @@ type File struct {
type Header struct { type Header struct {
Magic uint32 Magic uint32
Compiler string Compiler string
Version Version Version string
}
// Version represents compiler version.
type Version struct {
Major int32
Minor int32
Build int32
Revision int32
} }
// NewFile returns new NEF3 file with script specified. // NewFile returns new NEF3 file with script specified.
func NewFile(script []byte) (File, error) { func NewFile(script []byte) (*File, error) {
file := File{ file := &File{
Header: Header{ Header: Header{
Magic: Magic, Magic: Magic,
Compiler: "neo-go", Compiler: "neo-go",
Version: config.Version,
}, },
Script: script, Script: script,
} }
v, err := GetVersion(config.Version) if len(config.Version) > versionFieldSize {
if err != nil { return nil, errors.New("too long version")
return file, err
} }
file.Header.Version = v
file.Checksum = file.CalculateChecksum() file.Checksum = file.CalculateChecksum()
return file, nil return file, nil
} }
// GetVersion returns Version from the given string. It accepts the following formats:
// `major[-...].minor[-...].build[-...]` and `major[-...].minor[-...].build[-...].revision[-...]`
// where `major`, `minor`, `build` and `revision` are 32-bit integers with base=10
func GetVersion(version string) (Version, error) {
var (
result Version
err error
)
versions := strings.SplitN(version, ".", 4)
if len(versions) < 3 {
return result, errors.New("invalid version format")
}
result.Major, err = parseDashedVersion(versions[0])
if err != nil {
return result, fmt.Errorf("failed to parse major version: %w", err)
}
result.Minor, err = parseDashedVersion(versions[1])
if err != nil {
return result, fmt.Errorf("failed to parse minor version: %w", err)
}
result.Build, err = parseDashedVersion(versions[2])
if err != nil {
return result, fmt.Errorf("failed to parse build version: %w", err)
}
if len(versions) == 4 {
result.Revision, err = parseDashedVersion(versions[3])
if err != nil {
return result, fmt.Errorf("failed to parse revision version: %w", err)
}
}
return result, nil
}
// parseDashedVersion extracts int from string of the format `int[-...]` where `int` is
// a 32-bit integer with base=10.
func parseDashedVersion(version string) (int32, error) {
version = strings.SplitN(version, "-", 2)[0]
result, err := strconv.ParseInt(version, 10, 32)
if err != nil {
return 0, err
}
return int32(result), nil
}
// EncodeBinary implements io.Serializable interface.
func (v *Version) EncodeBinary(w *io.BinWriter) {
w.WriteU32LE(uint32(v.Major))
w.WriteU32LE(uint32(v.Minor))
w.WriteU32LE(uint32(v.Build))
w.WriteU32LE(uint32(v.Revision))
}
// DecodeBinary implements io.Serializable interface.
func (v *Version) DecodeBinary(r *io.BinReader) {
v.Major = int32(r.ReadU32LE())
v.Minor = int32(r.ReadU32LE())
v.Build = int32(r.ReadU32LE())
v.Revision = int32(r.ReadU32LE())
}
// EncodeBinary implements io.Serializable interface. // EncodeBinary implements io.Serializable interface.
func (h *Header) EncodeBinary(w *io.BinWriter) { func (h *Header) EncodeBinary(w *io.BinWriter) {
w.WriteU32LE(h.Magic) w.WriteU32LE(h.Magic)
@ -146,12 +74,15 @@ func (h *Header) EncodeBinary(w *io.BinWriter) {
w.Err = errors.New("invalid compiler name length") w.Err = errors.New("invalid compiler name length")
return return
} }
bytes := []byte(h.Compiler) var b = make([]byte, compilerFieldSize)
w.WriteBytes(bytes) copy(b, []byte(h.Compiler))
if len(bytes) < compilerFieldSize { w.WriteBytes(b)
w.WriteBytes(make([]byte, compilerFieldSize-len(bytes))) b = b[:versionFieldSize]
for i := range b {
b[i] = 0
} }
h.Version.EncodeBinary(w) copy(b, []byte(h.Version))
w.WriteBytes(b)
} }
// DecodeBinary implements io.Serializable interface. // DecodeBinary implements io.Serializable interface.
@ -167,7 +98,12 @@ func (h *Header) DecodeBinary(r *io.BinReader) {
return r == 0 return r == 0
}) })
h.Compiler = string(buf) h.Compiler = string(buf)
h.Version.DecodeBinary(r) buf = buf[:versionFieldSize]
r.ReadBytes(buf)
buf = bytes.TrimRightFunc(buf, func(r rune) bool {
return r == 0
})
h.Version = string(buf)
} }
// CalculateChecksum returns first 4 bytes of double-SHA256(Header) converted to uint32. // CalculateChecksum returns first 4 bytes of double-SHA256(Header) converted to uint32.

View file

@ -13,12 +13,7 @@ func TestEncodeDecodeBinary(t *testing.T) {
Header: Header{ Header: Header{
Magic: Magic, Magic: Magic,
Compiler: "the best compiler ever", Compiler: "the best compiler ever",
Version: Version{ Version: "1.2.3.4",
Major: 1,
Minor: 2,
Build: 3,
Revision: 4,
},
}, },
Script: script, Script: script,
} }
@ -67,12 +62,7 @@ func TestBytesFromBytes(t *testing.T) {
Header: Header{ Header: Header{
Magic: Magic, Magic: Magic,
Compiler: "the best compiler ever", Compiler: "the best compiler ever",
Version: Version{ Version: "1.2.3.4",
Major: 1,
Minor: 2,
Build: 3,
Revision: 4,
},
}, },
Script: script, Script: script,
} }
@ -84,79 +74,3 @@ func TestBytesFromBytes(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, expected, actual) require.Equal(t, expected, actual)
} }
func TestGetVersion(t *testing.T) {
testCases := map[string]struct {
input string
fails bool
expected Version
}{
"major only": {
input: "1",
fails: true,
},
"major and minor only": {
input: "1.1",
fails: true,
},
"major, minor and revision only": {
input: "1.1.1",
expected: Version{
Major: 1,
Minor: 1,
Build: 1,
Revision: 0,
},
},
"full version": {
input: "1.1.1.1",
expected: Version{
Major: 1,
Minor: 1,
Build: 1,
Revision: 1,
},
},
"dashed, without revision": {
input: "1-pre.2-pre.3-pre",
expected: Version{
Major: 1,
Minor: 2,
Build: 3,
Revision: 0,
},
},
"dashed, full version": {
input: "1-pre.2-pre.3-pre.4-pre",
expected: Version{
Major: 1,
Minor: 2,
Build: 3,
Revision: 4,
},
},
"dashed build": {
input: "1.2.3-pre.4",
expected: Version{
Major: 1,
Minor: 2,
Build: 3,
Revision: 4,
},
},
"extra versions": {
input: "1.2.3.4.5",
fails: true,
},
}
for name, test := range testCases {
t.Run(name, func(t *testing.T) {
actual, err := GetVersion(test.input)
if test.fails {
require.NotNil(t, err)
} else {
require.Equal(t, test.expected, actual)
}
})
}
}