Merge pull request #104 from DanielHeath/update-deps

Update all dependencies to latest
This commit is contained in:
Zack 2018-01-23 18:41:25 -05:00 committed by GitHub
commit 526688c7e3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
327 changed files with 43002 additions and 30657 deletions

32
Gopkg.lock generated
View File

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

View File

@ -1,9 +1,14 @@
// A GIN middleware providing low-fi security for personal stuff.
package gin_teeny_security
import "github.com/gin-gonic/gin"
import "github.com/gin-contrib/sessions"
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.
// 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
// would be particularly bad.
func RequiresSecretAccessCode(secretAccessCode, path string) gin.HandlerFunc {
return func(c *gin.Context) {
session := sessions.Default(c)
if c.Request.URL.Path == path {
if c.Request.Method == "POST" {
c.Request.ParseForm()
cfg := &Config{
Path: path,
Secret: secretAccessCode,
}
if c.Request.PostForm.Get("secretAccessCode") == secretAccessCode {
c.Header("Location", "/")
session.Set("secretAccessCode", secretAccessCode)
session.Save()
c.AbortWithStatus(http.StatusFound)
return
} else {
session.Set("secretAccessCode", "")
session.Save()
c.Data(http.StatusForbidden, "text/html", []byte(`
<h1>Login</h1>
<h2>Wrong password</h2>
<form action="`+path+`" method="POST">
<input name="secretAccessCode" />
<input type="submit" value="Login" />
</form>
`))
c.Abort()
return
}
} else if c.Request.Method == "GET" {
c.Data(http.StatusOK, "text/html", []byte(`
<h1>Login</h1>
<form action="`+path+`" method="POST">
<input name="secretAccessCode" />
<input type="submit" value="Login" />
</form>
`))
c.Abort()
return cfg.Middleware
}
type Config struct {
Path string // defaults to login
Secret string
RequireAuth func(*gin.Context) bool // defaults to always requiring auth if unset
Template *template.Template
SaveKeyToSession func(*gin.Context, string)
GetKeyFromSession func(*gin.Context) string
}
func (c Config) saveKey(ctx *gin.Context, k string) {
if c.SaveKeyToSession == nil {
c.SaveKeyToSession = DefaultSetSession
}
c.SaveKeyToSession(ctx, k)
}
func (c Config) getKey(ctx *gin.Context) string {
if c.GetKeyFromSession == nil {
c.GetKeyFromSession = DefaultGetSession
}
return c.GetKeyFromSession(ctx)
}
func DefaultSetSession(c *gin.Context, secret string) {
session := sessions.Default(c)
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
} else {
c.Next()
cfg.saveKey(c, "")
c.Writer.WriteHeader(http.StatusForbidden)
cfg.ExecTemplate(c.Writer, "Wrong Password", returnTo)
c.Abort()
return
}
}
v := session.Get("secretAccessCode")
if v != secretAccessCode {
c.Header("Location", path)
c.AbortWithStatus(http.StatusTemporaryRedirect)
} else if c.Request.Method == "GET" {
cfg.ExecTemplate(c.Writer, "", returnTo)
c.Abort()
return
} else {
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)
- [FAQ](https://github.com/garyburd/redigo/wiki/FAQ)
- [Examples](https://godoc.org/github.com/garyburd/redigo/redis#pkg-examples)
Installation
------------

View File

@ -29,9 +29,12 @@ import (
"time"
)
var (
_ ConnWithTimeout = (*conn)(nil)
)
// conn is the low-level implementation of Conn
type conn struct {
// Shared
mu sync.Mutex
pending int
@ -73,6 +76,7 @@ type DialOption struct {
type dialOptions struct {
readTimeout time.Duration
writeTimeout time.Duration
dialer *net.Dialer
dial func(network, addr string) (net.Conn, error)
db int
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 {
return DialOption{func(do *dialOptions) {
dialer := net.Dialer{Timeout: d}
do.dial = dialer.Dial
do.dialer.Timeout = d
}}
}
// 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
// connections. If this option is left out, then net.Dial is
// used. DialNetDial overrides DialConnectTimeout.
// connections, otherwise a net.Dialer customized via the other options is used.
// DialNetDial overrides DialConnectTimeout and DialKeepAlive.
func DialNetDial(dial func(network, addr string) (net.Conn, error)) DialOption {
return DialOption{func(do *dialOptions) {
do.dial = dial
@ -155,11 +169,16 @@ func DialUseTLS(useTLS bool) DialOption {
// address using the specified options.
func Dial(network, address string, options ...DialOption) (Conn, error) {
do := dialOptions{
dial: net.Dial,
dialer: &net.Dialer{
KeepAlive: time.Minute * 5,
},
}
for _, option := range options {
option.f(&do)
}
if do.dial == nil {
do.dial = do.dialer.Dial
}
netConn, err := do.dial(network, address)
if err != nil {
@ -167,7 +186,12 @@ func Dial(network, address string, options ...DialOption) (Conn, error) {
}
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 == "" {
host, _, err := net.SplitHostPort(address)
if err != nil {
@ -556,10 +580,17 @@ func (c *conn) Flush() error {
return nil
}
func (c *conn) Receive() (reply interface{}, err error) {
if c.readTimeout != 0 {
c.conn.SetReadDeadline(time.Now().Add(c.readTimeout))
func (c *conn) Receive() (interface{}, error) {
return c.ReceiveWithTimeout(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 {
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) {
return c.DoWithTimeout(c.readTimeout, cmd, args...)
}
func (c *conn) DoWithTimeout(readTimeout time.Duration, cmd string, args ...interface{}) (interface{}, error) {
c.mu.Lock()
pending := c.pending
c.pending = 0
@ -605,9 +640,11 @@ func (c *conn) Do(cmd string, args ...interface{}) (interface{}, error) {
return nil, c.fatal(err)
}
if c.readTimeout != 0 {
c.conn.SetReadDeadline(time.Now().Add(c.readTimeout))
var deadline time.Time
if readTimeout != 0 {
deadline = time.Now().Add(readTimeout)
}
c.conn.SetReadDeadline(deadline)
if cmd == "" {
reply := make([]interface{}, pending)

View File

@ -34,14 +34,16 @@ import (
type testConn struct {
io.Reader
io.Writer
readDeadline time.Time
writeDeadline time.Time
}
func (*testConn) Close() error { return nil }
func (*testConn) LocalAddr() net.Addr { return nil }
func (*testConn) RemoteAddr() net.Addr { return nil }
func (*testConn) SetDeadline(t time.Time) error { return nil }
func (*testConn) SetReadDeadline(t time.Time) error { return nil }
func (*testConn) SetWriteDeadline(t time.Time) error { return nil }
func (*testConn) Close() error { return nil }
func (*testConn) LocalAddr() net.Addr { return nil }
func (*testConn) RemoteAddr() net.Addr { return nil }
func (c *testConn) SetDeadline(t time.Time) error { c.readDeadline = t; c.writeDeadline = t; return nil }
func (c *testConn) SetReadDeadline(t time.Time) error { c.readDeadline = t; return nil }
func (c *testConn) SetWriteDeadline(t time.Time) error { c.writeDeadline = t; return nil }
func dialTestConn(r string, w io.Writer) redis.DialOption {
return redis.DialNetDial(func(network, addr string) (net.Conn, error) {
@ -764,7 +766,6 @@ func BenchmarkDoPing(b *testing.B) {
var clientTLSConfig, serverTLSConfig tls.Config
func init() {
// The certificate and key for testing TLS dial options was created
// using the command
//
@ -822,3 +823,45 @@ Bjqn3yoLHaoZVvbWOi0C2TCN4FjXjaLNZGifQPbIcaA=
clientTLSConfig.RootCAs = x509.NewCertPool()
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"
// similar cloneTLSClientConfig in the stdlib, but also honor skipVerify for the nil case
func cloneTLSClientConfig(cfg *tls.Config, skipVerify bool) *tls.Config {
if cfg == nil {
return &tls.Config{InsecureSkipVerify: skipVerify}
}
func cloneTLSConfig(cfg *tls.Config) *tls.Config {
return &tls.Config{
Rand: cfg.Rand,
Time: cfg.Time,

View File

@ -1,14 +1,10 @@
// +build go1.7
// +build go1.7,!go1.8
package redis
import "crypto/tls"
// similar cloneTLSClientConfig in the stdlib, but also honor skipVerify for the nil case
func cloneTLSClientConfig(cfg *tls.Config, skipVerify bool) *tls.Config {
if cfg == nil {
return &tls.Config{InsecureSkipVerify: skipVerify}
}
func cloneTLSConfig(cfg *tls.Config) *tls.Config {
return &tls.Config{
Rand: cfg.Rand,
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"
"fmt"
"log"
"time"
)
var (
_ ConnWithTimeout = (*loggingConn)(nil)
)
// NewLoggingConn returns a logging wrapper around a connection.
@ -104,6 +109,12 @@ func (c *loggingConn) Do(commandName string, args ...interface{}) (interface{},
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 {
err := c.Conn.Send(commandName, args...)
c.print("Send", commandName, args, nil, err)
@ -115,3 +126,9 @@ func (c *loggingConn) Receive() (interface{}, error) {
c.print("Receive", "", nil, 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"
)
var (
_ ConnWithTimeout = (*pooledConnection)(nil)
_ ConnWithTimeout = (*errorConnection)(nil)
)
var nowFunc = time.Now // for testing
// ErrPoolExhausted is returned from a pool connection method (Do, Send,
@ -96,7 +101,7 @@ var (
// return nil, err
// }
// return c, nil
// }
// },
// }
//
// Use the TestOnBorrow function to check the health of an idle connection
@ -115,7 +120,6 @@ var (
// }
//
type Pool struct {
// Dial is an application supplied function for creating and configuring a
// connection.
//
@ -269,7 +273,6 @@ func (p *Pool) get() (Conn, error) {
}
for {
// Get idle connection.
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...)
}
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 {
ci := internal.LookupCommandInfo(commandName)
pc.state = (pc.state | ci.Set) &^ ci.Clear
@ -434,11 +447,23 @@ func (pc *pooledConnection) Receive() (reply interface{}, err error) {
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 }
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) Err() error { return ec.err }
func (ec errorConnection) Close() error { return ec.err }
func (ec errorConnection) Flush() error { return ec.err }
func (ec errorConnection) Receive() (interface{}, error) { return nil, ec.err }
func (ec errorConnection) DoWithTimeout(time.Duration, string, ...interface{}) (interface{}, error) {
return nil, ec.err
}
func (ec errorConnection) Send(string, ...interface{}) error { return 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
import "errors"
import (
"errors"
"time"
)
// Subscription represents a subscribe or unsubscribe notification.
type Subscription struct {
// Kind is "subscribe", "unsubscribe", "psubscribe" or "punsubscribe"
Kind string
@ -31,7 +33,6 @@ type Subscription struct {
// Message represents a message notification.
type Message struct {
// The originating channel.
Channel string
@ -41,7 +42,6 @@ type Message struct {
// PMessage represents a pmessage notification.
type PMessage struct {
// The matched pattern.
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
// as illustrated in the PubSubConn example.
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 {
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
import (
"fmt"
"reflect"
"sync"
"testing"
"time"
"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{}) {
actual := c.Receive()
if !reflect.DeepEqual(actual, expected) {
@ -145,4 +65,10 @@ func TestPushed(t *testing.T) {
c.Conn.Send("PING")
c.Conn.Flush()
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
import (
"errors"
"time"
)
// Error represents an error returned in a command reply.
type Error string
@ -59,3 +64,54 @@ type Scanner interface {
// loss of information.
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)
}
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
// 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
// item is not a bulk string or nil.
func Strings(reply interface{}, err error) ([]string, error) {
if err != nil {
return nil, err
}
switch reply := reply.(type) {
case []interface{}:
result := make([]string, 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 Strings, got type %T", reply[i])
}
result[i] = string(p)
var result []string
err = sliceHelper(reply, err, "Strings", func(n int) { result = make([]string, n) }, func(i int, v interface{}) error {
switch v := v.(type) {
case string:
result[i] = v
return nil
case []byte:
result[i] = string(v)
return nil
default:
return fmt.Errorf("redigo: unexpected element type for Strings, got type %T", v)
}
return result, nil
case nil:
return nil, ErrNil
case Error:
return nil, reply
}
return nil, fmt.Errorf("redigo: unexpected type for Strings, got type %T", reply)
})
return result, err
}
// 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
// bulk string or nil.
func ByteSlices(reply interface{}, err error) ([][]byte, error) {
if err != nil {
return nil, err
}
switch reply := reply.(type) {
case []interface{}:
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
var result [][]byte
err = sliceHelper(reply, err, "ByteSlices", func(n int) { result = make([][]byte, n) }, func(i int, v interface{}) error {
p, ok := v.([]byte)
if !ok {
return fmt.Errorf("redigo: unexpected element type for ByteSlices, got type %T", v)
}
return result, nil
case nil:
return nil, ErrNil
case Error:
return nil, reply
}
return nil, fmt.Errorf("redigo: unexpected type for ByteSlices, got type %T", reply)
result[i] = p
return nil
})
return result, err
}
// Ints is a helper that converts an array command reply to a []int. If
// err is not equal to nil, then Ints returns nil, err.
// Int64s is a helper that converts an array command reply to a []int64.
// 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) {
var ints []int
values, err := Values(reply, err)
if err != nil {
return ints, err
}
if err := ScanSlice(values, &ints); err != nil {
return ints, err
}
return ints, nil
var result []int
err = sliceHelper(reply, err, "Ints", func(n int) { result = make([]int, n) }, func(i int, v interface{}) error {
switch v := v.(type) {
case int64:
n := int(v)
if int64(n) != v {
return strconv.ErrRange
}
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)

View File

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

View File

@ -38,6 +38,7 @@ var (
ErrNegativeInt = errNegativeInt
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")
serverLogName = flag.String("redis-log", "", "Write Redis server logs to `filename`")
serverLog = ioutil.Discard
@ -126,28 +127,32 @@ func stopDefaultServer() {
}
}
// startDefaultServer starts the default server if not already running.
func startDefaultServer() error {
// DefaultServerAddr starts the test server if not already started and returns
// the address of that server.
func DefaultServerAddr() (string, error) {
defaultServerMu.Lock()
defer defaultServerMu.Unlock()
addr := fmt.Sprintf("%v:%d", *serverAddress, *serverBasePort)
if defaultServer != nil || defaultServerErr != nil {
return defaultServerErr
return addr, defaultServerErr
}
defaultServer, defaultServerErr = NewServer(
"default",
"--port", strconv.Itoa(*serverBasePort),
"--bind", *serverAddress,
"--save", "",
"--appendonly", "no")
return defaultServerErr
return addr, defaultServerErr
}
// DialDefaultServer starts the test server if not already started and dials a
// connection to the server.
func DialDefaultServer() (Conn, error) {
if err := startDefaultServer(); err != nil {
addr, err := DefaultServerAddr()
if err != nil {
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 {
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,
// depending on required precision."
s, ns := s.Field(0).Int(), s.Field(1).Int()
d := time.Duration(s)*time.Second + time.Duration(ns)*time.Nanosecond
x := fmt.Sprintf("%.9f", d.Seconds())
x := fmt.Sprintf("%d.%09d", s, ns)
x = strings.TrimSuffix(x, "000")
x = strings.TrimSuffix(x, "000")
out.write(`"`)

View File

@ -407,6 +407,7 @@ var marshalingTests = []struct {
{"Any with WKT", marshaler, anyWellKnown, anyWellKnownJSON},
{"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: 100000000, Nanos: 1}}, `{"dur":"100000000.000000001s"}`},
{"Struct", marshaler, &pb.KnownTypes{St: &stpb.Struct{
Fields: map[string]*stpb.Value{
"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. Mike Samuel mikesamuel@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
* 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 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
// structure
p.AllowDocType(true)
p.AllowElements("html", "head", "body", "title")
// There are not safe, and is only being done here to demonstrate how to

View File

@ -47,9 +47,6 @@ type Policy struct {
// exceptions
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
// tag is replaced by a space character.
addSpaces bool
@ -369,21 +366,6 @@ func (p *Policy) AllowURLSchemeWithCustomPolicy(
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
// removing tags that are not whitelisted by the policy.
//
@ -498,6 +480,7 @@ func (p *Policy) addDefaultElementsWithoutAttrs() {
p.setOfElementsAllowedWithoutAttrs["ruby"] = struct{}{}
p.setOfElementsAllowedWithoutAttrs["s"] = struct{}{}
p.setOfElementsAllowedWithoutAttrs["samp"] = struct{}{}
p.setOfElementsAllowedWithoutAttrs["script"] = struct{}{}
p.setOfElementsAllowedWithoutAttrs["section"] = struct{}{}
p.setOfElementsAllowedWithoutAttrs["select"] = struct{}{}
p.setOfElementsAllowedWithoutAttrs["small"] = struct{}{}

View File

@ -112,9 +112,13 @@ func (p *Policy) sanitize(r io.Reader) *bytes.Buffer {
switch token.Type {
case html.DoctypeToken:
if p.allowDocType {
buff.WriteString(token.String())
}
// DocType is not handled as there is no safe parsing mechanism
// 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:
@ -217,7 +221,7 @@ func (p *Policy) sanitize(r io.Reader) *bytes.Buffer {
case html.TextToken:
if !skipElementContent {
switch strings.ToLower(mostRecentlyStartedToken) {
switch mostRecentlyStartedToken {
case "script":
// not encouraged, but if a policy allows JavaScript we
// 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())
}
}
default:
// A token that didn't exist in the html package when we wrote this
return &bytes.Buffer{}
@ -490,13 +493,18 @@ func (p *Policy) allowNoAttrs(elementName string) bool {
func (p *Policy) validURL(rawurl string) (string, bool) {
if p.requireParseableURLs {
// URLs do not contain whitespace
if strings.Contains(rawurl, " ") ||
// URLs are valid if when space is trimmed the URL is valid
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, "\n") {
strings.Contains(rawurl, "\n")) &&
!strings.HasPrefix(rawurl, `data:`) {
return "", false
}
// URLs are valid if they parse
u, err := url.Parse(rawurl)
if err != nil {
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) {
tests := []test{
@ -1506,3 +1473,214 @@ func TestTargetBlankNoOpener(t *testing.T) {
}
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
go:
- 1.7.x
- 1.8.x
- 1.9.x
sudo: false

View File

@ -199,114 +199,92 @@ var gfmHTMLConfig = syntaxhighlight.HTMLConfig{
Decimal: "m",
}
// TODO: Support highlighting for more languages.
func highlightCode(src []byte, lang string) (highlightedCode []byte, ok bool) {
switch lang {
case "Go":
case "Go", "Go-unformatted":
var buf bytes.Buffer
err := highlight_go.Print(src, &buf, syntaxhighlight.HTMLPrinter(gfmHTMLConfig))
if err != nil {
return nil, false
}
return buf.Bytes(), true
case "Go-old":
var buf bytes.Buffer
err := syntaxhighlight.Print(syntaxhighlight.NewScanner(src), &buf, syntaxhighlight.HTMLPrinter(gfmHTMLConfig))
case "diff":
anns, err := highlight_diff.Annotate(src)
if err != nil {
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"))
lineStarts := make([]int, len(lines))
var offset int
for lineIndex := 0; lineIndex < len(lines); lineIndex++ {
lineStarts[lineIndex] = offset
offset += len(lines[lineIndex]) + 1
}
lines := bytes.Split(src, []byte("\n"))
lineStarts := make([]int, len(lines))
var offset int
for lineIndex := 0; lineIndex < len(lines); lineIndex++ {
lineStarts[lineIndex] = offset
offset += len(lines[lineIndex]) + 1
}
lastDel, lastIns := -1, -1
for lineIndex := 0; lineIndex < len(lines); lineIndex++ {
var lineFirstChar byte
if len(lines[lineIndex]) > 0 {
lineFirstChar = lines[lineIndex][0]
lastDel, lastIns := -1, -1
for lineIndex := 0; lineIndex < len(lines); lineIndex++ {
var lineFirstChar byte
if len(lines[lineIndex]) > 0 {
lineFirstChar = lines[lineIndex][0]
}
switch lineFirstChar {
case '+':
if lastIns == -1 {
lastIns = lineIndex
}
switch lineFirstChar {
case '+':
if lastIns == -1 {
case '-':
if lastDel == -1 {
lastDel = lineIndex
}
default:
if lastDel != -1 || lastIns != -1 {
if lastDel == -1 {
lastDel = lastIns
} else if lastIns == -1 {
lastIns = lineIndex
}
case '-':
if lastDel == -1 {
lastDel = lineIndex
}
default:
if lastDel != -1 || lastIns != -1 {
if lastDel == -1 {
lastDel = lastIns
} else if lastIns == -1 {
lastIns = lineIndex
beginOffsetLeft := lineStarts[lastDel]
endOffsetLeft := lineStarts[lastIns]
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, &annotate.Annotation{Start: beginOffsetRight, End: endOffsetRight, Left: []byte(`<span class="gi input-block">`), Right: []byte(`</span>`), WantInner: 0})
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]
endOffsetLeft := lineStarts[lastIns]
beginOffsetRight := lineStarts[lastIns]
endOffsetRight := lineStarts[lineIndex]
var sectionSegments [2][]*annotate.Annotation
highlight_diff.HighlightedDiffFunc(leftContent, rightContent, &sectionSegments, [2]int{beginOffsetLeft, beginOffsetRight})
anns = append(anns, &annotate.Annotation{Start: beginOffsetLeft, End: endOffsetLeft, Left: []byte(`<span class="gd input-block">`), Right: []byte(`</span>`), WantInner: 0})
anns = append(anns, &annotate.Annotation{Start: beginOffsetRight, End: endOffsetRight, Left: []byte(`<span class="gi input-block">`), Right: []byte(`</span>`), WantInner: 0})
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]...)
}
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:
return nil, false
}

View File

@ -17,7 +17,6 @@ Directories
| 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. |
| [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. |

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.
```Go
client := graphql.NewClient("https://example.com/graphql", nil, nil)
client := graphql.NewClient("https://example.com/graphql", nil)
// Use client...
```
@ -41,7 +41,7 @@ func main() {
)
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...
```

View File

@ -38,7 +38,7 @@ func run() error {
mux := http.NewServeMux()
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 {

View File

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

View File

@ -131,6 +131,10 @@ func (n Name) ToMixedCaps() string {
n[i] = initialism
continue
}
if brand, ok := isBrand(word); ok {
n[i] = brand
continue
}
r, size := utf8.DecodeRuneInString(word)
n[i] = string(unicode.ToUpper(r)) + strings.ToLower(word[size:])
}
@ -220,3 +224,17 @@ var initialisms = map[string]struct{}{
// Additional common initialisms.
"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 {
in ident.Name
want string
}{
{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 {
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 {
in ident.Name
want string

View File

@ -2,6 +2,7 @@ package graphql
import (
"bytes"
"encoding/json"
"io"
"reflect"
"sort"
@ -9,16 +10,16 @@ import (
"github.com/shurcooL/graphql/ident"
)
func constructQuery(qctx *queryContext, v interface{}, variables map[string]interface{}) string {
query := qctx.Query(v)
func constructQuery(v interface{}, variables map[string]interface{}) string {
query := query(v)
if variables != nil {
return "query(" + queryArguments(variables) + ")" + query
}
return query
}
func constructMutation(qctx *queryContext, v interface{}, variables map[string]interface{}) string {
query := qctx.Query(v)
func constructMutation(v interface{}, variables map[string]interface{}) string {
query := query(v)
if variables != nil {
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".
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 {
sorted = append(sorted, k)
keys = append(keys, k)
}
sort.Strings(sorted)
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
}
sort.Strings(keys)
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
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()
}
// writeQuery writes a minified query for t to w. If inline is true,
// the struct fields of t are inlined into parent struct.
func (c *queryContext) writeQuery(w io.Writer, t reflect.Type, inline bool) {
// writeArgumentType writes a minified GraphQL type for t to w.
// value indicates whether t is a value (required) type or pointer (optional) type.
// 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() {
case reflect.Ptr, reflect.Slice:
c.writeQuery(w, t.Elem(), false)
writeQuery(w, t.Elem(), false)
case reflect.Struct:
// Special handling of scalar struct types. Don't expand them.
for _, scalar := range c.Scalars {
if t == scalar {
return
}
// If the type implements json.Unmarshaler, it's a scalar. Don't expand it.
if reflect.PtrTo(t).Implements(jsonUnmarshaler) {
return
}
if !inline {
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())
}
}
c.writeQuery(w, f.Type, inlineField)
writeQuery(w, f.Type, inlineField)
}
if !inline {
io.WriteString(w, "}")
}
}
}
var jsonUnmarshaler = reflect.TypeOf((*json.Unmarshaler)(nil)).Elem()

View File

@ -2,7 +2,6 @@ package graphql
import (
"net/url"
"reflect"
"testing"
"time"
)
@ -217,15 +216,20 @@ func TestConstructQuery(t *testing.T) {
}(),
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 {
qctx := &queryContext{
Scalars: []reflect.Type{
reflect.TypeOf(DateTime{}),
reflect.TypeOf(URI{}),
},
}
got := constructQuery(qctx, tc.inV, tc.inVariables)
got := constructQuery(tc.inV, tc.inVariables)
if 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 {
got := constructMutation(&queryContext{}, tc.inV, tc.inVariables)
got := constructMutation(tc.inV, tc.inVariables)
if 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) {
tests := []struct {
name string
in map[string]interface{}
want string
}{
@ -278,26 +281,43 @@ func TestQueryArguments(t *testing.T) {
want: "$a:Int!$b:Boolean",
},
{
in: map[string]interface{}{"states": []IssueState{IssueStateOpen, IssueStateClosed}},
want: "$states:[IssueState!]",
in: map[string]interface{}{
"required": []IssueState{IssueStateOpen, IssueStateClosed},
"optional": &[]IssueState{IssueStateOpen, IssueStateClosed},
},
want: "$optional:[IssueState!]$required:[IssueState!]!",
},
{
in: map[string]interface{}{"states": []IssueState(nil)},
want: "$states:[IssueState!]",
in: map[string]interface{}{
"required": []IssueState(nil),
"optional": (*[]IssueState)(nil),
},
want: "$optional:[IssueState!]$required:[IssueState!]!",
},
{
in: map[string]interface{}{"states": [...]IssueState{IssueStateOpen, IssueStateClosed}},
want: "$states:[IssueState!]",
in: map[string]interface{}{
"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!",
},
{
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)
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 }
)
func (u *URI) UnmarshalJSON(data []byte) error { panic("mock implementation") }
// IssueState represents the possible states of an issue.
type IssueState string

View File

@ -7,7 +7,6 @@ import (
"encoding/json"
"flag"
"fmt"
"go/format"
"io"
"log"
"os"
@ -55,6 +54,8 @@ func run() error {
fmt.Fprint(&buf, `package octiconssvg
import (
"strconv"
"golang.org/x/net/html"
"golang.org/x/net/html/atom"
)
@ -71,14 +72,18 @@ func Icon(name string) *html.Node {
return nil
}
}
`)
for _, name := range names {
processOcticon(&buf, octicons, name)
}
b, err := format.Source(buf.Bytes())
if err != nil {
return fmt.Errorf("error from format.Source(): %v", err)
// SetSize sets size of icon, and returns a reference to it.
func SetSize(icon *html.Node, size int) *html.Node {
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
@ -94,7 +99,7 @@ func Icon(name string) *html.Node {
w = f
}
_, err = w.Write(b)
_, err = w.Write(buf.Bytes())
return err
}
@ -104,10 +109,10 @@ type octicon struct {
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])
svg := parseOcticon(svgXML)
svg := parseOcticon(svgXML)
// 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
// 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.
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>`,
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 {
e, err := html.ParseFragment(strings.NewReader(svgXML), nil)
if err != nil {

File diff suppressed because it is too large Load Diff

View File

@ -10,7 +10,7 @@ import (
)
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())
if err != nil {
@ -20,3 +20,15 @@ func Example() {
// 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>
}
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
sudo: false
go:
- 1.8.x
- 1.9.x
- tip
- 1.7.x # go testing suite support, which we use, was introduced in go 1.7
- 1.8.x
- 1.9.x
- tip
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:
- 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)
[![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)
[![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)
# 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.
/*
High Performance, Feature-Rich Idiomatic Go 1.4+ codec/encoding library for
binc, msgpack, cbor, json
Package codec provides a
High Performance, Feature-Rich Idiomatic Go 1.4+ codec/encoding library
for binc, msgpack, cbor, json.
Supported Serialization formats are:
@ -32,15 +33,14 @@ Rich Feature Set includes:
- Simple but extremely powerful and feature-rich API
- 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.
Our extensive benchmarks show us outperforming Gob, Json, Bson, etc by 2-4X.
- Careful selected use of 'unsafe' for targeted performance gains.
100% mode exists where 'unsafe' is not used at all.
- Lock-free (sans mutex) concurrency for scaling to 100's of cores
- Multiple conversions:
Package coerces types where appropriate
e.g. decode an int in the stream into a float, etc.
- Coerce types where appropriate
e.g. decode an int in the stream into a float, decode numbers from formatted strings, etc
- Corner Cases:
Overflows, nil maps/slices, nil values in streams are handled correctly
- Standard field renaming via tags
@ -49,10 +49,16 @@ Rich Feature Set includes:
(struct, slice, map, primitives, pointers, interface{}, etc)
- Extensions to support efficient encoding/decoding of any named types
- 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{}).
Includes Options to configure what specific map or slice type to use
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
- 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
- Fast (no-reflection) encoding/decoding of common maps and slices
- 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.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() ),
then that type doesn't satisfy the check and we will continue walking down the
decision tree.
@ -201,6 +207,58 @@ Running Benchmarks
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
// 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
- 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.
Our extensive benchmarks show us outperforming Gob, Json, Bson, etc by 2-4X.
- Careful selected use of 'unsafe' for targeted performance gains.
100% mode exists where 'unsafe' is not used at all.
- Lock-free (sans mutex) concurrency for scaling to 100's of cores
- Multiple conversions:
Package coerces types where appropriate
e.g. decode an int in the stream into a float, etc.
- Coerce types where appropriate
e.g. decode an int in the stream into a float, decode numbers from formatted strings, etc
- Corner Cases:
Overflows, nil maps/slices, nil values in streams are handled correctly
- Standard field renaming via tags
@ -48,10 +47,16 @@ Rich Feature Set includes:
(struct, slice, map, primitives, pointers, interface{}, etc)
- Extensions to support efficient encoding/decoding of any named types
- 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{}).
Includes Options to configure what specific map or slice type to use
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
- 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
- Fast (no-reflection) encoding/decoding of common maps and slices
- 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.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() ),
then that type doesn't satisfy the check and we will continue walking down the
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 .
## 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.
package codec
@ -57,38 +57,31 @@ const (
type bincEncDriver struct {
e *Encoder
h *BincHandle
w encWriter
m map[string]uint16 // symbols
b [scratchByteArrayLen]byte
s uint16 // symbols sequencer
b [16]byte // scratch, used for encoding numbers - bigendian style
s uint16 // symbols sequencer
// c containerState
encDriverTrackContainerWriter
noBuiltInTypes
// 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() {
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) {
if b {
e.w.writen1(bincVdSpecial<<4 | bincSpTrue)
@ -198,13 +191,19 @@ func (e *bincEncDriver) encodeExtPreamble(xtag byte, length int) {
func (e *bincEncDriver) WriteArrayStart(length int) {
e.encLen(bincVdArray<<4, uint64(length))
e.c = containerArrayStart
}
func (e *bincEncDriver) WriteMapStart(length int) {
e.encLen(bincVdMap<<4, uint64(length))
e.c = containerMapStart
}
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))
e.encBytesLen(c, l)
if l > 0 {
@ -214,7 +213,7 @@ func (e *bincEncDriver) EncodeString(c charEncoding, v string) {
func (e *bincEncDriver) EncodeSymbol(v string) {
// if WriteSymbolsNoRefs {
// e.encodeString(c_UTF8, v)
// e.encodeString(cUTF8, v)
// return
// }
@ -224,10 +223,10 @@ func (e *bincEncDriver) EncodeSymbol(v string) {
l := len(v)
if l == 0 {
e.encBytesLen(c_UTF8, 0)
e.encBytesLen(cUTF8, 0)
return
} else if l == 1 {
e.encBytesLen(c_UTF8, 1)
e.encBytesLen(cUTF8, 1)
e.w.writen1(v[0])
return
}
@ -277,6 +276,10 @@ func (e *bincEncDriver) EncodeSymbol(v string) {
}
func (e *bincEncDriver) EncodeStringBytes(c charEncoding, v []byte) {
if v == nil {
e.EncodeNil()
return
}
l := uint64(len(v))
e.encBytesLen(c, l)
if l > 0 {
@ -286,7 +289,7 @@ func (e *bincEncDriver) EncodeStringBytes(c charEncoding, v []byte) {
func (e *bincEncDriver) encBytesLen(c charEncoding, length uint64) {
//TODO: support bincUnicodeOther (for now, just use string or bytearray)
if c == c_RAW {
if c == cRAW {
e.encLen(bincVdByteArray<<4, length)
} else {
e.encLen(bincVdString<<4, length)
@ -325,6 +328,9 @@ type bincDecSymbol struct {
}
type bincDecDriver struct {
decDriverNoopContainerReader
noBuiltInTypes
d *Decoder
h *BincHandle
r decReader
@ -333,14 +339,15 @@ type bincDecDriver struct {
bd byte
vd byte
vs byte
// noStreamingCodec
// decNoSeparator
b [scratchByteArrayLen]byte
_ [3]byte // padding
// linear searching on this slice is ok,
// because we typically expect < 32 symbols in each stream.
s []bincDecSymbol
decDriverNoopContainerReader
// noStreamingCodec
// decNoSeparator
b [8 * 8]byte // scratch
}
func (d *bincDecDriver) readNextBd() {
@ -371,9 +378,10 @@ func (d *bincDecDriver) ContainerType() (vt valueType) {
return valueTypeArray
} else if d.vd == bincVdMap {
return valueTypeMap
} else {
// d.d.errorf("isContainerType: unsupported parameter: %v", vt)
}
// else {
// d.d.errorf("isContainerType: unsupported parameter: %v", vt)
// }
return valueTypeUnset
}
@ -388,27 +396,24 @@ func (d *bincDecDriver) TryDecodeAsNil() bool {
return false
}
func (d *bincDecDriver) IsBuiltinType(rt uintptr) bool {
return rt == timeTypId
}
func (d *bincDecDriver) DecodeBuiltin(rt uintptr, v interface{}) {
func (d *bincDecDriver) DecodeTime() (t time.Time) {
if !d.bdRead {
d.readNextBd()
}
if rt == timeTypId {
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
if d.bd == bincVdSpecial<<4|bincSpNil {
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) {
@ -497,45 +502,33 @@ func (d *bincDecDriver) decCheckInteger() (ui uint64, neg bool) {
return
}
} 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
}
func (d *bincDecDriver) DecodeInt(bitsize uint8) (i int64) {
func (d *bincDecDriver) DecodeInt64() (i int64) {
ui, neg := d.decCheckInteger()
i, overflow := chkOvf.SignedInt(ui)
if overflow {
d.d.errorf("simple: overflow converting %v to signed integer", ui)
return
}
i = chkOvf.SignedIntV(ui)
if neg {
i = -i
}
if chkOvf.Int(i, bitsize) {
d.d.errorf("binc: overflow integer: %v for num bits: %v", i, bitsize)
return
}
d.bdRead = false
return
}
func (d *bincDecDriver) DecodeUint(bitsize uint8) (ui uint64) {
func (d *bincDecDriver) DecodeUint64() (ui uint64) {
ui, neg := d.decCheckInteger()
if neg {
d.d.errorf("Assigning negative signed value to unsigned type")
return
}
if chkOvf.Uint(ui, bitsize) {
d.d.errorf("binc: overflow integer: %v", ui)
return
}
d.bdRead = false
return
}
func (d *bincDecDriver) DecodeFloat(chkOverflow32 bool) (f float64) {
func (d *bincDecDriver) DecodeFloat64() (f float64) {
if !d.bdRead {
d.readNextBd()
}
@ -557,11 +550,7 @@ func (d *bincDecDriver) DecodeFloat(chkOverflow32 bool) (f float64) {
} else if vd == bincVdFloat {
f = d.decFloat()
} else {
f = float64(d.DecodeInt(64))
}
if chkOverflow32 && chkOvf.Float32(f) {
d.d.errorf("binc: float32 overflow: %v", f)
return
f = float64(d.DecodeInt64())
}
d.bdRead = false
return
@ -633,7 +622,8 @@ func (d *bincDecDriver) decLenNumber() (v uint64) {
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 {
d.readNextBd()
}
@ -641,7 +631,7 @@ func (d *bincDecDriver) decStringAndBytes(bs []byte, withString, zerocopy bool)
d.bdRead = false
return
}
var slen int = -1
var slen = -1
// var ok bool
switch d.vd {
case bincVdString, bincVdByteArray:
@ -743,6 +733,11 @@ func (d *bincDecDriver) DecodeBytes(bs []byte, zerocopy bool) (bsOut []byte) {
d.bdRead = false
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
if d.vd == bincVdString || d.vd == bincVdByteArray {
clen = d.decLen()
@ -863,8 +858,8 @@ func (d *bincDecDriver) DecodeNaked() {
n.v = valueTypeBytes
n.l = d.DecodeBytes(nil, false)
case bincVdTimestamp:
n.v = valueTypeTimestamp
tt, err := decodeTime(d.r.readx(int(d.vs)))
n.v = valueTypeTime
tt, err := bincDecodeTime(d.r.readx(int(d.vs)))
if err != nil {
panic(err)
}
@ -912,27 +907,50 @@ type BincHandle struct {
BasicHandle
binaryEncodingType
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) {
return h.SetExt(rt, tag, &setExtWrapper{b: ext})
return h.SetExt(rt, tag, &extWrapper{ext, interfaceExtFailer{}})
}
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 {
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() {
e.w = e.e.w
e.s = 0
e.c = 0
e.m = nil
}
@ -942,5 +960,165 @@ func (d *bincDecDriver) reset() {
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 _ 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.
package codec
@ -6,6 +6,7 @@ package codec
import (
"math"
"reflect"
"time"
)
const (
@ -38,6 +39,8 @@ const (
cborBdBreak = 0xff
)
// These define some in-stream descriptors for
// manual encoding e.g. when doing explicit indefinite-length
const (
CborStreamBytes byte = 0x5f
CborStreamString = 0x7f
@ -67,6 +70,7 @@ type cborEncDriver struct {
w encWriter
h *CborHandle
x [8]byte
_ [3]uint64 // padding
}
func (e *cborEncDriver) EncodeNil() {
@ -124,6 +128,24 @@ func (e *cborEncDriver) encLen(bd byte, length int) {
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) {
e.encUint(uint64(xtag), cborBaseTag)
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) {
e.encStringBytesS(cborBaseString, v)
}
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))
} else {
e.encStringBytesS(cborBaseString, stringView(v))
@ -223,16 +243,17 @@ func (e *cborEncDriver) encStringBytesS(bb byte, v string) {
// ----------------------
type cborDecDriver struct {
d *Decoder
h *CborHandle
r decReader
b [scratchByteArrayLen]byte
d *Decoder
h *CborHandle
r decReader
// b [scratchByteArrayLen]byte
br bool // bytes reader
bdRead bool
bd byte
noBuiltInTypes
// decNoSeparator
decDriverNoopContainerReader
_ [3]uint64 // padding
}
func (d *cborDecDriver) readNextBd() {
@ -261,9 +282,10 @@ func (d *cborDecDriver) ContainerType() (vt valueType) {
return valueTypeArray
} else if d.bd == cborBdIndefiniteMap || (d.bd >= cborBaseMap && d.bd < cborBaseTag) {
return valueTypeMap
} else {
// d.d.errorf("isContainerType: unsupported parameter: %v", vt)
}
// else {
// d.d.errorf("isContainerType: unsupported parameter: %v", vt)
// }
return valueTypeUnset
}
@ -326,46 +348,30 @@ func (d *cborDecDriver) decCheckInteger() (neg bool) {
return
}
func (d *cborDecDriver) DecodeInt(bitsize uint8) (i int64) {
func (d *cborDecDriver) DecodeInt64() (i int64) {
neg := d.decCheckInteger()
ui := d.decUint()
// check if this number can be converted to an int without overflow
var overflow bool
if neg {
if i, overflow = chkOvf.SignedInt(ui + 1); overflow {
d.d.errorf("cbor: overflow converting %v to signed integer", ui+1)
return
}
i = -i
i = -(chkOvf.SignedIntV(ui + 1))
} else {
if i, overflow = chkOvf.SignedInt(ui); overflow {
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
i = chkOvf.SignedIntV(ui)
}
d.bdRead = false
return
}
func (d *cborDecDriver) DecodeUint(bitsize uint8) (ui uint64) {
func (d *cborDecDriver) DecodeUint64() (ui uint64) {
if d.decCheckInteger() {
d.d.errorf("Assigning negative signed value to unsigned type")
return
}
ui = d.decUint()
if chkOvf.Uint(ui, bitsize) {
d.d.errorf("cbor: overflow integer: %v", ui)
return
}
d.bdRead = false
return
}
func (d *cborDecDriver) DecodeFloat(chkOverflow32 bool) (f float64) {
func (d *cborDecDriver) DecodeFloat64() (f float64) {
if !d.bdRead {
d.readNextBd()
}
@ -376,15 +382,11 @@ func (d *cborDecDriver) DecodeFloat(chkOverflow32 bool) (f float64) {
} else if bd == cborBdFloat64 {
f = math.Float64frombits(bigen.Uint64(d.r.readx(8)))
} else if bd >= cborBaseUint && bd < cborBaseBytes {
f = float64(d.DecodeInt(64))
f = float64(d.DecodeInt64())
} else {
d.d.errorf("Float only valid from float16/32/64: Invalid descriptor: %v", bd)
return
}
if chkOverflow32 && chkOvf.Float32(f) {
d.d.errorf("cbor: float32 overflow: %v", f)
return
}
d.bdRead = false
return
}
@ -438,7 +440,8 @@ func (d *cborDecDriver) decAppendIndefiniteBytes(bs []byte) []byte {
break
}
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
}
n := d.decLen()
@ -470,28 +473,82 @@ func (d *cborDecDriver) DecodeBytes(bs []byte, zerocopy bool) (bsOut []byte) {
if d.bd == cborBdIndefiniteBytes || d.bd == cborBdIndefiniteString {
d.bdRead = false
if bs == nil {
if zerocopy {
return d.decAppendIndefiniteBytes(d.d.b[:0])
}
return d.decAppendIndefiniteBytes(zeroByteSlice)
}
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()
d.bdRead = false
if zerocopy {
if d.br {
return d.r.readx(clen)
} 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) {
return string(d.DecodeBytes(d.b[:], true))
return string(d.DecodeBytes(d.d.b[:], true))
}
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) {
@ -534,12 +591,9 @@ func (d *cborDecDriver) DecodeNaked() {
case cborBdTrue:
n.v = valueTypeBool
n.b = true
case cborBdFloat16, cborBdFloat32:
case cborBdFloat16, cborBdFloat32, cborBdFloat64:
n.v = valueTypeFloat
n.f = d.DecodeFloat(true)
case cborBdFloat64:
n.v = valueTypeFloat
n.f = d.DecodeFloat(false)
n.f = d.DecodeFloat64()
case cborBdIndefiniteBytes:
n.v = valueTypeBytes
n.l = d.DecodeBytes(nil, false)
@ -557,14 +611,14 @@ func (d *cborDecDriver) DecodeNaked() {
case d.bd >= cborBaseUint && d.bd < cborBaseNegInt:
if d.h.SignedInteger {
n.v = valueTypeInt
n.i = d.DecodeInt(64)
n.i = d.DecodeInt64()
} else {
n.v = valueTypeUint
n.u = d.DecodeUint(64)
n.u = d.DecodeUint64()
}
case d.bd >= cborBaseNegInt && d.bd < cborBaseBytes:
n.v = valueTypeInt
n.i = d.DecodeInt(64)
n.i = d.DecodeInt64()
case d.bd >= cborBaseBytes && d.bd < cborBaseString:
n.v = valueTypeBytes
n.l = d.DecodeBytes(nil, false)
@ -581,6 +635,11 @@ func (d *cborDecDriver) DecodeNaked() {
n.v = valueTypeExt
n.u = d.decUint()
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.d.decode(&re.Value) // handled by decode itself.
// 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.
// 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.
//
// 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"
//
// - timestamp, BigNum, BigFloat, Decimals,
// - Encoded Text (e.g. URL, regexp, base64, MIME Message), etc.
type CborHandle struct {
binaryEncodingType
noElemSeparators
@ -635,10 +679,20 @@ type CborHandle struct {
// IndefiniteLength=true, means that we encode using indefinitelength
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) {
return h.SetExt(rt, tag, &setExtWrapper{i: ext})
return h.SetExt(rt, tag, &extWrapper{bytesExtFailer{}, ext})
}
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.
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.
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 (
"bufio"
"bytes"
"encoding/gob"
"fmt"
"io"
"io/ioutil"
"math"
"math/rand"
@ -49,10 +35,34 @@ func init() {
// )
}
type testCustomStringT string
// make this a mapbyslice
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
@ -102,6 +112,9 @@ var (
testRpcInt = new(TestRpcInt)
)
var wrapInt64Typ = reflect.TypeOf(wrapInt64(0))
var wrapBytesTyp = reflect.TypeOf(wrapBytes(nil))
func testByteBuf(in []byte) *bytes.Buffer {
return bytes.NewBuffer(in)
}
@ -179,23 +192,27 @@ type TestRawValue struct {
I int
}
// ----
type testUnixNanoTimeExt struct {
// 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) ReadExt(interface{}, []byte) { panic("unsupported") }
func (x *testUnixNanoTimeExt) WriteExt(v interface{}) []byte {
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{} {
switch v2 := v.(type) {
case time.Time:
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
v2 := v.(*time.Time) // structs are encoded by passing the ptr
return v2.UTC().UnixNano()
}
func (x *testUnixNanoTimeExt) UpdateExt(dest interface{}, v interface{}) {
@ -203,12 +220,8 @@ func (x *testUnixNanoTimeExt) UpdateExt(dest interface{}, v interface{}) {
switch v2 := v.(type) {
case int64:
*tt = time.Unix(0, v2).UTC()
case *int64:
*tt = time.Unix(0, *v2).UTC()
case uint64:
*tt = time.Unix(0, int64(v2)).UTC()
case *uint64:
*tt = time.Unix(0, int64(*v2)).UTC()
//case float64:
//case string:
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,
fn func([]byte) *bytes.Buffer, h Handle) (bs []byte, err error) {
return sTestCodecEncode(ts, bsIn, fn, h, h.getBasicHandle())
@ -258,8 +356,8 @@ func testInit() {
// pre-fill them first
bh.EncodeOptions = testEncodeOptions
bh.DecodeOptions = testDecodeOptions
// bh.InterfaceReset = true // TODO: remove
// bh.PreferArrayOverSlice = true // TODO: remove
// bh.InterfaceReset = true
// bh.PreferArrayOverSlice = true
// modify from flag'ish things
bh.InternString = testInternStr
bh.Canonical = testCanonical
@ -270,31 +368,59 @@ func testInit() {
testMsgpackH.RawToString = true
// testMsgpackH.AddExt(byteSliceTyp, 0, testMsgpackH.BinaryEncodeExt, testMsgpackH.BinaryDecodeExt)
// testMsgpackH.AddExt(timeTyp, 1, testMsgpackH.TimeEncodeExt, testMsgpackH.TimeDecodeExt)
var tTimeExt timeExt
var tBytesExt wrapBytesExt
var tI64Ext wrapInt64Ext
// add extensions for msgpack, simple for time.Time, so we can encode/decode same way.
// use different flavors of XXXExt calls, including deprecated ones.
// NOTE:
// DO NOT set extensions for JsonH, so we can test json(M|Unm)arshal support.
// create legacy functions suitable for deprecated AddExt functionality,
// and use on some places for testSimpleH e.g. for time.Time and wrapInt64
var (
timeExtEncFn = func(rv reflect.Value) (bs []byte, err error) {
defer panicToErr(&err)
bs = timeExt{}.WriteExt(rv.Interface())
myExtEncFn = func(x BytesExt, rv reflect.Value) (bs []byte, err error) {
defer panicToErr(errstrDecoratorDef{}, &err)
bs = x.WriteExt(rv.Interface())
return
}
timeExtDecFn = func(rv reflect.Value, bs []byte) (err error) {
defer panicToErr(&err)
timeExt{}.ReadExt(rv.Interface(), bs)
myExtDecFn = func(x BytesExt, rv reflect.Value, bs []byte) (err error) {
defer panicToErr(errstrDecoratorDef{}, &err)
x.ReadExt(rv.Interface(), bs)
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
testMsgpackH.SetBytesExt(timeTyp, 1, timeExt{})
testCborH.SetInterfaceExt(timeTyp, 1, &testUnixNanoTimeExt{})
chkErr(testMsgpackH.SetBytesExt(timeTyp, 1, timeExt{}))
chkErr(testCborH.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 := []interface{}{
int8(-8),
@ -427,6 +553,8 @@ func testTableVerify(f testVerifyFlag, h Handle) (av []interface{}) {
av[i] = testVerifyVal(v, f, h)
case map[interface{}]interface{}:
av[i] = testVerifyVal(v, f, h)
case time.Time:
av[i] = testVerifyVal(v, f, h)
default:
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 floats are float64
_, isMsgp := h.(*MsgpackHandle)
_, isCbor := h.(*CborHandle)
switch iv := v.(type) {
case int8:
v2 = testVerifyValInt(int64(iv), isMsgp)
@ -545,6 +674,11 @@ func testVerifyVal(v interface{}, f testVerifyFlag, h Handle) (v2 interface{}) {
} else {
v2 = int64(iv2)
}
case isMsgp:
v2 = iv.UTC()
case isCbor:
// fmt.Printf("%%%% cbor verifier\n")
v2 = iv.UTC().Round(time.Microsecond)
default:
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.
func doTestCodecTableOne(t *testing.T, testNil bool, h Handle,
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 {
// logT(t, " v1 returned: %T, %#v", v1, v1)
// //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"+
// " ====> 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, " ....... GOLDEN: (%T) %#v", v0check, v0check)
logT(t, " ....... DECODED: (%T) %#v", v1, v1)
logT(t, " ....... GOLDEN: (%T) %v %#v", v0check, v0check, v0check)
logT(t, " ....... DECODED: (%T) %v %#v", v1, v1, v1)
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")
// failT(t)
// }
var i2 int32 = 0
var i2 int32
testUnmarshalErr(&i2, b, h, t, "int32-ptr")
if i2 != int32(32) {
logT(t, "------- didn't unmarshal to 32: Received: %d", i2)
@ -814,6 +959,19 @@ func testCodecMiscOne(t *testing.T, h Handle) {
var ya = ystruct{}
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) {
@ -1001,7 +1159,7 @@ func testCodecRpcOne(t *testing.T, rr Rpc, h Handle, doRequest bool, exitSleepMs
// opts.MapType = mapStrIntfTyp
// opts.RawToString = false
serverExitChan := make(chan bool, 1)
var serverExitFlag uint64 = 0
var serverExitFlag uint64
serverFn := func() {
for {
conn1, err1 := ln.Accept()
@ -1015,7 +1173,7 @@ func testCodecRpcOne(t *testing.T, rr Rpc, h Handle, doRequest bool, exitSleepMs
return // exit serverFn goroutine
}
if err1 == nil {
var sc rpc.ServerCodec = rr.ServerCodec(conn1, h)
sc := rr.ServerCodec(testReadWriteCloser(conn1), h)
srv.ServeCodec(sc)
}
}
@ -1066,7 +1224,7 @@ func testCodecRpcOne(t *testing.T, rr Rpc, h Handle, doRequest bool, exitSleepMs
}
if doRequest {
bs := connFn()
cc := rr.ClientCodec(bs, h)
cc := rr.ClientCodec(testReadWriteCloser(bs), h)
clientFn(cc)
}
if exitSleepMs != 0 {
@ -1476,7 +1634,7 @@ func doTestMsgpackRpcSpecGoClientToPythonSvc(t *testing.T) {
bs, err2 = net.Dial("tcp", ":"+openPort)
}
checkErrT(t, err2)
cc := MsgpackSpecRpc.ClientCodec(bs, testMsgpackH)
cc := MsgpackSpecRpc.ClientCodec(testReadWriteCloser(bs), testMsgpackH)
cl := rpc.NewClientWithCodec(cc)
defer cl.Close()
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))
failT(t)
}
d1.setZero(v1)
setZero(v1)
testDeepEqualErr(v1, &TestStrucFlex{}, t, "filled-and-zeroed")
}
func doTestRawExt(t *testing.T, h Handle) {
testOnce.Do(testInitAll)
// return // TODO: need to fix this ...
var b []byte
var v interface{}
var v RawExt // interface{}
_, isJson := h.(*JsonHandle)
_, isCbor := h.(*CborHandle)
isValuer := isJson || isCbor
_ = isValuer
bh := h.getBasicHandle()
// isValuer := isJson || isCbor
// _ = isValuer
for _, r := range []RawExt{
{Tag: 99, Value: "9999", Data: []byte("9999")},
} {
e := NewEncoderBytes(&b, h)
e.MustEncode(&r)
// fmt.Printf(">>>> rawext: isnil? %v, %d - %v\n", b == nil, len(b), b)
d := NewDecoderBytes(b, h)
d.MustDecode(&v)
switch h.(type) {
case *JsonHandle:
testDeepEqualErr(r.Value, v, t, "rawext-json")
var r2 = r
switch {
case isJson:
r2.Tag = 0
r2.Data = nil
case isCbor:
r2.Data = nil
default:
r2 := r
if isValuer {
r2.Data = nil
} else {
r2.Value = nil
}
testDeepEqualErr(v, r2, t, "rawext-default")
r2.Value = nil
}
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) {
@ -1589,6 +1783,7 @@ func doTestMapStructKey(t *testing.T, h Handle) {
}
func doTestDecodeNilMapValue(t *testing.T, handle Handle) {
testOnce.Do(testInitAll)
type Struct 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) {
testOnce.Do(testInitAll)
type Embedded struct {
Field byte
}
@ -1672,6 +1868,7 @@ func doTestEmbeddedFieldPrecedence(t *testing.T, h Handle) {
}
func doTestLargeContainerLen(t *testing.T, h Handle) {
testOnce.Do(testInitAll)
m := make(map[int][]struct{})
for i := range []int{
0, 1,
@ -1691,8 +1888,6 @@ func doTestLargeContainerLen(t *testing.T, h Handle) {
testUnmarshalErr(m2, bs, h, t, "-")
testDeepEqualErr(m, m2, t, "-")
// TODO: skip rest if 32-bit
// do same tests for large strings (encoded as symbols or not)
// skip if 32-bit or not using unsafe mode
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,
// use use flags to switch from symbols to non-symbols
bh := h.getBasicHandle()
oldAsSymbols := bh.AsSymbols
defer func() { bh.AsSymbols = oldAsSymbols }()
hbinc, okbinc := h.(*BincHandle)
if okbinc {
oldAsSymbols := hbinc.AsSymbols
defer func() { hbinc.AsSymbols = oldAsSymbols }()
}
var out []byte = make([]byte, 0, math.MaxUint16*3/2)
var in []byte = make([]byte, math.MaxUint16*3/2)
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)
m1[s1] = true
bh.AsSymbols = AsSymbolNone
if okbinc {
hbinc.AsSymbols = 2
}
out = out[:0]
e.ResetBytes(&out)
e.MustEncode(m1)
@ -1737,15 +1935,17 @@ func doTestLargeContainerLen(t *testing.T, h Handle) {
testUnmarshalErr(m2, out, h, t, "no-symbols")
testDeepEqualErr(m1, m2, t, "no-symbols")
// now, do as symbols
bh.AsSymbols = AsSymbolAll
out = out[:0]
e.ResetBytes(&out)
e.MustEncode(m1)
// bs, _ = testMarshalErr(m1, h, t, "-")
m2 = make(map[string]bool, 1)
testUnmarshalErr(m2, out, h, t, "symbols")
testDeepEqualErr(m1, m2, t, "symbols")
if okbinc {
// now, do as symbols
hbinc.AsSymbols = 1
out = out[:0]
e.ResetBytes(&out)
e.MustEncode(m1)
// bs, _ = testMarshalErr(m1, h, t, "-")
m2 = make(map[string]bool, 1)
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:
v.SetFloat(float64(fneg()) * float64(rand.Float32()))
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:
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) {
testOnce.Do(testInitAll)
// test time which uses the time.go implementation (ie Binc)
var tt, tt2 time.Time
// 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) {
testOnce.Do(testInitAll)
var golden = [...]int64{
0, 1, 22, 333, 4444, 55555, 666666,
// 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) {
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 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) {
testOnce.Do(testInitAll)
v := TestSimplish{
Ii: -794,
Ss: `A Man is
@ -2026,6 +2419,7 @@ after the new line
}
func TestBufioDecReader(t *testing.T) {
testOnce.Do(testInitAll)
// try to read 85 bytes in chunks of 7 at a time.
var s = strings.Repeat("01234'56789 ", 5)
// fmt.Printf("s: %s\n", s)
@ -2092,6 +2486,7 @@ func TestBufioDecReader(t *testing.T) {
// -----------
func TestJsonLargeInteger(t *testing.T) {
testOnce.Do(testInitAll)
for _, i := range []uint8{'L', 'A', 0} {
for _, j := range []interface{}{
int64(1 << 60),
@ -2109,6 +2504,7 @@ func TestJsonLargeInteger(t *testing.T) {
}
func TestJsonInvalidUnicode(t *testing.T) {
testOnce.Do(testInitAll)
var m = map[string]string{
`"\udc49\u0430abc"`: "\uFFFDabc",
`"\udc49\u0430"`: "\uFFFD",
@ -2431,6 +2827,11 @@ func TestCborMammothMapsAndSlices(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)
}
@ -2482,41 +2883,85 @@ func TestSimpleUintToInt(t *testing.T) {
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:
//
// Add Tests for:
// - struct tags:
// on anonymous fields, _struct (all fields), etc
// - 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 Tests for:
// - struct tags: on anonymous fields, _struct (all fields), etc
// - chan to encode and decode (with support for codecgen also)
//
// Add negative tests for failure conditions:
// - bad input with large array length prefix
// Add negative tests for failure conditions:
// - bad input with large array length prefix
//
// msgpack
// - support time as built-in extension:
// see https://github.com/msgpack/msgpack/blob/master/spec.md#timestamp-extension-type
//
// decode.go
// decode.go (standalone)
// - UnreadByte: only 2 states (z.ls = 2 and z.ls = 1) (0 --> 2 --> 1)
// - track
// z.trb: track, stop track, check
// - maptype, slicetype: diff from map[string]intf, map[intf]intf or []intf,
// - PreferArrayOverSlice??? (standalone test)
// - InterfaceReset (standalone test)
// - track: z.trb: track, stop track, check
// - PreferArrayOverSlice???
// - InterfaceReset
// - (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
// - ktypeisintf
// - DeleteOnNilMapValue (standalone test)
// - DeleteOnNilMapValue
// - 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{},
//
// encode.go
// - mapbyslice (for non-fastpath things)
// encode.go (standalone)
// - 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.
// 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
// Code generated - temporary main package for codecgen - DO NOT EDIT.
package main
{{ if .Types }}import "{{ .ImportPath }}"{{ end }}
func main() {
@ -38,6 +40,9 @@ func main() {
// const genFrunPkgTmpl = `//+build codecgen
const genFrunPkgTmpl = `
// Code generated - temporary package for codecgen - DO NOT EDIT.
package {{ $.PackageName }}
import (
@ -50,20 +55,33 @@ import (
)
func CodecGenTempWrite{{ .RandString }}() {
os.Remove("{{ .OutFile }}")
fout, err := os.Create("{{ .OutFile }}")
if err != nil {
panic(err)
}
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 }}
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 }}
{{ 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())
// println("... lengths: before formatting: ", len(out.Bytes()), ", after formatting", len(bout))
if err != nil {
fout.Write(out.Bytes())
panic(err)
@ -98,8 +116,7 @@ func Generate(outfile, buildTag, codecPkgPath string,
}
if uid < 0 {
uid = -uid
}
if uid == 0 {
} else if uid == 0 {
rr := rand.New(rand.NewSource(time.Now().UnixNano()))
uid = 101 + rr.Int63n(9777)
}
@ -124,6 +141,7 @@ func Generate(outfile, buildTag, codecPkgPath string,
BuildTag string
StructTags string
Types []string
AllFilesSize int64
CodecPkgFiles bool
NoExtensions bool
}
@ -145,11 +163,17 @@ func Generate(outfile, buildTag, codecPkgPath string,
tv.ImportPath = stripVendor(tv.ImportPath)
}
astfiles := make([]*ast.File, len(infiles))
var fi os.FileInfo
for i, infile := range infiles {
if filepath.Dir(infile) != lastdir {
err = errors.New("in files must all be in same directory as outfile")
return
}
if fi, err = os.Stat(infile); err != nil {
return
}
tv.AllFilesSize += fi.Size()
fset := token.NewFileSet()
astfiles[i], err = parser.ParseFile(fset, infile, nil, 0)
if err != nil {
@ -211,6 +235,10 @@ func Generate(outfile, buildTag, codecPkgPath string,
// chan: ChanType
// do not generate:
// 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) {
case *ast.StructType, *ast.Ident, *ast.MapType, *ast.ArrayType, *ast.ChanType:
// 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)
if err = t.Execute(bw, tv); err != nil {
bw.Flush()
return
}
if err = bw.Flush(); err != nil {
@ -317,14 +346,14 @@ func main() {
rt := flag.String("rt", "", "tags for go run")
st := flag.String("st", "codec,json", "struct tag keys to introspect")
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")
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()
if err := Generate(*o, *t, *c, *d, *rt, *st,
regexp.MustCompile(*r), regexp.MustCompile(*nr), !*x, *nx,
flag.Args()...); err != nil {
err := Generate(*o, *t, *c, *d, *rt, *st,
regexp.MustCompile(*r), regexp.MustCompile(*nr), !*x, *nx, flag.Args()...)
if err != nil {
fmt.Fprintf(os.Stderr, "codecgen error: %v\n", err)
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.
// Use of this source code is governed by a MIT license found in the LICENSE file.
// ************************************************************
// DO NOT EDIT.
// THIS FILE IS AUTO-GENERATED from fast-path.go.tmpl
// ************************************************************
// Code generated from fast-path.go.tmpl - DO NOT EDIT.
package codec
@ -18,19 +15,19 @@ package codec
// This file can be omitted without causing a build failure.
//
// The advantage of fast paths is:
// - Many calls bypass reflection altogether
// - Many calls bypass reflection altogether
//
// Currently support
// - slice of all builtin types,
// - map of all builtin types to string or interface value
// - symmetrical maps of all builtin types (e.g. str-str, uint8-uint8)
// - slice of all builtin types,
// - map of all builtin types to string or interface value
// - symmetrical maps of all builtin types (e.g. str-str, uint8-uint8)
// This should provide adequate "typical" implementations.
//
// Note that fast track decode functions must handle values for which an address cannot be obtained.
// For example:
// m2 := map[string]int{}
// p2 := []interface{}{m2}
// // decoding into p2 will bomb if fast track functions do not treat like unaddressable.
// m2 := map[string]int{}
// p2 := []interface{}{m2}
// // decoding into p2 will bomb if fast track functions do not treat like unaddressable.
//
import (
@ -80,26 +77,19 @@ var fastpathAV fastpathA
// due to possible initialization loop error, make fastpath in an init()
func init() {
if useLookupRecognizedTypes && recognizedRtidsLoaded {
panic("recognizedRtidsLoaded = true - cannot happen")
}
i := 0
fn := func(v interface{},
fe func(*Encoder, *codecFnInfo, reflect.Value),
fd func(*Decoder, *codecFnInfo, reflect.Value)) (f fastpathE) {
xrt := reflect.TypeOf(v)
xptr := rt2id(xrt)
if useLookupRecognizedTypes {
recognizedRtids = append(recognizedRtids, xptr)
recognizedRtidPtrs = append(recognizedRtidPtrs, rt2id(reflect.PtrTo(xrt)))
}
fastpathAV[i] = fastpathE{xptr, xrt, fe, fd}
i++
return
}
{{range .Values}}{{if not .Primitive}}{{if not .MapKey }}
fn([]{{ .Elem }}(nil), (*Encoder).{{ .MethodNamePfx "fastpathEnc" false }}R, (*Decoder).{{ .MethodNamePfx "fastpathDec" false }}R){{end}}{{end}}{{end}}
{{/* do not register []uint8 in fast-path */}}
{{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}}{{end}}
{{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}}
@ -112,22 +102,37 @@ func init() {
// -- -- fast path type switch
func fastpathEncodeTypeSwitch(iv interface{}, e *Encoder) bool {
switch v := iv.(type) {
{{range .Values}}{{if not .Primitive}}{{if not .MapKey }}
case []{{ .Elem }}:{{else}}
case map[{{ .MapKey }}]{{ .Elem }}:{{end}}
fastpathTV.{{ .MethodNamePfx "Enc" false }}V(v, e){{if not .MapKey }}
case *[]{{ .Elem }}:{{else}}
case *map[{{ .MapKey }}]{{ .Elem }}:{{end}}
fastpathTV.{{ .MethodNamePfx "Enc" false }}V(*v, e)
{{end}}{{end}}
{{range .Values}}{{if not .Primitive}}{{if not .MapKey }}{{if ne .Elem "uint8"}}
case []{{ .Elem }}:
fastpathTV.{{ .MethodNamePfx "Enc" false }}V(v, e)
case *[]{{ .Elem }}:
fastpathTV.{{ .MethodNamePfx "Enc" false }}V(*v, e){{/*
*/}}{{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:
_ = 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 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 {
switch v := iv.(type) {
{{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)
{{end}}{{end}}{{end}}
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 true
@ -152,16 +157,23 @@ func fastpathEncodeTypeSwitchMap(iv interface{}, e *Encoder) bool {
fastpathTV.{{ .MethodNamePfx "Enc" false }}V(*v, e)
{{end}}{{end}}{{end}}
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 true
}
**** removing this block, as they are never called directly ****
**** removing this block, as they are never called directly ****
*/}}
// -- -- fast path functions
{{range .Values}}{{if not .Primitive}}{{if not .MapKey }}
func (e *Encoder) {{ .MethodNamePfx "fastpathEnc" false }}R(f *codecFnInfo, rv reflect.Value) {
if f.ti.mbs {
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 }
ee, esep := e.e, e.hh.hasElemSeparators()
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 {
if esep { ee.WriteArrayElem() }
{{ encmd .Elem "v2"}}
}
} */}}
ee.WriteArrayEnd()
}
func (_ fastpathT) {{ .MethodNamePfx "EncAsMap" false }}V(v []{{ .Elem }}, e *Encoder) {
ee, esep := e.e, e.hh.hasElemSeparators()
if len(v)%2 == 1 {
@ -187,6 +208,20 @@ func (_ fastpathT) {{ .MethodNamePfx "EncAsMap" false }}V(v []{{ .Elem }}, e *En
return
}
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 {
if esep {
if j%2 == 0 {
@ -196,14 +231,12 @@ func (_ fastpathT) {{ .MethodNamePfx "EncAsMap" false }}V(v []{{ .Elem }}, e *En
}
}
{{ encmd .Elem "v2"}}
}
} */}}
ee.WriteMapEnd()
}
{{end}}{{end}}{{end}}
{{range .Values}}{{if not .Primitive}}{{if .MapKey }}
func (e *Encoder) {{ .MethodNamePfx "fastpathEnc" false }}R(f *codecFnInfo, rv reflect.Value) {
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 }
ee, esep := e.e, e.hh.hasElemSeparators()
ee.WriteMapStart(len(v))
{{if eq .MapKey "string"}}asSymbols := e.h.AsSymbols&AsSymbolMapStringKeysFlag != 0
{{end}}if e.h.Canonical {
if e.h.Canonical {
{{if eq .MapKey "interface{}"}}{{/* out of band
*/}}var mksv []byte = make([]byte, 0, len(v)*16) // temporary byte slice for the encoding
e2 := NewEncoderBytes(&mksv, e.hh)
@ -228,76 +260,126 @@ func (_ fastpathT) {{ .MethodNamePfx "Enc" false }}V(v map[{{ .MapKey }}]{{ .Ele
i++
}
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 {
if esep { ee.WriteMapElemKey() }
e.asis(v2[j].v)
if esep { ee.WriteMapElemValue() }
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
for k, _ := range v {
v2[i] = {{ $x }}(k)
i++
}
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 {
if esep { ee.WriteMapElemKey() }
{{if eq .MapKey "string"}}if asSymbols {
ee.EncodeSymbol(k2)
} else {
ee.EncodeString(c_UTF8, k2)
}{{else}}{{ $y := printf "%s(k2)" .MapKey }}{{ encmd .MapKey $y }}{{end}}
{{if eq .MapKey "string"}}ee.EncodeString(cUTF8, k2){{else}}{{ $y := printf "%s(k2)" .MapKey }}{{ encmd .MapKey $y }}{{end}}
if esep { ee.WriteMapElemValue() }
{{ $y := printf "v[%s(k2)]" .MapKey }}{{ encmd .Elem $y }}
} {{end}}
} */}} {{end}}
} 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 {
if esep { ee.WriteMapElemKey() }
{{if eq .MapKey "string"}}if asSymbols {
ee.EncodeSymbol(k2)
} else {
ee.EncodeString(c_UTF8, k2)
}{{else}}{{ encmd .MapKey "k2"}}{{end}}
{{if eq .MapKey "string"}}ee.EncodeString(cUTF8, k2){{else}}{{ encmd .MapKey "k2"}}{{end}}
if esep { ee.WriteMapElemValue() }
{{ encmd .Elem "v2"}}
}
} */}}
}
ee.WriteMapEnd()
}
{{end}}{{end}}{{end}}
// -- decode
// -- -- fast path type switch
func fastpathDecodeTypeSwitch(iv interface{}, d *Decoder) bool {
var changed bool
switch v := iv.(type) {
{{range .Values}}{{if not .Primitive}}{{if not .MapKey }}
case []{{ .Elem }}:{{else}}
case map[{{ .MapKey }}]{{ .Elem }}:{{end}}
fastpathTV.{{ .MethodNamePfx "Dec" false }}V(v, false, d){{if not .MapKey }}
case *[]{{ .Elem }}: {{else}}
case *map[{{ .MapKey }}]{{ .Elem }}: {{end}}
if v2, changed2 := fastpathTV.{{ .MethodNamePfx "Dec" false }}V(*v, true, d); changed2 {
*v = v2
{{range .Values}}{{if not .Primitive}}{{if not .MapKey }}{{if ne .Elem "uint8"}}
case []{{ .Elem }}:
var v2 []{{ .Elem }}
v2, changed = fastpathTV.{{ .MethodNamePfx "Dec" false }}V(v, false, d)
if changed && len(v) > 0 && len(v2) > 0 && !(len(v2) == len(v) && &v2[0] == &v[0]) {
copy(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:
_ = 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 true
}
func fastpathDecodeSetZeroTypeSwitch(iv interface{}, d *Decoder) bool {
func fastpathDecodeSetZeroTypeSwitch(iv interface{}) bool {
switch v := iv.(type) {
{{range .Values}}{{if not .Primitive}}{{if not .MapKey }}
case *[]{{ .Elem }}: {{else}}
case *map[{{ .MapKey }}]{{ .Elem }}: {{end}}
*v = nil
{{end}}{{end}}
case *[]{{ .Elem }}:
*v = nil {{/*
*/}}{{end}}{{end}}{{end}}
{{range .Values}}{{if not .Primitive}}{{if .MapKey }}
case *map[{{ .MapKey }}]{{ .Elem }}:
*v = nil {{/*
*/}}{{end}}{{end}}{{end}}
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 true
@ -311,38 +393,36 @@ Slices can change if they
- are addressable (from a ptr)
- 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 {
var vp = rv2i(rv).(*[]{{ .Elem }})
if v, changed := fastpathTV.{{ .MethodNamePfx "Dec" false }}V(*vp, !array, d); changed {
*vp = v
}
vp := rv2i(rv).(*[]{{ .Elem }})
v, changed := fastpathTV.{{ .MethodNamePfx "Dec" false }}V(*vp, !array, d)
if changed { *vp = v }
} 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) {
if v, changed := f.{{ .MethodNamePfx "Dec" false }}V(*vp, true, d); changed {
*vp = v
}
v, changed := f.{{ .MethodNamePfx "Dec" false }}V(*vp, true, d)
if changed { *vp = v }
}
func (_ fastpathT) {{ .MethodNamePfx "Dec" false }}V(v []{{ .Elem }}, canChange bool, d *Decoder) (_ []{{ .Elem }}, changed bool) {
dd := d.d
{{/* // if dd.isContainerType(valueTypeNil) { dd.TryDecodeAsNil() */}}
dd := d.d{{/*
// if dd.isContainerType(valueTypeNil) { dd.TryDecodeAsNil()
*/}}
slh, containerLenS := d.decSliceHelperStart()
if containerLenS == 0 {
if canChange {
if v == nil {
v = []{{ .Elem }}{}
} else if len(v) != 0 {
v = v[:0]
}
if v == nil { v = []{{ .Elem }}{} } else if len(v) != 0 { v = v[:0] }
changed = true
}
slh.End()
return v, changed
}
hasLen := containerLenS > 0
var xlen int
if hasLen && canChange {
@ -361,7 +441,7 @@ func (_ fastpathT) {{ .MethodNamePfx "Dec" false }}V(v []{{ .Elem }}, canChange
}
j := 0
for ; (hasLen && j < containerLenS) || !(hasLen || dd.CheckBreak()); j++ {
if j == 0 && len(v) == 0 {
if j == 0 && len(v) == 0 && canChange {
if hasLen {
xlen = decInferLen(containerLenS, d.h.MaxInitLen, {{ .Size }})
} else {
@ -380,10 +460,12 @@ func (_ fastpathT) {{ .MethodNamePfx "Dec" false }}V(v []{{ .Elem }}, canChange
d.arrayCannotExpand(len(v), j+1)
decodeIntoBlank = true
}
}
}
slh.ElemContainerState(j)
if decodeIntoBlank {
d.swallow()
} else if dd.TryDecodeAsNil() {
v[j] = {{ zerocmd .Elem }}
} else {
{{ 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()
return v, changed
}
{{end}}{{end}}{{end}}
{{range .Values}}{{if not .Primitive}}{{if .MapKey }}
{{/*
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) {
if rv.Kind() == reflect.Ptr {
vp := rv2i(rv).(*map[{{ .MapKey }}]{{ .Elem }})
if v, changed := fastpathTV.{{ .MethodNamePfx "Dec" false }}V(*vp, true, d); changed {
*vp = v
}
return
}
fastpathTV.{{ .MethodNamePfx "Dec" false }}V(rv2i(rv).(map[{{ .MapKey }}]{{ .Elem }}), false, d)
v, changed := fastpathTV.{{ .MethodNamePfx "Dec" false }}V(*vp, true, d);
if changed { *vp = v }
} else {
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) {
if v, changed := f.{{ .MethodNamePfx "Dec" false }}V(*vp, true, d); changed {
*vp = v
}
v, changed := f.{{ .MethodNamePfx "Dec" false }}V(*vp, true, d)
if changed { *vp = v }
}
func (_ fastpathT) {{ .MethodNamePfx "Dec" false }}V(v map[{{ .MapKey }}]{{ .Elem }}, canChange bool,
d *Decoder) (_ map[{{ .MapKey }}]{{ .Elem }}, changed bool) {
dd, esep := d.d, d.hh.hasElemSeparators()
{{/* // if dd.isContainerType(valueTypeNil) {dd.TryDecodeAsNil() */}}
dd, esep := d.d, d.hh.hasElemSeparators(){{/*
// if dd.isContainerType(valueTypeNil) {dd.TryDecodeAsNil()
*/}}
containerLen := dd.ReadMapStart()
if canChange && v == nil {
xlen := decInferLen(containerLen, d.h.MaxInitLen, {{ .Size }})
@ -439,8 +518,8 @@ func (_ fastpathT) {{ .MethodNamePfx "Dec" false }}V(v map[{{ .MapKey }}]{{ .Ele
dd.ReadMapEnd()
return v, changed
}
{{ if eq .Elem "interface{}" }}mapGet := !d.h.MapValueReset && !d.h.InterfaceReset{{end}}
var mk {{ .MapKey }}
{{ if eq .Elem "interface{}" }}mapGet := v != nil && !d.h.MapValueReset && !d.h.InterfaceReset
{{end}}var mk {{ .MapKey }}
var mv {{ .Elem }}
hasLen := containerLen > 0
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 }}
if esep { dd.ReadMapElemValue() }
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
}
{{ if eq .Elem "interface{}" }}if mapGet { mv = v[mk] } else { mv = nil }
d.decode(&mv){{ else }}mv = {{ decmd .Elem }}{{ end }}
if v != nil {
v[mk] = mv
}
if v != nil { v[mk] = mv }
}
dd.ReadMapEnd()
return v, changed
}
{{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
package codec
@ -14,11 +17,11 @@ const fastpathEnabled = false
// This tag disables fastpath during build, allowing for faster build, test execution,
// short-program runs, etc.
func fastpathDecodeTypeSwitch(iv interface{}, d *Decoder) bool { return false }
func fastpathEncodeTypeSwitch(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 fastpathDecodeSetZeroTypeSwitch(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 fastpathEncodeTypeSwitchSlice(iv interface{}, e *Encoder) bool { return false }
func fastpathEncodeTypeSwitchMap(iv interface{}, e *Encoder) bool { return false }
func fastpathDecodeSetZeroTypeSwitch(iv interface{}) bool { return false }
type fastpathT struct{}
type fastpathE struct {
@ -31,6 +34,12 @@ type fastpathA [0]fastpathE
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 fastpathTV fastpathT

View File

@ -43,7 +43,7 @@ if {{var "l"}} == 0 {
{{var "c"}} = true
}{{end}}
{{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 }}
{{ decLineVar $x }}
{{var "v"}} <- {{ $x }}

View File

@ -3,10 +3,7 @@
// 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.
// ************************************************************
// DO NOT EDIT.
// THIS FILE IS AUTO-GENERATED from gen-helper.go.tmpl
// ************************************************************
// Code generated from gen-helper.go.tmpl - DO NOT EDIT.
package codec
@ -31,25 +28,73 @@ const GenVersion = 8
// 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.
func GenHelperEncoder(e *Encoder) (genHelperEncoder, encDriver) {
return genHelperEncoder{e: e}, e.e
func GenHelperEncoder(e *Encoder) (ge genHelperEncoder, ee genHelperEncDriver) {
ge = genHelperEncoder{e: e}
ee = genHelperEncDriver{encDriver: e.e}
return
}
// 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.
func GenHelperDecoder(d *Decoder) (genHelperDecoder, decDriver) {
return genHelperDecoder{d: d}, d.d
func GenHelperDecoder(d *Decoder) (gd genHelperDecoder, dd genHelperDecDriver) {
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*
type genHelperEncoder struct {
M must
e *Encoder
F fastpathT
}
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
type genHelperDecoder struct {
C checkOverflow
d *Decoder
F fastpathT
}
@ -61,7 +106,12 @@ func (f genHelperEncoder) EncBasicHandle() *BasicHandle {
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
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*
@ -74,52 +124,65 @@ func (f genHelperEncoder) EncFallback(iv interface{}) {
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
func (f genHelperEncoder) EncTextMarshal(iv encoding.TextMarshaler) {
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*
func (f genHelperEncoder) EncJSONMarshal(iv jsonMarshaler) {
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*
func (f genHelperEncoder) EncBinaryMarshal(iv encoding.BinaryMarshaler) {
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*
func (f genHelperEncoder) EncRaw(iv Raw) {
f.e.rawBytes(iv)
func (f genHelperEncoder) EncRaw(iv Raw) { 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*
func (f genHelperEncoder) TimeRtidIfBinc() uintptr {
if _, ok := f.e.hh.(*BincHandle); ok {
return timeTypId
}
return 0
func (f genHelperEncoder) Extension(rtid uintptr) (xfn *extTypeTagFn) {
return f.e.h.getExt(rtid)
}
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
func (f genHelperEncoder) IsJSONHandle() bool {
return f.e.cf.js
func (f genHelperEncoder) EncExtension(v interface{}, xfFn *extTypeTagFn) {
f.e.e.EncodeExt(v, xfFn.tag, xfFn.ext, f.e)
}
// 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 {
return len(f.e.h.extHandle) != 0
}
// 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) {
rt := reflect.TypeOf(v)
if rt.Kind() == reflect.Ptr {
rt = rt.Elem()
}
rtid := rt2id(rt)
if xfFn := f.e.h.getExt(rtid); xfFn != nil {
if xfFn := f.e.h.getExt(i2rtid(v)); xfFn != nil {
f.e.e.EncodeExt(v, xfFn.tag, xfFn.ext, f.e)
return true
}
@ -139,15 +202,18 @@ func (f genHelperDecoder) DecBinary() bool {
}
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
func (f genHelperDecoder) DecSwallow() {
f.d.swallow()
}
func (f genHelperDecoder) DecSwallow() { f.d.swallow() }
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
func (f genHelperDecoder) DecScratchBuffer() []byte {
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*
func (f genHelperDecoder) DecFallback(iv interface{}, chkPtr bool) {
// println(">>>>>>>>> DecFallback")
@ -155,7 +221,7 @@ func (f genHelperDecoder) DecFallback(iv interface{}, chkPtr bool) {
if chkPtr {
rv = f.d.ensureDecodeable(rv)
}
f.d.decodeValue(rv, nil, false, false)
f.d.decodeValue(rv, nil, false)
// 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*
func (f genHelperDecoder) DecRaw() []byte {
return f.d.rawBytes()
}
func (f genHelperDecoder) DecRaw() []byte { return f.d.rawBytes() }
// 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 {
return timeTypId
}
return 0
}
//
// 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 genHelperDecoder) TimeRtidIfBinc() (v uintptr) { return }
// 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*
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*
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 {
return len(f.d.h.extHandle) != 0
}
// 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) {
rt := reflect.TypeOf(v).Elem()
rtid := rt2id(rt)
if xfFn := f.d.h.getExt(rtid); xfFn != nil {
if xfFn := f.d.h.getExt(i2rtid(v)); xfFn != nil {
f.d.d.DecodeExt(v, xfFn.tag, xfFn.ext)
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*
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.
// Use of this source code is governed by a MIT license found in the LICENSE file.
// ************************************************************
// DO NOT EDIT.
// THIS FILE IS AUTO-GENERATED from gen-helper.go.tmpl
// ************************************************************
// Code generated from gen-helper.go.tmpl - DO NOT EDIT.
package codec
@ -21,35 +18,83 @@ const GenVersion = {{ .Version }}
// 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
// library users. They WILL change continuously and without notice.
//
//
// 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).
//
//
// 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.
// 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.
func GenHelperEncoder(e *Encoder) (genHelperEncoder, encDriver) {
return genHelperEncoder{e:e}, e.e
func GenHelperEncoder(e *Encoder) (ge genHelperEncoder, ee genHelperEncDriver) {
ge = genHelperEncoder{e: e}
ee = genHelperEncDriver{encDriver: e.e}
return
}
// 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.
func GenHelperDecoder(d *Decoder) (genHelperDecoder, decDriver) {
return genHelperDecoder{d:d}, d.d
func GenHelperDecoder(d *Decoder) (gd genHelperDecoder, dd genHelperDecDriver) {
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*
type genHelperEncoder struct {
M must
e *Encoder
F fastpathT
}
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
type genHelperDecoder struct {
C checkOverflow
d *Decoder
F fastpathT
}
@ -58,10 +103,13 @@ type genHelperDecoder struct {
func (f genHelperEncoder) EncBasicHandle() *BasicHandle {
return f.e.h
}
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
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*
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*
func (f genHelperEncoder) EncTextMarshal(iv encoding.TextMarshaler) {
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*
func (f genHelperEncoder) EncJSONMarshal(iv jsonMarshaler) {
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*
func (f genHelperEncoder) EncBinaryMarshal(iv encoding.BinaryMarshaler) {
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*
func (f genHelperEncoder) EncRaw(iv Raw) {
f.e.rawBytes(iv)
func (f genHelperEncoder) EncRaw(iv Raw) { 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*
func (f genHelperEncoder) TimeRtidIfBinc() uintptr {
if _, ok := f.e.hh.(*BincHandle); ok {
return timeTypId
}
return 0
func (f genHelperEncoder) Extension(rtid uintptr) (xfn *extTypeTagFn) {
return f.e.h.getExt(rtid)
}
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
func (f genHelperEncoder) IsJSONHandle() bool {
return f.e.cf.js
func (f genHelperEncoder) EncExtension(v interface{}, xfFn *extTypeTagFn) {
f.e.e.EncodeExt(v, xfFn.tag, xfFn.ext, f.e)
}
// 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 {
return len(f.e.h.extHandle) != 0
}
// 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) {
rt := reflect.TypeOf(v)
if rt.Kind() == reflect.Ptr {
rt = rt.Elem()
}
rtid := rt2id(rt)
if xfFn := f.e.h.getExt(rtid); xfFn != nil {
if xfFn := f.e.h.getExt(i2rtid(v)); xfFn != nil {
f.e.e.EncodeExt(v, xfFn.tag, xfFn.ext, f.e)
return true
}
@ -128,21 +187,23 @@ func (f genHelperDecoder) DecBinary() bool {
return f.d.be // f.d.hh.isBinaryEncoding()
}
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
func (f genHelperDecoder) DecSwallow() {
f.d.swallow()
}
func (f genHelperDecoder) DecSwallow() { f.d.swallow() }
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
func (f genHelperDecoder) DecScratchBuffer() []byte {
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*
func (f genHelperDecoder) DecFallback(iv interface{}, chkPtr bool) {
// println(">>>>>>>>> DecFallback")
rv := reflect.ValueOf(iv)
if chkPtr {
rv = f.d.ensureDecodeable(rv)
}
f.d.decodeValue(rv, nil, false, false)
f.d.decodeValue(rv, nil, false)
// f.d.decodeValueFallback(rv)
}
// 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*
func (f genHelperDecoder) DecRaw() []byte {
return f.d.rawBytes()
}
func (f genHelperDecoder) DecRaw() []byte { return f.d.rawBytes() }
// 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 {
return timeTypId
}
return 0
}
//
// 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 genHelperDecoder) TimeRtidIfBinc() (v uintptr) { return }
// 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*
func (f genHelperDecoder) IsJSONHandle() bool {
return f.d.js
}
// 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 {
return len(f.d.h.extHandle) != 0
}
// 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) {
rt := reflect.TypeOf(v).Elem()
rtid := rt2id(rt)
if xfFn := f.d.h.getExt(rtid); xfFn != nil {
if xfFn := f.d.h.getExt(i2rtid(v)); xfFn != nil {
f.d.d.DecodeExt(v, xfFn.tag, xfFn.ext)
return true
}
@ -214,7 +295,8 @@ func (f genHelperDecoder) DecInferLen(clen, maxlen, unit int) (rvlen int) {
return decInferLen(clen, maxlen, unit)
}
// 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.
// 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
}{{end}}
{{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 }}
{{ decLineVar $x }}
{{var "v"}} <- {{ $x }}
@ -129,4 +131,3 @@ if {{var "l"}} == 0 {
}{{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.
// +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.
// +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.
// +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.
// +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.
// +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.
// +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.
// +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.
// +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.
// +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,
// 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) {
if len(v) < 2 {
} else if pos && v[0] == 0 {
@ -97,21 +30,20 @@ func halfFloatToFloatBits(yy uint16) (d uint32) {
if e == 0 {
if m == 0 { // plu or minus 0
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 {
if m == 0 { // Inf
return (s << 31) | 0x7f800000
} else { // NaN
return (s << 31) | 0x7f800000 | (m << 13)
}
return (s << 31) | 0x7f800000 | (m << 13) // NaN
}
e = e + (127 - 15)
m = m << 13

View File

@ -1,6 +1,6 @@
// +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.
package codec
@ -8,6 +8,7 @@ package codec
import (
"reflect"
"sync/atomic"
"time"
)
const safeMode = true
@ -34,29 +35,10 @@ func bytesView(v string) []byte {
func definitelyNil(v interface{}) bool {
// 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
// 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{} {
return rv.Interface()
}
@ -69,28 +51,62 @@ func rv2rtid(rv reflect.Value) uintptr {
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{}
// func (_ *ptrToRvMap) init() {}
// func (_ *ptrToRvMap) get(i interface{}) reflect.Value {
// func (*ptrToRvMap) init() {}
// func (*ptrToRvMap) get(i interface{}) reflect.Value {
// return reflect.ValueOf(i).Elem()
// }
// --------------------------
type atomicTypeInfoSlice struct {
type atomicTypeInfoSlice struct { // expected to be 2 words
v atomic.Value
}
func (x *atomicTypeInfoSlice) load() *[]rtid2ti {
func (x *atomicTypeInfoSlice) load() []rtid2ti {
i := x.v.Load()
if i == 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)
}
@ -107,56 +123,64 @@ func (d *Decoder) kBool(f *codecFnInfo, rv reflect.Value) {
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) {
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) {
rv.SetFloat(d.d.DecodeFloat(false))
rv.SetFloat(d.d.DecodeFloat64())
}
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) {
rv.SetInt(d.d.DecodeInt(8))
rv.SetInt(chkOvf.IntV(d.d.DecodeInt64(), 8))
}
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) {
rv.SetInt(d.d.DecodeInt(32))
rv.SetInt(chkOvf.IntV(d.d.DecodeInt64(), 32))
}
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) {
rv.SetUint(d.d.DecodeUint(uintBitsize))
rv.SetUint(chkOvf.UintV(d.d.DecodeUint64(), uintBitsize))
}
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) {
rv.SetUint(d.d.DecodeUint(8))
rv.SetUint(chkOvf.UintV(d.d.DecodeUint64(), 8))
}
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) {
rv.SetUint(d.d.DecodeUint(32))
rv.SetUint(chkOvf.UintV(d.d.DecodeUint64(), 32))
}
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())
}
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) {
e.e.EncodeString(c_UTF8, rv.String())
e.e.EncodeString(cUTF8, rv.String())
}
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) {
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 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.
package codec
@ -10,6 +10,7 @@ package codec
import (
"reflect"
"sync/atomic"
"time"
"unsafe"
)
@ -22,12 +23,12 @@ const safeMode = false
const unsafeFlagIndir = 1 << 7 // keep in sync with GO_ROOT/src/reflect/value.go
type unsafeString struct {
Data uintptr
Data unsafe.Pointer
Len int
}
type unsafeSlice struct {
Data uintptr
Data unsafe.Pointer
Len int
Cap int
}
@ -47,20 +48,16 @@ func stringView(v []byte) string {
if len(v) == 0 {
return ""
}
bx := (*unsafeSlice)(unsafe.Pointer(&v))
sx := unsafeString{bx.Data, bx.Len}
return *(*string)(unsafe.Pointer(&sx))
return *(*string)(unsafe.Pointer(&unsafeString{bx.Data, bx.Len}))
}
func bytesView(v string) []byte {
if len(v) == 0 {
return zeroByteSlice
}
sx := (*unsafeString)(unsafe.Pointer(&v))
bx := unsafeSlice{sx.Data, sx.Len, sx.Len}
return *(*[]byte)(unsafe.Pointer(&bx))
return *(*[]byte)(unsafe.Pointer(&unsafeSlice{sx.Data, sx.Len, sx.Len}))
}
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
// 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.
//
// 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
}
// 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{} {
// 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))
// 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 rt2id(rt reflect.Type) uintptr {
@ -113,32 +101,104 @@ func rv2rtid(rv reflect.Value) uintptr {
return uintptr((*unsafeReflectValue)(unsafe.Pointer(&rv)).typ)
}
// 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 i2rtid(i interface{}) uintptr {
return uintptr(((*unsafeIntf)(unsafe.Pointer(&i))).typ)
}
// --------------------------
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) {
atomic.StorePointer(&x.v, unsafe.Pointer(p))
func (x *atomicTypeInfoSlice) load() []rtid2ti {
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) {
urv := (*unsafeReflectValue)(unsafe.Pointer(&rv))
// if urv.flag&unsafeFlagIndir != 0 {
// urv.ptr = *(*unsafe.Pointer)(urv.ptr)
// }
*(*[]byte)(urv.ptr) = d.rawBytes()
}
@ -152,69 +212,78 @@ func (d *Decoder) kBool(f *codecFnInfo, rv reflect.Value) {
*(*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))
*(*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) {
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) {
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) {
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) {
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) {
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) {
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) {
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) {
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) {
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) {
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) {
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) {
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))
}
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) {
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) {
@ -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 {
// return uintptr(((*unsafeIntf)(unsafe.Pointer(&rt))).word)
// // 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.
// Use of this source code is governed by a MIT license found in the LICENSE file.
// ************************************************************
// DO NOT EDIT.
// THIS FILE IS AUTO-GENERATED from mammoth-test.go.tmpl
// ************************************************************
// Code generated from mammoth-test.go.tmpl - DO NOT EDIT.
package codec
import "testing"
import "fmt"
import "reflect"
// TestMammoth has all the different paths optimized in fast-path
// It has all the primitives, slices and maps.
@ -38,36 +36,78 @@ type TestMammoth struct {
}
{{range .Values }}{{if not .Primitive }}{{if not .MapKey }}{{/*
*/}} type {{ .MethodNamePfx "type" false }} []{{ .Elem }}
func (_ {{ .MethodNamePfx "type" false }}) MapBySlice() { }
*/}} type {{ .MethodNamePfx "typMbs" false }} []{{ .Elem }}
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}}
func doTestMammothSlices(t *testing.T, h Handle) {
{{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)
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
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}}")
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")
v{{$i}}v2 = nil
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")
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
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")
v{{$i}}v4 = {{ .MethodNamePfx "type" false }}(v{{$i}}v2)
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")
v{{$i}}v2 = nil
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")
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) {
{{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)
var v{{$i}}v1, v{{$i}}v2 map[{{ .MapKey }}]{{ .Elem }}
v{{$i}}v1 = v
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}}")
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")
v{{$i}}v2 = nil
testUnmarshalErr(&v{{$i}}v2, bs{{$i}}, h, t, "dec-map-v{{$i}}-p")
testDeepEqualErr(v{{$i}}v1, v{{$i}}v2, t, "equal-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-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}}

View File

@ -3,10 +3,7 @@
// 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.
// ************************************************************
// DO NOT EDIT.
// THIS FILE IS AUTO-GENERATED from mammoth2-test.go.tmpl
// ************************************************************
// Code generated from mammoth2-test.go.tmpl - DO NOT EDIT.
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.
// Use of this source code is governed by a MIT license found in the LICENSE file.
// ************************************************************
// DO NOT EDIT.
// THIS FILE IS AUTO-GENERATED from mammoth2-test.go.tmpl
// ************************************************************
// Code generated from mammoth2-test.go.tmpl - DO NOT EDIT.
package codec
@ -74,6 +71,8 @@ type TestMammoth2 struct {
FptrSliceFloat64 *[]float64
FSliceUint []uint
FptrSliceUint *[]uint
FSliceUint8 []uint8
FptrSliceUint8 *[]uint8
FSliceUint16 []uint16
FptrSliceUint16 *[]uint16
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.
/*
@ -15,8 +15,8 @@ For compatibility with behaviour of msgpack-c reference implementation:
- Go intX (<0)
IS ENCODED AS
msgpack -ve fixnum, signed
*/
package codec
import (
@ -25,6 +25,7 @@ import (
"math"
"net/rpc"
"reflect"
"time"
)
const (
@ -78,6 +79,9 @@ const (
mpNegFixNumMax = 0xff
)
var mpTimeExtTag int8 = -1
var mpTimeExtTagU = uint8(mpTimeExtTag)
// MsgpackSpecRpcMultiArgs is a special type which signifies to the MsgpackSpecRpcCodec
// that the backend RPC service takes multiple arguments, which have been arranged
// in sequence in the slice.
@ -94,10 +98,18 @@ type msgpackContainerType struct {
}
var (
msgpackContainerStr = msgpackContainerType{32, mpFixStrMin, mpStr8, mpStr16, mpStr32, true, true, 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}
msgpackContainerStr = msgpackContainerType{
32, mpFixStrMin, mpStr8, mpStr16, mpStr32, true, true, 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
h *MsgpackHandle
x [8]byte
_ [3]uint64 // padding
}
func (e *msgpackEncDriver) EncodeNil() {
@ -190,6 +203,39 @@ func (e *msgpackEncDriver) EncodeFloat64(f float64) {
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) {
bs := ext.WriteExt(v)
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.w.writeb(bs)
} 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) {
slen := len(s)
if c == c_RAW && e.h.WriteExt {
if c == cRAW && e.h.WriteExt {
e.writeContainerLen(msgpackContainerBin, slen)
} else {
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) {
if bs == nil {
e.EncodeNil()
return
}
slen := len(bs)
if c == c_RAW && e.h.WriteExt {
if c == cRAW && e.h.WriteExt {
e.writeContainerLen(msgpackContainerBin, slen)
} else {
e.writeContainerLen(msgpackContainerStr, slen)
@ -287,10 +333,10 @@ func (e *msgpackEncDriver) writeContainerLen(ct msgpackContainerType, l int) {
//---------------------------------------------
type msgpackDecDriver struct {
d *Decoder
r decReader // *Decoder decReader decReaderT
h *MsgpackHandle
b [scratchByteArrayLen]byte
d *Decoder
r decReader // *Decoder decReader decReaderT
h *MsgpackHandle
// b [scratchByteArrayLen]byte
bd byte
bdRead bool
br bool // bytes reader
@ -298,6 +344,7 @@ type msgpackDecDriver struct {
// noStreamingCodec
// decNoSeparator
decDriverNoopContainerReader
_ [3]uint64 // padding
}
// Note: This returns either a primitive (int, bool, etc) for non-containers,
@ -388,7 +435,12 @@ func (d *msgpackDecDriver) DecodeNaked() {
n.v = valueTypeExt
clen := d.readExtLen()
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:
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
func (d *msgpackDecDriver) DecodeInt(bitsize uint8) (i int64) {
func (d *msgpackDecDriver) DecodeInt64() (i int64) {
if !d.bdRead {
d.readNextBd()
}
@ -436,19 +488,12 @@ func (d *msgpackDecDriver) DecodeInt(bitsize uint8) (i int64) {
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
return
}
// 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 {
d.readNextBd()
}
@ -501,19 +546,12 @@ func (d *msgpackDecDriver) DecodeUint(bitsize uint8) (ui uint64) {
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
return
}
// 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 {
d.readNextBd()
}
@ -522,11 +560,7 @@ func (d *msgpackDecDriver) DecodeFloat(chkOverflow32 bool) (f float64) {
} else if d.bd == mpDouble {
f = math.Float64frombits(bigen.Uint64(d.r.readx(8)))
} else {
f = float64(d.DecodeInt(0))
}
if chkOverflow32 && chkOvf.Float32(f) {
d.d.errorf("msgpack: float32 overflow: %v", f)
return
f = float64(d.DecodeInt64())
}
d.bdRead = false
return
@ -554,13 +588,15 @@ func (d *msgpackDecDriver) DecodeBytes(bs []byte, zerocopy bool) (bsOut []byte)
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 ...
var clen int
vt := d.ContainerType()
switch vt {
case valueTypeBytes:
// 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)
} else {
clen = d.readContainerLen(msgpackContainerStr)
@ -568,28 +604,17 @@ func (d *msgpackDecDriver) DecodeBytes(bs []byte, zerocopy bool) (bsOut []byte)
case valueTypeString:
clen = d.readContainerLen(msgpackContainerStr)
case valueTypeArray:
clen = d.readContainerLen(msgpackContainerList)
// ensure everything after is one byte each
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
}
if zerocopy && len(bs) == 0 {
bs = d.d.b[:]
}
d.bdRead = false
return bs
bsOut, _ = fastpathTV.DecSliceUint8V(bs, true, d.d)
return
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
}
// these are (bin|str)(8|16|32)
// println("DecodeBytes: clen: ", clen)
d.bdRead = false
// bytes may be nil, so handle it. if nil, clen=-1.
if clen < 0 {
@ -599,18 +624,18 @@ func (d *msgpackDecDriver) DecodeBytes(bs []byte, zerocopy bool) (bsOut []byte)
if d.br {
return d.r.readx(clen)
} 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) {
return string(d.DecodeBytes(d.b[:], true))
return string(d.DecodeBytes(d.d.b[:], true))
}
func (d *msgpackDecDriver) DecodeStringAsBytes() (s []byte) {
return d.DecodeBytes(d.b[:], true)
return d.DecodeBytes(d.d.b[:], true)
}
func (d *msgpackDecDriver) readNextBd() {
@ -643,9 +668,10 @@ func (d *msgpackDecDriver) ContainerType() (vt valueType) {
return valueTypeArray
} else if bd == mpMap16 || bd == mpMap32 || (bd >= mpFixMapMin && bd <= mpFixMapMax) {
return valueTypeMap
} else {
// d.d.errorf("isContainerType: unsupported parameter: %v", vt)
}
// else {
// d.d.errorf("isContainerType: unsupported parameter: %v", vt)
// }
return valueTypeUnset
}
@ -655,7 +681,7 @@ func (d *msgpackDecDriver) TryDecodeAsNil() (v bool) {
}
if d.bd == mpNil {
d.bdRead = false
v = true
return true
}
return
}
@ -721,6 +747,57 @@ func (d *msgpackDecDriver) readExtLen() (clen int) {
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) {
if xtag > 0xff {
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
// a []byte or string based on the setting of RawToString.
WriteExt bool
binaryEncodingType
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) {
return h.SetExt(rt, tag, &setExtWrapper{b: ext})
return h.SetExt(rt, tag, &extWrapper{ext, interfaceExtFailer{}})
}
func (h *MsgpackHandle) newEncDriver(e *Encoder) encDriver {
@ -827,7 +911,7 @@ func (c *msgpackSpecRpcCodec) WriteRequest(r *rpc.Request, body interface{}) err
bodyArr = []interface{}{body}
}
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 {
@ -839,7 +923,7 @@ func (c *msgpackSpecRpcCodec) WriteResponse(r *rpc.Response, body interface{}) e
body = nil
}
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 {
@ -887,21 +971,19 @@ func (c *msgpackSpecRpcCodec) parseCustomHeader(expectTypeByte byte, msgid *uint
var b = ba[0]
if b != fia {
err = fmt.Errorf("Unexpected value for array descriptor: Expecting %v. Received %v", fia, b)
return
}
if err = c.read(&b); err != nil {
return
}
if b != expectTypeByte {
err = fmt.Errorf("Unexpected byte descriptor in header. Expecting %v. Received %v", expectTypeByte, b)
return
}
if err = c.read(msgid); err != nil {
return
}
if err = c.read(methodOrError); err != nil {
return
} else {
err = c.read(&b)
if err == nil {
if b != expectTypeByte {
err = fmt.Errorf("Unexpected byte descriptor. Expecting %v; Received %v",
expectTypeByte, b)
} else {
err = c.read(msgid)
if err == nil {
err = c.read(methodOrError)
}
}
}
}
return
}
@ -914,7 +996,8 @@ type msgpackSpecRpc struct{}
// MsgpackSpecRpc implements Rpc using the communication protocol defined in
// 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
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.
// +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) EncodeEnd() { h.end() }
func (h *noopDrv) EncodeString(c charEncoding, v string) {}
func (h *noopDrv) EncodeSymbol(v string) {}
func (h *noopDrv) EncodeString(c charEncoding, v string) {}
// func (h *noopDrv) EncodeSymbol(v string) {}
func (h *noopDrv) EncodeStringBytes(c charEncoding, v []byte) {}
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) {
// return h.m(2) == 0
// handle kStruct, which will bomb is it calls this and doesn't get back a map or array.
// 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)
// handle kStruct, which will bomb is it calls this and
// doesn't get back a map or array.
// 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.
var vals = [...]valueType{valueTypeArray, valueTypeMap}
// ------------ TAKE ------------
@ -150,7 +154,8 @@ func (h *noopDrv) ContainerType() (vt valueType) {
// }
// return valueTypeUnset
// 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.ct = vt
// return h.cb

View File

@ -1,6 +1,6 @@
// +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.
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.
package codec
import (
"bufio"
"errors"
"io"
"net/rpc"
@ -16,19 +17,14 @@ type Rpc interface {
ClientCodec(conn io.ReadWriteCloser, h Handle) rpc.ClientCodec
}
// // RpcCodecBuffered allows access to the underlying bufio.Reader/Writer
// // used by the rpc connection. It accommodates use-cases where the connection
// // should be used by rpc and non-rpc functions, e.g. streaming a file after
// // sending an rpc response.
// type RpcCodecBuffered interface {
// BufferedReader() *bufio.Reader
// BufferedWriter() *bufio.Writer
// }
// -------------------------------------
type rpcFlusher interface {
Flush() error
// RPCOptions holds options specific to rpc functionality
type RPCOptions struct {
// RPCNoBuffer configures whether we attempt to buffer reads and writes during RPC calls.
//
// Set RPCNoBuffer=true to turn buffering off.
// Buffering can still be done if buffered connections are passed in, or
// buffering is configured on the handle.
RPCNoBuffer bool
}
// rpcCodec defines the struct members and common methods.
@ -36,7 +32,7 @@ type rpcCodec struct {
c io.Closer
r io.Reader
w io.Writer
f rpcFlusher
f ioFlusher
dec *Decoder
enc *Encoder
@ -45,8 +41,9 @@ type rpcCodec struct {
mu sync.Mutex
h Handle
cls bool
clsmu sync.RWMutex
cls bool
clsmu sync.RWMutex
clsErr error
}
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 {
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{
c: c,
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 {
// return c.br
// }
// func (c *rpcCodec) BufferedWriter() *bufio.Writer {
// return c.bw
// }
func (c *rpcCodec) write(obj1, obj2 interface{}, writeObj2, doFlush bool) (err error) {
func (c *rpcCodec) write(obj1, obj2 interface{}, writeObj2 bool) (err error) {
if c.isClosed() {
return io.EOF
return c.clsErr
}
if err = c.enc.Encode(obj1); err != nil {
return
}
if writeObj2 {
if err = c.enc.Encode(obj2); err != nil {
return
err = c.enc.Encode(obj1)
if err == nil {
if writeObj2 {
err = c.enc.Encode(obj2)
}
// if err == nil && c.f != nil {
// err = c.f.Flush()
// }
}
if doFlush && c.f != nil {
return c.f.Flush()
if c.f != nil {
if err == nil {
err = c.f.Flush()
} else {
c.f.Flush()
}
}
return
}
func (c *rpcCodec) swallow(err *error) {
defer panicToErr(c.dec, err)
c.dec.swallow()
}
func (c *rpcCodec) read(obj interface{}) (err error) {
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 {
var obj2 interface{}
return c.dec.Decode(&obj2)
// var obj2 interface{}
// return c.dec.Decode(&obj2)
c.swallow(&err)
return
}
return c.dec.Decode(obj)
}
func (c *rpcCodec) isClosed() bool {
if c.c == nil {
return false
func (c *rpcCodec) isClosed() (b bool) {
if c.c != nil {
c.clsmu.RLock()
b = c.cls
c.clsmu.RUnlock()
}
c.clsmu.RLock()
x := c.cls
c.clsmu.RUnlock()
return x
return
}
func (c *rpcCodec) Close() error {
if c.c == nil {
return nil
}
if c.isClosed() {
return io.EOF
if c.c == nil || c.isClosed() {
return c.clsErr
}
c.clsmu.Lock()
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()
return err
return c.clsErr
}
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
c.mu.Lock()
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 {
c.mu.Lock()
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 {
@ -175,7 +200,36 @@ func (c *goRpcCodec) ReadRequestBody(body interface{}) error {
type goRpc struct{}
// 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
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 {
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.
package codec
@ -45,6 +45,8 @@ import (
"flag"
"fmt"
"io"
"io/ioutil"
"log"
"sync"
"testing"
)
@ -110,6 +112,8 @@ var (
testMaxInitLen int
testNumRepeatString int
testRpcBufsize int
)
// variables that are not flags, but which can configure the handles
@ -131,6 +135,7 @@ var (
)
func init() {
log.SetOutput(ioutil.Discard) // don't allow things log to standard out/err
testHEDs = make([]testHED, 0, 32)
testHandles = append(testHandles,
// testNoopH,
@ -143,7 +148,7 @@ func init() {
func testInitFlags() {
// delete(testDecOpts.ExtFuncs, timeTyp)
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.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")
@ -272,7 +277,7 @@ func logT(x interface{}, format string, args ...interface{}) {
t.Logf(format, args...)
} else if b, ok := x.(*testing.B); ok && b != nil {
b.Logf(format, args...)
} else if testVerbose {
} else { // if testing.Verbose() { // if testVerbose {
if len(format) == 0 || format[len(format)-1] != '\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.
package codec
@ -6,6 +6,7 @@ package codec
import (
"math"
"reflect"
"time"
)
const (
@ -20,6 +21,8 @@ const (
simpleVdPosInt = 8
simpleVdNegInt = 12
simpleVdTime = 24
// containers: each lasts for 4 (ie n, n+1, n+2, ... n+7)
simpleVdString = 216
simpleVdByteArray = 224
@ -30,12 +33,15 @@ const (
type simpleEncDriver struct {
noBuiltInTypes
encDriverNoopContainerWriter
// encNoSeparator
e *Encoder
h *SimpleHandle
w encWriter
b [8]byte
// c containerState
encDriverTrackContainerWriter
// encDriverNoopContainerWriter
_ [2]uint64 // padding
}
func (e *simpleEncDriver) EncodeNil() {
@ -43,6 +49,10 @@ func (e *simpleEncDriver) EncodeNil() {
}
func (e *simpleEncDriver) EncodeBool(b bool) {
if e.h.EncZeroValuesAsNil && e.c != containerMapKey && !b {
e.EncodeNil()
return
}
if b {
e.w.writen1(simpleVdTrue)
} else {
@ -51,11 +61,19 @@ func (e *simpleEncDriver) EncodeBool(b bool) {
}
func (e *simpleEncDriver) EncodeFloat32(f float32) {
if e.h.EncZeroValuesAsNil && e.c != containerMapKey && f == 0.0 {
e.EncodeNil()
return
}
e.w.writen1(simpleVdFloat32)
bigenHelper{e.b[:4], e.w}.writeUint32(math.Float32bits(f))
}
func (e *simpleEncDriver) EncodeFloat64(f float64) {
if e.h.EncZeroValuesAsNil && e.c != containerMapKey && f == 0.0 {
e.EncodeNil()
return
}
e.w.writen1(simpleVdFloat64)
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) {
if e.h.EncZeroValuesAsNil && e.c != containerMapKey && v == 0 {
e.EncodeNil()
return
}
if v <= math.MaxUint8 {
e.w.writen2(bd, uint8(v))
} else if v <= math.MaxUint16 {
@ -126,27 +148,54 @@ func (e *simpleEncDriver) encodeExtPreamble(xtag byte, length int) {
}
func (e *simpleEncDriver) WriteArrayStart(length int) {
e.c = containerArrayStart
e.encLen(simpleVdArray, length)
}
func (e *simpleEncDriver) WriteMapStart(length int) {
e.c = containerMapStart
e.encLen(simpleVdMap, length)
}
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.w.writestr(v)
}
func (e *simpleEncDriver) EncodeSymbol(v string) {
e.EncodeString(c_UTF8, v)
}
// func (e *simpleEncDriver) EncodeSymbol(v string) {
// e.EncodeString(cUTF8, v)
// }
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.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 {
@ -155,11 +204,13 @@ type simpleDecDriver struct {
r decReader
bdRead bool
bd byte
br bool // bytes reader
b [scratchByteArrayLen]byte
br bool // a bytes reader?
c containerState
// b [scratchByteArrayLen]byte
noBuiltInTypes
// noStreamingCodec
decDriverNoopContainerReader
_ [3]uint64 // padding
}
func (d *simpleDecDriver) readNextBd() {
@ -178,23 +229,27 @@ func (d *simpleDecDriver) ContainerType() (vt valueType) {
if !d.bdRead {
d.readNextBd()
}
if d.bd == simpleVdNil {
switch d.bd {
case simpleVdNil:
return valueTypeNil
} else if d.bd == simpleVdByteArray || d.bd == simpleVdByteArray+1 ||
d.bd == simpleVdByteArray+2 || d.bd == simpleVdByteArray+3 || d.bd == simpleVdByteArray+4 {
case simpleVdByteArray, simpleVdByteArray + 1,
simpleVdByteArray + 2, simpleVdByteArray + 3, simpleVdByteArray + 4:
return valueTypeBytes
} else if d.bd == simpleVdString || d.bd == simpleVdString+1 ||
d.bd == simpleVdString+2 || d.bd == simpleVdString+3 || d.bd == simpleVdString+4 {
case simpleVdString, simpleVdString + 1,
simpleVdString + 2, simpleVdString + 3, simpleVdString + 4:
return valueTypeString
} else if d.bd == simpleVdArray || d.bd == simpleVdArray+1 ||
d.bd == simpleVdArray+2 || d.bd == simpleVdArray+3 || d.bd == simpleVdArray+4 {
case simpleVdArray, simpleVdArray + 1,
simpleVdArray + 2, simpleVdArray + 3, simpleVdArray + 4:
return valueTypeArray
} else if d.bd == simpleVdMap || d.bd == simpleVdMap+1 ||
d.bd == simpleVdMap+2 || d.bd == simpleVdMap+3 || d.bd == simpleVdMap+4 {
case simpleVdMap, simpleVdMap + 1,
simpleVdMap + 2, simpleVdMap + 3, simpleVdMap + 4:
return valueTypeMap
} else {
// d.d.errorf("isContainerType: unsupported parameter: %v", vt)
// case simpleVdTime:
// return valueTypeTime
}
// else {
// d.d.errorf("isContainerType: unsupported parameter: %v", vt)
// }
return valueTypeUnset
}
@ -235,7 +290,7 @@ func (d *simpleDecDriver) decCheckInteger() (ui uint64, neg bool) {
ui = uint64(bigen.Uint64(d.r.readx(8)))
neg = true
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
}
// 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
}
func (d *simpleDecDriver) DecodeInt(bitsize uint8) (i int64) {
func (d *simpleDecDriver) DecodeInt64() (i int64) {
ui, neg := d.decCheckInteger()
i, overflow := chkOvf.SignedInt(ui)
if overflow {
d.d.errorf("simple: overflow converting %v to signed integer", ui)
return
}
i = chkOvf.SignedIntV(ui)
if neg {
i = -i
}
if chkOvf.Int(i, bitsize) {
d.d.errorf("simple: overflow integer: %v", i)
return
}
d.bdRead = false
return
}
func (d *simpleDecDriver) DecodeUint(bitsize uint8) (ui uint64) {
func (d *simpleDecDriver) DecodeUint64() (ui uint64) {
ui, neg := d.decCheckInteger()
if neg {
d.d.errorf("Assigning negative signed value to unsigned type")
return
}
if chkOvf.Uint(ui, bitsize) {
d.d.errorf("simple: overflow integer: %v", ui)
return
}
d.bdRead = false
return
}
func (d *simpleDecDriver) DecodeFloat(chkOverflow32 bool) (f float64) {
func (d *simpleDecDriver) DecodeFloat64() (f float64) {
if !d.bdRead {
d.readNextBd()
}
@ -288,16 +331,12 @@ func (d *simpleDecDriver) DecodeFloat(chkOverflow32 bool) (f float64) {
f = math.Float64frombits(bigen.Uint64(d.r.readx(8)))
} else {
if d.bd >= simpleVdPosInt && d.bd <= simpleVdNegInt+3 {
f = float64(d.DecodeInt(64))
f = float64(d.DecodeInt64())
} else {
d.d.errorf("Float only valid from float32/64: Invalid descriptor: %v", d.bd)
return
}
}
if chkOverflow32 && chkOvf.Float32(f) {
d.d.errorf("msgpack: float32 overflow: %v", f)
return
}
d.bdRead = false
return
}
@ -323,6 +362,7 @@ func (d *simpleDecDriver) ReadMapStart() (length int) {
d.readNextBd()
}
d.bdRead = false
d.c = containerMapStart
return d.decLen()
}
@ -331,9 +371,30 @@ func (d *simpleDecDriver) ReadArrayStart() (length int) {
d.readNextBd()
}
d.bdRead = false
d.c = containerArrayStart
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 {
switch d.bd % 8 {
case 0:
@ -345,14 +406,14 @@ func (d *simpleDecDriver) decLen() int {
case 3:
ui := uint64(bigen.Uint32(d.r.readx(4)))
if chkOvf.Uint(ui, intBitsize) {
d.d.errorf("simple: overflow integer: %v", ui)
d.d.errorf("overflow integer: %v", ui)
return 0
}
return int(ui)
case 4:
ui := bigen.Uint64(d.r.readx(8))
if chkOvf.Uint(ui, intBitsize) {
d.d.errorf("simple: overflow integer: %v", ui)
d.d.errorf("overflow integer: %v", ui)
return 0
}
return int(ui)
@ -362,11 +423,11 @@ func (d *simpleDecDriver) decLen() int {
}
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) {
return d.DecodeBytes(d.b[:], true)
return d.DecodeBytes(d.d.b[:], true)
}
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
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()
d.bdRead = false
if zerocopy {
if d.br {
return d.r.readx(clen)
} else if len(bs) == 0 {
bs = d.b[:]
bs = d.d.b[:]
}
}
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) {
if xtag > 0xff {
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
}
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)
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
}
d.bdRead = false
@ -449,24 +541,29 @@ func (d *simpleDecDriver) DecodeNaked() {
case simpleVdPosInt, simpleVdPosInt + 1, simpleVdPosInt + 2, simpleVdPosInt + 3:
if d.h.SignedInteger {
n.v = valueTypeInt
n.i = d.DecodeInt(64)
n.i = d.DecodeInt64()
} else {
n.v = valueTypeUint
n.u = d.DecodeUint(64)
n.u = d.DecodeUint64()
}
case simpleVdNegInt, simpleVdNegInt + 1, simpleVdNegInt + 2, simpleVdNegInt + 3:
n.v = valueTypeInt
n.i = d.DecodeInt(64)
n.i = d.DecodeInt64()
case simpleVdFloat32:
n.v = valueTypeFloat
n.f = d.DecodeFloat(true)
n.f = d.DecodeFloat64()
case simpleVdFloat64:
n.v = valueTypeFloat
n.f = d.DecodeFloat(false)
case simpleVdString, simpleVdString + 1, simpleVdString + 2, simpleVdString + 3, simpleVdString + 4:
n.f = d.DecodeFloat64()
case simpleVdTime:
n.v = valueTypeTime
n.t = d.DecodeTime()
case simpleVdString, simpleVdString + 1,
simpleVdString + 2, simpleVdString + 3, simpleVdString + 4:
n.v = valueTypeString
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.l = d.DecodeBytes(nil, false)
case simpleVdExt, simpleVdExt + 1, simpleVdExt + 2, simpleVdExt + 3, simpleVdExt + 4:
@ -474,7 +571,8 @@ func (d *simpleDecDriver) DecodeNaked() {
l := d.decLen()
n.u = uint64(d.r.readn1())
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
decodeFurther = true
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).
// There are positive (uintXXX and intXXX >= 0) and negative (intXXX < 0) integers.
// - 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.
// Zero-length containers have no length encoded.
// 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]...
// - extensions are encoded as [bd] [length] [tag] [byte]...
// - strings/bytearrays are encoded as [bd] [length] [byte]...
// - time.Time are encoded as [bd] [length] [byte]...
//
// The full spec will be published soon.
type SimpleHandle struct {
BasicHandle
binaryEncodingType
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) {
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 {
return &simpleEncDriver{e: e, w: e.w, h: h}
}
@ -529,10 +638,12 @@ func (h *SimpleHandle) newDecDriver(d *Decoder) decDriver {
}
func (e *simpleEncDriver) reset() {
e.c = 0
e.w = e.e.w
}
func (d *simpleDecDriver) reset() {
d.c = 0
d.r, d.br = d.d.r, d.d.bytes
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 */
// 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.
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 {
_struct struct{} `codec:",omitempty"` //set omitempty for every field
testStrucCommon
TestStrucCommon
Mis map[int]string
Mbu64 map[bool]struct{}
@ -19,6 +87,20 @@ type TestStrucFlex struct {
Mui2wss map[uint64]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:"-"`
Mtsptr map[string]*TestStrucFlex
Mts map[string]TestStrucFlex
@ -52,9 +134,42 @@ func newTestStrucFlex(depth, n int, bench, useInterface, useStringKeyOnly bool)
22: "twenty two",
-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 {
depth--
if ts.Mtsptr == nil {

View File

@ -1,20 +1,31 @@
/* // +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.
package codec
// This file contains values used by tests and benchmarks.
// JSON/BSON do not like maps with keys that are not strings,
// so we only use maps with string keys here.
// The benchmarks will test performance against other libraries
// (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 (
"math"
"strings"
"time"
)
// func init() {
// rt := reflect.TypeOf((*TestStruc)(nil)).Elem()
// defTypeInfos.get(rt2id(rt), rt)
// }
type wrapSliceUint64 []uint64
type wrapSliceString []string
type wrapUint64 uint64
@ -50,59 +61,36 @@ type AnonInTestStruc struct {
AMSU16E map[string]uint16
}
type AnonInTestStrucIntf struct {
Islice []interface{}
Ms map[string]interface{}
Nintf interface{} //don't set this, so we can test for nil
T time.Time
}
// testSimpleFields is a sub-set of TestStrucCommon
type testSimpleFields struct {
S string
I64 int64
I32 int32
I16 int16
I8 int8
I64n int64
I32n int32
I16n int16
I8n int8
Ui64 uint64
Ui32 uint32
Ui16 uint16
Ui8 uint8
F64 float64
F32 float32
B bool
By uint8 // byte: msgp doesn't like byte
B bool
Sslice []string
I64slice []int64
I16slice []int16
Ui64slice []uint64
Ui8slice []uint8
Bslice []bool
Byslice []byte
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
WrapSliceString wrapSliceString
Msi64 map[string]int64
}
type testStrucCommon struct {
type TestStrucCommon struct {
S string
I64 int64
@ -136,11 +124,6 @@ type testStrucCommon struct {
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
WrapSliceString wrapSliceString
@ -148,14 +131,14 @@ type testStrucCommon struct {
Simplef testSimpleFields
SstrUi64T []stringUint64T
AnonInTestStruc
NotAnon AnonInTestStruc
// 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 `codec:",omitempty"`
// R Raw // Testing Raw must be explicitly turned on, so use standalone test
// Rext RawExt // Testing RawExt is tricky, so use standalone test
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
@ -163,9 +146,9 @@ type testStrucCommon 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
Mts map[string]TestStruc
@ -173,52 +156,11 @@ type TestStruc struct {
Nteststruc *TestStruc
}
// small struct for testing that codecgen works for unexported types
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) {
func populateTestStrucCommon(ts *TestStrucCommon, n int, bench, useInterface, useStringKeyOnly bool) {
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{
// There's more leeway in altering this.
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.MaxUint16, math.MaxUint16 + 4, math.MaxUint16 - 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},
@ -300,7 +241,10 @@ func populateTestStrucCommon(ts *testStrucCommon, n int, bench, useInterface, us
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? `),
// set the numbers close to the limits
@ -338,11 +282,12 @@ func populateTestStrucCommon(ts *testStrucCommon, n int, bench, useInterface, us
strRpt(n, "\"three\""): 3,
},
Ui64array: [4]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")},
// 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,
// and a map cannot be the key type of a map.
// 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
// TODO: should this have slightly different values???
Simplef: testSimpleFields{
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
I8: math.MaxInt8 * 2 / 3, // 8,
I8n: math.MinInt8 * 2 / 3, // 8,
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,
I8: math.MaxInt8 * 2 / 3, // 8,
I64: math.MaxInt64 * 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
F32: 3.402823e+38, // max representable float32 without losing precision
F64: 3.40281991833838838338e+53,
B: true,
By: 5,
B: true,
Sslice: []string{strRpt(n, "one"), strRpt(n, "two"), strRpt(n, "three")},
I64slice: []int64{1111, 2222, 3333},
I16slice: []int16{44, 55, 66},
Ui64slice: []uint64{12121212, 34343434, 56565656},
Ui8slice: []uint8{210, 211, 212},
Bslice: []bool{true, false, true, false},
Byslice: []byte{13, 14, 15},
Msi64: map[string]int64{
strRpt(n, "one"): 1,
@ -390,27 +325,18 @@ func populateTestStrucCommon(ts *testStrucCommon, n int, bench, useInterface, us
strRpt(n, "\"three\""): 3,
},
Ui64array: [4]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")},
},
SstrUi64T: []stringUint64T{{"1", 1}, {"2", 2}, {"3", 3}, {"4", 4}},
AnonInTestStruc: a,
NotAnon: a,
}
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,
}
if bench {
ts.Ui64 = math.MaxInt64 * 2 / 3
ts.Simplef.Ui64 = ts.Ui64
}
//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) {
ts = &TestStruc{}
populateTestStrucCommon(&ts.testStrucCommon, n, bench, useInterface, useStringKeyOnly)
populateTestStrucCommon(&ts.TestStrucCommon, n, bench, useInterface, useStringKeyOnly)
if depth > 0 {
depth--
if ts.Mtsptr == nil {
@ -447,6 +373,28 @@ func newTestStruc(depth, n int, bench, useInterface, useStringKeyOnly bool) (ts
return
}
var testStrRptMap = make(map[int]map[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
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.
Challenges:
- 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
- How to decide when to use an attribute VS an element
@ -34,24 +36,26 @@ Challenges:
Extend the struct tag. See representative example:
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
- 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".
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:
- 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.
- *Encoder and *Decoder know whether the Handle "prefers" namespaces.
- add *Encoder.getEncName(*structFieldInfo).
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
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)
- 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)
@ -62,13 +66,14 @@ To handle namespaces:
default {
switch {
case !nsAware: panic(...)
case strings.endsWith("nsabc"): x.abc()
case strings.endsWith(":abc"): x.abc()
case strings.endsWith(":def"): x.def()
default: panic(...)
}
}
}
The structure below accomodates this:
The structure below accommodates this:
type typeInfo struct {
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(...)
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:
- 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)
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.
e.g. <hn:name xmlns:hn="http://www.ugorji.net" >
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.*
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,
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).
We have to "DecodeNaked" into something that resembles XML data.
To support DecodeNaked (decode into nil interface{}) we have to define some "supporting" types:
type Name struct { // Prefered. Less allocations due to conversions.
To support DecodeNaked (decode into nil interface{}), we have to define some "supporting" types:
type Name struct { // Preferred. Less allocations due to conversions.
Local 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.
// ------------------
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
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
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'
all whitespace. This is more critical when doing a DecodeNaked, but MAY not be as critical
when decoding into a typed value.
// ------------------
Per XML spec and our default handling, white space is always treated as
insignificant between elements, except in a text node. The xml:space='preserve'
attribute is ignored.
**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**.
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:
- 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.
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:
- if encoding a map, what if map keys have spaces in them???
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,
and converting every white space sequence to a single space.
- ATTLIST restrictions are enforced.
@ -284,6 +293,8 @@ Misc:
CheckName bool
}
Misc:
ROADMAP (1 weeks):
- build encoder (1 day)
- build decoder (based off xmlParser) (1 day)
@ -292,7 +303,78 @@ ROADMAP (1 weeks):
- integrate and TEST (1 days)
- 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 -------------------
@ -419,7 +501,7 @@ func (h *XMLHandle) newDecDriver(d *Decoder) decDriver {
}
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)

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 go1.7
@ -41,11 +44,15 @@ func testGroupResetFlags() {
testMaxInitLen = 0
testUseIoWrapper = false
testNumRepeatString = 8
testEncodeOptions.RecursiveEmptyCheck = false
testDecodeOptions.MapValueReset = false
testUseIoEncDec = -1
testDepth = 0
}
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'
// 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
@ -65,20 +72,22 @@ func testSuite(t *testing.T, f func(t *testing.T)) {
testCheckCircRef = true
testUseReset = true
testDecodeOptions.MapValueReset = true
testEncodeOptions.RecursiveEmptyCheck = true
testReinit()
t.Run("optionsTrue", f)
testEncodeOptions.AsSymbols = AsSymbolAll
testDepth = 6
testReinit()
t.Run("optionsTrue-deepstruct", f)
testDepth = 0
// testEncodeOptions.AsSymbols = AsSymbolAll
testUseIoWrapper = true
testReinit()
t.Run("optionsTrue-ioWrapper", f)
testUseIoEncDec = -1
testDepth = 6
testReinit()
t.Run("optionsTrue-deepstruct", f)
// make buffer small enough so that we have to re-fill multiple times.
testSkipRPCTests = true
testUseIoEncDec = 128
@ -110,8 +119,7 @@ func testSuite(t *testing.T, f func(t *testing.T)) {
}
/*
z='codec_test.go'
find . -name "$z" | xargs grep -e '^func Test' | \
find . -name "codec_test.go" | xargs grep -e '^func Test' | \
cut -d '(' -f 1 | cut -d ' ' -f 2 | \
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("TestBincUintToInt", TestBincUintToInt)
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("TestCborHalfFloat", TestCborHalfFloat)
@ -240,6 +263,9 @@ func testJsonGroup(t *testing.T) {
t.Run("TestJsonInvalidUnicode", TestJsonInvalidUnicode)
t.Run("TestJsonTime", TestJsonTime)
t.Run("TestJsonUintToInt", TestJsonUintToInt)
t.Run("TestJsonDifferentMapOrSliceType", TestJsonDifferentMapOrSliceType)
t.Run("TestJsonScalars", TestJsonScalars)
t.Run("TestJsonIntfMapping", TestJsonIntfMapping)
}
func testBincGroup(t *testing.T) {
@ -261,6 +287,8 @@ func testBincGroup(t *testing.T) {
t.Run("TestBincMammothMapsAndSlices", TestBincMammothMapsAndSlices)
t.Run("TestBincTime", TestBincTime)
t.Run("TestBincUintToInt", TestBincUintToInt)
t.Run("TestBincDifferentMapOrSliceType", TestBincDifferentMapOrSliceType)
t.Run("TestBincScalars", TestBincScalars)
}
func testCborGroup(t *testing.T) {
@ -283,6 +311,8 @@ func testCborGroup(t *testing.T) {
t.Run("TestCborMammothMapsAndSlices", TestCborMammothMapsAndSlices)
t.Run("TestCborTime", TestCborTime)
t.Run("TestCborUintToInt", TestCborUintToInt)
t.Run("TestCborDifferentMapOrSliceType", TestCborDifferentMapOrSliceType)
t.Run("TestCborScalars", TestCborScalars)
t.Run("TestCborHalfFloat", TestCborHalfFloat)
}
@ -305,6 +335,42 @@ func testMsgpackGroup(t *testing.T) {
t.Run("TestMsgpackMammothMapsAndSlices", TestMsgpackMammothMapsAndSlices)
t.Run("TestMsgpackTime", TestMsgpackTime)
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) {
@ -342,17 +408,23 @@ func TestCodecSuite(t *testing.T) {
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()
t.Run("binc-no-symbols", testBincGroup)
testBincH.getBasicHandle().AsSymbols = AsSymbolAll
testBincH.AsSymbols = 1 // AsSymbolAll
testReinit()
t.Run("binc-all-symbols", testBincGroup)
testBincH.getBasicHandle().AsSymbols = oldSymbols
testBincH.AsSymbols = oldSymbols
oldWriteExt := testMsgpackH.WriteExt
oldNoFixedNum := testMsgpackH.NoFixedNum
@ -369,6 +441,26 @@ func TestCodecSuite(t *testing.T) {
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()
}

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": {"nonce"}})
nonces := map[string]struct{}{"nonce": struct{}{}}
nonces := map[string]struct{}{"nonce": {}}
if !reflect.DeepEqual(c.nonces, nonces) {
t.Errorf("c.nonces = %q; want %q", c.nonces, nonces)
}

View File

@ -24,7 +24,9 @@ import (
"fmt"
"io"
mathrand "math/rand"
"net"
"net/http"
"path"
"strconv"
"strings"
"sync"
@ -80,8 +82,9 @@ func defaultHostPolicy(context.Context, string) error {
}
// Manager is a stateful certificate manager built on top of acme.Client.
// It obtains and refreshes certificates automatically,
// as well as providing them to a TLS server via tls.Config.
// It obtains and refreshes certificates automatically using "tls-sni-01",
// "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,
// to reuse obtained certificates across program restarts.
@ -150,15 +153,26 @@ type Manager struct {
stateMu sync.Mutex
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.
// It is keyed by domain name.
renewalMu sync.Mutex
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.
@ -185,14 +199,16 @@ func (m *Manager) GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate,
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)
defer cancel()
// check whether this is a token cert requested for TLS-SNI challenge
if strings.HasSuffix(name, ".acme.invalid") {
m.tokenCertMu.RLock()
defer m.tokenCertMu.RUnlock()
if cert := m.tokenCert[name]; cert != nil {
m.tokensMu.RLock()
defer m.tokensMu.RUnlock()
if cert := m.certTokens[name]; cert != nil {
return cert, 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
}
// 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.
// If a certificate is found in cache but not in m.state, the latter will be filled
// 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.
// 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) {
if err := m.verify(ctx, domain); err != nil {
return nil, nil, err
}
client, err := m.acmeClient(ctx)
if err != nil {
return nil, nil, err
}
if err := m.verify(ctx, client, domain); err != nil {
return nil, nil, err
}
csr, err := certRequest(key, domain)
if err != nil {
return nil, nil, err
@ -464,98 +543,171 @@ func (m *Manager) authorizedCert(ctx context.Context, key crypto.Signer, domain
return der, leaf, nil
}
// verify starts a new identifier (domain) authorization flow.
// It prepares a challenge response and then blocks until the authorization
// is marked as "completed" by the CA (either succeeded or failed).
//
// verify returns nil iff the verification was successful.
func (m *Manager) verify(ctx context.Context, domain string) error {
client, err := m.acmeClient(ctx)
if err != nil {
return err
// verify runs the identifier (domain) authorization flow
// using each applicable ACME challenge type.
func (m *Manager) verify(ctx context.Context, client *acme.Client, domain string) error {
// The list of challenge types we'll try to fulfill
// in this specific order.
challengeTypes := []string{"tls-sni-02", "tls-sni-01"}
m.tokensMu.RLock()
if m.tryHTTP01 {
challengeTypes = append(challengeTypes, "http-01")
}
m.tokensMu.RUnlock()
// start domain authorization and get the challenge
authz, err := client.Authorize(ctx, domain)
if err != nil {
return err
}
// maybe don't need to at all
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
var nextTyp int // challengeType index of the next challenge type to try
for {
// Start domain authorization and get the challenge.
authz, err := client.Authorize(ctx, domain)
if err != nil {
return err
}
if c.Type == "tls-sni-01" {
chal = c
// No point in accepting challenges if the authorization status
// 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
// and m.Cache.
func (m *Manager) putTokenCert(ctx context.Context, name string, cert *tls.Certificate) {
m.tokenCertMu.Lock()
defer m.tokenCertMu.Unlock()
if m.tokenCert == nil {
m.tokenCert = make(map[string]*tls.Certificate)
// fulfill provisions a response to the challenge chal.
// The cleanup is non-nil only if provisioning succeeded.
func (m *Manager) fulfill(ctx context.Context, client *acme.Client, chal *acme.Challenge) (cleanup func(), err error) {
switch chal.Type {
case "tls-sni-01":
cert, name, err := client.TLSSNI01ChallengeCert(chal.Token)
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)
}
// deleteTokenCert removes the token certificate for the specified domain name
// from both m.tokenCert map and m.Cache.
func (m *Manager) deleteTokenCert(name string) {
m.tokenCertMu.Lock()
defer m.tokenCertMu.Unlock()
delete(m.tokenCert, name)
// deleteCertToken removes the token certificate for the specified domain name
// from both m.certTokens map and m.Cache.
func (m *Manager) deleteCertToken(name string) {
m.tokensMu.Lock()
defer m.tokensMu.Unlock()
delete(m.certTokens, name)
if m.Cache != nil {
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.
//
// The loop is scheduled in two cases:

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