Skip to content
Open
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
3 changes: 1 addition & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
go: ['1.23', '1.24', '1.25']
go: ["1.23", "1.24", "1.25", "1.26"]
name: ${{ matrix.os }} @ Go ${{ matrix.go }}
runs-on: ${{ matrix.os }}
steps:
Expand All @@ -36,7 +36,6 @@ jobs:
restore-keys: |
${{ runner.os }}-go-


- name: Build
run: go build -v ./...

Expand Down
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@

.DS_Store
.idea
.vscode
.vscode
.gocache/
23 changes: 23 additions & 0 deletions .gosec.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"global": {},
"exclude-rules": [
{
"path": "(.*/)?(internal/(encoding|decoding|stream/encoding|stream/decoding)|ext|time)/.*\\.go$",
"rules": [
"G115"
]
},
{
"path": "(.*/)?internal/common/testutil/.*\\.go$",
"rules": [
"G115"
]
},
{
"path": "(.*/)?internal/(encoding/string|decoding/bin|stream/encoding/string|stream/decoding/bin)\\.go$",
"rules": [
"G103"
]
}
]
}
38 changes: 22 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@
[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fshamaton%2Fmsgpack.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2Fshamaton%2Fmsgpack?ref=badge_shield)

## 📣 Announcement: `time.Time` decoding defaults to **UTC** in v3

Starting with **v3.0.0**, when decoding MessagePack **Timestamp** into Go’s `time.Time`,
the default `Location` will be **UTC** (previously `Local`). The instant is unchanged.
To keep the old behavior, use `SetDecodedTimeAsLocal()`.

## Features

* Supported types : primitive / array / slice / struct / map / interface{} and time.Time
* Renaming fields via `msgpack:"field_name"`
* Omitting fields via `msgpack:"-"`
Expand All @@ -22,15 +24,18 @@ To keep the old behavior, use `SetDecodedTimeAsLocal()`.
## Installation

Current version is **msgpack/v3**.

```sh
go get -u github.com/shamaton/msgpack/v3
```

### Upgrading from v2

If you are upgrading from v2, please note the `time.Time` decoding change mentioned in the announcement above.
To keep the v2 behavior, use `msgpack.SetDecodedTimeAsLocal()` after upgrading.

## Quick Start

