1
0
mirror of https://github.com/schollz/cowyo.git synced 2023-08-10 21:13:00 +03:00

Update all dependencies to latest

This commit is contained in:
Daniel Heath 2018-01-22 21:07:50 +11:00
parent ff420fb81d
commit b4638476cc
327 changed files with 43002 additions and 30657 deletions

32
Gopkg.lock generated
View File

@ -29,13 +29,13 @@
branch = "master" branch = "master"
name = "github.com/danielheath/gin-teeny-security" name = "github.com/danielheath/gin-teeny-security"
packages = ["."] packages = ["."]
revision = "0bc769386cc5a75bd79ccdcceaf0f977eeb6990e" revision = "5f00fb6ac0933c2b378c907a3e2a43667afc4289"
[[projects]] [[projects]]
name = "github.com/garyburd/redigo" name = "github.com/garyburd/redigo"
packages = ["internal","redis"] packages = ["internal","redis"]
revision = "34a326de1fea52965fa5ad664d3fc7163dd4b0a1" revision = "d1ed5c67e5794de818ea85e6b522fda02623a484"
version = "v1.2.0" version = "v1.4.0"
[[projects]] [[projects]]
branch = "master" branch = "master"
@ -65,7 +65,7 @@
branch = "master" branch = "master"
name = "github.com/golang/protobuf" name = "github.com/golang/protobuf"
packages = ["proto"] packages = ["proto"]
revision = "1643683e1b54a9e88ad26d98f81400c8c9d9f4f9" revision = "1e59b77b52bf8e4b449a57e6f79f21226d571845"
[[projects]] [[projects]]
name = "github.com/gorilla/context" name = "github.com/gorilla/context"
@ -107,7 +107,7 @@
branch = "master" branch = "master"
name = "github.com/microcosm-cc/bluemonday" name = "github.com/microcosm-cc/bluemonday"
packages = ["."] packages = ["."]
revision = "68fecaef60268522d2ac3f0123cec9d3bcab7b6e" revision = "542fd4642604d0d0c26112396ce5b1a9d01eee0b"
[[projects]] [[projects]]
name = "github.com/russross/blackfriday" name = "github.com/russross/blackfriday"
@ -137,19 +137,19 @@
branch = "master" branch = "master"
name = "github.com/sergi/go-diff" name = "github.com/sergi/go-diff"
packages = ["diffmatchpatch"] packages = ["diffmatchpatch"]
revision = "2fc9cd33b5f86077aa3e0f442fa0476a9fa9a1dc" revision = "1744e2970ca51c86172c8190fadad617561ed6e7"
[[projects]] [[projects]]
branch = "master" branch = "master"
name = "github.com/shurcooL/github_flavored_markdown" name = "github.com/shurcooL/github_flavored_markdown"
packages = ["."] packages = ["."]
revision = "cccd3ce4f8e394ae9f87de0bd8b37e00625913d9" revision = "28433ea3fc83827d77424782fefdcd94703366cc"
[[projects]] [[projects]]
branch = "master" branch = "master"
name = "github.com/shurcooL/go" name = "github.com/shurcooL/go"
packages = ["parserutil","printerutil","reflectfind","reflectsource"] packages = ["parserutil","printerutil","reflectfind","reflectsource"]
revision = "c661e953e604ba4a84a3c4e458462a481bd6ce72" revision = "004faa6b0118cf52635363b72b51cdcc297800a2"
[[projects]] [[projects]]
branch = "master" branch = "master"
@ -161,7 +161,7 @@
branch = "master" branch = "master"
name = "github.com/shurcooL/graphql" name = "github.com/shurcooL/graphql"
packages = ["ident"] packages = ["ident"]
revision = "cf6db17b893acfad0ca1929ba6be45bf854790ed" revision = "d0549edd16dceb6939e538fdb1b4f2ec7ee816cc"
[[projects]] [[projects]]
branch = "master" branch = "master"
@ -179,7 +179,7 @@
branch = "master" branch = "master"
name = "github.com/shurcooL/octiconssvg" name = "github.com/shurcooL/octiconssvg"
packages = ["."] packages = ["."]
revision = "8c9861b86a08c72d14e0285d0dc313bb6df52295" revision = "38b02129bb6460858e11f90798a3832da1e502bd"
[[projects]] [[projects]]
branch = "master" branch = "master"
@ -200,28 +200,28 @@
revision = "bd320f5d308e1a3c4314c678d8227a0d72574ae7" revision = "bd320f5d308e1a3c4314c678d8227a0d72574ae7"
[[projects]] [[projects]]
branch = "master"
name = "github.com/ugorji/go" name = "github.com/ugorji/go"
packages = ["codec"] packages = ["codec"]
revision = "50189f05eaf5a0c17e5084eb8f7fb91e23699840" revision = "9831f2c3ac1068a78f50999a30db84270f647af6"
version = "v1.1"
[[projects]] [[projects]]
branch = "master" branch = "master"
name = "golang.org/x/crypto" name = "golang.org/x/crypto"
packages = ["bcrypt","blowfish"] packages = ["bcrypt","blowfish"]
revision = "bd6f299fb381e4c3393d1c4b1f0b94f5e77650c8" revision = "39efaea5da11abd5e2b90a435b1f338cdb94619c"
[[projects]] [[projects]]
branch = "master" branch = "master"
name = "golang.org/x/net" name = "golang.org/x/net"
packages = ["html","html/atom"] packages = ["html","html/atom"]
revision = "01c190206fbdffa42f334f4b2bf2220f50e64920" revision = "5ccada7d0a7ba9aeb5d3aca8d3501b4c2a509fec"
[[projects]] [[projects]]
branch = "master" branch = "master"
name = "golang.org/x/sys" name = "golang.org/x/sys"
packages = ["unix"] packages = ["unix"]
revision = "4fe5d7925040acd225bf9c7cee65e82d07f06bff" revision = "af50095a40f9041b3b38960738837185c26e9419"
[[projects]] [[projects]]
name = "gopkg.in/go-playground/validator.v8" name = "gopkg.in/go-playground/validator.v8"
@ -245,7 +245,7 @@
branch = "v2" branch = "v2"
name = "gopkg.in/yaml.v2" name = "gopkg.in/yaml.v2"
packages = ["."] packages = ["."]
revision = "eb3733d160e74a9c7e442f435eb3bea458e1d19f" revision = "d670f9405373e636a5a2765eea47fac0c9bc91a4"
[solve-meta] [solve-meta]
analyzer-name = "dep" analyzer-name = "dep"

View File

@ -1,9 +1,14 @@
// A GIN middleware providing low-fi security for personal stuff. // A GIN middleware providing low-fi security for personal stuff.
package gin_teeny_security package gin_teeny_security
import "github.com/gin-gonic/gin" import "github.com/gin-gonic/gin"
import "github.com/gin-contrib/sessions" import "github.com/gin-contrib/sessions"
import "net/http" import "net/http"
import "net/url"
import "fmt"
import "io"
import "html/template"
// Forces you to a login page until you provide a secret code. // Forces you to a login page until you provide a secret code.
// No CSRF protection, so any script on any page can log you // No CSRF protection, so any script on any page can log you
@ -12,54 +17,135 @@ import "net/http"
// net can inject stuff. If you're sending open CORS headers this // net can inject stuff. If you're sending open CORS headers this
// would be particularly bad. // would be particularly bad.
func RequiresSecretAccessCode(secretAccessCode, path string) gin.HandlerFunc { func RequiresSecretAccessCode(secretAccessCode, path string) gin.HandlerFunc {
return func(c *gin.Context) { cfg := &Config{
session := sessions.Default(c) Path: path,
if c.Request.URL.Path == path { Secret: secretAccessCode,
if c.Request.Method == "POST" { }
c.Request.ParseForm()
if c.Request.PostForm.Get("secretAccessCode") == secretAccessCode { return cfg.Middleware
c.Header("Location", "/") }
session.Set("secretAccessCode", secretAccessCode)
session.Save() type Config struct {
c.AbortWithStatus(http.StatusFound) Path string // defaults to login
return Secret string
} else { RequireAuth func(*gin.Context) bool // defaults to always requiring auth if unset
session.Set("secretAccessCode", "") Template *template.Template
session.Save() SaveKeyToSession func(*gin.Context, string)
c.Data(http.StatusForbidden, "text/html", []byte(` GetKeyFromSession func(*gin.Context) string
<h1>Login</h1> }
<h2>Wrong password</h2>
<form action="`+path+`" method="POST"> func (c Config) saveKey(ctx *gin.Context, k string) {
<input name="secretAccessCode" /> if c.SaveKeyToSession == nil {
<input type="submit" value="Login" /> c.SaveKeyToSession = DefaultSetSession
</form> }
`)) c.SaveKeyToSession(ctx, k)
c.Abort() }
return
} func (c Config) getKey(ctx *gin.Context) string {
} else if c.Request.Method == "GET" { if c.GetKeyFromSession == nil {
c.Data(http.StatusOK, "text/html", []byte(` c.GetKeyFromSession = DefaultGetSession
<h1>Login</h1> }
<form action="`+path+`" method="POST"> return c.GetKeyFromSession(ctx)
<input name="secretAccessCode" /> }
<input type="submit" value="Login" />
</form> func DefaultSetSession(c *gin.Context, secret string) {
`)) session := sessions.Default(c)
c.Abort() session.Set("secretAccessCode", secret)
session.Save()
}
func DefaultGetSession(c *gin.Context) string {
session := sessions.Default(c)
str, ok := session.Get("secretAccessCode").(string)
if !ok {
fmt.Println(session.Get("secretAccessCode"))
return ""
}
return str
}
func (c Config) path() string {
if c.Path == "" {
return "/login/"
}
return c.Path
}
func (c Config) requireAuth(ctx *gin.Context) bool {
if ctx.Request.Header.Get("Authorization") == c.Secret {
return false
}
return c.RequireAuth == nil || c.RequireAuth(ctx)
}
func (c Config) template() *template.Template {
if c.Template == nil {
return DEFAULT_LOGIN_PAGE
}
return c.Template
}
func (c Config) ExecTemplate(w io.Writer, message, returnUrl string) error {
return c.template().Execute(w, LoginPageParams{
Message: message,
Path: c.path() + "?" + url.Values{"return": []string{returnUrl}}.Encode(),
})
}
type LoginPageParams struct {
Message string
Path string
}
var DEFAULT_LOGIN_PAGE = template.Must(template.New("login").Parse(`
<h1>Login</h1>
{{ if .Message }}<h2>{{ .Message }}</h2>{{ end }}
<form action="{{.Path}}" method="POST">
<input name="secretAccessCode" />
<input type="submit" value="Login" />
</form>
`))
func (cfg *Config) Middleware(c *gin.Context) {
if c.Request.URL.Path == cfg.path() {
returnTo := c.Request.URL.Query().Get("return")
if returnTo == "" {
returnTo = "/"
}
if c.Request.Method == "POST" {
c.Request.ParseForm()
fmt.Println(c.Request.PostForm.Get("secretAccessCode"))
if c.Request.PostForm.Get("secretAccessCode") == cfg.Secret {
c.Header("Location", returnTo)
cfg.saveKey(c, cfg.Secret)
c.AbortWithStatus(http.StatusFound)
return return
} else { } else {
c.Next() cfg.saveKey(c, "")
c.Writer.WriteHeader(http.StatusForbidden)
cfg.ExecTemplate(c.Writer, "Wrong Password", returnTo)
c.Abort()
return return
} }
} } else if c.Request.Method == "GET" {
cfg.ExecTemplate(c.Writer, "", returnTo)
v := session.Get("secretAccessCode") c.Abort()
if v != secretAccessCode { return
c.Header("Location", path)
c.AbortWithStatus(http.StatusTemporaryRedirect)
} else { } else {
c.Next() c.Next()
return
} }
} }
v := cfg.getKey(c)
if cfg.requireAuth(c) && (v != cfg.Secret) {
c.Header("Location", cfg.Path+"?"+url.Values{"return": []string{c.Request.URL.RequestURI()}}.Encode())
c.AbortWithStatus(http.StatusTemporaryRedirect)
} else {
c.Next()
}
} }

View File

@ -0,0 +1,109 @@
package gin_teeny_security
import "net/http/cookiejar"
import "strings"
import "net/http/httptest"
import "net/http"
import "net/url"
import "log"
import "io"
import "io/ioutil"
import "testing"
import "github.com/gin-gonic/gin"
import "github.com/gin-contrib/sessions"
func init() {
http.DefaultClient.Jar, _ = cookiejar.New(nil)
}
func SampleGinApp() *gin.Engine {
router := gin.Default()
store := sessions.NewCookieStore([]byte("tis a secret"))
router.Use(sessions.Sessions("mysession", store))
cfg := &Config{
Path: "/enter-password/",
Secret: "garden",
RequireAuth: func(c *gin.Context) bool {
return !strings.HasPrefix(c.Request.URL.Path, "/public")
},
}
router.Use(cfg.Middleware)
router.GET("/private", func(c *gin.Context) {
c.Data(http.StatusOK, "application/html", []byte("private stuff"))
})
router.GET("/public", func(c *gin.Context) {
c.Data(http.StatusOK, "application/html", []byte("public stuff"))
})
return router
}
func TestAuth(t *testing.T) {
ts := httptest.NewServer(SampleGinApp())
// Check public stuff can be accessed
res, err := http.Get(ts.URL + "/public/")
die(err)
mustBe("public stuff", readString(res.Body))
// Check private stuff can't be accessed
res, err = http.Get(ts.URL + "/private/")
die(err)
// Check entering the password as an HTTP header instead of a cookie works
r, err := http.NewRequest("GET", ts.URL+"/private/", nil)
die(err)
r.Header.Set("Authorization", "garden")
res, err = http.DefaultClient.Do(r)
die(err)
mustBe("private stuff", readString(res.Body))
// Check entering the wrong password as an HTTP header instead of a cookie works
r, err = http.NewRequest("GET", ts.URL+"/private/", nil)
die(err)
r.Header.Set("Authorization", "wrong")
res, err = http.DefaultClient.Do(r)
die(err)
mustStartWith("<h1>Login</h1>\n\n<form action=\"/enter-password/?return=%2Fprivate\"", readString(res.Body))
res, err = http.Get(ts.URL + "/private/")
die(err)
mustStartWith("<h1>Login</h1>\n\n<form action=\"/enter-password/?return=%2Fprivate\"", readString(res.Body))
// Check entering a bad password gives you a message
res, err = http.PostForm(ts.URL+"/enter-password/", url.Values{"secretAccessCode": []string{"wrong"}})
die(err)
mustStartWith("<h1>Login</h1>\n<h2>Wrong Password</h2>", readString(res.Body))
// Check entering a good password lets you access things
res, err = http.PostForm(ts.URL+"/enter-password/?return=/private/", url.Values{"secretAccessCode": []string{"garden"}})
die(err)
mustBe("private stuff", readString(res.Body))
}
func mustStartWith(expected, actual string) {
if !strings.HasPrefix(strings.TrimSpace(actual), expected) {
log.Panicf("Should have gotten content starting with '%s' but got '%s'", expected, actual)
}
}
func mustBe(expected, actual string) {
if actual != expected {
log.Panicf("Should have gotten '%s' but got '%s'", expected, actual)
}
}
func readString(r io.ReadCloser) string {
b, e := ioutil.ReadAll(r)
defer r.Close()
die(e)
return string(b)
}
func die(e error) {
if e != nil {
log.Fatal(e)
}
}

View File

@ -21,6 +21,7 @@ Documentation
- [API Reference](http://godoc.org/github.com/garyburd/redigo/redis) - [API Reference](http://godoc.org/github.com/garyburd/redigo/redis)
- [FAQ](https://github.com/garyburd/redigo/wiki/FAQ) - [FAQ](https://github.com/garyburd/redigo/wiki/FAQ)
- [Examples](https://godoc.org/github.com/garyburd/redigo/redis#pkg-examples)
Installation Installation
------------ ------------

View File

@ -29,9 +29,12 @@ import (
"time" "time"
) )
var (
_ ConnWithTimeout = (*conn)(nil)
)
// conn is the low-level implementation of Conn // conn is the low-level implementation of Conn
type conn struct { type conn struct {
// Shared // Shared
mu sync.Mutex mu sync.Mutex
pending int pending int
@ -73,6 +76,7 @@ type DialOption struct {
type dialOptions struct { type dialOptions struct {
readTimeout time.Duration readTimeout time.Duration
writeTimeout time.Duration writeTimeout time.Duration
dialer *net.Dialer
dial func(network, addr string) (net.Conn, error) dial func(network, addr string) (net.Conn, error)
db int db int
password string password string
@ -95,17 +99,27 @@ func DialWriteTimeout(d time.Duration) DialOption {
}} }}
} }
// DialConnectTimeout specifies the timeout for connecting to the Redis server. // DialConnectTimeout specifies the timeout for connecting to the Redis server when
// no DialNetDial option is specified.
func DialConnectTimeout(d time.Duration) DialOption { func DialConnectTimeout(d time.Duration) DialOption {
return DialOption{func(do *dialOptions) { return DialOption{func(do *dialOptions) {
dialer := net.Dialer{Timeout: d} do.dialer.Timeout = d
do.dial = dialer.Dial }}
}
// DialKeepAlive specifies the keep-alive period for TCP connections to the Redis server
// when no DialNetDial option is specified.
// If zero, keep-alives are not enabled. If no DialKeepAlive option is specified then
// the default of 5 minutes is used to ensure that half-closed TCP sessions are detected.
func DialKeepAlive(d time.Duration) DialOption {
return DialOption{func(do *dialOptions) {
do.dialer.KeepAlive = d
}} }}
} }
// DialNetDial specifies a custom dial function for creating TCP // DialNetDial specifies a custom dial function for creating TCP
// connections. If this option is left out, then net.Dial is // connections, otherwise a net.Dialer customized via the other options is used.
// used. DialNetDial overrides DialConnectTimeout. // DialNetDial overrides DialConnectTimeout and DialKeepAlive.
func DialNetDial(dial func(network, addr string) (net.Conn, error)) DialOption { func DialNetDial(dial func(network, addr string) (net.Conn, error)) DialOption {
return DialOption{func(do *dialOptions) { return DialOption{func(do *dialOptions) {
do.dial = dial do.dial = dial
@ -155,11 +169,16 @@ func DialUseTLS(useTLS bool) DialOption {
// address using the specified options. // address using the specified options.
func Dial(network, address string, options ...DialOption) (Conn, error) { func Dial(network, address string, options ...DialOption) (Conn, error) {
do := dialOptions{ do := dialOptions{
dial: net.Dial, dialer: &net.Dialer{
KeepAlive: time.Minute * 5,
},
} }
for _, option := range options { for _, option := range options {
option.f(&do) option.f(&do)
} }
if do.dial == nil {
do.dial = do.dialer.Dial
}
netConn, err := do.dial(network, address) netConn, err := do.dial(network, address)
if err != nil { if err != nil {
@ -167,7 +186,12 @@ func Dial(network, address string, options ...DialOption) (Conn, error) {
} }
if do.useTLS { if do.useTLS {
tlsConfig := cloneTLSClientConfig(do.tlsConfig, do.skipVerify) var tlsConfig *tls.Config
if do.tlsConfig == nil {
tlsConfig = &tls.Config{InsecureSkipVerify: do.skipVerify}
} else {
tlsConfig = cloneTLSConfig(do.tlsConfig)
}
if tlsConfig.ServerName == "" { if tlsConfig.ServerName == "" {
host, _, err := net.SplitHostPort(address) host, _, err := net.SplitHostPort(address)
if err != nil { if err != nil {
@ -556,10 +580,17 @@ func (c *conn) Flush() error {
return nil return nil
} }
func (c *conn) Receive() (reply interface{}, err error) { func (c *conn) Receive() (interface{}, error) {
if c.readTimeout != 0 { return c.ReceiveWithTimeout(c.readTimeout)
c.conn.SetReadDeadline(time.Now().Add(c.readTimeout)) }
func (c *conn) ReceiveWithTimeout(timeout time.Duration) (reply interface{}, err error) {
var deadline time.Time
if timeout != 0 {
deadline = time.Now().Add(timeout)
} }
c.conn.SetReadDeadline(deadline)
if reply, err = c.readReply(); err != nil { if reply, err = c.readReply(); err != nil {
return nil, c.fatal(err) return nil, c.fatal(err)
} }
@ -582,6 +613,10 @@ func (c *conn) Receive() (reply interface{}, err error) {
} }
func (c *conn) Do(cmd string, args ...interface{}) (interface{}, error) { func (c *conn) Do(cmd string, args ...interface{}) (interface{}, error) {
return c.DoWithTimeout(c.readTimeout, cmd, args...)
}
func (c *conn) DoWithTimeout(readTimeout time.Duration, cmd string, args ...interface{}) (interface{}, error) {
c.mu.Lock() c.mu.Lock()
pending := c.pending pending := c.pending
c.pending = 0 c.pending = 0
@ -605,9 +640,11 @@ func (c *conn) Do(cmd string, args ...interface{}) (interface{}, error) {
return nil, c.fatal(err) return nil, c.fatal(err)
} }
if c.readTimeout != 0 { var deadline time.Time
c.conn.SetReadDeadline(time.Now().Add(c.readTimeout)) if readTimeout != 0 {
deadline = time.Now().Add(readTimeout)
} }
c.conn.SetReadDeadline(deadline)
if cmd == "" { if cmd == "" {
reply := make([]interface{}, pending) reply := make([]interface{}, pending)

View File

@ -34,14 +34,16 @@ import (
type testConn struct { type testConn struct {
io.Reader io.Reader
io.Writer io.Writer
readDeadline time.Time
writeDeadline time.Time
} }
func (*testConn) Close() error { return nil } func (*testConn) Close() error { return nil }
func (*testConn) LocalAddr() net.Addr { return nil } func (*testConn) LocalAddr() net.Addr { return nil }
func (*testConn) RemoteAddr() net.Addr { return nil } func (*testConn) RemoteAddr() net.Addr { return nil }
func (*testConn) SetDeadline(t time.Time) error { return nil } func (c *testConn) SetDeadline(t time.Time) error { c.readDeadline = t; c.writeDeadline = t; return nil }
func (*testConn) SetReadDeadline(t time.Time) error { return nil } func (c *testConn) SetReadDeadline(t time.Time) error { c.readDeadline = t; return nil }
func (*testConn) SetWriteDeadline(t time.Time) error { return nil } func (c *testConn) SetWriteDeadline(t time.Time) error { c.writeDeadline = t; return nil }
func dialTestConn(r string, w io.Writer) redis.DialOption { func dialTestConn(r string, w io.Writer) redis.DialOption {
return redis.DialNetDial(func(network, addr string) (net.Conn, error) { return redis.DialNetDial(func(network, addr string) (net.Conn, error) {
@ -764,7 +766,6 @@ func BenchmarkDoPing(b *testing.B) {
var clientTLSConfig, serverTLSConfig tls.Config var clientTLSConfig, serverTLSConfig tls.Config
func init() { func init() {
// The certificate and key for testing TLS dial options was created // The certificate and key for testing TLS dial options was created
// using the command // using the command
// //
@ -822,3 +823,45 @@ Bjqn3yoLHaoZVvbWOi0C2TCN4FjXjaLNZGifQPbIcaA=
clientTLSConfig.RootCAs = x509.NewCertPool() clientTLSConfig.RootCAs = x509.NewCertPool()
clientTLSConfig.RootCAs.AddCert(certificate) clientTLSConfig.RootCAs.AddCert(certificate)
} }
func TestWithTimeout(t *testing.T) {
for _, recv := range []bool{true, false} {
for _, defaultTimout := range []time.Duration{0, time.Minute} {
var buf bytes.Buffer
nc := &testConn{Reader: strings.NewReader("+OK\r\n+OK\r\n+OK\r\n+OK\r\n+OK\r\n+OK\r\n+OK\r\n+OK\r\n+OK\r\n+OK\r\n"), Writer: &buf}
c, _ := redis.Dial("", "", redis.DialReadTimeout(defaultTimout), redis.DialNetDial(func(network, addr string) (net.Conn, error) { return nc, nil }))
for i := 0; i < 4; i++ {
var minDeadline, maxDeadline time.Time
// Alternate between default and specified timeout.
if i%2 == 0 {
if defaultTimout != 0 {
minDeadline = time.Now().Add(defaultTimout)
}
if recv {
c.Receive()
} else {
c.Do("PING")
}
if defaultTimout != 0 {
maxDeadline = time.Now().Add(defaultTimout)
}
} else {
timeout := 10 * time.Minute
minDeadline = time.Now().Add(timeout)
if recv {
redis.ReceiveWithTimeout(c, timeout)
} else {
redis.DoWithTimeout(c, timeout, "PING")
}
maxDeadline = time.Now().Add(timeout)
}
// Expect set deadline in expected range.
if nc.readDeadline.Before(minDeadline) || nc.readDeadline.After(maxDeadline) {
t.Errorf("recv %v, %d: do deadline error: %v, %v, %v", recv, i, minDeadline, nc.readDeadline, maxDeadline)
}
}
}
}
}

View File

@ -4,11 +4,7 @@ package redis
import "crypto/tls" import "crypto/tls"
// similar cloneTLSClientConfig in the stdlib, but also honor skipVerify for the nil case func cloneTLSConfig(cfg *tls.Config) *tls.Config {
func cloneTLSClientConfig(cfg *tls.Config, skipVerify bool) *tls.Config {
if cfg == nil {
return &tls.Config{InsecureSkipVerify: skipVerify}
}
return &tls.Config{ return &tls.Config{
Rand: cfg.Rand, Rand: cfg.Rand,
Time: cfg.Time, Time: cfg.Time,

View File

@ -1,14 +1,10 @@
// +build go1.7 // +build go1.7,!go1.8
package redis package redis
import "crypto/tls" import "crypto/tls"
// similar cloneTLSClientConfig in the stdlib, but also honor skipVerify for the nil case func cloneTLSConfig(cfg *tls.Config) *tls.Config {
func cloneTLSClientConfig(cfg *tls.Config, skipVerify bool) *tls.Config {
if cfg == nil {
return &tls.Config{InsecureSkipVerify: skipVerify}
}
return &tls.Config{ return &tls.Config{
Rand: cfg.Rand, Rand: cfg.Rand,
Time: cfg.Time, Time: cfg.Time,

9
vendor/github.com/garyburd/redigo/redis/go18.go generated vendored Normal file
View File

@ -0,0 +1,9 @@
// +build go1.8
package redis
import "crypto/tls"
func cloneTLSConfig(cfg *tls.Config) *tls.Config {
return cfg.Clone()
}

View File

@ -18,6 +18,11 @@ import (
"bytes" "bytes"
"fmt" "fmt"
"log" "log"
"time"
)
var (
_ ConnWithTimeout = (*loggingConn)(nil)
) )
// NewLoggingConn returns a logging wrapper around a connection. // NewLoggingConn returns a logging wrapper around a connection.
@ -104,6 +109,12 @@ func (c *loggingConn) Do(commandName string, args ...interface{}) (interface{},
return reply, err return reply, err
} }
func (c *loggingConn) DoWithTimeout(timeout time.Duration, commandName string, args ...interface{}) (interface{}, error) {
reply, err := DoWithTimeout(c.Conn, timeout, commandName, args...)
c.print("DoWithTimeout", commandName, args, reply, err)
return reply, err
}
func (c *loggingConn) Send(commandName string, args ...interface{}) error { func (c *loggingConn) Send(commandName string, args ...interface{}) error {
err := c.Conn.Send(commandName, args...) err := c.Conn.Send(commandName, args...)
c.print("Send", commandName, args, nil, err) c.print("Send", commandName, args, nil, err)
@ -115,3 +126,9 @@ func (c *loggingConn) Receive() (interface{}, error) {
c.print("Receive", "", nil, reply, err) c.print("Receive", "", nil, reply, err)
return reply, err return reply, err
} }
func (c *loggingConn) ReceiveWithTimeout(timeout time.Duration) (interface{}, error) {
reply, err := ReceiveWithTimeout(c.Conn, timeout)
c.print("ReceiveWithTimeout", "", nil, reply, err)
return reply, err
}

View File

@ -28,6 +28,11 @@ import (
"github.com/garyburd/redigo/internal" "github.com/garyburd/redigo/internal"
) )
var (
_ ConnWithTimeout = (*pooledConnection)(nil)
_ ConnWithTimeout = (*errorConnection)(nil)
)
var nowFunc = time.Now // for testing var nowFunc = time.Now // for testing
// ErrPoolExhausted is returned from a pool connection method (Do, Send, // ErrPoolExhausted is returned from a pool connection method (Do, Send,
@ -96,7 +101,7 @@ var (
// return nil, err // return nil, err
// } // }
// return c, nil // return c, nil
// } // },
// } // }
// //
// Use the TestOnBorrow function to check the health of an idle connection // Use the TestOnBorrow function to check the health of an idle connection
@ -115,7 +120,6 @@ var (
// } // }
// //
type Pool struct { type Pool struct {
// Dial is an application supplied function for creating and configuring a // Dial is an application supplied function for creating and configuring a
// connection. // connection.
// //
@ -269,7 +273,6 @@ func (p *Pool) get() (Conn, error) {
} }
for { for {
// Get idle connection. // Get idle connection.
for i, n := 0, p.idle.Len(); i < n; i++ { for i, n := 0, p.idle.Len(); i < n; i++ {
@ -420,6 +423,16 @@ func (pc *pooledConnection) Do(commandName string, args ...interface{}) (reply i
return pc.c.Do(commandName, args...) return pc.c.Do(commandName, args...)
} }
func (pc *pooledConnection) DoWithTimeout(timeout time.Duration, commandName string, args ...interface{}) (reply interface{}, err error) {
cwt, ok := pc.c.(ConnWithTimeout)
if !ok {
return nil, errTimeoutNotSupported
}
ci := internal.LookupCommandInfo(commandName)
pc.state = (pc.state | ci.Set) &^ ci.Clear
return cwt.DoWithTimeout(timeout, commandName, args...)
}
func (pc *pooledConnection) Send(commandName string, args ...interface{}) error { func (pc *pooledConnection) Send(commandName string, args ...interface{}) error {
ci := internal.LookupCommandInfo(commandName) ci := internal.LookupCommandInfo(commandName)
pc.state = (pc.state | ci.Set) &^ ci.Clear pc.state = (pc.state | ci.Set) &^ ci.Clear
@ -434,11 +447,23 @@ func (pc *pooledConnection) Receive() (reply interface{}, err error) {
return pc.c.Receive() return pc.c.Receive()
} }
func (pc *pooledConnection) ReceiveWithTimeout(timeout time.Duration) (reply interface{}, err error) {
cwt, ok := pc.c.(ConnWithTimeout)
if !ok {
return nil, errTimeoutNotSupported
}
return cwt.ReceiveWithTimeout(timeout)
}
type errorConnection struct{ err error } type errorConnection struct{ err error }
func (ec errorConnection) Do(string, ...interface{}) (interface{}, error) { return nil, ec.err } func (ec errorConnection) Do(string, ...interface{}) (interface{}, error) { return nil, ec.err }
func (ec errorConnection) Send(string, ...interface{}) error { return ec.err } func (ec errorConnection) DoWithTimeout(time.Duration, string, ...interface{}) (interface{}, error) {
func (ec errorConnection) Err() error { return ec.err } return nil, ec.err
func (ec errorConnection) Close() error { return ec.err } }
func (ec errorConnection) Flush() error { return ec.err } func (ec errorConnection) Send(string, ...interface{}) error { return ec.err }
func (ec errorConnection) Receive() (interface{}, error) { return nil, ec.err } func (ec errorConnection) Err() error { return ec.err }
func (ec errorConnection) Close() error { return nil }
func (ec errorConnection) Flush() error { return ec.err }
func (ec errorConnection) Receive() (interface{}, error) { return nil, ec.err }
func (ec errorConnection) ReceiveWithTimeout(time.Duration) (interface{}, error) { return nil, ec.err }

View File

@ -14,11 +14,13 @@
package redis package redis
import "errors" import (
"errors"
"time"
)
// Subscription represents a subscribe or unsubscribe notification. // Subscription represents a subscribe or unsubscribe notification.
type Subscription struct { type Subscription struct {
// Kind is "subscribe", "unsubscribe", "psubscribe" or "punsubscribe" // Kind is "subscribe", "unsubscribe", "psubscribe" or "punsubscribe"
Kind string Kind string
@ -31,7 +33,6 @@ type Subscription struct {
// Message represents a message notification. // Message represents a message notification.
type Message struct { type Message struct {
// The originating channel. // The originating channel.
Channel string Channel string
@ -41,7 +42,6 @@ type Message struct {
// PMessage represents a pmessage notification. // PMessage represents a pmessage notification.
type PMessage struct { type PMessage struct {
// The matched pattern. // The matched pattern.
Pattern string Pattern string
@ -106,7 +106,17 @@ func (c PubSubConn) Ping(data string) error {
// or error. The return value is intended to be used directly in a type switch // or error. The return value is intended to be used directly in a type switch
// as illustrated in the PubSubConn example. // as illustrated in the PubSubConn example.
func (c PubSubConn) Receive() interface{} { func (c PubSubConn) Receive() interface{} {
reply, err := Values(c.Conn.Receive()) return c.receiveInternal(c.Conn.Receive())
}
// ReceiveWithTimeout is like Receive, but it allows the application to
// override the connection's default timeout.
func (c PubSubConn) ReceiveWithTimeout(timeout time.Duration) interface{} {
return c.receiveInternal(ReceiveWithTimeout(c.Conn, timeout))
}
func (c PubSubConn) receiveInternal(replyArg interface{}, errArg error) interface{} {
reply, err := Values(replyArg, errArg)
if err != nil { if err != nil {
return err return err
} }

View File

@ -0,0 +1,165 @@
// Copyright 2012 Gary Burd
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
// +build go1.7
package redis_test
import (
"context"
"fmt"
"time"
"github.com/garyburd/redigo/redis"
)
// listenPubSubChannels listens for messages on Redis pubsub channels. The
// onStart function is called after the channels are subscribed. The onMessage
// function is called for each message.
func listenPubSubChannels(ctx context.Context, redisServerAddr string,
onStart func() error,
onMessage func(channel string, data []byte) error,
channels ...string) error {
// A ping is set to the server with this period to test for the health of
// the connection and server.
const healthCheckPeriod = time.Minute
c, err := redis.Dial("tcp", redisServerAddr,
// Read timeout on server should be greater than ping period.
redis.DialReadTimeout(healthCheckPeriod+10*time.Second),
redis.DialWriteTimeout(10*time.Second))
if err != nil {
return err
}
defer c.Close()
psc := redis.PubSubConn{Conn: c}
if err := psc.Subscribe(redis.Args{}.AddFlat(channels)...); err != nil {
return err
}
done := make(chan error, 1)
// Start a goroutine to receive notifications from the server.
go func() {
for {
switch n := psc.Receive().(type) {
case error:
done <- n
return
case redis.Message:
if err := onMessage(n.Channel, n.Data); err != nil {
done <- err
return
}
case redis.Subscription:
switch n.Count {
case len(channels):
// Notify application when all channels are subscribed.
if err := onStart(); err != nil {
done <- err
return
}
case 0:
// Return from the goroutine when all channels are unsubscribed.
done <- nil
return
}
}
}
}()
ticker := time.NewTicker(healthCheckPeriod)
defer ticker.Stop()
loop:
for err == nil {
select {
case <-ticker.C:
// Send ping to test health of connection and server. If
// corresponding pong is not received, then receive on the
// connection will timeout and the receive goroutine will exit.
if err = psc.Ping(""); err != nil {
break loop
}
case <-ctx.Done():
break loop
case err := <-done:
// Return error from the receive goroutine.
return err
}
}
// Signal the receiving goroutine to exit by unsubscribing from all channels.
psc.Unsubscribe()
// Wait for goroutine to complete.
return <-done
}
func publish() {
c, err := dial()
if err != nil {
fmt.Println(err)
return
}
defer c.Close()
c.Do("PUBLISH", "c1", "hello")
c.Do("PUBLISH", "c2", "world")
c.Do("PUBLISH", "c1", "goodbye")
}
// This example shows how receive pubsub notifications with cancelation and
// health checks.
func ExamplePubSubConn() {
redisServerAddr, err := serverAddr()
if err != nil {
fmt.Println(err)
return
}
ctx, cancel := context.WithCancel(context.Background())
err = listenPubSubChannels(ctx,
redisServerAddr,
func() error {
// The start callback is a good place to backfill missed
// notifications. For the purpose of this example, a goroutine is
// started to send notifications.
go publish()
return nil
},
func(channel string, message []byte) error {
fmt.Printf("channel: %s, message: %s\n", channel, message)
// For the purpose of this example, cancel the listener's context
// after receiving last message sent by publish().
if string(message) == "goodbye" {
cancel()
}
return nil
},
"c1", "c2")
if err != nil {
fmt.Println(err)
return
}
// Output:
// channel: c1, message: hello
// channel: c2, message: world
// channel: c1, message: goodbye
}

View File

@ -15,93 +15,13 @@
package redis_test package redis_test
import ( import (
"fmt"
"reflect" "reflect"
"sync"
"testing" "testing"
"time"
"github.com/garyburd/redigo/redis" "github.com/garyburd/redigo/redis"
) )
func publish(channel, value interface{}) {
c, err := dial()
if err != nil {
fmt.Println(err)
return
}
defer c.Close()
c.Do("PUBLISH", channel, value)
}
// Applications can receive pushed messages from one goroutine and manage subscriptions from another goroutine.
func ExamplePubSubConn() {
c, err := dial()
if err != nil {
fmt.Println(err)
return
}
defer c.Close()
var wg sync.WaitGroup
wg.Add(2)
psc := redis.PubSubConn{Conn: c}
// This goroutine receives and prints pushed notifications from the server.
// The goroutine exits when the connection is unsubscribed from all
// channels or there is an error.
go func() {
defer wg.Done()
for {
switch n := psc.Receive().(type) {
case redis.Message:
fmt.Printf("Message: %s %s\n", n.Channel, n.Data)
case redis.PMessage:
fmt.Printf("PMessage: %s %s %s\n", n.Pattern, n.Channel, n.Data)
case redis.Subscription:
fmt.Printf("Subscription: %s %s %d\n", n.Kind, n.Channel, n.Count)
if n.Count == 0 {
return
}
case error:
fmt.Printf("error: %v\n", n)
return
}
}
}()
// This goroutine manages subscriptions for the connection.
go func() {
defer wg.Done()
psc.Subscribe("example")
psc.PSubscribe("p*")
// The following function calls publish a message using another
// connection to the Redis server.
publish("example", "hello")
publish("example", "world")
publish("pexample", "foo")
publish("pexample", "bar")
// Unsubscribe from all connections. This will cause the receiving
// goroutine to exit.
psc.Unsubscribe()
psc.PUnsubscribe()
}()
wg.Wait()
// Output:
// Subscription: subscribe example 1
// Subscription: psubscribe p* 2
// Message: example hello
// Message: example world
// PMessage: p* pexample foo
// PMessage: p* pexample bar
// Subscription: unsubscribe example 1
// Subscription: punsubscribe p* 0
}
func expectPushed(t *testing.T, c redis.PubSubConn, message string, expected interface{}) { func expectPushed(t *testing.T, c redis.PubSubConn, message string, expected interface{}) {
actual := c.Receive() actual := c.Receive()
if !reflect.DeepEqual(actual, expected) { if !reflect.DeepEqual(actual, expected) {
@ -145,4 +65,10 @@ func TestPushed(t *testing.T) {
c.Conn.Send("PING") c.Conn.Send("PING")
c.Conn.Flush() c.Conn.Flush()
expectPushed(t, c, `Send("PING")`, redis.Pong{}) expectPushed(t, c, `Send("PING")`, redis.Pong{})
c.Ping("timeout")
got := c.ReceiveWithTimeout(time.Minute)
if want := (redis.Pong{Data: "timeout"}); want != got {
t.Errorf("recv /w timeout got %v, want %v", got, want)
}
} }

View File

@ -14,6 +14,11 @@
package redis package redis
import (
"errors"
"time"
)
// Error represents an error returned in a command reply. // Error represents an error returned in a command reply.
type Error string type Error string
@ -59,3 +64,54 @@ type Scanner interface {
// loss of information. // loss of information.
RedisScan(src interface{}) error RedisScan(src interface{}) error
} }
// ConnWithTimeout is an optional interface that allows the caller to override
// a connection's default read timeout. This interface is useful for executing
// the BLPOP, BRPOP, BRPOPLPUSH, XREAD and other commands that block at the
// server.
//
// A connection's default read timeout is set with the DialReadTimeout dial
// option. Applications should rely on the default timeout for commands that do
// not block at the server.
//
// All of the Conn implementations in this package satisfy the ConnWithTimeout
// interface.
//
// Use the DoWithTimeout and ReceiveWithTimeout helper functions to simplify
// use of this interface.
type ConnWithTimeout interface {
Conn
// Do sends a command to the server and returns the received reply.
// The timeout overrides the read timeout set when dialing the
// connection.
DoWithTimeout(timeout time.Duration, commandName string, args ...interface{}) (reply interface{}, err error)
// Receive receives a single reply from the Redis server. The timeout
// overrides the read timeout set when dialing the connection.
ReceiveWithTimeout(timeout time.Duration) (reply interface{}, err error)
}
var errTimeoutNotSupported = errors.New("redis: connection does not support ConnWithTimeout")
// DoWithTimeout executes a Redis command with the specified read timeout. If
// the connection does not satisfy the ConnWithTimeout interface, then an error
// is returned.
func DoWithTimeout(c Conn, timeout time.Duration, cmd string, args ...interface{}) (interface{}, error) {
cwt, ok := c.(ConnWithTimeout)
if !ok {
return nil, errTimeoutNotSupported
}
return cwt.DoWithTimeout(timeout, cmd, args...)
}
// ReceiveWithTimeout receives a reply with the specified read timeout. If the
// connection does not satisfy the ConnWithTimeout interface, then an error is
// returned.
func ReceiveWithTimeout(c Conn, timeout time.Duration) (interface{}, error) {
cwt, ok := c.(ConnWithTimeout)
if !ok {
return nil, errTimeoutNotSupported
}
return cwt.ReceiveWithTimeout(timeout)
}

71
vendor/github.com/garyburd/redigo/redis/redis_test.go generated vendored Normal file
View File

@ -0,0 +1,71 @@
// Copyright 2017 Gary Burd
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package redis_test
import (
"testing"
"time"
"github.com/garyburd/redigo/redis"
)
type timeoutTestConn int
func (tc timeoutTestConn) Do(string, ...interface{}) (interface{}, error) {
return time.Duration(-1), nil
}
func (tc timeoutTestConn) DoWithTimeout(timeout time.Duration, cmd string, args ...interface{}) (interface{}, error) {
return timeout, nil
}
func (tc timeoutTestConn) Receive() (interface{}, error) {
return time.Duration(-1), nil
}
func (tc timeoutTestConn) ReceiveWithTimeout(timeout time.Duration) (interface{}, error) {
return timeout, nil
}
func (tc timeoutTestConn) Send(string, ...interface{}) error { return nil }
func (tc timeoutTestConn) Err() error { return nil }
func (tc timeoutTestConn) Close() error { return nil }
func (tc timeoutTestConn) Flush() error { return nil }
func testTimeout(t *testing.T, c redis.Conn) {
r, err := c.Do("PING")
if r != time.Duration(-1) || err != nil {
t.Errorf("Do() = %v, %v, want %v, %v", r, err, time.Duration(-1), nil)
}
r, err = redis.DoWithTimeout(c, time.Minute, "PING")
if r != time.Minute || err != nil {
t.Errorf("DoWithTimeout() = %v, %v, want %v, %v", r, err, time.Minute, nil)
}
r, err = c.Receive()
if r != time.Duration(-1) || err != nil {
t.Errorf("Receive() = %v, %v, want %v, %v", r, err, time.Duration(-1), nil)
}
r, err = redis.ReceiveWithTimeout(c, time.Minute)
if r != time.Minute || err != nil {
t.Errorf("ReceiveWithTimeout() = %v, %v, want %v, %v", r, err, time.Minute, nil)
}
}
func TestConnTimeout(t *testing.T) {
testTimeout(t, timeoutTestConn(0))
}
func TestPoolConnTimeout(t *testing.T) {
p := &redis.Pool{Dial: func() (redis.Conn, error) { return timeoutTestConn(0), nil }}
testTimeout(t, p.Get())
}

View File

@ -243,34 +243,67 @@ func Values(reply interface{}, err error) ([]interface{}, error) {
return nil, fmt.Errorf("redigo: unexpected type for Values, got type %T", reply) return nil, fmt.Errorf("redigo: unexpected type for Values, got type %T", reply)
} }
func sliceHelper(reply interface{}, err error, name string, makeSlice func(int), assign func(int, interface{}) error) error {
if err != nil {
return err
}
switch reply := reply.(type) {
case []interface{}:
makeSlice(len(reply))
for i := range reply {
if reply[i] == nil {
continue
}
if err := assign(i, reply[i]); err != nil {
return err
}
}
return nil
case nil:
return ErrNil
case Error:
return reply
}
return fmt.Errorf("redigo: unexpected type for %s, got type %T", name, reply)
}
// Float64s is a helper that converts an array command reply to a []float64. If
// err is not equal to nil, then Float64s returns nil, err. Nil array items are
// converted to 0 in the output slice. Floats64 returns an error if an array
// item is not a bulk string or nil.
func Float64s(reply interface{}, err error) ([]float64, error) {
var result []float64
err = sliceHelper(reply, err, "Float64s", func(n int) { result = make([]float64, n) }, func(i int, v interface{}) error {
p, ok := v.([]byte)
if !ok {
return fmt.Errorf("redigo: unexpected element type for Floats64, got type %T", v)
}
f, err := strconv.ParseFloat(string(p), 64)
result[i] = f
return err
})
return result, err
}
// Strings is a helper that converts an array command reply to a []string. If // Strings is a helper that converts an array command reply to a []string. If
// err is not equal to nil, then Strings returns nil, err. Nil array items are // err is not equal to nil, then Strings returns nil, err. Nil array items are
// converted to "" in the output slice. Strings returns an error if an array // converted to "" in the output slice. Strings returns an error if an array
// item is not a bulk string or nil. // item is not a bulk string or nil.
func Strings(reply interface{}, err error) ([]string, error) { func Strings(reply interface{}, err error) ([]string, error) {
if err != nil { var result []string
return nil, err err = sliceHelper(reply, err, "Strings", func(n int) { result = make([]string, n) }, func(i int, v interface{}) error {
} switch v := v.(type) {
switch reply := reply.(type) { case string:
case []interface{}: result[i] = v
result := make([]string, len(reply)) return nil
for i := range reply { case []byte:
if reply[i] == nil { result[i] = string(v)
continue return nil
} default:
p, ok := reply[i].([]byte) return fmt.Errorf("redigo: unexpected element type for Strings, got type %T", v)
if !ok {
return nil, fmt.Errorf("redigo: unexpected element type for Strings, got type %T", reply[i])
}
result[i] = string(p)
} }
return result, nil })
case nil: return result, err
return nil, ErrNil
case Error:
return nil, reply
}
return nil, fmt.Errorf("redigo: unexpected type for Strings, got type %T", reply)
} }
// ByteSlices is a helper that converts an array command reply to a [][]byte. // ByteSlices is a helper that converts an array command reply to a [][]byte.
@ -278,43 +311,64 @@ func Strings(reply interface{}, err error) ([]string, error) {
// items are stay nil. ByteSlices returns an error if an array item is not a // items are stay nil. ByteSlices returns an error if an array item is not a
// bulk string or nil. // bulk string or nil.
func ByteSlices(reply interface{}, err error) ([][]byte, error) { func ByteSlices(reply interface{}, err error) ([][]byte, error) {
if err != nil { var result [][]byte
return nil, err err = sliceHelper(reply, err, "ByteSlices", func(n int) { result = make([][]byte, n) }, func(i int, v interface{}) error {
} p, ok := v.([]byte)
switch reply := reply.(type) { if !ok {
case []interface{}: return fmt.Errorf("redigo: unexpected element type for ByteSlices, got type %T", v)
result := make([][]byte, len(reply))
for i := range reply {
if reply[i] == nil {
continue
}
p, ok := reply[i].([]byte)
if !ok {
return nil, fmt.Errorf("redigo: unexpected element type for ByteSlices, got type %T", reply[i])
}
result[i] = p
} }
return result, nil result[i] = p
case nil: return nil
return nil, ErrNil })
case Error: return result, err
return nil, reply
}
return nil, fmt.Errorf("redigo: unexpected type for ByteSlices, got type %T", reply)
} }
// Ints is a helper that converts an array command reply to a []int. If // Int64s is a helper that converts an array command reply to a []int64.
// err is not equal to nil, then Ints returns nil, err. // If err is not equal to nil, then Int64s returns nil, err. Nil array
// items are stay nil. Int64s returns an error if an array item is not a
// bulk string or nil.
func Int64s(reply interface{}, err error) ([]int64, error) {
var result []int64
err = sliceHelper(reply, err, "Int64s", func(n int) { result = make([]int64, n) }, func(i int, v interface{}) error {
switch v := v.(type) {
case int64:
result[i] = v
return nil
case []byte:
n, err := strconv.ParseInt(string(v), 10, 64)
result[i] = n
return err
default:
return fmt.Errorf("redigo: unexpected element type for Int64s, got type %T", v)
}
})
return result, err
}
// Ints is a helper that converts an array command reply to a []in.
// If err is not equal to nil, then Ints returns nil, err. Nil array
// items are stay nil. Ints returns an error if an array item is not a
// bulk string or nil.
func Ints(reply interface{}, err error) ([]int, error) { func Ints(reply interface{}, err error) ([]int, error) {
var ints []int var result []int
values, err := Values(reply, err) err = sliceHelper(reply, err, "Ints", func(n int) { result = make([]int, n) }, func(i int, v interface{}) error {
if err != nil { switch v := v.(type) {
return ints, err case int64:
} n := int(v)
if err := ScanSlice(values, &ints); err != nil { if int64(n) != v {
return ints, err return strconv.ErrRange
} }
return ints, nil result[i] = n
return nil
case []byte:
n, err := strconv.Atoi(string(v))
result[i] = n
return err
default:
return fmt.Errorf("redigo: unexpected element type for Ints, got type %T", v)
}
})
return result, err
} }
// StringMap is a helper that converts an array of strings (alternating key, value) // StringMap is a helper that converts an array of strings (alternating key, value)

View File

@ -37,24 +37,44 @@ var replyTests = []struct {
expected valueError expected valueError
}{ }{
{ {
"ints([v1, v2])", "ints([[]byte, []byte])",
ve(redis.Ints([]interface{}{[]byte("4"), []byte("5")}, nil)), ve(redis.Ints([]interface{}{[]byte("4"), []byte("5")}, nil)),
ve([]int{4, 5}, nil), ve([]int{4, 5}, nil),
}, },
{
"ints([nt64, int64])",
ve(redis.Ints([]interface{}{int64(4), int64(5)}, nil)),
ve([]int{4, 5}, nil),
},
{
"ints([[]byte, nil, []byte])",
ve(redis.Ints([]interface{}{[]byte("4"), nil, []byte("5")}, nil)),
ve([]int{4, 0, 5}, nil),
},
{ {
"ints(nil)", "ints(nil)",
ve(redis.Ints(nil, nil)), ve(redis.Ints(nil, nil)),
ve([]int(nil), redis.ErrNil), ve([]int(nil), redis.ErrNil),
}, },
{ {
"strings([v1, v2])", "int64s([[]byte, []byte])",
ve(redis.Int64s([]interface{}{[]byte("4"), []byte("5")}, nil)),
ve([]int64{4, 5}, nil),
},
{
"int64s([int64, int64])",
ve(redis.Int64s([]interface{}{int64(4), int64(5)}, nil)),
ve([]int64{4, 5}, nil),
},
{
"strings([[]byte, []bytev2])",
ve(redis.Strings([]interface{}{[]byte("v1"), []byte("v2")}, nil)), ve(redis.Strings([]interface{}{[]byte("v1"), []byte("v2")}, nil)),
ve([]string{"v1", "v2"}, nil), ve([]string{"v1", "v2"}, nil),
}, },
{ {
"strings(nil)", "strings([string, string])",
ve(redis.Strings(nil, nil)), ve(redis.Strings([]interface{}{"v1", "v2"}, nil)),
ve([]string(nil), redis.ErrNil), ve([]string{"v1", "v2"}, nil),
}, },
{ {
"byteslices([v1, v2])", "byteslices([v1, v2])",
@ -62,9 +82,9 @@ var replyTests = []struct {
ve([][]byte{[]byte("v1"), []byte("v2")}, nil), ve([][]byte{[]byte("v1"), []byte("v2")}, nil),
}, },
{ {
"byteslices(nil)", "float64s([v1, v2])",
ve(redis.ByteSlices(nil, nil)), ve(redis.Float64s([]interface{}{[]byte("1.234"), []byte("5.678")}, nil)),
ve([][]byte(nil), redis.ErrNil), ve([]float64{1.234, 5.678}, nil),
}, },
{ {
"values([v1, v2])", "values([v1, v2])",
@ -120,6 +140,11 @@ func dial() (redis.Conn, error) {
return redis.DialDefaultServer() return redis.DialDefaultServer()
} }
// serverAddr wraps DefaultServerAddr() with a more suitable function name for examples.
func serverAddr() (string, error) {
return redis.DefaultServerAddr()
}
func ExampleBool() { func ExampleBool() {
c, err := dial() c, err := dial()
if err != nil { if err != nil {

View File

@ -38,6 +38,7 @@ var (
ErrNegativeInt = errNegativeInt ErrNegativeInt = errNegativeInt
serverPath = flag.String("redis-server", "redis-server", "Path to redis server binary") serverPath = flag.String("redis-server", "redis-server", "Path to redis server binary")
serverAddress = flag.String("redis-address", "127.0.0.1", "The address of the server")
serverBasePort = flag.Int("redis-port", 16379, "Beginning of port range for test servers") serverBasePort = flag.Int("redis-port", 16379, "Beginning of port range for test servers")
serverLogName = flag.String("redis-log", "", "Write Redis server logs to `filename`") serverLogName = flag.String("redis-log", "", "Write Redis server logs to `filename`")
serverLog = ioutil.Discard serverLog = ioutil.Discard
@ -126,28 +127,32 @@ func stopDefaultServer() {
} }
} }
// startDefaultServer starts the default server if not already running. // DefaultServerAddr starts the test server if not already started and returns
func startDefaultServer() error { // the address of that server.
func DefaultServerAddr() (string, error) {
defaultServerMu.Lock() defaultServerMu.Lock()
defer defaultServerMu.Unlock() defer defaultServerMu.Unlock()
addr := fmt.Sprintf("%v:%d", *serverAddress, *serverBasePort)
if defaultServer != nil || defaultServerErr != nil { if defaultServer != nil || defaultServerErr != nil {
return defaultServerErr return addr, defaultServerErr
} }
defaultServer, defaultServerErr = NewServer( defaultServer, defaultServerErr = NewServer(
"default", "default",
"--port", strconv.Itoa(*serverBasePort), "--port", strconv.Itoa(*serverBasePort),
"--bind", *serverAddress,
"--save", "", "--save", "",
"--appendonly", "no") "--appendonly", "no")
return defaultServerErr return addr, defaultServerErr
} }
// DialDefaultServer starts the test server if not already started and dials a // DialDefaultServer starts the test server if not already started and dials a
// connection to the server. // connection to the server.
func DialDefaultServer() (Conn, error) { func DialDefaultServer() (Conn, error) {
if err := startDefaultServer(); err != nil { addr, err := DefaultServerAddr()
if err != nil {
return nil, err return nil, err
} }
c, err := Dial("tcp", fmt.Sprintf(":%d", *serverBasePort), DialReadTimeout(1*time.Second), DialWriteTimeout(1*time.Second)) c, err := Dial("tcp", addr, DialReadTimeout(1*time.Second), DialWriteTimeout(1*time.Second))
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -193,8 +193,7 @@ func (m *Marshaler) marshalObject(out *errWriter, v proto.Message, indent, typeU
// "Generated output always contains 3, 6, or 9 fractional digits, // "Generated output always contains 3, 6, or 9 fractional digits,
// depending on required precision." // depending on required precision."
s, ns := s.Field(0).Int(), s.Field(1).Int() s, ns := s.Field(0).Int(), s.Field(1).Int()
d := time.Duration(s)*time.Second + time.Duration(ns)*time.Nanosecond x := fmt.Sprintf("%d.%09d", s, ns)
x := fmt.Sprintf("%.9f", d.Seconds())
x = strings.TrimSuffix(x, "000") x = strings.TrimSuffix(x, "000")
x = strings.TrimSuffix(x, "000") x = strings.TrimSuffix(x, "000")
out.write(`"`) out.write(`"`)

View File

@ -407,6 +407,7 @@ var marshalingTests = []struct {
{"Any with WKT", marshaler, anyWellKnown, anyWellKnownJSON}, {"Any with WKT", marshaler, anyWellKnown, anyWellKnownJSON},
{"Any with WKT and indent", marshalerAllOptions, anyWellKnown, anyWellKnownPrettyJSON}, {"Any with WKT and indent", marshalerAllOptions, anyWellKnown, anyWellKnownPrettyJSON},
{"Duration", marshaler, &pb.KnownTypes{Dur: &durpb.Duration{Seconds: 3}}, `{"dur":"3.000s"}`}, {"Duration", marshaler, &pb.KnownTypes{Dur: &durpb.Duration{Seconds: 3}}, `{"dur":"3.000s"}`},
{"Duration", marshaler, &pb.KnownTypes{Dur: &durpb.Duration{Seconds: 100000000, Nanos: 1}}, `{"dur":"100000000.000000001s"}`},
{"Struct", marshaler, &pb.KnownTypes{St: &stpb.Struct{ {"Struct", marshaler, &pb.KnownTypes{St: &stpb.Struct{
Fields: map[string]*stpb.Value{ Fields: map[string]*stpb.Value{
"one": {Kind: &stpb.Value_StringValue{"loneliest number"}}, "one": {Kind: &stpb.Value_StringValue{"loneliest number"}},

View File

@ -1,4 +1,4 @@
1. Andrew Krasichkov @buglloc https://github.com/buglloc
1. John Graham-Cumming http://jgc.org/ 1. John Graham-Cumming http://jgc.org/
1. Mike Samuel mikesamuel@gmail.com 1. Mike Samuel mikesamuel@gmail.com
1. Dmitri Shuralyov shurcooL@gmail.com 1. Dmitri Shuralyov shurcooL@gmail.com

View File

@ -312,7 +312,7 @@ It is not the job of bluemonday to fix your bad HTML, it is merely the job of bl
## TODO ## TODO
* Add support for CSS sanitisation to allow some CSS properties based on a whitelist, possibly using the [Gorilla CSS3 scanner](http://www.gorillatoolkit.org/pkg/css/scanner) * Add support for CSS sanitisation to allow some CSS properties based on a whitelist, possibly using the [Gorilla CSS3 scanner](http://www.gorillatoolkit.org/pkg/css/scanner) - PRs welcome so long as testing covers XSS and demonstrates safety first
* Investigate whether devs want to blacklist elements and attributes. This would allow devs to take an existing policy (such as the `bluemonday.UGCPolicy()` ) that encapsulates 90% of what they're looking for but does more than they need, and to remove the extra things they do not want to make it 100% what they want * Investigate whether devs want to blacklist elements and attributes. This would allow devs to take an existing policy (such as the `bluemonday.UGCPolicy()` ) that encapsulates 90% of what they're looking for but does more than they need, and to remove the extra things they do not want to make it 100% what they want
* Investigate whether devs want a validating HTML mode, in which the HTML elements are not just transformed into a balanced tree (every start tag has a closing tag at the correct depth) but also that elements and character data appear only in their allowed context (i.e. that a `table` element isn't a descendent of a `caption`, that `colgroup`, `thead`, `tbody`, `tfoot` and `tr` are permitted, and that character data is not permitted) * Investigate whether devs want a validating HTML mode, in which the HTML elements are not just transformed into a balanced tree (every start tag has a closing tag at the correct depth) but also that elements and character data appear only in their allowed context (i.e. that a `table` element isn't a descendent of a `caption`, that `colgroup`, `thead`, `tbody`, `tfoot` and `tr` are permitted, and that character data is not permitted)

View File

@ -27,7 +27,6 @@ func main() {
// HTML email is often displayed in iframes and needs to preserve core // HTML email is often displayed in iframes and needs to preserve core
// structure // structure
p.AllowDocType(true)
p.AllowElements("html", "head", "body", "title") p.AllowElements("html", "head", "body", "title")
// There are not safe, and is only being done here to demonstrate how to // There are not safe, and is only being done here to demonstrate how to

View File

@ -47,9 +47,6 @@ type Policy struct {
// exceptions // exceptions
initialized bool initialized bool
// Allows the <!DOCTYPE > tag to exist in the sanitized document
allowDocType bool
// If true then we add spaces when stripping tags, specifically the closing // If true then we add spaces when stripping tags, specifically the closing
// tag is replaced by a space character. // tag is replaced by a space character.
addSpaces bool addSpaces bool
@ -369,21 +366,6 @@ func (p *Policy) AllowURLSchemeWithCustomPolicy(
return p return p
} }
// AllowDocType states whether the HTML sanitised by the sanitizer is allowed to
// contain the HTML DocType tag: <!DOCTYPE HTML> or one of it's variants.
//
// The HTML spec only permits one doctype per document, and as you know how you
// are using the output of this, you know best as to whether we should ignore it
// (default) or not.
//
// If you are sanitizing a HTML fragment the default (false) is fine.
func (p *Policy) AllowDocType(allow bool) *Policy {
p.allowDocType = allow
return p
}
// AddSpaceWhenStrippingTag states whether to add a single space " " when // AddSpaceWhenStrippingTag states whether to add a single space " " when
// removing tags that are not whitelisted by the policy. // removing tags that are not whitelisted by the policy.
// //
@ -498,6 +480,7 @@ func (p *Policy) addDefaultElementsWithoutAttrs() {
p.setOfElementsAllowedWithoutAttrs["ruby"] = struct{}{} p.setOfElementsAllowedWithoutAttrs["ruby"] = struct{}{}
p.setOfElementsAllowedWithoutAttrs["s"] = struct{}{} p.setOfElementsAllowedWithoutAttrs["s"] = struct{}{}
p.setOfElementsAllowedWithoutAttrs["samp"] = struct{}{} p.setOfElementsAllowedWithoutAttrs["samp"] = struct{}{}
p.setOfElementsAllowedWithoutAttrs["script"] = struct{}{}
p.setOfElementsAllowedWithoutAttrs["section"] = struct{}{} p.setOfElementsAllowedWithoutAttrs["section"] = struct{}{}
p.setOfElementsAllowedWithoutAttrs["select"] = struct{}{} p.setOfElementsAllowedWithoutAttrs["select"] = struct{}{}
p.setOfElementsAllowedWithoutAttrs["small"] = struct{}{} p.setOfElementsAllowedWithoutAttrs["small"] = struct{}{}

View File

@ -112,9 +112,13 @@ func (p *Policy) sanitize(r io.Reader) *bytes.Buffer {
switch token.Type { switch token.Type {
case html.DoctypeToken: case html.DoctypeToken:
if p.allowDocType { // DocType is not handled as there is no safe parsing mechanism
buff.WriteString(token.String()) // provided by golang.org/x/net/html for the content, and this can
} // be misused to insert HTML tags that are not then sanitized
//
// One might wish to recursively sanitize here using the same policy
// but I will need to do some further testing before considering
// this.
case html.CommentToken: case html.CommentToken:
@ -217,7 +221,7 @@ func (p *Policy) sanitize(r io.Reader) *bytes.Buffer {
case html.TextToken: case html.TextToken:
if !skipElementContent { if !skipElementContent {
switch strings.ToLower(mostRecentlyStartedToken) { switch mostRecentlyStartedToken {
case "script": case "script":
// not encouraged, but if a policy allows JavaScript we // not encouraged, but if a policy allows JavaScript we
// should not HTML escape it as that would break the output // should not HTML escape it as that would break the output
@ -231,7 +235,6 @@ func (p *Policy) sanitize(r io.Reader) *bytes.Buffer {
buff.WriteString(token.String()) buff.WriteString(token.String())
} }
} }
default: default:
// A token that didn't exist in the html package when we wrote this // A token that didn't exist in the html package when we wrote this
return &bytes.Buffer{} return &bytes.Buffer{}
@ -490,13 +493,18 @@ func (p *Policy) allowNoAttrs(elementName string) bool {
func (p *Policy) validURL(rawurl string) (string, bool) { func (p *Policy) validURL(rawurl string) (string, bool) {
if p.requireParseableURLs { if p.requireParseableURLs {
// URLs do not contain whitespace // URLs are valid if when space is trimmed the URL is valid
if strings.Contains(rawurl, " ") || rawurl = strings.TrimSpace(rawurl)
// URLs cannot contain whitespace, unless it is a data-uri
if (strings.Contains(rawurl, " ") ||
strings.Contains(rawurl, "\t") || strings.Contains(rawurl, "\t") ||
strings.Contains(rawurl, "\n") { strings.Contains(rawurl, "\n")) &&
!strings.HasPrefix(rawurl, `data:`) {
return "", false return "", false
} }
// URLs are valid if they parse
u, err := url.Parse(rawurl) u, err := url.Parse(rawurl)
if err != nil { if err != nil {
return "", false return "", false

View File

@ -92,39 +92,6 @@ func TestSignatureBehaviour(t *testing.T) {
} }
} }
func TestAllowDocType(t *testing.T) {
p := NewPolicy()
p.AllowElements("b")
in := "<!DOCTYPE html>Hello, <b>World</b>!"
expected := "Hello, <b>World</b>!"
out := p.Sanitize(in)
if out != expected {
t.Errorf(
"test 1 failed;\ninput : %s\noutput : %s\nexpected: %s",
in,
out,
expected,
)
}
// Allow the doctype and run the test again
p.AllowDocType(true)
expected = "<!DOCTYPE html>Hello, <b>World</b>!"
out = p.Sanitize(in)
if out != expected {
t.Errorf(
"test 1 failed;\ninput : %s\noutput : %s\nexpected: %s",
in,
out,
expected,
)
}
}
func TestLinks(t *testing.T) { func TestLinks(t *testing.T) {
tests := []test{ tests := []test{
@ -1506,3 +1473,214 @@ func TestTargetBlankNoOpener(t *testing.T) {
} }
wg.Wait() wg.Wait()
} }
func TestIssue51(t *testing.T) {
// Whitespace in URLs is permitted within HTML according to:
// https://dev.w3.org/html5/spec-LC/urls.html#parsing-urls
//
// We were aggressively rejecting URLs that contained line feeds but these
// are permitted.
//
// This test ensures that we do not regress that fix.
p := NewPolicy()
p.AllowImages()
p.AllowDataURIImages()
input := `<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEgAAABICAIAAADajyQQAAAAhnpUWHRSYXcgcHJvZmlsZSB0eXBlIGV4aWYAAHjadY5LCsNADEP3c4oewb+R7eOUkEBv0OPXZpKmm76FLIQRGvv7dYxHwyTDpgcSoMLSUp5lghZKxELct3RxXuVycsdDZRlkONn9aGd+MRWBw80dExs2qXbZlTVKu6hbqWfkT8l30Z/8WvEBQsUsKBcOhtYAAAoCaVRYdFhNTDpjb20uYWRvYmUueG1wAAAAAAA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/Pgo8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJYTVAgQ29yZSA0LjQuMC1FeGl2MiI+CiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPgogIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICB4bWxuczpleGlmPSJodHRwOi8vbnMuYWRvYmUuY29tL2V4aWYvMS4wLyIKICAgIHhtbG5zOnRpZmY9Imh0dHA6Ly9ucy5hZG9iZS5jb20vdGlmZi8xLjAvIgogICBleGlmOlBpeGVsWERpbWVuc2lvbj0iNzIiCiAgIGV4aWY6UGl4ZWxZRGltZW5zaW9uPSI3MiIKICAgdGlmZjpJbWFnZVdpZHRoPSI3MiIKICAgdGlmZjpJbWFnZUhlaWdodD0iNzIiCiAgIHRpZmY6T3JpZW50YXRpb249IjEiLz4KIDwvcmRmOlJERj4KPC94OnhtcG1ldGE+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAKPD94cGFja2V0IGVuZD0idyI/Pq6cYi8AAAADc0JJVAgICNvhT+AAAAN7SURBVGje7dtRSBNhHADwfxJ3L96Le0kf1GD1sBDyO5ALbEkyMyY9bHswg+FDW5B7EKVhJSeElrQUcRIkFFHoi0toPriEVi8KbUQxKSYNk8HpYE5ot4e7e/l68NT08aTp6v9/25+P7+O3/3d3H3ffB7RooSSH7IQQYu0KS4qeeeEWyHbY+qLZvbbZiEcghBBHIJ43NhrQ4oYiRUU7sQ0lFJqPizbBEViUFCWfnOmyCp4ZaV/bfHLKIwiecLYUYJTSbLid2ALJX/E+q7VnUdGz0pSDOKakA39DQrQSd8RI0cqgCLEe8rZ55zb1X5oKwLAMywJoANpOI4ZhAEBdHnA6B5ZVPalqwHCckTGLAqvi69jPwZF36yrIK6GR4NrZjrbTbK2ziVsaeba0CaD+nAtOrtU6m6rY2qbazYWH08syqOtLwUcfoamjzpCsSPNPigy5bYQQIti7xuP6VaOshsV26052Uc/mE1M9DoEQQmxuMbyqGBvwBKUU/sUog380EIYwhCEMYQhD2DGMk4VCASuGMIQhDGEIQ9hxe0Af5eDyj7ejw5PRVAGgwnLNJ/qaK+HTnRZ/bF8rc9/s86umEoKpXyb8E+nWx7NP65nM+9HuB/5T5tc3zouzs/q7Ri0d6vdHLb5GU2lNxa0txuLq6aw3scDVNHZcrsjE0jKwnEmPQnQiVLg26KvnSmwqVjb3DjXvVC8djRVOtVbvGTbmh19utY55z7Cle/NQN94/8IcYl+iq2U19m55Mmb2d51ijnR45TP7yrPvmaME1NnZrrzjy1+mo1tBp6OI6DndF2Ji/f3s03Si+6r34p0FNRb5q50ULd4iuj7Bi8reR7uFUgzjYYYFcLpfL5WT9I0sm9l2rbjQfxnWEFcvFJsIZgEi/O3LgiaVmUluMubr8UN2fkGUZl1QIQxjCEIYwhCEMYYdbUuE+D4QhDGEIQxjC/luYvBK667zE8zx/oc0XXNK3B8vL0716tsX75IOe3fzwxNtyged5vuX6QGhFNThkUfakJ0Sb4H6RyFOqrIZ7rIInmqdUSQbsxDEez+5mI3lKpRm3YOuLSAql2fi4g9gDSUObZ4vy+o2tu/dmATiOBZA1UIEzcQDAMiaO+aPV9nbtKtfkwhWW4wBUWVOh3FTFsce2YnhSAk9K4EmJvxt4UgJPSuCSCmEIQxjCEAYAAL8BrebxGP8KiJcAAAAASUVORK5CYII=" alt="">`
out := p.Sanitize(input)
expected := `<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEgAAABICAIAAADajyQQAAAAhnpUWHRSYXcgcHJvZmlsZSB0eXBlIGV4aWYAAHjadY5LCsNADEP3c4oewb+R7eOUkEBv0OPXZpKmm76FLIQRGvv7dYxHwyTDpgcSoMLSUp5lghZKxELct3RxXuVycsdDZRlkONn9aGd+MRWBw80dExs2qXbZlTVKu6hbqWfkT8l30Z/8WvEBQsUsKBcOhtYAAAoCaVRYdFhNTDpjb20uYWRvYmUueG1wAAAAAAA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/Pgo8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJYTVAgQ29yZSA0LjQuMC1FeGl2MiI+CiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPgogIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICB4bWxuczpleGlmPSJodHRwOi8vbnMuYWRvYmUuY29tL2V4aWYvMS4wLyIKICAgIHhtbG5zOnRpZmY9Imh0dHA6Ly9ucy5hZG9iZS5jb20vdGlmZi8xLjAvIgogICBleGlmOlBpeGVsWERpbWVuc2lvbj0iNzIiCiAgIGV4aWY6UGl4ZWxZRGltZW5zaW9uPSI3MiIKICAgdGlmZjpJbWFnZVdpZHRoPSI3MiIKICAgdGlmZjpJbWFnZUhlaWdodD0iNzIiCiAgIHRpZmY6T3JpZW50YXRpb249IjEiLz4KIDwvcmRmOlJERj4KPC94OnhtcG1ldGE+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAKPD94cGFja2V0IGVuZD0idyI/Pq6cYi8AAAADc0JJVAgICNvhT+AAAAN7SURBVGje7dtRSBNhHADwfxJ3L96Le0kf1GD1sBDyO5ALbEkyMyY9bHswg+FDW5B7EKVhJSeElrQUcRIkFFHoi0toPriEVi8KbUQxKSYNk8HpYE5ot4e7e/l68NT08aTp6v9/25+P7+O3/3d3H3ffB7RooSSH7IQQYu0KS4qeeeEWyHbY+qLZvbbZiEcghBBHIJ43NhrQ4oYiRUU7sQ0lFJqPizbBEViUFCWfnOmyCp4ZaV/bfHLKIwiecLYUYJTSbLid2ALJX/E+q7VnUdGz0pSDOKakA39DQrQSd8RI0cqgCLEe8rZ55zb1X5oKwLAMywJoANpOI4ZhAEBdHnA6B5ZVPalqwHCckTGLAqvi69jPwZF36yrIK6GR4NrZjrbTbK2ziVsaeba0CaD+nAtOrtU6m6rY2qbazYWH08syqOtLwUcfoamjzpCsSPNPigy5bYQQIti7xuP6VaOshsV26052Uc/mE1M9DoEQQmxuMbyqGBvwBKUU/sUog380EIYwhCEMYQhD2DGMk4VCASuGMIQhDGEIQ9hxe0Af5eDyj7ejw5PRVAGgwnLNJ/qaK+HTnRZ/bF8rc9/s86umEoKpXyb8E+nWx7NP65nM+9HuB/5T5tc3zouzs/q7Ri0d6vdHLb5GU2lNxa0txuLq6aw3scDVNHZcrsjE0jKwnEmPQnQiVLg26KvnSmwqVjb3DjXvVC8djRVOtVbvGTbmh19utY55z7Cle/NQN94/8IcYl+iq2U19m55Mmb2d51ijnR45TP7yrPvmaME1NnZrrzjy1+mo1tBp6OI6DndF2Ji/f3s03Si+6r34p0FNRb5q50ULd4iuj7Bi8reR7uFUgzjYYYFcLpfL5WT9I0sm9l2rbjQfxnWEFcvFJsIZgEi/O3LgiaVmUluMubr8UN2fkGUZl1QIQxjCEIYwhCEMYYdbUuE+D4QhDGEIQxjC/luYvBK667zE8zx/oc0XXNK3B8vL0716tsX75IOe3fzwxNtyged5vuX6QGhFNThkUfakJ0Sb4H6RyFOqrIZ7rIInmqdUSQbsxDEez+5mI3lKpRm3YOuLSAql2fi4g9gDSUObZ4vy+o2tu/dmATiOBZA1UIEzcQDAMiaO+aPV9nbtKtfkwhWW4wBUWVOh3FTFsce2YnhSAk9K4EmJvxt4UgJPSuCSCmEIQxjCEAYAAL8BrebxGP8KiJcAAAAASUVORK5CYII=" alt="">`
if out != expected {
t.Errorf(
"test failed;\ninput : %s\noutput : %s\nexpected: %s",
input,
out,
expected)
}
input = `<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEgAAABICAIAAADajyQQAAAAhnpUWHRSYXcgcHJvZmlsZSB0
eXBlIGV4aWYAAHjadY5LCsNADEP3c4oewb+R7eOUkEBv0OPXZpKmm76FLIQRGvv7dYxHwyTD
pgcSoMLSUp5lghZKxELct3RxXuVycsdDZRlkONn9aGd+MRWBw80dExs2qXbZlTVKu6hbqWfk
T8l30Z/8WvEBQsUsKBcOhtYAAAoCaVRYdFhNTDpjb20uYWRvYmUueG1wAAAAAAA8P3hwYWNr
ZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/Pgo8eDp4bXBt
ZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJYTVAgQ29yZSA0LjQuMC1F
eGl2MiI+CiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIv
MjItcmRmLXN5bnRheC1ucyMiPgogIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAg
ICB4bWxuczpleGlmPSJodHRwOi8vbnMuYWRvYmUuY29tL2V4aWYvMS4wLyIKICAgIHhtbG5z
OnRpZmY9Imh0dHA6Ly9ucy5hZG9iZS5jb20vdGlmZi8xLjAvIgogICBleGlmOlBpeGVsWERp
bWVuc2lvbj0iNzIiCiAgIGV4aWY6UGl4ZWxZRGltZW5zaW9uPSI3MiIKICAgdGlmZjpJbWFn
ZVdpZHRoPSI3MiIKICAgdGlmZjpJbWFnZUhlaWdodD0iNzIiCiAgIHRpZmY6T3JpZW50YXRp
b249IjEiLz4KIDwvcmRmOlJERj4KPC94OnhtcG1ldGE+CiAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAog
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
IAogICAgICAgICAgICAgICAgICAgICAgICAgICAKPD94cGFja2V0IGVuZD0idyI/Pq6cYi8A
AAADc0JJVAgICNvhT+AAAAN7SURBVGje7dtRSBNhHADwfxJ3L96Le0kf1GD1sBDyO5ALbEky
MyY9bHswg+FDW5B7EKVhJSeElrQUcRIkFFHoi0toPriEVi8KbUQxKSYNk8HpYE5ot4e7e/l6
8NT08aTp6v9/25+P7+O3/3d3H3ffB7RooSSH7IQQYu0KS4qeeeEWyHbY+qLZvbbZiEcghBBH
IJ43NhrQ4oYiRUU7sQ0lFJqPizbBEViUFCWfnOmyCp4ZaV/bfHLKIwiecLYUYJTSbLid2ALJ
X/E+q7VnUdGz0pSDOKakA39DQrQSd8RI0cqgCLEe8rZ55zb1X5oKwLAMywJoANpOI4ZhAEBd
HnA6B5ZVPalqwHCckTGLAqvi69jPwZF36yrIK6GR4NrZjrbTbK2ziVsaeba0CaD+nAtOrtU6
m6rY2qbazYWH08syqOtLwUcfoamjzpCsSPNPigy5bYQQIti7xuP6VaOshsV26052Uc/mE1M9
DoEQQmxuMbyqGBvwBKUU/sUog380EIYwhCEMYQhD2DGMk4VCASuGMIQhDGEIQ9hxe0Af5eDy
j7ejw5PRVAGgwnLNJ/qaK+HTnRZ/bF8rc9/s86umEoKpXyb8E+nWx7NP65nM+9HuB/5T5tc3
zouzs/q7Ri0d6vdHLb5GU2lNxa0txuLq6aw3scDVNHZcrsjE0jKwnEmPQnQiVLg26KvnSmwq
Vjb3DjXvVC8djRVOtVbvGTbmh19utY55z7Cle/NQN94/8IcYl+iq2U19m55Mmb2d51ijnR45
TP7yrPvmaME1NnZrrzjy1+mo1tBp6OI6DndF2Ji/f3s03Si+6r34p0FNRb5q50ULd4iuj7Bi
8reR7uFUgzjYYYFcLpfL5WT9I0sm9l2rbjQfxnWEFcvFJsIZgEi/O3LgiaVmUluMubr8UN2f
kGUZl1QIQxjCEIYwhCEMYYdbUuE+D4QhDGEIQxjC/luYvBK667zE8zx/oc0XXNK3B8vL0716
tsX75IOe3fzwxNtyged5vuX6QGhFNThkUfakJ0Sb4H6RyFOqrIZ7rIInmqdUSQbsxDEez+5m
I3lKpRm3YOuLSAql2fi4g9gDSUObZ4vy+o2tu/dmATiOBZA1UIEzcQDAMiaO+aPV9nbtKtfk
whWW4wBUWVOh3FTFsce2YnhSAk9K4EmJvxt4UgJPSuCSCmEIQxjCEAYAAL8BrebxGP8KiJcA
AAAASUVORK5CYII=" alt="">`
out = p.Sanitize(input)
expected = `<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEgAAABICAIAAADajyQQAAAAhnpUWHRSYXcgcHJvZmlsZSB0
eXBlIGV4aWYAAHjadY5LCsNADEP3c4oewb+R7eOUkEBv0OPXZpKmm76FLIQRGvv7dYxHwyTD
pgcSoMLSUp5lghZKxELct3RxXuVycsdDZRlkONn9aGd+MRWBw80dExs2qXbZlTVKu6hbqWfk
T8l30Z/8WvEBQsUsKBcOhtYAAAoCaVRYdFhNTDpjb20uYWRvYmUueG1wAAAAAAA8P3hwYWNr
ZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/Pgo8eDp4bXBt
ZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJYTVAgQ29yZSA0LjQuMC1F
eGl2MiI+CiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIv
MjItcmRmLXN5bnRheC1ucyMiPgogIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAg
ICB4bWxuczpleGlmPSJodHRwOi8vbnMuYWRvYmUuY29tL2V4aWYvMS4wLyIKICAgIHhtbG5z
OnRpZmY9Imh0dHA6Ly9ucy5hZG9iZS5jb20vdGlmZi8xLjAvIgogICBleGlmOlBpeGVsWERp
bWVuc2lvbj0iNzIiCiAgIGV4aWY6UGl4ZWxZRGltZW5zaW9uPSI3MiIKICAgdGlmZjpJbWFn
ZVdpZHRoPSI3MiIKICAgdGlmZjpJbWFnZUhlaWdodD0iNzIiCiAgIHRpZmY6T3JpZW50YXRp
b249IjEiLz4KIDwvcmRmOlJERj4KPC94OnhtcG1ldGE+CiAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAog
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
IAogICAgICAgICAgICAgICAgICAgICAgICAgICAKPD94cGFja2V0IGVuZD0idyI/Pq6cYi8A
AAADc0JJVAgICNvhT+AAAAN7SURBVGje7dtRSBNhHADwfxJ3L96Le0kf1GD1sBDyO5ALbEky
MyY9bHswg+FDW5B7EKVhJSeElrQUcRIkFFHoi0toPriEVi8KbUQxKSYNk8HpYE5ot4e7e/l6
8NT08aTp6v9/25+P7+O3/3d3H3ffB7RooSSH7IQQYu0KS4qeeeEWyHbY+qLZvbbZiEcghBBH
IJ43NhrQ4oYiRUU7sQ0lFJqPizbBEViUFCWfnOmyCp4ZaV/bfHLKIwiecLYUYJTSbLid2ALJ
X/E+q7VnUdGz0pSDOKakA39DQrQSd8RI0cqgCLEe8rZ55zb1X5oKwLAMywJoANpOI4ZhAEBd
HnA6B5ZVPalqwHCckTGLAqvi69jPwZF36yrIK6GR4NrZjrbTbK2ziVsaeba0CaD+nAtOrtU6
m6rY2qbazYWH08syqOtLwUcfoamjzpCsSPNPigy5bYQQIti7xuP6VaOshsV26052Uc/mE1M9
DoEQQmxuMbyqGBvwBKUU/sUog380EIYwhCEMYQhD2DGMk4VCASuGMIQhDGEIQ9hxe0Af5eDy
j7ejw5PRVAGgwnLNJ/qaK+HTnRZ/bF8rc9/s86umEoKpXyb8E+nWx7NP65nM+9HuB/5T5tc3
zouzs/q7Ri0d6vdHLb5GU2lNxa0txuLq6aw3scDVNHZcrsjE0jKwnEmPQnQiVLg26KvnSmwq
Vjb3DjXvVC8djRVOtVbvGTbmh19utY55z7Cle/NQN94/8IcYl+iq2U19m55Mmb2d51ijnR45
TP7yrPvmaME1NnZrrzjy1+mo1tBp6OI6DndF2Ji/f3s03Si+6r34p0FNRb5q50ULd4iuj7Bi
8reR7uFUgzjYYYFcLpfL5WT9I0sm9l2rbjQfxnWEFcvFJsIZgEi/O3LgiaVmUluMubr8UN2f
kGUZl1QIQxjCEIYwhCEMYYdbUuE+D4QhDGEIQxjC/luYvBK667zE8zx/oc0XXNK3B8vL0716
tsX75IOe3fzwxNtyged5vuX6QGhFNThkUfakJ0Sb4H6RyFOqrIZ7rIInmqdUSQbsxDEez+5m
I3lKpRm3YOuLSAql2fi4g9gDSUObZ4vy+o2tu/dmATiOBZA1UIEzcQDAMiaO+aPV9nbtKtfk
whWW4wBUWVOh3FTFsce2YnhSAk9K4EmJvxt4UgJPSuCSCmEIQxjCEAYAAL8BrebxGP8KiJcA
AAAASUVORK5CYII=" alt="">`
if out != expected {
t.Errorf(
"test failed;\ninput : %s\noutput : %s\nexpected: %s",
input,
out,
expected)
}
}
func TestIssue55ScriptTags(t *testing.T) {
p1 := NewPolicy()
p2 := UGCPolicy()
p3 := UGCPolicy().AllowElements("script")
in := `<SCRIPT>document.write('<h1><header/h1>')</SCRIPT>`
expected := ``
out := p1.Sanitize(in)
if out != expected {
t.Errorf(
"test failed;\ninput : %s\noutput : %s\nexpected: %s",
in,
out,
expected,
)
}
expected = ``
out = p2.Sanitize(in)
if out != expected {
t.Errorf(
"test failed;\ninput : %s\noutput : %s\nexpected: %s",
in,
out,
expected,
)
}
expected = `<script>document.write('<h1><header/h1>')</script>`
out = p3.Sanitize(in)
if out != expected {
t.Errorf(
"test failed;\ninput : %s\noutput : %s\nexpected: %s",
in,
out,
expected,
)
}
}

View File

@ -5,8 +5,8 @@ os:
- osx - osx
go: go:
- 1.7.x
- 1.8.x - 1.8.x
- 1.9.x
sudo: false sudo: false

View File

@ -199,114 +199,92 @@ var gfmHTMLConfig = syntaxhighlight.HTMLConfig{
Decimal: "m", Decimal: "m",
} }
// TODO: Support highlighting for more languages.
func highlightCode(src []byte, lang string) (highlightedCode []byte, ok bool) { func highlightCode(src []byte, lang string) (highlightedCode []byte, ok bool) {
switch lang { switch lang {
case "Go": case "Go", "Go-unformatted":
var buf bytes.Buffer var buf bytes.Buffer
err := highlight_go.Print(src, &buf, syntaxhighlight.HTMLPrinter(gfmHTMLConfig)) err := highlight_go.Print(src, &buf, syntaxhighlight.HTMLPrinter(gfmHTMLConfig))
if err != nil { if err != nil {
return nil, false return nil, false
} }
return buf.Bytes(), true return buf.Bytes(), true
case "Go-old": case "diff":
var buf bytes.Buffer anns, err := highlight_diff.Annotate(src)
err := syntaxhighlight.Print(syntaxhighlight.NewScanner(src), &buf, syntaxhighlight.HTMLPrinter(gfmHTMLConfig))
if err != nil { if err != nil {
return nil, false return nil, false
} }
return buf.Bytes(), true
case "diff":
switch 2 {
default:
var buf bytes.Buffer
err := highlight_diff.Print(highlight_diff.NewScanner(src), &buf)
if err != nil {
return nil, false
}
return buf.Bytes(), true
case 1:
lines := bytes.Split(src, []byte("\n"))
return bytes.Join(lines, []byte("\n")), true
case 2:
anns, err := highlight_diff.Annotate(src)
if err != nil {
return nil, false
}
lines := bytes.Split(src, []byte("\n")) lines := bytes.Split(src, []byte("\n"))
lineStarts := make([]int, len(lines)) lineStarts := make([]int, len(lines))
var offset int var offset int
for lineIndex := 0; lineIndex < len(lines); lineIndex++ { for lineIndex := 0; lineIndex < len(lines); lineIndex++ {
lineStarts[lineIndex] = offset lineStarts[lineIndex] = offset
offset += len(lines[lineIndex]) + 1 offset += len(lines[lineIndex]) + 1
} }
lastDel, lastIns := -1, -1 lastDel, lastIns := -1, -1
for lineIndex := 0; lineIndex < len(lines); lineIndex++ { for lineIndex := 0; lineIndex < len(lines); lineIndex++ {
var lineFirstChar byte var lineFirstChar byte
if len(lines[lineIndex]) > 0 { if len(lines[lineIndex]) > 0 {
lineFirstChar = lines[lineIndex][0] lineFirstChar = lines[lineIndex][0]
}
switch lineFirstChar {
case '+':
if lastIns == -1 {
lastIns = lineIndex
} }
switch lineFirstChar { case '-':
case '+': if lastDel == -1 {
if lastIns == -1 { lastDel = lineIndex
}
default:
if lastDel != -1 || lastIns != -1 {
if lastDel == -1 {
lastDel = lastIns
} else if lastIns == -1 {
lastIns = lineIndex lastIns = lineIndex
} }
case '-':
if lastDel == -1 { beginOffsetLeft := lineStarts[lastDel]
lastDel = lineIndex endOffsetLeft := lineStarts[lastIns]
} beginOffsetRight := lineStarts[lastIns]
default: endOffsetRight := lineStarts[lineIndex]
if lastDel != -1 || lastIns != -1 {
if lastDel == -1 { anns = append(anns, &annotate.Annotation{Start: beginOffsetLeft, End: endOffsetLeft, Left: []byte(`<span class="gd input-block">`), Right: []byte(`</span>`), WantInner: 0})
lastDel = lastIns anns = append(anns, &annotate.Annotation{Start: beginOffsetRight, End: endOffsetRight, Left: []byte(`<span class="gi input-block">`), Right: []byte(`</span>`), WantInner: 0})
} else if lastIns == -1 {
lastIns = lineIndex if '@' != lineFirstChar {
//leftContent := string(src[beginOffsetLeft:endOffsetLeft])
//rightContent := string(src[beginOffsetRight:endOffsetRight])
// This is needed to filter out the "-" and "+" at the beginning of each line from being highlighted.
// TODO: Still not completely filtered out.
leftContent := ""
for line := lastDel; line < lastIns; line++ {
leftContent += "\x00" + string(lines[line][1:]) + "\n"
}
rightContent := ""
for line := lastIns; line < lineIndex; line++ {
rightContent += "\x00" + string(lines[line][1:]) + "\n"
} }
beginOffsetLeft := lineStarts[lastDel] var sectionSegments [2][]*annotate.Annotation
endOffsetLeft := lineStarts[lastIns] highlight_diff.HighlightedDiffFunc(leftContent, rightContent, &sectionSegments, [2]int{beginOffsetLeft, beginOffsetRight})
beginOffsetRight := lineStarts[lastIns]
endOffsetRight := lineStarts[lineIndex]
anns = append(anns, &annotate.Annotation{Start: beginOffsetLeft, End: endOffsetLeft, Left: []byte(`<span class="gd input-block">`), Right: []byte(`</span>`), WantInner: 0}) anns = append(anns, sectionSegments[0]...)
anns = append(anns, &annotate.Annotation{Start: beginOffsetRight, End: endOffsetRight, Left: []byte(`<span class="gi input-block">`), Right: []byte(`</span>`), WantInner: 0}) anns = append(anns, sectionSegments[1]...)
if '@' != lineFirstChar {
//leftContent := string(src[beginOffsetLeft:endOffsetLeft])
//rightContent := string(src[beginOffsetRight:endOffsetRight])
// This is needed to filter out the "-" and "+" at the beginning of each line from being highlighted.
// TODO: Still not completely filtered out.
leftContent := ""
for line := lastDel; line < lastIns; line++ {
leftContent += "\x00" + string(lines[line][1:]) + "\n"
}
rightContent := ""
for line := lastIns; line < lineIndex; line++ {
rightContent += "\x00" + string(lines[line][1:]) + "\n"
}
var sectionSegments [2][]*annotate.Annotation
highlight_diff.HighlightedDiffFunc(leftContent, rightContent, &sectionSegments, [2]int{beginOffsetLeft, beginOffsetRight})
anns = append(anns, sectionSegments[0]...)
anns = append(anns, sectionSegments[1]...)
}
} }
lastDel, lastIns = -1, -1
} }
lastDel, lastIns = -1, -1
} }
sort.Sort(anns)
out, err := annotate.Annotate(src, anns, template.HTMLEscape)
if err != nil {
return nil, false
}
return out, true
} }
sort.Sort(anns)
out, err := annotate.Annotate(src, anns, template.HTMLEscape)
if err != nil {
return nil, false
}
return out, true
default: default:
return nil, false return nil, false
} }

View File

@ -17,7 +17,6 @@ Directories
| Path | Synopsis | | Path | Synopsis |
|-------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------| |-------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| [analysis](https://godoc.org/github.com/shurcooL/go/analysis) | Package analysis provides a routine that determines if a file is generated or handcrafted. |
| [browser](https://godoc.org/github.com/shurcooL/go/browser) | Package browser provides utilities for interacting with users' browsers. | | [browser](https://godoc.org/github.com/shurcooL/go/browser) | Package browser provides utilities for interacting with users' browsers. |
| [ctxhttp](https://godoc.org/github.com/shurcooL/go/ctxhttp) | Package ctxhttp provides helper functions for performing context-aware HTTP requests. | | [ctxhttp](https://godoc.org/github.com/shurcooL/go/ctxhttp) | Package ctxhttp provides helper functions for performing context-aware HTTP requests. |
| [gddo](https://godoc.org/github.com/shurcooL/go/gddo) | Package gddo is a simple client library for accessing the godoc.org API. | | [gddo](https://godoc.org/github.com/shurcooL/go/gddo) | Package gddo is a simple client library for accessing the godoc.org API. |

View File

@ -1,51 +0,0 @@
// Package analysis provides a routine that determines if a file is generated or handcrafted.
//
// Deprecated: Use github.com/shurcooL/go/generated package instead. This implementation
// was done ad-hoc before a standard was proposed.
package analysis
import (
"bufio"
"io"
"os"
"path/filepath"
"strings"
)
// IsFileGenerated returns true if the specified file is generated, or false if it's handcrafted.
// rootDir is the filepath of root directory, but name is a '/'-separated path to file.
//
// It considers vendored files as "generated", in the sense that they are not the canonical
// version of a file. This behavior would ideally be factored out into a higher level utility,
// since it has nothing to do with generated comments.
//
// Deprecated: Use generated.ParseFile instead, which is more well defined because it
// implements a specification.
func IsFileGenerated(rootDir, name string) (bool, error) {
// Detect from name.
switch {
case strings.HasPrefix(name, "vendor/") || strings.Contains(name, "/vendor/"):
return true, nil
case strings.HasPrefix(name, "Godeps/"):
return true, nil
}
// Detect from file contents.
f, err := os.Open(filepath.Join(rootDir, filepath.FromSlash(name)))
if err != nil {
return false, err
}
defer f.Close()
r := bufio.NewReader(f)
s, err := r.ReadString('\n')
if err == io.EOF {
// Empty file or exactly 1 line is not considered to be generated.
return false, nil
} else if err != nil {
return false, err
}
if strings.Contains(s, "Code generated by") { // Consistent with https://golang.org/cl/15073.
return true, nil
}
return (strings.Contains(s, "GENERATED") || strings.Contains(s, "generated")) && strings.Contains(s, "DO NOT EDIT"), nil
}

View File

@ -1,28 +0,0 @@
package analysis_test
import (
"fmt"
"os"
"github.com/shurcooL/go/analysis"
)
func ExampleIsFileGenerated() {
cwd, err := os.Getwd()
if err != nil {
panic(err)
}
fmt.Println(analysis.IsFileGenerated(cwd, "testdata/generated_0.go.txt"))
fmt.Println(analysis.IsFileGenerated(cwd, "testdata/handcrafted_0.go.txt"))
fmt.Println(analysis.IsFileGenerated(cwd, "testdata/handcrafted_1.go.txt"))
fmt.Println(analysis.IsFileGenerated(cwd, "vendor/github.com/shurcooL/go/analysis/file.go"))
fmt.Println(analysis.IsFileGenerated(cwd, "subpkg/vendor/math/math.go"))
// Output:
// true <nil>
// false <nil>
// false <nil>
// true <nil>
// true <nil>
}

View File

@ -1,32 +0,0 @@
// generated by vfsgen; DO NOT EDIT
// +build !dev
package issues
import (
"bytes"
"compress/gzip"
"fmt"
"io"
"net/http"
"os"
pathpkg "path"
"time"
)
// Assets statically implements the virtual filesystem given to vfsgen as input.
var Assets = func() http.FileSystem {
mustUnmarshalTextTime := func(text string) time.Time {
var t time.Time
err := t.UnmarshalText([]byte(text))
if err != nil {
panic(err)
}
return t
}
fs := _vfsgen_fs{
...
}
}

View File

@ -1,9 +0,0 @@
// Package foo offers bar.
package foo
import "strings"
// Bar is bar.
func Bar() string {
return strings.Title("bar")
}

View File

@ -1 +0,0 @@
// Code generated by protoc-gen-gogo. Actually it isn't, because it's only 1 line.

View File

@ -24,7 +24,7 @@ Usage
Construct a GraphQL client, specifying the GraphQL server URL. Then, you can use it to make GraphQL queries and mutations. Construct a GraphQL client, specifying the GraphQL server URL. Then, you can use it to make GraphQL queries and mutations.
```Go ```Go
client := graphql.NewClient("https://example.com/graphql", nil, nil) client := graphql.NewClient("https://example.com/graphql", nil)
// Use client... // Use client...
``` ```
@ -41,7 +41,7 @@ func main() {
) )
httpClient := oauth2.NewClient(context.Background(), src) httpClient := oauth2.NewClient(context.Background(), src)
client := graphql.NewClient("https://example.com/graphql", httpClient, nil) client := graphql.NewClient("https://example.com/graphql", httpClient)
// Use client... // Use client...
``` ```

View File

@ -38,7 +38,7 @@ func run() error {
mux := http.NewServeMux() mux := http.NewServeMux()
mux.Handle("/query", &relay.Handler{Schema: schema}) mux.Handle("/query", &relay.Handler{Schema: schema})
client := graphql.NewClient("/query", &http.Client{Transport: localRoundTripper{handler: mux}}, nil) client := graphql.NewClient("/query", &http.Client{Transport: localRoundTripper{handler: mux}})
/* /*
query { query {

View File

@ -6,7 +6,6 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"net/http" "net/http"
"reflect"
"github.com/shurcooL/go/ctxhttp" "github.com/shurcooL/go/ctxhttp"
"github.com/shurcooL/graphql/internal/jsonutil" "github.com/shurcooL/graphql/internal/jsonutil"
@ -16,25 +15,17 @@ import (
type Client struct { type Client struct {
url string // GraphQL server URL. url string // GraphQL server URL.
httpClient *http.Client httpClient *http.Client
qctx *queryContext
} }
// NewClient creates a GraphQL client targeting the specified GraphQL server URL. // NewClient creates a GraphQL client targeting the specified GraphQL server URL.
// If httpClient is nil, then http.DefaultClient is used. // If httpClient is nil, then http.DefaultClient is used.
// scalars optionally specifies types that are scalars (this matters func NewClient(url string, httpClient *http.Client) *Client {
// when constructing queries from types, scalars are never expanded).
func NewClient(url string, httpClient *http.Client, scalars []reflect.Type) *Client {
if httpClient == nil { if httpClient == nil {
httpClient = http.DefaultClient httpClient = http.DefaultClient
} }
return &Client{ return &Client{
url: url, url: url,
httpClient: httpClient, httpClient: httpClient,
qctx: &queryContext{
Scalars: scalars,
},
} }
} }
@ -57,9 +48,9 @@ func (c *Client) do(ctx context.Context, op operationType, v interface{}, variab
var query string var query string
switch op { switch op {
case queryOperation: case queryOperation:
query = constructQuery(c.qctx, v, variables) query = constructQuery(v, variables)
case mutationOperation: case mutationOperation:
query = constructMutation(c.qctx, v, variables) query = constructMutation(v, variables)
} }
in := struct { in := struct {
Query string `json:"query"` Query string `json:"query"`

View File

@ -131,6 +131,10 @@ func (n Name) ToMixedCaps() string {
n[i] = initialism n[i] = initialism
continue continue
} }
if brand, ok := isBrand(word); ok {
n[i] = brand
continue
}
r, size := utf8.DecodeRuneInString(word) r, size := utf8.DecodeRuneInString(word)
n[i] = string(unicode.ToUpper(r)) + strings.ToLower(word[size:]) n[i] = string(unicode.ToUpper(r)) + strings.ToLower(word[size:])
} }
@ -220,3 +224,17 @@ var initialisms = map[string]struct{}{
// Additional common initialisms. // Additional common initialisms.
"RSS": {}, "RSS": {},
} }
// isBrand reports whether word is a brand.
func isBrand(word string) (string, bool) {
brand, ok := brands[strings.ToLower(word)]
return brand, ok
}
// brands is the map of brands in the MixedCaps naming convention;
// see https://dmitri.shuralyov.com/idiomatic-go#for-brands-or-words-with-more-than-1-capital-letter-lowercase-all-letters.
// Key is the lower case version of the brand, value is the canonical brand spelling.
// Only add entries that are highly unlikely to be non-brands.
var brands = map[string]string{
"github": "GitHub",
}

View File

@ -81,13 +81,14 @@ func TestParseScreamingSnakeCase(t *testing.T) {
} }
} }
func TestWords_ToMixedCaps(t *testing.T) { func TestName_ToMixedCaps(t *testing.T) {
tests := []struct { tests := []struct {
in ident.Name in ident.Name
want string want string
}{ }{
{in: ident.Name{"client", "Mutation", "Id"}, want: "ClientMutationID"}, {in: ident.Name{"client", "Mutation", "Id"}, want: "ClientMutationID"},
{in: ident.Name{"CLIENT", "MUTATION", "ID"}, want: "ClientMutationID"}, {in: ident.Name{"CLIENT", "MUTATION", "ID"}, want: "ClientMutationID"},
{in: ident.Name{"github", "logo"}, want: "GitHubLogo"},
} }
for _, tc := range tests { for _, tc := range tests {
got := tc.in.ToMixedCaps() got := tc.in.ToMixedCaps()
@ -97,7 +98,7 @@ func TestWords_ToMixedCaps(t *testing.T) {
} }
} }
func TestWords_ToLowerCamelCase(t *testing.T) { func TestName_ToLowerCamelCase(t *testing.T) {
tests := []struct { tests := []struct {
in ident.Name in ident.Name
want string want string

View File

@ -2,6 +2,7 @@ package graphql
import ( import (
"bytes" "bytes"
"encoding/json"
"io" "io"
"reflect" "reflect"
"sort" "sort"
@ -9,16 +10,16 @@ import (
"github.com/shurcooL/graphql/ident" "github.com/shurcooL/graphql/ident"
) )
func constructQuery(qctx *queryContext, v interface{}, variables map[string]interface{}) string { func constructQuery(v interface{}, variables map[string]interface{}) string {
query := qctx.Query(v) query := query(v)
if variables != nil { if variables != nil {
return "query(" + queryArguments(variables) + ")" + query return "query(" + queryArguments(variables) + ")" + query
} }
return query return query
} }
func constructMutation(qctx *queryContext, v interface{}, variables map[string]interface{}) string { func constructMutation(v interface{}, variables map[string]interface{}) string {
query := qctx.Query(v) query := query(v)
if variables != nil { if variables != nil {
return "mutation(" + queryArguments(variables) + ")" + query return "mutation(" + queryArguments(variables) + ")" + query
} }
@ -29,62 +30,78 @@ func constructMutation(qctx *queryContext, v interface{}, variables map[string]i
// //
// E.g., map[string]interface{}{"a": Int(123), "b": NewBoolean(true)} -> "$a:Int!$b:Boolean". // E.g., map[string]interface{}{"a": Int(123), "b": NewBoolean(true)} -> "$a:Int!$b:Boolean".
func queryArguments(variables map[string]interface{}) string { func queryArguments(variables map[string]interface{}) string {
sorted := make([]string, 0, len(variables)) // Sort keys in order to produce deterministic output for testing purposes.
// TODO: If tests can be made to work with non-deterministic output, then no need to sort.
keys := make([]string, 0, len(variables))
for k := range variables { for k := range variables {
sorted = append(sorted, k) keys = append(keys, k)
} }
sort.Strings(sorted) sort.Strings(keys)
var s string
for _, k := range sorted {
v := variables[k]
s += "$" + k + ":"
t := reflect.TypeOf(v)
switch t.Kind() {
case reflect.Slice, reflect.Array:
// TODO: Support t.Elem() being a pointer, if needed. Probably want to do this recursively.
s += "[" + t.Elem().Name() + "!]" // E.g., "[IssueState!]".
case reflect.Ptr:
// Pointer is an optional type, so no "!" at the end.
s += t.Elem().Name() // E.g., "Int".
default:
name := t.Name()
if name == "string" { // HACK: Workaround for https://github.com/shurcooL/githubql/issues/12.
name = "ID"
}
// Value is a required type, so add "!" to the end.
s += name + "!" // E.g., "Int!".
}
}
return s
}
type queryContext struct {
// Scalars are Go types that map to GraphQL scalars, and therefore we don't want to expand them.
Scalars []reflect.Type
}
// Query uses writeQuery to recursively construct
// a minified query string from the provided struct v.
//
// E.g., struct{Foo Int, BarBaz *Boolean} -> "{foo,barBaz}".
func (c *queryContext) Query(v interface{}) string {
var buf bytes.Buffer var buf bytes.Buffer
c.writeQuery(&buf, reflect.TypeOf(v), false) for _, k := range keys {
io.WriteString(&buf, "$")
io.WriteString(&buf, k)
io.WriteString(&buf, ":")
writeArgumentType(&buf, reflect.TypeOf(variables[k]), true)
// Don't insert a comma here.
// Commas in GraphQL are insignificant, and we want minified output.
// See https://facebook.github.io/graphql/October2016/#sec-Insignificant-Commas.
}
return buf.String() return buf.String()
} }
// writeQuery writes a minified query for t to w. If inline is true, // writeArgumentType writes a minified GraphQL type for t to w.
// the struct fields of t are inlined into parent struct. // value indicates whether t is a value (required) type or pointer (optional) type.
func (c *queryContext) writeQuery(w io.Writer, t reflect.Type, inline bool) { // If value is true, then "!" is written at the end of t.
func writeArgumentType(w io.Writer, t reflect.Type, value bool) {
if t.Kind() == reflect.Ptr {
// Pointer is an optional type, so no "!" at the end of the pointer's underlying type.
writeArgumentType(w, t.Elem(), false)
return
}
switch t.Kind() {
case reflect.Slice, reflect.Array:
// List. E.g., "[Int]".
io.WriteString(w, "[")
writeArgumentType(w, t.Elem(), true)
io.WriteString(w, "]")
default:
// Named type. E.g., "Int".
name := t.Name()
if name == "string" { // HACK: Workaround for https://github.com/shurcooL/githubql/issues/12.
name = "ID"
}
io.WriteString(w, name)
}
if value {
// Value is a required type, so add "!" to the end.
io.WriteString(w, "!")
}
}
// query uses writeQuery to recursively construct
// a minified query string from the provided struct v.
//
// E.g., struct{Foo Int, BarBaz *Boolean} -> "{foo,barBaz}".
func query(v interface{}) string {
var buf bytes.Buffer
writeQuery(&buf, reflect.TypeOf(v), false)
return buf.String()
}
// writeQuery writes a minified query for t to w.
// If inline is true, the struct fields of t are inlined into parent struct.
func writeQuery(w io.Writer, t reflect.Type, inline bool) {
switch t.Kind() { switch t.Kind() {
case reflect.Ptr, reflect.Slice: case reflect.Ptr, reflect.Slice:
c.writeQuery(w, t.Elem(), false) writeQuery(w, t.Elem(), false)
case reflect.Struct: case reflect.Struct:
// Special handling of scalar struct types. Don't expand them. // If the type implements json.Unmarshaler, it's a scalar. Don't expand it.
for _, scalar := range c.Scalars { if reflect.PtrTo(t).Implements(jsonUnmarshaler) {
if t == scalar { return
return
}
} }
if !inline { if !inline {
io.WriteString(w, "{") io.WriteString(w, "{")
@ -103,10 +120,12 @@ func (c *queryContext) writeQuery(w io.Writer, t reflect.Type, inline bool) {
io.WriteString(w, ident.ParseMixedCaps(f.Name).ToLowerCamelCase()) io.WriteString(w, ident.ParseMixedCaps(f.Name).ToLowerCamelCase())
} }
} }
c.writeQuery(w, f.Type, inlineField) writeQuery(w, f.Type, inlineField)
} }
if !inline { if !inline {
io.WriteString(w, "}") io.WriteString(w, "}")
} }
} }
} }
var jsonUnmarshaler = reflect.TypeOf((*json.Unmarshaler)(nil)).Elem()

View File

@ -2,7 +2,6 @@ package graphql
import ( import (
"net/url" "net/url"
"reflect"
"testing" "testing"
"time" "time"
) )
@ -217,15 +216,20 @@ func TestConstructQuery(t *testing.T) {
}(), }(),
want: `{actor{login,avatarUrl,url},createdAt,... on IssueComment{body},currentTitle,previousTitle,label{name,color}}`, want: `{actor{login,avatarUrl,url},createdAt,... on IssueComment{body},currentTitle,previousTitle,label{name,color}}`,
}, },
{
inV: struct {
Viewer struct {
Login string
CreatedAt time.Time
ID interface{}
DatabaseID int
}
}{},
want: `{viewer{login,createdAt,id,databaseId}}`,
},
} }
for _, tc := range tests { for _, tc := range tests {
qctx := &queryContext{ got := constructQuery(tc.inV, tc.inVariables)
Scalars: []reflect.Type{
reflect.TypeOf(DateTime{}),
reflect.TypeOf(URI{}),
},
}
got := constructQuery(qctx, tc.inV, tc.inVariables)
if got != tc.want { if got != tc.want {
t.Errorf("\ngot: %q\nwant: %q\n", got, tc.want) t.Errorf("\ngot: %q\nwant: %q\n", got, tc.want)
} }
@ -260,7 +264,7 @@ func TestConstructMutation(t *testing.T) {
}, },
} }
for _, tc := range tests { for _, tc := range tests {
got := constructMutation(&queryContext{}, tc.inV, tc.inVariables) got := constructMutation(tc.inV, tc.inVariables)
if got != tc.want { if got != tc.want {
t.Errorf("\ngot: %q\nwant: %q\n", got, tc.want) t.Errorf("\ngot: %q\nwant: %q\n", got, tc.want)
} }
@ -269,7 +273,6 @@ func TestConstructMutation(t *testing.T) {
func TestQueryArguments(t *testing.T) { func TestQueryArguments(t *testing.T) {
tests := []struct { tests := []struct {
name string
in map[string]interface{} in map[string]interface{}
want string want string
}{ }{
@ -278,26 +281,43 @@ func TestQueryArguments(t *testing.T) {
want: "$a:Int!$b:Boolean", want: "$a:Int!$b:Boolean",
}, },
{ {
in: map[string]interface{}{"states": []IssueState{IssueStateOpen, IssueStateClosed}}, in: map[string]interface{}{
want: "$states:[IssueState!]", "required": []IssueState{IssueStateOpen, IssueStateClosed},
"optional": &[]IssueState{IssueStateOpen, IssueStateClosed},
},
want: "$optional:[IssueState!]$required:[IssueState!]!",
}, },
{ {
in: map[string]interface{}{"states": []IssueState(nil)}, in: map[string]interface{}{
want: "$states:[IssueState!]", "required": []IssueState(nil),
"optional": (*[]IssueState)(nil),
},
want: "$optional:[IssueState!]$required:[IssueState!]!",
}, },
{ {
in: map[string]interface{}{"states": [...]IssueState{IssueStateOpen, IssueStateClosed}}, in: map[string]interface{}{
want: "$states:[IssueState!]", "required": [...]IssueState{IssueStateOpen, IssueStateClosed},
"optional": &[...]IssueState{IssueStateOpen, IssueStateClosed},
},
want: "$optional:[IssueState!]$required:[IssueState!]!",
}, },
{ {
in: map[string]interface{}{"id": ID("someid")}, in: map[string]interface{}{"id": ID("someID")},
want: "$id:ID!", want: "$id:ID!",
}, },
{
in: map[string]interface{}{"ids": []ID{"someID", "anotherID"}},
want: `$ids:[ID!]!`,
},
{
in: map[string]interface{}{"ids": &[]ID{"someID", "anotherID"}},
want: `$ids:[ID!]`,
},
} }
for _, tc := range tests { for i, tc := range tests {
got := queryArguments(tc.in) got := queryArguments(tc.in)
if got != tc.want { if got != tc.want {
t.Errorf("%s: got: %q, want: %q", tc.name, got, tc.want) t.Errorf("test case %d:\n got: %q\nwant: %q", i, got, tc.want)
} }
} }
} }
@ -311,6 +331,8 @@ type (
URI struct{ *url.URL } URI struct{ *url.URL }
) )
func (u *URI) UnmarshalJSON(data []byte) error { panic("mock implementation") }
// IssueState represents the possible states of an issue. // IssueState represents the possible states of an issue.
type IssueState string type IssueState string

View File

@ -7,7 +7,6 @@ import (
"encoding/json" "encoding/json"
"flag" "flag"
"fmt" "fmt"
"go/format"
"io" "io"
"log" "log"
"os" "os"
@ -55,6 +54,8 @@ func run() error {
fmt.Fprint(&buf, `package octiconssvg fmt.Fprint(&buf, `package octiconssvg
import ( import (
"strconv"
"golang.org/x/net/html" "golang.org/x/net/html"
"golang.org/x/net/html/atom" "golang.org/x/net/html/atom"
) )
@ -71,14 +72,18 @@ func Icon(name string) *html.Node {
return nil return nil
} }
} }
`)
for _, name := range names {
processOcticon(&buf, octicons, name)
}
b, err := format.Source(buf.Bytes()) // SetSize sets size of icon, and returns a reference to it.
if err != nil { func SetSize(icon *html.Node, size int) *html.Node {
return fmt.Errorf("error from format.Source(): %v", err) icon.Attr[`, widthAttrIndex, `].Val = strconv.Itoa(size)
icon.Attr[`, heightAttrIndex, `].Val = strconv.Itoa(size)
return icon
}
`)
// Write all individual Octicon functions.
for _, name := range names {
generateAndWriteOcticon(&buf, octicons, name)
} }
var w io.Writer var w io.Writer
@ -94,7 +99,7 @@ func Icon(name string) *html.Node {
w = f w = f
} }
_, err = w.Write(b) _, err = w.Write(buf.Bytes())
return err return err
} }
@ -104,10 +109,10 @@ type octicon struct {
Height int `json:",string"` Height int `json:",string"`
} }
func processOcticon(w io.Writer, octicons map[string]octicon, name string) { func generateAndWriteOcticon(w io.Writer, octicons map[string]octicon, name string) {
svgXML := generateOcticon(octicons[name]) svgXML := generateOcticon(octicons[name])
svg := parseOcticon(svgXML)
svg := parseOcticon(svgXML)
// Clear these fields to remove cycles in the data structure, since go-goon // Clear these fields to remove cycles in the data structure, since go-goon
// cannot print those in a way that's valid Go code. The generated data structure // cannot print those in a way that's valid Go code. The generated data structure
// is not a proper *html.Node with all fields set, but it's enough for rendering // is not a proper *html.Node with all fields set, but it's enough for rendering
@ -129,10 +134,19 @@ func generateOcticon(o octicon) (svgXML string) {
// Skip fill-rule, if present. It has no effect on displayed SVG, but takes up space. // Skip fill-rule, if present. It has no effect on displayed SVG, but takes up space.
path = `<path ` + path[len(`<path fill-rule="evenodd" `):] path = `<path ` + path[len(`<path fill-rule="evenodd" `):]
} }
// Note, SetSize relies on the absolute position of the width, height attributes.
// Keep them in sync with widthAttrIndex and heightAttrIndex.
return fmt.Sprintf(`<svg xmlns="http://www.w3.org/2000/svg" width="%d" height="%d" viewBox="0 0 %d %d">%s</svg>`, return fmt.Sprintf(`<svg xmlns="http://www.w3.org/2000/svg" width="%d" height="%d" viewBox="0 0 %d %d">%s</svg>`,
o.Width, o.Height, o.Width, o.Height, path) o.Width, o.Height, o.Width, o.Height, path)
} }
// These constants are used during generation of SetSize function.
// Keep them in sync with generateOcticon.
const (
widthAttrIndex = 1
heightAttrIndex = 2
)
func parseOcticon(svgXML string) *html.Node { func parseOcticon(svgXML string) *html.Node {
e, err := html.ParseFragment(strings.NewReader(svgXML), nil) e, err := html.ParseFragment(strings.NewReader(svgXML), nil)
if err != nil { if err != nil {

File diff suppressed because it is too large Load Diff

View File

@ -10,7 +10,7 @@ import (
) )
func Example() { func Example() {
var w io.Writer = os.Stdout // Or, e.g., http.ResponseWriter in your handler, etc. var w io.Writer = os.Stdout // Or, e.g., http.ResponseWriter in your HTTP handler, etc.
err := html.Render(w, octiconssvg.Alert()) err := html.Render(w, octiconssvg.Alert())
if err != nil { if err != nil {
@ -20,3 +20,15 @@ func Example() {
// Output: // Output:
// <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" style="fill: currentColor; vertical-align: top;"><path d="M8.865 1.52c-.18-.31-.51-.5-.87-.5s-.69.19-.87.5L.275 13.5c-.18.31-.18.69 0 1 .19.31.52.5.87.5h13.7c.36 0 .69-.19.86-.5.17-.31.18-.69.01-1L8.865 1.52zM8.995 13h-2v-2h2v2zm0-3h-2V6h2v4z"></path></svg> // <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" style="fill: currentColor; vertical-align: top;"><path d="M8.865 1.52c-.18-.31-.51-.5-.87-.5s-.69.19-.87.5L.275 13.5c-.18.31-.18.69 0 1 .19.31.52.5.87.5h13.7c.36 0 .69-.19.86-.5.17-.31.18-.69.01-1L8.865 1.52zM8.995 13h-2v-2h2v2zm0-3h-2V6h2v4z"></path></svg>
} }
func ExampleSetSize() {
var w io.Writer = os.Stdout // Or, e.g., http.ResponseWriter in your HTTP handler, etc.
err := html.Render(w, octiconssvg.SetSize(octiconssvg.MarkGitHub(), 24))
if err != nil {
log.Fatalln(err)
}
// Output:
// <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 16 16" style="fill: currentColor; vertical-align: top;"><path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0 0 16 8c0-4.42-3.58-8-8-8z"></path></svg>
}

View File

@ -1,10 +1,11 @@
language: go language: go
sudo: false sudo: false
go: go:
- 1.8.x - 1.7.x # go testing suite support, which we use, was introduced in go 1.7
- 1.9.x - 1.8.x
- tip - 1.9.x
- tip
script: script:
- go test -tags "alltests" -run Suite -coverprofile coverage.txt github.com/ugorji/go/codec - go test -tags "alltests" -run Suite -coverprofile coverage.txt github.com/ugorji/go/codec
after_success: after_success:
- bash <(curl -s https://codecov.io/bash) - bash <(curl -s https://codecov.io/bash)

View File

@ -2,7 +2,7 @@
[![Build Status](https://travis-ci.org/ugorji/go.svg?branch=master)](https://travis-ci.org/ugorji/go) [![Build Status](https://travis-ci.org/ugorji/go.svg?branch=master)](https://travis-ci.org/ugorji/go)
[![codecov](https://codecov.io/gh/ugorji/go/branch/master/graph/badge.svg)](https://codecov.io/gh/ugorji/go) [![codecov](https://codecov.io/gh/ugorji/go/branch/master/graph/badge.svg)](https://codecov.io/gh/ugorji/go)
[![GoDoc](http://img.shields.io/badge/go-documentation-blue.svg?style=flat-square)](http://godoc.org/github.com/ugorji/go/codec) [![GoDoc](http://img.shields.io/badge/go-documentation-blue.svg?style=flat-square)](http://godoc.org/github.com/ugorji/go/codec)
[![rcard](https://goreportcard.com/badge/github.com/ugorji/go/codec)](https://goreportcard.com/report/github.com/ugorji/go/codec) [![rcard](https://goreportcard.com/badge/github.com/ugorji/go/codec?v=2)](https://goreportcard.com/report/github.com/ugorji/go/codec)
[![License](http://img.shields.io/badge/license-mit-blue.svg?style=flat-square)](https://raw.githubusercontent.com/ugorji/go/master/LICENSE) [![License](http://img.shields.io/badge/license-mit-blue.svg?style=flat-square)](https://raw.githubusercontent.com/ugorji/go/master/LICENSE)
# go/codec # go/codec

View File

@ -1,9 +1,10 @@
// Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved. // Copyright (c) 2012-2018 Ugorji Nwoke. All rights reserved.
// Use of this source code is governed by a MIT license found in the LICENSE file. // Use of this source code is governed by a MIT license found in the LICENSE file.
/* /*
High Performance, Feature-Rich Idiomatic Go 1.4+ codec/encoding library for Package codec provides a
binc, msgpack, cbor, json High Performance, Feature-Rich Idiomatic Go 1.4+ codec/encoding library
for binc, msgpack, cbor, json.
Supported Serialization formats are: Supported Serialization formats are:
@ -32,15 +33,14 @@ Rich Feature Set includes:
- Simple but extremely powerful and feature-rich API - Simple but extremely powerful and feature-rich API
- Support for go1.4 and above, while selectively using newer APIs for later releases - Support for go1.4 and above, while selectively using newer APIs for later releases
- Good code coverage ( > 70% ) - Excellent code coverage ( > 90% )
- Very High Performance. - Very High Performance.
Our extensive benchmarks show us outperforming Gob, Json, Bson, etc by 2-4X. Our extensive benchmarks show us outperforming Gob, Json, Bson, etc by 2-4X.
- Careful selected use of 'unsafe' for targeted performance gains. - Careful selected use of 'unsafe' for targeted performance gains.
100% mode exists where 'unsafe' is not used at all. 100% mode exists where 'unsafe' is not used at all.
- Lock-free (sans mutex) concurrency for scaling to 100's of cores - Lock-free (sans mutex) concurrency for scaling to 100's of cores
- Multiple conversions: - Coerce types where appropriate
Package coerces types where appropriate e.g. decode an int in the stream into a float, decode numbers from formatted strings, etc
e.g. decode an int in the stream into a float, etc.
- Corner Cases: - Corner Cases:
Overflows, nil maps/slices, nil values in streams are handled correctly Overflows, nil maps/slices, nil values in streams are handled correctly
- Standard field renaming via tags - Standard field renaming via tags
@ -49,10 +49,16 @@ Rich Feature Set includes:
(struct, slice, map, primitives, pointers, interface{}, etc) (struct, slice, map, primitives, pointers, interface{}, etc)
- Extensions to support efficient encoding/decoding of any named types - Extensions to support efficient encoding/decoding of any named types
- Support encoding.(Binary|Text)(M|Unm)arshaler interfaces - Support encoding.(Binary|Text)(M|Unm)arshaler interfaces
- Support IsZero() bool to determine if a value is a zero value.
Analogous to time.Time.IsZero() bool.
- Decoding without a schema (into a interface{}). - Decoding without a schema (into a interface{}).
Includes Options to configure what specific map or slice type to use Includes Options to configure what specific map or slice type to use
when decoding an encoded list or map into a nil interface{} when decoding an encoded list or map into a nil interface{}
- Mapping a non-interface type to an interface, so we can decode appropriately
into any interface type with a correctly configured non-interface value.
- Encode a struct as an array, and decode struct from an array in the data stream - Encode a struct as an array, and decode struct from an array in the data stream
- Option to encode struct keys as numbers (instead of strings)
(to support structured streams with fields encoded as numeric codes)
- Comprehensive support for anonymous fields - Comprehensive support for anonymous fields
- Fast (no-reflection) encoding/decoding of common maps and slices - Fast (no-reflection) encoding/decoding of common maps and slices
- Code-generation for faster performance. - Code-generation for faster performance.
@ -109,7 +115,7 @@ This symmetry is important to reduce chances of issues happening because the
encoding and decoding sides are out of sync e.g. decoded via very specific encoding and decoding sides are out of sync e.g. decoded via very specific
encoding.TextUnmarshaler but encoded via kind-specific generalized mode. encoding.TextUnmarshaler but encoded via kind-specific generalized mode.
Consequently, if a type only defines one-half of the symetry Consequently, if a type only defines one-half of the symmetry
(e.g. it implements UnmarshalJSON() but not MarshalJSON() ), (e.g. it implements UnmarshalJSON() but not MarshalJSON() ),
then that type doesn't satisfy the check and we will continue walking down the then that type doesn't satisfy the check and we will continue walking down the
decision tree. decision tree.
@ -201,6 +207,58 @@ Running Benchmarks
Please see http://github.com/ugorji/go-codec-bench . Please see http://github.com/ugorji/go-codec-bench .
Caveats
Struct fields matching the following are ignored during encoding and decoding
- struct tag value set to -
- func, complex numbers, unsafe pointers
- unexported and not embedded
- unexported and embedded and not struct kind
- unexported and embedded pointers (from go1.10)
Every other field in a struct will be encoded/decoded.
Embedded fields are encoded as if they exist in the top-level struct,
with some caveats. See Encode documentation.
*/ */
package codec package codec
// TODO:
// - In Go 1.10, when mid-stack inlining is enabled,
// we should use committed functions for writeXXX and readXXX calls.
// This involves uncommenting the methods for decReaderSwitch and encWriterSwitch
// and using those (decReaderSwitch and encWriterSwitch) in all handles
// instead of encWriter and decReader.
// The benefit is that, for the (En|De)coder over []byte, the encWriter/decReader
// will be inlined, giving a performance bump for that typical case.
// However, it will only be inlined if mid-stack inlining is enabled,
// as we call panic to raise errors, and panic currently prevents inlining.
//
// PUNTED:
// - To make Handle comparable, make extHandle in BasicHandle a non-embedded pointer,
// and use overlay methods on *BasicHandle to call through to extHandle after initializing
// the "xh *extHandle" to point to a real slice.
//
// BEFORE EACH RELEASE:
// - Look through and fix padding for each type, to eliminate false sharing
// - critical shared objects that are read many times
// TypeInfos
// - pooled objects:
// decNaked, decNakedContainers, codecFner, typeInfoLoadArray,
// - small objects allocated independently, that we read/use much across threads:
// codecFn, typeInfo
// - Objects allocated independently and used a lot
// Decoder, Encoder,
// xxxHandle, xxxEncDriver, xxxDecDriver (xxx = json, msgpack, cbor, binc, simple)
// - In all above, arrange values modified together to be close to each other.
//
// For all of these, either ensure that they occupy full cache lines,
// or ensure that the things just past the cache line boundary are hardly read/written
// e.g. JsonHandle.RawBytesExt - which is copied into json(En|De)cDriver at init
//
// Occupying full cache lines means they occupy 8*N words (where N is an integer).
// Check this out by running: ./run.sh -z
// - look at those tagged ****, meaning they are not occupying full cache lines
// - look at those tagged <<<<, meaning they are larger than 32 words (something to watch)
// - Run "golint -min_confidence 0.81"

View File

@ -31,15 +31,14 @@ Rich Feature Set includes:
- Simple but extremely powerful and feature-rich API - Simple but extremely powerful and feature-rich API
- Support for go1.4 and above, while selectively using newer APIs for later releases - Support for go1.4 and above, while selectively using newer APIs for later releases
- Good code coverage ( > 70% ) - Excellent code coverage ( > 90% )
- Very High Performance. - Very High Performance.
Our extensive benchmarks show us outperforming Gob, Json, Bson, etc by 2-4X. Our extensive benchmarks show us outperforming Gob, Json, Bson, etc by 2-4X.
- Careful selected use of 'unsafe' for targeted performance gains. - Careful selected use of 'unsafe' for targeted performance gains.
100% mode exists where 'unsafe' is not used at all. 100% mode exists where 'unsafe' is not used at all.
- Lock-free (sans mutex) concurrency for scaling to 100's of cores - Lock-free (sans mutex) concurrency for scaling to 100's of cores
- Multiple conversions: - Coerce types where appropriate
Package coerces types where appropriate e.g. decode an int in the stream into a float, decode numbers from formatted strings, etc
e.g. decode an int in the stream into a float, etc.
- Corner Cases: - Corner Cases:
Overflows, nil maps/slices, nil values in streams are handled correctly Overflows, nil maps/slices, nil values in streams are handled correctly
- Standard field renaming via tags - Standard field renaming via tags
@ -48,10 +47,16 @@ Rich Feature Set includes:
(struct, slice, map, primitives, pointers, interface{}, etc) (struct, slice, map, primitives, pointers, interface{}, etc)
- Extensions to support efficient encoding/decoding of any named types - Extensions to support efficient encoding/decoding of any named types
- Support encoding.(Binary|Text)(M|Unm)arshaler interfaces - Support encoding.(Binary|Text)(M|Unm)arshaler interfaces
- Support IsZero() bool to determine if a value is a zero value.
Analogous to time.Time.IsZero() bool.
- Decoding without a schema (into a interface{}). - Decoding without a schema (into a interface{}).
Includes Options to configure what specific map or slice type to use Includes Options to configure what specific map or slice type to use
when decoding an encoded list or map into a nil interface{} when decoding an encoded list or map into a nil interface{}
- Mapping a non-interface type to an interface, so we can decode appropriately
into any interface type with a correctly configured non-interface value.
- Encode a struct as an array, and decode struct from an array in the data stream - Encode a struct as an array, and decode struct from an array in the data stream
- Option to encode struct keys as numbers (instead of strings)
(to support structured streams with fields encoded as numeric codes)
- Comprehensive support for anonymous fields - Comprehensive support for anonymous fields
- Fast (no-reflection) encoding/decoding of common maps and slices - Fast (no-reflection) encoding/decoding of common maps and slices
- Code-generation for faster performance. - Code-generation for faster performance.
@ -107,7 +112,7 @@ This symmetry is important to reduce chances of issues happening because the
encoding and decoding sides are out of sync e.g. decoded via very specific encoding and decoding sides are out of sync e.g. decoded via very specific
encoding.TextUnmarshaler but encoded via kind-specific generalized mode. encoding.TextUnmarshaler but encoded via kind-specific generalized mode.
Consequently, if a type only defines one-half of the symetry Consequently, if a type only defines one-half of the symmetry
(e.g. it implements UnmarshalJSON() but not MarshalJSON() ), (e.g. it implements UnmarshalJSON() but not MarshalJSON() ),
then that type doesn't satisfy the check and we will continue walking down the then that type doesn't satisfy the check and we will continue walking down the
decision tree. decision tree.
@ -185,3 +190,17 @@ You can run the tag 'safe' to run tests or build in safe mode. e.g.
Please see http://github.com/ugorji/go-codec-bench . Please see http://github.com/ugorji/go-codec-bench .
## Caveats
Struct fields matching the following are ignored during encoding and decoding
- struct tag value set to -
- func, complex numbers, unsafe pointers
- unexported and not embedded
- unexported and embedded and not struct kind
- unexported and embedded pointers (from go1.10)
Every other field in a struct will be encoded/decoded.
Embedded fields are encoded as if they exist in the top-level struct,
with some caveats. See Encode documentation.

View File

@ -1,4 +1,4 @@
// Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved. // Copyright (c) 2012-2018 Ugorji Nwoke. All rights reserved.
// Use of this source code is governed by a MIT license found in the LICENSE file. // Use of this source code is governed by a MIT license found in the LICENSE file.
package codec package codec
@ -57,38 +57,31 @@ const (
type bincEncDriver struct { type bincEncDriver struct {
e *Encoder e *Encoder
h *BincHandle
w encWriter w encWriter
m map[string]uint16 // symbols m map[string]uint16 // symbols
b [scratchByteArrayLen]byte b [16]byte // scratch, used for encoding numbers - bigendian style
s uint16 // symbols sequencer s uint16 // symbols sequencer
// c containerState
encDriverTrackContainerWriter
noBuiltInTypes
// encNoSeparator // encNoSeparator
encDriverNoopContainerWriter
}
func (e *bincEncDriver) IsBuiltinType(rt uintptr) bool {
return rt == timeTypId
}
func (e *bincEncDriver) EncodeBuiltin(rt uintptr, v interface{}) {
if rt == timeTypId {
var bs []byte
switch x := v.(type) {
case time.Time:
bs = encodeTime(x)
case *time.Time:
bs = encodeTime(*x)
default:
e.e.errorf("binc error encoding builtin: expect time.Time, received %T", v)
}
e.w.writen1(bincVdTimestamp<<4 | uint8(len(bs)))
e.w.writeb(bs)
}
} }
func (e *bincEncDriver) EncodeNil() { func (e *bincEncDriver) EncodeNil() {
e.w.writen1(bincVdSpecial<<4 | bincSpNil) e.w.writen1(bincVdSpecial<<4 | bincSpNil)
} }
func (e *bincEncDriver) EncodeTime(t time.Time) {
if t.IsZero() {
e.EncodeNil()
} else {
bs := bincEncodeTime(t)
e.w.writen1(bincVdTimestamp<<4 | uint8(len(bs)))
e.w.writeb(bs)
}
}
func (e *bincEncDriver) EncodeBool(b bool) { func (e *bincEncDriver) EncodeBool(b bool) {
if b { if b {
e.w.writen1(bincVdSpecial<<4 | bincSpTrue) e.w.writen1(bincVdSpecial<<4 | bincSpTrue)
@ -198,13 +191,19 @@ func (e *bincEncDriver) encodeExtPreamble(xtag byte, length int) {
func (e *bincEncDriver) WriteArrayStart(length int) { func (e *bincEncDriver) WriteArrayStart(length int) {
e.encLen(bincVdArray<<4, uint64(length)) e.encLen(bincVdArray<<4, uint64(length))
e.c = containerArrayStart
} }
func (e *bincEncDriver) WriteMapStart(length int) { func (e *bincEncDriver) WriteMapStart(length int) {
e.encLen(bincVdMap<<4, uint64(length)) e.encLen(bincVdMap<<4, uint64(length))
e.c = containerMapStart
} }
func (e *bincEncDriver) EncodeString(c charEncoding, v string) { func (e *bincEncDriver) EncodeString(c charEncoding, v string) {
if e.c == containerMapKey && c == cUTF8 && (e.h.AsSymbols == 0 || e.h.AsSymbols == 1) {
e.EncodeSymbol(v)
return
}
l := uint64(len(v)) l := uint64(len(v))
e.encBytesLen(c, l) e.encBytesLen(c, l)
if l > 0 { if l > 0 {
@ -214,7 +213,7 @@ func (e *bincEncDriver) EncodeString(c charEncoding, v string) {
func (e *bincEncDriver) EncodeSymbol(v string) { func (e *bincEncDriver) EncodeSymbol(v string) {
// if WriteSymbolsNoRefs { // if WriteSymbolsNoRefs {
// e.encodeString(c_UTF8, v) // e.encodeString(cUTF8, v)
// return // return
// } // }
@ -224,10 +223,10 @@ func (e *bincEncDriver) EncodeSymbol(v string) {
l := len(v) l := len(v)
if l == 0 { if l == 0 {
e.encBytesLen(c_UTF8, 0) e.encBytesLen(cUTF8, 0)
return return
} else if l == 1 { } else if l == 1 {
e.encBytesLen(c_UTF8, 1) e.encBytesLen(cUTF8, 1)
e.w.writen1(v[0]) e.w.writen1(v[0])
return return
} }
@ -277,6 +276,10 @@ func (e *bincEncDriver) EncodeSymbol(v string) {
} }
func (e *bincEncDriver) EncodeStringBytes(c charEncoding, v []byte) { func (e *bincEncDriver) EncodeStringBytes(c charEncoding, v []byte) {
if v == nil {
e.EncodeNil()
return
}
l := uint64(len(v)) l := uint64(len(v))
e.encBytesLen(c, l) e.encBytesLen(c, l)
if l > 0 { if l > 0 {
@ -286,7 +289,7 @@ func (e *bincEncDriver) EncodeStringBytes(c charEncoding, v []byte) {
func (e *bincEncDriver) encBytesLen(c charEncoding, length uint64) { func (e *bincEncDriver) encBytesLen(c charEncoding, length uint64) {
//TODO: support bincUnicodeOther (for now, just use string or bytearray) //TODO: support bincUnicodeOther (for now, just use string or bytearray)
if c == c_RAW { if c == cRAW {
e.encLen(bincVdByteArray<<4, length) e.encLen(bincVdByteArray<<4, length)
} else { } else {
e.encLen(bincVdString<<4, length) e.encLen(bincVdString<<4, length)
@ -325,6 +328,9 @@ type bincDecSymbol struct {
} }
type bincDecDriver struct { type bincDecDriver struct {
decDriverNoopContainerReader
noBuiltInTypes
d *Decoder d *Decoder
h *BincHandle h *BincHandle
r decReader r decReader
@ -333,14 +339,15 @@ type bincDecDriver struct {
bd byte bd byte
vd byte vd byte
vs byte vs byte
// noStreamingCodec _ [3]byte // padding
// decNoSeparator
b [scratchByteArrayLen]byte
// linear searching on this slice is ok, // linear searching on this slice is ok,
// because we typically expect < 32 symbols in each stream. // because we typically expect < 32 symbols in each stream.
s []bincDecSymbol s []bincDecSymbol
decDriverNoopContainerReader
// noStreamingCodec
// decNoSeparator
b [8 * 8]byte // scratch
} }
func (d *bincDecDriver) readNextBd() { func (d *bincDecDriver) readNextBd() {
@ -371,9 +378,10 @@ func (d *bincDecDriver) ContainerType() (vt valueType) {
return valueTypeArray return valueTypeArray
} else if d.vd == bincVdMap { } else if d.vd == bincVdMap {
return valueTypeMap return valueTypeMap
} else {
// d.d.errorf("isContainerType: unsupported parameter: %v", vt)
} }
// else {
// d.d.errorf("isContainerType: unsupported parameter: %v", vt)
// }
return valueTypeUnset return valueTypeUnset
} }
@ -388,27 +396,24 @@ func (d *bincDecDriver) TryDecodeAsNil() bool {
return false return false
} }
func (d *bincDecDriver) IsBuiltinType(rt uintptr) bool { func (d *bincDecDriver) DecodeTime() (t time.Time) {
return rt == timeTypId
}
func (d *bincDecDriver) DecodeBuiltin(rt uintptr, v interface{}) {
if !d.bdRead { if !d.bdRead {
d.readNextBd() d.readNextBd()
} }
if rt == timeTypId { if d.bd == bincVdSpecial<<4|bincSpNil {
if d.vd != bincVdTimestamp {
d.d.errorf("Invalid d.vd. Expecting 0x%x. Received: 0x%x", bincVdTimestamp, d.vd)
return
}
tt, err := decodeTime(d.r.readx(int(d.vs)))
if err != nil {
panic(err)
}
var vt *time.Time = v.(*time.Time)
*vt = tt
d.bdRead = false d.bdRead = false
return
} }
if d.vd != bincVdTimestamp {
d.d.errorf("Invalid d.vd. Expecting 0x%x. Received: 0x%x", bincVdTimestamp, d.vd)
return
}
t, err := bincDecodeTime(d.r.readx(int(d.vs)))
if err != nil {
panic(err)
}
d.bdRead = false
return
} }
func (d *bincDecDriver) decFloatPre(vs, defaultLen byte) { func (d *bincDecDriver) decFloatPre(vs, defaultLen byte) {
@ -497,45 +502,33 @@ func (d *bincDecDriver) decCheckInteger() (ui uint64, neg bool) {
return return
} }
} else { } else {
d.d.errorf("number can only be decoded from uint or int values. d.bd: 0x%x, d.vd: 0x%x", d.bd, d.vd) d.d.errorf("integer can only be decoded from int/uint. d.bd: 0x%x, d.vd: 0x%x", d.bd, d.vd)
return return
} }
return return
} }
func (d *bincDecDriver) DecodeInt(bitsize uint8) (i int64) { func (d *bincDecDriver) DecodeInt64() (i int64) {
ui, neg := d.decCheckInteger() ui, neg := d.decCheckInteger()
i, overflow := chkOvf.SignedInt(ui) i = chkOvf.SignedIntV(ui)
if overflow {
d.d.errorf("simple: overflow converting %v to signed integer", ui)
return
}
if neg { if neg {
i = -i i = -i
} }
if chkOvf.Int(i, bitsize) {
d.d.errorf("binc: overflow integer: %v for num bits: %v", i, bitsize)
return
}
d.bdRead = false d.bdRead = false
return return
} }
func (d *bincDecDriver) DecodeUint(bitsize uint8) (ui uint64) { func (d *bincDecDriver) DecodeUint64() (ui uint64) {
ui, neg := d.decCheckInteger() ui, neg := d.decCheckInteger()
if neg { if neg {
d.d.errorf("Assigning negative signed value to unsigned type") d.d.errorf("Assigning negative signed value to unsigned type")
return return
} }
if chkOvf.Uint(ui, bitsize) {
d.d.errorf("binc: overflow integer: %v", ui)
return
}
d.bdRead = false d.bdRead = false
return return
} }
func (d *bincDecDriver) DecodeFloat(chkOverflow32 bool) (f float64) { func (d *bincDecDriver) DecodeFloat64() (f float64) {
if !d.bdRead { if !d.bdRead {
d.readNextBd() d.readNextBd()
} }
@ -557,11 +550,7 @@ func (d *bincDecDriver) DecodeFloat(chkOverflow32 bool) (f float64) {
} else if vd == bincVdFloat { } else if vd == bincVdFloat {
f = d.decFloat() f = d.decFloat()
} else { } else {
f = float64(d.DecodeInt(64)) f = float64(d.DecodeInt64())
}
if chkOverflow32 && chkOvf.Float32(f) {
d.d.errorf("binc: float32 overflow: %v", f)
return
} }
d.bdRead = false d.bdRead = false
return return
@ -633,7 +622,8 @@ func (d *bincDecDriver) decLenNumber() (v uint64) {
return return
} }
func (d *bincDecDriver) decStringAndBytes(bs []byte, withString, zerocopy bool) (bs2 []byte, s string) { func (d *bincDecDriver) decStringAndBytes(bs []byte, withString, zerocopy bool) (
bs2 []byte, s string) {
if !d.bdRead { if !d.bdRead {
d.readNextBd() d.readNextBd()
} }
@ -641,7 +631,7 @@ func (d *bincDecDriver) decStringAndBytes(bs []byte, withString, zerocopy bool)
d.bdRead = false d.bdRead = false
return return
} }
var slen int = -1 var slen = -1
// var ok bool // var ok bool
switch d.vd { switch d.vd {
case bincVdString, bincVdByteArray: case bincVdString, bincVdByteArray:
@ -743,6 +733,11 @@ func (d *bincDecDriver) DecodeBytes(bs []byte, zerocopy bool) (bsOut []byte) {
d.bdRead = false d.bdRead = false
return nil return nil
} }
// check if an "array" of uint8's (see ContainerType for how to infer if an array)
if d.vd == bincVdArray {
bsOut, _ = fastpathTV.DecSliceUint8V(bs, true, d.d)
return
}
var clen int var clen int
if d.vd == bincVdString || d.vd == bincVdByteArray { if d.vd == bincVdString || d.vd == bincVdByteArray {
clen = d.decLen() clen = d.decLen()
@ -863,8 +858,8 @@ func (d *bincDecDriver) DecodeNaked() {
n.v = valueTypeBytes n.v = valueTypeBytes
n.l = d.DecodeBytes(nil, false) n.l = d.DecodeBytes(nil, false)
case bincVdTimestamp: case bincVdTimestamp:
n.v = valueTypeTimestamp n.v = valueTypeTime
tt, err := decodeTime(d.r.readx(int(d.vs))) tt, err := bincDecodeTime(d.r.readx(int(d.vs)))
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -912,27 +907,50 @@ type BincHandle struct {
BasicHandle BasicHandle
binaryEncodingType binaryEncodingType
noElemSeparators noElemSeparators
// AsSymbols defines what should be encoded as symbols.
//
// Encoding as symbols can reduce the encoded size significantly.
//
// However, during decoding, each string to be encoded as a symbol must
// be checked to see if it has been seen before. Consequently, encoding time
// will increase if using symbols, because string comparisons has a clear cost.
//
// Values:
// - 0: default: library uses best judgement
// - 1: use symbols
// - 2: do not use symbols
AsSymbols uint8
// AsSymbols: may later on introduce more options ...
// - m: map keys
// - s: struct fields
// - n: none
// - a: all: same as m, s, ...
_ [1]uint64 // padding
} }
// Name returns the name of the handle: binc
func (h *BincHandle) Name() string { return "binc" }
// SetBytesExt sets an extension
func (h *BincHandle) SetBytesExt(rt reflect.Type, tag uint64, ext BytesExt) (err error) { func (h *BincHandle) SetBytesExt(rt reflect.Type, tag uint64, ext BytesExt) (err error) {
return h.SetExt(rt, tag, &setExtWrapper{b: ext}) return h.SetExt(rt, tag, &extWrapper{ext, interfaceExtFailer{}})
} }
func (h *BincHandle) newEncDriver(e *Encoder) encDriver { func (h *BincHandle) newEncDriver(e *Encoder) encDriver {
return &bincEncDriver{e: e, w: e.w} return &bincEncDriver{e: e, h: h, w: e.w}
} }
func (h *BincHandle) newDecDriver(d *Decoder) decDriver { func (h *BincHandle) newDecDriver(d *Decoder) decDriver {
return &bincDecDriver{d: d, h: h, r: d.r, br: d.bytes} return &bincDecDriver{d: d, h: h, r: d.r, br: d.bytes}
} }
func (_ *BincHandle) IsBuiltinType(rt uintptr) bool {
return rt == timeTypId
}
func (e *bincEncDriver) reset() { func (e *bincEncDriver) reset() {
e.w = e.e.w e.w = e.e.w
e.s = 0 e.s = 0
e.c = 0
e.m = nil e.m = nil
} }
@ -942,5 +960,165 @@ func (d *bincDecDriver) reset() {
d.bd, d.bdRead, d.vd, d.vs = 0, false, 0, 0 d.bd, d.bdRead, d.vd, d.vs = 0, false, 0, 0
} }
// var timeDigits = [...]byte{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'}
// EncodeTime encodes a time.Time as a []byte, including
// information on the instant in time and UTC offset.
//
// Format Description
//
// A timestamp is composed of 3 components:
//
// - secs: signed integer representing seconds since unix epoch
// - nsces: unsigned integer representing fractional seconds as a
// nanosecond offset within secs, in the range 0 <= nsecs < 1e9
// - tz: signed integer representing timezone offset in minutes east of UTC,
// and a dst (daylight savings time) flag
//
// When encoding a timestamp, the first byte is the descriptor, which
// defines which components are encoded and how many bytes are used to
// encode secs and nsecs components. *If secs/nsecs is 0 or tz is UTC, it
// is not encoded in the byte array explicitly*.
//
// Descriptor 8 bits are of the form `A B C DDD EE`:
// A: Is secs component encoded? 1 = true
// B: Is nsecs component encoded? 1 = true
// C: Is tz component encoded? 1 = true
// DDD: Number of extra bytes for secs (range 0-7).
// If A = 1, secs encoded in DDD+1 bytes.
// If A = 0, secs is not encoded, and is assumed to be 0.
// If A = 1, then we need at least 1 byte to encode secs.
// DDD says the number of extra bytes beyond that 1.
// E.g. if DDD=0, then secs is represented in 1 byte.
// if DDD=2, then secs is represented in 3 bytes.
// EE: Number of extra bytes for nsecs (range 0-3).
// If B = 1, nsecs encoded in EE+1 bytes (similar to secs/DDD above)
//
// Following the descriptor bytes, subsequent bytes are:
//
// secs component encoded in `DDD + 1` bytes (if A == 1)
// nsecs component encoded in `EE + 1` bytes (if B == 1)
// tz component encoded in 2 bytes (if C == 1)
//
// secs and nsecs components are integers encoded in a BigEndian
// 2-complement encoding format.
//
// tz component is encoded as 2 bytes (16 bits). Most significant bit 15 to
// Least significant bit 0 are described below:
//
// Timezone offset has a range of -12:00 to +14:00 (ie -720 to +840 minutes).
// Bit 15 = have\_dst: set to 1 if we set the dst flag.
// Bit 14 = dst\_on: set to 1 if dst is in effect at the time, or 0 if not.
// Bits 13..0 = timezone offset in minutes. It is a signed integer in Big Endian format.
//
func bincEncodeTime(t time.Time) []byte {
//t := rv.Interface().(time.Time)
tsecs, tnsecs := t.Unix(), t.Nanosecond()
var (
bd byte
btmp [8]byte
bs [16]byte
i int = 1
)
l := t.Location()
if l == time.UTC {
l = nil
}
if tsecs != 0 {
bd = bd | 0x80
bigen.PutUint64(btmp[:], uint64(tsecs))
f := pruneSignExt(btmp[:], tsecs >= 0)
bd = bd | (byte(7-f) << 2)
copy(bs[i:], btmp[f:])
i = i + (8 - f)
}
if tnsecs != 0 {
bd = bd | 0x40
bigen.PutUint32(btmp[:4], uint32(tnsecs))
f := pruneSignExt(btmp[:4], true)
bd = bd | byte(3-f)
copy(bs[i:], btmp[f:4])
i = i + (4 - f)
}
if l != nil {
bd = bd | 0x20
// Note that Go Libs do not give access to dst flag.
_, zoneOffset := t.Zone()
//zoneName, zoneOffset := t.Zone()
zoneOffset /= 60
z := uint16(zoneOffset)
bigen.PutUint16(btmp[:2], z)
// clear dst flags
bs[i] = btmp[0] & 0x3f
bs[i+1] = btmp[1]
i = i + 2
}
bs[0] = bd
return bs[0:i]
}
// bincDecodeTime decodes a []byte into a time.Time.
func bincDecodeTime(bs []byte) (tt time.Time, err error) {
bd := bs[0]
var (
tsec int64
tnsec uint32
tz uint16
i byte = 1
i2 byte
n byte
)
if bd&(1<<7) != 0 {
var btmp [8]byte
n = ((bd >> 2) & 0x7) + 1
i2 = i + n
copy(btmp[8-n:], bs[i:i2])
//if first bit of bs[i] is set, then fill btmp[0..8-n] with 0xff (ie sign extend it)
if bs[i]&(1<<7) != 0 {
copy(btmp[0:8-n], bsAll0xff)
//for j,k := byte(0), 8-n; j < k; j++ { btmp[j] = 0xff }
}
i = i2
tsec = int64(bigen.Uint64(btmp[:]))
}
if bd&(1<<6) != 0 {
var btmp [4]byte
n = (bd & 0x3) + 1
i2 = i + n
copy(btmp[4-n:], bs[i:i2])
i = i2
tnsec = bigen.Uint32(btmp[:])
}
if bd&(1<<5) == 0 {
tt = time.Unix(tsec, int64(tnsec)).UTC()
return
}
// In stdlib time.Parse, when a date is parsed without a zone name, it uses "" as zone name.
// However, we need name here, so it can be shown when time is printed.
// Zone name is in form: UTC-08:00.
// Note that Go Libs do not give access to dst flag, so we ignore dst bits
i2 = i + 2
tz = bigen.Uint16(bs[i:i2])
// i = i2
// sign extend sign bit into top 2 MSB (which were dst bits):
if tz&(1<<13) == 0 { // positive
tz = tz & 0x3fff //clear 2 MSBs: dst bits
} else { // negative
tz = tz | 0xc000 //set 2 MSBs: dst bits
}
tzint := int16(tz)
if tzint == 0 {
tt = time.Unix(tsec, int64(tnsec)).UTC()
} else {
// For Go Time, do not use a descriptive timezone.
// It's unnecessary, and makes it harder to do a reflect.DeepEqual.
// The Offset already tells what the offset should be, if not on UTC and unknown zone name.
// var zoneName = timeLocUTCName(tzint)
tt = time.Unix(tsec, int64(tnsec)).In(time.FixedZone("", int(tzint)*60))
}
return
}
var _ decDriver = (*bincDecDriver)(nil) var _ decDriver = (*bincDecDriver)(nil)
var _ encDriver = (*bincEncDriver)(nil) var _ encDriver = (*bincEncDriver)(nil)

View File

@ -1,4 +1,4 @@
// Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved. // Copyright (c) 2012-2018 Ugorji Nwoke. All rights reserved.
// Use of this source code is governed by a MIT license found in the LICENSE file. // Use of this source code is governed by a MIT license found in the LICENSE file.
package codec package codec
@ -6,6 +6,7 @@ package codec
import ( import (
"math" "math"
"reflect" "reflect"
"time"
) )
const ( const (
@ -38,6 +39,8 @@ const (
cborBdBreak = 0xff cborBdBreak = 0xff
) )
// These define some in-stream descriptors for
// manual encoding e.g. when doing explicit indefinite-length
const ( const (
CborStreamBytes byte = 0x5f CborStreamBytes byte = 0x5f
CborStreamString = 0x7f CborStreamString = 0x7f
@ -67,6 +70,7 @@ type cborEncDriver struct {
w encWriter w encWriter
h *CborHandle h *CborHandle
x [8]byte x [8]byte
_ [3]uint64 // padding
} }
func (e *cborEncDriver) EncodeNil() { func (e *cborEncDriver) EncodeNil() {
@ -124,6 +128,24 @@ func (e *cborEncDriver) encLen(bd byte, length int) {
e.encUint(uint64(length), bd) e.encUint(uint64(length), bd)
} }
func (e *cborEncDriver) EncodeTime(t time.Time) {
if t.IsZero() {
e.EncodeNil()
} else if e.h.TimeRFC3339 {
e.encUint(0, cborBaseTag)
e.EncodeString(cUTF8, t.Format(time.RFC3339Nano))
} else {
e.encUint(1, cborBaseTag)
t = t.UTC().Round(time.Microsecond)
sec, nsec := t.Unix(), uint64(t.Nanosecond())
if nsec == 0 {
e.EncodeInt(sec)
} else {
e.EncodeFloat64(float64(sec) + float64(nsec)/1e9)
}
}
}
func (e *cborEncDriver) EncodeExt(rv interface{}, xtag uint64, ext Ext, en *Encoder) { func (e *cborEncDriver) EncodeExt(rv interface{}, xtag uint64, ext Ext, en *Encoder) {
e.encUint(uint64(xtag), cborBaseTag) e.encUint(uint64(xtag), cborBaseTag)
if v := ext.ConvertExt(rv); v == nil { if v := ext.ConvertExt(rv); v == nil {
@ -172,16 +194,14 @@ func (e *cborEncDriver) WriteArrayEnd() {
} }
} }
func (e *cborEncDriver) EncodeSymbol(v string) {
e.encStringBytesS(cborBaseString, v)
}
func (e *cborEncDriver) EncodeString(c charEncoding, v string) { func (e *cborEncDriver) EncodeString(c charEncoding, v string) {
e.encStringBytesS(cborBaseString, v) e.encStringBytesS(cborBaseString, v)
} }
func (e *cborEncDriver) EncodeStringBytes(c charEncoding, v []byte) { func (e *cborEncDriver) EncodeStringBytes(c charEncoding, v []byte) {
if c == c_RAW { if v == nil {
e.EncodeNil()
} else if c == cRAW {
e.encStringBytesS(cborBaseBytes, stringView(v)) e.encStringBytesS(cborBaseBytes, stringView(v))
} else { } else {
e.encStringBytesS(cborBaseString, stringView(v)) e.encStringBytesS(cborBaseString, stringView(v))
@ -223,16 +243,17 @@ func (e *cborEncDriver) encStringBytesS(bb byte, v string) {
// ---------------------- // ----------------------
type cborDecDriver struct { type cborDecDriver struct {
d *Decoder d *Decoder
h *CborHandle h *CborHandle
r decReader r decReader
b [scratchByteArrayLen]byte // b [scratchByteArrayLen]byte
br bool // bytes reader br bool // bytes reader
bdRead bool bdRead bool
bd byte bd byte
noBuiltInTypes noBuiltInTypes
// decNoSeparator // decNoSeparator
decDriverNoopContainerReader decDriverNoopContainerReader
_ [3]uint64 // padding
} }
func (d *cborDecDriver) readNextBd() { func (d *cborDecDriver) readNextBd() {
@ -261,9 +282,10 @@ func (d *cborDecDriver) ContainerType() (vt valueType) {
return valueTypeArray return valueTypeArray
} else if d.bd == cborBdIndefiniteMap || (d.bd >= cborBaseMap && d.bd < cborBaseTag) { } else if d.bd == cborBdIndefiniteMap || (d.bd >= cborBaseMap && d.bd < cborBaseTag) {
return valueTypeMap return valueTypeMap
} else {
// d.d.errorf("isContainerType: unsupported parameter: %v", vt)
} }
// else {
// d.d.errorf("isContainerType: unsupported parameter: %v", vt)
// }
return valueTypeUnset return valueTypeUnset
} }
@ -326,46 +348,30 @@ func (d *cborDecDriver) decCheckInteger() (neg bool) {
return return
} }
func (d *cborDecDriver) DecodeInt(bitsize uint8) (i int64) { func (d *cborDecDriver) DecodeInt64() (i int64) {
neg := d.decCheckInteger() neg := d.decCheckInteger()
ui := d.decUint() ui := d.decUint()
// check if this number can be converted to an int without overflow // check if this number can be converted to an int without overflow
var overflow bool
if neg { if neg {
if i, overflow = chkOvf.SignedInt(ui + 1); overflow { i = -(chkOvf.SignedIntV(ui + 1))
d.d.errorf("cbor: overflow converting %v to signed integer", ui+1)
return
}
i = -i
} else { } else {
if i, overflow = chkOvf.SignedInt(ui); overflow { i = chkOvf.SignedIntV(ui)
d.d.errorf("cbor: overflow converting %v to signed integer", ui)
return
}
}
if chkOvf.Int(i, bitsize) {
d.d.errorf("cbor: overflow integer: %v", i)
return
} }
d.bdRead = false d.bdRead = false
return return
} }
func (d *cborDecDriver) DecodeUint(bitsize uint8) (ui uint64) { func (d *cborDecDriver) DecodeUint64() (ui uint64) {
if d.decCheckInteger() { if d.decCheckInteger() {
d.d.errorf("Assigning negative signed value to unsigned type") d.d.errorf("Assigning negative signed value to unsigned type")
return return
} }
ui = d.decUint() ui = d.decUint()
if chkOvf.Uint(ui, bitsize) {
d.d.errorf("cbor: overflow integer: %v", ui)
return
}
d.bdRead = false d.bdRead = false
return return
} }
func (d *cborDecDriver) DecodeFloat(chkOverflow32 bool) (f float64) { func (d *cborDecDriver) DecodeFloat64() (f float64) {
if !d.bdRead { if !d.bdRead {
d.readNextBd() d.readNextBd()
} }
@ -376,15 +382,11 @@ func (d *cborDecDriver) DecodeFloat(chkOverflow32 bool) (f float64) {
} else if bd == cborBdFloat64 { } else if bd == cborBdFloat64 {
f = math.Float64frombits(bigen.Uint64(d.r.readx(8))) f = math.Float64frombits(bigen.Uint64(d.r.readx(8)))
} else if bd >= cborBaseUint && bd < cborBaseBytes { } else if bd >= cborBaseUint && bd < cborBaseBytes {
f = float64(d.DecodeInt(64)) f = float64(d.DecodeInt64())
} else { } else {
d.d.errorf("Float only valid from float16/32/64: Invalid descriptor: %v", bd) d.d.errorf("Float only valid from float16/32/64: Invalid descriptor: %v", bd)
return return
} }
if chkOverflow32 && chkOvf.Float32(f) {
d.d.errorf("cbor: float32 overflow: %v", f)
return
}
d.bdRead = false d.bdRead = false
return return
} }
@ -438,7 +440,8 @@ func (d *cborDecDriver) decAppendIndefiniteBytes(bs []byte) []byte {
break break
} }
if major := d.bd >> 5; major != cborMajorBytes && major != cborMajorText { if major := d.bd >> 5; major != cborMajorBytes && major != cborMajorText {
d.d.errorf("cbor: expect bytes or string major type in indefinite string/bytes; got: %v, byte: %v", major, d.bd) d.d.errorf("expect bytes/string major type in indefinite string/bytes;"+
" got: %v, byte: %v", major, d.bd)
return nil return nil
} }
n := d.decLen() n := d.decLen()
@ -470,28 +473,82 @@ func (d *cborDecDriver) DecodeBytes(bs []byte, zerocopy bool) (bsOut []byte) {
if d.bd == cborBdIndefiniteBytes || d.bd == cborBdIndefiniteString { if d.bd == cborBdIndefiniteBytes || d.bd == cborBdIndefiniteString {
d.bdRead = false d.bdRead = false
if bs == nil { if bs == nil {
if zerocopy {
return d.decAppendIndefiniteBytes(d.d.b[:0])
}
return d.decAppendIndefiniteBytes(zeroByteSlice) return d.decAppendIndefiniteBytes(zeroByteSlice)
} }
return d.decAppendIndefiniteBytes(bs[:0]) return d.decAppendIndefiniteBytes(bs[:0])
} }
// check if an "array" of uint8's (see ContainerType for how to infer if an array)
if d.bd == cborBdIndefiniteArray || (d.bd >= cborBaseArray && d.bd < cborBaseMap) {
bsOut, _ = fastpathTV.DecSliceUint8V(bs, true, d.d)
return
}
clen := d.decLen() clen := d.decLen()
d.bdRead = false d.bdRead = false
if zerocopy { if zerocopy {
if d.br { if d.br {
return d.r.readx(clen) return d.r.readx(clen)
} else if len(bs) == 0 { } else if len(bs) == 0 {
bs = d.b[:] bs = d.d.b[:]
} }
} }
return decByteSlice(d.r, clen, d.d.h.MaxInitLen, bs) return decByteSlice(d.r, clen, d.h.MaxInitLen, bs)
} }
func (d *cborDecDriver) DecodeString() (s string) { func (d *cborDecDriver) DecodeString() (s string) {
return string(d.DecodeBytes(d.b[:], true)) return string(d.DecodeBytes(d.d.b[:], true))
} }
func (d *cborDecDriver) DecodeStringAsBytes() (s []byte) { func (d *cborDecDriver) DecodeStringAsBytes() (s []byte) {
return d.DecodeBytes(d.b[:], true) return d.DecodeBytes(d.d.b[:], true)
}
func (d *cborDecDriver) DecodeTime() (t time.Time) {
if !d.bdRead {
d.readNextBd()
}
if d.bd == cborBdNil || d.bd == cborBdUndefined {
d.bdRead = false
return
}
xtag := d.decUint()
d.bdRead = false
return d.decodeTime(xtag)
}
func (d *cborDecDriver) decodeTime(xtag uint64) (t time.Time) {
if !d.bdRead {
d.readNextBd()
}
switch xtag {
case 0:
var err error
if t, err = time.Parse(time.RFC3339, stringView(d.DecodeStringAsBytes())); err != nil {
d.d.errorv(err)
}
case 1:
// decode an int64 or a float, and infer time.Time from there.
// for floats, round to microseconds, as that is what is guaranteed to fit well.
switch {
case d.bd == cborBdFloat16, d.bd == cborBdFloat32:
f1, f2 := math.Modf(d.DecodeFloat64())
t = time.Unix(int64(f1), int64(f2*1e9))
case d.bd == cborBdFloat64:
f1, f2 := math.Modf(d.DecodeFloat64())
t = time.Unix(int64(f1), int64(f2*1e9))
case d.bd >= cborBaseUint && d.bd < cborBaseNegInt,
d.bd >= cborBaseNegInt && d.bd < cborBaseBytes:
t = time.Unix(d.DecodeInt64(), 0)
default:
d.d.errorf("time.Time can only be decoded from a number (or RFC3339 string)")
}
default:
d.d.errorf("invalid tag for time.Time - expecting 0 or 1, got 0x%x", xtag)
}
t = t.UTC().Round(time.Microsecond)
return
} }
func (d *cborDecDriver) DecodeExt(rv interface{}, xtag uint64, ext Ext) (realxtag uint64) { func (d *cborDecDriver) DecodeExt(rv interface{}, xtag uint64, ext Ext) (realxtag uint64) {
@ -534,12 +591,9 @@ func (d *cborDecDriver) DecodeNaked() {
case cborBdTrue: case cborBdTrue:
n.v = valueTypeBool n.v = valueTypeBool
n.b = true n.b = true
case cborBdFloat16, cborBdFloat32: case cborBdFloat16, cborBdFloat32, cborBdFloat64:
n.v = valueTypeFloat n.v = valueTypeFloat
n.f = d.DecodeFloat(true) n.f = d.DecodeFloat64()
case cborBdFloat64:
n.v = valueTypeFloat
n.f = d.DecodeFloat(false)
case cborBdIndefiniteBytes: case cborBdIndefiniteBytes:
n.v = valueTypeBytes n.v = valueTypeBytes
n.l = d.DecodeBytes(nil, false) n.l = d.DecodeBytes(nil, false)
@ -557,14 +611,14 @@ func (d *cborDecDriver) DecodeNaked() {
case d.bd >= cborBaseUint && d.bd < cborBaseNegInt: case d.bd >= cborBaseUint && d.bd < cborBaseNegInt:
if d.h.SignedInteger { if d.h.SignedInteger {
n.v = valueTypeInt n.v = valueTypeInt
n.i = d.DecodeInt(64) n.i = d.DecodeInt64()
} else { } else {
n.v = valueTypeUint n.v = valueTypeUint
n.u = d.DecodeUint(64) n.u = d.DecodeUint64()
} }
case d.bd >= cborBaseNegInt && d.bd < cborBaseBytes: case d.bd >= cborBaseNegInt && d.bd < cborBaseBytes:
n.v = valueTypeInt n.v = valueTypeInt
n.i = d.DecodeInt(64) n.i = d.DecodeInt64()
case d.bd >= cborBaseBytes && d.bd < cborBaseString: case d.bd >= cborBaseBytes && d.bd < cborBaseString:
n.v = valueTypeBytes n.v = valueTypeBytes
n.l = d.DecodeBytes(nil, false) n.l = d.DecodeBytes(nil, false)
@ -581,6 +635,11 @@ func (d *cborDecDriver) DecodeNaked() {
n.v = valueTypeExt n.v = valueTypeExt
n.u = d.decUint() n.u = d.decUint()
n.l = nil n.l = nil
if n.u == 0 || n.u == 1 {
d.bdRead = false
n.v = valueTypeTime
n.t = d.decodeTime(n.u)
}
// d.bdRead = false // d.bdRead = false
// d.d.decode(&re.Value) // handled by decode itself. // d.d.decode(&re.Value) // handled by decode itself.
// decodeFurther = true // decodeFurther = true
@ -611,23 +670,8 @@ func (d *cborDecDriver) DecodeNaked() {
// //
// None of the optional extensions (with tags) defined in the spec are supported out-of-the-box. // None of the optional extensions (with tags) defined in the spec are supported out-of-the-box.
// Users can implement them as needed (using SetExt), including spec-documented ones: // Users can implement them as needed (using SetExt), including spec-documented ones:
// - timestamp, BigNum, BigFloat, Decimals, Encoded Text (e.g. URL, regexp, base64, MIME Message), etc. // - timestamp, BigNum, BigFloat, Decimals,
// // - Encoded Text (e.g. URL, regexp, base64, MIME Message), etc.
// To encode with indefinite lengths (streaming), users will use
// (Must)Encode methods of *Encoder, along with writing CborStreamXXX constants.
//
// For example, to encode "one-byte" as an indefinite length string:
// var buf bytes.Buffer
// e := NewEncoder(&buf, new(CborHandle))
// buf.WriteByte(CborStreamString)
// e.MustEncode("one-")
// e.MustEncode("byte")
// buf.WriteByte(CborStreamBreak)
// encodedBytes := buf.Bytes()
// var vv interface{}
// NewDecoderBytes(buf.Bytes(), new(CborHandle)).MustDecode(&vv)
// // Now, vv contains the same string "one-byte"
//
type CborHandle struct { type CborHandle struct {
binaryEncodingType binaryEncodingType
noElemSeparators noElemSeparators
@ -635,10 +679,20 @@ type CborHandle struct {
// IndefiniteLength=true, means that we encode using indefinitelength // IndefiniteLength=true, means that we encode using indefinitelength
IndefiniteLength bool IndefiniteLength bool
// TimeRFC3339 says to encode time.Time using RFC3339 format.
// If unset, we encode time.Time using seconds past epoch.
TimeRFC3339 bool
_ [1]uint64 // padding
} }
// Name returns the name of the handle: cbor
func (h *CborHandle) Name() string { return "cbor" }
// SetInterfaceExt sets an extension
func (h *CborHandle) SetInterfaceExt(rt reflect.Type, tag uint64, ext InterfaceExt) (err error) { func (h *CborHandle) SetInterfaceExt(rt reflect.Type, tag uint64, ext InterfaceExt) (err error) {
return h.SetExt(rt, tag, &setExtWrapper{i: ext}) return h.SetExt(rt, tag, &extWrapper{bytesExtFailer{}, ext})
} }
func (h *CborHandle) newEncDriver(e *Encoder) encDriver { func (h *CborHandle) newEncDriver(e *Encoder) encDriver {

View File

@ -1,4 +1,4 @@
// Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved. // Copyright (c) 2012-2018 Ugorji Nwoke. All rights reserved.
// Use of this source code is governed by a MIT license found in the LICENSE file. // Use of this source code is governed by a MIT license found in the LICENSE file.
package codec package codec

View File

@ -1,28 +1,14 @@
// Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved. // Copyright (c) 2012-2018 Ugorji Nwoke. All rights reserved.
// Use of this source code is governed by a MIT license found in the LICENSE file. // Use of this source code is governed by a MIT license found in the LICENSE file.
package codec package codec
// Test works by using a slice of interfaces.
// It can test for encoding/decoding into/from a nil interface{}
// or passing the object to encode/decode into.
//
// There are basically 2 main tests here.
// First test internally encodes and decodes things and verifies that
// the artifact was as expected.
// Second test will use python msgpack to create a bunch of golden files,
// read those files, and compare them to what it should be. It then
// writes those files back out and compares the byte streams.
//
// Taken together, the tests are pretty extensive.
//
// The following manual tests must be done:
// - TestCodecUnderlyingType
import ( import (
"bufio"
"bytes" "bytes"
"encoding/gob" "encoding/gob"
"fmt" "fmt"
"io"
"io/ioutil" "io/ioutil"
"math" "math"
"math/rand" "math/rand"
@ -49,10 +35,34 @@ func init() {
// ) // )
} }
type testCustomStringT string
// make this a mapbyslice // make this a mapbyslice
type testMbsT []interface{} type testMbsT []interface{}
func (_ testMbsT) MapBySlice() {} func (testMbsT) MapBySlice() {}
type testMbsCustStrT []testCustomStringT
func (testMbsCustStrT) MapBySlice() {}
type testIntfMapI interface {
GetIntfMapV() string
}
type testIntfMapT1 struct {
IntfMapV string
}
func (x *testIntfMapT1) GetIntfMapV() string { return x.IntfMapV }
type testIntfMapT2 struct {
IntfMapV string
}
func (x testIntfMapT2) GetIntfMapV() string { return x.IntfMapV }
// ----
type testVerifyFlag uint8 type testVerifyFlag uint8
@ -102,6 +112,9 @@ var (
testRpcInt = new(TestRpcInt) testRpcInt = new(TestRpcInt)
) )
var wrapInt64Typ = reflect.TypeOf(wrapInt64(0))
var wrapBytesTyp = reflect.TypeOf(wrapBytes(nil))
func testByteBuf(in []byte) *bytes.Buffer { func testByteBuf(in []byte) *bytes.Buffer {
return bytes.NewBuffer(in) return bytes.NewBuffer(in)
} }
@ -179,23 +192,27 @@ type TestRawValue struct {
I int I int
} }
// ----
type testUnixNanoTimeExt struct { type testUnixNanoTimeExt struct {
// keep timestamp here, so that do not incur interface-conversion costs // keep timestamp here, so that do not incur interface-conversion costs
ts int64 // ts int64
} }
// func (x *testUnixNanoTimeExt) WriteExt(interface{}) []byte { panic("unsupported") } func (x *testUnixNanoTimeExt) WriteExt(v interface{}) []byte {
// func (x *testUnixNanoTimeExt) ReadExt(interface{}, []byte) { panic("unsupported") } v2 := v.(*time.Time)
bs := make([]byte, 8)
bigen.PutUint64(bs, uint64(v2.UnixNano()))
return bs
}
func (x *testUnixNanoTimeExt) ReadExt(v interface{}, bs []byte) {
v2 := v.(*time.Time)
ui := bigen.Uint64(bs)
*v2 = time.Unix(0, int64(ui)).UTC()
}
func (x *testUnixNanoTimeExt) ConvertExt(v interface{}) interface{} { func (x *testUnixNanoTimeExt) ConvertExt(v interface{}) interface{} {
switch v2 := v.(type) { v2 := v.(*time.Time) // structs are encoded by passing the ptr
case time.Time: return v2.UTC().UnixNano()
x.ts = v2.UTC().UnixNano()
case *time.Time:
x.ts = v2.UTC().UnixNano()
default:
panic(fmt.Sprintf("unsupported format for time conversion: expecting time.Time; got %T", v))
}
return &x.ts
} }
func (x *testUnixNanoTimeExt) UpdateExt(dest interface{}, v interface{}) { func (x *testUnixNanoTimeExt) UpdateExt(dest interface{}, v interface{}) {
@ -203,12 +220,8 @@ func (x *testUnixNanoTimeExt) UpdateExt(dest interface{}, v interface{}) {
switch v2 := v.(type) { switch v2 := v.(type) {
case int64: case int64:
*tt = time.Unix(0, v2).UTC() *tt = time.Unix(0, v2).UTC()
case *int64:
*tt = time.Unix(0, *v2).UTC()
case uint64: case uint64:
*tt = time.Unix(0, int64(v2)).UTC() *tt = time.Unix(0, int64(v2)).UTC()
case *uint64:
*tt = time.Unix(0, int64(*v2)).UTC()
//case float64: //case float64:
//case string: //case string:
default: default:
@ -216,6 +229,91 @@ func (x *testUnixNanoTimeExt) UpdateExt(dest interface{}, v interface{}) {
} }
} }
// ----
type wrapInt64Ext int64
func (x *wrapInt64Ext) WriteExt(v interface{}) []byte {
v2 := uint64(int64(v.(wrapInt64)))
bs := make([]byte, 8)
bigen.PutUint64(bs, v2)
return bs
}
func (x *wrapInt64Ext) ReadExt(v interface{}, bs []byte) {
v2 := v.(*wrapInt64)
ui := bigen.Uint64(bs)
*v2 = wrapInt64(int64(ui))
}
func (x *wrapInt64Ext) ConvertExt(v interface{}) interface{} {
return int64(v.(wrapInt64))
}
func (x *wrapInt64Ext) UpdateExt(dest interface{}, v interface{}) {
v2 := dest.(*wrapInt64)
*v2 = wrapInt64(v.(int64))
}
// ----
type wrapBytesExt struct{}
func (x *wrapBytesExt) WriteExt(v interface{}) []byte {
return ([]byte)(v.(wrapBytes))
}
func (x *wrapBytesExt) ReadExt(v interface{}, bs []byte) {
v2 := v.(*wrapBytes)
*v2 = wrapBytes(bs)
}
func (x *wrapBytesExt) ConvertExt(v interface{}) interface{} {
return ([]byte)(v.(wrapBytes))
}
func (x *wrapBytesExt) UpdateExt(dest interface{}, v interface{}) {
v2 := dest.(*wrapBytes)
// some formats (e.g. json) cannot nakedly determine []byte from string, so expect both
switch v3 := v.(type) {
case []byte:
*v2 = wrapBytes(v3)
case string:
*v2 = wrapBytes([]byte(v3))
default:
panic("UpdateExt for wrapBytesExt expects string or []byte")
}
// *v2 = wrapBytes(v.([]byte))
}
// ----
// timeExt is an extension handler for time.Time, that uses binc model for encoding/decoding time.
// we used binc model, as that is the only custom time representation that we designed ourselves.
type timeExt struct{}
func (x timeExt) WriteExt(v interface{}) (bs []byte) {
switch v2 := v.(type) {
case time.Time:
bs = bincEncodeTime(v2)
case *time.Time:
bs = bincEncodeTime(*v2)
default:
panic(fmt.Errorf("unsupported format for time conversion: expecting time.Time; got %T", v2))
}
return
}
func (x timeExt) ReadExt(v interface{}, bs []byte) {
tt, err := bincDecodeTime(bs)
if err != nil {
panic(err)
}
*(v.(*time.Time)) = tt
}
func (x timeExt) ConvertExt(v interface{}) interface{} {
return x.WriteExt(v)
}
func (x timeExt) UpdateExt(v interface{}, src interface{}) {
x.ReadExt(v, src.([]byte))
}
// ----
func testCodecEncode(ts interface{}, bsIn []byte, func testCodecEncode(ts interface{}, bsIn []byte,
fn func([]byte) *bytes.Buffer, h Handle) (bs []byte, err error) { fn func([]byte) *bytes.Buffer, h Handle) (bs []byte, err error) {
return sTestCodecEncode(ts, bsIn, fn, h, h.getBasicHandle()) return sTestCodecEncode(ts, bsIn, fn, h, h.getBasicHandle())
@ -258,8 +356,8 @@ func testInit() {
// pre-fill them first // pre-fill them first
bh.EncodeOptions = testEncodeOptions bh.EncodeOptions = testEncodeOptions
bh.DecodeOptions = testDecodeOptions bh.DecodeOptions = testDecodeOptions
// bh.InterfaceReset = true // TODO: remove // bh.InterfaceReset = true
// bh.PreferArrayOverSlice = true // TODO: remove // bh.PreferArrayOverSlice = true
// modify from flag'ish things // modify from flag'ish things
bh.InternString = testInternStr bh.InternString = testInternStr
bh.Canonical = testCanonical bh.Canonical = testCanonical
@ -270,31 +368,59 @@ func testInit() {
testMsgpackH.RawToString = true testMsgpackH.RawToString = true
// testMsgpackH.AddExt(byteSliceTyp, 0, testMsgpackH.BinaryEncodeExt, testMsgpackH.BinaryDecodeExt) var tTimeExt timeExt
// testMsgpackH.AddExt(timeTyp, 1, testMsgpackH.TimeEncodeExt, testMsgpackH.TimeDecodeExt) var tBytesExt wrapBytesExt
var tI64Ext wrapInt64Ext
// add extensions for msgpack, simple for time.Time, so we can encode/decode same way. // create legacy functions suitable for deprecated AddExt functionality,
// use different flavors of XXXExt calls, including deprecated ones. // and use on some places for testSimpleH e.g. for time.Time and wrapInt64
// NOTE:
// DO NOT set extensions for JsonH, so we can test json(M|Unm)arshal support.
var ( var (
timeExtEncFn = func(rv reflect.Value) (bs []byte, err error) { myExtEncFn = func(x BytesExt, rv reflect.Value) (bs []byte, err error) {
defer panicToErr(&err) defer panicToErr(errstrDecoratorDef{}, &err)
bs = timeExt{}.WriteExt(rv.Interface()) bs = x.WriteExt(rv.Interface())
return return
} }
timeExtDecFn = func(rv reflect.Value, bs []byte) (err error) { myExtDecFn = func(x BytesExt, rv reflect.Value, bs []byte) (err error) {
defer panicToErr(&err) defer panicToErr(errstrDecoratorDef{}, &err)
timeExt{}.ReadExt(rv.Interface(), bs) x.ReadExt(rv.Interface(), bs)
return return
} }
timeExtEncFn = func(rv reflect.Value) (bs []byte, err error) { return myExtEncFn(tTimeExt, rv) }
timeExtDecFn = func(rv reflect.Value, bs []byte) (err error) { return myExtDecFn(tTimeExt, rv, bs) }
wrapInt64ExtEncFn = func(rv reflect.Value) (bs []byte, err error) { return myExtEncFn(&tI64Ext, rv) }
wrapInt64ExtDecFn = func(rv reflect.Value, bs []byte) (err error) { return myExtDecFn(&tI64Ext, rv, bs) }
) )
testSimpleH.AddExt(timeTyp, 1, timeExtEncFn, timeExtDecFn)
chkErr := func(err error) {
if err != nil {
panic(err)
}
}
// time.Time is a native type, so extensions will have no effect.
// However, we add these here to ensure nothing happens.
chkErr(testSimpleH.AddExt(timeTyp, 1, timeExtEncFn, timeExtDecFn))
// testBincH.SetBytesExt(timeTyp, 1, timeExt{}) // time is builtin for binc // testBincH.SetBytesExt(timeTyp, 1, timeExt{}) // time is builtin for binc
testMsgpackH.SetBytesExt(timeTyp, 1, timeExt{}) chkErr(testMsgpackH.SetBytesExt(timeTyp, 1, timeExt{}))
testCborH.SetInterfaceExt(timeTyp, 1, &testUnixNanoTimeExt{}) chkErr(testCborH.SetInterfaceExt(timeTyp, 1, &testUnixNanoTimeExt{}))
// testJsonH.SetInterfaceExt(timeTyp, 1, &testUnixNanoTimeExt{}) // testJsonH.SetInterfaceExt(timeTyp, 1, &testUnixNanoTimeExt{})
// Now, add extensions for the type wrapInt64 and wrapBytes,
// so we can execute the Encode/Decode Ext paths.
chkErr(testSimpleH.SetBytesExt(wrapBytesTyp, 32, &tBytesExt))
chkErr(testMsgpackH.SetBytesExt(wrapBytesTyp, 32, &tBytesExt))
chkErr(testBincH.SetBytesExt(wrapBytesTyp, 32, &tBytesExt))
chkErr(testJsonH.SetInterfaceExt(wrapBytesTyp, 32, &tBytesExt))
chkErr(testCborH.SetInterfaceExt(wrapBytesTyp, 32, &tBytesExt))
chkErr(testSimpleH.AddExt(wrapInt64Typ, 16, wrapInt64ExtEncFn, wrapInt64ExtDecFn))
// chkErr(testSimpleH.SetBytesExt(wrapInt64Typ, 16, &tI64Ext))
chkErr(testMsgpackH.SetBytesExt(wrapInt64Typ, 16, &tI64Ext))
chkErr(testBincH.SetBytesExt(wrapInt64Typ, 16, &tI64Ext))
chkErr(testJsonH.SetInterfaceExt(wrapInt64Typ, 16, &tI64Ext))
chkErr(testCborH.SetInterfaceExt(wrapInt64Typ, 16, &tI64Ext))
// primitives MUST be an even number, so it can be used as a mapBySlice also. // primitives MUST be an even number, so it can be used as a mapBySlice also.
primitives := []interface{}{ primitives := []interface{}{
int8(-8), int8(-8),
@ -427,6 +553,8 @@ func testTableVerify(f testVerifyFlag, h Handle) (av []interface{}) {
av[i] = testVerifyVal(v, f, h) av[i] = testVerifyVal(v, f, h)
case map[interface{}]interface{}: case map[interface{}]interface{}:
av[i] = testVerifyVal(v, f, h) av[i] = testVerifyVal(v, f, h)
case time.Time:
av[i] = testVerifyVal(v, f, h)
default: default:
av[i] = v av[i] = v
} }
@ -455,6 +583,7 @@ func testVerifyVal(v interface{}, f testVerifyFlag, h Handle) (v2 interface{}) {
// - all positive integers are unsigned 64-bit ints // - all positive integers are unsigned 64-bit ints
// - all floats are float64 // - all floats are float64
_, isMsgp := h.(*MsgpackHandle) _, isMsgp := h.(*MsgpackHandle)
_, isCbor := h.(*CborHandle)
switch iv := v.(type) { switch iv := v.(type) {
case int8: case int8:
v2 = testVerifyValInt(int64(iv), isMsgp) v2 = testVerifyValInt(int64(iv), isMsgp)
@ -545,6 +674,11 @@ func testVerifyVal(v interface{}, f testVerifyFlag, h Handle) (v2 interface{}) {
} else { } else {
v2 = int64(iv2) v2 = int64(iv2)
} }
case isMsgp:
v2 = iv.UTC()
case isCbor:
// fmt.Printf("%%%% cbor verifier\n")
v2 = iv.UTC().Round(time.Microsecond)
default: default:
v2 = v v2 = v
} }
@ -584,6 +718,17 @@ func testDeepEqualErr(v1, v2 interface{}, t *testing.T, name string) {
} }
} }
func testReadWriteCloser(c io.ReadWriteCloser) io.ReadWriteCloser {
if testRpcBufsize <= 0 && rand.Int63()%2 == 0 {
return c
}
return struct {
io.Closer
*bufio.Reader
*bufio.Writer
}{c, bufio.NewReaderSize(c, testRpcBufsize), bufio.NewWriterSize(c, testRpcBufsize)}
}
// doTestCodecTableOne allows us test for different variations based on arguments passed. // doTestCodecTableOne allows us test for different variations based on arguments passed.
func doTestCodecTableOne(t *testing.T, testNil bool, h Handle, func doTestCodecTableOne(t *testing.T, testNil bool, h Handle,
vs []interface{}, vsVerify []interface{}) { vs []interface{}, vsVerify []interface{}) {
@ -623,7 +768,7 @@ func doTestCodecTableOne(t *testing.T, testNil bool, h Handle,
} }
} }
logT(t, " v1 returned: %T, %#v", v1, v1) logT(t, " v1 returned: %T, %v %#v", v1, v1, v1)
// if v1 != nil { // if v1 != nil {
// logT(t, " v1 returned: %T, %#v", v1, v1) // logT(t, " v1 returned: %T, %#v", v1, v1)
// //we always indirect, because ptr to typed value may be passed (if not testNil) // //we always indirect, because ptr to typed value may be passed (if not testNil)
@ -644,8 +789,8 @@ func doTestCodecTableOne(t *testing.T, testNil bool, h Handle,
// logT(t, "-------- Before and After marshal do not match: Error: %v"+ // logT(t, "-------- Before and After marshal do not match: Error: %v"+
// " ====> GOLDEN: (%T) %#v, DECODED: (%T) %#v\n", err, v0check, v0check, v1, v1) // " ====> GOLDEN: (%T) %#v, DECODED: (%T) %#v\n", err, v0check, v0check, v1, v1)
logT(t, "-------- FAIL: Before and After marshal do not match: Error: %v", err) logT(t, "-------- FAIL: Before and After marshal do not match: Error: %v", err)
logT(t, " ....... GOLDEN: (%T) %#v", v0check, v0check) logT(t, " ....... GOLDEN: (%T) %v %#v", v0check, v0check, v0check)
logT(t, " ....... DECODED: (%T) %#v", v1, v1) logT(t, " ....... DECODED: (%T) %v %#v", v1, v1, v1)
failT(t) failT(t)
} }
} }
@ -713,7 +858,7 @@ func testCodecMiscOne(t *testing.T, h Handle) {
// logT(t, "------- Expecting error because we cannot unmarshal to int32 nil ptr") // logT(t, "------- Expecting error because we cannot unmarshal to int32 nil ptr")
// failT(t) // failT(t)
// } // }
var i2 int32 = 0 var i2 int32
testUnmarshalErr(&i2, b, h, t, "int32-ptr") testUnmarshalErr(&i2, b, h, t, "int32-ptr")
if i2 != int32(32) { if i2 != int32(32) {
logT(t, "------- didn't unmarshal to 32: Received: %d", i2) logT(t, "------- didn't unmarshal to 32: Received: %d", i2)
@ -814,6 +959,19 @@ func testCodecMiscOne(t *testing.T, h Handle) {
var ya = ystruct{} var ya = ystruct{}
testUnmarshalErr(&ya, []byte{0x91, 0x90}, h, t, "ya") testUnmarshalErr(&ya, []byte{0x91, 0x90}, h, t, "ya")
} }
var tt1, tt2 time.Time
tt2 = time.Now()
bs = testMarshalErr(tt1, h, t, "zero-time-enc")
testUnmarshalErr(&tt2, bs, h, t, "zero-time-dec")
testDeepEqualErr(tt1, tt2, t, "zero-time-eq")
// test encoding a slice of byte (but not []byte) and decoding into a []byte
var sw = []wrapUint8{'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J'}
var bw []byte // ("ABCDEFGHIJ")
bs = testMarshalErr(sw, h, t, "wrap-bytes-enc")
testUnmarshalErr(&bw, bs, h, t, "wrap-bytes-dec")
testDeepEqualErr(bw, []byte("ABCDEFGHIJ"), t, "wrap-bytes-eq")
} }
func testCodecEmbeddedPointer(t *testing.T, h Handle) { func testCodecEmbeddedPointer(t *testing.T, h Handle) {
@ -1001,7 +1159,7 @@ func testCodecRpcOne(t *testing.T, rr Rpc, h Handle, doRequest bool, exitSleepMs
// opts.MapType = mapStrIntfTyp // opts.MapType = mapStrIntfTyp
// opts.RawToString = false // opts.RawToString = false
serverExitChan := make(chan bool, 1) serverExitChan := make(chan bool, 1)
var serverExitFlag uint64 = 0 var serverExitFlag uint64
serverFn := func() { serverFn := func() {
for { for {
conn1, err1 := ln.Accept() conn1, err1 := ln.Accept()
@ -1015,7 +1173,7 @@ func testCodecRpcOne(t *testing.T, rr Rpc, h Handle, doRequest bool, exitSleepMs
return // exit serverFn goroutine return // exit serverFn goroutine
} }
if err1 == nil { if err1 == nil {
var sc rpc.ServerCodec = rr.ServerCodec(conn1, h) sc := rr.ServerCodec(testReadWriteCloser(conn1), h)
srv.ServeCodec(sc) srv.ServeCodec(sc)
} }
} }
@ -1066,7 +1224,7 @@ func testCodecRpcOne(t *testing.T, rr Rpc, h Handle, doRequest bool, exitSleepMs
} }
if doRequest { if doRequest {
bs := connFn() bs := connFn()
cc := rr.ClientCodec(bs, h) cc := rr.ClientCodec(testReadWriteCloser(bs), h)
clientFn(cc) clientFn(cc)
} }
if exitSleepMs != 0 { if exitSleepMs != 0 {
@ -1476,7 +1634,7 @@ func doTestMsgpackRpcSpecGoClientToPythonSvc(t *testing.T) {
bs, err2 = net.Dial("tcp", ":"+openPort) bs, err2 = net.Dial("tcp", ":"+openPort)
} }
checkErrT(t, err2) checkErrT(t, err2)
cc := MsgpackSpecRpc.ClientCodec(bs, testMsgpackH) cc := MsgpackSpecRpc.ClientCodec(testReadWriteCloser(bs), testMsgpackH)
cl := rpc.NewClientWithCodec(cc) cl := rpc.NewClientWithCodec(cc)
defer cl.Close() defer cl.Close()
var rstr string var rstr string
@ -1520,39 +1678,75 @@ func doTestSwallowAndZero(t *testing.T, h Handle) {
logT(t, "swallow didn't consume all encoded bytes: %v out of %v", d1.r.numread(), len(b1)) logT(t, "swallow didn't consume all encoded bytes: %v out of %v", d1.r.numread(), len(b1))
failT(t) failT(t)
} }
d1.setZero(v1) setZero(v1)
testDeepEqualErr(v1, &TestStrucFlex{}, t, "filled-and-zeroed") testDeepEqualErr(v1, &TestStrucFlex{}, t, "filled-and-zeroed")
} }
func doTestRawExt(t *testing.T, h Handle) { func doTestRawExt(t *testing.T, h Handle) {
testOnce.Do(testInitAll) testOnce.Do(testInitAll)
// return // TODO: need to fix this ...
var b []byte var b []byte
var v interface{} var v RawExt // interface{}
_, isJson := h.(*JsonHandle) _, isJson := h.(*JsonHandle)
_, isCbor := h.(*CborHandle) _, isCbor := h.(*CborHandle)
isValuer := isJson || isCbor bh := h.getBasicHandle()
_ = isValuer // isValuer := isJson || isCbor
// _ = isValuer
for _, r := range []RawExt{ for _, r := range []RawExt{
{Tag: 99, Value: "9999", Data: []byte("9999")}, {Tag: 99, Value: "9999", Data: []byte("9999")},
} { } {
e := NewEncoderBytes(&b, h) e := NewEncoderBytes(&b, h)
e.MustEncode(&r) e.MustEncode(&r)
// fmt.Printf(">>>> rawext: isnil? %v, %d - %v\n", b == nil, len(b), b)
d := NewDecoderBytes(b, h) d := NewDecoderBytes(b, h)
d.MustDecode(&v) d.MustDecode(&v)
switch h.(type) { var r2 = r
case *JsonHandle: switch {
testDeepEqualErr(r.Value, v, t, "rawext-json") case isJson:
r2.Tag = 0
r2.Data = nil
case isCbor:
r2.Data = nil
default: default:
r2 := r r2.Value = nil
if isValuer {
r2.Data = nil
} else {
r2.Value = nil
}
testDeepEqualErr(v, r2, t, "rawext-default")
} }
testDeepEqualErr(v, r2, t, "rawext-default")
// switch h.(type) {
// case *JsonHandle:
// testDeepEqualErr(r.Value, v, t, "rawext-json")
// default:
// var r2 = r
// if isValuer {
// r2.Data = nil
// } else {
// r2.Value = nil
// }
// testDeepEqualErr(v, r2, t, "rawext-default")
// }
} }
// Add testing for Raw also
if b != nil {
b = b[:0]
}
oldRawMode := bh.Raw
defer func() { bh.Raw = oldRawMode }()
bh.Raw = true
var v2 Raw
for _, s := range []string{
"goodbye",
"hello",
} {
e := NewEncoderBytes(&b, h)
e.MustEncode(&s)
// fmt.Printf(">>>> rawext: isnil? %v, %d - %v\n", b == nil, len(b), b)
var r Raw = make([]byte, len(b))
copy(r, b)
d := NewDecoderBytes(b, h)
d.MustDecode(&v2)
testDeepEqualErr(v2, r, t, "raw-default")
}
} }
// func doTestTimeExt(t *testing.T, h Handle) { // func doTestTimeExt(t *testing.T, h Handle) {
@ -1589,6 +1783,7 @@ func doTestMapStructKey(t *testing.T, h Handle) {
} }
func doTestDecodeNilMapValue(t *testing.T, handle Handle) { func doTestDecodeNilMapValue(t *testing.T, handle Handle) {
testOnce.Do(testInitAll)
type Struct struct { type Struct struct {
Field map[uint16]map[uint32]struct{} Field map[uint16]map[uint32]struct{}
} }
@ -1631,6 +1826,7 @@ func doTestDecodeNilMapValue(t *testing.T, handle Handle) {
} }
func doTestEmbeddedFieldPrecedence(t *testing.T, h Handle) { func doTestEmbeddedFieldPrecedence(t *testing.T, h Handle) {
testOnce.Do(testInitAll)
type Embedded struct { type Embedded struct {
Field byte Field byte
} }
@ -1672,6 +1868,7 @@ func doTestEmbeddedFieldPrecedence(t *testing.T, h Handle) {
} }
func doTestLargeContainerLen(t *testing.T, h Handle) { func doTestLargeContainerLen(t *testing.T, h Handle) {
testOnce.Do(testInitAll)
m := make(map[int][]struct{}) m := make(map[int][]struct{})
for i := range []int{ for i := range []int{
0, 1, 0, 1,
@ -1691,8 +1888,6 @@ func doTestLargeContainerLen(t *testing.T, h Handle) {
testUnmarshalErr(m2, bs, h, t, "-") testUnmarshalErr(m2, bs, h, t, "-")
testDeepEqualErr(m, m2, t, "-") testDeepEqualErr(m, m2, t, "-")
// TODO: skip rest if 32-bit
// do same tests for large strings (encoded as symbols or not) // do same tests for large strings (encoded as symbols or not)
// skip if 32-bit or not using unsafe mode // skip if 32-bit or not using unsafe mode
if safeMode || (32<<(^uint(0)>>63)) < 64 { if safeMode || (32<<(^uint(0)>>63)) < 64 {
@ -1704,10 +1899,11 @@ func doTestLargeContainerLen(t *testing.T, h Handle) {
// to do this, we create a simple one-field struct, // to do this, we create a simple one-field struct,
// use use flags to switch from symbols to non-symbols // use use flags to switch from symbols to non-symbols
bh := h.getBasicHandle() hbinc, okbinc := h.(*BincHandle)
oldAsSymbols := bh.AsSymbols if okbinc {
defer func() { bh.AsSymbols = oldAsSymbols }() oldAsSymbols := hbinc.AsSymbols
defer func() { hbinc.AsSymbols = oldAsSymbols }()
}
var out []byte = make([]byte, 0, math.MaxUint16*3/2) var out []byte = make([]byte, 0, math.MaxUint16*3/2)
var in []byte = make([]byte, math.MaxUint16*3/2) var in []byte = make([]byte, math.MaxUint16*3/2)
for i := range in { for i := range in {
@ -1728,7 +1924,9 @@ func doTestLargeContainerLen(t *testing.T, h Handle) {
// fmt.Printf("testcontainerlen: large string: i: %v, |%s|\n", i, s1) // fmt.Printf("testcontainerlen: large string: i: %v, |%s|\n", i, s1)
m1[s1] = true m1[s1] = true
bh.AsSymbols = AsSymbolNone if okbinc {
hbinc.AsSymbols = 2
}
out = out[:0] out = out[:0]
e.ResetBytes(&out) e.ResetBytes(&out)
e.MustEncode(m1) e.MustEncode(m1)
@ -1737,15 +1935,17 @@ func doTestLargeContainerLen(t *testing.T, h Handle) {
testUnmarshalErr(m2, out, h, t, "no-symbols") testUnmarshalErr(m2, out, h, t, "no-symbols")
testDeepEqualErr(m1, m2, t, "no-symbols") testDeepEqualErr(m1, m2, t, "no-symbols")
// now, do as symbols if okbinc {
bh.AsSymbols = AsSymbolAll // now, do as symbols
out = out[:0] hbinc.AsSymbols = 1
e.ResetBytes(&out) out = out[:0]
e.MustEncode(m1) e.ResetBytes(&out)
// bs, _ = testMarshalErr(m1, h, t, "-") e.MustEncode(m1)
m2 = make(map[string]bool, 1) // bs, _ = testMarshalErr(m1, h, t, "-")
testUnmarshalErr(m2, out, h, t, "symbols") m2 = make(map[string]bool, 1)
testDeepEqualErr(m1, m2, t, "symbols") testUnmarshalErr(m2, out, h, t, "symbols")
testDeepEqualErr(m1, m2, t, "symbols")
}
} }
} }
@ -1813,7 +2013,9 @@ func testRandomFillRV(v reflect.Value) {
case reflect.Float32, reflect.Float64: case reflect.Float32, reflect.Float64:
v.SetFloat(float64(fneg()) * float64(rand.Float32())) v.SetFloat(float64(fneg()) * float64(rand.Float32()))
case reflect.String: case reflect.String:
v.SetString(strings.Repeat(strconv.FormatInt(rand.Int63n(99), 10), rand.Intn(8))) // ensure this string can test the extent of json string decoding
v.SetString(strings.Repeat(strconv.FormatInt(rand.Int63n(99), 10), rand.Intn(8)) +
"- ABC \x41=\x42 \u2318 - \r \b \f - \u2028 and \u2029 .")
default: default:
panic(fmt.Errorf("testRandomFillRV: unsupported type: %v", v.Kind())) panic(fmt.Errorf("testRandomFillRV: unsupported type: %v", v.Kind()))
} }
@ -1838,6 +2040,7 @@ func testMammoth(t *testing.T, name string, h Handle) {
} }
func testTime(t *testing.T, name string, h Handle) { func testTime(t *testing.T, name string, h Handle) {
testOnce.Do(testInitAll)
// test time which uses the time.go implementation (ie Binc) // test time which uses the time.go implementation (ie Binc)
var tt, tt2 time.Time var tt, tt2 time.Time
// time in 1990 // time in 1990
@ -1854,6 +2057,7 @@ func testTime(t *testing.T, name string, h Handle) {
} }
func testUintToInt(t *testing.T, name string, h Handle) { func testUintToInt(t *testing.T, name string, h Handle) {
testOnce.Do(testInitAll)
var golden = [...]int64{ var golden = [...]int64{
0, 1, 22, 333, 4444, 55555, 666666, 0, 1, 22, 333, 4444, 55555, 666666,
// msgpack ones // msgpack ones
@ -1895,9 +2099,197 @@ func testUintToInt(t *testing.T, name string, h Handle) {
} }
} }
func doTestDifferentMapOrSliceType(t *testing.T, name string, h Handle) {
testOnce.Do(testInitAll)
// - maptype, slicetype: diff from map[string]intf, map[intf]intf or []intf, etc
// include map[interface{}]string where some keys are []byte.
// To test, take a sequence of []byte and string, and decode into []string and []interface.
// Also, decode into map[string]string, map[string]interface{}, map[interface{}]string
bh := h.getBasicHandle()
oldM, oldS := bh.MapType, bh.SliceType
defer func() { bh.MapType, bh.SliceType = oldM, oldS }()
var b []byte
var vi = []interface{}{
"hello 1",
[]byte("hello 2"),
"hello 3",
[]byte("hello 4"),
"hello 5",
}
var vs []string
var v2i, v2s testMbsT
var v2ss testMbsCustStrT
// encode it as a map or as a slice
for i, v := range vi {
vv, ok := v.(string)
if !ok {
vv = string(v.([]byte))
}
vs = append(vs, vv)
v2i = append(v2i, v, strconv.FormatInt(int64(i+1), 10))
v2s = append(v2s, vv, strconv.FormatInt(int64(i+1), 10))
v2ss = append(v2ss, testCustomStringT(vv), testCustomStringT(strconv.FormatInt(int64(i+1), 10)))
}
var v2d interface{}
// encode vs as a list, and decode into a list and compare
var goldSliceS = []string{"hello 1", "hello 2", "hello 3", "hello 4", "hello 5"}
var goldSliceI = []interface{}{"hello 1", "hello 2", "hello 3", "hello 4", "hello 5"}
var goldSlice = []interface{}{goldSliceS, goldSliceI}
for j, g := range goldSlice {
bh.SliceType = reflect.TypeOf(g)
name := fmt.Sprintf("slice-%s-%v", name, j+1)
b = testMarshalErr(vs, h, t, name)
v2d = nil
// v2d = reflect.New(bh.SliceType).Elem().Interface()
testUnmarshalErr(&v2d, b, h, t, name)
testDeepEqualErr(v2d, goldSlice[j], t, name)
}
// to ensure that we do not use fast-path for map[intf]string, use a custom string type (for goldMapIS).
// this will allow us to test out the path that sees a []byte where a map has an interface{} type,
// and convert it to a string for the decoded map key.
// encode v2i as a map, and decode into a map and compare
var goldMapSS = map[string]string{"hello 1": "1", "hello 2": "2", "hello 3": "3", "hello 4": "4", "hello 5": "5"}
var goldMapSI = map[string]interface{}{"hello 1": "1", "hello 2": "2", "hello 3": "3", "hello 4": "4", "hello 5": "5"}
var goldMapIS = map[interface{}]testCustomStringT{"hello 1": "1", "hello 2": "2", "hello 3": "3", "hello 4": "4", "hello 5": "5"}
var goldMap = []interface{}{goldMapSS, goldMapSI, goldMapIS}
for j, g := range goldMap {
bh.MapType = reflect.TypeOf(g)
name := fmt.Sprintf("map-%s-%v", name, j+1)
// for formats that clearly differentiate binary from string, use v2i
// else use the v2s (with all strings, no []byte)
v2d = nil
// v2d = reflect.New(bh.MapType).Elem().Interface()
switch h.(type) {
case *MsgpackHandle, *BincHandle, *CborHandle:
b = testMarshalErr(v2i, h, t, name)
testUnmarshalErr(&v2d, b, h, t, name)
testDeepEqualErr(v2d, goldMap[j], t, name)
default:
b = testMarshalErr(v2s, h, t, name)
testUnmarshalErr(&v2d, b, h, t, name)
testDeepEqualErr(v2d, goldMap[j], t, name)
b = testMarshalErr(v2ss, h, t, name)
v2d = nil
testUnmarshalErr(&v2d, b, h, t, name)
testDeepEqualErr(v2d, goldMap[j], t, name)
}
}
}
func doTestScalars(t *testing.T, name string, h Handle) {
testOnce.Do(testInitAll)
// for each scalar:
// - encode its ptr
// - encode it (non-ptr)
// - check that bytes are same
// - make a copy (using reflect)
// - check that same
// - set zero on it
// - check that its equal to 0 value
// - decode into new
// - compare to original
bh := h.getBasicHandle()
if !bh.Canonical {
bh.Canonical = true
defer func() { bh.Canonical = false }()
}
vi := []interface{}{
int(0),
int8(0),
int16(0),
int32(0),
int64(0),
uint(0),
uint8(0),
uint16(0),
uint32(0),
uint64(0),
uintptr(0),
float32(0),
float64(0),
bool(false),
string(""),
[]byte(nil),
}
for _, v := range fastpathAV {
vi = append(vi, reflect.Zero(v.rt).Interface())
}
for _, v := range vi {
rv := reflect.New(reflect.TypeOf(v)).Elem()
testRandomFillRV(rv)
v = rv.Interface()
rv2 := reflect.New(rv.Type())
rv2.Elem().Set(rv)
vp := rv2.Interface()
var tname string
switch rv.Kind() {
case reflect.Map:
tname = "map[" + rv.Type().Key().Name() + "]" + rv.Type().Elem().Name()
case reflect.Slice:
tname = "[]" + rv.Type().Elem().Name()
default:
tname = rv.Type().Name()
}
var b, b1, b2 []byte
b1 = testMarshalErr(v, h, t, tname+"-enc")
// store b1 into b, as b1 slice is reused for next marshal
b = make([]byte, len(b1))
copy(b, b1)
b2 = testMarshalErr(vp, h, t, tname+"-enc-ptr")
testDeepEqualErr(b1, b2, t, tname+"-enc-eq")
setZero(vp)
testDeepEqualErr(rv2.Elem().Interface(), reflect.Zero(rv.Type()).Interface(), t, tname+"-enc-eq-zero-ref")
vp = rv2.Interface()
testUnmarshalErr(vp, b, h, t, tname+"-dec")
testDeepEqualErr(rv2.Elem().Interface(), v, t, tname+"-dec-eq")
}
}
func doTestIntfMapping(t *testing.T, name string, h Handle) {
testOnce.Do(testInitAll)
rti := reflect.TypeOf((*testIntfMapI)(nil)).Elem()
defer func() { h.getBasicHandle().Intf2Impl(rti, nil) }()
type T9 struct {
I testIntfMapI
}
for i, v := range []testIntfMapI{
// Use a valid string to test some extents of json string decoding
&testIntfMapT1{"ABC \x41=\x42 \u2318 - \r \b \f - \u2028 and \u2029 ."},
testIntfMapT2{"DEF"},
} {
if err := h.getBasicHandle().Intf2Impl(rti, reflect.TypeOf(v)); err != nil {
failT(t, "Error mapping %v to %T", rti, v)
}
var v1, v2 T9
v1 = T9{v}
b := testMarshalErr(v1, h, t, name+"-enc-"+strconv.Itoa(i))
testUnmarshalErr(&v2, b, h, t, name+"-dec-"+strconv.Itoa(i))
testDeepEqualErr(v1, v2, t, name+"-dec-eq-"+strconv.Itoa(i))
}
}
// ----------------- // -----------------
func TestJsonDecodeNonStringScalarInStringContext(t *testing.T) { func TestJsonDecodeNonStringScalarInStringContext(t *testing.T) {
testOnce.Do(testInitAll)
var b = `{"s.true": "true", "b.true": true, "s.false": "false", "b.false": false, "s.10": "10", "i.10": 10, "i.-10": -10}` var b = `{"s.true": "true", "b.true": true, "s.false": "false", "b.false": false, "s.10": "10", "i.10": 10, "i.-10": -10}`
var golden = map[string]string{"s.true": "true", "b.true": "true", "s.false": "false", "b.false": "false", "s.10": "10", "i.10": "10", "i.-10": "-10"} var golden = map[string]string{"s.true": "true", "b.true": "true", "s.false": "false", "b.false": "false", "s.10": "10", "i.10": "10", "i.-10": "-10"}
@ -1913,6 +2305,7 @@ func TestJsonDecodeNonStringScalarInStringContext(t *testing.T) {
} }
func TestJsonEncodeIndent(t *testing.T) { func TestJsonEncodeIndent(t *testing.T) {
testOnce.Do(testInitAll)
v := TestSimplish{ v := TestSimplish{
Ii: -794, Ii: -794,
Ss: `A Man is Ss: `A Man is
@ -2026,6 +2419,7 @@ after the new line
} }
func TestBufioDecReader(t *testing.T) { func TestBufioDecReader(t *testing.T) {
testOnce.Do(testInitAll)
// try to read 85 bytes in chunks of 7 at a time. // try to read 85 bytes in chunks of 7 at a time.
var s = strings.Repeat("01234'56789 ", 5) var s = strings.Repeat("01234'56789 ", 5)
// fmt.Printf("s: %s\n", s) // fmt.Printf("s: %s\n", s)
@ -2092,6 +2486,7 @@ func TestBufioDecReader(t *testing.T) {
// ----------- // -----------
func TestJsonLargeInteger(t *testing.T) { func TestJsonLargeInteger(t *testing.T) {
testOnce.Do(testInitAll)
for _, i := range []uint8{'L', 'A', 0} { for _, i := range []uint8{'L', 'A', 0} {
for _, j := range []interface{}{ for _, j := range []interface{}{
int64(1 << 60), int64(1 << 60),
@ -2109,6 +2504,7 @@ func TestJsonLargeInteger(t *testing.T) {
} }
func TestJsonInvalidUnicode(t *testing.T) { func TestJsonInvalidUnicode(t *testing.T) {
testOnce.Do(testInitAll)
var m = map[string]string{ var m = map[string]string{
`"\udc49\u0430abc"`: "\uFFFDabc", `"\udc49\u0430abc"`: "\uFFFDabc",
`"\udc49\u0430"`: "\uFFFD", `"\udc49\u0430"`: "\uFFFD",
@ -2431,6 +2827,11 @@ func TestCborMammothMapsAndSlices(t *testing.T) {
} }
func TestMsgpackMammothMapsAndSlices(t *testing.T) { func TestMsgpackMammothMapsAndSlices(t *testing.T) {
old1, old2 := testMsgpackH.RawToString, testMsgpackH.WriteExt
defer func() { testMsgpackH.RawToString, testMsgpackH.WriteExt = old1, old2 }()
testMsgpackH.RawToString = true
testMsgpackH.WriteExt = true
doTestMammothMapsAndSlices(t, testMsgpackH) doTestMammothMapsAndSlices(t, testMsgpackH)
} }
@ -2482,41 +2883,85 @@ func TestSimpleUintToInt(t *testing.T) {
testUintToInt(t, "simple", testSimpleH) testUintToInt(t, "simple", testSimpleH)
} }
func TestJsonDifferentMapOrSliceType(t *testing.T) {
doTestDifferentMapOrSliceType(t, "json", testJsonH)
}
func TestCborDifferentMapOrSliceType(t *testing.T) {
doTestDifferentMapOrSliceType(t, "cbor", testCborH)
}
func TestMsgpackDifferentMapOrSliceType(t *testing.T) {
doTestDifferentMapOrSliceType(t, "msgpack", testMsgpackH)
}
func TestBincDifferentMapOrSliceType(t *testing.T) {
doTestDifferentMapOrSliceType(t, "binc", testBincH)
}
func TestSimpleDifferentMapOrSliceType(t *testing.T) {
doTestDifferentMapOrSliceType(t, "simple", testSimpleH)
}
func TestJsonScalars(t *testing.T) {
doTestScalars(t, "json", testJsonH)
}
func TestCborScalars(t *testing.T) {
doTestScalars(t, "cbor", testCborH)
}
func TestMsgpackScalars(t *testing.T) {
doTestScalars(t, "msgpack", testMsgpackH)
}
func TestBincScalars(t *testing.T) {
doTestScalars(t, "binc", testBincH)
}
func TestSimpleScalars(t *testing.T) {
doTestScalars(t, "simple", testSimpleH)
}
func TestJsonIntfMapping(t *testing.T) {
doTestIntfMapping(t, "json", testJsonH)
}
func TestCborIntfMapping(t *testing.T) {
doTestIntfMapping(t, "cbor", testCborH)
}
func TestMsgpackIntfMapping(t *testing.T) {
doTestIntfMapping(t, "msgpack", testMsgpackH)
}
func TestBincIntfMapping(t *testing.T) {
doTestIntfMapping(t, "binc", testBincH)
}
func TestSimpleIntfMapping(t *testing.T) {
doTestIntfMapping(t, "simple", testSimpleH)
}
// TODO: // TODO:
// //
// Add Tests for: // Add Tests for:
// - struct tags: // - struct tags: on anonymous fields, _struct (all fields), etc
// on anonymous fields, _struct (all fields), etc // - chan to encode and decode (with support for codecgen also)
// - codecgen of struct containing channels.
// - (encode extensions: ext, raw ext, etc)
// - extension that isn't builtin e.g. type uint64Ext uint64.
// it encodes as a uint64.
// //
// Add negative tests for failure conditions: // Add negative tests for failure conditions:
// - bad input with large array length prefix // - bad input with large array length prefix
// //
// msgpack // decode.go (standalone)
// - support time as built-in extension:
// see https://github.com/msgpack/msgpack/blob/master/spec.md#timestamp-extension-type
//
// decode.go
// - UnreadByte: only 2 states (z.ls = 2 and z.ls = 1) (0 --> 2 --> 1) // - UnreadByte: only 2 states (z.ls = 2 and z.ls = 1) (0 --> 2 --> 1)
// - track // - track: z.trb: track, stop track, check
// z.trb: track, stop track, check // - PreferArrayOverSlice???
// - maptype, slicetype: diff from map[string]intf, map[intf]intf or []intf, // - InterfaceReset
// - PreferArrayOverSlice??? (standalone test)
// - InterfaceReset (standalone test)
// - (chan byte) to decode []byte (with mapbyslice track) // - (chan byte) to decode []byte (with mapbyslice track)
// - decode slice of len 6, 16 into slice of (len 4, cap 8) and (len ) with maxinitlen=6, 8, 16 // - decode slice of len 6, 16 into slice of (len 4, cap 8) and (len ) with maxinitlen=6, 8, 16
// - ktypeisintf // - DeleteOnNilMapValue
// - DeleteOnNilMapValue (standalone test)
// - decnaked: n.l == nil // - decnaked: n.l == nil
// - setZero: all types: *bool, *intXXX, *uintXXX, *floatXXX, *Raw, *[]byte, etc (not just struct)
// - codec.Selfer implementation
// Ensure it is called when (en|de)coding interface{} or reflect.Value (2 different codepaths).
// - ensureDecodeable (try to decode into a non-decodeable thing e.g. a nil interface{}, // - ensureDecodeable (try to decode into a non-decodeable thing e.g. a nil interface{},
// //
// encode.go // encode.go (standalone)
// - mapbyslice (for non-fastpath things)
// - nil and 0-len slices and maps for non-fastpath things // - nil and 0-len slices and maps for non-fastpath things
// - pointers to scalars at top-level e.g. v := uint(7), encode(&v)

View File

@ -1,4 +1,4 @@
// Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved. // Copyright (c) 2012-2018 Ugorji Nwoke. All rights reserved.
// Use of this source code is governed by a MIT license found in the LICENSE file. // Use of this source code is governed by a MIT license found in the LICENSE file.
// codecgen generates codec.Selfer implementations for a set of types. // codecgen generates codec.Selfer implementations for a set of types.
@ -29,6 +29,8 @@ const genCodecPkg = "codec1978" // keep this in sync with codec.genCodecPkg
const genFrunMainTmpl = `//+build ignore const genFrunMainTmpl = `//+build ignore
// Code generated - temporary main package for codecgen - DO NOT EDIT.
package main package main
{{ if .Types }}import "{{ .ImportPath }}"{{ end }} {{ if .Types }}import "{{ .ImportPath }}"{{ end }}
func main() { func main() {
@ -38,6 +40,9 @@ func main() {
// const genFrunPkgTmpl = `//+build codecgen // const genFrunPkgTmpl = `//+build codecgen
const genFrunPkgTmpl = ` const genFrunPkgTmpl = `
// Code generated - temporary package for codecgen - DO NOT EDIT.
package {{ $.PackageName }} package {{ $.PackageName }}
import ( import (
@ -50,20 +55,33 @@ import (
) )
func CodecGenTempWrite{{ .RandString }}() { func CodecGenTempWrite{{ .RandString }}() {
os.Remove("{{ .OutFile }}")
fout, err := os.Create("{{ .OutFile }}") fout, err := os.Create("{{ .OutFile }}")
if err != nil { if err != nil {
panic(err) panic(err)
} }
defer fout.Close() defer fout.Close()
var out bytes.Buffer
var typs []reflect.Type var typs []reflect.Type
var typ reflect.Type
var numfields int
{{ range $index, $element := .Types }} {{ range $index, $element := .Types }}
var t{{ $index }} {{ . }} var t{{ $index }} {{ . }}
typs = append(typs, reflect.TypeOf(t{{ $index }})) typ = reflect.TypeOf(t{{ $index }})
typs = append(typs, typ)
if typ.Kind() == reflect.Struct { numfields += typ.NumField() } else { numfields += 1 }
{{ end }} {{ end }}
{{ if not .CodecPkgFiles }}{{ .CodecPkgName }}.{{ end }}Gen(&out, "{{ .BuildTag }}", "{{ .PackageName }}", "{{ .RandString }}", {{ .NoExtensions }}, {{ if not .CodecPkgFiles }}{{ .CodecPkgName }}.{{ end }}NewTypeInfos(strings.Split("{{ .StructTags }}", ",")), typs...)
// println("initializing {{ .OutFile }}, buf size: {{ .AllFilesSize }}*16",
// {{ .AllFilesSize }}*16, "num fields: ", numfields)
var out = bytes.NewBuffer(make([]byte, 0, numfields*1024)) // {{ .AllFilesSize }}*16
{{ if not .CodecPkgFiles }}{{ .CodecPkgName }}.{{ end }}Gen(out,
"{{ .BuildTag }}", "{{ .PackageName }}", "{{ .RandString }}", {{ .NoExtensions }},
{{ if not .CodecPkgFiles }}{{ .CodecPkgName }}.{{ end }}NewTypeInfos(strings.Split("{{ .StructTags }}", ",")),
typs...)
bout, err := format.Source(out.Bytes()) bout, err := format.Source(out.Bytes())
// println("... lengths: before formatting: ", len(out.Bytes()), ", after formatting", len(bout))
if err != nil { if err != nil {
fout.Write(out.Bytes()) fout.Write(out.Bytes())
panic(err) panic(err)
@ -98,8 +116,7 @@ func Generate(outfile, buildTag, codecPkgPath string,
} }
if uid < 0 { if uid < 0 {
uid = -uid uid = -uid
} } else if uid == 0 {
if uid == 0 {
rr := rand.New(rand.NewSource(time.Now().UnixNano())) rr := rand.New(rand.NewSource(time.Now().UnixNano()))
uid = 101 + rr.Int63n(9777) uid = 101 + rr.Int63n(9777)
} }
@ -124,6 +141,7 @@ func Generate(outfile, buildTag, codecPkgPath string,
BuildTag string BuildTag string
StructTags string StructTags string
Types []string Types []string
AllFilesSize int64
CodecPkgFiles bool CodecPkgFiles bool
NoExtensions bool NoExtensions bool
} }
@ -145,11 +163,17 @@ func Generate(outfile, buildTag, codecPkgPath string,
tv.ImportPath = stripVendor(tv.ImportPath) tv.ImportPath = stripVendor(tv.ImportPath)
} }
astfiles := make([]*ast.File, len(infiles)) astfiles := make([]*ast.File, len(infiles))
var fi os.FileInfo
for i, infile := range infiles { for i, infile := range infiles {
if filepath.Dir(infile) != lastdir { if filepath.Dir(infile) != lastdir {
err = errors.New("in files must all be in same directory as outfile") err = errors.New("in files must all be in same directory as outfile")
return return
} }
if fi, err = os.Stat(infile); err != nil {
return
}
tv.AllFilesSize += fi.Size()
fset := token.NewFileSet() fset := token.NewFileSet()
astfiles[i], err = parser.ParseFile(fset, infile, nil, 0) astfiles[i], err = parser.ParseFile(fset, infile, nil, 0)
if err != nil { if err != nil {
@ -211,6 +235,10 @@ func Generate(outfile, buildTag, codecPkgPath string,
// chan: ChanType // chan: ChanType
// do not generate: // do not generate:
// FuncType, InterfaceType, StarExpr (ptr), etc // FuncType, InterfaceType, StarExpr (ptr), etc
//
// We generate for all these types (not just structs), because they may be a field
// in another struct which doesn't have codecgen run on it, and it will be nice
// to take advantage of the fact that the type is a Selfer.
switch td.Type.(type) { switch td.Type.(type) {
case *ast.StructType, *ast.Ident, *ast.MapType, *ast.ArrayType, *ast.ChanType: case *ast.StructType, *ast.Ident, *ast.MapType, *ast.ArrayType, *ast.ChanType:
// only add to tv.Types iff // only add to tv.Types iff
@ -286,6 +314,7 @@ func gen1(frunName, tmplStr string, tv interface{}) (frun *os.File, err error) {
} }
bw := bufio.NewWriter(frun) bw := bufio.NewWriter(frun)
if err = t.Execute(bw, tv); err != nil { if err = t.Execute(bw, tv); err != nil {
bw.Flush()
return return
} }
if err = bw.Flush(); err != nil { if err = bw.Flush(); err != nil {
@ -317,14 +346,14 @@ func main() {
rt := flag.String("rt", "", "tags for go run") rt := flag.String("rt", "", "tags for go run")
st := flag.String("st", "codec,json", "struct tag keys to introspect") st := flag.String("st", "codec,json", "struct tag keys to introspect")
x := flag.Bool("x", false, "keep temp file") x := flag.Bool("x", false, "keep temp file")
_ = flag.Bool("u", false, "*IGNORED - kept for backwards compatibility*: Allow unsafe use") _ = flag.Bool("u", false, "Allow unsafe use. ***IGNORED*** - kept for backwards compatibility: ")
d := flag.Int64("d", 0, "random identifier for use in generated code") d := flag.Int64("d", 0, "random identifier for use in generated code")
nx := flag.Bool("nx", false, "no extensions") nx := flag.Bool("nx", false, "do not support extensions - support of extensions may cause extra allocation")
flag.Parse() flag.Parse()
if err := Generate(*o, *t, *c, *d, *rt, *st, err := Generate(*o, *t, *c, *d, *rt, *st,
regexp.MustCompile(*r), regexp.MustCompile(*nr), !*x, *nx, regexp.MustCompile(*r), regexp.MustCompile(*nr), !*x, *nx, flag.Args()...)
flag.Args()...); err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "codecgen error: %v\n", err) fmt.Fprintf(os.Stderr, "codecgen error: %v\n", err)
os.Exit(1) os.Exit(1)
} }

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -3,10 +3,7 @@
// Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved. // Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved.
// Use of this source code is governed by a MIT license found in the LICENSE file. // Use of this source code is governed by a MIT license found in the LICENSE file.
// ************************************************************ // Code generated from fast-path.go.tmpl - DO NOT EDIT.
// DO NOT EDIT.
// THIS FILE IS AUTO-GENERATED from fast-path.go.tmpl
// ************************************************************
package codec package codec
@ -18,19 +15,19 @@ package codec
// This file can be omitted without causing a build failure. // This file can be omitted without causing a build failure.
// //
// The advantage of fast paths is: // The advantage of fast paths is:
// - Many calls bypass reflection altogether // - Many calls bypass reflection altogether
// //
// Currently support // Currently support
// - slice of all builtin types, // - slice of all builtin types,
// - map of all builtin types to string or interface value // - map of all builtin types to string or interface value
// - symmetrical maps of all builtin types (e.g. str-str, uint8-uint8) // - symmetrical maps of all builtin types (e.g. str-str, uint8-uint8)
// This should provide adequate "typical" implementations. // This should provide adequate "typical" implementations.
// //
// Note that fast track decode functions must handle values for which an address cannot be obtained. // Note that fast track decode functions must handle values for which an address cannot be obtained.
// For example: // For example:
// m2 := map[string]int{} // m2 := map[string]int{}
// p2 := []interface{}{m2} // p2 := []interface{}{m2}
// // decoding into p2 will bomb if fast track functions do not treat like unaddressable. // // decoding into p2 will bomb if fast track functions do not treat like unaddressable.
// //
import ( import (
@ -80,26 +77,19 @@ var fastpathAV fastpathA
// due to possible initialization loop error, make fastpath in an init() // due to possible initialization loop error, make fastpath in an init()
func init() { func init() {
if useLookupRecognizedTypes && recognizedRtidsLoaded {
panic("recognizedRtidsLoaded = true - cannot happen")
}
i := 0 i := 0
fn := func(v interface{}, fn := func(v interface{},
fe func(*Encoder, *codecFnInfo, reflect.Value), fe func(*Encoder, *codecFnInfo, reflect.Value),
fd func(*Decoder, *codecFnInfo, reflect.Value)) (f fastpathE) { fd func(*Decoder, *codecFnInfo, reflect.Value)) (f fastpathE) {
xrt := reflect.TypeOf(v) xrt := reflect.TypeOf(v)
xptr := rt2id(xrt) xptr := rt2id(xrt)
if useLookupRecognizedTypes {
recognizedRtids = append(recognizedRtids, xptr)
recognizedRtidPtrs = append(recognizedRtidPtrs, rt2id(reflect.PtrTo(xrt)))
}
fastpathAV[i] = fastpathE{xptr, xrt, fe, fd} fastpathAV[i] = fastpathE{xptr, xrt, fe, fd}
i++ i++
return return
} }
{{/* do not register []uint8 in fast-path */}}
{{range .Values}}{{if not .Primitive}}{{if not .MapKey }} {{range .Values}}{{if not .Primitive}}{{if not .MapKey }}{{if ne .Elem "uint8"}}
fn([]{{ .Elem }}(nil), (*Encoder).{{ .MethodNamePfx "fastpathEnc" false }}R, (*Decoder).{{ .MethodNamePfx "fastpathDec" false }}R){{end}}{{end}}{{end}} fn([]{{ .Elem }}(nil), (*Encoder).{{ .MethodNamePfx "fastpathEnc" false }}R, (*Decoder).{{ .MethodNamePfx "fastpathDec" false }}R){{end}}{{end}}{{end}}{{end}}
{{range .Values}}{{if not .Primitive}}{{if .MapKey }} {{range .Values}}{{if not .Primitive}}{{if .MapKey }}
fn(map[{{ .MapKey }}]{{ .Elem }}(nil), (*Encoder).{{ .MethodNamePfx "fastpathEnc" false }}R, (*Decoder).{{ .MethodNamePfx "fastpathDec" false }}R){{end}}{{end}}{{end}} fn(map[{{ .MapKey }}]{{ .Elem }}(nil), (*Encoder).{{ .MethodNamePfx "fastpathEnc" false }}R, (*Decoder).{{ .MethodNamePfx "fastpathDec" false }}R){{end}}{{end}}{{end}}
@ -112,22 +102,37 @@ func init() {
// -- -- fast path type switch // -- -- fast path type switch
func fastpathEncodeTypeSwitch(iv interface{}, e *Encoder) bool { func fastpathEncodeTypeSwitch(iv interface{}, e *Encoder) bool {
switch v := iv.(type) { switch v := iv.(type) {
{{range .Values}}{{if not .Primitive}}{{if not .MapKey }}
case []{{ .Elem }}:{{else}} {{range .Values}}{{if not .Primitive}}{{if not .MapKey }}{{if ne .Elem "uint8"}}
case map[{{ .MapKey }}]{{ .Elem }}:{{end}} case []{{ .Elem }}:
fastpathTV.{{ .MethodNamePfx "Enc" false }}V(v, e){{if not .MapKey }} fastpathTV.{{ .MethodNamePfx "Enc" false }}V(v, e)
case *[]{{ .Elem }}:{{else}} case *[]{{ .Elem }}:
case *map[{{ .MapKey }}]{{ .Elem }}:{{end}} fastpathTV.{{ .MethodNamePfx "Enc" false }}V(*v, e){{/*
fastpathTV.{{ .MethodNamePfx "Enc" false }}V(*v, e) */}}{{end}}{{end}}{{end}}{{end}}
{{end}}{{end}}
{{range .Values}}{{if not .Primitive}}{{if .MapKey }}
case map[{{ .MapKey }}]{{ .Elem }}:
fastpathTV.{{ .MethodNamePfx "Enc" false }}V(v, e)
case *map[{{ .MapKey }}]{{ .Elem }}:
fastpathTV.{{ .MethodNamePfx "Enc" false }}V(*v, e){{/*
*/}}{{end}}{{end}}{{end}}
default: default:
_ = v // TODO: workaround https://github.com/golang/go/issues/12927 (remove after go 1.6 release) _ = v // workaround https://github.com/golang/go/issues/12927 seen in go1.4
return false return false
} }
return true return true
} }
{{/* **** removing this block, as they are never called directly **** {{/*
**** removing this block, as they are never called directly ****
**** removing this block, as they are never called directly ****
func fastpathEncodeTypeSwitchSlice(iv interface{}, e *Encoder) bool { func fastpathEncodeTypeSwitchSlice(iv interface{}, e *Encoder) bool {
switch v := iv.(type) { switch v := iv.(type) {
{{range .Values}}{{if not .Primitive}}{{if not .MapKey }} {{range .Values}}{{if not .Primitive}}{{if not .MapKey }}
@ -137,7 +142,7 @@ func fastpathEncodeTypeSwitchSlice(iv interface{}, e *Encoder) bool {
fastpathTV.{{ .MethodNamePfx "Enc" false }}V(*v, e) fastpathTV.{{ .MethodNamePfx "Enc" false }}V(*v, e)
{{end}}{{end}}{{end}} {{end}}{{end}}{{end}}
default: default:
_ = v // TODO: workaround https://github.com/golang/go/issues/12927 (remove after go 1.6 release) _ = v // workaround https://github.com/golang/go/issues/12927 seen in go1.4
return false return false
} }
return true return true
@ -152,16 +157,23 @@ func fastpathEncodeTypeSwitchMap(iv interface{}, e *Encoder) bool {
fastpathTV.{{ .MethodNamePfx "Enc" false }}V(*v, e) fastpathTV.{{ .MethodNamePfx "Enc" false }}V(*v, e)
{{end}}{{end}}{{end}} {{end}}{{end}}{{end}}
default: default:
_ = v // TODO: workaround https://github.com/golang/go/issues/12927 (remove after go 1.6 release) _ = v // workaround https://github.com/golang/go/issues/12927 seen in go1.4
return false return false
} }
return true return true
} }
**** removing this block, as they are never called directly ****
**** removing this block, as they are never called directly ****
*/}} */}}
// -- -- fast path functions // -- -- fast path functions
{{range .Values}}{{if not .Primitive}}{{if not .MapKey }} {{range .Values}}{{if not .Primitive}}{{if not .MapKey }}
func (e *Encoder) {{ .MethodNamePfx "fastpathEnc" false }}R(f *codecFnInfo, rv reflect.Value) { func (e *Encoder) {{ .MethodNamePfx "fastpathEnc" false }}R(f *codecFnInfo, rv reflect.Value) {
if f.ti.mbs { if f.ti.mbs {
fastpathTV.{{ .MethodNamePfx "EncAsMap" false }}V(rv2i(rv).([]{{ .Elem }}), e) fastpathTV.{{ .MethodNamePfx "EncAsMap" false }}V(rv2i(rv).([]{{ .Elem }}), e)
@ -173,13 +185,22 @@ func (_ fastpathT) {{ .MethodNamePfx "Enc" false }}V(v []{{ .Elem }}, e *Encoder
if v == nil { e.e.EncodeNil(); return } if v == nil { e.e.EncodeNil(); return }
ee, esep := e.e, e.hh.hasElemSeparators() ee, esep := e.e, e.hh.hasElemSeparators()
ee.WriteArrayStart(len(v)) ee.WriteArrayStart(len(v))
if esep {
for _, v2 := range v {
ee.WriteArrayElem()
{{ encmd .Elem "v2"}}
}
} else {
for _, v2 := range v {
{{ encmd .Elem "v2"}}
}
} {{/*
for _, v2 := range v { for _, v2 := range v {
if esep { ee.WriteArrayElem() } if esep { ee.WriteArrayElem() }
{{ encmd .Elem "v2"}} {{ encmd .Elem "v2"}}
} } */}}
ee.WriteArrayEnd() ee.WriteArrayEnd()
} }
func (_ fastpathT) {{ .MethodNamePfx "EncAsMap" false }}V(v []{{ .Elem }}, e *Encoder) { func (_ fastpathT) {{ .MethodNamePfx "EncAsMap" false }}V(v []{{ .Elem }}, e *Encoder) {
ee, esep := e.e, e.hh.hasElemSeparators() ee, esep := e.e, e.hh.hasElemSeparators()
if len(v)%2 == 1 { if len(v)%2 == 1 {
@ -187,6 +208,20 @@ func (_ fastpathT) {{ .MethodNamePfx "EncAsMap" false }}V(v []{{ .Elem }}, e *En
return return
} }
ee.WriteMapStart(len(v) / 2) ee.WriteMapStart(len(v) / 2)
if esep {
for j, v2 := range v {
if j%2 == 0 {
ee.WriteMapElemKey()
} else {
ee.WriteMapElemValue()
}
{{ encmd .Elem "v2"}}
}
} else {
for _, v2 := range v {
{{ encmd .Elem "v2"}}
}
} {{/*
for j, v2 := range v { for j, v2 := range v {
if esep { if esep {
if j%2 == 0 { if j%2 == 0 {
@ -196,14 +231,12 @@ func (_ fastpathT) {{ .MethodNamePfx "EncAsMap" false }}V(v []{{ .Elem }}, e *En
} }
} }
{{ encmd .Elem "v2"}} {{ encmd .Elem "v2"}}
} } */}}
ee.WriteMapEnd() ee.WriteMapEnd()
} }
{{end}}{{end}}{{end}} {{end}}{{end}}{{end}}
{{range .Values}}{{if not .Primitive}}{{if .MapKey }} {{range .Values}}{{if not .Primitive}}{{if .MapKey }}
func (e *Encoder) {{ .MethodNamePfx "fastpathEnc" false }}R(f *codecFnInfo, rv reflect.Value) { func (e *Encoder) {{ .MethodNamePfx "fastpathEnc" false }}R(f *codecFnInfo, rv reflect.Value) {
fastpathTV.{{ .MethodNamePfx "Enc" false }}V(rv2i(rv).(map[{{ .MapKey }}]{{ .Elem }}), e) fastpathTV.{{ .MethodNamePfx "Enc" false }}V(rv2i(rv).(map[{{ .MapKey }}]{{ .Elem }}), e)
} }
@ -211,8 +244,7 @@ func (_ fastpathT) {{ .MethodNamePfx "Enc" false }}V(v map[{{ .MapKey }}]{{ .Ele
if v == nil { e.e.EncodeNil(); return } if v == nil { e.e.EncodeNil(); return }
ee, esep := e.e, e.hh.hasElemSeparators() ee, esep := e.e, e.hh.hasElemSeparators()
ee.WriteMapStart(len(v)) ee.WriteMapStart(len(v))
{{if eq .MapKey "string"}}asSymbols := e.h.AsSymbols&AsSymbolMapStringKeysFlag != 0 if e.h.Canonical {
{{end}}if e.h.Canonical {
{{if eq .MapKey "interface{}"}}{{/* out of band {{if eq .MapKey "interface{}"}}{{/* out of band
*/}}var mksv []byte = make([]byte, 0, len(v)*16) // temporary byte slice for the encoding */}}var mksv []byte = make([]byte, 0, len(v)*16) // temporary byte slice for the encoding
e2 := NewEncoderBytes(&mksv, e.hh) e2 := NewEncoderBytes(&mksv, e.hh)
@ -228,76 +260,126 @@ func (_ fastpathT) {{ .MethodNamePfx "Enc" false }}V(v map[{{ .MapKey }}]{{ .Ele
i++ i++
} }
sort.Sort(bytesISlice(v2)) sort.Sort(bytesISlice(v2))
if esep {
for j := range v2 {
ee.WriteMapElemKey()
e.asis(v2[j].v)
ee.WriteMapElemValue()
e.encode(v[v2[j].i])
}
} else {
for j := range v2 {
e.asis(v2[j].v)
e.encode(v[v2[j].i])
}
} {{/*
for j := range v2 { for j := range v2 {
if esep { ee.WriteMapElemKey() } if esep { ee.WriteMapElemKey() }
e.asis(v2[j].v) e.asis(v2[j].v)
if esep { ee.WriteMapElemValue() } if esep { ee.WriteMapElemValue() }
e.encode(v[v2[j].i]) e.encode(v[v2[j].i])
} {{else}}{{ $x := sorttype .MapKey true}}v2 := make([]{{ $x }}, len(v)) } */}} {{else}}{{ $x := sorttype .MapKey true}}v2 := make([]{{ $x }}, len(v))
var i int var i int
for k, _ := range v { for k, _ := range v {
v2[i] = {{ $x }}(k) v2[i] = {{ $x }}(k)
i++ i++
} }
sort.Sort({{ sorttype .MapKey false}}(v2)) sort.Sort({{ sorttype .MapKey false}}(v2))
if esep {
for _, k2 := range v2 {
ee.WriteMapElemKey()
{{if eq .MapKey "string"}}ee.EncodeString(cUTF8, k2){{else}}{{ $y := printf "%s(k2)" .MapKey }}{{ encmd .MapKey $y }}{{end}}
ee.WriteMapElemValue()
{{ $y := printf "v[%s(k2)]" .MapKey }}{{ encmd .Elem $y }}
}
} else {
for _, k2 := range v2 {
{{if eq .MapKey "string"}}ee.EncodeString(cUTF8, k2){{else}}{{ $y := printf "%s(k2)" .MapKey }}{{ encmd .MapKey $y }}{{end}}
{{ $y := printf "v[%s(k2)]" .MapKey }}{{ encmd .Elem $y }}
}
} {{/*
for _, k2 := range v2 { for _, k2 := range v2 {
if esep { ee.WriteMapElemKey() } if esep { ee.WriteMapElemKey() }
{{if eq .MapKey "string"}}if asSymbols { {{if eq .MapKey "string"}}ee.EncodeString(cUTF8, k2){{else}}{{ $y := printf "%s(k2)" .MapKey }}{{ encmd .MapKey $y }}{{end}}
ee.EncodeSymbol(k2)
} else {
ee.EncodeString(c_UTF8, k2)
}{{else}}{{ $y := printf "%s(k2)" .MapKey }}{{ encmd .MapKey $y }}{{end}}
if esep { ee.WriteMapElemValue() } if esep { ee.WriteMapElemValue() }
{{ $y := printf "v[%s(k2)]" .MapKey }}{{ encmd .Elem $y }} {{ $y := printf "v[%s(k2)]" .MapKey }}{{ encmd .Elem $y }}
} {{end}} } */}} {{end}}
} else { } else {
if esep {
for k2, v2 := range v {
ee.WriteMapElemKey()
{{if eq .MapKey "string"}}ee.EncodeString(cUTF8, k2){{else}}{{ encmd .MapKey "k2"}}{{end}}
ee.WriteMapElemValue()
{{ encmd .Elem "v2"}}
}
} else {
for k2, v2 := range v {
{{if eq .MapKey "string"}}ee.EncodeString(cUTF8, k2){{else}}{{ encmd .MapKey "k2"}}{{end}}
{{ encmd .Elem "v2"}}
}
} {{/*
for k2, v2 := range v { for k2, v2 := range v {
if esep { ee.WriteMapElemKey() } if esep { ee.WriteMapElemKey() }
{{if eq .MapKey "string"}}if asSymbols { {{if eq .MapKey "string"}}ee.EncodeString(cUTF8, k2){{else}}{{ encmd .MapKey "k2"}}{{end}}
ee.EncodeSymbol(k2)
} else {
ee.EncodeString(c_UTF8, k2)
}{{else}}{{ encmd .MapKey "k2"}}{{end}}
if esep { ee.WriteMapElemValue() } if esep { ee.WriteMapElemValue() }
{{ encmd .Elem "v2"}} {{ encmd .Elem "v2"}}
} } */}}
} }
ee.WriteMapEnd() ee.WriteMapEnd()
} }
{{end}}{{end}}{{end}} {{end}}{{end}}{{end}}
// -- decode // -- decode
// -- -- fast path type switch // -- -- fast path type switch
func fastpathDecodeTypeSwitch(iv interface{}, d *Decoder) bool { func fastpathDecodeTypeSwitch(iv interface{}, d *Decoder) bool {
var changed bool
switch v := iv.(type) { switch v := iv.(type) {
{{range .Values}}{{if not .Primitive}}{{if not .MapKey }} {{range .Values}}{{if not .Primitive}}{{if not .MapKey }}{{if ne .Elem "uint8"}}
case []{{ .Elem }}:{{else}} case []{{ .Elem }}:
case map[{{ .MapKey }}]{{ .Elem }}:{{end}} var v2 []{{ .Elem }}
fastpathTV.{{ .MethodNamePfx "Dec" false }}V(v, false, d){{if not .MapKey }} v2, changed = fastpathTV.{{ .MethodNamePfx "Dec" false }}V(v, false, d)
case *[]{{ .Elem }}: {{else}} if changed && len(v) > 0 && len(v2) > 0 && !(len(v2) == len(v) && &v2[0] == &v[0]) {
case *map[{{ .MapKey }}]{{ .Elem }}: {{end}} copy(v, v2)
if v2, changed2 := fastpathTV.{{ .MethodNamePfx "Dec" false }}V(*v, true, d); changed2 {
*v = v2
} }
{{end}}{{end}} case *[]{{ .Elem }}:
var v2 []{{ .Elem }}
v2, changed = fastpathTV.{{ .MethodNamePfx "Dec" false }}V(*v, true, d)
if changed {
*v = v2
}{{/*
*/}}{{end}}{{end}}{{end}}{{end}}
{{range .Values}}{{if not .Primitive}}{{if .MapKey }}{{/*
// maps only change if nil, and in that case, there's no point copying
*/}}
case map[{{ .MapKey }}]{{ .Elem }}:
fastpathTV.{{ .MethodNamePfx "Dec" false }}V(v, false, d)
case *map[{{ .MapKey }}]{{ .Elem }}:
var v2 map[{{ .MapKey }}]{{ .Elem }}
v2, changed = fastpathTV.{{ .MethodNamePfx "Dec" false }}V(*v, true, d)
if changed {
*v = v2
}{{/*
*/}}{{end}}{{end}}{{end}}
default: default:
_ = v // TODO: workaround https://github.com/golang/go/issues/12927 (remove after go 1.6 release) _ = v // workaround https://github.com/golang/go/issues/12927 seen in go1.4
return false return false
} }
return true return true
} }
func fastpathDecodeSetZeroTypeSwitch(iv interface{}, d *Decoder) bool { func fastpathDecodeSetZeroTypeSwitch(iv interface{}) bool {
switch v := iv.(type) { switch v := iv.(type) {
{{range .Values}}{{if not .Primitive}}{{if not .MapKey }} {{range .Values}}{{if not .Primitive}}{{if not .MapKey }}
case *[]{{ .Elem }}: {{else}} case *[]{{ .Elem }}:
case *map[{{ .MapKey }}]{{ .Elem }}: {{end}} *v = nil {{/*
*v = nil */}}{{end}}{{end}}{{end}}
{{end}}{{end}} {{range .Values}}{{if not .Primitive}}{{if .MapKey }}
case *map[{{ .MapKey }}]{{ .Elem }}:
*v = nil {{/*
*/}}{{end}}{{end}}{{end}}
default: default:
_ = v // TODO: workaround https://github.com/golang/go/issues/12927 (remove after go 1.6 release) _ = v // workaround https://github.com/golang/go/issues/12927 seen in go1.4
return false return false
} }
return true return true
@ -311,38 +393,36 @@ Slices can change if they
- are addressable (from a ptr) - are addressable (from a ptr)
- are settable (e.g. contained in an interface{}) - are settable (e.g. contained in an interface{})
*/}} */}}
func (d *Decoder) {{ .MethodNamePfx "fastpathDec" false }}R(f *codecFnInfo, rv reflect.Value) { func (d *Decoder) {{ .MethodNamePfx "fastpathDec" false }}R(f *codecFnInfo, rv reflect.Value) {
if array := f.seq == seqTypeArray; !array && rv.Kind() == reflect.Ptr { if array := f.seq == seqTypeArray; !array && rv.Kind() == reflect.Ptr {
var vp = rv2i(rv).(*[]{{ .Elem }}) vp := rv2i(rv).(*[]{{ .Elem }})
if v, changed := fastpathTV.{{ .MethodNamePfx "Dec" false }}V(*vp, !array, d); changed { v, changed := fastpathTV.{{ .MethodNamePfx "Dec" false }}V(*vp, !array, d)
*vp = v if changed { *vp = v }
}
} else { } else {
fastpathTV.{{ .MethodNamePfx "Dec" false }}V(rv2i(rv).([]{{ .Elem }}), !array, d) v := rv2i(rv).([]{{ .Elem }})
v2, changed := fastpathTV.{{ .MethodNamePfx "Dec" false }}V(v, !array, d)
if changed && len(v) > 0 && len(v2) > 0 && !(len(v2) == len(v) && &v2[0] == &v[0]) {
copy(v, v2)
}
} }
} }
func (f fastpathT) {{ .MethodNamePfx "Dec" false }}X(vp *[]{{ .Elem }}, d *Decoder) { func (f fastpathT) {{ .MethodNamePfx "Dec" false }}X(vp *[]{{ .Elem }}, d *Decoder) {
if v, changed := f.{{ .MethodNamePfx "Dec" false }}V(*vp, true, d); changed { v, changed := f.{{ .MethodNamePfx "Dec" false }}V(*vp, true, d)
*vp = v if changed { *vp = v }
}
} }
func (_ fastpathT) {{ .MethodNamePfx "Dec" false }}V(v []{{ .Elem }}, canChange bool, d *Decoder) (_ []{{ .Elem }}, changed bool) { func (_ fastpathT) {{ .MethodNamePfx "Dec" false }}V(v []{{ .Elem }}, canChange bool, d *Decoder) (_ []{{ .Elem }}, changed bool) {
dd := d.d dd := d.d{{/*
{{/* // if dd.isContainerType(valueTypeNil) { dd.TryDecodeAsNil() */}} // if dd.isContainerType(valueTypeNil) { dd.TryDecodeAsNil()
*/}}
slh, containerLenS := d.decSliceHelperStart() slh, containerLenS := d.decSliceHelperStart()
if containerLenS == 0 { if containerLenS == 0 {
if canChange { if canChange {
if v == nil { if v == nil { v = []{{ .Elem }}{} } else if len(v) != 0 { v = v[:0] }
v = []{{ .Elem }}{}
} else if len(v) != 0 {
v = v[:0]
}
changed = true changed = true
} }
slh.End() slh.End()
return v, changed return v, changed
} }
hasLen := containerLenS > 0 hasLen := containerLenS > 0
var xlen int var xlen int
if hasLen && canChange { if hasLen && canChange {
@ -361,7 +441,7 @@ func (_ fastpathT) {{ .MethodNamePfx "Dec" false }}V(v []{{ .Elem }}, canChange
} }
j := 0 j := 0
for ; (hasLen && j < containerLenS) || !(hasLen || dd.CheckBreak()); j++ { for ; (hasLen && j < containerLenS) || !(hasLen || dd.CheckBreak()); j++ {
if j == 0 && len(v) == 0 { if j == 0 && len(v) == 0 && canChange {
if hasLen { if hasLen {
xlen = decInferLen(containerLenS, d.h.MaxInitLen, {{ .Size }}) xlen = decInferLen(containerLenS, d.h.MaxInitLen, {{ .Size }})
} else { } else {
@ -380,10 +460,12 @@ func (_ fastpathT) {{ .MethodNamePfx "Dec" false }}V(v []{{ .Elem }}, canChange
d.arrayCannotExpand(len(v), j+1) d.arrayCannotExpand(len(v), j+1)
decodeIntoBlank = true decodeIntoBlank = true
} }
} }
slh.ElemContainerState(j) slh.ElemContainerState(j)
if decodeIntoBlank { if decodeIntoBlank {
d.swallow() d.swallow()
} else if dd.TryDecodeAsNil() {
v[j] = {{ zerocmd .Elem }}
} else { } else {
{{ if eq .Elem "interface{}" }}d.decode(&v[j]){{ else }}v[j] = {{ decmd .Elem }}{{ end }} {{ if eq .Elem "interface{}" }}d.decode(&v[j]){{ else }}v[j] = {{ decmd .Elem }}{{ end }}
} }
@ -400,10 +482,8 @@ func (_ fastpathT) {{ .MethodNamePfx "Dec" false }}V(v []{{ .Elem }}, canChange
slh.End() slh.End()
return v, changed return v, changed
} }
{{end}}{{end}}{{end}} {{end}}{{end}}{{end}}
{{range .Values}}{{if not .Primitive}}{{if .MapKey }} {{range .Values}}{{if not .Primitive}}{{if .MapKey }}
{{/* {{/*
Maps can change if they are Maps can change if they are
@ -413,22 +493,21 @@ Maps can change if they are
func (d *Decoder) {{ .MethodNamePfx "fastpathDec" false }}R(f *codecFnInfo, rv reflect.Value) { func (d *Decoder) {{ .MethodNamePfx "fastpathDec" false }}R(f *codecFnInfo, rv reflect.Value) {
if rv.Kind() == reflect.Ptr { if rv.Kind() == reflect.Ptr {
vp := rv2i(rv).(*map[{{ .MapKey }}]{{ .Elem }}) vp := rv2i(rv).(*map[{{ .MapKey }}]{{ .Elem }})
if v, changed := fastpathTV.{{ .MethodNamePfx "Dec" false }}V(*vp, true, d); changed { v, changed := fastpathTV.{{ .MethodNamePfx "Dec" false }}V(*vp, true, d);
*vp = v if changed { *vp = v }
} } else {
return fastpathTV.{{ .MethodNamePfx "Dec" false }}V(rv2i(rv).(map[{{ .MapKey }}]{{ .Elem }}), false, d)
} }
fastpathTV.{{ .MethodNamePfx "Dec" false }}V(rv2i(rv).(map[{{ .MapKey }}]{{ .Elem }}), false, d)
} }
func (f fastpathT) {{ .MethodNamePfx "Dec" false }}X(vp *map[{{ .MapKey }}]{{ .Elem }}, d *Decoder) { func (f fastpathT) {{ .MethodNamePfx "Dec" false }}X(vp *map[{{ .MapKey }}]{{ .Elem }}, d *Decoder) {
if v, changed := f.{{ .MethodNamePfx "Dec" false }}V(*vp, true, d); changed { v, changed := f.{{ .MethodNamePfx "Dec" false }}V(*vp, true, d)
*vp = v if changed { *vp = v }
}
} }
func (_ fastpathT) {{ .MethodNamePfx "Dec" false }}V(v map[{{ .MapKey }}]{{ .Elem }}, canChange bool, func (_ fastpathT) {{ .MethodNamePfx "Dec" false }}V(v map[{{ .MapKey }}]{{ .Elem }}, canChange bool,
d *Decoder) (_ map[{{ .MapKey }}]{{ .Elem }}, changed bool) { d *Decoder) (_ map[{{ .MapKey }}]{{ .Elem }}, changed bool) {
dd, esep := d.d, d.hh.hasElemSeparators() dd, esep := d.d, d.hh.hasElemSeparators(){{/*
{{/* // if dd.isContainerType(valueTypeNil) {dd.TryDecodeAsNil() */}} // if dd.isContainerType(valueTypeNil) {dd.TryDecodeAsNil()
*/}}
containerLen := dd.ReadMapStart() containerLen := dd.ReadMapStart()
if canChange && v == nil { if canChange && v == nil {
xlen := decInferLen(containerLen, d.h.MaxInitLen, {{ .Size }}) xlen := decInferLen(containerLen, d.h.MaxInitLen, {{ .Size }})
@ -439,8 +518,8 @@ func (_ fastpathT) {{ .MethodNamePfx "Dec" false }}V(v map[{{ .MapKey }}]{{ .Ele
dd.ReadMapEnd() dd.ReadMapEnd()
return v, changed return v, changed
} }
{{ if eq .Elem "interface{}" }}mapGet := !d.h.MapValueReset && !d.h.InterfaceReset{{end}} {{ if eq .Elem "interface{}" }}mapGet := v != nil && !d.h.MapValueReset && !d.h.InterfaceReset
var mk {{ .MapKey }} {{end}}var mk {{ .MapKey }}
var mv {{ .Elem }} var mv {{ .Elem }}
hasLen := containerLen > 0 hasLen := containerLen > 0
for j := 0; (hasLen && j < containerLen) || !(hasLen || dd.CheckBreak()); j++ { for j := 0; (hasLen && j < containerLen) || !(hasLen || dd.CheckBreak()); j++ {
@ -452,17 +531,14 @@ func (_ fastpathT) {{ .MethodNamePfx "Dec" false }}V(v map[{{ .MapKey }}]{{ .Ele
}{{ else }}mk = {{ decmd .MapKey }}{{ end }} }{{ else }}mk = {{ decmd .MapKey }}{{ end }}
if esep { dd.ReadMapElemValue() } if esep { dd.ReadMapElemValue() }
if dd.TryDecodeAsNil() { if dd.TryDecodeAsNil() {
if d.h.DeleteOnNilMapValue { delete(v, mk) } else { v[mk] = {{ zerocmd .Elem }} } if v == nil {} else if d.h.DeleteOnNilMapValue { delete(v, mk) } else { v[mk] = {{ zerocmd .Elem }} }
continue continue
} }
{{ if eq .Elem "interface{}" }}if mapGet { mv = v[mk] } else { mv = nil } {{ if eq .Elem "interface{}" }}if mapGet { mv = v[mk] } else { mv = nil }
d.decode(&mv){{ else }}mv = {{ decmd .Elem }}{{ end }} d.decode(&mv){{ else }}mv = {{ decmd .Elem }}{{ end }}
if v != nil { if v != nil { v[mk] = mv }
v[mk] = mv
}
} }
dd.ReadMapEnd() dd.ReadMapEnd()
return v, changed return v, changed
} }
{{end}}{{end}}{{end}} {{end}}{{end}}{{end}}

View File

@ -1,3 +1,6 @@
// Copyright (c) 2012-2018 Ugorji Nwoke. All rights reserved.
// Use of this source code is governed by a MIT license found in the LICENSE file.
// +build notfastpath // +build notfastpath
package codec package codec
@ -14,11 +17,11 @@ const fastpathEnabled = false
// This tag disables fastpath during build, allowing for faster build, test execution, // This tag disables fastpath during build, allowing for faster build, test execution,
// short-program runs, etc. // short-program runs, etc.
func fastpathDecodeTypeSwitch(iv interface{}, d *Decoder) bool { return false } func fastpathDecodeTypeSwitch(iv interface{}, d *Decoder) bool { return false }
func fastpathEncodeTypeSwitch(iv interface{}, e *Encoder) bool { return false } func fastpathEncodeTypeSwitch(iv interface{}, e *Encoder) bool { return false }
func fastpathEncodeTypeSwitchSlice(iv interface{}, e *Encoder) bool { return false } func fastpathEncodeTypeSwitchSlice(iv interface{}, e *Encoder) bool { return false }
func fastpathEncodeTypeSwitchMap(iv interface{}, e *Encoder) bool { return false } func fastpathEncodeTypeSwitchMap(iv interface{}, e *Encoder) bool { return false }
func fastpathDecodeSetZeroTypeSwitch(iv interface{}, d *Decoder) bool { return false } func fastpathDecodeSetZeroTypeSwitch(iv interface{}) bool { return false }
type fastpathT struct{} type fastpathT struct{}
type fastpathE struct { type fastpathE struct {
@ -31,6 +34,12 @@ type fastpathA [0]fastpathE
func (x fastpathA) index(rtid uintptr) int { return -1 } func (x fastpathA) index(rtid uintptr) int { return -1 }
func (_ fastpathT) DecSliceUint8V(v []uint8, canChange bool, d *Decoder) (_ []uint8, changed bool) {
fn := d.cfer().get(uint8SliceTyp, true, true)
d.kSlice(&fn.i, reflect.ValueOf(&v).Elem())
return v, true
}
var fastpathAV fastpathA var fastpathAV fastpathA
var fastpathTV fastpathT var fastpathTV fastpathT

View File

@ -43,7 +43,7 @@ if {{var "l"}} == 0 {
{{var "c"}} = true {{var "c"}} = true
}{{end}} }{{end}}
{{var "h"}}.ElemContainerState({{var "j"}}) {{var "h"}}.ElemContainerState({{var "j"}})
// {{var "dn"}} = r.TryDecodeAsNil() {{/* {{var "dn"}} = r.TryDecodeAsNil() */}}
{{if isChan}}{{ $x := printf "%[1]vv%[2]v" .TempVar .Rand }}var {{var $x}} {{ .Typ }} {{if isChan}}{{ $x := printf "%[1]vv%[2]v" .TempVar .Rand }}var {{var $x}} {{ .Typ }}
{{ decLineVar $x }} {{ decLineVar $x }}
{{var "v"}} <- {{ $x }} {{var "v"}} <- {{ $x }}

View File

@ -3,10 +3,7 @@
// Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved. // Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved.
// Use of this source code is governed by a MIT license found in the LICENSE file. // Use of this source code is governed by a MIT license found in the LICENSE file.
// ************************************************************ // Code generated from gen-helper.go.tmpl - DO NOT EDIT.
// DO NOT EDIT.
// THIS FILE IS AUTO-GENERATED from gen-helper.go.tmpl
// ************************************************************
package codec package codec
@ -31,25 +28,73 @@ const GenVersion = 8
// GenHelperEncoder is exported so that it can be used externally by codecgen. // GenHelperEncoder is exported so that it can be used externally by codecgen.
// //
// Library users: DO NOT USE IT DIRECTLY. IT WILL CHANGE CONTINOUSLY WITHOUT NOTICE. // Library users: DO NOT USE IT DIRECTLY. IT WILL CHANGE CONTINOUSLY WITHOUT NOTICE.
func GenHelperEncoder(e *Encoder) (genHelperEncoder, encDriver) { func GenHelperEncoder(e *Encoder) (ge genHelperEncoder, ee genHelperEncDriver) {
return genHelperEncoder{e: e}, e.e ge = genHelperEncoder{e: e}
ee = genHelperEncDriver{encDriver: e.e}
return
} }
// GenHelperDecoder is exported so that it can be used externally by codecgen. // GenHelperDecoder is exported so that it can be used externally by codecgen.
// //
// Library users: DO NOT USE IT DIRECTLY. IT WILL CHANGE CONTINOUSLY WITHOUT NOTICE. // Library users: DO NOT USE IT DIRECTLY. IT WILL CHANGE CONTINOUSLY WITHOUT NOTICE.
func GenHelperDecoder(d *Decoder) (genHelperDecoder, decDriver) { func GenHelperDecoder(d *Decoder) (gd genHelperDecoder, dd genHelperDecDriver) {
return genHelperDecoder{d: d}, d.d gd = genHelperDecoder{d: d}
dd = genHelperDecDriver{decDriver: d.d}
return
}
type genHelperEncDriver struct {
encDriver
}
func (x genHelperEncDriver) EncodeBuiltin(rt uintptr, v interface{}) {}
func (x genHelperEncDriver) EncStructFieldKey(keyType valueType, s string) {
encStructFieldKey(x.encDriver, keyType, s)
}
func (x genHelperEncDriver) EncodeSymbol(s string) {
x.encDriver.EncodeString(cUTF8, s)
}
type genHelperDecDriver struct {
decDriver
C checkOverflow
}
func (x genHelperDecDriver) DecodeBuiltin(rt uintptr, v interface{}) {}
func (x genHelperDecDriver) DecStructFieldKey(keyType valueType, buf *[decScratchByteArrayLen]byte) []byte {
return decStructFieldKey(x.decDriver, keyType, buf)
}
func (x genHelperDecDriver) DecodeInt(bitsize uint8) (i int64) {
return x.C.IntV(x.decDriver.DecodeInt64(), bitsize)
}
func (x genHelperDecDriver) DecodeUint(bitsize uint8) (ui uint64) {
return x.C.UintV(x.decDriver.DecodeUint64(), bitsize)
}
func (x genHelperDecDriver) DecodeFloat(chkOverflow32 bool) (f float64) {
f = x.DecodeFloat64()
if chkOverflow32 && chkOvf.Float32(f) {
panicv.errorf("float32 overflow: %v", f)
}
return
}
func (x genHelperDecDriver) DecodeFloat32As64() (f float64) {
f = x.DecodeFloat64()
if chkOvf.Float32(f) {
panicv.errorf("float32 overflow: %v", f)
}
return
} }
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
type genHelperEncoder struct { type genHelperEncoder struct {
M must
e *Encoder e *Encoder
F fastpathT F fastpathT
} }
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
type genHelperDecoder struct { type genHelperDecoder struct {
C checkOverflow
d *Decoder d *Decoder
F fastpathT F fastpathT
} }
@ -61,7 +106,12 @@ func (f genHelperEncoder) EncBasicHandle() *BasicHandle {
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
func (f genHelperEncoder) EncBinary() bool { func (f genHelperEncoder) EncBinary() bool {
return f.e.cf.be // f.e.hh.isBinaryEncoding() return f.e.be // f.e.hh.isBinaryEncoding()
}
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
func (f genHelperEncoder) IsJSONHandle() bool {
return f.e.js
} }
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
@ -74,52 +124,65 @@ func (f genHelperEncoder) EncFallback(iv interface{}) {
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
func (f genHelperEncoder) EncTextMarshal(iv encoding.TextMarshaler) { func (f genHelperEncoder) EncTextMarshal(iv encoding.TextMarshaler) {
bs, fnerr := iv.MarshalText() bs, fnerr := iv.MarshalText()
f.e.marshal(bs, fnerr, false, c_UTF8) f.e.marshal(bs, fnerr, false, cUTF8)
} }
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
func (f genHelperEncoder) EncJSONMarshal(iv jsonMarshaler) { func (f genHelperEncoder) EncJSONMarshal(iv jsonMarshaler) {
bs, fnerr := iv.MarshalJSON() bs, fnerr := iv.MarshalJSON()
f.e.marshal(bs, fnerr, true, c_UTF8) f.e.marshal(bs, fnerr, true, cUTF8)
} }
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
func (f genHelperEncoder) EncBinaryMarshal(iv encoding.BinaryMarshaler) { func (f genHelperEncoder) EncBinaryMarshal(iv encoding.BinaryMarshaler) {
bs, fnerr := iv.MarshalBinary() bs, fnerr := iv.MarshalBinary()
f.e.marshal(bs, fnerr, false, c_RAW) f.e.marshal(bs, fnerr, false, cRAW)
} }
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
func (f genHelperEncoder) EncRaw(iv Raw) { func (f genHelperEncoder) EncRaw(iv Raw) { f.e.rawBytes(iv) }
f.e.rawBytes(iv)
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
//
// Deprecated: builtin no longer supported - so we make this method a no-op,
// but leave in-place so that old generated files continue to work without regeneration.
func (f genHelperEncoder) TimeRtidIfBinc() (v uintptr) { return }
// func (f genHelperEncoder) TimeRtidIfBinc() uintptr {
// if _, ok := f.e.hh.(*BincHandle); ok {
// return timeTypId
// }
// }
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
func (f genHelperEncoder) I2Rtid(v interface{}) uintptr {
return i2rtid(v)
} }
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
func (f genHelperEncoder) TimeRtidIfBinc() uintptr { func (f genHelperEncoder) Extension(rtid uintptr) (xfn *extTypeTagFn) {
if _, ok := f.e.hh.(*BincHandle); ok { return f.e.h.getExt(rtid)
return timeTypId
}
return 0
} }
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
func (f genHelperEncoder) IsJSONHandle() bool { func (f genHelperEncoder) EncExtension(v interface{}, xfFn *extTypeTagFn) {
return f.e.cf.js f.e.e.EncodeExt(v, xfFn.tag, xfFn.ext, f.e)
} }
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
//
// Deprecated: No longer used,
// but leave in-place so that old generated files continue to work without regeneration.
func (f genHelperEncoder) HasExtensions() bool { func (f genHelperEncoder) HasExtensions() bool {
return len(f.e.h.extHandle) != 0 return len(f.e.h.extHandle) != 0
} }
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
//
// Deprecated: No longer used,
// but leave in-place so that old generated files continue to work without regeneration.
func (f genHelperEncoder) EncExt(v interface{}) (r bool) { func (f genHelperEncoder) EncExt(v interface{}) (r bool) {
rt := reflect.TypeOf(v) if xfFn := f.e.h.getExt(i2rtid(v)); xfFn != nil {
if rt.Kind() == reflect.Ptr {
rt = rt.Elem()
}
rtid := rt2id(rt)
if xfFn := f.e.h.getExt(rtid); xfFn != nil {
f.e.e.EncodeExt(v, xfFn.tag, xfFn.ext, f.e) f.e.e.EncodeExt(v, xfFn.tag, xfFn.ext, f.e)
return true return true
} }
@ -139,15 +202,18 @@ func (f genHelperDecoder) DecBinary() bool {
} }
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
func (f genHelperDecoder) DecSwallow() { func (f genHelperDecoder) DecSwallow() { f.d.swallow() }
f.d.swallow()
}
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
func (f genHelperDecoder) DecScratchBuffer() []byte { func (f genHelperDecoder) DecScratchBuffer() []byte {
return f.d.b[:] return f.d.b[:]
} }
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
func (f genHelperDecoder) DecScratchArrayBuffer() *[decScratchByteArrayLen]byte {
return &f.d.b
}
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
func (f genHelperDecoder) DecFallback(iv interface{}, chkPtr bool) { func (f genHelperDecoder) DecFallback(iv interface{}, chkPtr bool) {
// println(">>>>>>>>> DecFallback") // println(">>>>>>>>> DecFallback")
@ -155,7 +221,7 @@ func (f genHelperDecoder) DecFallback(iv interface{}, chkPtr bool) {
if chkPtr { if chkPtr {
rv = f.d.ensureDecodeable(rv) rv = f.d.ensureDecodeable(rv)
} }
f.d.decodeValue(rv, nil, false, false) f.d.decodeValue(rv, nil, false)
// f.d.decodeValueFallback(rv) // f.d.decodeValueFallback(rv)
} }
@ -201,17 +267,21 @@ func (f genHelperDecoder) DecBinaryUnmarshal(bm encoding.BinaryUnmarshaler) {
} }
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
func (f genHelperDecoder) DecRaw() []byte { func (f genHelperDecoder) DecRaw() []byte { return f.d.rawBytes() }
return f.d.rawBytes()
}
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
func (f genHelperDecoder) TimeRtidIfBinc() uintptr { //
if _, ok := f.d.hh.(*BincHandle); ok { // Deprecated: builtin no longer supported - so we make this method a no-op,
return timeTypId // but leave in-place so that old generated files continue to work without regeneration.
} func (f genHelperDecoder) TimeRtidIfBinc() (v uintptr) { return }
return 0
} // func (f genHelperDecoder) TimeRtidIfBinc() uintptr {
// // Note: builtin is no longer supported - so make this a no-op
// if _, ok := f.d.hh.(*BincHandle); ok {
// return timeTypId
// }
// return 0
// }
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
func (f genHelperDecoder) IsJSONHandle() bool { func (f genHelperDecoder) IsJSONHandle() bool {
@ -219,15 +289,34 @@ func (f genHelperDecoder) IsJSONHandle() bool {
} }
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
func (f genHelperDecoder) I2Rtid(v interface{}) uintptr {
return i2rtid(v)
}
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
func (f genHelperDecoder) Extension(rtid uintptr) (xfn *extTypeTagFn) {
return f.d.h.getExt(rtid)
}
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
func (f genHelperDecoder) DecExtension(v interface{}, xfFn *extTypeTagFn) {
f.d.d.DecodeExt(v, xfFn.tag, xfFn.ext)
}
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
//
// Deprecated: No longer used,
// but leave in-place so that old generated files continue to work without regeneration.
func (f genHelperDecoder) HasExtensions() bool { func (f genHelperDecoder) HasExtensions() bool {
return len(f.d.h.extHandle) != 0 return len(f.d.h.extHandle) != 0
} }
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
//
// Deprecated: No longer used,
// but leave in-place so that old generated files continue to work without regeneration.
func (f genHelperDecoder) DecExt(v interface{}) (r bool) { func (f genHelperDecoder) DecExt(v interface{}) (r bool) {
rt := reflect.TypeOf(v).Elem() if xfFn := f.d.h.getExt(i2rtid(v)); xfFn != nil {
rtid := rt2id(rt)
if xfFn := f.d.h.getExt(rtid); xfFn != nil {
f.d.d.DecodeExt(v, xfFn.tag, xfFn.ext) f.d.d.DecodeExt(v, xfFn.tag, xfFn.ext)
return true return true
} }
@ -240,6 +329,7 @@ func (f genHelperDecoder) DecInferLen(clen, maxlen, unit int) (rvlen int) {
} }
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
func (f genHelperDecoder) StringView(v []byte) string { //
return stringView(v) // Deprecated: no longer used,
} // but leave in-place so that old generated files continue to work without regeneration.
func (f genHelperDecoder) StringView(v []byte) string { return stringView(v) }

View File

@ -3,10 +3,7 @@
// Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved. // Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved.
// Use of this source code is governed by a MIT license found in the LICENSE file. // Use of this source code is governed by a MIT license found in the LICENSE file.
// ************************************************************ // Code generated from gen-helper.go.tmpl - DO NOT EDIT.
// DO NOT EDIT.
// THIS FILE IS AUTO-GENERATED from gen-helper.go.tmpl
// ************************************************************
package codec package codec
@ -21,35 +18,83 @@ const GenVersion = {{ .Version }}
// This file is used to generate helper code for codecgen. // This file is used to generate helper code for codecgen.
// The values here i.e. genHelper(En|De)coder are not to be used directly by // The values here i.e. genHelper(En|De)coder are not to be used directly by
// library users. They WILL change continuously and without notice. // library users. They WILL change continuously and without notice.
// //
// To help enforce this, we create an unexported type with exported members. // To help enforce this, we create an unexported type with exported members.
// The only way to get the type is via the one exported type that we control (somewhat). // The only way to get the type is via the one exported type that we control (somewhat).
// //
// When static codecs are created for types, they will use this value // When static codecs are created for types, they will use this value
// to perform encoding or decoding of primitives or known slice or map types. // to perform encoding or decoding of primitives or known slice or map types.
// GenHelperEncoder is exported so that it can be used externally by codecgen. // GenHelperEncoder is exported so that it can be used externally by codecgen.
// //
// Library users: DO NOT USE IT DIRECTLY. IT WILL CHANGE CONTINOUSLY WITHOUT NOTICE. // Library users: DO NOT USE IT DIRECTLY. IT WILL CHANGE CONTINOUSLY WITHOUT NOTICE.
func GenHelperEncoder(e *Encoder) (genHelperEncoder, encDriver) { func GenHelperEncoder(e *Encoder) (ge genHelperEncoder, ee genHelperEncDriver) {
return genHelperEncoder{e:e}, e.e ge = genHelperEncoder{e: e}
ee = genHelperEncDriver{encDriver: e.e}
return
} }
// GenHelperDecoder is exported so that it can be used externally by codecgen. // GenHelperDecoder is exported so that it can be used externally by codecgen.
// //
// Library users: DO NOT USE IT DIRECTLY. IT WILL CHANGE CONTINOUSLY WITHOUT NOTICE. // Library users: DO NOT USE IT DIRECTLY. IT WILL CHANGE CONTINOUSLY WITHOUT NOTICE.
func GenHelperDecoder(d *Decoder) (genHelperDecoder, decDriver) { func GenHelperDecoder(d *Decoder) (gd genHelperDecoder, dd genHelperDecDriver) {
return genHelperDecoder{d:d}, d.d gd = genHelperDecoder{d: d}
dd = genHelperDecDriver{decDriver: d.d}
return
}
type genHelperEncDriver struct {
encDriver
}
func (x genHelperEncDriver) EncodeBuiltin(rt uintptr, v interface{}) {}
func (x genHelperEncDriver) EncStructFieldKey(keyType valueType, s string) {
encStructFieldKey(x.encDriver, keyType, s)
}
func (x genHelperEncDriver) EncodeSymbol(s string) {
x.encDriver.EncodeString(cUTF8, s)
}
type genHelperDecDriver struct {
decDriver
C checkOverflow
}
func (x genHelperDecDriver) DecodeBuiltin(rt uintptr, v interface{}) {}
func (x genHelperDecDriver) DecStructFieldKey(keyType valueType, buf *[decScratchByteArrayLen]byte) []byte {
return decStructFieldKey(x.decDriver, keyType, buf)
}
func (x genHelperDecDriver) DecodeInt(bitsize uint8) (i int64) {
return x.C.IntV(x.decDriver.DecodeInt64(), bitsize)
}
func (x genHelperDecDriver) DecodeUint(bitsize uint8) (ui uint64) {
return x.C.UintV(x.decDriver.DecodeUint64(), bitsize)
}
func (x genHelperDecDriver) DecodeFloat(chkOverflow32 bool) (f float64) {
f = x.DecodeFloat64()
if chkOverflow32 && chkOvf.Float32(f) {
panicv.errorf("float32 overflow: %v", f)
}
return
}
func (x genHelperDecDriver) DecodeFloat32As64() (f float64) {
f = x.DecodeFloat64()
if chkOvf.Float32(f) {
panicv.errorf("float32 overflow: %v", f)
}
return
} }
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
type genHelperEncoder struct { type genHelperEncoder struct {
M must
e *Encoder e *Encoder
F fastpathT F fastpathT
} }
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
type genHelperDecoder struct { type genHelperDecoder struct {
C checkOverflow
d *Decoder d *Decoder
F fastpathT F fastpathT
} }
@ -58,10 +103,13 @@ type genHelperDecoder struct {
func (f genHelperEncoder) EncBasicHandle() *BasicHandle { func (f genHelperEncoder) EncBasicHandle() *BasicHandle {
return f.e.h return f.e.h
} }
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
func (f genHelperEncoder) EncBinary() bool { func (f genHelperEncoder) EncBinary() bool {
return f.e.cf.be // f.e.hh.isBinaryEncoding() return f.e.be // f.e.hh.isBinaryEncoding()
}
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
func (f genHelperEncoder) IsJSONHandle() bool {
return f.e.js
} }
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
func (f genHelperEncoder) EncFallback(iv interface{}) { func (f genHelperEncoder) EncFallback(iv interface{}) {
@ -72,45 +120,56 @@ func (f genHelperEncoder) EncFallback(iv interface{}) {
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
func (f genHelperEncoder) EncTextMarshal(iv encoding.TextMarshaler) { func (f genHelperEncoder) EncTextMarshal(iv encoding.TextMarshaler) {
bs, fnerr := iv.MarshalText() bs, fnerr := iv.MarshalText()
f.e.marshal(bs, fnerr, false, c_UTF8) f.e.marshal(bs, fnerr, false, cUTF8)
} }
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
func (f genHelperEncoder) EncJSONMarshal(iv jsonMarshaler) { func (f genHelperEncoder) EncJSONMarshal(iv jsonMarshaler) {
bs, fnerr := iv.MarshalJSON() bs, fnerr := iv.MarshalJSON()
f.e.marshal(bs, fnerr, true, c_UTF8) f.e.marshal(bs, fnerr, true, cUTF8)
} }
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
func (f genHelperEncoder) EncBinaryMarshal(iv encoding.BinaryMarshaler) { func (f genHelperEncoder) EncBinaryMarshal(iv encoding.BinaryMarshaler) {
bs, fnerr := iv.MarshalBinary() bs, fnerr := iv.MarshalBinary()
f.e.marshal(bs, fnerr, false, c_RAW) f.e.marshal(bs, fnerr, false, cRAW)
} }
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
func (f genHelperEncoder) EncRaw(iv Raw) { func (f genHelperEncoder) EncRaw(iv Raw) { f.e.rawBytes(iv) }
f.e.rawBytes(iv) // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
//
// Deprecated: builtin no longer supported - so we make this method a no-op,
// but leave in-place so that old generated files continue to work without regeneration.
func (f genHelperEncoder) TimeRtidIfBinc() (v uintptr) { return }
// func (f genHelperEncoder) TimeRtidIfBinc() uintptr {
// if _, ok := f.e.hh.(*BincHandle); ok {
// return timeTypId
// }
// }
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
func (f genHelperEncoder) I2Rtid(v interface{}) uintptr {
return i2rtid(v)
} }
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
func (f genHelperEncoder) TimeRtidIfBinc() uintptr { func (f genHelperEncoder) Extension(rtid uintptr) (xfn *extTypeTagFn) {
if _, ok := f.e.hh.(*BincHandle); ok { return f.e.h.getExt(rtid)
return timeTypId
}
return 0
} }
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
func (f genHelperEncoder) IsJSONHandle() bool { func (f genHelperEncoder) EncExtension(v interface{}, xfFn *extTypeTagFn) {
return f.e.cf.js f.e.e.EncodeExt(v, xfFn.tag, xfFn.ext, f.e)
} }
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
//
// Deprecated: No longer used,
// but leave in-place so that old generated files continue to work without regeneration.
func (f genHelperEncoder) HasExtensions() bool { func (f genHelperEncoder) HasExtensions() bool {
return len(f.e.h.extHandle) != 0 return len(f.e.h.extHandle) != 0
} }
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
//
// Deprecated: No longer used,
// but leave in-place so that old generated files continue to work without regeneration.
func (f genHelperEncoder) EncExt(v interface{}) (r bool) { func (f genHelperEncoder) EncExt(v interface{}) (r bool) {
rt := reflect.TypeOf(v) if xfFn := f.e.h.getExt(i2rtid(v)); xfFn != nil {
if rt.Kind() == reflect.Ptr {
rt = rt.Elem()
}
rtid := rt2id(rt)
if xfFn := f.e.h.getExt(rtid); xfFn != nil {
f.e.e.EncodeExt(v, xfFn.tag, xfFn.ext, f.e) f.e.e.EncodeExt(v, xfFn.tag, xfFn.ext, f.e)
return true return true
} }
@ -128,21 +187,23 @@ func (f genHelperDecoder) DecBinary() bool {
return f.d.be // f.d.hh.isBinaryEncoding() return f.d.be // f.d.hh.isBinaryEncoding()
} }
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
func (f genHelperDecoder) DecSwallow() { func (f genHelperDecoder) DecSwallow() { f.d.swallow() }
f.d.swallow()
}
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
func (f genHelperDecoder) DecScratchBuffer() []byte { func (f genHelperDecoder) DecScratchBuffer() []byte {
return f.d.b[:] return f.d.b[:]
} }
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
func (f genHelperDecoder) DecScratchArrayBuffer() *[decScratchByteArrayLen]byte {
return &f.d.b
}
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
func (f genHelperDecoder) DecFallback(iv interface{}, chkPtr bool) { func (f genHelperDecoder) DecFallback(iv interface{}, chkPtr bool) {
// println(">>>>>>>>> DecFallback") // println(">>>>>>>>> DecFallback")
rv := reflect.ValueOf(iv) rv := reflect.ValueOf(iv)
if chkPtr { if chkPtr {
rv = f.d.ensureDecodeable(rv) rv = f.d.ensureDecodeable(rv)
} }
f.d.decodeValue(rv, nil, false, false) f.d.decodeValue(rv, nil, false)
// f.d.decodeValueFallback(rv) // f.d.decodeValueFallback(rv)
} }
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
@ -181,29 +242,49 @@ func (f genHelperDecoder) DecBinaryUnmarshal(bm encoding.BinaryUnmarshaler) {
} }
} }
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
func (f genHelperDecoder) DecRaw() []byte { func (f genHelperDecoder) DecRaw() []byte { return f.d.rawBytes() }
return f.d.rawBytes()
}
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
func (f genHelperDecoder) TimeRtidIfBinc() uintptr { //
if _, ok := f.d.hh.(*BincHandle); ok { // Deprecated: builtin no longer supported - so we make this method a no-op,
return timeTypId // but leave in-place so that old generated files continue to work without regeneration.
} func (f genHelperDecoder) TimeRtidIfBinc() (v uintptr) { return }
return 0 // func (f genHelperDecoder) TimeRtidIfBinc() uintptr {
} // // Note: builtin is no longer supported - so make this a no-op
// if _, ok := f.d.hh.(*BincHandle); ok {
// return timeTypId
// }
// return 0
// }
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
func (f genHelperDecoder) IsJSONHandle() bool { func (f genHelperDecoder) IsJSONHandle() bool {
return f.d.js return f.d.js
} }
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
func (f genHelperDecoder) I2Rtid(v interface{}) uintptr {
return i2rtid(v)
}
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
func (f genHelperDecoder) Extension(rtid uintptr) (xfn *extTypeTagFn) {
return f.d.h.getExt(rtid)
}
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
func (f genHelperDecoder) DecExtension(v interface{}, xfFn *extTypeTagFn) {
f.d.d.DecodeExt(v, xfFn.tag, xfFn.ext)
}
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
//
// Deprecated: No longer used,
// but leave in-place so that old generated files continue to work without regeneration.
func (f genHelperDecoder) HasExtensions() bool { func (f genHelperDecoder) HasExtensions() bool {
return len(f.d.h.extHandle) != 0 return len(f.d.h.extHandle) != 0
} }
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
//
// Deprecated: No longer used,
// but leave in-place so that old generated files continue to work without regeneration.
func (f genHelperDecoder) DecExt(v interface{}) (r bool) { func (f genHelperDecoder) DecExt(v interface{}) (r bool) {
rt := reflect.TypeOf(v).Elem() if xfFn := f.d.h.getExt(i2rtid(v)); xfFn != nil {
rtid := rt2id(rt)
if xfFn := f.d.h.getExt(rtid); xfFn != nil {
f.d.d.DecodeExt(v, xfFn.tag, xfFn.ext) f.d.d.DecodeExt(v, xfFn.tag, xfFn.ext)
return true return true
} }
@ -214,7 +295,8 @@ func (f genHelperDecoder) DecInferLen(clen, maxlen, unit int) (rvlen int) {
return decInferLen(clen, maxlen, unit) return decInferLen(clen, maxlen, unit)
} }
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
func (f genHelperDecoder) StringView(v []byte) string { //
return stringView(v) // Deprecated: no longer used,
} // but leave in-place so that old generated files continue to work without regeneration.
func (f genHelperDecoder) StringView(v []byte) string { return stringView(v) }

View File

@ -1,3 +1,5 @@
// +build codecgen.exec
// Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved. // Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved.
// Use of this source code is governed by a MIT license found in the LICENSE file. // Use of this source code is governed by a MIT license found in the LICENSE file.
@ -96,7 +98,7 @@ if {{var "l"}} == 0 {
{{var "c"}} = true {{var "c"}} = true
}{{end}} }{{end}}
{{var "h"}}.ElemContainerState({{var "j"}}) {{var "h"}}.ElemContainerState({{var "j"}})
// {{var "dn"}} = r.TryDecodeAsNil() {{/* {{var "dn"}} = r.TryDecodeAsNil() */}}
{{if isChan}}{{ $x := printf "%[1]vv%[2]v" .TempVar .Rand }}var {{var $x}} {{ .Typ }} {{if isChan}}{{ $x := printf "%[1]vv%[2]v" .TempVar .Rand }}var {{var $x}} {{ .Typ }}
{{ decLineVar $x }} {{ decLineVar $x }}
{{var "v"}} <- {{ $x }} {{var "v"}} <- {{ $x }}
@ -129,4 +131,3 @@ if {{var "l"}} == 0 {
}{{end}} }{{end}}
` `

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,4 @@
// Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved. // Copyright (c) 2012-2018 Ugorji Nwoke. All rights reserved.
// Use of this source code is governed by a MIT license found in the LICENSE file. // Use of this source code is governed by a MIT license found in the LICENSE file.
// +build go1.5 // +build go1.5

View File

@ -1,4 +1,4 @@
// Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved. // Copyright (c) 2012-2018 Ugorji Nwoke. All rights reserved.
// Use of this source code is governed by a MIT license found in the LICENSE file. // Use of this source code is governed by a MIT license found in the LICENSE file.
// +build !go1.5 // +build !go1.5

View File

@ -1,4 +1,4 @@
// Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved. // Copyright (c) 2012-2018 Ugorji Nwoke. All rights reserved.
// Use of this source code is governed by a MIT license found in the LICENSE file. // Use of this source code is governed by a MIT license found in the LICENSE file.
// +build go1.9 // +build go1.9

View File

@ -1,4 +1,4 @@
// Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved. // Copyright (c) 2012-2018 Ugorji Nwoke. All rights reserved.
// Use of this source code is governed by a MIT license found in the LICENSE file. // Use of this source code is governed by a MIT license found in the LICENSE file.
// +build !go1.9 // +build !go1.9

View File

@ -0,0 +1,8 @@
// Copyright (c) 2012-2018 Ugorji Nwoke. All rights reserved.
// Use of this source code is governed by a MIT license found in the LICENSE file.
// +build go1.10
package codec
const allowSetUnexportedEmbeddedPtr = false

View File

@ -0,0 +1,8 @@
// Copyright (c) 2012-2018 Ugorji Nwoke. All rights reserved.
// Use of this source code is governed by a MIT license found in the LICENSE file.
// +build !go1.10
package codec
const allowSetUnexportedEmbeddedPtr = true

View File

@ -1,4 +1,4 @@
// Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved. // Copyright (c) 2012-2018 Ugorji Nwoke. All rights reserved.
// Use of this source code is governed by a MIT license found in the LICENSE file. // Use of this source code is governed by a MIT license found in the LICENSE file.
// +build !go1.4 // +build !go1.4

View File

@ -1,4 +1,4 @@
// Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved. // Copyright (c) 2012-2018 Ugorji Nwoke. All rights reserved.
// Use of this source code is governed by a MIT license found in the LICENSE file. // Use of this source code is governed by a MIT license found in the LICENSE file.
// +build go1.5,!go1.6 // +build go1.5,!go1.6

View File

@ -1,4 +1,4 @@
// Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved. // Copyright (c) 2012-2018 Ugorji Nwoke. All rights reserved.
// Use of this source code is governed by a MIT license found in the LICENSE file. // Use of this source code is governed by a MIT license found in the LICENSE file.
// +build go1.6,!go1.7 // +build go1.6,!go1.7

View File

@ -1,4 +1,4 @@
// Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved. // Copyright (c) 2012-2018 Ugorji Nwoke. All rights reserved.
// Use of this source code is governed by a MIT license found in the LICENSE file. // Use of this source code is governed by a MIT license found in the LICENSE file.
// +build go1.7 // +build go1.7

View File

@ -1,4 +1,4 @@
// Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved. // Copyright (c) 2012-2018 Ugorji Nwoke. All rights reserved.
// Use of this source code is governed by a MIT license found in the LICENSE file. // Use of this source code is governed by a MIT license found in the LICENSE file.
// +build !go1.5 // +build !go1.5

File diff suppressed because it is too large Load Diff

View File

@ -6,73 +6,6 @@ package codec
// All non-std package dependencies live in this file, // All non-std package dependencies live in this file,
// so porting to different environment is easy (just update functions). // so porting to different environment is easy (just update functions).
import (
"errors"
"fmt"
"reflect"
)
func panicValToErr(panicVal interface{}, err *error) {
if panicVal == nil {
return
}
// case nil
switch xerr := panicVal.(type) {
case error:
*err = xerr
case string:
*err = errors.New(xerr)
default:
*err = fmt.Errorf("%v", panicVal)
}
return
}
func hIsEmptyValue(v reflect.Value, deref, checkStruct bool) bool {
switch v.Kind() {
case reflect.Invalid:
return true
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
return v.Len() == 0
case reflect.Bool:
return !v.Bool()
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return v.Int() == 0
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return v.Uint() == 0
case reflect.Float32, reflect.Float64:
return v.Float() == 0
case reflect.Interface, reflect.Ptr:
if deref {
if v.IsNil() {
return true
}
return hIsEmptyValue(v.Elem(), deref, checkStruct)
} else {
return v.IsNil()
}
case reflect.Struct:
if !checkStruct {
return false
}
// return true if all fields are empty. else return false.
// we cannot use equality check, because some fields may be maps/slices/etc
// and consequently the structs are not comparable.
// return v.Interface() == reflect.Zero(v.Type()).Interface()
for i, n := 0, v.NumField(); i < n; i++ {
if !hIsEmptyValue(v.Field(i), deref, checkStruct) {
return false
}
}
return true
}
return false
}
func isEmptyValue(v reflect.Value, deref, checkStruct bool) bool {
return hIsEmptyValue(v, deref, checkStruct)
}
func pruneSignExt(v []byte, pos bool) (n int) { func pruneSignExt(v []byte, pos bool) (n int) {
if len(v) < 2 { if len(v) < 2 {
} else if pos && v[0] == 0 { } else if pos && v[0] == 0 {
@ -97,21 +30,20 @@ func halfFloatToFloatBits(yy uint16) (d uint32) {
if e == 0 { if e == 0 {
if m == 0 { // plu or minus 0 if m == 0 { // plu or minus 0
return s << 31 return s << 31
} else { // Denormalized number -- renormalize it
for (m & 0x00000400) == 0 {
m <<= 1
e -= 1
}
e += 1
const zz uint32 = 0x0400
m &= ^zz
} }
// Denormalized number -- renormalize it
for (m & 0x00000400) == 0 {
m <<= 1
e -= 1
}
e += 1
const zz uint32 = 0x0400
m &= ^zz
} else if e == 31 { } else if e == 31 {
if m == 0 { // Inf if m == 0 { // Inf
return (s << 31) | 0x7f800000 return (s << 31) | 0x7f800000
} else { // NaN
return (s << 31) | 0x7f800000 | (m << 13)
} }
return (s << 31) | 0x7f800000 | (m << 13) // NaN
} }
e = e + (127 - 15) e = e + (127 - 15)
m = m << 13 m = m << 13

View File

@ -1,6 +1,6 @@
// +build !go1.7 safe appengine // +build !go1.7 safe appengine
// Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved. // Copyright (c) 2012-2018 Ugorji Nwoke. All rights reserved.
// Use of this source code is governed by a MIT license found in the LICENSE file. // Use of this source code is governed by a MIT license found in the LICENSE file.
package codec package codec
@ -8,6 +8,7 @@ package codec
import ( import (
"reflect" "reflect"
"sync/atomic" "sync/atomic"
"time"
) )
const safeMode = true const safeMode = true
@ -34,29 +35,10 @@ func bytesView(v string) []byte {
func definitelyNil(v interface{}) bool { func definitelyNil(v interface{}) bool {
// this is a best-effort option. // this is a best-effort option.
// We just return false, so we don't unneessarily incur the cost of reflection this early. // We just return false, so we don't unnecessarily incur the cost of reflection this early.
return false return false
// rv := reflect.ValueOf(v)
// switch rv.Kind() {
// case reflect.Invalid:
// return true
// case reflect.Ptr, reflect.Interface, reflect.Chan, reflect.Slice, reflect.Map, reflect.Func:
// return rv.IsNil()
// default:
// return false
// }
} }
// // keepAlive4BytesView maintains a reference to the input parameter for bytesView.
// //
// // Usage: call this at point where done with the bytes view.
// func keepAlive4BytesView(v string) {}
// // keepAlive4BytesView maintains a reference to the input parameter for stringView.
// //
// // Usage: call this at point where done with the string view.
// func keepAlive4StringView(v []byte) {}
func rv2i(rv reflect.Value) interface{} { func rv2i(rv reflect.Value) interface{} {
return rv.Interface() return rv.Interface()
} }
@ -69,28 +51,62 @@ func rv2rtid(rv reflect.Value) uintptr {
return reflect.ValueOf(rv.Type()).Pointer() return reflect.ValueOf(rv.Type()).Pointer()
} }
func i2rtid(i interface{}) uintptr {
return reflect.ValueOf(reflect.TypeOf(i)).Pointer()
}
// --------------------------
func isEmptyValue(v reflect.Value, tinfos *TypeInfos, deref, checkStruct bool) bool {
switch v.Kind() {
case reflect.Invalid:
return true
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
return v.Len() == 0
case reflect.Bool:
return !v.Bool()
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return v.Int() == 0
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return v.Uint() == 0
case reflect.Float32, reflect.Float64:
return v.Float() == 0
case reflect.Interface, reflect.Ptr:
if deref {
if v.IsNil() {
return true
}
return isEmptyValue(v.Elem(), tinfos, deref, checkStruct)
}
return v.IsNil()
case reflect.Struct:
return isEmptyStruct(v, tinfos, deref, checkStruct)
}
return false
}
// -------------------------- // --------------------------
// type ptrToRvMap struct{} // type ptrToRvMap struct{}
// func (_ *ptrToRvMap) init() {} // func (*ptrToRvMap) init() {}
// func (_ *ptrToRvMap) get(i interface{}) reflect.Value { // func (*ptrToRvMap) get(i interface{}) reflect.Value {
// return reflect.ValueOf(i).Elem() // return reflect.ValueOf(i).Elem()
// } // }
// -------------------------- // --------------------------
type atomicTypeInfoSlice struct { type atomicTypeInfoSlice struct { // expected to be 2 words
v atomic.Value v atomic.Value
} }
func (x *atomicTypeInfoSlice) load() *[]rtid2ti { func (x *atomicTypeInfoSlice) load() []rtid2ti {
i := x.v.Load() i := x.v.Load()
if i == nil { if i == nil {
return nil return nil
} }
return i.(*[]rtid2ti) return i.([]rtid2ti)
} }
func (x *atomicTypeInfoSlice) store(p *[]rtid2ti) { func (x *atomicTypeInfoSlice) store(p []rtid2ti) {
x.v.Store(p) x.v.Store(p)
} }
@ -107,56 +123,64 @@ func (d *Decoder) kBool(f *codecFnInfo, rv reflect.Value) {
rv.SetBool(d.d.DecodeBool()) rv.SetBool(d.d.DecodeBool())
} }
func (d *Decoder) kTime(f *codecFnInfo, rv reflect.Value) {
rv.Set(reflect.ValueOf(d.d.DecodeTime()))
}
func (d *Decoder) kFloat32(f *codecFnInfo, rv reflect.Value) { func (d *Decoder) kFloat32(f *codecFnInfo, rv reflect.Value) {
rv.SetFloat(d.d.DecodeFloat(true)) fv := d.d.DecodeFloat64()
if chkOvf.Float32(fv) {
d.errorf("float32 overflow: %v", fv)
}
rv.SetFloat(fv)
} }
func (d *Decoder) kFloat64(f *codecFnInfo, rv reflect.Value) { func (d *Decoder) kFloat64(f *codecFnInfo, rv reflect.Value) {
rv.SetFloat(d.d.DecodeFloat(false)) rv.SetFloat(d.d.DecodeFloat64())
} }
func (d *Decoder) kInt(f *codecFnInfo, rv reflect.Value) { func (d *Decoder) kInt(f *codecFnInfo, rv reflect.Value) {
rv.SetInt(d.d.DecodeInt(intBitsize)) rv.SetInt(chkOvf.IntV(d.d.DecodeInt64(), intBitsize))
} }
func (d *Decoder) kInt8(f *codecFnInfo, rv reflect.Value) { func (d *Decoder) kInt8(f *codecFnInfo, rv reflect.Value) {
rv.SetInt(d.d.DecodeInt(8)) rv.SetInt(chkOvf.IntV(d.d.DecodeInt64(), 8))
} }
func (d *Decoder) kInt16(f *codecFnInfo, rv reflect.Value) { func (d *Decoder) kInt16(f *codecFnInfo, rv reflect.Value) {
rv.SetInt(d.d.DecodeInt(16)) rv.SetInt(chkOvf.IntV(d.d.DecodeInt64(), 16))
} }
func (d *Decoder) kInt32(f *codecFnInfo, rv reflect.Value) { func (d *Decoder) kInt32(f *codecFnInfo, rv reflect.Value) {
rv.SetInt(d.d.DecodeInt(32)) rv.SetInt(chkOvf.IntV(d.d.DecodeInt64(), 32))
} }
func (d *Decoder) kInt64(f *codecFnInfo, rv reflect.Value) { func (d *Decoder) kInt64(f *codecFnInfo, rv reflect.Value) {
rv.SetInt(d.d.DecodeInt(64)) rv.SetInt(d.d.DecodeInt64())
} }
func (d *Decoder) kUint(f *codecFnInfo, rv reflect.Value) { func (d *Decoder) kUint(f *codecFnInfo, rv reflect.Value) {
rv.SetUint(d.d.DecodeUint(uintBitsize)) rv.SetUint(chkOvf.UintV(d.d.DecodeUint64(), uintBitsize))
} }
func (d *Decoder) kUintptr(f *codecFnInfo, rv reflect.Value) { func (d *Decoder) kUintptr(f *codecFnInfo, rv reflect.Value) {
rv.SetUint(d.d.DecodeUint(uintBitsize)) rv.SetUint(chkOvf.UintV(d.d.DecodeUint64(), uintBitsize))
} }
func (d *Decoder) kUint8(f *codecFnInfo, rv reflect.Value) { func (d *Decoder) kUint8(f *codecFnInfo, rv reflect.Value) {
rv.SetUint(d.d.DecodeUint(8)) rv.SetUint(chkOvf.UintV(d.d.DecodeUint64(), 8))
} }
func (d *Decoder) kUint16(f *codecFnInfo, rv reflect.Value) { func (d *Decoder) kUint16(f *codecFnInfo, rv reflect.Value) {
rv.SetUint(d.d.DecodeUint(16)) rv.SetUint(chkOvf.UintV(d.d.DecodeUint64(), 16))
} }
func (d *Decoder) kUint32(f *codecFnInfo, rv reflect.Value) { func (d *Decoder) kUint32(f *codecFnInfo, rv reflect.Value) {
rv.SetUint(d.d.DecodeUint(32)) rv.SetUint(chkOvf.UintV(d.d.DecodeUint64(), 32))
} }
func (d *Decoder) kUint64(f *codecFnInfo, rv reflect.Value) { func (d *Decoder) kUint64(f *codecFnInfo, rv reflect.Value) {
rv.SetUint(d.d.DecodeUint(64)) rv.SetUint(d.d.DecodeUint64())
} }
// ---------------- // ----------------
@ -165,8 +189,12 @@ func (e *Encoder) kBool(f *codecFnInfo, rv reflect.Value) {
e.e.EncodeBool(rv.Bool()) e.e.EncodeBool(rv.Bool())
} }
func (e *Encoder) kTime(f *codecFnInfo, rv reflect.Value) {
e.e.EncodeTime(rv2i(rv).(time.Time))
}
func (e *Encoder) kString(f *codecFnInfo, rv reflect.Value) { func (e *Encoder) kString(f *codecFnInfo, rv reflect.Value) {
e.e.EncodeString(c_UTF8, rv.String()) e.e.EncodeString(cUTF8, rv.String())
} }
func (e *Encoder) kFloat64(f *codecFnInfo, rv reflect.Value) { func (e *Encoder) kFloat64(f *codecFnInfo, rv reflect.Value) {
@ -220,3 +248,25 @@ func (e *Encoder) kUint64(f *codecFnInfo, rv reflect.Value) {
func (e *Encoder) kUintptr(f *codecFnInfo, rv reflect.Value) { func (e *Encoder) kUintptr(f *codecFnInfo, rv reflect.Value) {
e.e.EncodeUint(rv.Uint()) e.e.EncodeUint(rv.Uint())
} }
// // keepAlive4BytesView maintains a reference to the input parameter for bytesView.
// //
// // Usage: call this at point where done with the bytes view.
// func keepAlive4BytesView(v string) {}
// // keepAlive4BytesView maintains a reference to the input parameter for stringView.
// //
// // Usage: call this at point where done with the string view.
// func keepAlive4StringView(v []byte) {}
// func definitelyNil(v interface{}) bool {
// rv := reflect.ValueOf(v)
// switch rv.Kind() {
// case reflect.Invalid:
// return true
// case reflect.Ptr, reflect.Interface, reflect.Chan, reflect.Slice, reflect.Map, reflect.Func:
// return rv.IsNil()
// default:
// return false
// }
// }

View File

@ -2,7 +2,7 @@
// +build !appengine // +build !appengine
// +build go1.7 // +build go1.7
// Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved. // Copyright (c) 2012-2018 Ugorji Nwoke. All rights reserved.
// Use of this source code is governed by a MIT license found in the LICENSE file. // Use of this source code is governed by a MIT license found in the LICENSE file.
package codec package codec
@ -10,6 +10,7 @@ package codec
import ( import (
"reflect" "reflect"
"sync/atomic" "sync/atomic"
"time"
"unsafe" "unsafe"
) )
@ -22,12 +23,12 @@ const safeMode = false
const unsafeFlagIndir = 1 << 7 // keep in sync with GO_ROOT/src/reflect/value.go const unsafeFlagIndir = 1 << 7 // keep in sync with GO_ROOT/src/reflect/value.go
type unsafeString struct { type unsafeString struct {
Data uintptr Data unsafe.Pointer
Len int Len int
} }
type unsafeSlice struct { type unsafeSlice struct {
Data uintptr Data unsafe.Pointer
Len int Len int
Cap int Cap int
} }
@ -47,20 +48,16 @@ func stringView(v []byte) string {
if len(v) == 0 { if len(v) == 0 {
return "" return ""
} }
bx := (*unsafeSlice)(unsafe.Pointer(&v)) bx := (*unsafeSlice)(unsafe.Pointer(&v))
sx := unsafeString{bx.Data, bx.Len} return *(*string)(unsafe.Pointer(&unsafeString{bx.Data, bx.Len}))
return *(*string)(unsafe.Pointer(&sx))
} }
func bytesView(v string) []byte { func bytesView(v string) []byte {
if len(v) == 0 { if len(v) == 0 {
return zeroByteSlice return zeroByteSlice
} }
sx := (*unsafeString)(unsafe.Pointer(&v)) sx := (*unsafeString)(unsafe.Pointer(&v))
bx := unsafeSlice{sx.Data, sx.Len, sx.Len} return *(*[]byte)(unsafe.Pointer(&unsafeSlice{sx.Data, sx.Len, sx.Len}))
return *(*[]byte)(unsafe.Pointer(&bx))
} }
func definitelyNil(v interface{}) bool { func definitelyNil(v interface{}) bool {
@ -68,41 +65,32 @@ func definitelyNil(v interface{}) bool {
// For true references (map, ptr, func, chan), you can just look // For true references (map, ptr, func, chan), you can just look
// at the word of the interface. However, for slices, you have to dereference // at the word of the interface. However, for slices, you have to dereference
// the word, and get a pointer to the 3-word interface value. // the word, and get a pointer to the 3-word interface value.
//
// However, the following are cheap calls
// - TypeOf(interface): cheap 2-line call.
// - ValueOf(interface{}): expensive
// - type.Kind: cheap call through an interface
// - Value.Type(): cheap call
// except it's a method value (e.g. r.Read, which implies that it is a Func)
// var ui *unsafeIntf = (*unsafeIntf)(unsafe.Pointer(&v))
// var word unsafe.Pointer = ui.word
// // fmt.Printf(">>>> definitely nil: isnil: %v, TYPE: \t%T, word: %v, *word: %v, type: %v, nil: %v\n", v == nil, v, word, *((*unsafe.Pointer)(word)), ui.typ, nil)
// return word == nil // || *((*unsafe.Pointer)(word)) == nil
return ((*unsafeIntf)(unsafe.Pointer(&v))).word == nil return ((*unsafeIntf)(unsafe.Pointer(&v))).word == nil
} }
// func keepAlive4BytesView(v string) {
// runtime.KeepAlive(v)
// }
// func keepAlive4StringView(v []byte) {
// runtime.KeepAlive(v)
// }
// TODO: consider a more generally-known optimization for reflect.Value ==> Interface
//
// Currently, we use this fragile method that taps into implememtation details from
// the source go stdlib reflect/value.go,
// and trims the implementation.
func rv2i(rv reflect.Value) interface{} { func rv2i(rv reflect.Value) interface{} {
// TODO: consider a more generally-known optimization for reflect.Value ==> Interface
//
// Currently, we use this fragile method that taps into implememtation details from
// the source go stdlib reflect/value.go, and trims the implementation.
urv := (*unsafeReflectValue)(unsafe.Pointer(&rv)) urv := (*unsafeReflectValue)(unsafe.Pointer(&rv))
// true references (map, func, chan, ptr - NOT slice) may be double-referenced as flagIndir // true references (map, func, chan, ptr - NOT slice) may be double-referenced as flagIndir
var ptr unsafe.Pointer var ptr unsafe.Pointer
// kk := reflect.Kind(urv.flag & (1<<5 - 1))
// if (kk == reflect.Map || kk == reflect.Ptr || kk == reflect.Chan || kk == reflect.Func) && urv.flag&unsafeFlagIndir != 0 {
if refBitset.isset(byte(urv.flag&(1<<5-1))) && urv.flag&unsafeFlagIndir != 0 { if refBitset.isset(byte(urv.flag&(1<<5-1))) && urv.flag&unsafeFlagIndir != 0 {
ptr = *(*unsafe.Pointer)(urv.ptr) ptr = *(*unsafe.Pointer)(urv.ptr)
} else { } else {
ptr = urv.ptr ptr = urv.ptr
} }
return *(*interface{})(unsafe.Pointer(&unsafeIntf{typ: urv.typ, word: ptr})) return *(*interface{})(unsafe.Pointer(&unsafeIntf{typ: urv.typ, word: ptr}))
// return *(*interface{})(unsafe.Pointer(&unsafeIntf{word: *(*unsafe.Pointer)(urv.ptr), typ: urv.typ}))
// return *(*interface{})(unsafe.Pointer(&unsafeIntf{word: urv.ptr, typ: urv.typ}))
} }
func rt2id(rt reflect.Type) uintptr { func rt2id(rt reflect.Type) uintptr {
@ -113,32 +101,104 @@ func rv2rtid(rv reflect.Value) uintptr {
return uintptr((*unsafeReflectValue)(unsafe.Pointer(&rv)).typ) return uintptr((*unsafeReflectValue)(unsafe.Pointer(&rv)).typ)
} }
// func rv0t(rt reflect.Type) reflect.Value { func i2rtid(i interface{}) uintptr {
// ut := (*unsafeIntf)(unsafe.Pointer(&rt)) return uintptr(((*unsafeIntf)(unsafe.Pointer(&i))).typ)
// // we need to determine whether ifaceIndir, and then whether to just pass 0 as the ptr }
// uv := unsafeReflectValue{ut.word, &zeroRTv, flag(rt.Kind())}
// return *(*reflect.Value)(unsafe.Pointer(&uv})
// }
// -------------------------- // --------------------------
type atomicTypeInfoSlice struct {
v unsafe.Pointer func isEmptyValue(v reflect.Value, tinfos *TypeInfos, deref, checkStruct bool) bool {
urv := (*unsafeReflectValue)(unsafe.Pointer(&v))
if urv.flag == 0 {
return true
}
switch v.Kind() {
case reflect.Invalid:
return true
case reflect.String:
return (*unsafeString)(urv.ptr).Len == 0
case reflect.Slice:
return (*unsafeSlice)(urv.ptr).Len == 0
case reflect.Bool:
return !*(*bool)(urv.ptr)
case reflect.Int:
return *(*int)(urv.ptr) == 0
case reflect.Int8:
return *(*int8)(urv.ptr) == 0
case reflect.Int16:
return *(*int16)(urv.ptr) == 0
case reflect.Int32:
return *(*int32)(urv.ptr) == 0
case reflect.Int64:
return *(*int64)(urv.ptr) == 0
case reflect.Uint:
return *(*uint)(urv.ptr) == 0
case reflect.Uint8:
return *(*uint8)(urv.ptr) == 0
case reflect.Uint16:
return *(*uint16)(urv.ptr) == 0
case reflect.Uint32:
return *(*uint32)(urv.ptr) == 0
case reflect.Uint64:
return *(*uint64)(urv.ptr) == 0
case reflect.Uintptr:
return *(*uintptr)(urv.ptr) == 0
case reflect.Float32:
return *(*float32)(urv.ptr) == 0
case reflect.Float64:
return *(*float64)(urv.ptr) == 0
case reflect.Interface:
isnil := urv.ptr == nil || *(*unsafe.Pointer)(urv.ptr) == nil
if deref {
if isnil {
return true
}
return isEmptyValue(v.Elem(), tinfos, deref, checkStruct)
}
return isnil
case reflect.Ptr:
isnil := urv.ptr == nil
if deref {
if isnil {
return true
}
return isEmptyValue(v.Elem(), tinfos, deref, checkStruct)
}
return isnil
case reflect.Struct:
return isEmptyStruct(v, tinfos, deref, checkStruct)
case reflect.Map, reflect.Array, reflect.Chan:
return v.Len() == 0
}
return false
} }
func (x *atomicTypeInfoSlice) load() *[]rtid2ti { // --------------------------
return (*[]rtid2ti)(atomic.LoadPointer(&x.v))
type atomicTypeInfoSlice struct { // expected to be 2 words
v unsafe.Pointer // data array - Pointer (not uintptr) to maintain GC reference
l int64 // length of the data array
} }
func (x *atomicTypeInfoSlice) store(p *[]rtid2ti) { func (x *atomicTypeInfoSlice) load() []rtid2ti {
atomic.StorePointer(&x.v, unsafe.Pointer(p)) l := int(atomic.LoadInt64(&x.l))
if l == 0 {
return nil
}
return *(*[]rtid2ti)(unsafe.Pointer(&unsafeSlice{Data: atomic.LoadPointer(&x.v), Len: l, Cap: l}))
// return (*[]rtid2ti)(atomic.LoadPointer(&x.v))
}
func (x *atomicTypeInfoSlice) store(p []rtid2ti) {
s := (*unsafeSlice)(unsafe.Pointer(&p))
atomic.StorePointer(&x.v, s.Data)
atomic.StoreInt64(&x.l, int64(s.Len))
// atomic.StorePointer(&x.v, unsafe.Pointer(p))
} }
// -------------------------- // --------------------------
func (d *Decoder) raw(f *codecFnInfo, rv reflect.Value) { func (d *Decoder) raw(f *codecFnInfo, rv reflect.Value) {
urv := (*unsafeReflectValue)(unsafe.Pointer(&rv)) urv := (*unsafeReflectValue)(unsafe.Pointer(&rv))
// if urv.flag&unsafeFlagIndir != 0 {
// urv.ptr = *(*unsafe.Pointer)(urv.ptr)
// }
*(*[]byte)(urv.ptr) = d.rawBytes() *(*[]byte)(urv.ptr) = d.rawBytes()
} }
@ -152,69 +212,78 @@ func (d *Decoder) kBool(f *codecFnInfo, rv reflect.Value) {
*(*bool)(urv.ptr) = d.d.DecodeBool() *(*bool)(urv.ptr) = d.d.DecodeBool()
} }
func (d *Decoder) kFloat32(f *codecFnInfo, rv reflect.Value) { func (d *Decoder) kTime(f *codecFnInfo, rv reflect.Value) {
urv := (*unsafeReflectValue)(unsafe.Pointer(&rv)) urv := (*unsafeReflectValue)(unsafe.Pointer(&rv))
*(*float32)(urv.ptr) = float32(d.d.DecodeFloat(true)) *(*time.Time)(urv.ptr) = d.d.DecodeTime()
}
func (d *Decoder) kFloat32(f *codecFnInfo, rv reflect.Value) {
fv := d.d.DecodeFloat64()
if chkOvf.Float32(fv) {
d.errorf("float32 overflow: %v", fv)
}
urv := (*unsafeReflectValue)(unsafe.Pointer(&rv))
*(*float32)(urv.ptr) = float32(fv)
} }
func (d *Decoder) kFloat64(f *codecFnInfo, rv reflect.Value) { func (d *Decoder) kFloat64(f *codecFnInfo, rv reflect.Value) {
urv := (*unsafeReflectValue)(unsafe.Pointer(&rv)) urv := (*unsafeReflectValue)(unsafe.Pointer(&rv))
*(*float64)(urv.ptr) = d.d.DecodeFloat(false) *(*float64)(urv.ptr) = d.d.DecodeFloat64()
} }
func (d *Decoder) kInt(f *codecFnInfo, rv reflect.Value) { func (d *Decoder) kInt(f *codecFnInfo, rv reflect.Value) {
urv := (*unsafeReflectValue)(unsafe.Pointer(&rv)) urv := (*unsafeReflectValue)(unsafe.Pointer(&rv))
*(*int)(urv.ptr) = int(d.d.DecodeInt(intBitsize)) *(*int)(urv.ptr) = int(chkOvf.IntV(d.d.DecodeInt64(), intBitsize))
} }
func (d *Decoder) kInt8(f *codecFnInfo, rv reflect.Value) { func (d *Decoder) kInt8(f *codecFnInfo, rv reflect.Value) {
urv := (*unsafeReflectValue)(unsafe.Pointer(&rv)) urv := (*unsafeReflectValue)(unsafe.Pointer(&rv))
*(*int8)(urv.ptr) = int8(d.d.DecodeInt(8)) *(*int8)(urv.ptr) = int8(chkOvf.IntV(d.d.DecodeInt64(), 8))
} }
func (d *Decoder) kInt16(f *codecFnInfo, rv reflect.Value) { func (d *Decoder) kInt16(f *codecFnInfo, rv reflect.Value) {
urv := (*unsafeReflectValue)(unsafe.Pointer(&rv)) urv := (*unsafeReflectValue)(unsafe.Pointer(&rv))
*(*int16)(urv.ptr) = int16(d.d.DecodeInt(16)) *(*int16)(urv.ptr) = int16(chkOvf.IntV(d.d.DecodeInt64(), 16))
} }
func (d *Decoder) kInt32(f *codecFnInfo, rv reflect.Value) { func (d *Decoder) kInt32(f *codecFnInfo, rv reflect.Value) {
urv := (*unsafeReflectValue)(unsafe.Pointer(&rv)) urv := (*unsafeReflectValue)(unsafe.Pointer(&rv))
*(*int32)(urv.ptr) = int32(d.d.DecodeInt(32)) *(*int32)(urv.ptr) = int32(chkOvf.IntV(d.d.DecodeInt64(), 32))
} }
func (d *Decoder) kInt64(f *codecFnInfo, rv reflect.Value) { func (d *Decoder) kInt64(f *codecFnInfo, rv reflect.Value) {
urv := (*unsafeReflectValue)(unsafe.Pointer(&rv)) urv := (*unsafeReflectValue)(unsafe.Pointer(&rv))
*(*int64)(urv.ptr) = d.d.DecodeInt(64) *(*int64)(urv.ptr) = d.d.DecodeInt64()
} }
func (d *Decoder) kUint(f *codecFnInfo, rv reflect.Value) { func (d *Decoder) kUint(f *codecFnInfo, rv reflect.Value) {
urv := (*unsafeReflectValue)(unsafe.Pointer(&rv)) urv := (*unsafeReflectValue)(unsafe.Pointer(&rv))
*(*uint)(urv.ptr) = uint(d.d.DecodeUint(uintBitsize)) *(*uint)(urv.ptr) = uint(chkOvf.UintV(d.d.DecodeUint64(), uintBitsize))
} }
func (d *Decoder) kUintptr(f *codecFnInfo, rv reflect.Value) { func (d *Decoder) kUintptr(f *codecFnInfo, rv reflect.Value) {
urv := (*unsafeReflectValue)(unsafe.Pointer(&rv)) urv := (*unsafeReflectValue)(unsafe.Pointer(&rv))
*(*uintptr)(urv.ptr) = uintptr(d.d.DecodeUint(uintBitsize)) *(*uintptr)(urv.ptr) = uintptr(chkOvf.UintV(d.d.DecodeUint64(), uintBitsize))
} }
func (d *Decoder) kUint8(f *codecFnInfo, rv reflect.Value) { func (d *Decoder) kUint8(f *codecFnInfo, rv reflect.Value) {
urv := (*unsafeReflectValue)(unsafe.Pointer(&rv)) urv := (*unsafeReflectValue)(unsafe.Pointer(&rv))
*(*uint8)(urv.ptr) = uint8(d.d.DecodeUint(8)) *(*uint8)(urv.ptr) = uint8(chkOvf.UintV(d.d.DecodeUint64(), 8))
} }
func (d *Decoder) kUint16(f *codecFnInfo, rv reflect.Value) { func (d *Decoder) kUint16(f *codecFnInfo, rv reflect.Value) {
urv := (*unsafeReflectValue)(unsafe.Pointer(&rv)) urv := (*unsafeReflectValue)(unsafe.Pointer(&rv))
*(*uint16)(urv.ptr) = uint16(d.d.DecodeUint(16)) *(*uint16)(urv.ptr) = uint16(chkOvf.UintV(d.d.DecodeUint64(), 16))
} }
func (d *Decoder) kUint32(f *codecFnInfo, rv reflect.Value) { func (d *Decoder) kUint32(f *codecFnInfo, rv reflect.Value) {
urv := (*unsafeReflectValue)(unsafe.Pointer(&rv)) urv := (*unsafeReflectValue)(unsafe.Pointer(&rv))
*(*uint32)(urv.ptr) = uint32(d.d.DecodeUint(32)) *(*uint32)(urv.ptr) = uint32(chkOvf.UintV(d.d.DecodeUint64(), 32))
} }
func (d *Decoder) kUint64(f *codecFnInfo, rv reflect.Value) { func (d *Decoder) kUint64(f *codecFnInfo, rv reflect.Value) {
urv := (*unsafeReflectValue)(unsafe.Pointer(&rv)) urv := (*unsafeReflectValue)(unsafe.Pointer(&rv))
*(*uint64)(urv.ptr) = d.d.DecodeUint(64) *(*uint64)(urv.ptr) = d.d.DecodeUint64()
} }
// ------------ // ------------
@ -224,9 +293,14 @@ func (e *Encoder) kBool(f *codecFnInfo, rv reflect.Value) {
e.e.EncodeBool(*(*bool)(v.ptr)) e.e.EncodeBool(*(*bool)(v.ptr))
} }
func (e *Encoder) kTime(f *codecFnInfo, rv reflect.Value) {
v := (*unsafeReflectValue)(unsafe.Pointer(&rv))
e.e.EncodeTime(*(*time.Time)(v.ptr))
}
func (e *Encoder) kString(f *codecFnInfo, rv reflect.Value) { func (e *Encoder) kString(f *codecFnInfo, rv reflect.Value) {
v := (*unsafeReflectValue)(unsafe.Pointer(&rv)) v := (*unsafeReflectValue)(unsafe.Pointer(&rv))
e.e.EncodeString(c_UTF8, *(*string)(v.ptr)) e.e.EncodeString(cUTF8, *(*string)(v.ptr))
} }
func (e *Encoder) kFloat64(f *codecFnInfo, rv reflect.Value) { func (e *Encoder) kFloat64(f *codecFnInfo, rv reflect.Value) {
@ -296,6 +370,56 @@ func (e *Encoder) kUintptr(f *codecFnInfo, rv reflect.Value) {
// ------------ // ------------
// func (d *Decoder) raw(f *codecFnInfo, rv reflect.Value) {
// urv := (*unsafeReflectValue)(unsafe.Pointer(&rv))
// // if urv.flag&unsafeFlagIndir != 0 {
// // urv.ptr = *(*unsafe.Pointer)(urv.ptr)
// // }
// *(*[]byte)(urv.ptr) = d.rawBytes()
// }
// func rv0t(rt reflect.Type) reflect.Value {
// ut := (*unsafeIntf)(unsafe.Pointer(&rt))
// // we need to determine whether ifaceIndir, and then whether to just pass 0 as the ptr
// uv := unsafeReflectValue{ut.word, &zeroRTv, flag(rt.Kind())}
// return *(*reflect.Value)(unsafe.Pointer(&uv})
// }
// func rv2i(rv reflect.Value) interface{} {
// urv := (*unsafeReflectValue)(unsafe.Pointer(&rv))
// // true references (map, func, chan, ptr - NOT slice) may be double-referenced as flagIndir
// var ptr unsafe.Pointer
// // kk := reflect.Kind(urv.flag & (1<<5 - 1))
// // if (kk == reflect.Map || kk == reflect.Ptr || kk == reflect.Chan || kk == reflect.Func) && urv.flag&unsafeFlagIndir != 0 {
// if refBitset.isset(byte(urv.flag&(1<<5-1))) && urv.flag&unsafeFlagIndir != 0 {
// ptr = *(*unsafe.Pointer)(urv.ptr)
// } else {
// ptr = urv.ptr
// }
// return *(*interface{})(unsafe.Pointer(&unsafeIntf{typ: urv.typ, word: ptr}))
// // return *(*interface{})(unsafe.Pointer(&unsafeIntf{word: *(*unsafe.Pointer)(urv.ptr), typ: urv.typ}))
// // return *(*interface{})(unsafe.Pointer(&unsafeIntf{word: urv.ptr, typ: urv.typ}))
// }
// func definitelyNil(v interface{}) bool {
// var ui *unsafeIntf = (*unsafeIntf)(unsafe.Pointer(&v))
// if ui.word == nil {
// return true
// }
// var tk = reflect.TypeOf(v).Kind()
// return (tk == reflect.Interface || tk == reflect.Slice) && *(*unsafe.Pointer)(ui.word) == nil
// fmt.Printf(">>>> definitely nil: isnil: %v, TYPE: \t%T, word: %v, *word: %v, type: %v, nil: %v\n",
// v == nil, v, word, *((*unsafe.Pointer)(word)), ui.typ, nil)
// }
// func keepAlive4BytesView(v string) {
// runtime.KeepAlive(v)
// }
// func keepAlive4StringView(v []byte) {
// runtime.KeepAlive(v)
// }
// func rt2id(rt reflect.Type) uintptr { // func rt2id(rt reflect.Type) uintptr {
// return uintptr(((*unsafeIntf)(unsafe.Pointer(&rt))).word) // return uintptr(((*unsafeIntf)(unsafe.Pointer(&rt))).word)
// // var i interface{} = rt // // var i interface{} = rt

File diff suppressed because it is too large Load Diff

View File

@ -1,15 +1,13 @@
// Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved. // Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved.
// Use of this source code is governed by a MIT license found in the LICENSE file. // Use of this source code is governed by a MIT license found in the LICENSE file.
// ************************************************************ // Code generated from mammoth-test.go.tmpl - DO NOT EDIT.
// DO NOT EDIT.
// THIS FILE IS AUTO-GENERATED from mammoth-test.go.tmpl
// ************************************************************
package codec package codec
import "testing" import "testing"
import "fmt" import "fmt"
import "reflect"
// TestMammoth has all the different paths optimized in fast-path // TestMammoth has all the different paths optimized in fast-path
// It has all the primitives, slices and maps. // It has all the primitives, slices and maps.
@ -38,36 +36,78 @@ type TestMammoth struct {
} }
{{range .Values }}{{if not .Primitive }}{{if not .MapKey }}{{/* {{range .Values }}{{if not .Primitive }}{{if not .MapKey }}{{/*
*/}} type {{ .MethodNamePfx "type" false }} []{{ .Elem }} */}} type {{ .MethodNamePfx "typMbs" false }} []{{ .Elem }}
func (_ {{ .MethodNamePfx "type" false }}) MapBySlice() { } func (_ {{ .MethodNamePfx "typMbs" false }}) MapBySlice() { }
{{end}}{{end}}{{end}}
{{range .Values }}{{if not .Primitive }}{{if .MapKey }}{{/*
*/}} type {{ .MethodNamePfx "typMap" false }} map[{{ .MapKey }}]{{ .Elem }}
{{end}}{{end}}{{end}} {{end}}{{end}}{{end}}
func doTestMammothSlices(t *testing.T, h Handle) { func doTestMammothSlices(t *testing.T, h Handle) {
{{range $i, $e := .Values }}{{if not .Primitive }}{{if not .MapKey }}{{/* {{range $i, $e := .Values }}{{if not .Primitive }}{{if not .MapKey }}{{/*
*/}} */}}
for _, v := range [][]{{ .Elem }}{ nil, []{{ .Elem }}{}, []{{ .Elem }}{ {{ nonzerocmd .Elem }}, {{ nonzerocmd .Elem }} } } { var v{{$i}}va [8]{{ .Elem }}
for _, v := range [][]{{ .Elem }}{ nil, {}, { {{ nonzerocmd .Elem }}, {{ zerocmd .Elem }}, {{ zerocmd .Elem }}, {{ nonzerocmd .Elem }} } } { {{/*
// fmt.Printf(">>>> running mammoth slice v{{$i}}: %v\n", v) // fmt.Printf(">>>> running mammoth slice v{{$i}}: %v\n", v)
var v{{$i}}v1, v{{$i}}v2, v{{$i}}v3, v{{$i}}v4 []{{ .Elem }} // - encode value to some []byte
// - decode into a length-wise-equal []byte
// - check if equal to initial slice
// - encode ptr to the value
// - check if encode bytes are same
// - decode into ptrs to: nil, then 1-elem slice, equal-length, then large len slice
// - decode into non-addressable slice of equal length, then larger len
// - for each decode, compare elem-by-elem to the original slice
// -
// - rinse and repeat for a MapBySlice version
// -
*/}}
var v{{$i}}v1, v{{$i}}v2 []{{ .Elem }}
v{{$i}}v1 = v v{{$i}}v1 = v
bs{{$i}} := testMarshalErr(v{{$i}}v1, h, t, "enc-slice-v{{$i}}") bs{{$i}} := testMarshalErr(v{{$i}}v1, h, t, "enc-slice-v{{$i}}")
if v != nil { v{{$i}}v2 = make([]{{ .Elem }}, len(v)) } if v == nil { v{{$i}}v2 = nil } else { v{{$i}}v2 = make([]{{ .Elem }}, len(v)) }
testUnmarshalErr(v{{$i}}v2, bs{{$i}}, h, t, "dec-slice-v{{$i}}") testUnmarshalErr(v{{$i}}v2, bs{{$i}}, h, t, "dec-slice-v{{$i}}")
testDeepEqualErr(v{{$i}}v1, v{{$i}}v2, t, "equal-slice-v{{$i}}") testDeepEqualErr(v{{$i}}v1, v{{$i}}v2, t, "equal-slice-v{{$i}}")
if v == nil { v{{$i}}v2 = nil } else { v{{$i}}v2 = make([]{{ .Elem }}, len(v)) }
testUnmarshalErr(reflect.ValueOf(v{{$i}}v2), bs{{$i}}, h, t, "dec-slice-v{{$i}}-noaddr") // non-addressable value
testDeepEqualErr(v{{$i}}v1, v{{$i}}v2, t, "equal-slice-v{{$i}}-noaddr")
// ...
bs{{$i}} = testMarshalErr(&v{{$i}}v1, h, t, "enc-slice-v{{$i}}-p") bs{{$i}} = testMarshalErr(&v{{$i}}v1, h, t, "enc-slice-v{{$i}}-p")
v{{$i}}v2 = nil v{{$i}}v2 = nil
testUnmarshalErr(&v{{$i}}v2, bs{{$i}}, h, t, "dec-slice-v{{$i}}-p") testUnmarshalErr(&v{{$i}}v2, bs{{$i}}, h, t, "dec-slice-v{{$i}}-p")
testDeepEqualErr(v{{$i}}v1, v{{$i}}v2, t, "equal-slice-v{{$i}}-p") testDeepEqualErr(v{{$i}}v1, v{{$i}}v2, t, "equal-slice-v{{$i}}-p")
v{{$i}}va = [8]{{ .Elem }}{} // clear the array
v{{$i}}v2 = v{{$i}}va[:1:1]
testUnmarshalErr(&v{{$i}}v2, bs{{$i}}, h, t, "dec-slice-v{{$i}}-p-1")
testDeepEqualErr(v{{$i}}v1, v{{$i}}v2, t, "equal-slice-v{{$i}}-p-1")
v{{$i}}va = [8]{{ .Elem }}{} // clear the array
v{{$i}}v2 = v{{$i}}va[:len(v{{$i}}v1):len(v{{$i}}v1)]
testUnmarshalErr(&v{{$i}}v2, bs{{$i}}, h, t, "dec-slice-v{{$i}}-p-len")
testDeepEqualErr(v{{$i}}v1, v{{$i}}v2, t, "equal-slice-v{{$i}}-p-len")
v{{$i}}va = [8]{{ .Elem }}{} // clear the array
v{{$i}}v2 = v{{$i}}va[:]
testUnmarshalErr(&v{{$i}}v2, bs{{$i}}, h, t, "dec-slice-v{{$i}}-p-cap")
testDeepEqualErr(v{{$i}}v1, v{{$i}}v2, t, "equal-slice-v{{$i}}-p-cap")
if len(v{{$i}}v1) > 1 {
v{{$i}}va = [8]{{ .Elem }}{} // clear the array
testUnmarshalErr((&v{{$i}}va)[:len(v{{$i}}v1)], bs{{$i}}, h, t, "dec-slice-v{{$i}}-p-len-noaddr")
testDeepEqualErr(v{{$i}}v1, v{{$i}}va[:len(v{{$i}}v1)], t, "equal-slice-v{{$i}}-p-len-noaddr")
v{{$i}}va = [8]{{ .Elem }}{} // clear the array
testUnmarshalErr((&v{{$i}}va)[:], bs{{$i}}, h, t, "dec-slice-v{{$i}}-p-cap-noaddr")
testDeepEqualErr(v{{$i}}v1, v{{$i}}va[:len(v{{$i}}v1)], t, "equal-slice-v{{$i}}-p-cap-noaddr")
}
// ... // ...
var v{{$i}}v3, v{{$i}}v4 {{ .MethodNamePfx "typMbs" false }}
v{{$i}}v2 = nil v{{$i}}v2 = nil
if v != nil { v{{$i}}v2 = make([]{{ .Elem }}, len(v)) } if v != nil { v{{$i}}v2 = make([]{{ .Elem }}, len(v)) }
v{{$i}}v3 = {{ .MethodNamePfx "type" false }}(v{{$i}}v1) v{{$i}}v3 = {{ .MethodNamePfx "typMbs" false }}(v{{$i}}v1)
v{{$i}}v4 = {{ .MethodNamePfx "typMbs" false }}(v{{$i}}v2)
bs{{$i}} = testMarshalErr(v{{$i}}v3, h, t, "enc-slice-v{{$i}}-custom") bs{{$i}} = testMarshalErr(v{{$i}}v3, h, t, "enc-slice-v{{$i}}-custom")
v{{$i}}v4 = {{ .MethodNamePfx "type" false }}(v{{$i}}v2)
testUnmarshalErr(v{{$i}}v4, bs{{$i}}, h, t, "dec-slice-v{{$i}}-custom") testUnmarshalErr(v{{$i}}v4, bs{{$i}}, h, t, "dec-slice-v{{$i}}-custom")
testDeepEqualErr(v{{$i}}v3, v{{$i}}v4, t, "equal-slice-v{{$i}}-custom") testDeepEqualErr(v{{$i}}v3, v{{$i}}v4, t, "equal-slice-v{{$i}}-custom")
v{{$i}}v2 = nil
bs{{$i}} = testMarshalErr(&v{{$i}}v3, h, t, "enc-slice-v{{$i}}-custom-p") bs{{$i}} = testMarshalErr(&v{{$i}}v3, h, t, "enc-slice-v{{$i}}-custom-p")
v{{$i}}v4 = {{ .MethodNamePfx "type" false }}(v{{$i}}v2) v{{$i}}v2 = nil
v{{$i}}v4 = {{ .MethodNamePfx "typMbs" false }}(v{{$i}}v2)
testUnmarshalErr(&v{{$i}}v4, bs{{$i}}, h, t, "dec-slice-v{{$i}}-custom-p") testUnmarshalErr(&v{{$i}}v4, bs{{$i}}, h, t, "dec-slice-v{{$i}}-custom-p")
testDeepEqualErr(v{{$i}}v3, v{{$i}}v4, t, "equal-slice-v{{$i}}-custom-p") testDeepEqualErr(v{{$i}}v3, v{{$i}}v4, t, "equal-slice-v{{$i}}-custom-p")
} }
@ -77,18 +117,32 @@ func doTestMammothSlices(t *testing.T, h Handle) {
func doTestMammothMaps(t *testing.T, h Handle) { func doTestMammothMaps(t *testing.T, h Handle) {
{{range $i, $e := .Values }}{{if not .Primitive }}{{if .MapKey }}{{/* {{range $i, $e := .Values }}{{if not .Primitive }}{{if .MapKey }}{{/*
*/}} */}}
for _, v := range []map[{{ .MapKey }}]{{ .Elem }}{ nil, map[{{ .MapKey }}]{{ .Elem }}{}, map[{{ .MapKey }}]{{ .Elem }}{ {{ nonzerocmd .MapKey }}:{{ nonzerocmd .Elem }} } } { for _, v := range []map[{{ .MapKey }}]{{ .Elem }}{ nil, {}, { {{ nonzerocmd .MapKey }}:{{ zerocmd .Elem }} {{if ne "bool" .MapKey}}, {{ nonzerocmd .MapKey }}:{{ nonzerocmd .Elem }} {{end}} } } {
// fmt.Printf(">>>> running mammoth map v{{$i}}: %v\n", v) // fmt.Printf(">>>> running mammoth map v{{$i}}: %v\n", v)
var v{{$i}}v1, v{{$i}}v2 map[{{ .MapKey }}]{{ .Elem }} var v{{$i}}v1, v{{$i}}v2 map[{{ .MapKey }}]{{ .Elem }}
v{{$i}}v1 = v v{{$i}}v1 = v
bs{{$i}} := testMarshalErr(v{{$i}}v1, h, t, "enc-map-v{{$i}}") bs{{$i}} := testMarshalErr(v{{$i}}v1, h, t, "enc-map-v{{$i}}")
if v != nil { v{{$i}}v2 = make(map[{{ .MapKey }}]{{ .Elem }}, len(v)) } if v == nil { v{{$i}}v2 = nil } else { v{{$i}}v2 = make(map[{{ .MapKey }}]{{ .Elem }}, len(v)) } // reset map
testUnmarshalErr(v{{$i}}v2, bs{{$i}}, h, t, "dec-map-v{{$i}}") testUnmarshalErr(v{{$i}}v2, bs{{$i}}, h, t, "dec-map-v{{$i}}")
testDeepEqualErr(v{{$i}}v1, v{{$i}}v2, t, "equal-map-v{{$i}}") testDeepEqualErr(v{{$i}}v1, v{{$i}}v2, t, "equal-map-v{{$i}}")
if v == nil { v{{$i}}v2 = nil } else { v{{$i}}v2 = make(map[{{ .MapKey }}]{{ .Elem }}, len(v)) } // reset map
testUnmarshalErr(reflect.ValueOf(v{{$i}}v2), bs{{$i}}, h, t, "dec-map-v{{$i}}-noaddr") // decode into non-addressable map value
testDeepEqualErr(v{{$i}}v1, v{{$i}}v2, t, "equal-map-v{{$i}}-noaddr")
if v == nil { v{{$i}}v2 = nil } else { v{{$i}}v2 = make(map[{{ .MapKey }}]{{ .Elem }}, len(v)) } // reset map
testUnmarshalErr(&v{{$i}}v2, bs{{$i}}, h, t, "dec-map-v{{$i}}-p-len")
testDeepEqualErr(v{{$i}}v1, v{{$i}}v2, t, "equal-map-v{{$i}}-p-len")
bs{{$i}} = testMarshalErr(&v{{$i}}v1, h, t, "enc-map-v{{$i}}-p") bs{{$i}} = testMarshalErr(&v{{$i}}v1, h, t, "enc-map-v{{$i}}-p")
v{{$i}}v2 = nil v{{$i}}v2 = nil
testUnmarshalErr(&v{{$i}}v2, bs{{$i}}, h, t, "dec-map-v{{$i}}-p") testUnmarshalErr(&v{{$i}}v2, bs{{$i}}, h, t, "dec-map-v{{$i}}-p-nil")
testDeepEqualErr(v{{$i}}v1, v{{$i}}v2, t, "equal-map-v{{$i}}-p") testDeepEqualErr(v{{$i}}v1, v{{$i}}v2, t, "equal-map-v{{$i}}-p-nil")
// ...
if v == nil { v{{$i}}v2 = nil } else { v{{$i}}v2 = make(map[{{ .MapKey }}]{{ .Elem }}, len(v)) } // reset map
var v{{$i}}v3, v{{$i}}v4 {{ .MethodNamePfx "typMap" false }}
v{{$i}}v3 = {{ .MethodNamePfx "typMap" false }}(v{{$i}}v1)
v{{$i}}v4 = {{ .MethodNamePfx "typMap" false }}(v{{$i}}v2)
bs{{$i}} = testMarshalErr(v{{$i}}v3, h, t, "enc-map-v{{$i}}-custom")
testUnmarshalErr(v{{$i}}v4, bs{{$i}}, h, t, "dec-map-v{{$i}}-p-len")
testDeepEqualErr(v{{$i}}v3, v{{$i}}v4, t, "equal-map-v{{$i}}-p-len")
} }
{{end}}{{end}}{{end}} {{end}}{{end}}{{end}}

View File

@ -3,10 +3,7 @@
// Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved. // Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved.
// Use of this source code is governed by a MIT license found in the LICENSE file. // Use of this source code is governed by a MIT license found in the LICENSE file.
// ************************************************************ // Code generated from mammoth2-test.go.tmpl - DO NOT EDIT.
// DO NOT EDIT.
// THIS FILE IS AUTO-GENERATED from mammoth2-test.go.tmpl
// ************************************************************
package codec package codec

File diff suppressed because it is too large Load Diff

View File

@ -3,10 +3,7 @@
// Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved. // Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved.
// Use of this source code is governed by a MIT license found in the LICENSE file. // Use of this source code is governed by a MIT license found in the LICENSE file.
// ************************************************************ // Code generated from mammoth2-test.go.tmpl - DO NOT EDIT.
// DO NOT EDIT.
// THIS FILE IS AUTO-GENERATED from mammoth2-test.go.tmpl
// ************************************************************
package codec package codec
@ -74,6 +71,8 @@ type TestMammoth2 struct {
FptrSliceFloat64 *[]float64 FptrSliceFloat64 *[]float64
FSliceUint []uint FSliceUint []uint
FptrSliceUint *[]uint FptrSliceUint *[]uint
FSliceUint8 []uint8
FptrSliceUint8 *[]uint8
FSliceUint16 []uint16 FSliceUint16 []uint16
FptrSliceUint16 *[]uint16 FptrSliceUint16 *[]uint16
FSliceUint32 []uint32 FSliceUint32 []uint32

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,4 @@
// Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved. // Copyright (c) 2012-2018 Ugorji Nwoke. All rights reserved.
// Use of this source code is governed by a MIT license found in the LICENSE file. // Use of this source code is governed by a MIT license found in the LICENSE file.
/* /*
@ -15,8 +15,8 @@ For compatibility with behaviour of msgpack-c reference implementation:
- Go intX (<0) - Go intX (<0)
IS ENCODED AS IS ENCODED AS
msgpack -ve fixnum, signed msgpack -ve fixnum, signed
*/ */
package codec package codec
import ( import (
@ -25,6 +25,7 @@ import (
"math" "math"
"net/rpc" "net/rpc"
"reflect" "reflect"
"time"
) )
const ( const (
@ -78,6 +79,9 @@ const (
mpNegFixNumMax = 0xff mpNegFixNumMax = 0xff
) )
var mpTimeExtTag int8 = -1
var mpTimeExtTagU = uint8(mpTimeExtTag)
// MsgpackSpecRpcMultiArgs is a special type which signifies to the MsgpackSpecRpcCodec // MsgpackSpecRpcMultiArgs is a special type which signifies to the MsgpackSpecRpcCodec
// that the backend RPC service takes multiple arguments, which have been arranged // that the backend RPC service takes multiple arguments, which have been arranged
// in sequence in the slice. // in sequence in the slice.
@ -94,10 +98,18 @@ type msgpackContainerType struct {
} }
var ( var (
msgpackContainerStr = msgpackContainerType{32, mpFixStrMin, mpStr8, mpStr16, mpStr32, true, true, false} msgpackContainerStr = msgpackContainerType{
msgpackContainerBin = msgpackContainerType{0, 0, mpBin8, mpBin16, mpBin32, false, true, true} 32, mpFixStrMin, mpStr8, mpStr16, mpStr32, true, true, false,
msgpackContainerList = msgpackContainerType{16, mpFixArrayMin, 0, mpArray16, mpArray32, true, false, false} }
msgpackContainerMap = msgpackContainerType{16, mpFixMapMin, 0, mpMap16, mpMap32, true, false, false} msgpackContainerBin = msgpackContainerType{
0, 0, mpBin8, mpBin16, mpBin32, false, true, true,
}
msgpackContainerList = msgpackContainerType{
16, mpFixArrayMin, 0, mpArray16, mpArray32, true, false, false,
}
msgpackContainerMap = msgpackContainerType{
16, mpFixMapMin, 0, mpMap16, mpMap32, true, false, false,
}
) )
//--------------------------------------------- //---------------------------------------------
@ -110,6 +122,7 @@ type msgpackEncDriver struct {
w encWriter w encWriter
h *MsgpackHandle h *MsgpackHandle
x [8]byte x [8]byte
_ [3]uint64 // padding
} }
func (e *msgpackEncDriver) EncodeNil() { func (e *msgpackEncDriver) EncodeNil() {
@ -190,6 +203,39 @@ func (e *msgpackEncDriver) EncodeFloat64(f float64) {
bigenHelper{e.x[:8], e.w}.writeUint64(math.Float64bits(f)) bigenHelper{e.x[:8], e.w}.writeUint64(math.Float64bits(f))
} }
func (e *msgpackEncDriver) EncodeTime(t time.Time) {
if t.IsZero() {
e.EncodeNil()
return
}
t = t.UTC()
sec, nsec := t.Unix(), uint64(t.Nanosecond())
var data64 uint64
var l = 4
if sec >= 0 && sec>>34 == 0 {
data64 = (nsec << 34) | uint64(sec)
if data64&0xffffffff00000000 != 0 {
l = 8
}
} else {
l = 12
}
if e.h.WriteExt {
e.encodeExtPreamble(mpTimeExtTagU, l)
} else {
e.writeContainerLen(msgpackContainerStr, l)
}
switch l {
case 4:
bigenHelper{e.x[:4], e.w}.writeUint32(uint32(data64))
case 8:
bigenHelper{e.x[:8], e.w}.writeUint64(data64)
case 12:
bigenHelper{e.x[:4], e.w}.writeUint32(uint32(nsec))
bigenHelper{e.x[:8], e.w}.writeUint64(uint64(sec))
}
}
func (e *msgpackEncDriver) EncodeExt(v interface{}, xtag uint64, ext Ext, _ *Encoder) { func (e *msgpackEncDriver) EncodeExt(v interface{}, xtag uint64, ext Ext, _ *Encoder) {
bs := ext.WriteExt(v) bs := ext.WriteExt(v)
if bs == nil { if bs == nil {
@ -200,7 +246,7 @@ func (e *msgpackEncDriver) EncodeExt(v interface{}, xtag uint64, ext Ext, _ *Enc
e.encodeExtPreamble(uint8(xtag), len(bs)) e.encodeExtPreamble(uint8(xtag), len(bs))
e.w.writeb(bs) e.w.writeb(bs)
} else { } else {
e.EncodeStringBytes(c_RAW, bs) e.EncodeStringBytes(cRAW, bs)
} }
} }
@ -244,7 +290,7 @@ func (e *msgpackEncDriver) WriteMapStart(length int) {
func (e *msgpackEncDriver) EncodeString(c charEncoding, s string) { func (e *msgpackEncDriver) EncodeString(c charEncoding, s string) {
slen := len(s) slen := len(s)
if c == c_RAW && e.h.WriteExt { if c == cRAW && e.h.WriteExt {
e.writeContainerLen(msgpackContainerBin, slen) e.writeContainerLen(msgpackContainerBin, slen)
} else { } else {
e.writeContainerLen(msgpackContainerStr, slen) e.writeContainerLen(msgpackContainerStr, slen)
@ -254,13 +300,13 @@ func (e *msgpackEncDriver) EncodeString(c charEncoding, s string) {
} }
} }
func (e *msgpackEncDriver) EncodeSymbol(v string) {
e.EncodeString(c_UTF8, v)
}
func (e *msgpackEncDriver) EncodeStringBytes(c charEncoding, bs []byte) { func (e *msgpackEncDriver) EncodeStringBytes(c charEncoding, bs []byte) {
if bs == nil {
e.EncodeNil()
return
}
slen := len(bs) slen := len(bs)
if c == c_RAW && e.h.WriteExt { if c == cRAW && e.h.WriteExt {
e.writeContainerLen(msgpackContainerBin, slen) e.writeContainerLen(msgpackContainerBin, slen)
} else { } else {
e.writeContainerLen(msgpackContainerStr, slen) e.writeContainerLen(msgpackContainerStr, slen)
@ -287,10 +333,10 @@ func (e *msgpackEncDriver) writeContainerLen(ct msgpackContainerType, l int) {
//--------------------------------------------- //---------------------------------------------
type msgpackDecDriver struct { type msgpackDecDriver struct {
d *Decoder d *Decoder
r decReader // *Decoder decReader decReaderT r decReader // *Decoder decReader decReaderT
h *MsgpackHandle h *MsgpackHandle
b [scratchByteArrayLen]byte // b [scratchByteArrayLen]byte
bd byte bd byte
bdRead bool bdRead bool
br bool // bytes reader br bool // bytes reader
@ -298,6 +344,7 @@ type msgpackDecDriver struct {
// noStreamingCodec // noStreamingCodec
// decNoSeparator // decNoSeparator
decDriverNoopContainerReader decDriverNoopContainerReader
_ [3]uint64 // padding
} }
// Note: This returns either a primitive (int, bool, etc) for non-containers, // Note: This returns either a primitive (int, bool, etc) for non-containers,
@ -388,7 +435,12 @@ func (d *msgpackDecDriver) DecodeNaked() {
n.v = valueTypeExt n.v = valueTypeExt
clen := d.readExtLen() clen := d.readExtLen()
n.u = uint64(d.r.readn1()) n.u = uint64(d.r.readn1())
n.l = d.r.readx(clen) if n.u == uint64(mpTimeExtTagU) {
n.v = valueTypeTime
n.t = d.decodeTime(clen)
} else {
n.l = d.r.readx(clen)
}
default: default:
d.d.errorf("Nil-Deciphered DecodeValue: %s: hex: %x, dec: %d", msgBadDesc, bd, bd) d.d.errorf("Nil-Deciphered DecodeValue: %s: hex: %x, dec: %d", msgBadDesc, bd, bd)
} }
@ -404,7 +456,7 @@ func (d *msgpackDecDriver) DecodeNaked() {
} }
// int can be decoded from msgpack type: intXXX or uintXXX // int can be decoded from msgpack type: intXXX or uintXXX
func (d *msgpackDecDriver) DecodeInt(bitsize uint8) (i int64) { func (d *msgpackDecDriver) DecodeInt64() (i int64) {
if !d.bdRead { if !d.bdRead {
d.readNextBd() d.readNextBd()
} }
@ -436,19 +488,12 @@ func (d *msgpackDecDriver) DecodeInt(bitsize uint8) (i int64) {
return return
} }
} }
// check overflow (logic adapted from std pkg reflect/value.go OverflowUint()
if bitsize > 0 {
if trunc := (i << (64 - bitsize)) >> (64 - bitsize); i != trunc {
d.d.errorf("Overflow int value: %v", i)
return
}
}
d.bdRead = false d.bdRead = false
return return
} }
// uint can be decoded from msgpack type: intXXX or uintXXX // uint can be decoded from msgpack type: intXXX or uintXXX
func (d *msgpackDecDriver) DecodeUint(bitsize uint8) (ui uint64) { func (d *msgpackDecDriver) DecodeUint64() (ui uint64) {
if !d.bdRead { if !d.bdRead {
d.readNextBd() d.readNextBd()
} }
@ -501,19 +546,12 @@ func (d *msgpackDecDriver) DecodeUint(bitsize uint8) (ui uint64) {
return return
} }
} }
// check overflow (logic adapted from std pkg reflect/value.go OverflowUint()
if bitsize > 0 {
if trunc := (ui << (64 - bitsize)) >> (64 - bitsize); ui != trunc {
d.d.errorf("Overflow uint value: %v", ui)
return
}
}
d.bdRead = false d.bdRead = false
return return
} }
// float can either be decoded from msgpack type: float, double or intX // float can either be decoded from msgpack type: float, double or intX
func (d *msgpackDecDriver) DecodeFloat(chkOverflow32 bool) (f float64) { func (d *msgpackDecDriver) DecodeFloat64() (f float64) {
if !d.bdRead { if !d.bdRead {
d.readNextBd() d.readNextBd()
} }
@ -522,11 +560,7 @@ func (d *msgpackDecDriver) DecodeFloat(chkOverflow32 bool) (f float64) {
} else if d.bd == mpDouble { } else if d.bd == mpDouble {
f = math.Float64frombits(bigen.Uint64(d.r.readx(8))) f = math.Float64frombits(bigen.Uint64(d.r.readx(8)))
} else { } else {
f = float64(d.DecodeInt(0)) f = float64(d.DecodeInt64())
}
if chkOverflow32 && chkOvf.Float32(f) {
d.d.errorf("msgpack: float32 overflow: %v", f)
return
} }
d.bdRead = false d.bdRead = false
return return
@ -554,13 +588,15 @@ func (d *msgpackDecDriver) DecodeBytes(bs []byte, zerocopy bool) (bsOut []byte)
d.readNextBd() d.readNextBd()
} }
// check if an "array" of uint8's (see ContainerType for how to infer if an array)
bd := d.bd
// DecodeBytes could be from: bin str fixstr fixarray array ... // DecodeBytes could be from: bin str fixstr fixarray array ...
var clen int var clen int
vt := d.ContainerType() vt := d.ContainerType()
switch vt { switch vt {
case valueTypeBytes: case valueTypeBytes:
// valueTypeBytes may be a mpBin or an mpStr container // valueTypeBytes may be a mpBin or an mpStr container
if bd := d.bd; bd == mpBin8 || bd == mpBin16 || bd == mpBin32 { if bd == mpBin8 || bd == mpBin16 || bd == mpBin32 {
clen = d.readContainerLen(msgpackContainerBin) clen = d.readContainerLen(msgpackContainerBin)
} else { } else {
clen = d.readContainerLen(msgpackContainerStr) clen = d.readContainerLen(msgpackContainerStr)
@ -568,28 +604,17 @@ func (d *msgpackDecDriver) DecodeBytes(bs []byte, zerocopy bool) (bsOut []byte)
case valueTypeString: case valueTypeString:
clen = d.readContainerLen(msgpackContainerStr) clen = d.readContainerLen(msgpackContainerStr)
case valueTypeArray: case valueTypeArray:
clen = d.readContainerLen(msgpackContainerList) if zerocopy && len(bs) == 0 {
// ensure everything after is one byte each bs = d.d.b[:]
for i := 0; i < clen; i++ {
d.readNextBd()
if d.bd == mpNil {
bs = append(bs, 0)
} else if d.bd == mpUint8 {
bs = append(bs, d.r.readn1())
} else {
d.d.errorf("cannot read non-byte into a byte array")
return
}
} }
d.bdRead = false bsOut, _ = fastpathTV.DecSliceUint8V(bs, true, d.d)
return bs return
default: default:
d.d.errorf("invalid container type: expecting bin|str|array") d.d.errorf("invalid container type: expecting bin|str|array, got: 0x%x", uint8(vt))
return return
} }
// these are (bin|str)(8|16|32) // these are (bin|str)(8|16|32)
// println("DecodeBytes: clen: ", clen)
d.bdRead = false d.bdRead = false
// bytes may be nil, so handle it. if nil, clen=-1. // bytes may be nil, so handle it. if nil, clen=-1.
if clen < 0 { if clen < 0 {
@ -599,18 +624,18 @@ func (d *msgpackDecDriver) DecodeBytes(bs []byte, zerocopy bool) (bsOut []byte)
if d.br { if d.br {
return d.r.readx(clen) return d.r.readx(clen)
} else if len(bs) == 0 { } else if len(bs) == 0 {
bs = d.b[:] bs = d.d.b[:]
} }
} }
return decByteSlice(d.r, clen, d.d.h.MaxInitLen, bs) return decByteSlice(d.r, clen, d.h.MaxInitLen, bs)
} }
func (d *msgpackDecDriver) DecodeString() (s string) { func (d *msgpackDecDriver) DecodeString() (s string) {
return string(d.DecodeBytes(d.b[:], true)) return string(d.DecodeBytes(d.d.b[:], true))
} }
func (d *msgpackDecDriver) DecodeStringAsBytes() (s []byte) { func (d *msgpackDecDriver) DecodeStringAsBytes() (s []byte) {
return d.DecodeBytes(d.b[:], true) return d.DecodeBytes(d.d.b[:], true)
} }
func (d *msgpackDecDriver) readNextBd() { func (d *msgpackDecDriver) readNextBd() {
@ -643,9 +668,10 @@ func (d *msgpackDecDriver) ContainerType() (vt valueType) {
return valueTypeArray return valueTypeArray
} else if bd == mpMap16 || bd == mpMap32 || (bd >= mpFixMapMin && bd <= mpFixMapMax) { } else if bd == mpMap16 || bd == mpMap32 || (bd >= mpFixMapMin && bd <= mpFixMapMax) {
return valueTypeMap return valueTypeMap
} else {
// d.d.errorf("isContainerType: unsupported parameter: %v", vt)
} }
// else {
// d.d.errorf("isContainerType: unsupported parameter: %v", vt)
// }
return valueTypeUnset return valueTypeUnset
} }
@ -655,7 +681,7 @@ func (d *msgpackDecDriver) TryDecodeAsNil() (v bool) {
} }
if d.bd == mpNil { if d.bd == mpNil {
d.bdRead = false d.bdRead = false
v = true return true
} }
return return
} }
@ -721,6 +747,57 @@ func (d *msgpackDecDriver) readExtLen() (clen int) {
return return
} }
func (d *msgpackDecDriver) DecodeTime() (t time.Time) {
// decode time from string bytes or ext
if !d.bdRead {
d.readNextBd()
}
if d.bd == mpNil {
d.bdRead = false
return
}
var clen int
switch d.ContainerType() {
case valueTypeBytes, valueTypeString:
clen = d.readContainerLen(msgpackContainerStr)
default:
// expect to see mpFixExt4,-1 OR mpFixExt8,-1 OR mpExt8,12,-1
d.bdRead = false
b2 := d.r.readn1()
if d.bd == mpFixExt4 && b2 == mpTimeExtTagU {
clen = 4
} else if d.bd == mpFixExt8 && b2 == mpTimeExtTagU {
clen = 8
} else if d.bd == mpExt8 && b2 == 12 && d.r.readn1() == mpTimeExtTagU {
clen = 12
} else {
d.d.errorf("invalid bytes for decoding time as extension: got 0x%x, 0x%x", d.bd, b2)
return
}
}
return d.decodeTime(clen)
}
func (d *msgpackDecDriver) decodeTime(clen int) (t time.Time) {
// bs = d.r.readx(clen)
d.bdRead = false
switch clen {
case 4:
t = time.Unix(int64(bigen.Uint32(d.r.readx(4))), 0).UTC()
case 8:
tv := bigen.Uint64(d.r.readx(8))
t = time.Unix(int64(tv&0x00000003ffffffff), int64(tv>>34)).UTC()
case 12:
nsec := bigen.Uint32(d.r.readx(4))
sec := bigen.Uint64(d.r.readx(8))
t = time.Unix(int64(sec), int64(nsec)).UTC()
default:
d.d.errorf("invalid length of bytes for decoding time - expecting 4 or 8 or 12, got %d", clen)
return
}
return
}
func (d *msgpackDecDriver) DecodeExt(rv interface{}, xtag uint64, ext Ext) (realxtag uint64) { func (d *msgpackDecDriver) DecodeExt(rv interface{}, xtag uint64, ext Ext) (realxtag uint64) {
if xtag > 0xff { if xtag > 0xff {
d.d.errorf("decodeExt: tag must be <= 0xff; got: %v", xtag) d.d.errorf("decodeExt: tag must be <= 0xff; got: %v", xtag)
@ -784,12 +861,19 @@ type MsgpackHandle struct {
// type is provided (e.g. decoding into a nil interface{}), you get back // type is provided (e.g. decoding into a nil interface{}), you get back
// a []byte or string based on the setting of RawToString. // a []byte or string based on the setting of RawToString.
WriteExt bool WriteExt bool
binaryEncodingType binaryEncodingType
noElemSeparators noElemSeparators
_ [1]uint64 // padding
} }
// Name returns the name of the handle: msgpack
func (h *MsgpackHandle) Name() string { return "msgpack" }
// SetBytesExt sets an extension
func (h *MsgpackHandle) SetBytesExt(rt reflect.Type, tag uint64, ext BytesExt) (err error) { func (h *MsgpackHandle) SetBytesExt(rt reflect.Type, tag uint64, ext BytesExt) (err error) {
return h.SetExt(rt, tag, &setExtWrapper{b: ext}) return h.SetExt(rt, tag, &extWrapper{ext, interfaceExtFailer{}})
} }
func (h *MsgpackHandle) newEncDriver(e *Encoder) encDriver { func (h *MsgpackHandle) newEncDriver(e *Encoder) encDriver {
@ -827,7 +911,7 @@ func (c *msgpackSpecRpcCodec) WriteRequest(r *rpc.Request, body interface{}) err
bodyArr = []interface{}{body} bodyArr = []interface{}{body}
} }
r2 := []interface{}{0, uint32(r.Seq), r.ServiceMethod, bodyArr} r2 := []interface{}{0, uint32(r.Seq), r.ServiceMethod, bodyArr}
return c.write(r2, nil, false, true) return c.write(r2, nil, false)
} }
func (c *msgpackSpecRpcCodec) WriteResponse(r *rpc.Response, body interface{}) error { func (c *msgpackSpecRpcCodec) WriteResponse(r *rpc.Response, body interface{}) error {
@ -839,7 +923,7 @@ func (c *msgpackSpecRpcCodec) WriteResponse(r *rpc.Response, body interface{}) e
body = nil body = nil
} }
r2 := []interface{}{1, uint32(r.Seq), moe, body} r2 := []interface{}{1, uint32(r.Seq), moe, body}
return c.write(r2, nil, false, true) return c.write(r2, nil, false)
} }
func (c *msgpackSpecRpcCodec) ReadResponseHeader(r *rpc.Response) error { func (c *msgpackSpecRpcCodec) ReadResponseHeader(r *rpc.Response) error {
@ -887,21 +971,19 @@ func (c *msgpackSpecRpcCodec) parseCustomHeader(expectTypeByte byte, msgid *uint
var b = ba[0] var b = ba[0]
if b != fia { if b != fia {
err = fmt.Errorf("Unexpected value for array descriptor: Expecting %v. Received %v", fia, b) err = fmt.Errorf("Unexpected value for array descriptor: Expecting %v. Received %v", fia, b)
return } else {
} err = c.read(&b)
if err == nil {
if err = c.read(&b); err != nil { if b != expectTypeByte {
return err = fmt.Errorf("Unexpected byte descriptor. Expecting %v; Received %v",
} expectTypeByte, b)
if b != expectTypeByte { } else {
err = fmt.Errorf("Unexpected byte descriptor in header. Expecting %v. Received %v", expectTypeByte, b) err = c.read(msgid)
return if err == nil {
} err = c.read(methodOrError)
if err = c.read(msgid); err != nil { }
return }
} }
if err = c.read(methodOrError); err != nil {
return
} }
return return
} }
@ -914,7 +996,8 @@ type msgpackSpecRpc struct{}
// MsgpackSpecRpc implements Rpc using the communication protocol defined in // MsgpackSpecRpc implements Rpc using the communication protocol defined in
// the msgpack spec at https://github.com/msgpack-rpc/msgpack-rpc/blob/master/spec.md . // the msgpack spec at https://github.com/msgpack-rpc/msgpack-rpc/blob/master/spec.md .
// Its methods (ServerCodec and ClientCodec) return values that implement RpcCodecBuffered. //
// See GoRpc documentation, for information on buffering for better performance.
var MsgpackSpecRpc msgpackSpecRpc var MsgpackSpecRpc msgpackSpecRpc
func (x msgpackSpecRpc) ServerCodec(conn io.ReadWriteCloser, h Handle) rpc.ServerCodec { func (x msgpackSpecRpc) ServerCodec(conn io.ReadWriteCloser, h Handle) rpc.ServerCodec {

View File

@ -1,4 +1,4 @@
// Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved. // Copyright (c) 2012-2018 Ugorji Nwoke. All rights reserved.
// Use of this source code is governed by a MIT license found in the LICENSE file. // Use of this source code is governed by a MIT license found in the LICENSE file.
// +build ignore // +build ignore
@ -91,8 +91,9 @@ func (h *noopDrv) EncodeArrayStart(length int) { h.start(true) }
func (h *noopDrv) EncodeMapStart(length int) { h.start(false) } func (h *noopDrv) EncodeMapStart(length int) { h.start(false) }
func (h *noopDrv) EncodeEnd() { h.end() } func (h *noopDrv) EncodeEnd() { h.end() }
func (h *noopDrv) EncodeString(c charEncoding, v string) {} func (h *noopDrv) EncodeString(c charEncoding, v string) {}
func (h *noopDrv) EncodeSymbol(v string) {}
// func (h *noopDrv) EncodeSymbol(v string) {}
func (h *noopDrv) EncodeStringBytes(c charEncoding, v []byte) {} func (h *noopDrv) EncodeStringBytes(c charEncoding, v []byte) {}
func (h *noopDrv) EncodeExt(rv interface{}, xtag uint64, ext Ext, e *Encoder) {} func (h *noopDrv) EncodeExt(rv interface{}, xtag uint64, ext Ext, e *Encoder) {}
@ -119,9 +120,12 @@ func (h *noopDrv) ReadArrayStart() int { h.start(false); return h.m(10) }
func (h *noopDrv) ContainerType() (vt valueType) { func (h *noopDrv) ContainerType() (vt valueType) {
// return h.m(2) == 0 // return h.m(2) == 0
// handle kStruct, which will bomb is it calls this and doesn't get back a map or array. // handle kStruct, which will bomb is it calls this and
// consequently, if the return value is not map or array, reset it to one of them based on h.m(7) % 2 // doesn't get back a map or array.
// for kstruct: at least one out of every 2 times, return one of valueTypeMap or Array (else kstruct bombs) // consequently, if the return value is not map or array,
// reset it to one of them based on h.m(7) % 2
// for kstruct: at least one out of every 2 times,
// return one of valueTypeMap or Array (else kstruct bombs)
// however, every 10th time it is called, we just return something else. // however, every 10th time it is called, we just return something else.
var vals = [...]valueType{valueTypeArray, valueTypeMap} var vals = [...]valueType{valueTypeArray, valueTypeMap}
// ------------ TAKE ------------ // ------------ TAKE ------------
@ -150,7 +154,8 @@ func (h *noopDrv) ContainerType() (vt valueType) {
// } // }
// return valueTypeUnset // return valueTypeUnset
// TODO: may need to tweak this so it works. // TODO: may need to tweak this so it works.
// if h.ct == valueTypeMap && vt == valueTypeArray || h.ct == valueTypeArray && vt == valueTypeMap { // if h.ct == valueTypeMap && vt == valueTypeArray ||
// h.ct == valueTypeArray && vt == valueTypeMap {
// h.cb = !h.cb // h.cb = !h.cb
// h.ct = vt // h.ct = vt
// return h.cb // return h.cb

View File

@ -1,6 +1,6 @@
// +build x // +build x
// Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved. // Copyright (c) 2012-2018 Ugorji Nwoke. All rights reserved.
// Use of this source code is governed by a MIT license found in the LICENSE file. // Use of this source code is governed by a MIT license found in the LICENSE file.
package codec package codec

View File

@ -1,9 +1,10 @@
// Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved. // Copyright (c) 2012-2018 Ugorji Nwoke. All rights reserved.
// Use of this source code is governed by a MIT license found in the LICENSE file. // Use of this source code is governed by a MIT license found in the LICENSE file.
package codec package codec
import ( import (
"bufio"
"errors" "errors"
"io" "io"
"net/rpc" "net/rpc"
@ -16,19 +17,14 @@ type Rpc interface {
ClientCodec(conn io.ReadWriteCloser, h Handle) rpc.ClientCodec ClientCodec(conn io.ReadWriteCloser, h Handle) rpc.ClientCodec
} }
// // RpcCodecBuffered allows access to the underlying bufio.Reader/Writer // RPCOptions holds options specific to rpc functionality
// // used by the rpc connection. It accommodates use-cases where the connection type RPCOptions struct {
// // should be used by rpc and non-rpc functions, e.g. streaming a file after // RPCNoBuffer configures whether we attempt to buffer reads and writes during RPC calls.
// // sending an rpc response. //
// type RpcCodecBuffered interface { // Set RPCNoBuffer=true to turn buffering off.
// BufferedReader() *bufio.Reader // Buffering can still be done if buffered connections are passed in, or
// BufferedWriter() *bufio.Writer // buffering is configured on the handle.
// } RPCNoBuffer bool
// -------------------------------------
type rpcFlusher interface {
Flush() error
} }
// rpcCodec defines the struct members and common methods. // rpcCodec defines the struct members and common methods.
@ -36,7 +32,7 @@ type rpcCodec struct {
c io.Closer c io.Closer
r io.Reader r io.Reader
w io.Writer w io.Writer
f rpcFlusher f ioFlusher
dec *Decoder dec *Decoder
enc *Encoder enc *Encoder
@ -45,8 +41,9 @@ type rpcCodec struct {
mu sync.Mutex mu sync.Mutex
h Handle h Handle
cls bool cls bool
clsmu sync.RWMutex clsmu sync.RWMutex
clsErr error
} }
func newRPCCodec(conn io.ReadWriteCloser, h Handle) rpcCodec { func newRPCCodec(conn io.ReadWriteCloser, h Handle) rpcCodec {
@ -59,7 +56,26 @@ func newRPCCodec2(r io.Reader, w io.Writer, c io.Closer, h Handle) rpcCodec {
if jsonH, ok := h.(*JsonHandle); ok && !jsonH.TermWhitespace { if jsonH, ok := h.(*JsonHandle); ok && !jsonH.TermWhitespace {
panic(errors.New("rpc requires a JsonHandle with TermWhitespace set to true")) panic(errors.New("rpc requires a JsonHandle with TermWhitespace set to true"))
} }
f, _ := w.(rpcFlusher) // always ensure that we use a flusher, and always flush what was written to the connection.
// we lose nothing by using a buffered writer internally.
f, ok := w.(ioFlusher)
bh := h.getBasicHandle()
if !bh.RPCNoBuffer {
if bh.WriterBufferSize <= 0 {
if !ok {
bw := bufio.NewWriter(w)
f, w = bw, bw
}
}
if bh.ReaderBufferSize <= 0 {
if _, ok = w.(ioPeeker); !ok {
if _, ok = w.(ioBuffered); !ok {
br := bufio.NewReader(r)
r = br
}
}
}
}
return rpcCodec{ return rpcCodec{
c: c, c: c,
w: w, w: w,
@ -71,66 +87,75 @@ func newRPCCodec2(r io.Reader, w io.Writer, c io.Closer, h Handle) rpcCodec {
} }
} }
// func (c *rpcCodec) BufferedReader() *bufio.Reader { func (c *rpcCodec) write(obj1, obj2 interface{}, writeObj2 bool) (err error) {
// return c.br
// }
// func (c *rpcCodec) BufferedWriter() *bufio.Writer {
// return c.bw
// }
func (c *rpcCodec) write(obj1, obj2 interface{}, writeObj2, doFlush bool) (err error) {
if c.isClosed() { if c.isClosed() {
return io.EOF return c.clsErr
} }
if err = c.enc.Encode(obj1); err != nil { err = c.enc.Encode(obj1)
return if err == nil {
} if writeObj2 {
if writeObj2 { err = c.enc.Encode(obj2)
if err = c.enc.Encode(obj2); err != nil {
return
} }
// if err == nil && c.f != nil {
// err = c.f.Flush()
// }
} }
if doFlush && c.f != nil { if c.f != nil {
return c.f.Flush() if err == nil {
err = c.f.Flush()
} else {
c.f.Flush()
}
} }
return return
} }
func (c *rpcCodec) swallow(err *error) {
defer panicToErr(c.dec, err)
c.dec.swallow()
}
func (c *rpcCodec) read(obj interface{}) (err error) { func (c *rpcCodec) read(obj interface{}) (err error) {
if c.isClosed() { if c.isClosed() {
return io.EOF return c.clsErr
} }
//If nil is passed in, we should still attempt to read content to nowhere. //If nil is passed in, we should read and discard
if obj == nil { if obj == nil {
var obj2 interface{} // var obj2 interface{}
return c.dec.Decode(&obj2) // return c.dec.Decode(&obj2)
c.swallow(&err)
return
} }
return c.dec.Decode(obj) return c.dec.Decode(obj)
} }
func (c *rpcCodec) isClosed() bool { func (c *rpcCodec) isClosed() (b bool) {
if c.c == nil { if c.c != nil {
return false c.clsmu.RLock()
b = c.cls
c.clsmu.RUnlock()
} }
c.clsmu.RLock() return
x := c.cls
c.clsmu.RUnlock()
return x
} }
func (c *rpcCodec) Close() error { func (c *rpcCodec) Close() error {
if c.c == nil { if c.c == nil || c.isClosed() {
return nil return c.clsErr
}
if c.isClosed() {
return io.EOF
} }
c.clsmu.Lock() c.clsmu.Lock()
c.cls = true c.cls = true
err := c.c.Close() // var fErr error
// if c.f != nil {
// fErr = c.f.Flush()
// }
// _ = fErr
// c.clsErr = c.c.Close()
// if c.clsErr == nil && fErr != nil {
// c.clsErr = fErr
// }
c.clsErr = c.c.Close()
c.clsmu.Unlock() c.clsmu.Unlock()
return err return c.clsErr
} }
func (c *rpcCodec) ReadResponseBody(body interface{}) error { func (c *rpcCodec) ReadResponseBody(body interface{}) error {
@ -147,13 +172,13 @@ func (c *goRpcCodec) WriteRequest(r *rpc.Request, body interface{}) error {
// Must protect for concurrent access as per API // Must protect for concurrent access as per API
c.mu.Lock() c.mu.Lock()
defer c.mu.Unlock() defer c.mu.Unlock()
return c.write(r, body, true, true) return c.write(r, body, true)
} }
func (c *goRpcCodec) WriteResponse(r *rpc.Response, body interface{}) error { func (c *goRpcCodec) WriteResponse(r *rpc.Response, body interface{}) error {
c.mu.Lock() c.mu.Lock()
defer c.mu.Unlock() defer c.mu.Unlock()
return c.write(r, body, true, true) return c.write(r, body, true)
} }
func (c *goRpcCodec) ReadResponseHeader(r *rpc.Response) error { func (c *goRpcCodec) ReadResponseHeader(r *rpc.Response) error {
@ -175,7 +200,36 @@ func (c *goRpcCodec) ReadRequestBody(body interface{}) error {
type goRpc struct{} type goRpc struct{}
// GoRpc implements Rpc using the communication protocol defined in net/rpc package. // GoRpc implements Rpc using the communication protocol defined in net/rpc package.
// Its methods (ServerCodec and ClientCodec) return values that implement RpcCodecBuffered. //
// Note: network connection (from net.Dial, of type io.ReadWriteCloser) is not buffered.
//
// For performance, you should configure WriterBufferSize and ReaderBufferSize on the handle.
// This ensures we use an adequate buffer during reading and writing.
// If not configured, we will internally initialize and use a buffer during reads and writes.
// This can be turned off via the RPCNoBuffer option on the Handle.
// var handle codec.JsonHandle
// handle.RPCNoBuffer = true // turns off attempt by rpc module to initialize a buffer
//
// Example 1: one way of configuring buffering explicitly:
// var handle codec.JsonHandle // codec handle
// handle.ReaderBufferSize = 1024
// handle.WriterBufferSize = 1024
// var conn io.ReadWriteCloser // connection got from a socket
// var serverCodec = GoRpc.ServerCodec(conn, handle)
// var clientCodec = GoRpc.ClientCodec(conn, handle)
//
// Example 2: you can also explicitly create a buffered connection yourself,
// and not worry about configuring the buffer sizes in the Handle.
// var handle codec.Handle // codec handle
// var conn io.ReadWriteCloser // connection got from a socket
// var bufconn = struct { // bufconn here is a buffered io.ReadWriteCloser
// io.Closer
// *bufio.Reader
// *bufio.Writer
// }{conn, bufio.NewReader(conn), bufio.NewWriter(conn)}
// var serverCodec = GoRpc.ServerCodec(bufconn, handle)
// var clientCodec = GoRpc.ClientCodec(bufconn, handle)
//
var GoRpc goRpc var GoRpc goRpc
func (x goRpc) ServerCodec(conn io.ReadWriteCloser, h Handle) rpc.ServerCodec { func (x goRpc) ServerCodec(conn io.ReadWriteCloser, h Handle) rpc.ServerCodec {
@ -185,11 +239,3 @@ func (x goRpc) ServerCodec(conn io.ReadWriteCloser, h Handle) rpc.ServerCodec {
func (x goRpc) ClientCodec(conn io.ReadWriteCloser, h Handle) rpc.ClientCodec { func (x goRpc) ClientCodec(conn io.ReadWriteCloser, h Handle) rpc.ClientCodec {
return &goRpcCodec{newRPCCodec(conn, h)} return &goRpcCodec{newRPCCodec(conn, h)}
} }
// Use this method to allow you create wrapped versions of the reader, writer if desired.
// For example, to create a buffered implementation.
func (x goRpc) Codec(r io.Reader, w io.Writer, c io.Closer, h Handle) *goRpcCodec {
return &goRpcCodec{newRPCCodec2(r, w, c, h)}
}
// var _ RpcCodecBuffered = (*rpcCodec)(nil) // ensure *rpcCodec implements RpcCodecBuffered

View File

@ -1,4 +1,4 @@
// Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved. // Copyright (c) 2012-2018 Ugorji Nwoke. All rights reserved.
// Use of this source code is governed by a MIT license found in the LICENSE file. // Use of this source code is governed by a MIT license found in the LICENSE file.
package codec package codec
@ -45,6 +45,8 @@ import (
"flag" "flag"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"log"
"sync" "sync"
"testing" "testing"
) )
@ -110,6 +112,8 @@ var (
testMaxInitLen int testMaxInitLen int
testNumRepeatString int testNumRepeatString int
testRpcBufsize int
) )
// variables that are not flags, but which can configure the handles // variables that are not flags, but which can configure the handles
@ -131,6 +135,7 @@ var (
) )
func init() { func init() {
log.SetOutput(ioutil.Discard) // don't allow things log to standard out/err
testHEDs = make([]testHED, 0, 32) testHEDs = make([]testHED, 0, 32)
testHandles = append(testHandles, testHandles = append(testHandles,
// testNoopH, // testNoopH,
@ -143,7 +148,7 @@ func init() {
func testInitFlags() { func testInitFlags() {
// delete(testDecOpts.ExtFuncs, timeTyp) // delete(testDecOpts.ExtFuncs, timeTyp)
flag.IntVar(&testDepth, "tsd", 0, "Test Struc Depth") flag.IntVar(&testDepth, "tsd", 0, "Test Struc Depth")
flag.BoolVar(&testVerbose, "tv", false, "Test Verbose") flag.BoolVar(&testVerbose, "tv", false, "Test Verbose (no longer used - here for compatibility)")
flag.BoolVar(&testInitDebug, "tg", false, "Test Init Debug") flag.BoolVar(&testInitDebug, "tg", false, "Test Init Debug")
flag.IntVar(&testUseIoEncDec, "ti", -1, "Use IO Reader/Writer for Marshal/Unmarshal ie >= 0") flag.IntVar(&testUseIoEncDec, "ti", -1, "Use IO Reader/Writer for Marshal/Unmarshal ie >= 0")
flag.BoolVar(&testUseIoWrapper, "tiw", false, "Wrap the IO Reader/Writer with a base pass-through reader/writer") flag.BoolVar(&testUseIoWrapper, "tiw", false, "Wrap the IO Reader/Writer with a base pass-through reader/writer")
@ -272,7 +277,7 @@ func logT(x interface{}, format string, args ...interface{}) {
t.Logf(format, args...) t.Logf(format, args...)
} else if b, ok := x.(*testing.B); ok && b != nil { } else if b, ok := x.(*testing.B); ok && b != nil {
b.Logf(format, args...) b.Logf(format, args...)
} else if testVerbose { } else { // if testing.Verbose() { // if testVerbose {
if len(format) == 0 || format[len(format)-1] != '\n' { if len(format) == 0 || format[len(format)-1] != '\n' {
format = format + "\n" format = format + "\n"
} }

View File

@ -1,4 +1,4 @@
// Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved. // Copyright (c) 2012-2018 Ugorji Nwoke. All rights reserved.
// Use of this source code is governed by a MIT license found in the LICENSE file. // Use of this source code is governed by a MIT license found in the LICENSE file.
package codec package codec
@ -6,6 +6,7 @@ package codec
import ( import (
"math" "math"
"reflect" "reflect"
"time"
) )
const ( const (
@ -20,6 +21,8 @@ const (
simpleVdPosInt = 8 simpleVdPosInt = 8
simpleVdNegInt = 12 simpleVdNegInt = 12
simpleVdTime = 24
// containers: each lasts for 4 (ie n, n+1, n+2, ... n+7) // containers: each lasts for 4 (ie n, n+1, n+2, ... n+7)
simpleVdString = 216 simpleVdString = 216
simpleVdByteArray = 224 simpleVdByteArray = 224
@ -30,12 +33,15 @@ const (
type simpleEncDriver struct { type simpleEncDriver struct {
noBuiltInTypes noBuiltInTypes
encDriverNoopContainerWriter
// encNoSeparator // encNoSeparator
e *Encoder e *Encoder
h *SimpleHandle h *SimpleHandle
w encWriter w encWriter
b [8]byte b [8]byte
// c containerState
encDriverTrackContainerWriter
// encDriverNoopContainerWriter
_ [2]uint64 // padding
} }
func (e *simpleEncDriver) EncodeNil() { func (e *simpleEncDriver) EncodeNil() {
@ -43,6 +49,10 @@ func (e *simpleEncDriver) EncodeNil() {
} }
func (e *simpleEncDriver) EncodeBool(b bool) { func (e *simpleEncDriver) EncodeBool(b bool) {
if e.h.EncZeroValuesAsNil && e.c != containerMapKey && !b {
e.EncodeNil()
return
}
if b { if b {
e.w.writen1(simpleVdTrue) e.w.writen1(simpleVdTrue)
} else { } else {
@ -51,11 +61,19 @@ func (e *simpleEncDriver) EncodeBool(b bool) {
} }
func (e *simpleEncDriver) EncodeFloat32(f float32) { func (e *simpleEncDriver) EncodeFloat32(f float32) {
if e.h.EncZeroValuesAsNil && e.c != containerMapKey && f == 0.0 {
e.EncodeNil()
return
}
e.w.writen1(simpleVdFloat32) e.w.writen1(simpleVdFloat32)
bigenHelper{e.b[:4], e.w}.writeUint32(math.Float32bits(f)) bigenHelper{e.b[:4], e.w}.writeUint32(math.Float32bits(f))
} }
func (e *simpleEncDriver) EncodeFloat64(f float64) { func (e *simpleEncDriver) EncodeFloat64(f float64) {
if e.h.EncZeroValuesAsNil && e.c != containerMapKey && f == 0.0 {
e.EncodeNil()
return
}
e.w.writen1(simpleVdFloat64) e.w.writen1(simpleVdFloat64)
bigenHelper{e.b[:8], e.w}.writeUint64(math.Float64bits(f)) bigenHelper{e.b[:8], e.w}.writeUint64(math.Float64bits(f))
} }
@ -73,6 +91,10 @@ func (e *simpleEncDriver) EncodeUint(v uint64) {
} }
func (e *simpleEncDriver) encUint(v uint64, bd uint8) { func (e *simpleEncDriver) encUint(v uint64, bd uint8) {
if e.h.EncZeroValuesAsNil && e.c != containerMapKey && v == 0 {
e.EncodeNil()
return
}
if v <= math.MaxUint8 { if v <= math.MaxUint8 {
e.w.writen2(bd, uint8(v)) e.w.writen2(bd, uint8(v))
} else if v <= math.MaxUint16 { } else if v <= math.MaxUint16 {
@ -126,27 +148,54 @@ func (e *simpleEncDriver) encodeExtPreamble(xtag byte, length int) {
} }
func (e *simpleEncDriver) WriteArrayStart(length int) { func (e *simpleEncDriver) WriteArrayStart(length int) {
e.c = containerArrayStart
e.encLen(simpleVdArray, length) e.encLen(simpleVdArray, length)
} }
func (e *simpleEncDriver) WriteMapStart(length int) { func (e *simpleEncDriver) WriteMapStart(length int) {
e.c = containerMapStart
e.encLen(simpleVdMap, length) e.encLen(simpleVdMap, length)
} }
func (e *simpleEncDriver) EncodeString(c charEncoding, v string) { func (e *simpleEncDriver) EncodeString(c charEncoding, v string) {
if false && e.h.EncZeroValuesAsNil && e.c != containerMapKey && v == "" {
e.EncodeNil()
return
}
e.encLen(simpleVdString, len(v)) e.encLen(simpleVdString, len(v))
e.w.writestr(v) e.w.writestr(v)
} }
func (e *simpleEncDriver) EncodeSymbol(v string) { // func (e *simpleEncDriver) EncodeSymbol(v string) {
e.EncodeString(c_UTF8, v) // e.EncodeString(cUTF8, v)
} // }
func (e *simpleEncDriver) EncodeStringBytes(c charEncoding, v []byte) { func (e *simpleEncDriver) EncodeStringBytes(c charEncoding, v []byte) {
// if e.h.EncZeroValuesAsNil && e.c != containerMapKey && v == nil {
if v == nil {
e.EncodeNil()
return
}
e.encLen(simpleVdByteArray, len(v)) e.encLen(simpleVdByteArray, len(v))
e.w.writeb(v) e.w.writeb(v)
} }
func (e *simpleEncDriver) EncodeTime(t time.Time) {
// if e.h.EncZeroValuesAsNil && e.c != containerMapKey && t.IsZero() {
if t.IsZero() {
e.EncodeNil()
return
}
v, err := t.MarshalBinary()
if err != nil {
e.e.errorv(err)
return
}
// time.Time marshalbinary takes about 14 bytes.
e.w.writen2(simpleVdTime, uint8(len(v)))
e.w.writeb(v)
}
//------------------------------------ //------------------------------------
type simpleDecDriver struct { type simpleDecDriver struct {
@ -155,11 +204,13 @@ type simpleDecDriver struct {
r decReader r decReader
bdRead bool bdRead bool
bd byte bd byte
br bool // bytes reader br bool // a bytes reader?
b [scratchByteArrayLen]byte c containerState
// b [scratchByteArrayLen]byte
noBuiltInTypes noBuiltInTypes
// noStreamingCodec // noStreamingCodec
decDriverNoopContainerReader decDriverNoopContainerReader
_ [3]uint64 // padding
} }
func (d *simpleDecDriver) readNextBd() { func (d *simpleDecDriver) readNextBd() {
@ -178,23 +229,27 @@ func (d *simpleDecDriver) ContainerType() (vt valueType) {
if !d.bdRead { if !d.bdRead {
d.readNextBd() d.readNextBd()
} }
if d.bd == simpleVdNil { switch d.bd {
case simpleVdNil:
return valueTypeNil return valueTypeNil
} else if d.bd == simpleVdByteArray || d.bd == simpleVdByteArray+1 || case simpleVdByteArray, simpleVdByteArray + 1,
d.bd == simpleVdByteArray+2 || d.bd == simpleVdByteArray+3 || d.bd == simpleVdByteArray+4 { simpleVdByteArray + 2, simpleVdByteArray + 3, simpleVdByteArray + 4:
return valueTypeBytes return valueTypeBytes
} else if d.bd == simpleVdString || d.bd == simpleVdString+1 || case simpleVdString, simpleVdString + 1,
d.bd == simpleVdString+2 || d.bd == simpleVdString+3 || d.bd == simpleVdString+4 { simpleVdString + 2, simpleVdString + 3, simpleVdString + 4:
return valueTypeString return valueTypeString
} else if d.bd == simpleVdArray || d.bd == simpleVdArray+1 || case simpleVdArray, simpleVdArray + 1,
d.bd == simpleVdArray+2 || d.bd == simpleVdArray+3 || d.bd == simpleVdArray+4 { simpleVdArray + 2, simpleVdArray + 3, simpleVdArray + 4:
return valueTypeArray return valueTypeArray
} else if d.bd == simpleVdMap || d.bd == simpleVdMap+1 || case simpleVdMap, simpleVdMap + 1,
d.bd == simpleVdMap+2 || d.bd == simpleVdMap+3 || d.bd == simpleVdMap+4 { simpleVdMap + 2, simpleVdMap + 3, simpleVdMap + 4:
return valueTypeMap return valueTypeMap
} else { // case simpleVdTime:
// d.d.errorf("isContainerType: unsupported parameter: %v", vt) // return valueTypeTime
} }
// else {
// d.d.errorf("isContainerType: unsupported parameter: %v", vt)
// }
return valueTypeUnset return valueTypeUnset
} }
@ -235,7 +290,7 @@ func (d *simpleDecDriver) decCheckInteger() (ui uint64, neg bool) {
ui = uint64(bigen.Uint64(d.r.readx(8))) ui = uint64(bigen.Uint64(d.r.readx(8)))
neg = true neg = true
default: default:
d.d.errorf("decIntAny: Integer only valid from pos/neg integer1..8. Invalid descriptor: %v", d.bd) d.d.errorf("Integer only valid from pos/neg integer1..8. Invalid descriptor: %v", d.bd)
return return
} }
// don't do this check, because callers may only want the unsigned value. // don't do this check, because callers may only want the unsigned value.
@ -246,39 +301,27 @@ func (d *simpleDecDriver) decCheckInteger() (ui uint64, neg bool) {
return return
} }
func (d *simpleDecDriver) DecodeInt(bitsize uint8) (i int64) { func (d *simpleDecDriver) DecodeInt64() (i int64) {
ui, neg := d.decCheckInteger() ui, neg := d.decCheckInteger()
i, overflow := chkOvf.SignedInt(ui) i = chkOvf.SignedIntV(ui)
if overflow {
d.d.errorf("simple: overflow converting %v to signed integer", ui)
return
}
if neg { if neg {
i = -i i = -i
} }
if chkOvf.Int(i, bitsize) {
d.d.errorf("simple: overflow integer: %v", i)
return
}
d.bdRead = false d.bdRead = false
return return
} }
func (d *simpleDecDriver) DecodeUint(bitsize uint8) (ui uint64) { func (d *simpleDecDriver) DecodeUint64() (ui uint64) {
ui, neg := d.decCheckInteger() ui, neg := d.decCheckInteger()
if neg { if neg {
d.d.errorf("Assigning negative signed value to unsigned type") d.d.errorf("Assigning negative signed value to unsigned type")
return return
} }
if chkOvf.Uint(ui, bitsize) {
d.d.errorf("simple: overflow integer: %v", ui)
return
}
d.bdRead = false d.bdRead = false
return return
} }
func (d *simpleDecDriver) DecodeFloat(chkOverflow32 bool) (f float64) { func (d *simpleDecDriver) DecodeFloat64() (f float64) {
if !d.bdRead { if !d.bdRead {
d.readNextBd() d.readNextBd()
} }
@ -288,16 +331,12 @@ func (d *simpleDecDriver) DecodeFloat(chkOverflow32 bool) (f float64) {
f = math.Float64frombits(bigen.Uint64(d.r.readx(8))) f = math.Float64frombits(bigen.Uint64(d.r.readx(8)))
} else { } else {
if d.bd >= simpleVdPosInt && d.bd <= simpleVdNegInt+3 { if d.bd >= simpleVdPosInt && d.bd <= simpleVdNegInt+3 {
f = float64(d.DecodeInt(64)) f = float64(d.DecodeInt64())
} else { } else {
d.d.errorf("Float only valid from float32/64: Invalid descriptor: %v", d.bd) d.d.errorf("Float only valid from float32/64: Invalid descriptor: %v", d.bd)
return return
} }
} }
if chkOverflow32 && chkOvf.Float32(f) {
d.d.errorf("msgpack: float32 overflow: %v", f)
return
}
d.bdRead = false d.bdRead = false
return return
} }
@ -323,6 +362,7 @@ func (d *simpleDecDriver) ReadMapStart() (length int) {
d.readNextBd() d.readNextBd()
} }
d.bdRead = false d.bdRead = false
d.c = containerMapStart
return d.decLen() return d.decLen()
} }
@ -331,9 +371,30 @@ func (d *simpleDecDriver) ReadArrayStart() (length int) {
d.readNextBd() d.readNextBd()
} }
d.bdRead = false d.bdRead = false
d.c = containerArrayStart
return d.decLen() return d.decLen()
} }
func (d *simpleDecDriver) ReadArrayElem() {
d.c = containerArrayElem
}
func (d *simpleDecDriver) ReadArrayEnd() {
d.c = containerArrayEnd
}
func (d *simpleDecDriver) ReadMapElemKey() {
d.c = containerMapKey
}
func (d *simpleDecDriver) ReadMapElemValue() {
d.c = containerMapValue
}
func (d *simpleDecDriver) ReadMapEnd() {
d.c = containerMapEnd
}
func (d *simpleDecDriver) decLen() int { func (d *simpleDecDriver) decLen() int {
switch d.bd % 8 { switch d.bd % 8 {
case 0: case 0:
@ -345,14 +406,14 @@ func (d *simpleDecDriver) decLen() int {
case 3: case 3:
ui := uint64(bigen.Uint32(d.r.readx(4))) ui := uint64(bigen.Uint32(d.r.readx(4)))
if chkOvf.Uint(ui, intBitsize) { if chkOvf.Uint(ui, intBitsize) {
d.d.errorf("simple: overflow integer: %v", ui) d.d.errorf("overflow integer: %v", ui)
return 0 return 0
} }
return int(ui) return int(ui)
case 4: case 4:
ui := bigen.Uint64(d.r.readx(8)) ui := bigen.Uint64(d.r.readx(8))
if chkOvf.Uint(ui, intBitsize) { if chkOvf.Uint(ui, intBitsize) {
d.d.errorf("simple: overflow integer: %v", ui) d.d.errorf("overflow integer: %v", ui)
return 0 return 0
} }
return int(ui) return int(ui)
@ -362,11 +423,11 @@ func (d *simpleDecDriver) decLen() int {
} }
func (d *simpleDecDriver) DecodeString() (s string) { func (d *simpleDecDriver) DecodeString() (s string) {
return string(d.DecodeBytes(d.b[:], true)) return string(d.DecodeBytes(d.d.b[:], true))
} }
func (d *simpleDecDriver) DecodeStringAsBytes() (s []byte) { func (d *simpleDecDriver) DecodeStringAsBytes() (s []byte) {
return d.DecodeBytes(d.b[:], true) return d.DecodeBytes(d.d.b[:], true)
} }
func (d *simpleDecDriver) DecodeBytes(bs []byte, zerocopy bool) (bsOut []byte) { func (d *simpleDecDriver) DecodeBytes(bs []byte, zerocopy bool) (bsOut []byte) {
@ -377,18 +438,48 @@ func (d *simpleDecDriver) DecodeBytes(bs []byte, zerocopy bool) (bsOut []byte) {
d.bdRead = false d.bdRead = false
return return
} }
// check if an "array" of uint8's (see ContainerType for how to infer if an array)
if d.bd >= simpleVdArray && d.bd <= simpleVdMap+4 {
if len(bs) == 0 && zerocopy {
bs = d.d.b[:]
}
bsOut, _ = fastpathTV.DecSliceUint8V(bs, true, d.d)
return
}
clen := d.decLen() clen := d.decLen()
d.bdRead = false d.bdRead = false
if zerocopy { if zerocopy {
if d.br { if d.br {
return d.r.readx(clen) return d.r.readx(clen)
} else if len(bs) == 0 { } else if len(bs) == 0 {
bs = d.b[:] bs = d.d.b[:]
} }
} }
return decByteSlice(d.r, clen, d.d.h.MaxInitLen, bs) return decByteSlice(d.r, clen, d.d.h.MaxInitLen, bs)
} }
func (d *simpleDecDriver) DecodeTime() (t time.Time) {
if !d.bdRead {
d.readNextBd()
}
if d.bd == simpleVdNil {
d.bdRead = false
return
}
if d.bd != simpleVdTime {
d.d.errorf("invalid descriptor for time.Time - expect 0x%x, received 0x%x", simpleVdTime, d.bd)
return
}
d.bdRead = false
clen := int(d.r.readn1())
b := d.r.readx(clen)
if err := (&t).UnmarshalBinary(b); err != nil {
d.d.errorv(err)
}
return
}
func (d *simpleDecDriver) DecodeExt(rv interface{}, xtag uint64, ext Ext) (realxtag uint64) { func (d *simpleDecDriver) DecodeExt(rv interface{}, xtag uint64, ext Ext) (realxtag uint64) {
if xtag > 0xff { if xtag > 0xff {
d.d.errorf("decodeExt: tag must be <= 0xff; got: %v", xtag) d.d.errorf("decodeExt: tag must be <= 0xff; got: %v", xtag)
@ -419,10 +510,11 @@ func (d *simpleDecDriver) decodeExtV(verifyTag bool, tag byte) (xtag byte, xbs [
return return
} }
xbs = d.r.readx(l) xbs = d.r.readx(l)
case simpleVdByteArray, simpleVdByteArray + 1, simpleVdByteArray + 2, simpleVdByteArray + 3, simpleVdByteArray + 4: case simpleVdByteArray, simpleVdByteArray + 1,
simpleVdByteArray + 2, simpleVdByteArray + 3, simpleVdByteArray + 4:
xbs = d.DecodeBytes(nil, true) xbs = d.DecodeBytes(nil, true)
default: default:
d.d.errorf("Invalid d.bd for extensions (Expecting extensions or byte array). Got: 0x%x", d.bd) d.d.errorf("Invalid descriptor - expecting extensions/bytearray, got: 0x%x", d.bd)
return return
} }
d.bdRead = false d.bdRead = false
@ -449,24 +541,29 @@ func (d *simpleDecDriver) DecodeNaked() {
case simpleVdPosInt, simpleVdPosInt + 1, simpleVdPosInt + 2, simpleVdPosInt + 3: case simpleVdPosInt, simpleVdPosInt + 1, simpleVdPosInt + 2, simpleVdPosInt + 3:
if d.h.SignedInteger { if d.h.SignedInteger {
n.v = valueTypeInt n.v = valueTypeInt
n.i = d.DecodeInt(64) n.i = d.DecodeInt64()
} else { } else {
n.v = valueTypeUint n.v = valueTypeUint
n.u = d.DecodeUint(64) n.u = d.DecodeUint64()
} }
case simpleVdNegInt, simpleVdNegInt + 1, simpleVdNegInt + 2, simpleVdNegInt + 3: case simpleVdNegInt, simpleVdNegInt + 1, simpleVdNegInt + 2, simpleVdNegInt + 3:
n.v = valueTypeInt n.v = valueTypeInt
n.i = d.DecodeInt(64) n.i = d.DecodeInt64()
case simpleVdFloat32: case simpleVdFloat32:
n.v = valueTypeFloat n.v = valueTypeFloat
n.f = d.DecodeFloat(true) n.f = d.DecodeFloat64()
case simpleVdFloat64: case simpleVdFloat64:
n.v = valueTypeFloat n.v = valueTypeFloat
n.f = d.DecodeFloat(false) n.f = d.DecodeFloat64()
case simpleVdString, simpleVdString + 1, simpleVdString + 2, simpleVdString + 3, simpleVdString + 4: case simpleVdTime:
n.v = valueTypeTime
n.t = d.DecodeTime()
case simpleVdString, simpleVdString + 1,
simpleVdString + 2, simpleVdString + 3, simpleVdString + 4:
n.v = valueTypeString n.v = valueTypeString
n.s = d.DecodeString() n.s = d.DecodeString()
case simpleVdByteArray, simpleVdByteArray + 1, simpleVdByteArray + 2, simpleVdByteArray + 3, simpleVdByteArray + 4: case simpleVdByteArray, simpleVdByteArray + 1,
simpleVdByteArray + 2, simpleVdByteArray + 3, simpleVdByteArray + 4:
n.v = valueTypeBytes n.v = valueTypeBytes
n.l = d.DecodeBytes(nil, false) n.l = d.DecodeBytes(nil, false)
case simpleVdExt, simpleVdExt + 1, simpleVdExt + 2, simpleVdExt + 3, simpleVdExt + 4: case simpleVdExt, simpleVdExt + 1, simpleVdExt + 2, simpleVdExt + 3, simpleVdExt + 4:
@ -474,7 +571,8 @@ func (d *simpleDecDriver) DecodeNaked() {
l := d.decLen() l := d.decLen()
n.u = uint64(d.r.readn1()) n.u = uint64(d.r.readn1())
n.l = d.r.readx(l) n.l = d.r.readx(l)
case simpleVdArray, simpleVdArray + 1, simpleVdArray + 2, simpleVdArray + 3, simpleVdArray + 4: case simpleVdArray, simpleVdArray + 1, simpleVdArray + 2,
simpleVdArray + 3, simpleVdArray + 4:
n.v = valueTypeArray n.v = valueTypeArray
decodeFurther = true decodeFurther = true
case simpleVdMap, simpleVdMap + 1, simpleVdMap + 2, simpleVdMap + 3, simpleVdMap + 4: case simpleVdMap, simpleVdMap + 1, simpleVdMap + 2, simpleVdMap + 3, simpleVdMap + 4:
@ -500,7 +598,7 @@ func (d *simpleDecDriver) DecodeNaked() {
// - Integers (intXXX, uintXXX) are encoded in 1, 2, 4 or 8 bytes (plus a descriptor byte). // - Integers (intXXX, uintXXX) are encoded in 1, 2, 4 or 8 bytes (plus a descriptor byte).
// There are positive (uintXXX and intXXX >= 0) and negative (intXXX < 0) integers. // There are positive (uintXXX and intXXX >= 0) and negative (intXXX < 0) integers.
// - Floats are encoded in 4 or 8 bytes (plus a descriptor byte) // - Floats are encoded in 4 or 8 bytes (plus a descriptor byte)
// - Lenght of containers (strings, bytes, array, map, extensions) // - Length of containers (strings, bytes, array, map, extensions)
// are encoded in 0, 1, 2, 4 or 8 bytes. // are encoded in 0, 1, 2, 4 or 8 bytes.
// Zero-length containers have no length encoded. // Zero-length containers have no length encoded.
// For others, the number of bytes is given by pow(2, bd%3) // For others, the number of bytes is given by pow(2, bd%3)
@ -508,18 +606,29 @@ func (d *simpleDecDriver) DecodeNaked() {
// - arrays are encoded as [bd] [length] [value]... // - arrays are encoded as [bd] [length] [value]...
// - extensions are encoded as [bd] [length] [tag] [byte]... // - extensions are encoded as [bd] [length] [tag] [byte]...
// - strings/bytearrays are encoded as [bd] [length] [byte]... // - strings/bytearrays are encoded as [bd] [length] [byte]...
// - time.Time are encoded as [bd] [length] [byte]...
// //
// The full spec will be published soon. // The full spec will be published soon.
type SimpleHandle struct { type SimpleHandle struct {
BasicHandle BasicHandle
binaryEncodingType binaryEncodingType
noElemSeparators noElemSeparators
// EncZeroValuesAsNil says to encode zero values for numbers, bool, string, etc as nil
EncZeroValuesAsNil bool
_ [1]uint64 // padding
} }
// Name returns the name of the handle: simple
func (h *SimpleHandle) Name() string { return "simple" }
// SetBytesExt sets an extension
func (h *SimpleHandle) SetBytesExt(rt reflect.Type, tag uint64, ext BytesExt) (err error) { func (h *SimpleHandle) SetBytesExt(rt reflect.Type, tag uint64, ext BytesExt) (err error) {
return h.SetExt(rt, tag, &setExtWrapper{b: ext}) return h.SetExt(rt, tag, &extWrapper{ext, interfaceExtFailer{}})
} }
func (h *SimpleHandle) hasElemSeparators() bool { return true } // as it implements Write(Map|Array)XXX
func (h *SimpleHandle) newEncDriver(e *Encoder) encDriver { func (h *SimpleHandle) newEncDriver(e *Encoder) encDriver {
return &simpleEncDriver{e: e, w: e.w, h: h} return &simpleEncDriver{e: e, w: e.w, h: h}
} }
@ -529,10 +638,12 @@ func (h *SimpleHandle) newDecDriver(d *Decoder) decDriver {
} }
func (e *simpleEncDriver) reset() { func (e *simpleEncDriver) reset() {
e.c = 0
e.w = e.e.w e.w = e.e.w
} }
func (d *simpleDecDriver) reset() { func (d *simpleDecDriver) reset() {
d.c = 0
d.r, d.br = d.d.r, d.d.bytes d.r, d.br = d.d.r, d.d.bytes
d.bd, d.bdRead = 0, false d.bd, d.bdRead = 0, false
} }

View File

@ -1,220 +0,0 @@
// Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved.
// Use of this source code is governed by a MIT license found in the LICENSE file.
package codec
import (
"fmt"
"time"
)
var timeDigits = [...]byte{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'}
type timeExt struct{}
func (x timeExt) WriteExt(v interface{}) (bs []byte) {
switch v2 := v.(type) {
case time.Time:
bs = encodeTime(v2)
case *time.Time:
bs = encodeTime(*v2)
default:
panic(fmt.Errorf("unsupported format for time conversion: expecting time.Time; got %T", v2))
}
return
}
func (x timeExt) ReadExt(v interface{}, bs []byte) {
tt, err := decodeTime(bs)
if err != nil {
panic(err)
}
*(v.(*time.Time)) = tt
}
func (x timeExt) ConvertExt(v interface{}) interface{} {
return x.WriteExt(v)
}
func (x timeExt) UpdateExt(v interface{}, src interface{}) {
x.ReadExt(v, src.([]byte))
}
// EncodeTime encodes a time.Time as a []byte, including
// information on the instant in time and UTC offset.
//
// Format Description
//
// A timestamp is composed of 3 components:
//
// - secs: signed integer representing seconds since unix epoch
// - nsces: unsigned integer representing fractional seconds as a
// nanosecond offset within secs, in the range 0 <= nsecs < 1e9
// - tz: signed integer representing timezone offset in minutes east of UTC,
// and a dst (daylight savings time) flag
//
// When encoding a timestamp, the first byte is the descriptor, which
// defines which components are encoded and how many bytes are used to
// encode secs and nsecs components. *If secs/nsecs is 0 or tz is UTC, it
// is not encoded in the byte array explicitly*.
//
// Descriptor 8 bits are of the form `A B C DDD EE`:
// A: Is secs component encoded? 1 = true
// B: Is nsecs component encoded? 1 = true
// C: Is tz component encoded? 1 = true
// DDD: Number of extra bytes for secs (range 0-7).
// If A = 1, secs encoded in DDD+1 bytes.
// If A = 0, secs is not encoded, and is assumed to be 0.
// If A = 1, then we need at least 1 byte to encode secs.
// DDD says the number of extra bytes beyond that 1.
// E.g. if DDD=0, then secs is represented in 1 byte.
// if DDD=2, then secs is represented in 3 bytes.
// EE: Number of extra bytes for nsecs (range 0-3).
// If B = 1, nsecs encoded in EE+1 bytes (similar to secs/DDD above)
//
// Following the descriptor bytes, subsequent bytes are:
//
// secs component encoded in `DDD + 1` bytes (if A == 1)
// nsecs component encoded in `EE + 1` bytes (if B == 1)
// tz component encoded in 2 bytes (if C == 1)
//
// secs and nsecs components are integers encoded in a BigEndian
// 2-complement encoding format.
//
// tz component is encoded as 2 bytes (16 bits). Most significant bit 15 to
// Least significant bit 0 are described below:
//
// Timezone offset has a range of -12:00 to +14:00 (ie -720 to +840 minutes).
// Bit 15 = have\_dst: set to 1 if we set the dst flag.
// Bit 14 = dst\_on: set to 1 if dst is in effect at the time, or 0 if not.
// Bits 13..0 = timezone offset in minutes. It is a signed integer in Big Endian format.
//
func encodeTime(t time.Time) []byte {
//t := rv.Interface().(time.Time)
tsecs, tnsecs := t.Unix(), t.Nanosecond()
var (
bd byte
btmp [8]byte
bs [16]byte
i int = 1
)
l := t.Location()
if l == time.UTC {
l = nil
}
if tsecs != 0 {
bd = bd | 0x80
bigen.PutUint64(btmp[:], uint64(tsecs))
f := pruneSignExt(btmp[:], tsecs >= 0)
bd = bd | (byte(7-f) << 2)
copy(bs[i:], btmp[f:])
i = i + (8 - f)
}
if tnsecs != 0 {
bd = bd | 0x40
bigen.PutUint32(btmp[:4], uint32(tnsecs))
f := pruneSignExt(btmp[:4], true)
bd = bd | byte(3-f)
copy(bs[i:], btmp[f:4])
i = i + (4 - f)
}
if l != nil {
bd = bd | 0x20
// Note that Go Libs do not give access to dst flag.
_, zoneOffset := t.Zone()
//zoneName, zoneOffset := t.Zone()
zoneOffset /= 60
z := uint16(zoneOffset)
bigen.PutUint16(btmp[:2], z)
// clear dst flags
bs[i] = btmp[0] & 0x3f
bs[i+1] = btmp[1]
i = i + 2
}
bs[0] = bd
return bs[0:i]
}
// DecodeTime decodes a []byte into a time.Time.
func decodeTime(bs []byte) (tt time.Time, err error) {
bd := bs[0]
var (
tsec int64
tnsec uint32
tz uint16
i byte = 1
i2 byte
n byte
)
if bd&(1<<7) != 0 {
var btmp [8]byte
n = ((bd >> 2) & 0x7) + 1
i2 = i + n
copy(btmp[8-n:], bs[i:i2])
//if first bit of bs[i] is set, then fill btmp[0..8-n] with 0xff (ie sign extend it)
if bs[i]&(1<<7) != 0 {
copy(btmp[0:8-n], bsAll0xff)
//for j,k := byte(0), 8-n; j < k; j++ { btmp[j] = 0xff }
}
i = i2
tsec = int64(bigen.Uint64(btmp[:]))
}
if bd&(1<<6) != 0 {
var btmp [4]byte
n = (bd & 0x3) + 1
i2 = i + n
copy(btmp[4-n:], bs[i:i2])
i = i2
tnsec = bigen.Uint32(btmp[:])
}
if bd&(1<<5) == 0 {
tt = time.Unix(tsec, int64(tnsec)).UTC()
return
}
// In stdlib time.Parse, when a date is parsed without a zone name, it uses "" as zone name.
// However, we need name here, so it can be shown when time is printed.
// Zone name is in form: UTC-08:00.
// Note that Go Libs do not give access to dst flag, so we ignore dst bits
i2 = i + 2
tz = bigen.Uint16(bs[i:i2])
i = i2
// sign extend sign bit into top 2 MSB (which were dst bits):
if tz&(1<<13) == 0 { // positive
tz = tz & 0x3fff //clear 2 MSBs: dst bits
} else { // negative
tz = tz | 0xc000 //set 2 MSBs: dst bits
//tzname[3] = '-' (TODO: verify. this works here)
}
tzint := int16(tz)
if tzint == 0 {
tt = time.Unix(tsec, int64(tnsec)).UTC()
} else {
// For Go Time, do not use a descriptive timezone.
// It's unnecessary, and makes it harder to do a reflect.DeepEqual.
// The Offset already tells what the offset should be, if not on UTC and unknown zone name.
// var zoneName = timeLocUTCName(tzint)
tt = time.Unix(tsec, int64(tnsec)).In(time.FixedZone("", int(tzint)*60))
}
return
}
// func timeLocUTCName(tzint int16) string {
// if tzint == 0 {
// return "UTC"
// }
// var tzname = []byte("UTC+00:00")
// //tzname := fmt.Sprintf("UTC%s%02d:%02d", tzsign, tz/60, tz%60) //perf issue using Sprintf. inline below.
// //tzhr, tzmin := tz/60, tz%60 //faster if u convert to int first
// var tzhr, tzmin int16
// if tzint < 0 {
// tzname[3] = '-' // (TODO: verify. this works here)
// tzhr, tzmin = -tzint/60, (-tzint)%60
// } else {
// tzhr, tzmin = tzint/60, tzint%60
// }
// tzname[4] = timeDigits[tzhr/10]
// tzname[5] = timeDigits[tzhr%10]
// tzname[7] = timeDigits[tzmin/10]
// tzname[8] = timeDigits[tzmin%10]
// return string(tzname)
// //return time.FixedZone(string(tzname), int(tzint)*60)
// }

View File

@ -1,15 +1,83 @@
/* // +build testing */ /* // +build testing */
// Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved. // Copyright (c) 2012-2018 Ugorji Nwoke. All rights reserved.
// Use of this source code is governed by a MIT license found in the LICENSE file. // Use of this source code is governed by a MIT license found in the LICENSE file.
package codec package codec
// This file contains values used by tests and benchmarks. import "time"
// This file contains values used by tests alone.
// This is where we may try out different things,
// that other engines may not support or may barf upon
// e.g. custom extensions for wrapped types, maps with non-string keys, etc.
// Some unused types just stored here
type Bbool bool
type Sstring string
type Sstructsmall struct {
A int
}
type Sstructbig struct {
A int
B bool
c string
// Sval Sstruct
Ssmallptr *Sstructsmall
Ssmall *Sstructsmall
Sptr *Sstructbig
}
type SstructbigMapBySlice struct {
_struct struct{} `codec:",toarray"`
A int
B bool
c string
// Sval Sstruct
Ssmallptr *Sstructsmall
Ssmall *Sstructsmall
Sptr *Sstructbig
}
type Sinterface interface {
Noop()
}
// small struct for testing that codecgen works for unexported types
type tLowerFirstLetter struct {
I int
u uint64
S string
b []byte
}
// Some used types
type wrapInt64 int64
type wrapUint8 uint8
type wrapBytes []uint8
type AnonInTestStrucIntf struct {
Islice []interface{}
Ms map[string]interface{}
Nintf interface{} //don't set this, so we can test for nil
T time.Time
}
var testWRepeated512 wrapBytes
var testStrucTime = time.Date(2012, 2, 2, 2, 2, 2, 2000, time.UTC).UTC()
func init() {
var testARepeated512 [512]byte
for i := range testARepeated512 {
testARepeated512[i] = 'A'
}
testWRepeated512 = wrapBytes(testARepeated512[:])
}
type TestStrucFlex struct { type TestStrucFlex struct {
_struct struct{} `codec:",omitempty"` //set omitempty for every field _struct struct{} `codec:",omitempty"` //set omitempty for every field
testStrucCommon TestStrucCommon
Mis map[int]string Mis map[int]string
Mbu64 map[bool]struct{} Mbu64 map[bool]struct{}
@ -19,6 +87,20 @@ type TestStrucFlex struct {
Mui2wss map[uint64]wrapStringSlice Mui2wss map[uint64]wrapStringSlice
Msu2wss map[stringUint64T]wrapStringSlice Msu2wss map[stringUint64T]wrapStringSlice
Ci64 wrapInt64
Swrapbytes []wrapBytes
Swrapuint8 []wrapUint8
ArrStrUi64T [4]stringUint64T
Ui64array [4]uint64
Ui64slicearray []*[4]uint64
// make this a ptr, so that it could be set or not.
// for comparison (e.g. with msgp), give it a struct tag (so it is not inlined),
// make this one omitempty (so it is excluded if nil).
*AnonInTestStrucIntf `json:",omitempty"`
//M map[interface{}]interface{} `json:"-",bson:"-"` //M map[interface{}]interface{} `json:"-",bson:"-"`
Mtsptr map[string]*TestStrucFlex Mtsptr map[string]*TestStrucFlex
Mts map[string]TestStrucFlex Mts map[string]TestStrucFlex
@ -52,9 +134,42 @@ func newTestStrucFlex(depth, n int, bench, useInterface, useStringKeyOnly bool)
22: "twenty two", 22: "twenty two",
-44: "minus forty four", -44: "minus forty four",
}, },
Mbu64: map[bool]struct{}{false: struct{}{}, true: struct{}{}}, Mbu64: map[bool]struct{}{false: {}, true: {}},
Ci64: -22,
Swrapbytes: []wrapBytes{ // lengths of 1, 2, 4, 8, 16, 32, 64, 128, 256,
testWRepeated512[:1],
testWRepeated512[:2],
testWRepeated512[:4],
testWRepeated512[:8],
testWRepeated512[:16],
testWRepeated512[:32],
testWRepeated512[:64],
testWRepeated512[:128],
testWRepeated512[:256],
testWRepeated512[:512],
},
Swrapuint8: []wrapUint8{
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
},
Ui64array: [4]uint64{4, 16, 64, 256},
ArrStrUi64T: [4]stringUint64T{{"4", 4}, {"3", 3}, {"2", 2}, {"1", 1}},
} }
populateTestStrucCommon(&ts.testStrucCommon, n, bench, useInterface, useStringKeyOnly)
ts.Ui64slicearray = []*[4]uint64{&ts.Ui64array, &ts.Ui64array}
if useInterface {
ts.AnonInTestStrucIntf = &AnonInTestStrucIntf{
Islice: []interface{}{strRpt(n, "true"), true, strRpt(n, "no"), false, uint64(288), float64(0.4)},
Ms: map[string]interface{}{
strRpt(n, "true"): strRpt(n, "true"),
strRpt(n, "int64(9)"): false,
},
T: testStrucTime,
}
}
populateTestStrucCommon(&ts.TestStrucCommon, n, bench, useInterface, useStringKeyOnly)
if depth > 0 { if depth > 0 {
depth-- depth--
if ts.Mtsptr == nil { if ts.Mtsptr == nil {

View File

@ -1,20 +1,31 @@
/* // +build testing */ /* // +build testing */
// Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved. // Copyright (c) 2012-2018 Ugorji Nwoke. All rights reserved.
// Use of this source code is governed by a MIT license found in the LICENSE file. // Use of this source code is governed by a MIT license found in the LICENSE file.
package codec package codec
// This file contains values used by tests and benchmarks. // This file contains values used by tests and benchmarks.
// JSON/BSON do not like maps with keys that are not strings, // The benchmarks will test performance against other libraries
// so we only use maps with string keys here. // (encoding/json, json-iterator, bson, gob, etc).
// Consequently, we only use values that will parse well in all engines,
// and only leverage features that work across multiple libraries for a truer comparison.
// For example,
// - JSON/BSON do not like maps with keys that are not strings,
// so we only use maps with string keys here.
// - _struct options are not honored by other libraries,
// so we don't use them in this file.
import ( import (
"math" "math"
"strings" "strings"
"time"
) )
// func init() {
// rt := reflect.TypeOf((*TestStruc)(nil)).Elem()
// defTypeInfos.get(rt2id(rt), rt)
// }
type wrapSliceUint64 []uint64 type wrapSliceUint64 []uint64
type wrapSliceString []string type wrapSliceString []string
type wrapUint64 uint64 type wrapUint64 uint64
@ -50,59 +61,36 @@ type AnonInTestStruc struct {
AMSU16E map[string]uint16 AMSU16E map[string]uint16
} }
type AnonInTestStrucIntf struct { // testSimpleFields is a sub-set of TestStrucCommon
Islice []interface{}
Ms map[string]interface{}
Nintf interface{} //don't set this, so we can test for nil
T time.Time
}
type testSimpleFields struct { type testSimpleFields struct {
S string S string
I64 int64 I64 int64
I32 int32
I16 int16
I8 int8 I8 int8
I64n int64
I32n int32
I16n int16
I8n int8
Ui64 uint64 Ui64 uint64
Ui32 uint32
Ui16 uint16
Ui8 uint8 Ui8 uint8
F64 float64 F64 float64
F32 float32 F32 float32
B bool B bool
By uint8 // byte: msgp doesn't like byte
Sslice []string Sslice []string
I64slice []int64
I16slice []int16 I16slice []int16
Ui64slice []uint64 Ui64slice []uint64
Ui8slice []uint8 Ui8slice []uint8
Bslice []bool Bslice []bool
Byslice []byte
Iptrslice []*int64 Iptrslice []*int64
// TODO: test these separately, specifically for reflection and codecgen.
// Unfortunately, ffjson doesn't support these. Its compilation even fails.
Ui64array [4]uint64
Ui64slicearray []*[4]uint64
WrapSliceInt64 wrapSliceUint64 WrapSliceInt64 wrapSliceUint64
WrapSliceString wrapSliceString WrapSliceString wrapSliceString
Msi64 map[string]int64 Msi64 map[string]int64
} }
type testStrucCommon struct { type TestStrucCommon struct {
S string S string
I64 int64 I64 int64
@ -136,11 +124,6 @@ type testStrucCommon struct {
Iptrslice []*int64 Iptrslice []*int64
// TODO: test these separately, specifically for reflection and codecgen.
// Unfortunately, ffjson doesn't support these. Its compilation even fails.
Ui64array [4]uint64
Ui64slicearray []*[4]uint64
WrapSliceInt64 wrapSliceUint64 WrapSliceInt64 wrapSliceUint64
WrapSliceString wrapSliceString WrapSliceString wrapSliceString
@ -148,14 +131,14 @@ type testStrucCommon struct {
Simplef testSimpleFields Simplef testSimpleFields
SstrUi64T []stringUint64T
AnonInTestStruc AnonInTestStruc
NotAnon AnonInTestStruc NotAnon AnonInTestStruc
// make this a ptr, so that it could be set or not. // R Raw // Testing Raw must be explicitly turned on, so use standalone test
// for comparison (e.g. with msgp), give it a struct tag (so it is not inlined), // Rext RawExt // Testing RawExt is tricky, so use standalone test
// make this one omitempty (so it is excluded if nil).
*AnonInTestStrucIntf `codec:",omitempty"`
Nmap map[string]bool //don't set this, so we can test for nil Nmap map[string]bool //don't set this, so we can test for nil
Nslice []byte //don't set this, so we can test for nil Nslice []byte //don't set this, so we can test for nil
@ -163,9 +146,9 @@ type testStrucCommon struct {
} }
type TestStruc struct { type TestStruc struct {
_struct struct{} `codec:",omitempty"` //set omitempty for every field // _struct struct{} `json:",omitempty"` //set omitempty for every field
testStrucCommon TestStrucCommon
Mtsptr map[string]*TestStruc Mtsptr map[string]*TestStruc
Mts map[string]TestStruc Mts map[string]TestStruc
@ -173,52 +156,11 @@ type TestStruc struct {
Nteststruc *TestStruc Nteststruc *TestStruc
} }
// small struct for testing that codecgen works for unexported types func populateTestStrucCommon(ts *TestStrucCommon, n int, bench, useInterface, useStringKeyOnly bool) {
type tLowerFirstLetter struct {
I int
u uint64
S string
b []byte
}
// Some other types
type Sstring string
type Bbool bool
type Sstructsmall struct {
A int
}
type Sstructbig struct {
A int
B bool
c string
// Sval Sstruct
Ssmallptr *Sstructsmall
Ssmall *Sstructsmall
Sptr *Sstructbig
}
type SstructbigMapBySlice struct {
_struct struct{} `codec:",toarray"`
A int
B bool
c string
// Sval Sstruct
Ssmallptr *Sstructsmall
Ssmall *Sstructsmall
Sptr *Sstructbig
}
type Sinterface interface {
Noop()
}
var testStrucTime = time.Date(2012, 2, 2, 2, 2, 2, 2000, time.UTC).UTC()
func populateTestStrucCommon(ts *testStrucCommon, n int, bench, useInterface, useStringKeyOnly bool) {
var i64a, i64b, i64c, i64d int64 = 64, 6464, 646464, 64646464 var i64a, i64b, i64c, i64d int64 = 64, 6464, 646464, 64646464
// if bench, do not use uint64 values > math.MaxInt64, as bson, etc cannot decode them
var a = AnonInTestStruc{ var a = AnonInTestStruc{
// There's more leeway in altering this. // There's more leeway in altering this.
AS: strRpt(n, "A-String"), AS: strRpt(n, "A-String"),
@ -255,7 +197,6 @@ func populateTestStrucCommon(ts *testStrucCommon, n int, bench, useInterface, us
math.MaxUint8, math.MaxUint8 + 4, math.MaxUint8 - 4, math.MaxUint8, math.MaxUint8 + 4, math.MaxUint8 - 4,
math.MaxUint16, math.MaxUint16 + 4, math.MaxUint16 - 4, math.MaxUint16, math.MaxUint16 + 4, math.MaxUint16 - 4,
math.MaxUint32, math.MaxUint32 + 4, math.MaxUint32 - 4, math.MaxUint32, math.MaxUint32 + 4, math.MaxUint32 - 4,
math.MaxUint64, math.MaxUint64 - 4,
}, },
AMSU16: map[string]uint16{strRpt(n, "1"): 1, strRpt(n, "22"): 2, strRpt(n, "333"): 3, strRpt(n, "4444"): 4}, AMSU16: map[string]uint16{strRpt(n, "1"): 1, strRpt(n, "22"): 2, strRpt(n, "333"): 3, strRpt(n, "4444"): 4},
@ -300,7 +241,10 @@ func populateTestStrucCommon(ts *testStrucCommon, n int, bench, useInterface, us
AMSU16E: map[string]uint16{}, AMSU16E: map[string]uint16{},
} }
*ts = testStrucCommon{ if !bench {
a.AUi64slice = append(a.AUi64slice, math.MaxUint64, math.MaxUint64-4)
}
*ts = TestStrucCommon{
S: strRpt(n, `some really really cool names that are nigerian and american like "ugorji melody nwoke" - get it? `), S: strRpt(n, `some really really cool names that are nigerian and american like "ugorji melody nwoke" - get it? `),
// set the numbers close to the limits // set the numbers close to the limits
@ -338,11 +282,12 @@ func populateTestStrucCommon(ts *testStrucCommon, n int, bench, useInterface, us
strRpt(n, "\"three\""): 3, strRpt(n, "\"three\""): 3,
}, },
Ui64array: [4]uint64{4, 16, 64, 256},
WrapSliceInt64: []uint64{4, 16, 64, 256}, WrapSliceInt64: []uint64{4, 16, 64, 256},
WrapSliceString: []string{strRpt(n, "4"), strRpt(n, "16"), strRpt(n, "64"), strRpt(n, "256")}, WrapSliceString: []string{strRpt(n, "4"), strRpt(n, "16"), strRpt(n, "64"), strRpt(n, "256")},
// R: Raw([]byte("goodbye")),
// Rext: RawExt{ 120, []byte("hello"), }, // TODO: don't set this - it's hard to test
// DecodeNaked bombs here, because the stringUint64T is decoded as a map, // DecodeNaked bombs here, because the stringUint64T is decoded as a map,
// and a map cannot be the key type of a map. // and a map cannot be the key type of a map.
// Thus, don't initialize this here. // Thus, don't initialize this here.
@ -352,37 +297,27 @@ func populateTestStrucCommon(ts *testStrucCommon, n int, bench, useInterface, us
// }, // },
// make Simplef same as top-level // make Simplef same as top-level
// TODO: should this have slightly different values???
Simplef: testSimpleFields{ Simplef: testSimpleFields{
S: strRpt(n, `some really really cool names that are nigerian and american like "ugorji melody nwoke" - get it? `), S: strRpt(n, `some really really cool names that are nigerian and american like "ugorji melody nwoke" - get it? `),
// set the numbers close to the limits // set the numbers close to the limits
I8: math.MaxInt8 * 2 / 3, // 8, I8: math.MaxInt8 * 2 / 3, // 8,
I8n: math.MinInt8 * 2 / 3, // 8, I64: math.MaxInt64 * 2 / 3, // 64,
I16: math.MaxInt16 * 2 / 3, // 16,
I16n: math.MinInt16 * 2 / 3, // 16,
I32: math.MaxInt32 * 2 / 3, // 32,
I32n: math.MinInt32 * 2 / 3, // 32,
I64: math.MaxInt64 * 2 / 3, // 64,
I64n: math.MinInt64 * 2 / 3, // 64,
Ui64: math.MaxUint64 * 2 / 3, // 64 Ui64: math.MaxUint64 * 2 / 3, // 64
Ui32: math.MaxUint32 * 2 / 3, // 32
Ui16: math.MaxUint16 * 2 / 3, // 16
Ui8: math.MaxUint8 * 2 / 3, // 8 Ui8: math.MaxUint8 * 2 / 3, // 8
F32: 3.402823e+38, // max representable float32 without losing precision F32: 3.402823e+38, // max representable float32 without losing precision
F64: 3.40281991833838838338e+53, F64: 3.40281991833838838338e+53,
B: true, B: true,
By: 5,
Sslice: []string{strRpt(n, "one"), strRpt(n, "two"), strRpt(n, "three")}, Sslice: []string{strRpt(n, "one"), strRpt(n, "two"), strRpt(n, "three")},
I64slice: []int64{1111, 2222, 3333},
I16slice: []int16{44, 55, 66}, I16slice: []int16{44, 55, 66},
Ui64slice: []uint64{12121212, 34343434, 56565656}, Ui64slice: []uint64{12121212, 34343434, 56565656},
Ui8slice: []uint8{210, 211, 212}, Ui8slice: []uint8{210, 211, 212},
Bslice: []bool{true, false, true, false}, Bslice: []bool{true, false, true, false},
Byslice: []byte{13, 14, 15},
Msi64: map[string]int64{ Msi64: map[string]int64{
strRpt(n, "one"): 1, strRpt(n, "one"): 1,
@ -390,27 +325,18 @@ func populateTestStrucCommon(ts *testStrucCommon, n int, bench, useInterface, us
strRpt(n, "\"three\""): 3, strRpt(n, "\"three\""): 3,
}, },
Ui64array: [4]uint64{4, 16, 64, 256},
WrapSliceInt64: []uint64{4, 16, 64, 256}, WrapSliceInt64: []uint64{4, 16, 64, 256},
WrapSliceString: []string{strRpt(n, "4"), strRpt(n, "16"), strRpt(n, "64"), strRpt(n, "256")}, WrapSliceString: []string{strRpt(n, "4"), strRpt(n, "16"), strRpt(n, "64"), strRpt(n, "256")},
}, },
SstrUi64T: []stringUint64T{{"1", 1}, {"2", 2}, {"3", 3}, {"4", 4}},
AnonInTestStruc: a, AnonInTestStruc: a,
NotAnon: a, NotAnon: a,
} }
ts.Ui64slicearray = []*[4]uint64{&ts.Ui64array, &ts.Ui64array} if bench {
ts.Ui64 = math.MaxInt64 * 2 / 3
if useInterface { ts.Simplef.Ui64 = ts.Ui64
ts.AnonInTestStrucIntf = &AnonInTestStrucIntf{
Islice: []interface{}{strRpt(n, "true"), true, strRpt(n, "no"), false, uint64(288), float64(0.4)},
Ms: map[string]interface{}{
strRpt(n, "true"): strRpt(n, "true"),
strRpt(n, "int64(9)"): false,
},
T: testStrucTime,
}
} }
//For benchmarks, some things will not work. //For benchmarks, some things will not work.
@ -431,7 +357,7 @@ func populateTestStrucCommon(ts *testStrucCommon, n int, bench, useInterface, us
func newTestStruc(depth, n int, bench, useInterface, useStringKeyOnly bool) (ts *TestStruc) { func newTestStruc(depth, n int, bench, useInterface, useStringKeyOnly bool) (ts *TestStruc) {
ts = &TestStruc{} ts = &TestStruc{}
populateTestStrucCommon(&ts.testStrucCommon, n, bench, useInterface, useStringKeyOnly) populateTestStrucCommon(&ts.TestStrucCommon, n, bench, useInterface, useStringKeyOnly)
if depth > 0 { if depth > 0 {
depth-- depth--
if ts.Mtsptr == nil { if ts.Mtsptr == nil {
@ -447,6 +373,28 @@ func newTestStruc(depth, n int, bench, useInterface, useStringKeyOnly bool) (ts
return return
} }
var testStrRptMap = make(map[int]map[string]string)
func strRpt(n int, s string) string { func strRpt(n int, s string) string {
return strings.Repeat(s, n) if false {
// fmt.Printf(">>>> calling strings.Repeat on n: %d, key: %s\n", n, s)
return strings.Repeat(s, n)
}
m1, ok := testStrRptMap[n]
if !ok {
// fmt.Printf(">>>> making new map for n: %v\n", n)
m1 = make(map[string]string)
testStrRptMap[n] = m1
}
v1, ok := m1[s]
if !ok {
// fmt.Printf(">>>> creating new entry for key: %s\n", s)
v1 = strings.Repeat(s, n)
m1[s] = v1
}
return v1
} }
// func wstrRpt(n int, s string) wrapBytes {
// return wrapBytes(bytes.Repeat([]byte(s), n))
// }

View File

@ -1,123 +0,0 @@
// +build x
// +build generated
// Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved.
// Use of this source code is governed by a MIT license found in the LICENSE file.
package codec
import (
"bytes"
"errors"
"fmt"
"testing"
"github.com/mailru/easyjson"
"github.com/pquerna/ffjson/ffjson"
"github.com/tinylib/msgp/msgp"
)
/*
To update all these, use:
go get -u github.com/tinylib/msgp/msgp github.com/tinylib/msgp \
github.com/pquerna/ffjson/ffjson github.com/pquerna/ffjson \
github.com/mailru/easyjson/...
Known Issues with external libraries:
- msgp io.R/W support doesn't work. It throws error
*/
func init() {
testPreInitFns = append(testPreInitFns, benchXGenPreInit)
}
func benchXGenPreInit() {
benchCheckers = append(benchCheckers,
benchChecker{"msgp", fnMsgpEncodeFn, fnMsgpDecodeFn},
benchChecker{"easyjson", fnEasyjsonEncodeFn, fnEasyjsonDecodeFn},
benchChecker{"ffjson", fnFfjsonEncodeFn, fnFfjsonDecodeFn},
)
}
func fnEasyjsonEncodeFn(ts interface{}, bsIn []byte) ([]byte, error) {
if _, ok := ts.(easyjson.Marshaler); !ok {
return nil, errors.New("easyjson: input is not a easyjson.Marshaler")
}
if testUseIoEncDec >= 0 {
buf := new(bytes.Buffer)
_, err := easyjson.MarshalToWriter(ts.(easyjson.Marshaler), buf)
return buf.Bytes(), err
}
return easyjson.Marshal(ts.(easyjson.Marshaler))
// return ts.(json.Marshaler).MarshalJSON()
}
func fnEasyjsonDecodeFn(buf []byte, ts interface{}) error {
if _, ok := ts.(easyjson.Unmarshaler); !ok {
return errors.New("easyjson: input is not a easyjson.Unmarshaler")
}
if testUseIoEncDec >= 0 {
return easyjson.UnmarshalFromReader(bytes.NewReader(buf), ts.(easyjson.Unmarshaler))
}
return easyjson.Unmarshal(buf, ts.(easyjson.Unmarshaler))
// return ts.(json.Unmarshaler).UnmarshalJSON(buf)
}
func fnFfjsonEncodeFn(ts interface{}, bsIn []byte) ([]byte, error) {
return ffjson.Marshal(ts)
// return ts.(json.Marshaler).MarshalJSON()
}
func fnFfjsonDecodeFn(buf []byte, ts interface{}) error {
return ffjson.Unmarshal(buf, ts)
// return ts.(json.Unmarshaler).UnmarshalJSON(buf)
}
func fnMsgpEncodeFn(ts interface{}, bsIn []byte) ([]byte, error) {
if _, ok := ts.(msgp.Encodable); !ok {
return nil, fmt.Errorf("msgp: input of type %T is not a msgp.Encodable", ts)
}
if testUseIoEncDec >= 0 {
buf := fnBenchmarkByteBuf(bsIn)
err := ts.(msgp.Encodable).EncodeMsg(msgp.NewWriter(buf))
return buf.Bytes(), err
}
return ts.(msgp.Marshaler).MarshalMsg(bsIn[:0]) // msgp appends to slice.
}
func fnMsgpDecodeFn(buf []byte, ts interface{}) (err error) {
if _, ok := ts.(msgp.Decodable); !ok {
return fmt.Errorf("msgp: input of type %T is not a msgp.Decodable", ts)
}
if testUseIoEncDec >= 0 {
err = ts.(msgp.Decodable).DecodeMsg(msgp.NewReader(bytes.NewReader(buf)))
return
}
_, err = ts.(msgp.Unmarshaler).UnmarshalMsg(buf)
return
}
func Benchmark__Msgp_______Encode(b *testing.B) {
fnBenchmarkEncode(b, "msgp", benchTs, fnMsgpEncodeFn)
}
func Benchmark__Msgp_______Decode(b *testing.B) {
fnBenchmarkDecode(b, "msgp", benchTs, fnMsgpEncodeFn, fnMsgpDecodeFn, fnBenchNewTs)
}
func Benchmark__Easyjson___Encode(b *testing.B) {
fnBenchmarkEncode(b, "easyjson", benchTs, fnEasyjsonEncodeFn)
}
func Benchmark__Easyjson___Decode(b *testing.B) {
fnBenchmarkDecode(b, "easyjson", benchTs, fnEasyjsonEncodeFn, fnEasyjsonDecodeFn, fnBenchNewTs)
}
func Benchmark__Ffjson_____Encode(b *testing.B) {
fnBenchmarkEncode(b, "ffjson", benchTs, fnFfjsonEncodeFn)
}
func Benchmark__Ffjson_____Decode(b *testing.B) {
fnBenchmarkDecode(b, "ffjson", benchTs, fnFfjsonEncodeFn, fnFfjsonDecodeFn, fnBenchNewTs)
}

View File

@ -1,3 +1,6 @@
// Copyright (c) 2012-2018 Ugorji Nwoke. All rights reserved.
// Use of this source code is governed by a MIT license found in the LICENSE file.
// +build ignore // +build ignore
package codec package codec
@ -24,7 +27,6 @@ It is a replacement, based on the simplicity and performance of codec.
Look at it like JAXB for Go. Look at it like JAXB for Go.
Challenges: Challenges:
- Need to output XML preamble, with all namespaces at the right location in the output. - Need to output XML preamble, with all namespaces at the right location in the output.
- Each "end" block is dynamic, so we need to maintain a context-aware stack - Each "end" block is dynamic, so we need to maintain a context-aware stack
- How to decide when to use an attribute VS an element - How to decide when to use an attribute VS an element
@ -34,24 +36,26 @@ Challenges:
Extend the struct tag. See representative example: Extend the struct tag. See representative example:
type X struct { type X struct {
ID uint8 codec:"xid|http://ugorji.net/x-namespace id,omitempty,toarray,attr,cdata" ID uint8 `codec:"http://ugorji.net/x-namespace xid id,omitempty,toarray,attr,cdata"`
// format: [namespace-uri ][namespace-prefix ]local-name, ...
} }
Based on this, we encode Based on this, we encode
- fields as elements, BUT encode as attributes if struct tag contains ",attr". - fields as elements, BUT
encode as attributes if struct tag contains ",attr" and is a scalar (bool, number or string)
- text as entity-escaped text, BUT encode as CDATA if struct tag contains ",cdata". - text as entity-escaped text, BUT encode as CDATA if struct tag contains ",cdata".
In this mode, we only encode as attribute if ",attr" is found, and only encode as CDATA
if ",cdata" is found in the struct tag.
To handle namespaces: To handle namespaces:
- XMLHandle is denoted as being namespace-aware. - XMLHandle is denoted as being namespace-aware.
Consequently, we WILL use the ns:name pair to encode and decode if defined, else use the plain name. Consequently, we WILL use the ns:name pair to encode and decode if defined, else use the plain name.
- *Encoder and *Decoder know whether the Handle "prefers" namespaces. - *Encoder and *Decoder know whether the Handle "prefers" namespaces.
- add *Encoder.getEncName(*structFieldInfo). - add *Encoder.getEncName(*structFieldInfo).
No one calls *structFieldInfo.indexForEncName directly anymore No one calls *structFieldInfo.indexForEncName directly anymore
- OR better yet: indexForEncName is namespace-aware, and helper.go is all namespace-aware
indexForEncName takes a parameter of the form namespace:local-name OR local-name
- add *Decoder.getStructFieldInfo(encName string) // encName here is either like abc, or h1:nsabc - add *Decoder.getStructFieldInfo(encName string) // encName here is either like abc, or h1:nsabc
No one accesses .encName anymore except in by being a method on *Decoder, or maybe a method on the Handle itself.
No one accesses .encName anymore
- let encode.go and decode.go use these (for consistency) - let encode.go and decode.go use these (for consistency)
- only problem exists for gen.go, where we create a big switch on encName. - only problem exists for gen.go, where we create a big switch on encName.
Now, we also have to add a switch on strings.endsWith(kName, encNsName) Now, we also have to add a switch on strings.endsWith(kName, encNsName)
@ -62,13 +66,14 @@ To handle namespaces:
default { default {
switch { switch {
case !nsAware: panic(...) case !nsAware: panic(...)
case strings.endsWith("nsabc"): x.abc() case strings.endsWith(":abc"): x.abc()
case strings.endsWith(":def"): x.def()
default: panic(...) default: panic(...)
} }
} }
} }
The structure below accomodates this: The structure below accommodates this:
type typeInfo struct { type typeInfo struct {
sfi []*structFieldInfo // sorted by encName sfi []*structFieldInfo // sorted by encName
@ -88,7 +93,10 @@ indexForEncName is now an internal helper function that takes a sorted array
(one of ti.sfins or ti.sfi). It is only used by *Encoder.getStructFieldInfo(...) (one of ti.sfins or ti.sfi). It is only used by *Encoder.getStructFieldInfo(...)
There will be a separate parser from the builder. There will be a separate parser from the builder.
The parser will have a method: next() xmlToken method. The parser will have a method: next() xmlToken method. It has lookahead support,
so you can pop multiple tokens, make a determination, and push them back in the order popped.
This will be needed to determine whether we are "nakedly" decoding a container or not.
The stack will be implemented using a slice and push/pop happens at the [0] element.
xmlToken has fields: xmlToken has fields:
- type uint8: 0 | ElementStart | ElementEnd | AttrKey | AttrVal | Text - type uint8: 0 | ElementStart | ElementEnd | AttrKey | AttrVal | Text
@ -132,7 +140,7 @@ At decode time, a structure containing the following is kept
- all internal entities (<>&"' and others written in the document) - all internal entities (<>&"' and others written in the document)
When decode starts, it parses XML namespace declarations and creates a map in the When decode starts, it parses XML namespace declarations and creates a map in the
xmlDecDriver. While parsing, that map continously gets updated. xmlDecDriver. While parsing, that map continuously gets updated.
The only problem happens when a namespace declaration happens on the node that it defines. The only problem happens when a namespace declaration happens on the node that it defines.
e.g. <hn:name xmlns:hn="http://www.ugorji.net" > e.g. <hn:name xmlns:hn="http://www.ugorji.net" >
To handle this, each Element must be fully parsed at a time, To handle this, each Element must be fully parsed at a time,
@ -144,7 +152,7 @@ xmlns is a special attribute name.
*We may decide later to allow user to use it e.g. you want to parse the xmlns mappings into a field.* *We may decide later to allow user to use it e.g. you want to parse the xmlns mappings into a field.*
Number, bool, null, mapKey, etc can all be decoded from any xmlToken. Number, bool, null, mapKey, etc can all be decoded from any xmlToken.
This accomodates map[int]string for example. This accommodates map[int]string for example.
It should be possible to create a schema from the types, It should be possible to create a schema from the types,
or vice versa (generate types from schema with appropriate tags). or vice versa (generate types from schema with appropriate tags).
@ -178,8 +186,8 @@ An XML document is a name, a map of attributes and a list of children.
Consequently, we cannot "DecodeNaked" into a map[string]interface{} (for example). Consequently, we cannot "DecodeNaked" into a map[string]interface{} (for example).
We have to "DecodeNaked" into something that resembles XML data. We have to "DecodeNaked" into something that resembles XML data.
To support DecodeNaked (decode into nil interface{}) we have to define some "supporting" types: To support DecodeNaked (decode into nil interface{}), we have to define some "supporting" types:
type Name struct { // Prefered. Less allocations due to conversions. type Name struct { // Preferred. Less allocations due to conversions.
Local string Local string
Space string Space string
} }
@ -190,6 +198,8 @@ To support DecodeNaked (decode into nil interface{}) we have to define some "sup
} }
Only two "supporting" types are exposed for XML: Name and Element. Only two "supporting" types are exposed for XML: Name and Element.
// ------------------
We considered 'type Name string' where Name is like "Space Local" (space-separated). We considered 'type Name string' where Name is like "Space Local" (space-separated).
We decided against it, because each creation of a name would lead to We decided against it, because each creation of a name would lead to
double allocation (first convert []byte to string, then concatenate them into a string). double allocation (first convert []byte to string, then concatenate them into a string).
@ -215,16 +225,16 @@ intelligent accessor methods to extract information and for performance.
} }
func (x *Element) child(i) interface{} // returns string or *Element func (x *Element) child(i) interface{} // returns string or *Element
Per XML spec and our default handling, white space is insignificant between elements, // ------------------
specifically between parent-child or siblings. White space occuring alone between start
and end element IS significant. However, if xml:space='preserve', then we 'preserve' Per XML spec and our default handling, white space is always treated as
all whitespace. This is more critical when doing a DecodeNaked, but MAY not be as critical insignificant between elements, except in a text node. The xml:space='preserve'
when decoding into a typed value. attribute is ignored.
**Note: there is no xml: namespace. The xml: attributes were defined before namespaces.** **Note: there is no xml: namespace. The xml: attributes were defined before namespaces.**
**So treat them as just "directives" that should be interpreted to mean something**. **So treat them as just "directives" that should be interpreted to mean something**.
On encoding, we don't add any prettifying markup (indenting, etc). On encoding, we support indenting aka prettifying markup in the same way we support it for json.
A document or element can only be encoded/decoded from/to a struct. In this mode: A document or element can only be encoded/decoded from/to a struct. In this mode:
- struct name maps to element name (or tag-info from _struct field) - struct name maps to element name (or tag-info from _struct field)
@ -258,15 +268,14 @@ the struct tag signifying it should be attr, then all its fields are encoded as
e.g. e.g.
type X struct { type X struct {
M map[string]int `codec:"m,attr"` // encode as attributes M map[string]int `codec:"m,attr"` // encode keys as attributes named
} }
Question: Question:
- if encoding a map, what if map keys have spaces in them??? - if encoding a map, what if map keys have spaces in them???
Then they cannot be attributes or child elements. Error. Then they cannot be attributes or child elements. Error.
Misc: Options to consider adding later:
- For attribute values, normalize by trimming beginning and ending white space, - For attribute values, normalize by trimming beginning and ending white space,
and converting every white space sequence to a single space. and converting every white space sequence to a single space.
- ATTLIST restrictions are enforced. - ATTLIST restrictions are enforced.
@ -284,6 +293,8 @@ Misc:
CheckName bool CheckName bool
} }
Misc:
ROADMAP (1 weeks): ROADMAP (1 weeks):
- build encoder (1 day) - build encoder (1 day)
- build decoder (based off xmlParser) (1 day) - build decoder (based off xmlParser) (1 day)
@ -292,7 +303,78 @@ ROADMAP (1 weeks):
- integrate and TEST (1 days) - integrate and TEST (1 days)
- write article and post it (1 day) - write article and post it (1 day)
// ---------- MORE NOTES FROM 2017-11-30 ------------
when parsing
- parse the attributes first
- then parse the nodes
basically:
- if encoding a field: we use the field name for the wrapper
- if encoding a non-field, then just use the element type name
map[string]string ==> <map><key>abc</key><value>val</value></map>... or
<map key="abc">val</map>... OR
<key1>val1</key1><key2>val2</key2>... <- PREFERED
[]string ==> <string>v1</string><string>v2</string>...
string v1 ==> <string>v1</string>
bool true ==> <bool>true</bool>
float 1.0 ==> <float>1.0</float>
...
F1 map[string]string ==> <F1><key>abc</key><value>val</value></F1>... OR
<F1 key="abc">val</F1>... OR
<F1><abc>val</abc>...</F1> <- PREFERED
F2 []string ==> <F2>v1</F2><F2>v2</F2>...
F3 bool ==> <F3>true</F3>
...
- a scalar is encoded as:
(value) of type T ==> <T><value/></T>
(value) of field F ==> <F><value/></F>
- A kv-pair is encoded as:
(key,value) ==> <map><key><value/></key></map> OR <map key="value">
(key,value) of field F ==> <F><key><value/></key></F> OR <F key="value">
- A map or struct is just a list of kv-pairs
- A list is encoded as sequences of same node e.g.
<F1 key1="value11">
<F1 key2="value12">
<F2>value21</F2>
<F2>value22</F2>
- we may have to singularize the field name, when entering into xml,
and pluralize them when encoding.
- bi-directional encode->decode->encode is not a MUST.
even encoding/xml cannot decode correctly what was encoded:
see https://play.golang.org/p/224V_nyhMS
func main() {
fmt.Println("Hello, playground")
v := []interface{}{"hello", 1, true, nil, time.Now()}
s, err := xml.Marshal(v)
fmt.Printf("err: %v, \ns: %s\n", err, s)
var v2 []interface{}
err = xml.Unmarshal(s, &v2)
fmt.Printf("err: %v, \nv2: %v\n", err, v2)
type T struct {
V []interface{}
}
v3 := T{V: v}
s, err = xml.Marshal(v3)
fmt.Printf("err: %v, \ns: %s\n", err, s)
var v4 T
err = xml.Unmarshal(s, &v4)
fmt.Printf("err: %v, \nv4: %v\n", err, v4)
}
Output:
err: <nil>,
s: <string>hello</string><int>1</int><bool>true</bool><Time>2009-11-10T23:00:00Z</Time>
err: <nil>,
v2: [<nil>]
err: <nil>,
s: <T><V>hello</V><V>1</V><V>true</V><V>2009-11-10T23:00:00Z</V></T>
err: <nil>,
v4: {[<nil> <nil> <nil> <nil>]}
-
*/ */
// ----------- PARSER ------------------- // ----------- PARSER -------------------
@ -419,7 +501,7 @@ func (h *XMLHandle) newDecDriver(d *Decoder) decDriver {
} }
func (h *XMLHandle) SetInterfaceExt(rt reflect.Type, tag uint64, ext InterfaceExt) (err error) { func (h *XMLHandle) SetInterfaceExt(rt reflect.Type, tag uint64, ext InterfaceExt) (err error) {
return h.SetExt(rt, tag, &setExtWrapper{i: ext}) return h.SetExt(rt, tag, &extWrapper{bytesExtFailer{}, ext})
} }
var _ decDriver = (*xmlDecDriver)(nil) var _ decDriver = (*xmlDecDriver)(nil)

View File

@ -1,3 +1,6 @@
// Copyright (c) 2012-2018 Ugorji Nwoke. All rights reserved.
// Use of this source code is governed by a MIT license found in the LICENSE file.
// +build alltests // +build alltests
// +build go1.7 // +build go1.7
@ -41,11 +44,15 @@ func testGroupResetFlags() {
testMaxInitLen = 0 testMaxInitLen = 0
testUseIoWrapper = false testUseIoWrapper = false
testNumRepeatString = 8 testNumRepeatString = 8
testEncodeOptions.RecursiveEmptyCheck = false
testDecodeOptions.MapValueReset = false
testUseIoEncDec = -1
testDepth = 0
} }
func testSuite(t *testing.T, f func(t *testing.T)) { func testSuite(t *testing.T, f func(t *testing.T)) {
// find . -name "*_test.go" | xargs grep -e 'flag.' | cut -d '&' -f 2 | cut -d ',' -f 1 | grep -e '^test' // find . -name "*_test.go" | xargs grep -e 'flag.' | cut -d '&' -f 2 | cut -d ',' -f 1 | grep -e '^test'
// Disregard the following: testVerbose, testInitDebug, testSkipIntf, testJsonIndent (Need a test for it) // Disregard the following: testInitDebug, testSkipIntf, testJsonIndent (Need a test for it)
testReinit() // so flag.Parse() is called first, and never called again testReinit() // so flag.Parse() is called first, and never called again
@ -65,20 +72,22 @@ func testSuite(t *testing.T, f func(t *testing.T)) {
testCheckCircRef = true testCheckCircRef = true
testUseReset = true testUseReset = true
testDecodeOptions.MapValueReset = true testDecodeOptions.MapValueReset = true
testEncodeOptions.RecursiveEmptyCheck = true
testReinit() testReinit()
t.Run("optionsTrue", f) t.Run("optionsTrue", f)
testEncodeOptions.AsSymbols = AsSymbolAll testDepth = 6
testReinit()
t.Run("optionsTrue-deepstruct", f)
testDepth = 0
// testEncodeOptions.AsSymbols = AsSymbolAll
testUseIoWrapper = true testUseIoWrapper = true
testReinit() testReinit()
t.Run("optionsTrue-ioWrapper", f) t.Run("optionsTrue-ioWrapper", f)
testUseIoEncDec = -1 testUseIoEncDec = -1
testDepth = 6
testReinit()
t.Run("optionsTrue-deepstruct", f)
// make buffer small enough so that we have to re-fill multiple times. // make buffer small enough so that we have to re-fill multiple times.
testSkipRPCTests = true testSkipRPCTests = true
testUseIoEncDec = 128 testUseIoEncDec = 128
@ -110,8 +119,7 @@ func testSuite(t *testing.T, f func(t *testing.T)) {
} }
/* /*
z='codec_test.go' find . -name "codec_test.go" | xargs grep -e '^func Test' | \
find . -name "$z" | xargs grep -e '^func Test' | \
cut -d '(' -f 1 | cut -d ' ' -f 2 | \ cut -d '(' -f 1 | cut -d ' ' -f 2 | \
while read f; do echo "t.Run(\"$f\", $f)"; done while read f; do echo "t.Run(\"$f\", $f)"; done
*/ */
@ -211,6 +219,21 @@ func testCodecGroup(t *testing.T) {
t.Run("TestMsgpackUintToInt", TestMsgpackUintToInt) t.Run("TestMsgpackUintToInt", TestMsgpackUintToInt)
t.Run("TestBincUintToInt", TestBincUintToInt) t.Run("TestBincUintToInt", TestBincUintToInt)
t.Run("TestSimpleUintToInt", TestSimpleUintToInt) t.Run("TestSimpleUintToInt", TestSimpleUintToInt)
t.Run("TestJsonDifferentMapOrSliceType", TestJsonDifferentMapOrSliceType)
t.Run("TestCborDifferentMapOrSliceType", TestCborDifferentMapOrSliceType)
t.Run("TestMsgpackDifferentMapOrSliceType", TestMsgpackDifferentMapOrSliceType)
t.Run("TestBincDifferentMapOrSliceType", TestBincDifferentMapOrSliceType)
t.Run("TestSimpleDifferentMapOrSliceType", TestSimpleDifferentMapOrSliceType)
t.Run("TestJsonScalars", TestJsonScalars)
t.Run("TestCborScalars", TestCborScalars)
t.Run("TestMsgpackScalars", TestMsgpackScalars)
t.Run("TestBincScalars", TestBincScalars)
t.Run("TestSimpleScalars", TestSimpleScalars)
t.Run("TestJsonIntfMapping", TestJsonIntfMapping)
t.Run("TestCborIntfMapping", TestCborIntfMapping)
t.Run("TestMsgpackIntfMapping", TestMsgpackIntfMapping)
t.Run("TestBincIntfMapping", TestBincIntfMapping)
t.Run("TestSimpleIntfMapping", TestSimpleIntfMapping)
t.Run("TestJsonInvalidUnicode", TestJsonInvalidUnicode) t.Run("TestJsonInvalidUnicode", TestJsonInvalidUnicode)
t.Run("TestCborHalfFloat", TestCborHalfFloat) t.Run("TestCborHalfFloat", TestCborHalfFloat)
@ -240,6 +263,9 @@ func testJsonGroup(t *testing.T) {
t.Run("TestJsonInvalidUnicode", TestJsonInvalidUnicode) t.Run("TestJsonInvalidUnicode", TestJsonInvalidUnicode)
t.Run("TestJsonTime", TestJsonTime) t.Run("TestJsonTime", TestJsonTime)
t.Run("TestJsonUintToInt", TestJsonUintToInt) t.Run("TestJsonUintToInt", TestJsonUintToInt)
t.Run("TestJsonDifferentMapOrSliceType", TestJsonDifferentMapOrSliceType)
t.Run("TestJsonScalars", TestJsonScalars)
t.Run("TestJsonIntfMapping", TestJsonIntfMapping)
} }
func testBincGroup(t *testing.T) { func testBincGroup(t *testing.T) {
@ -261,6 +287,8 @@ func testBincGroup(t *testing.T) {
t.Run("TestBincMammothMapsAndSlices", TestBincMammothMapsAndSlices) t.Run("TestBincMammothMapsAndSlices", TestBincMammothMapsAndSlices)
t.Run("TestBincTime", TestBincTime) t.Run("TestBincTime", TestBincTime)
t.Run("TestBincUintToInt", TestBincUintToInt) t.Run("TestBincUintToInt", TestBincUintToInt)
t.Run("TestBincDifferentMapOrSliceType", TestBincDifferentMapOrSliceType)
t.Run("TestBincScalars", TestBincScalars)
} }
func testCborGroup(t *testing.T) { func testCborGroup(t *testing.T) {
@ -283,6 +311,8 @@ func testCborGroup(t *testing.T) {
t.Run("TestCborMammothMapsAndSlices", TestCborMammothMapsAndSlices) t.Run("TestCborMammothMapsAndSlices", TestCborMammothMapsAndSlices)
t.Run("TestCborTime", TestCborTime) t.Run("TestCborTime", TestCborTime)
t.Run("TestCborUintToInt", TestCborUintToInt) t.Run("TestCborUintToInt", TestCborUintToInt)
t.Run("TestCborDifferentMapOrSliceType", TestCborDifferentMapOrSliceType)
t.Run("TestCborScalars", TestCborScalars)
t.Run("TestCborHalfFloat", TestCborHalfFloat) t.Run("TestCborHalfFloat", TestCborHalfFloat)
} }
@ -305,6 +335,42 @@ func testMsgpackGroup(t *testing.T) {
t.Run("TestMsgpackMammothMapsAndSlices", TestMsgpackMammothMapsAndSlices) t.Run("TestMsgpackMammothMapsAndSlices", TestMsgpackMammothMapsAndSlices)
t.Run("TestMsgpackTime", TestMsgpackTime) t.Run("TestMsgpackTime", TestMsgpackTime)
t.Run("TestMsgpackUintToInt", TestMsgpackUintToInt) t.Run("TestMsgpackUintToInt", TestMsgpackUintToInt)
t.Run("TestMsgpackDifferentMapOrSliceType", TestMsgpackDifferentMapOrSliceType)
t.Run("TestMsgpackScalars", TestMsgpackScalars)
}
func testSimpleGroup(t *testing.T) {
t.Run("TestSimpleCodecsTable", TestSimpleCodecsTable)
t.Run("TestSimpleCodecsMisc", TestSimpleCodecsMisc)
t.Run("TestSimpleCodecsEmbeddedPointer", TestSimpleCodecsEmbeddedPointer)
t.Run("TestSimpleStdEncIntf", TestSimpleStdEncIntf)
t.Run("TestSimpleMammoth", TestSimpleMammoth)
t.Run("TestSimpleRaw", TestSimpleRaw)
t.Run("TestSimpleRpcGo", TestSimpleRpcGo)
t.Run("TestSimpleSwallowAndZero", TestSimpleSwallowAndZero)
t.Run("TestSimpleRawExt", TestSimpleRawExt)
t.Run("TestSimpleMapStructKey", TestSimpleMapStructKey)
t.Run("TestSimpleDecodeNilMapValue", TestSimpleDecodeNilMapValue)
t.Run("TestSimpleEmbeddedFieldPrecedence", TestSimpleEmbeddedFieldPrecedence)
t.Run("TestSimpleLargeContainerLen", TestSimpleLargeContainerLen)
t.Run("TestSimpleMammothMapsAndSlices", TestSimpleMammothMapsAndSlices)
t.Run("TestSimpleTime", TestSimpleTime)
t.Run("TestSimpleUintToInt", TestSimpleUintToInt)
t.Run("TestSimpleDifferentMapOrSliceType", TestSimpleDifferentMapOrSliceType)
t.Run("TestSimpleScalars", TestSimpleScalars)
}
func testSimpleMammothGroup(t *testing.T) {
t.Run("TestSimpleMammothMapsAndSlices", TestSimpleMammothMapsAndSlices)
}
func testRpcGroup(t *testing.T) {
t.Run("TestBincRpcGo", TestBincRpcGo)
t.Run("TestSimpleRpcGo", TestSimpleRpcGo)
t.Run("TestMsgpackRpcGo", TestMsgpackRpcGo)
t.Run("TestCborRpcGo", TestCborRpcGo)
t.Run("TestJsonRpcGo", TestJsonRpcGo)
t.Run("TestMsgpackRpcSpec", TestMsgpackRpcSpec)
} }
func TestCodecSuite(t *testing.T) { func TestCodecSuite(t *testing.T) {
@ -342,17 +408,23 @@ func TestCodecSuite(t *testing.T) {
testCborH.IndefiniteLength = oldIndefLen testCborH.IndefiniteLength = oldIndefLen
oldSymbols := testBincH.getBasicHandle().AsSymbols oldTimeRFC3339 := testCborH.TimeRFC3339
testCborH.TimeRFC3339 = !testCborH.TimeRFC3339
testReinit()
t.Run("cbor-rfc3339", testCborGroup)
testCborH.TimeRFC3339 = oldTimeRFC3339
testBincH.getBasicHandle().AsSymbols = AsSymbolNone oldSymbols := testBincH.AsSymbols
testBincH.AsSymbols = 2 // AsSymbolNone
testReinit() testReinit()
t.Run("binc-no-symbols", testBincGroup) t.Run("binc-no-symbols", testBincGroup)
testBincH.getBasicHandle().AsSymbols = AsSymbolAll testBincH.AsSymbols = 1 // AsSymbolAll
testReinit() testReinit()
t.Run("binc-all-symbols", testBincGroup) t.Run("binc-all-symbols", testBincGroup)
testBincH.getBasicHandle().AsSymbols = oldSymbols testBincH.AsSymbols = oldSymbols
oldWriteExt := testMsgpackH.WriteExt oldWriteExt := testMsgpackH.WriteExt
oldNoFixedNum := testMsgpackH.NoFixedNum oldNoFixedNum := testMsgpackH.NoFixedNum
@ -369,6 +441,26 @@ func TestCodecSuite(t *testing.T) {
testMsgpackH.NoFixedNum = oldNoFixedNum testMsgpackH.NoFixedNum = oldNoFixedNum
oldEncZeroValuesAsNil := testSimpleH.EncZeroValuesAsNil
testSimpleH.EncZeroValuesAsNil = !testSimpleH.EncZeroValuesAsNil
testUseMust = true
testReinit()
t.Run("simple-enczeroasnil", testSimpleMammothGroup) // testSimpleGroup
testSimpleH.EncZeroValuesAsNil = oldEncZeroValuesAsNil
oldRpcBufsize := testRpcBufsize
testRpcBufsize = 0
t.Run("rpc-buf-0", testRpcGroup)
testRpcBufsize = 0
t.Run("rpc-buf-00", testRpcGroup)
testRpcBufsize = 0
t.Run("rpc-buf-000", testRpcGroup)
testRpcBufsize = 16
t.Run("rpc-buf-16", testRpcGroup)
testRpcBufsize = 2048
t.Run("rpc-buf-2048", testRpcGroup)
testRpcBufsize = oldRpcBufsize
testGroupResetFlags() testGroupResetFlags()
} }

View File

@ -1,49 +0,0 @@
// +build alltests
// +build x
// +build go1.7
// +build generated
package codec
// see notes in z_all_bench_test.go
import "testing"
func benchmarkCodecXGenGroup(t *testing.B) {
logT(nil, "\n-------------------------------\n")
t.Run("Benchmark__Msgpack____Encode", Benchmark__Msgpack____Encode)
t.Run("Benchmark__Binc_______Encode", Benchmark__Binc_______Encode)
t.Run("Benchmark__Simple_____Encode", Benchmark__Simple_____Encode)
t.Run("Benchmark__Cbor_______Encode", Benchmark__Cbor_______Encode)
t.Run("Benchmark__Json_______Encode", Benchmark__Json_______Encode)
t.Run("Benchmark__Std_Json___Encode", Benchmark__Std_Json___Encode)
t.Run("Benchmark__Gob________Encode", Benchmark__Gob________Encode)
t.Run("Benchmark__JsonIter___Encode", Benchmark__JsonIter___Encode)
t.Run("Benchmark__Bson_______Encode", Benchmark__Bson_______Encode)
t.Run("Benchmark__VMsgpack___Encode", Benchmark__VMsgpack___Encode)
t.Run("Benchmark__Msgp_______Encode", Benchmark__Msgp_______Encode)
t.Run("Benchmark__Easyjson___Encode", Benchmark__Easyjson___Encode)
t.Run("Benchmark__Ffjson_____Encode", Benchmark__Ffjson_____Encode)
t.Run("Benchmark__Gcbor______Encode", Benchmark__Gcbor______Encode)
t.Run("Benchmark__Xdr________Encode", Benchmark__Xdr________Encode)
t.Run("Benchmark__Sereal_____Encode", Benchmark__Sereal_____Encode)
t.Run("Benchmark__Msgpack____Decode", Benchmark__Msgpack____Decode)
t.Run("Benchmark__Binc_______Decode", Benchmark__Binc_______Decode)
t.Run("Benchmark__Simple_____Decode", Benchmark__Simple_____Decode)
t.Run("Benchmark__Cbor_______Decode", Benchmark__Cbor_______Decode)
t.Run("Benchmark__Json_______Decode", Benchmark__Json_______Decode)
t.Run("Benchmark__Std_Json___Decode", Benchmark__Std_Json___Decode)
t.Run("Benchmark__Gob________Decode", Benchmark__Gob________Decode)
t.Run("Benchmark__JsonIter___Decode", Benchmark__JsonIter___Decode)
t.Run("Benchmark__Bson_______Decode", Benchmark__Bson_______Decode)
t.Run("Benchmark__VMsgpack___Decode", Benchmark__VMsgpack___Decode)
t.Run("Benchmark__Msgp_______Decode", Benchmark__Msgp_______Decode)
t.Run("Benchmark__Easyjson___Decode", Benchmark__Easyjson___Decode)
t.Run("Benchmark__Ffjson_____Decode", Benchmark__Ffjson_____Decode)
t.Run("Benchmark__Gcbor______Decode", Benchmark__Gcbor______Decode)
t.Run("Benchmark__Xdr________Decode", Benchmark__Xdr________Decode)
t.Run("Benchmark__Sereal_____Decode", Benchmark__Sereal_____Decode)
}
func BenchmarkCodecXGenSuite(t *testing.B) { benchmarkSuite(t, benchmarkCodecXGenGroup) }

View File

@ -946,7 +946,7 @@ func TestNonce_add(t *testing.T) {
c.addNonce(http.Header{"Replay-Nonce": {}}) c.addNonce(http.Header{"Replay-Nonce": {}})
c.addNonce(http.Header{"Replay-Nonce": {"nonce"}}) c.addNonce(http.Header{"Replay-Nonce": {"nonce"}})
nonces := map[string]struct{}{"nonce": struct{}{}} nonces := map[string]struct{}{"nonce": {}}
if !reflect.DeepEqual(c.nonces, nonces) { if !reflect.DeepEqual(c.nonces, nonces) {
t.Errorf("c.nonces = %q; want %q", c.nonces, nonces) t.Errorf("c.nonces = %q; want %q", c.nonces, nonces)
} }

View File

@ -24,7 +24,9 @@ import (
"fmt" "fmt"
"io" "io"
mathrand "math/rand" mathrand "math/rand"
"net"
"net/http" "net/http"
"path"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
@ -80,8 +82,9 @@ func defaultHostPolicy(context.Context, string) error {
} }
// Manager is a stateful certificate manager built on top of acme.Client. // Manager is a stateful certificate manager built on top of acme.Client.
// It obtains and refreshes certificates automatically, // It obtains and refreshes certificates automatically using "tls-sni-01",
// as well as providing them to a TLS server via tls.Config. // "tls-sni-02" and "http-01" challenge types, as well as providing them
// to a TLS server via tls.Config.
// //
// You must specify a cache implementation, such as DirCache, // You must specify a cache implementation, such as DirCache,
// to reuse obtained certificates across program restarts. // to reuse obtained certificates across program restarts.
@ -150,15 +153,26 @@ type Manager struct {
stateMu sync.Mutex stateMu sync.Mutex
state map[string]*certState // keyed by domain name state map[string]*certState // keyed by domain name
// tokenCert is keyed by token domain name, which matches server name
// of ClientHello. Keys always have ".acme.invalid" suffix.
tokenCertMu sync.RWMutex
tokenCert map[string]*tls.Certificate
// renewal tracks the set of domains currently running renewal timers. // renewal tracks the set of domains currently running renewal timers.
// It is keyed by domain name. // It is keyed by domain name.
renewalMu sync.Mutex renewalMu sync.Mutex
renewal map[string]*domainRenewal renewal map[string]*domainRenewal
// tokensMu guards the rest of the fields: tryHTTP01, certTokens and httpTokens.
tokensMu sync.RWMutex
// tryHTTP01 indicates whether the Manager should try "http-01" challenge type
// during the authorization flow.
tryHTTP01 bool
// httpTokens contains response body values for http-01 challenges
// and is keyed by the URL path at which a challenge response is expected
// to be provisioned.
// The entries are stored for the duration of the authorization flow.
httpTokens map[string][]byte
// certTokens contains temporary certificates for tls-sni challenges
// and is keyed by token domain name, which matches server name of ClientHello.
// Keys always have ".acme.invalid" suffix.
// The entries are stored for the duration of the authorization flow.
certTokens map[string]*tls.Certificate
} }
// GetCertificate implements the tls.Config.GetCertificate hook. // GetCertificate implements the tls.Config.GetCertificate hook.
@ -185,14 +199,16 @@ func (m *Manager) GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate,
return nil, errors.New("acme/autocert: server name contains invalid character") return nil, errors.New("acme/autocert: server name contains invalid character")
} }
// In the worst-case scenario, the timeout needs to account for caching, host policy,
// domain ownership verification and certificate issuance.
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer cancel() defer cancel()
// check whether this is a token cert requested for TLS-SNI challenge // check whether this is a token cert requested for TLS-SNI challenge
if strings.HasSuffix(name, ".acme.invalid") { if strings.HasSuffix(name, ".acme.invalid") {
m.tokenCertMu.RLock() m.tokensMu.RLock()
defer m.tokenCertMu.RUnlock() defer m.tokensMu.RUnlock()
if cert := m.tokenCert[name]; cert != nil { if cert := m.certTokens[name]; cert != nil {
return cert, nil return cert, nil
} }
if cert, err := m.cacheGet(ctx, name); err == nil { if cert, err := m.cacheGet(ctx, name); err == nil {
@ -224,6 +240,68 @@ func (m *Manager) GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate,
return cert, nil return cert, nil
} }
// HTTPHandler configures the Manager to provision ACME "http-01" challenge responses.
// It returns an http.Handler that responds to the challenges and must be
// running on port 80. If it receives a request that is not an ACME challenge,
// it delegates the request to the optional fallback handler.
//
// If fallback is nil, the returned handler redirects all GET and HEAD requests
// to the default TLS port 443 with 302 Found status code, preserving the original
// request path and query. It responds with 400 Bad Request to all other HTTP methods.
// The fallback is not protected by the optional HostPolicy.
//
// Because the fallback handler is run with unencrypted port 80 requests,
// the fallback should not serve TLS-only requests.
//
// If HTTPHandler is never called, the Manager will only use TLS SNI
// challenges for domain verification.
func (m *Manager) HTTPHandler(fallback http.Handler) http.Handler {
m.tokensMu.Lock()
defer m.tokensMu.Unlock()
m.tryHTTP01 = true
if fallback == nil {
fallback = http.HandlerFunc(handleHTTPRedirect)
}
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if !strings.HasPrefix(r.URL.Path, "/.well-known/acme-challenge/") {
fallback.ServeHTTP(w, r)
return
}
// A reasonable context timeout for cache and host policy only,
// because we don't wait for a new certificate issuance here.
ctx, cancel := context.WithTimeout(r.Context(), time.Minute)
defer cancel()
if err := m.hostPolicy()(ctx, r.Host); err != nil {
http.Error(w, err.Error(), http.StatusForbidden)
return
}
data, err := m.httpToken(ctx, r.URL.Path)
if err != nil {
http.Error(w, err.Error(), http.StatusNotFound)
return
}
w.Write(data)
})
}
func handleHTTPRedirect(w http.ResponseWriter, r *http.Request) {
if r.Method != "GET" && r.Method != "HEAD" {
http.Error(w, "Use HTTPS", http.StatusBadRequest)
return
}
target := "https://" + stripPort(r.Host) + r.URL.RequestURI()
http.Redirect(w, r, target, http.StatusFound)
}
func stripPort(hostport string) string {
host, _, err := net.SplitHostPort(hostport)
if err != nil {
return hostport
}
return net.JoinHostPort(host, "443")
}
// cert returns an existing certificate either from m.state or cache. // cert returns an existing certificate either from m.state or cache.
// If a certificate is found in cache but not in m.state, the latter will be filled // If a certificate is found in cache but not in m.state, the latter will be filled
// with the cached value. // with the cached value.
@ -442,13 +520,14 @@ func (m *Manager) certState(domain string) (*certState, error) {
// authorizedCert starts the domain ownership verification process and requests a new cert upon success. // authorizedCert starts the domain ownership verification process and requests a new cert upon success.
// The key argument is the certificate private key. // The key argument is the certificate private key.
func (m *Manager) authorizedCert(ctx context.Context, key crypto.Signer, domain string) (der [][]byte, leaf *x509.Certificate, err error) { func (m *Manager) authorizedCert(ctx context.Context, key crypto.Signer, domain string) (der [][]byte, leaf *x509.Certificate, err error) {
if err := m.verify(ctx, domain); err != nil {
return nil, nil, err
}
client, err := m.acmeClient(ctx) client, err := m.acmeClient(ctx)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
if err := m.verify(ctx, client, domain); err != nil {
return nil, nil, err
}
csr, err := certRequest(key, domain) csr, err := certRequest(key, domain)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
@ -464,98 +543,171 @@ func (m *Manager) authorizedCert(ctx context.Context, key crypto.Signer, domain
return der, leaf, nil return der, leaf, nil
} }
// verify starts a new identifier (domain) authorization flow. // verify runs the identifier (domain) authorization flow
// It prepares a challenge response and then blocks until the authorization // using each applicable ACME challenge type.
// is marked as "completed" by the CA (either succeeded or failed). func (m *Manager) verify(ctx context.Context, client *acme.Client, domain string) error {
// // The list of challenge types we'll try to fulfill
// verify returns nil iff the verification was successful. // in this specific order.
func (m *Manager) verify(ctx context.Context, domain string) error { challengeTypes := []string{"tls-sni-02", "tls-sni-01"}
client, err := m.acmeClient(ctx) m.tokensMu.RLock()
if err != nil { if m.tryHTTP01 {
return err challengeTypes = append(challengeTypes, "http-01")
} }
m.tokensMu.RUnlock()
// start domain authorization and get the challenge var nextTyp int // challengeType index of the next challenge type to try
authz, err := client.Authorize(ctx, domain) for {
if err != nil { // Start domain authorization and get the challenge.
return err authz, err := client.Authorize(ctx, domain)
} if err != nil {
// maybe don't need to at all return err
if authz.Status == acme.StatusValid {
return nil
}
// pick a challenge: prefer tls-sni-02 over tls-sni-01
// TODO: consider authz.Combinations
var chal *acme.Challenge
for _, c := range authz.Challenges {
if c.Type == "tls-sni-02" {
chal = c
break
} }
if c.Type == "tls-sni-01" { // No point in accepting challenges if the authorization status
chal = c // is in a final state.
switch authz.Status {
case acme.StatusValid:
return nil // already authorized
case acme.StatusInvalid:
return fmt.Errorf("acme/autocert: invalid authorization %q", authz.URI)
}
// Pick the next preferred challenge.
var chal *acme.Challenge
for chal == nil && nextTyp < len(challengeTypes) {
chal = pickChallenge(challengeTypes[nextTyp], authz.Challenges)
nextTyp++
}
if chal == nil {
return fmt.Errorf("acme/autocert: unable to authorize %q; tried %q", domain, challengeTypes)
}
cleanup, err := m.fulfill(ctx, client, chal)
if err != nil {
continue
}
defer cleanup()
if _, err := client.Accept(ctx, chal); err != nil {
continue
}
// A challenge is fulfilled and accepted: wait for the CA to validate.
if _, err := client.WaitAuthorization(ctx, authz.URI); err == nil {
return nil
} }
} }
if chal == nil {
return errors.New("acme/autocert: no supported challenge type found")
}
// create a token cert for the challenge response
var (
cert tls.Certificate
name string
)
switch chal.Type {
case "tls-sni-01":
cert, name, err = client.TLSSNI01ChallengeCert(chal.Token)
case "tls-sni-02":
cert, name, err = client.TLSSNI02ChallengeCert(chal.Token)
default:
err = fmt.Errorf("acme/autocert: unknown challenge type %q", chal.Type)
}
if err != nil {
return err
}
m.putTokenCert(ctx, name, &cert)
defer func() {
// verification has ended at this point
// don't need token cert anymore
go m.deleteTokenCert(name)
}()
// ready to fulfill the challenge
if _, err := client.Accept(ctx, chal); err != nil {
return err
}
// wait for the CA to validate
_, err = client.WaitAuthorization(ctx, authz.URI)
return err
} }
// putTokenCert stores the cert under the named key in both m.tokenCert map // fulfill provisions a response to the challenge chal.
// and m.Cache. // The cleanup is non-nil only if provisioning succeeded.
func (m *Manager) putTokenCert(ctx context.Context, name string, cert *tls.Certificate) { func (m *Manager) fulfill(ctx context.Context, client *acme.Client, chal *acme.Challenge) (cleanup func(), err error) {
m.tokenCertMu.Lock() switch chal.Type {
defer m.tokenCertMu.Unlock() case "tls-sni-01":
if m.tokenCert == nil { cert, name, err := client.TLSSNI01ChallengeCert(chal.Token)
m.tokenCert = make(map[string]*tls.Certificate) if err != nil {
return nil, err
}
m.putCertToken(ctx, name, &cert)
return func() { go m.deleteCertToken(name) }, nil
case "tls-sni-02":
cert, name, err := client.TLSSNI02ChallengeCert(chal.Token)
if err != nil {
return nil, err
}
m.putCertToken(ctx, name, &cert)
return func() { go m.deleteCertToken(name) }, nil
case "http-01":
resp, err := client.HTTP01ChallengeResponse(chal.Token)
if err != nil {
return nil, err
}
p := client.HTTP01ChallengePath(chal.Token)
m.putHTTPToken(ctx, p, resp)
return func() { go m.deleteHTTPToken(p) }, nil
} }
m.tokenCert[name] = cert return nil, fmt.Errorf("acme/autocert: unknown challenge type %q", chal.Type)
}
func pickChallenge(typ string, chal []*acme.Challenge) *acme.Challenge {
for _, c := range chal {
if c.Type == typ {
return c
}
}
return nil
}
// putCertToken stores the cert under the named key in both m.certTokens map
// and m.Cache.
func (m *Manager) putCertToken(ctx context.Context, name string, cert *tls.Certificate) {
m.tokensMu.Lock()
defer m.tokensMu.Unlock()
if m.certTokens == nil {
m.certTokens = make(map[string]*tls.Certificate)
}
m.certTokens[name] = cert
m.cachePut(ctx, name, cert) m.cachePut(ctx, name, cert)
} }
// deleteTokenCert removes the token certificate for the specified domain name // deleteCertToken removes the token certificate for the specified domain name
// from both m.tokenCert map and m.Cache. // from both m.certTokens map and m.Cache.
func (m *Manager) deleteTokenCert(name string) { func (m *Manager) deleteCertToken(name string) {
m.tokenCertMu.Lock() m.tokensMu.Lock()
defer m.tokenCertMu.Unlock() defer m.tokensMu.Unlock()
delete(m.tokenCert, name) delete(m.certTokens, name)
if m.Cache != nil { if m.Cache != nil {
m.Cache.Delete(context.Background(), name) m.Cache.Delete(context.Background(), name)
} }
} }
// httpToken retrieves an existing http-01 token value from an in-memory map
// or the optional cache.
func (m *Manager) httpToken(ctx context.Context, tokenPath string) ([]byte, error) {
m.tokensMu.RLock()
defer m.tokensMu.RUnlock()
if v, ok := m.httpTokens[tokenPath]; ok {
return v, nil
}
if m.Cache == nil {
return nil, fmt.Errorf("acme/autocert: no token at %q", tokenPath)
}
return m.Cache.Get(ctx, httpTokenCacheKey(tokenPath))
}
// putHTTPToken stores an http-01 token value using tokenPath as key
// in both in-memory map and the optional Cache.
//
// It ignores any error returned from Cache.Put.
func (m *Manager) putHTTPToken(ctx context.Context, tokenPath, val string) {
m.tokensMu.Lock()
defer m.tokensMu.Unlock()
if m.httpTokens == nil {
m.httpTokens = make(map[string][]byte)
}
b := []byte(val)
m.httpTokens[tokenPath] = b
if m.Cache != nil {
m.Cache.Put(ctx, httpTokenCacheKey(tokenPath), b)
}
}
// deleteHTTPToken removes an http-01 token value from both in-memory map
// and the optional Cache, ignoring any error returned from the latter.
//
// If m.Cache is non-nil, it blocks until Cache.Delete returns without a timeout.
func (m *Manager) deleteHTTPToken(tokenPath string) {
m.tokensMu.Lock()
defer m.tokensMu.Unlock()
delete(m.httpTokens, tokenPath)
if m.Cache != nil {
m.Cache.Delete(context.Background(), httpTokenCacheKey(tokenPath))
}
}
// httpTokenCacheKey returns a key at which an http-01 token value may be stored
// in the Manager's optional Cache.
func httpTokenCacheKey(tokenPath string) string {
return "http-01-" + path.Base(tokenPath)
}
// renew starts a cert renewal timer loop, one per domain. // renew starts a cert renewal timer loop, one per domain.
// //
// The loop is scheduled in two cases: // The loop is scheduled in two cases:

Some files were not shown because too many files have changed in this diff Show More