```go
package main

Expand All @@ -40,31 +45,31 @@ import (
)

type Struct struct {
String string
String string
}

// simple
func main() {
v := Struct{String: "msgpack"}

d, err := msgpack.Marshal(v)
if err != nil {
panic(err)
}
r := Struct{}
if err = msgpack.Unmarshal(d, &r); err != nil {
panic(err)
}
v := Struct{String: "msgpack"}

d, err := msgpack.Marshal(v)
if err != nil {
panic(err)
}
r := Struct{}
if err = msgpack.Unmarshal(d, &r); err != nil {
panic(err)
}
}

// streaming
func handle(w http.ResponseWriter, r *http.Request) {
var body Struct
if err := msgpack.UnmarshalRead(r, &body); err != nil {
panic(err)
var body Struct
if err := msgpack.UnmarshalRead(r, &body); err != nil {
panic(err)
}
if err := msgpack.MarshalWrite(w, body); err != nil {
panic(err)
if err := msgpack.MarshalWrite(w, body); err != nil {
panic(err)
}
}
```
Expand Down Expand Up @@ -115,6 +120,7 @@ msgpack.SetDecodedTimeAsUTC()
```

## Benchmark

This result made from [shamaton/msgpack_bench](https://github.com/shamaton/msgpack_bench)

![msgpack_bench](https://github.com/user-attachments/assets/ed5bc4c5-a149-4083-98b8-ee6820c00eae)
Expand Down
3 changes: 1 addition & 2 deletions ext/decode.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@ type Decoder interface {
}

// DecoderCommon provides common utility methods for decoding data from bytes.
type DecoderCommon struct {
}
type DecoderCommon struct{}

// ReadSize1 reads a single byte from the given index in the byte slice.
// Returns the byte and the new index after reading.
Expand Down
3 changes: 1 addition & 2 deletions ext/encode.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,7 @@ type Encoder interface {
// EncoderCommon provides utility methods for encoding various types of values into bytes.
// It includes methods to encode integers and unsigned integers of different sizes,
// as well as methods to write raw byte slices into a target byte slice.
type EncoderCommon struct {
}
type EncoderCommon struct{}

// SetByte1Int64 encodes a single byte from the given int64 value into the byte slice at the specified offset.
// Returns the new offset after writing the byte.
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module github.com/shamaton/msgpack/v3

go 1.23
go 1.26.1
3 changes: 1 addition & 2 deletions internal/common/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ import (
)

// Common is used encoding/decoding
type Common struct {
}
type Common struct{}

// FieldInfo holds information about a struct field including its path for embedded structs
type FieldInfo struct {
Expand Down
3 changes: 2 additions & 1 deletion internal/decoding/bool_test.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
package decoding

import (
"github.com/shamaton/msgpack/v3/def"
"reflect"
"testing"

"github.com/shamaton/msgpack/v3/def"
)

func Test_asBool(t *testing.T) {
Expand Down
12 changes: 8 additions & 4 deletions internal/decoding/complex_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,10 @@ func Test_asComplex64(t *testing.T) {
},
{
Name: "Fixext16.ok",
Data: []byte{def.Fixext16, byte(def.ComplexTypeCode()),
63, 240, 0, 0, 0, 0, 0, 0, 63, 240, 0, 0, 0, 0, 0, 0},
Data: []byte{
def.Fixext16, byte(def.ComplexTypeCode()),
63, 240, 0, 0, 0, 0, 0, 0, 63, 240, 0, 0, 0, 0, 0, 0,
},
Expected: complex(1, 1),
MethodAs: method,
},
Expand Down Expand Up @@ -132,8 +134,10 @@ func Test_asComplex128(t *testing.T) {
},
{
Name: "Fixext16.ok",
Data: []byte{def.Fixext16, byte(def.ComplexTypeCode()),
63, 240, 0, 0, 0, 0, 0, 0, 63, 240, 0, 0, 0, 0, 0, 0},
Data: []byte{
def.Fixext16, byte(def.ComplexTypeCode()),
63, 240, 0, 0, 0, 0, 0, 0, 63, 240, 0, 0, 0, 0, 0, 0,
},
Expected: complex(1, 1),
MethodAs: method,
},
Expand Down
2 changes: 1 addition & 1 deletion internal/decoding/decoding.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ type decoder struct {
func Decode(data []byte, v interface{}, asArray bool) error {
d := decoder{data: data, asArray: asArray}

if d.data == nil || len(d.data) < 1 {
if len(d.data) < 1 {
return def.ErrNoData
}
rv := reflect.ValueOf(v)
Expand Down
60 changes: 58 additions & 2 deletions internal/decoding/ext.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
package decoding

import (
"encoding/binary"

"github.com/shamaton/msgpack/v3/def"
"github.com/shamaton/msgpack/v3/ext"
"github.com/shamaton/msgpack/v3/time"
)

var extCoderMap = map[int8]ext.Decoder{time.Decoder.Code(): time.Decoder}
var extCoders = []ext.Decoder{time.Decoder}
var (
extCoderMap = map[int8]ext.Decoder{time.Decoder.Code(): time.Decoder}
extCoders = []ext.Decoder{time.Decoder}
)

// AddExtDecoder adds decoders for extension types.
func AddExtDecoder(f ext.Decoder) {
Expand Down Expand Up @@ -45,6 +50,57 @@ func updateExtCoders() {
}
}

func (d *decoder) extEndOffset(offset int) (bool, int, error) {
code, offset, err := d.readSize1(offset)
if err != nil {
return false, 0, err
}
return d.extEndOffsetWithCode(code, offset)
}

func (d *decoder) extEndOffsetWithCode(code byte, offset int) (bool, int, error) {
switch code {
case def.Fixext1:
_, offset, err := d.readSizeN(offset, def.Byte1+def.Byte1)
return true, offset, err
case def.Fixext2:
_, offset, err := d.readSizeN(offset, def.Byte1+def.Byte2)
return true, offset, err
case def.Fixext4:
_, offset, err := d.readSizeN(offset, def.Byte1+def.Byte4)
return true, offset, err
case def.Fixext8:
_, offset, err := d.readSizeN(offset, def.Byte1+def.Byte8)
return true, offset, err
case def.Fixext16:
_, offset, err := d.readSizeN(offset, def.Byte1+def.Byte16)
return true, offset, err
case def.Ext8:
size, offset, err := d.readSize1(offset)
if err != nil {
return true, 0, err
}
_, offset, err = d.readSizeN(offset, def.Byte1+int(size))
return true, offset, err
case def.Ext16:
sizeBytes, offset, err := d.readSize2(offset)
if err != nil {
return true, 0, err
}
_, offset, err = d.readSizeN(offset, def.Byte1+int(binary.BigEndian.Uint16(sizeBytes)))
return true, offset, err
case def.Ext32:
sizeBytes, offset, err := d.readSize4(offset)
if err != nil {
return true, 0, err
}
_, offset, err = d.readSizeN(offset, def.Byte1+int(binary.BigEndian.Uint32(sizeBytes)))
return true, offset, err
default:
return false, 0, nil
}
}

/*
var zero = time.Unix(0,0)

Expand Down
66 changes: 66 additions & 0 deletions internal/decoding/ext_test.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package decoding

import (
"reflect"
"testing"

"github.com/shamaton/msgpack/v3/def"
tu "github.com/shamaton/msgpack/v3/internal/common/testutil"
"github.com/shamaton/msgpack/v3/time"
)
Expand All @@ -20,3 +22,67 @@ func Test_RemoveExtDecoder(t *testing.T) {
tu.Equal(t, len(extCoders), 1)
})
}

type trackingExtDecoder struct {
isTypeCalls int
asValueCalls int
}

func (td *trackingExtDecoder) Code() int8 {
return 42
}

func (td *trackingExtDecoder) IsType(_ int, _ *[]byte) bool {
td.isTypeCalls++
return false
}

func (td *trackingExtDecoder) AsValue(_ int, _ reflect.Kind, _ *[]byte) (interface{}, int, error) {
td.asValueCalls++
return nil, 0, ErrTestExtDecoder
}

func TestExtValidationRejectsTruncatedBytesBeforeCustomDecoders(t *testing.T) {
dec := &trackingExtDecoder{}
AddExtDecoder(dec)
defer RemoveExtDecoder(dec)

testcases := []struct {
name string
data []byte
}{
{name: "fixext1", data: []byte{def.Fixext1, 42}},
{name: "fixext2", data: []byte{def.Fixext2, 42, 0}},
{name: "fixext4", data: []byte{def.Fixext4, 42, 0, 0, 0}},
{name: "fixext8", data: []byte{def.Fixext8, 42, 0, 0, 0, 0, 0, 0, 0}},
{name: "fixext16", data: []byte{def.Fixext16, 42, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}},
{name: "ext8", data: []byte{def.Ext8, 1, 42}},
{name: "ext16", data: []byte{def.Ext16, 0, 1, 42}},
{name: "ext32", data: []byte{def.Ext32, 0, 0, 0, 1, 42}},
}

for _, tc := range testcases {
t.Run("interface/"+tc.name, func(t *testing.T) {
dec.isTypeCalls = 0
dec.asValueCalls = 0

d := decoder{data: tc.data}
_, _, err := d.asInterface(0, reflect.Interface)
tu.IsError(t, err, def.ErrTooShortBytes)
tu.Equal(t, dec.isTypeCalls, 0)
tu.Equal(t, dec.asValueCalls, 0)
})

t.Run("struct/"+tc.name, func(t *testing.T) {
dec.isTypeCalls = 0
dec.asValueCalls = 0

d := decoder{data: tc.data}
var v struct{}
_, err := d.setStruct(reflect.ValueOf(&v).Elem(), 0, reflect.Struct)
tu.IsError(t, err, def.ErrTooShortBytes)
tu.Equal(t, dec.isTypeCalls, 0)
tu.Equal(t, dec.asValueCalls, 0)
})
}
}
1 change: 0 additions & 1 deletion internal/decoding/int.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ func (d *decoder) isNegativeFixNum(v byte) bool {
}

func (d *decoder) asInt(offset int, k reflect.Kind) (int64, int, error) {

code, _, err := d.readSize1(offset)
if err != nil {
return 0, 0, err
Expand Down
Loading