mirror of https://github.com/miguelmota/cointop
Transfer from dep to go modules.
Also update vendored packages while we're at it.pull/22/head
parent
e41559ff17
commit
3ec497b96f
@ -1,104 +0,0 @@
|
|||||||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
|
||||||
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
digest = "1:289dd4d7abfb3ad2b5f728fbe9b1d5c1bf7d265a3eb9ef92869af1f7baba4c7a"
|
|
||||||
name = "github.com/BurntSushi/toml"
|
|
||||||
packages = ["."]
|
|
||||||
pruneopts = ""
|
|
||||||
revision = "b26d9c308763d68093482582cea63d69be07a0f0"
|
|
||||||
version = "v0.3.0"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
digest = "1:c742448be99f29f5233ab05bd50eea54ab53f438a3465afa8b247791f0cd885b"
|
|
||||||
name = "github.com/anaskhan96/soup"
|
|
||||||
packages = ["."]
|
|
||||||
pruneopts = ""
|
|
||||||
revision = "cd07662ec9300d16d2ece1c24b0b29ad89f65ff4"
|
|
||||||
version = "v1.1"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
digest = "1:d7d69c103aba5ea229451115b28ca06d061e3861b2f44bcd0100cbc65659c4d9"
|
|
||||||
name = "github.com/fatih/color"
|
|
||||||
packages = ["."]
|
|
||||||
pruneopts = ""
|
|
||||||
revision = "507f6050b8568533fb3f5504de8e5205fa62a114"
|
|
||||||
version = "v1.6.0"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
digest = "1:9ea83adf8e96d6304f394d40436f2eb44c1dc3250d223b74088cc253a6cd0a1c"
|
|
||||||
name = "github.com/mattn/go-colorable"
|
|
||||||
packages = ["."]
|
|
||||||
pruneopts = ""
|
|
||||||
revision = "167de6bfdfba052fa6b2d3664c8f5272e23c9072"
|
|
||||||
version = "v0.0.9"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
digest = "1:78229b46ddb7434f881390029bd1af7661294af31f6802e0e1bedaad4ab0af3c"
|
|
||||||
name = "github.com/mattn/go-isatty"
|
|
||||||
packages = ["."]
|
|
||||||
pruneopts = ""
|
|
||||||
revision = "0360b2af4f38e8d38c7fce2a9f4e702702d73a39"
|
|
||||||
version = "v0.0.3"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
digest = "1:81e673df85e765593a863f67cba4544cf40e8919590f04d67664940786c2b61a"
|
|
||||||
name = "github.com/mattn/go-runewidth"
|
|
||||||
packages = ["."]
|
|
||||||
pruneopts = ""
|
|
||||||
revision = "9e777a8366cce605130a531d2cd6363d07ad7317"
|
|
||||||
version = "v0.0.2"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
digest = "1:80ec738f420f13c23460cc92cd893d3e403588c60682afebbd4515eca5e19578"
|
|
||||||
name = "golang.org/x/net"
|
|
||||||
packages = [
|
|
||||||
"html",
|
|
||||||
"html/atom",
|
|
||||||
]
|
|
||||||
pruneopts = ""
|
|
||||||
revision = "5f9ae10d9af5b1c89ae6904293b14b064d4ada23"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
digest = "1:ba4898d0f47bb5a26d7e1842bc1b29799441fbcc76aed65ba45d5b6918e35130"
|
|
||||||
name = "golang.org/x/sys"
|
|
||||||
packages = ["unix"]
|
|
||||||
pruneopts = ""
|
|
||||||
revision = "bb9c189858d91f42db229b04d45a4c3d23a7662a"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
digest = "1:5acd3512b047305d49e8763eef7ba423901e85d5dd2fd1e71778a0ea8de10bd4"
|
|
||||||
name = "golang.org/x/text"
|
|
||||||
packages = [
|
|
||||||
"feature/plural",
|
|
||||||
"internal",
|
|
||||||
"internal/catmsg",
|
|
||||||
"internal/format",
|
|
||||||
"internal/gen",
|
|
||||||
"internal/number",
|
|
||||||
"internal/stringset",
|
|
||||||
"internal/tag",
|
|
||||||
"language",
|
|
||||||
"message",
|
|
||||||
"message/catalog",
|
|
||||||
"unicode/cldr",
|
|
||||||
]
|
|
||||||
pruneopts = ""
|
|
||||||
revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0"
|
|
||||||
version = "v0.3.0"
|
|
||||||
|
|
||||||
[solve-meta]
|
|
||||||
analyzer-name = "dep"
|
|
||||||
analyzer-version = 1
|
|
||||||
input-imports = [
|
|
||||||
"github.com/BurntSushi/toml",
|
|
||||||
"github.com/anaskhan96/soup",
|
|
||||||
"github.com/fatih/color",
|
|
||||||
"github.com/mattn/go-runewidth",
|
|
||||||
"golang.org/x/text/language",
|
|
||||||
"golang.org/x/text/message",
|
|
||||||
]
|
|
||||||
solver-name = "gps-cdcl"
|
|
||||||
solver-version = 1
|
|
@ -1,31 +0,0 @@
|
|||||||
# Gopkg.toml example
|
|
||||||
#
|
|
||||||
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
|
|
||||||
# for detailed Gopkg.toml documentation.
|
|
||||||
#
|
|
||||||
# required = ["github.com/user/thing/cmd/thing"]
|
|
||||||
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
|
|
||||||
#
|
|
||||||
# [[constraint]]
|
|
||||||
# name = "github.com/user/project"
|
|
||||||
# version = "1.0.0"
|
|
||||||
#
|
|
||||||
# [[constraint]]
|
|
||||||
# name = "github.com/user/project2"
|
|
||||||
# branch = "dev"
|
|
||||||
# source = "github.com/myfork/project2"
|
|
||||||
#
|
|
||||||
# [[override]]
|
|
||||||
# name = "github.com/x/y"
|
|
||||||
# version = "2.4.0"
|
|
||||||
#
|
|
||||||
# [prune]
|
|
||||||
# non-go = false
|
|
||||||
# go-tests = true
|
|
||||||
# unused-packages = true
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
name = "github.com/fatih/color"
|
|
||||||
version = "1.6.0"
|
|
@ -0,0 +1,13 @@
|
|||||||
|
module github.com/miguelmota/cointop
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/BurntSushi/toml v0.3.1
|
||||||
|
github.com/anaskhan96/soup v1.1.1
|
||||||
|
github.com/fatih/color v1.7.0
|
||||||
|
github.com/mattn/go-colorable v0.0.9 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.4 // indirect
|
||||||
|
github.com/mattn/go-runewidth v0.0.4
|
||||||
|
golang.org/x/net v0.0.0-20181220203305-927f97764cc3 // indirect
|
||||||
|
golang.org/x/sys v0.0.0-20181221143128-b4a75ba826a6 // indirect
|
||||||
|
golang.org/x/text v0.3.0
|
||||||
|
)
|
@ -0,0 +1,25 @@
|
|||||||
|
github.com/BurntSushi/toml v0.3.0/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
|
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||||
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
|
github.com/anaskhan96/soup v0.0.0-20180328123631-cd07662ec930/go.mod h1:pT5vs4HXDwA5y4KQCsKvnkpQd3D+joP7IqpiGskfWW0=
|
||||||
|
github.com/anaskhan96/soup v1.1.1 h1:Duux/0htS2Va7XLJ9qIakCSey790hg9OFRm2FwlMTy0=
|
||||||
|
github.com/anaskhan96/soup v1.1.1/go.mod h1:pT5vs4HXDwA5y4KQCsKvnkpQd3D+joP7IqpiGskfWW0=
|
||||||
|
github.com/fatih/color v1.6.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||||
|
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
|
||||||
|
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||||
|
github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4=
|
||||||
|
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||||
|
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||||
|
github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs=
|
||||||
|
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||||
|
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||||
|
github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y=
|
||||||
|
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||||
|
golang.org/x/net v0.0.0-20180420171651-5f9ae10d9af5/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20181220203305-927f97764cc3 h1:eH6Eip3UpmR+yM/qI9Ijluzb1bNv/cAU/n+6l8tRSis=
|
||||||
|
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/sys v0.0.0-20180425194835-bb9c189858d9/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20181221143128-b4a75ba826a6 h1:IcgEB62HYgAhX0Nd/QrVgZlxlcyxbGQHElLUhW2X4Fo=
|
||||||
|
golang.org/x/sys v0.0.0-20181221143128-b4a75ba826a6/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
@ -1,14 +1,21 @@
|
|||||||
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
The MIT License (MIT)
|
||||||
Version 2, December 2004
|
|
||||||
|
|
||||||
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
|
Copyright (c) 2013 TOML authors
|
||||||
|
|
||||||
Everyone is permitted to copy and distribute verbatim or modified
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
copies of this license document, and changing it is allowed as long
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
as the name is changed.
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
The above copyright notice and this permission notice shall be included in
|
||||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
0. You just DO WHAT THE FUCK YOU WANT TO.
|
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
|
@ -1,61 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/BurntSushi/toml"
|
|
||||||
)
|
|
||||||
|
|
||||||
type tomlConfig struct {
|
|
||||||
Title string
|
|
||||||
Owner ownerInfo
|
|
||||||
DB database `toml:"database"`
|
|
||||||
Servers map[string]server
|
|
||||||
Clients clients
|
|
||||||
}
|
|
||||||
|
|
||||||
type ownerInfo struct {
|
|
||||||
Name string
|
|
||||||
Org string `toml:"organization"`
|
|
||||||
Bio string
|
|
||||||
DOB time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
type database struct {
|
|
||||||
Server string
|
|
||||||
Ports []int
|
|
||||||
ConnMax int `toml:"connection_max"`
|
|
||||||
Enabled bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type server struct {
|
|
||||||
IP string
|
|
||||||
DC string
|
|
||||||
}
|
|
||||||
|
|
||||||
type clients struct {
|
|
||||||
Data [][]interface{}
|
|
||||||
Hosts []string
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
var config tomlConfig
|
|
||||||
if _, err := toml.DecodeFile("example.toml", &config); err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("Title: %s\n", config.Title)
|
|
||||||
fmt.Printf("Owner: %s (%s, %s), Born: %s\n",
|
|
||||||
config.Owner.Name, config.Owner.Org, config.Owner.Bio,
|
|
||||||
config.Owner.DOB)
|
|
||||||
fmt.Printf("Database: %s %v (Max conn. %d), Enabled? %v\n",
|
|
||||||
config.DB.Server, config.DB.Ports, config.DB.ConnMax,
|
|
||||||
config.DB.Enabled)
|
|
||||||
for serverName, server := range config.Servers {
|
|
||||||
fmt.Printf("Server: %s (%s, %s)\n", serverName, server.IP, server.DC)
|
|
||||||
}
|
|
||||||
fmt.Printf("Client data: %v\n", config.Clients.Data)
|
|
||||||
fmt.Printf("Client hosts: %v\n", config.Clients.Hosts)
|
|
||||||
}
|
|
@ -1,35 +0,0 @@
|
|||||||
# This is a TOML document. Boom.
|
|
||||||
|
|
||||||
title = "TOML Example"
|
|
||||||
|
|
||||||
[owner]
|
|
||||||
name = "Tom Preston-Werner"
|
|
||||||
organization = "GitHub"
|
|
||||||
bio = "GitHub Cofounder & CEO\nLikes tater tots and beer."
|
|
||||||
dob = 1979-05-27T07:32:00Z # First class dates? Why not?
|
|
||||||
|
|
||||||
[database]
|
|
||||||
server = "192.168.1.1"
|
|
||||||
ports = [ 8001, 8001, 8002 ]
|
|
||||||
connection_max = 5000
|
|
||||||
enabled = true
|
|
||||||
|
|
||||||
[servers]
|
|
||||||
|
|
||||||
# You can indent as you please. Tabs or spaces. TOML don't care.
|
|
||||||
[servers.alpha]
|
|
||||||
ip = "10.0.0.1"
|
|
||||||
dc = "eqdc10"
|
|
||||||
|
|
||||||
[servers.beta]
|
|
||||||
ip = "10.0.0.2"
|
|
||||||
dc = "eqdc10"
|
|
||||||
|
|
||||||
[clients]
|
|
||||||
data = [ ["gamma", "delta"], [1, 2] ] # just an update to make sure parsers support it
|
|
||||||
|
|
||||||
# Line breaks are OK when inside arrays
|
|
||||||
hosts = [
|
|
||||||
"alpha",
|
|
||||||
"omega"
|
|
||||||
]
|
|
@ -1,22 +0,0 @@
|
|||||||
# Test file for TOML
|
|
||||||
# Only this one tries to emulate a TOML file written by a user of the kind of parser writers probably hate
|
|
||||||
# This part you'll really hate
|
|
||||||
|
|
||||||
[the]
|
|
||||||
test_string = "You'll hate me after this - #" # " Annoying, isn't it?
|
|
||||||
|
|
||||||
[the.hard]
|
|
||||||
test_array = [ "] ", " # "] # ] There you go, parse this!
|
|
||||||
test_array2 = [ "Test #11 ]proved that", "Experiment #9 was a success" ]
|
|
||||||
# You didn't think it'd as easy as chucking out the last #, did you?
|
|
||||||
another_test_string = " Same thing, but with a string #"
|
|
||||||
harder_test_string = " And when \"'s are in the string, along with # \"" # "and comments are there too"
|
|
||||||
# Things will get harder
|
|
||||||
|
|
||||||
[the.hard.bit#]
|
|
||||||
what? = "You don't think some user won't do that?"
|
|
||||||
multi_line_array = [
|
|
||||||
"]",
|
|
||||||
# ] Oh yes I did
|
|
||||||
]
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
|||||||
# [x] you
|
|
||||||
# [x.y] don't
|
|
||||||
# [x.y.z] need these
|
|
||||||
[x.y.z.w] # for this to work
|
|
@ -1,6 +0,0 @@
|
|||||||
# DO NOT WANT
|
|
||||||
[fruit]
|
|
||||||
type = "apple"
|
|
||||||
|
|
||||||
[fruit.type]
|
|
||||||
apple = "yes"
|
|
@ -1,35 +0,0 @@
|
|||||||
# This is an INVALID TOML document. Boom.
|
|
||||||
# Can you spot the error without help?
|
|
||||||
|
|
||||||
title = "TOML Example"
|
|
||||||
|
|
||||||
[owner]
|
|
||||||
name = "Tom Preston-Werner"
|
|
||||||
organization = "GitHub"
|
|
||||||
bio = "GitHub Cofounder & CEO\nLikes tater tots and beer."
|
|
||||||
dob = 1979-05-27T7:32:00Z # First class dates? Why not?
|
|
||||||
|
|
||||||
[database]
|
|
||||||
server = "192.168.1.1"
|
|
||||||
ports = [ 8001, 8001, 8002 ]
|
|
||||||
connection_max = 5000
|
|
||||||
enabled = true
|
|
||||||
|
|
||||||
[servers]
|
|
||||||
# You can indent as you please. Tabs or spaces. TOML don't care.
|
|
||||||
[servers.alpha]
|
|
||||||
ip = "10.0.0.1"
|
|
||||||
dc = "eqdc10"
|
|
||||||
|
|
||||||
[servers.beta]
|
|
||||||
ip = "10.0.0.2"
|
|
||||||
dc = "eqdc10"
|
|
||||||
|
|
||||||
[clients]
|
|
||||||
data = [ ["gamma", "delta"], [1, 2] ] # just an update to make sure parsers support it
|
|
||||||
|
|
||||||
# Line breaks are OK when inside arrays
|
|
||||||
hosts = [
|
|
||||||
"alpha",
|
|
||||||
"omega"
|
|
||||||
]
|
|
@ -1,5 +0,0 @@
|
|||||||
Age = 25
|
|
||||||
Cats = [ "Cauchy", "Plato" ]
|
|
||||||
Pi = 3.14
|
|
||||||
Perfection = [ 6, 28, 496, 8128 ]
|
|
||||||
DOB = 1987-07-05T05:45:00Z
|
|
@ -1 +0,0 @@
|
|||||||
some_key_NAME = "wat"
|
|
@ -1,14 +0,0 @@
|
|||||||
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
|
||||||
Version 2, December 2004
|
|
||||||
|
|
||||||
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
|
|
||||||
|
|
||||||
Everyone is permitted to copy and distribute verbatim or modified
|
|
||||||
copies of this license document, and changing it is allowed as long
|
|
||||||
as the name is changed.
|
|
||||||
|
|
||||||
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
|
||||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
|
||||||
|
|
||||||
0. You just DO WHAT THE FUCK YOU WANT TO.
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
|||||||
# Implements the TOML test suite interface
|
|
||||||
|
|
||||||
This is an implementation of the interface expected by
|
|
||||||
[toml-test](https://github.com/BurntSushi/toml-test) for my
|
|
||||||
[toml parser written in Go](https://github.com/BurntSushi/toml).
|
|
||||||
In particular, it maps TOML data on `stdin` to a JSON format on `stdout`.
|
|
||||||
|
|
||||||
|
|
||||||
Compatible with TOML version
|
|
||||||
[v0.4.0](https://github.com/toml-lang/toml/blob/master/versions/en/toml-v0.4.0.md)
|
|
||||||
|
|
||||||
Compatible with `toml-test` version
|
|
||||||
[v0.2.0](https://github.com/BurntSushi/toml-test/tree/v0.2.0)
|
|
@ -1,90 +0,0 @@
|
|||||||
// Command toml-test-decoder satisfies the toml-test interface for testing
|
|
||||||
// TOML decoders. Namely, it accepts TOML on stdin and outputs JSON on stdout.
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/BurntSushi/toml"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
log.SetFlags(0)
|
|
||||||
|
|
||||||
flag.Usage = usage
|
|
||||||
flag.Parse()
|
|
||||||
}
|
|
||||||
|
|
||||||
func usage() {
|
|
||||||
log.Printf("Usage: %s < toml-file\n", path.Base(os.Args[0]))
|
|
||||||
flag.PrintDefaults()
|
|
||||||
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
if flag.NArg() != 0 {
|
|
||||||
flag.Usage()
|
|
||||||
}
|
|
||||||
|
|
||||||
var tmp interface{}
|
|
||||||
if _, err := toml.DecodeReader(os.Stdin, &tmp); err != nil {
|
|
||||||
log.Fatalf("Error decoding TOML: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
typedTmp := translate(tmp)
|
|
||||||
if err := json.NewEncoder(os.Stdout).Encode(typedTmp); err != nil {
|
|
||||||
log.Fatalf("Error encoding JSON: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func translate(tomlData interface{}) interface{} {
|
|
||||||
switch orig := tomlData.(type) {
|
|
||||||
case map[string]interface{}:
|
|
||||||
typed := make(map[string]interface{}, len(orig))
|
|
||||||
for k, v := range orig {
|
|
||||||
typed[k] = translate(v)
|
|
||||||
}
|
|
||||||
return typed
|
|
||||||
case []map[string]interface{}:
|
|
||||||
typed := make([]map[string]interface{}, len(orig))
|
|
||||||
for i, v := range orig {
|
|
||||||
typed[i] = translate(v).(map[string]interface{})
|
|
||||||
}
|
|
||||||
return typed
|
|
||||||
case []interface{}:
|
|
||||||
typed := make([]interface{}, len(orig))
|
|
||||||
for i, v := range orig {
|
|
||||||
typed[i] = translate(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
// We don't really need to tag arrays, but let's be future proof.
|
|
||||||
// (If TOML ever supports tuples, we'll need this.)
|
|
||||||
return tag("array", typed)
|
|
||||||
case time.Time:
|
|
||||||
return tag("datetime", orig.Format("2006-01-02T15:04:05Z"))
|
|
||||||
case bool:
|
|
||||||
return tag("bool", fmt.Sprintf("%v", orig))
|
|
||||||
case int64:
|
|
||||||
return tag("integer", fmt.Sprintf("%d", orig))
|
|
||||||
case float64:
|
|
||||||
return tag("float", fmt.Sprintf("%v", orig))
|
|
||||||
case string:
|
|
||||||
return tag("string", orig)
|
|
||||||
}
|
|
||||||
|
|
||||||
panic(fmt.Sprintf("Unknown type: %T", tomlData))
|
|
||||||
}
|
|
||||||
|
|
||||||
func tag(typeName string, data interface{}) map[string]interface{} {
|
|
||||||
return map[string]interface{}{
|
|
||||||
"type": typeName,
|
|
||||||
"value": data,
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,14 +0,0 @@
|
|||||||
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
|
||||||
Version 2, December 2004
|
|
||||||
|
|
||||||
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
|
|
||||||
|
|
||||||
Everyone is permitted to copy and distribute verbatim or modified
|
|
||||||
copies of this license document, and changing it is allowed as long
|
|
||||||
as the name is changed.
|
|
||||||
|
|
||||||
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
|
||||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
|
||||||
|
|
||||||
0. You just DO WHAT THE FUCK YOU WANT TO.
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
|||||||
# Implements the TOML test suite interface for TOML encoders
|
|
||||||
|
|
||||||
This is an implementation of the interface expected by
|
|
||||||
[toml-test](https://github.com/BurntSushi/toml-test) for the
|
|
||||||
[TOML encoder](https://github.com/BurntSushi/toml).
|
|
||||||
In particular, it maps JSON data on `stdin` to a TOML format on `stdout`.
|
|
||||||
|
|
||||||
|
|
||||||
Compatible with TOML version
|
|
||||||
[v0.4.0](https://github.com/toml-lang/toml/blob/master/versions/en/toml-v0.4.0.md)
|
|
||||||
|
|
||||||
Compatible with `toml-test` version
|
|
||||||
[v0.2.0](https://github.com/BurntSushi/toml-test/tree/v0.2.0)
|
|
@ -1,131 +0,0 @@
|
|||||||
// Command toml-test-encoder satisfies the toml-test interface for testing
|
|
||||||
// TOML encoders. Namely, it accepts JSON on stdin and outputs TOML on stdout.
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"flag"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/BurntSushi/toml"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
log.SetFlags(0)
|
|
||||||
|
|
||||||
flag.Usage = usage
|
|
||||||
flag.Parse()
|
|
||||||
}
|
|
||||||
|
|
||||||
func usage() {
|
|
||||||
log.Printf("Usage: %s < json-file\n", path.Base(os.Args[0]))
|
|
||||||
flag.PrintDefaults()
|
|
||||||
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
if flag.NArg() != 0 {
|
|
||||||
flag.Usage()
|
|
||||||
}
|
|
||||||
|
|
||||||
var tmp interface{}
|
|
||||||
if err := json.NewDecoder(os.Stdin).Decode(&tmp); err != nil {
|
|
||||||
log.Fatalf("Error decoding JSON: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
tomlData := translate(tmp)
|
|
||||||
if err := toml.NewEncoder(os.Stdout).Encode(tomlData); err != nil {
|
|
||||||
log.Fatalf("Error encoding TOML: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func translate(typedJson interface{}) interface{} {
|
|
||||||
switch v := typedJson.(type) {
|
|
||||||
case map[string]interface{}:
|
|
||||||
if len(v) == 2 && in("type", v) && in("value", v) {
|
|
||||||
return untag(v)
|
|
||||||
}
|
|
||||||
m := make(map[string]interface{}, len(v))
|
|
||||||
for k, v2 := range v {
|
|
||||||
m[k] = translate(v2)
|
|
||||||
}
|
|
||||||
return m
|
|
||||||
case []interface{}:
|
|
||||||
tabArray := make([]map[string]interface{}, len(v))
|
|
||||||
for i := range v {
|
|
||||||
if m, ok := translate(v[i]).(map[string]interface{}); ok {
|
|
||||||
tabArray[i] = m
|
|
||||||
} else {
|
|
||||||
log.Fatalf("JSON arrays may only contain objects. This " +
|
|
||||||
"corresponds to only tables being allowed in " +
|
|
||||||
"TOML table arrays.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return tabArray
|
|
||||||
}
|
|
||||||
log.Fatalf("Unrecognized JSON format '%T'.", typedJson)
|
|
||||||
panic("unreachable")
|
|
||||||
}
|
|
||||||
|
|
||||||
func untag(typed map[string]interface{}) interface{} {
|
|
||||||
t := typed["type"].(string)
|
|
||||||
v := typed["value"]
|
|
||||||
switch t {
|
|
||||||
case "string":
|
|
||||||
return v.(string)
|
|
||||||
case "integer":
|
|
||||||
v := v.(string)
|
|
||||||
n, err := strconv.Atoi(v)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Could not parse '%s' as integer: %s", v, err)
|
|
||||||
}
|
|
||||||
return n
|
|
||||||
case "float":
|
|
||||||
v := v.(string)
|
|
||||||
f, err := strconv.ParseFloat(v, 64)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Could not parse '%s' as float64: %s", v, err)
|
|
||||||
}
|
|
||||||
return f
|
|
||||||
case "datetime":
|
|
||||||
v := v.(string)
|
|
||||||
t, err := time.Parse("2006-01-02T15:04:05Z", v)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Could not parse '%s' as a datetime: %s", v, err)
|
|
||||||
}
|
|
||||||
return t
|
|
||||||
case "bool":
|
|
||||||
v := v.(string)
|
|
||||||
switch v {
|
|
||||||
case "true":
|
|
||||||
return true
|
|
||||||
case "false":
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
log.Fatalf("Could not parse '%s' as a boolean.", v)
|
|
||||||
case "array":
|
|
||||||
v := v.([]interface{})
|
|
||||||
array := make([]interface{}, len(v))
|
|
||||||
for i := range v {
|
|
||||||
if m, ok := v[i].(map[string]interface{}); ok {
|
|
||||||
array[i] = untag(m)
|
|
||||||
} else {
|
|
||||||
log.Fatalf("Arrays may only contain other arrays or "+
|
|
||||||
"primitive values, but found a '%T'.", m)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return array
|
|
||||||
}
|
|
||||||
log.Fatalf("Unrecognized tag type '%s'.", t)
|
|
||||||
panic("unreachable")
|
|
||||||
}
|
|
||||||
|
|
||||||
func in(key string, m map[string]interface{}) bool {
|
|
||||||
_, ok := m[key]
|
|
||||||
return ok
|
|
||||||
}
|
|
@ -1,14 +0,0 @@
|
|||||||
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
|
||||||
Version 2, December 2004
|
|
||||||
|
|
||||||
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
|
|
||||||
|
|
||||||
Everyone is permitted to copy and distribute verbatim or modified
|
|
||||||
copies of this license document, and changing it is allowed as long
|
|
||||||
as the name is changed.
|
|
||||||
|
|
||||||
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
|
||||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
|
||||||
|
|
||||||
0. You just DO WHAT THE FUCK YOU WANT TO.
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
|||||||
# TOML Validator
|
|
||||||
|
|
||||||
If Go is installed, it's simple to try it out:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
go get github.com/BurntSushi/toml/cmd/tomlv
|
|
||||||
tomlv some-toml-file.toml
|
|
||||||
```
|
|
||||||
|
|
||||||
You can see the types of every key in a TOML file with:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
tomlv -types some-toml-file.toml
|
|
||||||
```
|
|
||||||
|
|
||||||
At the moment, only one error message is reported at a time. Error messages
|
|
||||||
include line numbers. No output means that the files given are valid TOML, or
|
|
||||||
there is a bug in `tomlv`.
|
|
||||||
|
|
||||||
Compatible with TOML version
|
|
||||||
[v0.4.0](https://github.com/toml-lang/toml/blob/master/versions/en/toml-v0.4.0.md)
|
|
@ -1,61 +0,0 @@
|
|||||||
// Command tomlv validates TOML documents and prints each key's type.
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"strings"
|
|
||||||
"text/tabwriter"
|
|
||||||
|
|
||||||
"github.com/BurntSushi/toml"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
flagTypes = false
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
log.SetFlags(0)
|
|
||||||
|
|
||||||
flag.BoolVar(&flagTypes, "types", flagTypes,
|
|
||||||
"When set, the types of every defined key will be shown.")
|
|
||||||
|
|
||||||
flag.Usage = usage
|
|
||||||
flag.Parse()
|
|
||||||
}
|
|
||||||
|
|
||||||
func usage() {
|
|
||||||
log.Printf("Usage: %s toml-file [ toml-file ... ]\n",
|
|
||||||
path.Base(os.Args[0]))
|
|
||||||
flag.PrintDefaults()
|
|
||||||
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
if flag.NArg() < 1 {
|
|
||||||
flag.Usage()
|
|
||||||
}
|
|
||||||
for _, f := range flag.Args() {
|
|
||||||
var tmp interface{}
|
|
||||||
md, err := toml.DecodeFile(f, &tmp)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Error in '%s': %s", f, err)
|
|
||||||
}
|
|
||||||
if flagTypes {
|
|
||||||
printTypes(md)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func printTypes(md toml.MetaData) {
|
|
||||||
tabw := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
|
|
||||||
for _, key := range md.Keys() {
|
|
||||||
fmt.Fprintf(tabw, "%s%s\t%s\n",
|
|
||||||
strings.Repeat(" ", len(key)-1), key, md.Type(key...))
|
|
||||||
}
|
|
||||||
tabw.Flush()
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
@ -1,615 +0,0 @@
|
|||||||
package toml
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"net"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestEncodeRoundTrip(t *testing.T) {
|
|
||||||
type Config struct {
|
|
||||||
Age int
|
|
||||||
Cats []string
|
|
||||||
Pi float64
|
|
||||||
Perfection []int
|
|
||||||
DOB time.Time
|
|
||||||
Ipaddress net.IP
|
|
||||||
}
|
|
||||||
|
|
||||||
var inputs = Config{
|
|
||||||
13,
|
|
||||||
[]string{"one", "two", "three"},
|
|
||||||
3.145,
|
|
||||||
[]int{11, 2, 3, 4},
|
|
||||||
time.Now(),
|
|
||||||
net.ParseIP("192.168.59.254"),
|
|
||||||
}
|
|
||||||
|
|
||||||
var firstBuffer bytes.Buffer
|
|
||||||
e := NewEncoder(&firstBuffer)
|
|
||||||
err := e.Encode(inputs)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
var outputs Config
|
|
||||||
if _, err := Decode(firstBuffer.String(), &outputs); err != nil {
|
|
||||||
t.Logf("Could not decode:\n-----\n%s\n-----\n",
|
|
||||||
firstBuffer.String())
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// could test each value individually, but I'm lazy
|
|
||||||
var secondBuffer bytes.Buffer
|
|
||||||
e2 := NewEncoder(&secondBuffer)
|
|
||||||
err = e2.Encode(outputs)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if firstBuffer.String() != secondBuffer.String() {
|
|
||||||
t.Error(
|
|
||||||
firstBuffer.String(),
|
|
||||||
"\n\n is not identical to\n\n",
|
|
||||||
secondBuffer.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// XXX(burntsushi)
|
|
||||||
// I think these tests probably should be removed. They are good, but they
|
|
||||||
// ought to be obsolete by toml-test.
|
|
||||||
func TestEncode(t *testing.T) {
|
|
||||||
type Embedded struct {
|
|
||||||
Int int `toml:"_int"`
|
|
||||||
}
|
|
||||||
type NonStruct int
|
|
||||||
|
|
||||||
date := time.Date(2014, 5, 11, 20, 30, 40, 0, time.FixedZone("IST", 3600))
|
|
||||||
dateStr := "2014-05-11T19:30:40Z"
|
|
||||||
|
|
||||||
tests := map[string]struct {
|
|
||||||
input interface{}
|
|
||||||
wantOutput string
|
|
||||||
wantError error
|
|
||||||
}{
|
|
||||||
"bool field": {
|
|
||||||
input: struct {
|
|
||||||
BoolTrue bool
|
|
||||||
BoolFalse bool
|
|
||||||
}{true, false},
|
|
||||||
wantOutput: "BoolTrue = true\nBoolFalse = false\n",
|
|
||||||
},
|
|
||||||
"int fields": {
|
|
||||||
input: struct {
|
|
||||||
Int int
|
|
||||||
Int8 int8
|
|
||||||
Int16 int16
|
|
||||||
Int32 int32
|
|
||||||
Int64 int64
|
|
||||||
}{1, 2, 3, 4, 5},
|
|
||||||
wantOutput: "Int = 1\nInt8 = 2\nInt16 = 3\nInt32 = 4\nInt64 = 5\n",
|
|
||||||
},
|
|
||||||
"uint fields": {
|
|
||||||
input: struct {
|
|
||||||
Uint uint
|
|
||||||
Uint8 uint8
|
|
||||||
Uint16 uint16
|
|
||||||
Uint32 uint32
|
|
||||||
Uint64 uint64
|
|
||||||
}{1, 2, 3, 4, 5},
|
|
||||||
wantOutput: "Uint = 1\nUint8 = 2\nUint16 = 3\nUint32 = 4" +
|
|
||||||
"\nUint64 = 5\n",
|
|
||||||
},
|
|
||||||
"float fields": {
|
|
||||||
input: struct {
|
|
||||||
Float32 float32
|
|
||||||
Float64 float64
|
|
||||||
}{1.5, 2.5},
|
|
||||||
wantOutput: "Float32 = 1.5\nFloat64 = 2.5\n",
|
|
||||||
},
|
|
||||||
"string field": {
|
|
||||||
input: struct{ String string }{"foo"},
|
|
||||||
wantOutput: "String = \"foo\"\n",
|
|
||||||
},
|
|
||||||
"string field and unexported field": {
|
|
||||||
input: struct {
|
|
||||||
String string
|
|
||||||
unexported int
|
|
||||||
}{"foo", 0},
|
|
||||||
wantOutput: "String = \"foo\"\n",
|
|
||||||
},
|
|
||||||
"datetime field in UTC": {
|
|
||||||
input: struct{ Date time.Time }{date},
|
|
||||||
wantOutput: fmt.Sprintf("Date = %s\n", dateStr),
|
|
||||||
},
|
|
||||||
"datetime field as primitive": {
|
|
||||||
// Using a map here to fail if isStructOrMap() returns true for
|
|
||||||
// time.Time.
|
|
||||||
input: map[string]interface{}{
|
|
||||||
"Date": date,
|
|
||||||
"Int": 1,
|
|
||||||
},
|
|
||||||
wantOutput: fmt.Sprintf("Date = %s\nInt = 1\n", dateStr),
|
|
||||||
},
|
|
||||||
"array fields": {
|
|
||||||
input: struct {
|
|
||||||
IntArray0 [0]int
|
|
||||||
IntArray3 [3]int
|
|
||||||
}{[0]int{}, [3]int{1, 2, 3}},
|
|
||||||
wantOutput: "IntArray0 = []\nIntArray3 = [1, 2, 3]\n",
|
|
||||||
},
|
|
||||||
"slice fields": {
|
|
||||||
input: struct{ IntSliceNil, IntSlice0, IntSlice3 []int }{
|
|
||||||
nil, []int{}, []int{1, 2, 3},
|
|
||||||
},
|
|
||||||
wantOutput: "IntSlice0 = []\nIntSlice3 = [1, 2, 3]\n",
|
|
||||||
},
|
|
||||||
"datetime slices": {
|
|
||||||
input: struct{ DatetimeSlice []time.Time }{
|
|
||||||
[]time.Time{date, date},
|
|
||||||
},
|
|
||||||
wantOutput: fmt.Sprintf("DatetimeSlice = [%s, %s]\n",
|
|
||||||
dateStr, dateStr),
|
|
||||||
},
|
|
||||||
"nested arrays and slices": {
|
|
||||||
input: struct {
|
|
||||||
SliceOfArrays [][2]int
|
|
||||||
ArrayOfSlices [2][]int
|
|
||||||
SliceOfArraysOfSlices [][2][]int
|
|
||||||
ArrayOfSlicesOfArrays [2][][2]int
|
|
||||||
SliceOfMixedArrays [][2]interface{}
|
|
||||||
ArrayOfMixedSlices [2][]interface{}
|
|
||||||
}{
|
|
||||||
[][2]int{{1, 2}, {3, 4}},
|
|
||||||
[2][]int{{1, 2}, {3, 4}},
|
|
||||||
[][2][]int{
|
|
||||||
{
|
|
||||||
{1, 2}, {3, 4},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
{5, 6}, {7, 8},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
[2][][2]int{
|
|
||||||
{
|
|
||||||
{1, 2}, {3, 4},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
{5, 6}, {7, 8},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
[][2]interface{}{
|
|
||||||
{1, 2}, {"a", "b"},
|
|
||||||
},
|
|
||||||
[2][]interface{}{
|
|
||||||
{1, 2}, {"a", "b"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
wantOutput: `SliceOfArrays = [[1, 2], [3, 4]]
|
|
||||||
ArrayOfSlices = [[1, 2], [3, 4]]
|
|
||||||
SliceOfArraysOfSlices = [[[1, 2], [3, 4]], [[5, 6], [7, 8]]]
|
|
||||||
ArrayOfSlicesOfArrays = [[[1, 2], [3, 4]], [[5, 6], [7, 8]]]
|
|
||||||
SliceOfMixedArrays = [[1, 2], ["a", "b"]]
|
|
||||||
ArrayOfMixedSlices = [[1, 2], ["a", "b"]]
|
|
||||||
`,
|
|
||||||
},
|
|
||||||
"empty slice": {
|
|
||||||
input: struct{ Empty []interface{} }{[]interface{}{}},
|
|
||||||
wantOutput: "Empty = []\n",
|
|
||||||
},
|
|
||||||
"(error) slice with element type mismatch (string and integer)": {
|
|
||||||
input: struct{ Mixed []interface{} }{[]interface{}{1, "a"}},
|
|
||||||
wantError: errArrayMixedElementTypes,
|
|
||||||
},
|
|
||||||
"(error) slice with element type mismatch (integer and float)": {
|
|
||||||
input: struct{ Mixed []interface{} }{[]interface{}{1, 2.5}},
|
|
||||||
wantError: errArrayMixedElementTypes,
|
|
||||||
},
|
|
||||||
"slice with elems of differing Go types, same TOML types": {
|
|
||||||
input: struct {
|
|
||||||
MixedInts []interface{}
|
|
||||||
MixedFloats []interface{}
|
|
||||||
}{
|
|
||||||
[]interface{}{
|
|
||||||
int(1), int8(2), int16(3), int32(4), int64(5),
|
|
||||||
uint(1), uint8(2), uint16(3), uint32(4), uint64(5),
|
|
||||||
},
|
|
||||||
[]interface{}{float32(1.5), float64(2.5)},
|
|
||||||
},
|
|
||||||
wantOutput: "MixedInts = [1, 2, 3, 4, 5, 1, 2, 3, 4, 5]\n" +
|
|
||||||
"MixedFloats = [1.5, 2.5]\n",
|
|
||||||
},
|
|
||||||
"(error) slice w/ element type mismatch (one is nested array)": {
|
|
||||||
input: struct{ Mixed []interface{} }{
|
|
||||||
[]interface{}{1, []interface{}{2}},
|
|
||||||
},
|
|
||||||
wantError: errArrayMixedElementTypes,
|
|
||||||
},
|
|
||||||
"(error) slice with 1 nil element": {
|
|
||||||
input: struct{ NilElement1 []interface{} }{[]interface{}{nil}},
|
|
||||||
wantError: errArrayNilElement,
|
|
||||||
},
|
|
||||||
"(error) slice with 1 nil element (and other non-nil elements)": {
|
|
||||||
input: struct{ NilElement []interface{} }{
|
|
||||||
[]interface{}{1, nil},
|
|
||||||
},
|
|
||||||
wantError: errArrayNilElement,
|
|
||||||
},
|
|
||||||
"simple map": {
|
|
||||||
input: map[string]int{"a": 1, "b": 2},
|
|
||||||
wantOutput: "a = 1\nb = 2\n",
|
|
||||||
},
|
|
||||||
"map with interface{} value type": {
|
|
||||||
input: map[string]interface{}{"a": 1, "b": "c"},
|
|
||||||
wantOutput: "a = 1\nb = \"c\"\n",
|
|
||||||
},
|
|
||||||
"map with interface{} value type, some of which are structs": {
|
|
||||||
input: map[string]interface{}{
|
|
||||||
"a": struct{ Int int }{2},
|
|
||||||
"b": 1,
|
|
||||||
},
|
|
||||||
wantOutput: "b = 1\n\n[a]\n Int = 2\n",
|
|
||||||
},
|
|
||||||
"nested map": {
|
|
||||||
input: map[string]map[string]int{
|
|
||||||
"a": {"b": 1},
|
|
||||||
"c": {"d": 2},
|
|
||||||
},
|
|
||||||
wantOutput: "[a]\n b = 1\n\n[c]\n d = 2\n",
|
|
||||||
},
|
|
||||||
"nested struct": {
|
|
||||||
input: struct{ Struct struct{ Int int } }{
|
|
||||||
struct{ Int int }{1},
|
|
||||||
},
|
|
||||||
wantOutput: "[Struct]\n Int = 1\n",
|
|
||||||
},
|
|
||||||
"nested struct and non-struct field": {
|
|
||||||
input: struct {
|
|
||||||
Struct struct{ Int int }
|
|
||||||
Bool bool
|
|
||||||
}{struct{ Int int }{1}, true},
|
|
||||||
wantOutput: "Bool = true\n\n[Struct]\n Int = 1\n",
|
|
||||||
},
|
|
||||||
"2 nested structs": {
|
|
||||||
input: struct{ Struct1, Struct2 struct{ Int int } }{
|
|
||||||
struct{ Int int }{1}, struct{ Int int }{2},
|
|
||||||
},
|
|
||||||
wantOutput: "[Struct1]\n Int = 1\n\n[Struct2]\n Int = 2\n",
|
|
||||||
},
|
|
||||||
"deeply nested structs": {
|
|
||||||
input: struct {
|
|
||||||
Struct1, Struct2 struct{ Struct3 *struct{ Int int } }
|
|
||||||
}{
|
|
||||||
struct{ Struct3 *struct{ Int int } }{&struct{ Int int }{1}},
|
|
||||||
struct{ Struct3 *struct{ Int int } }{nil},
|
|
||||||
},
|
|
||||||
wantOutput: "[Struct1]\n [Struct1.Struct3]\n Int = 1" +
|
|
||||||
"\n\n[Struct2]\n",
|
|
||||||
},
|
|
||||||
"nested struct with nil struct elem": {
|
|
||||||
input: struct {
|
|
||||||
Struct struct{ Inner *struct{ Int int } }
|
|
||||||
}{
|
|
||||||
struct{ Inner *struct{ Int int } }{nil},
|
|
||||||
},
|
|
||||||
wantOutput: "[Struct]\n",
|
|
||||||
},
|
|
||||||
"nested struct with no fields": {
|
|
||||||
input: struct {
|
|
||||||
Struct struct{ Inner struct{} }
|
|
||||||
}{
|
|
||||||
struct{ Inner struct{} }{struct{}{}},
|
|
||||||
},
|
|
||||||
wantOutput: "[Struct]\n [Struct.Inner]\n",
|
|
||||||
},
|
|
||||||
"struct with tags": {
|
|
||||||
input: struct {
|
|
||||||
Struct struct {
|
|
||||||
Int int `toml:"_int"`
|
|
||||||
} `toml:"_struct"`
|
|
||||||
Bool bool `toml:"_bool"`
|
|
||||||
}{
|
|
||||||
struct {
|
|
||||||
Int int `toml:"_int"`
|
|
||||||
}{1}, true,
|
|
||||||
},
|
|
||||||
wantOutput: "_bool = true\n\n[_struct]\n _int = 1\n",
|
|
||||||
},
|
|
||||||
"embedded struct": {
|
|
||||||
input: struct{ Embedded }{Embedded{1}},
|
|
||||||
wantOutput: "_int = 1\n",
|
|
||||||
},
|
|
||||||
"embedded *struct": {
|
|
||||||
input: struct{ *Embedded }{&Embedded{1}},
|
|
||||||
wantOutput: "_int = 1\n",
|
|
||||||
},
|
|
||||||
"nested embedded struct": {
|
|
||||||
input: struct {
|
|
||||||
Struct struct{ Embedded } `toml:"_struct"`
|
|
||||||
}{struct{ Embedded }{Embedded{1}}},
|
|
||||||
wantOutput: "[_struct]\n _int = 1\n",
|
|
||||||
},
|
|
||||||
"nested embedded *struct": {
|
|
||||||
input: struct {
|
|
||||||
Struct struct{ *Embedded } `toml:"_struct"`
|
|
||||||
}{struct{ *Embedded }{&Embedded{1}}},
|
|
||||||
wantOutput: "[_struct]\n _int = 1\n",
|
|
||||||
},
|
|
||||||
"embedded non-struct": {
|
|
||||||
input: struct{ NonStruct }{5},
|
|
||||||
wantOutput: "NonStruct = 5\n",
|
|
||||||
},
|
|
||||||
"array of tables": {
|
|
||||||
input: struct {
|
|
||||||
Structs []*struct{ Int int } `toml:"struct"`
|
|
||||||
}{
|
|
||||||
[]*struct{ Int int }{{1}, {3}},
|
|
||||||
},
|
|
||||||
wantOutput: "[[struct]]\n Int = 1\n\n[[struct]]\n Int = 3\n",
|
|
||||||
},
|
|
||||||
"array of tables order": {
|
|
||||||
input: map[string]interface{}{
|
|
||||||
"map": map[string]interface{}{
|
|
||||||
"zero": 5,
|
|
||||||
"arr": []map[string]int{
|
|
||||||
{
|
|
||||||
"friend": 5,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
wantOutput: "[map]\n zero = 5\n\n [[map.arr]]\n friend = 5\n",
|
|
||||||
},
|
|
||||||
"(error) top-level slice": {
|
|
||||||
input: []struct{ Int int }{{1}, {2}, {3}},
|
|
||||||
wantError: errNoKey,
|
|
||||||
},
|
|
||||||
"(error) slice of slice": {
|
|
||||||
input: struct {
|
|
||||||
Slices [][]struct{ Int int }
|
|
||||||
}{
|
|
||||||
[][]struct{ Int int }{{{1}}, {{2}}, {{3}}},
|
|
||||||
},
|
|
||||||
wantError: errArrayNoTable,
|
|
||||||
},
|
|
||||||
"(error) map no string key": {
|
|
||||||
input: map[int]string{1: ""},
|
|
||||||
wantError: errNonString,
|
|
||||||
},
|
|
||||||
"(error) empty key name": {
|
|
||||||
input: map[string]int{"": 1},
|
|
||||||
wantError: errAnything,
|
|
||||||
},
|
|
||||||
"(error) empty map name": {
|
|
||||||
input: map[string]interface{}{
|
|
||||||
"": map[string]int{"v": 1},
|
|
||||||
},
|
|
||||||
wantError: errAnything,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for label, test := range tests {
|
|
||||||
encodeExpected(t, label, test.input, test.wantOutput, test.wantError)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEncodeNestedTableArrays(t *testing.T) {
|
|
||||||
type song struct {
|
|
||||||
Name string `toml:"name"`
|
|
||||||
}
|
|
||||||
type album struct {
|
|
||||||
Name string `toml:"name"`
|
|
||||||
Songs []song `toml:"songs"`
|
|
||||||
}
|
|
||||||
type springsteen struct {
|
|
||||||
Albums []album `toml:"albums"`
|
|
||||||
}
|
|
||||||
value := springsteen{
|
|
||||||
[]album{
|
|
||||||
{"Born to Run",
|
|
||||||
[]song{{"Jungleland"}, {"Meeting Across the River"}}},
|
|
||||||
{"Born in the USA",
|
|
||||||
[]song{{"Glory Days"}, {"Dancing in the Dark"}}},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
expected := `[[albums]]
|
|
||||||
name = "Born to Run"
|
|
||||||
|
|
||||||
[[albums.songs]]
|
|
||||||
name = "Jungleland"
|
|
||||||
|
|
||||||
[[albums.songs]]
|
|
||||||
name = "Meeting Across the River"
|
|
||||||
|
|
||||||
[[albums]]
|
|
||||||
name = "Born in the USA"
|
|
||||||
|
|
||||||
[[albums.songs]]
|
|
||||||
name = "Glory Days"
|
|
||||||
|
|
||||||
[[albums.songs]]
|
|
||||||
name = "Dancing in the Dark"
|
|
||||||
`
|
|
||||||
encodeExpected(t, "nested table arrays", value, expected, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEncodeArrayHashWithNormalHashOrder(t *testing.T) {
|
|
||||||
type Alpha struct {
|
|
||||||
V int
|
|
||||||
}
|
|
||||||
type Beta struct {
|
|
||||||
V int
|
|
||||||
}
|
|
||||||
type Conf struct {
|
|
||||||
V int
|
|
||||||
A Alpha
|
|
||||||
B []Beta
|
|
||||||
}
|
|
||||||
|
|
||||||
val := Conf{
|
|
||||||
V: 1,
|
|
||||||
A: Alpha{2},
|
|
||||||
B: []Beta{{3}},
|
|
||||||
}
|
|
||||||
expected := "V = 1\n\n[A]\n V = 2\n\n[[B]]\n V = 3\n"
|
|
||||||
encodeExpected(t, "array hash with normal hash order", val, expected, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEncodeWithOmitEmpty(t *testing.T) {
|
|
||||||
type simple struct {
|
|
||||||
Bool bool `toml:"bool,omitempty"`
|
|
||||||
String string `toml:"string,omitempty"`
|
|
||||||
Array [0]byte `toml:"array,omitempty"`
|
|
||||||
Slice []int `toml:"slice,omitempty"`
|
|
||||||
Map map[string]string `toml:"map,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
var v simple
|
|
||||||
encodeExpected(t, "fields with omitempty are omitted when empty", v, "", nil)
|
|
||||||
v = simple{
|
|
||||||
Bool: true,
|
|
||||||
String: " ",
|
|
||||||
Slice: []int{2, 3, 4},
|
|
||||||
Map: map[string]string{"foo": "bar"},
|
|
||||||
}
|
|
||||||
expected := `bool = true
|
|
||||||
string = " "
|
|
||||||
slice = [2, 3, 4]
|
|
||||||
|
|
||||||
[map]
|
|
||||||
foo = "bar"
|
|
||||||
`
|
|
||||||
encodeExpected(t, "fields with omitempty are not omitted when non-empty",
|
|
||||||
v, expected, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEncodeWithOmitZero(t *testing.T) {
|
|
||||||
type simple struct {
|
|
||||||
Number int `toml:"number,omitzero"`
|
|
||||||
Real float64 `toml:"real,omitzero"`
|
|
||||||
Unsigned uint `toml:"unsigned,omitzero"`
|
|
||||||
}
|
|
||||||
|
|
||||||
value := simple{0, 0.0, uint(0)}
|
|
||||||
expected := ""
|
|
||||||
|
|
||||||
encodeExpected(t, "simple with omitzero, all zero", value, expected, nil)
|
|
||||||
|
|
||||||
value.Number = 10
|
|
||||||
value.Real = 20
|
|
||||||
value.Unsigned = 5
|
|
||||||
expected = `number = 10
|
|
||||||
real = 20.0
|
|
||||||
unsigned = 5
|
|
||||||
`
|
|
||||||
encodeExpected(t, "simple with omitzero, non-zero", value, expected, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEncodeOmitemptyWithEmptyName(t *testing.T) {
|
|
||||||
type simple struct {
|
|
||||||
S []int `toml:",omitempty"`
|
|
||||||
}
|
|
||||||
v := simple{[]int{1, 2, 3}}
|
|
||||||
expected := "S = [1, 2, 3]\n"
|
|
||||||
encodeExpected(t, "simple with omitempty, no name, non-empty field",
|
|
||||||
v, expected, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEncodeAnonymousStruct(t *testing.T) {
|
|
||||||
type Inner struct{ N int }
|
|
||||||
type Outer0 struct{ Inner }
|
|
||||||
type Outer1 struct {
|
|
||||||
Inner `toml:"inner"`
|
|
||||||
}
|
|
||||||
|
|
||||||
v0 := Outer0{Inner{3}}
|
|
||||||
expected := "N = 3\n"
|
|
||||||
encodeExpected(t, "embedded anonymous untagged struct", v0, expected, nil)
|
|
||||||
|
|
||||||
v1 := Outer1{Inner{3}}
|
|
||||||
expected = "[inner]\n N = 3\n"
|
|
||||||
encodeExpected(t, "embedded anonymous tagged struct", v1, expected, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEncodeAnonymousStructPointerField(t *testing.T) {
|
|
||||||
type Inner struct{ N int }
|
|
||||||
type Outer0 struct{ *Inner }
|
|
||||||
type Outer1 struct {
|
|
||||||
*Inner `toml:"inner"`
|
|
||||||
}
|
|
||||||
|
|
||||||
v0 := Outer0{}
|
|
||||||
expected := ""
|
|
||||||
encodeExpected(t, "nil anonymous untagged struct pointer field", v0, expected, nil)
|
|
||||||
|
|
||||||
v0 = Outer0{&Inner{3}}
|
|
||||||
expected = "N = 3\n"
|
|
||||||
encodeExpected(t, "non-nil anonymous untagged struct pointer field", v0, expected, nil)
|
|
||||||
|
|
||||||
v1 := Outer1{}
|
|
||||||
expected = ""
|
|
||||||
encodeExpected(t, "nil anonymous tagged struct pointer field", v1, expected, nil)
|
|
||||||
|
|
||||||
v1 = Outer1{&Inner{3}}
|
|
||||||
expected = "[inner]\n N = 3\n"
|
|
||||||
encodeExpected(t, "non-nil anonymous tagged struct pointer field", v1, expected, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEncodeIgnoredFields(t *testing.T) {
|
|
||||||
type simple struct {
|
|
||||||
Number int `toml:"-"`
|
|
||||||
}
|
|
||||||
value := simple{}
|
|
||||||
expected := ""
|
|
||||||
encodeExpected(t, "ignored field", value, expected, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func encodeExpected(
|
|
||||||
t *testing.T, label string, val interface{}, wantStr string, wantErr error,
|
|
||||||
) {
|
|
||||||
var buf bytes.Buffer
|
|
||||||
enc := NewEncoder(&buf)
|
|
||||||
err := enc.Encode(val)
|
|
||||||
if err != wantErr {
|
|
||||||
if wantErr != nil {
|
|
||||||
if wantErr == errAnything && err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
t.Errorf("%s: want Encode error %v, got %v", label, wantErr, err)
|
|
||||||
} else {
|
|
||||||
t.Errorf("%s: Encode failed: %s", label, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if got := buf.String(); wantStr != got {
|
|
||||||
t.Errorf("%s: want\n-----\n%q\n-----\nbut got\n-----\n%q\n-----\n",
|
|
||||||
label, wantStr, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleEncoder_Encode() {
|
|
||||||
date, _ := time.Parse(time.RFC822, "14 Mar 10 18:00 UTC")
|
|
||||||
var config = map[string]interface{}{
|
|
||||||
"date": date,
|
|
||||||
"counts": []int{1, 1, 2, 3, 5, 8},
|
|
||||||
"hash": map[string]string{
|
|
||||||
"key1": "val1",
|
|
||||||
"key2": "val2",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
buf := new(bytes.Buffer)
|
|
||||||
if err := NewEncoder(buf).Encode(config); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
fmt.Println(buf.String())
|
|
||||||
|
|
||||||
// Output:
|
|
||||||
// counts = [1, 1, 2, 3, 5, 8]
|
|
||||||
// date = 2010-03-14T18:00:00Z
|
|
||||||
//
|
|
||||||
// [hash]
|
|
||||||
// key1 = "val1"
|
|
||||||
// key2 = "val2"
|
|
||||||
}
|
|
@ -1,38 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/anaskhan96/soup"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
fmt.Printf("Enter the name of the city : ")
|
|
||||||
city, _ := bufio.NewReader(os.Stdin).ReadString('\n')
|
|
||||||
city = city[:len(city)-1]
|
|
||||||
cityInURL := strings.Join(strings.Split(city, " "), "+")
|
|
||||||
url := "https://www.bing.com/search?q=weather+" + cityInURL
|
|
||||||
resp, err := soup.Get(url)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
doc := soup.HTMLParse(resp)
|
|
||||||
grid := doc.Find("div", "class", "b_antiTopBleed b_antiSideBleed b_antiBottomBleed")
|
|
||||||
heading := grid.Find("div", "class", "wtr_titleCtrn").Find("div").Text()
|
|
||||||
conditions := grid.Find("div", "class", "wtr_condition")
|
|
||||||
primaryCondition := conditions.Find("div")
|
|
||||||
secondaryCondition := primaryCondition.FindNextElementSibling()
|
|
||||||
temp := primaryCondition.Find("div", "class", "wtr_condiTemp").Find("div").Text()
|
|
||||||
others := primaryCondition.Find("div", "class", "wtr_condiAttribs").FindAll("div")
|
|
||||||
caption := secondaryCondition.Find("div").Text()
|
|
||||||
fmt.Println("City Name : " + heading)
|
|
||||||
fmt.Println("Temperature : " + temp + "˚C")
|
|
||||||
for _, i := range others {
|
|
||||||
fmt.Println(i.Text())
|
|
||||||
}
|
|
||||||
fmt.Println(caption)
|
|
||||||
}
|
|
@ -1,29 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/anaskhan96/soup"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
fmt.Println("Enter the xkcd comic number :")
|
|
||||||
var num int
|
|
||||||
fmt.Scanf("%d", &num)
|
|
||||||
url := fmt.Sprintf("https://xkcd.com/%d", num)
|
|
||||||
resp, _ := soup.Get(url)
|
|
||||||
doc := soup.HTMLParse(resp)
|
|
||||||
title := doc.Find("div", "id", "ctitle").Text()
|
|
||||||
fmt.Println("Title of the comic :", title)
|
|
||||||
comicImg := doc.Find("div", "id", "comic").Find("img")
|
|
||||||
fmt.Println("Source of the image :", comicImg.Attrs()["src"])
|
|
||||||
fmt.Println("Underlying text of the image :", comicImg.Attrs()["title"])
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --- Console I/O ---
|
|
||||||
Enter the xkcd comic number :
|
|
||||||
353
|
|
||||||
Title of the comic : Python
|
|
||||||
Source of the image : //imgs.xkcd.com/comics/python.png
|
|
||||||
Underlying text of the image : I wrote 20 short programs in Python yesterday. It was wonderful. Perl, I'm leaving you.
|
|
||||||
*/
|
|
@ -1,169 +0,0 @@
|
|||||||
package soup
|
|
||||||
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
const testHTML = `
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>Sample "Hello, World" Application</title>
|
|
||||||
</head>
|
|
||||||
<body bgcolor=white>
|
|
||||||
|
|
||||||
<table border="0" cellpadding="10">
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<img src="images/springsource.png">
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<h1>Sample "Hello, World" Application</h1>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
<div id="0">
|
|
||||||
<div id="1">Just two divs peacing out</div>
|
|
||||||
</div>
|
|
||||||
check
|
|
||||||
<div id="2">One more</div>
|
|
||||||
<p>This is the home page for the HelloWorld Web application. </p>
|
|
||||||
<p>To prove that they work, you can execute either of the following links:
|
|
||||||
<ul>
|
|
||||||
<li>To a <a href="hello.jsp">JSP page</a></li>
|
|
||||||
<li>To a <a href="hello">servlet</a></li>
|
|
||||||
</ul>
|
|
||||||
</p>
|
|
||||||
<div id="3">
|
|
||||||
<div id="4">Last one</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
`
|
|
||||||
|
|
||||||
const multipleClassesHTML = `
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>Sample Application</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="first second">Multiple classes</div>
|
|
||||||
<div class="first">Single class</div>
|
|
||||||
<div class="second first third">Multiple classes inorder</div>
|
|
||||||
<div>
|
|
||||||
<div class="first">Inner single class</div>
|
|
||||||
<div class="first second">Inner multiple classes</div>
|
|
||||||
<div class="second first">Inner multiple classes inorder</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
`
|
|
||||||
|
|
||||||
var doc = HTMLParse(testHTML)
|
|
||||||
var multipleClasses = HTMLParse(multipleClassesHTML)
|
|
||||||
|
|
||||||
func TestFind(t *testing.T) {
|
|
||||||
// Find() and Attrs()
|
|
||||||
actual := doc.Find("img").Attrs()["src"]
|
|
||||||
if !reflect.DeepEqual(actual, "images/springsource.png") {
|
|
||||||
t.Error("Instead of `images/springsource.png`, got", actual)
|
|
||||||
}
|
|
||||||
// Find(...) and Text()
|
|
||||||
actual = doc.Find("a", "href", "hello").Text()
|
|
||||||
if !reflect.DeepEqual(actual, "servlet") {
|
|
||||||
t.Error("Instead of `servlet`, got", actual)
|
|
||||||
}
|
|
||||||
// Nested Find()
|
|
||||||
actual = doc.Find("div").Find("div").Text()
|
|
||||||
if !reflect.DeepEqual(actual, "Just two divs peacing out") {
|
|
||||||
t.Error("Instead of `Just two divs peacing out`, got", actual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFindNextPrevElement(t *testing.T) {
|
|
||||||
// FindNextSibling() and NodeValue field
|
|
||||||
actual := doc.Find("div", "id", "0").FindNextSibling().NodeValue
|
|
||||||
if !reflect.DeepEqual(strings.TrimSpace(actual), "check") {
|
|
||||||
t.Error("Instead of `check`, got", actual)
|
|
||||||
}
|
|
||||||
// FindPrevSibling() and NodeValue field
|
|
||||||
actual = doc.Find("div", "id", "2").FindPrevSibling().NodeValue
|
|
||||||
if !reflect.DeepEqual(strings.TrimSpace(actual), "check") {
|
|
||||||
t.Error("Instead of `check`, got", actual)
|
|
||||||
}
|
|
||||||
// FindNextElementSibling() and NodeValue field
|
|
||||||
actual = doc.Find("table").FindNextElementSibling().NodeValue
|
|
||||||
if !reflect.DeepEqual(actual, "div") {
|
|
||||||
t.Error("Instead of `div`, got", actual)
|
|
||||||
}
|
|
||||||
// FindPrevElementSibling() and NodeValue field
|
|
||||||
actual = doc.Find("p").FindPrevElementSibling().NodeValue
|
|
||||||
if !reflect.DeepEqual(actual, "div") {
|
|
||||||
t.Error("Instead of `div`, got", actual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFindAll(t *testing.T) {
|
|
||||||
// FindAll() and Attrs()
|
|
||||||
allDivs := doc.FindAll("div")
|
|
||||||
for i := 0; i < len(allDivs); i++ {
|
|
||||||
id := allDivs[i].Attrs()["id"]
|
|
||||||
actual, _ := strconv.Atoi(id)
|
|
||||||
if !reflect.DeepEqual(actual, i) {
|
|
||||||
t.Error("Instead of", i, "got", actual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFindAllBySingleClass(t *testing.T) {
|
|
||||||
actual := multipleClasses.FindAll("div", "class", "first")
|
|
||||||
if len(actual) != 6 {
|
|
||||||
t.Errorf("Expected 6 elements to be returned. Actual: %d", len(actual))
|
|
||||||
}
|
|
||||||
actual = multipleClasses.FindAll("div", "class", "third")
|
|
||||||
if len(actual) != 1 {
|
|
||||||
t.Errorf("Expected 1 element to be returned. Actual: %d", len(actual))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFindBySingleClass(t *testing.T) {
|
|
||||||
actual := multipleClasses.Find("div", "class", "first")
|
|
||||||
if actual.Text() != "Multiple classes" {
|
|
||||||
t.Errorf("Wrong element returned: %s", actual.Text())
|
|
||||||
}
|
|
||||||
actual = multipleClasses.Find("div", "class", "third")
|
|
||||||
if actual.Text() != "Multiple classes inorder" {
|
|
||||||
t.Errorf("Wrong element returned: %s", actual.Text())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFindAllStrict(t *testing.T) {
|
|
||||||
actual := multipleClasses.FindAllStrict("div", "class", "first second")
|
|
||||||
if len(actual) != 2 {
|
|
||||||
t.Errorf("Expected 2 elements to be returned. Actual: %d", len(actual))
|
|
||||||
}
|
|
||||||
actual = multipleClasses.FindAllStrict("div", "class", "first third second")
|
|
||||||
if len(actual) != 0 {
|
|
||||||
t.Errorf("0 elements should be returned")
|
|
||||||
}
|
|
||||||
|
|
||||||
actual = multipleClasses.FindAllStrict("div", "class", "second first third")
|
|
||||||
if len(actual) != 1 {
|
|
||||||
t.Errorf("Single item should be returned")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFindStrict(t *testing.T) {
|
|
||||||
actual := multipleClasses.FindStrict("div", "class", "first")
|
|
||||||
if actual.Text() != "Single class" {
|
|
||||||
t.Errorf("Wrong element returned: %s", actual.Text())
|
|
||||||
}
|
|
||||||
|
|
||||||
actual = multipleClasses.FindStrict("div", "class", "third")
|
|
||||||
if actual.Error == nil {
|
|
||||||
t.Errorf("Element with class \"third\" should not be returned in strict mode")
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,342 +0,0 @@
|
|||||||
package color
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/mattn/go-colorable"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Testing colors is kinda different. First we test for given colors and their
|
|
||||||
// escaped formatted results. Next we create some visual tests to be tested.
|
|
||||||
// Each visual test includes the color name to be compared.
|
|
||||||
func TestColor(t *testing.T) {
|
|
||||||
rb := new(bytes.Buffer)
|
|
||||||
Output = rb
|
|
||||||
|
|
||||||
NoColor = false
|
|
||||||
|
|
||||||
testColors := []struct {
|
|
||||||
text string
|
|
||||||
code Attribute
|
|
||||||
}{
|
|
||||||
{text: "black", code: FgBlack},
|
|
||||||
{text: "red", code: FgRed},
|
|
||||||
{text: "green", code: FgGreen},
|
|
||||||
{text: "yellow", code: FgYellow},
|
|
||||||
{text: "blue", code: FgBlue},
|
|
||||||
{text: "magent", code: FgMagenta},
|
|
||||||
{text: "cyan", code: FgCyan},
|
|
||||||
{text: "white", code: FgWhite},
|
|
||||||
{text: "hblack", code: FgHiBlack},
|
|
||||||
{text: "hred", code: FgHiRed},
|
|
||||||
{text: "hgreen", code: FgHiGreen},
|
|
||||||
{text: "hyellow", code: FgHiYellow},
|
|
||||||
{text: "hblue", code: FgHiBlue},
|
|
||||||
{text: "hmagent", code: FgHiMagenta},
|
|
||||||
{text: "hcyan", code: FgHiCyan},
|
|
||||||
{text: "hwhite", code: FgHiWhite},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, c := range testColors {
|
|
||||||
New(c.code).Print(c.text)
|
|
||||||
|
|
||||||
line, _ := rb.ReadString('\n')
|
|
||||||
scannedLine := fmt.Sprintf("%q", line)
|
|
||||||
colored := fmt.Sprintf("\x1b[%dm%s\x1b[0m", c.code, c.text)
|
|
||||||
escapedForm := fmt.Sprintf("%q", colored)
|
|
||||||
|
|
||||||
fmt.Printf("%s\t: %s\n", c.text, line)
|
|
||||||
|
|
||||||
if scannedLine != escapedForm {
|
|
||||||
t.Errorf("Expecting %s, got '%s'\n", escapedForm, scannedLine)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, c := range testColors {
|
|
||||||
line := New(c.code).Sprintf("%s", c.text)
|
|
||||||
scannedLine := fmt.Sprintf("%q", line)
|
|
||||||
colored := fmt.Sprintf("\x1b[%dm%s\x1b[0m", c.code, c.text)
|
|
||||||
escapedForm := fmt.Sprintf("%q", colored)
|
|
||||||
|
|
||||||
fmt.Printf("%s\t: %s\n", c.text, line)
|
|
||||||
|
|
||||||
if scannedLine != escapedForm {
|
|
||||||
t.Errorf("Expecting %s, got '%s'\n", escapedForm, scannedLine)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestColorEquals(t *testing.T) {
|
|
||||||
fgblack1 := New(FgBlack)
|
|
||||||
fgblack2 := New(FgBlack)
|
|
||||||
bgblack := New(BgBlack)
|
|
||||||
fgbgblack := New(FgBlack, BgBlack)
|
|
||||||
fgblackbgred := New(FgBlack, BgRed)
|
|
||||||
fgred := New(FgRed)
|
|
||||||
bgred := New(BgRed)
|
|
||||||
|
|
||||||
if !fgblack1.Equals(fgblack2) {
|
|
||||||
t.Error("Two black colors are not equal")
|
|
||||||
}
|
|
||||||
|
|
||||||
if fgblack1.Equals(bgblack) {
|
|
||||||
t.Error("Fg and bg black colors are equal")
|
|
||||||
}
|
|
||||||
|
|
||||||
if fgblack1.Equals(fgbgblack) {
|
|
||||||
t.Error("Fg black equals fg/bg black color")
|
|
||||||
}
|
|
||||||
|
|
||||||
if fgblack1.Equals(fgred) {
|
|
||||||
t.Error("Fg black equals Fg red")
|
|
||||||
}
|
|
||||||
|
|
||||||
if fgblack1.Equals(bgred) {
|
|
||||||
t.Error("Fg black equals Bg red")
|
|
||||||
}
|
|
||||||
|
|
||||||
if fgblack1.Equals(fgblackbgred) {
|
|
||||||
t.Error("Fg black equals fg black bg red")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNoColor(t *testing.T) {
|
|
||||||
rb := new(bytes.Buffer)
|
|
||||||
Output = rb
|
|
||||||
|
|
||||||
testColors := []struct {
|
|
||||||
text string
|
|
||||||
code Attribute
|
|
||||||
}{
|
|
||||||
{text: "black", code: FgBlack},
|
|
||||||
{text: "red", code: FgRed},
|
|
||||||
{text: "green", code: FgGreen},
|
|
||||||
{text: "yellow", code: FgYellow},
|
|
||||||
{text: "blue", code: FgBlue},
|
|
||||||
{text: "magent", code: FgMagenta},
|
|
||||||
{text: "cyan", code: FgCyan},
|
|
||||||
{text: "white", code: FgWhite},
|
|
||||||
{text: "hblack", code: FgHiBlack},
|
|
||||||
{text: "hred", code: FgHiRed},
|
|
||||||
{text: "hgreen", code: FgHiGreen},
|
|
||||||
{text: "hyellow", code: FgHiYellow},
|
|
||||||
{text: "hblue", code: FgHiBlue},
|
|
||||||
{text: "hmagent", code: FgHiMagenta},
|
|
||||||
{text: "hcyan", code: FgHiCyan},
|
|
||||||
{text: "hwhite", code: FgHiWhite},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, c := range testColors {
|
|
||||||
p := New(c.code)
|
|
||||||
p.DisableColor()
|
|
||||||
p.Print(c.text)
|
|
||||||
|
|
||||||
line, _ := rb.ReadString('\n')
|
|
||||||
if line != c.text {
|
|
||||||
t.Errorf("Expecting %s, got '%s'\n", c.text, line)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// global check
|
|
||||||
NoColor = true
|
|
||||||
defer func() {
|
|
||||||
NoColor = false
|
|
||||||
}()
|
|
||||||
for _, c := range testColors {
|
|
||||||
p := New(c.code)
|
|
||||||
p.Print(c.text)
|
|
||||||
|
|
||||||
line, _ := rb.ReadString('\n')
|
|
||||||
if line != c.text {
|
|
||||||
t.Errorf("Expecting %s, got '%s'\n", c.text, line)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestColorVisual(t *testing.T) {
|
|
||||||
// First Visual Test
|
|
||||||
Output = colorable.NewColorableStdout()
|
|
||||||
|
|
||||||
New(FgRed).Printf("red\t")
|
|
||||||
New(BgRed).Print(" ")
|
|
||||||
New(FgRed, Bold).Println(" red")
|
|
||||||
|
|
||||||
New(FgGreen).Printf("green\t")
|
|
||||||
New(BgGreen).Print(" ")
|
|
||||||
New(FgGreen, Bold).Println(" green")
|
|
||||||
|
|
||||||
New(FgYellow).Printf("yellow\t")
|
|
||||||
New(BgYellow).Print(" ")
|
|
||||||
New(FgYellow, Bold).Println(" yellow")
|
|
||||||
|
|
||||||
New(FgBlue).Printf("blue\t")
|
|
||||||
New(BgBlue).Print(" ")
|
|
||||||
New(FgBlue, Bold).Println(" blue")
|
|
||||||
|
|
||||||
New(FgMagenta).Printf("magenta\t")
|
|
||||||
New(BgMagenta).Print(" ")
|
|
||||||
New(FgMagenta, Bold).Println(" magenta")
|
|
||||||
|
|
||||||
New(FgCyan).Printf("cyan\t")
|
|
||||||
New(BgCyan).Print(" ")
|
|
||||||
New(FgCyan, Bold).Println(" cyan")
|
|
||||||
|
|
||||||
New(FgWhite).Printf("white\t")
|
|
||||||
New(BgWhite).Print(" ")
|
|
||||||
New(FgWhite, Bold).Println(" white")
|
|
||||||
fmt.Println("")
|
|
||||||
|
|
||||||
// Second Visual test
|
|
||||||
Black("black")
|
|
||||||
Red("red")
|
|
||||||
Green("green")
|
|
||||||
Yellow("yellow")
|
|
||||||
Blue("blue")
|
|
||||||
Magenta("magenta")
|
|
||||||
Cyan("cyan")
|
|
||||||
White("white")
|
|
||||||
HiBlack("hblack")
|
|
||||||
HiRed("hred")
|
|
||||||
HiGreen("hgreen")
|
|
||||||
HiYellow("hyellow")
|
|
||||||
HiBlue("hblue")
|
|
||||||
HiMagenta("hmagenta")
|
|
||||||
HiCyan("hcyan")
|
|
||||||
HiWhite("hwhite")
|
|
||||||
|
|
||||||
// Third visual test
|
|
||||||
fmt.Println()
|
|
||||||
Set(FgBlue)
|
|
||||||
fmt.Println("is this blue?")
|
|
||||||
Unset()
|
|
||||||
|
|
||||||
Set(FgMagenta)
|
|
||||||
fmt.Println("and this magenta?")
|
|
||||||
Unset()
|
|
||||||
|
|
||||||
// Fourth Visual test
|
|
||||||
fmt.Println()
|
|
||||||
blue := New(FgBlue).PrintlnFunc()
|
|
||||||
blue("blue text with custom print func")
|
|
||||||
|
|
||||||
red := New(FgRed).PrintfFunc()
|
|
||||||
red("red text with a printf func: %d\n", 123)
|
|
||||||
|
|
||||||
put := New(FgYellow).SprintFunc()
|
|
||||||
warn := New(FgRed).SprintFunc()
|
|
||||||
|
|
||||||
fmt.Fprintf(Output, "this is a %s and this is %s.\n", put("warning"), warn("error"))
|
|
||||||
|
|
||||||
info := New(FgWhite, BgGreen).SprintFunc()
|
|
||||||
fmt.Fprintf(Output, "this %s rocks!\n", info("package"))
|
|
||||||
|
|
||||||
notice := New(FgBlue).FprintFunc()
|
|
||||||
notice(os.Stderr, "just a blue notice to stderr")
|
|
||||||
|
|
||||||
// Fifth Visual Test
|
|
||||||
fmt.Println()
|
|
||||||
|
|
||||||
fmt.Fprintln(Output, BlackString("black"))
|
|
||||||
fmt.Fprintln(Output, RedString("red"))
|
|
||||||
fmt.Fprintln(Output, GreenString("green"))
|
|
||||||
fmt.Fprintln(Output, YellowString("yellow"))
|
|
||||||
fmt.Fprintln(Output, BlueString("blue"))
|
|
||||||
fmt.Fprintln(Output, MagentaString("magenta"))
|
|
||||||
fmt.Fprintln(Output, CyanString("cyan"))
|
|
||||||
fmt.Fprintln(Output, WhiteString("white"))
|
|
||||||
fmt.Fprintln(Output, HiBlackString("hblack"))
|
|
||||||
fmt.Fprintln(Output, HiRedString("hred"))
|
|
||||||
fmt.Fprintln(Output, HiGreenString("hgreen"))
|
|
||||||
fmt.Fprintln(Output, HiYellowString("hyellow"))
|
|
||||||
fmt.Fprintln(Output, HiBlueString("hblue"))
|
|
||||||
fmt.Fprintln(Output, HiMagentaString("hmagenta"))
|
|
||||||
fmt.Fprintln(Output, HiCyanString("hcyan"))
|
|
||||||
fmt.Fprintln(Output, HiWhiteString("hwhite"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNoFormat(t *testing.T) {
|
|
||||||
fmt.Printf("%s %%s = ", BlackString("Black"))
|
|
||||||
Black("%s")
|
|
||||||
|
|
||||||
fmt.Printf("%s %%s = ", RedString("Red"))
|
|
||||||
Red("%s")
|
|
||||||
|
|
||||||
fmt.Printf("%s %%s = ", GreenString("Green"))
|
|
||||||
Green("%s")
|
|
||||||
|
|
||||||
fmt.Printf("%s %%s = ", YellowString("Yellow"))
|
|
||||||
Yellow("%s")
|
|
||||||
|
|
||||||
fmt.Printf("%s %%s = ", BlueString("Blue"))
|
|
||||||
Blue("%s")
|
|
||||||
|
|
||||||
fmt.Printf("%s %%s = ", MagentaString("Magenta"))
|
|
||||||
Magenta("%s")
|
|
||||||
|
|
||||||
fmt.Printf("%s %%s = ", CyanString("Cyan"))
|
|
||||||
Cyan("%s")
|
|
||||||
|
|
||||||
fmt.Printf("%s %%s = ", WhiteString("White"))
|
|
||||||
White("%s")
|
|
||||||
|
|
||||||
fmt.Printf("%s %%s = ", HiBlackString("HiBlack"))
|
|
||||||
HiBlack("%s")
|
|
||||||
|
|
||||||
fmt.Printf("%s %%s = ", HiRedString("HiRed"))
|
|
||||||
HiRed("%s")
|
|
||||||
|
|
||||||
fmt.Printf("%s %%s = ", HiGreenString("HiGreen"))
|
|
||||||
HiGreen("%s")
|
|
||||||
|
|
||||||
fmt.Printf("%s %%s = ", HiYellowString("HiYellow"))
|
|
||||||
HiYellow("%s")
|
|
||||||
|
|
||||||
fmt.Printf("%s %%s = ", HiBlueString("HiBlue"))
|
|
||||||
HiBlue("%s")
|
|
||||||
|
|
||||||
fmt.Printf("%s %%s = ", HiMagentaString("HiMagenta"))
|
|
||||||
HiMagenta("%s")
|
|
||||||
|
|
||||||
fmt.Printf("%s %%s = ", HiCyanString("HiCyan"))
|
|
||||||
HiCyan("%s")
|
|
||||||
|
|
||||||
fmt.Printf("%s %%s = ", HiWhiteString("HiWhite"))
|
|
||||||
HiWhite("%s")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNoFormatString(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
f func(string, ...interface{}) string
|
|
||||||
format string
|
|
||||||
args []interface{}
|
|
||||||
want string
|
|
||||||
}{
|
|
||||||
{BlackString, "%s", nil, "\x1b[30m%s\x1b[0m"},
|
|
||||||
{RedString, "%s", nil, "\x1b[31m%s\x1b[0m"},
|
|
||||||
{GreenString, "%s", nil, "\x1b[32m%s\x1b[0m"},
|
|
||||||
{YellowString, "%s", nil, "\x1b[33m%s\x1b[0m"},
|
|
||||||
{BlueString, "%s", nil, "\x1b[34m%s\x1b[0m"},
|
|
||||||
{MagentaString, "%s", nil, "\x1b[35m%s\x1b[0m"},
|
|
||||||
{CyanString, "%s", nil, "\x1b[36m%s\x1b[0m"},
|
|
||||||
{WhiteString, "%s", nil, "\x1b[37m%s\x1b[0m"},
|
|
||||||
{HiBlackString, "%s", nil, "\x1b[90m%s\x1b[0m"},
|
|
||||||
{HiRedString, "%s", nil, "\x1b[91m%s\x1b[0m"},
|
|
||||||
{HiGreenString, "%s", nil, "\x1b[92m%s\x1b[0m"},
|
|
||||||
{HiYellowString, "%s", nil, "\x1b[93m%s\x1b[0m"},
|
|
||||||
{HiBlueString, "%s", nil, "\x1b[94m%s\x1b[0m"},
|
|
||||||
{HiMagentaString, "%s", nil, "\x1b[95m%s\x1b[0m"},
|
|
||||||
{HiCyanString, "%s", nil, "\x1b[96m%s\x1b[0m"},
|
|
||||||
{HiWhiteString, "%s", nil, "\x1b[97m%s\x1b[0m"},
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, test := range tests {
|
|
||||||
s := fmt.Sprintf("%s", test.f(test.format, test.args...))
|
|
||||||
if s != test.want {
|
|
||||||
t.Errorf("[%d] want: %q, got: %q", i, test.want, s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,16 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/mattn/go-colorable"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
stdOut := bufio.NewWriter(colorable.NewColorableStdout())
|
|
||||||
|
|
||||||
fmt.Fprint(stdOut, "\x1B[3GMove to 3rd Column\n")
|
|
||||||
fmt.Fprint(stdOut, "\x1B[1;2HMove to 2nd Column on 1st Line\n")
|
|
||||||
stdOut.Flush()
|
|
||||||
}
|
|
@ -1,16 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/mattn/go-colorable"
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
logrus.SetFormatter(&logrus.TextFormatter{ForceColors: true})
|
|
||||||
logrus.SetOutput(colorable.NewColorableStdout())
|
|
||||||
|
|
||||||
logrus.Info("succeeded")
|
|
||||||
logrus.Warn("not correct")
|
|
||||||
logrus.Error("something error")
|
|
||||||
logrus.Fatal("panic")
|
|
||||||
}
|
|
@ -1,14 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
. "github.com/mattn/go-colorable"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
out := NewColorableStdout()
|
|
||||||
fmt.Fprint(out, "\x1B]0;TITLE Changed\007(See title and hit any key)")
|
|
||||||
var c [1]byte
|
|
||||||
os.Stdin.Read(c[:])
|
|
||||||
}
|
|
@ -1,83 +0,0 @@
|
|||||||
package colorable
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"os"
|
|
||||||
"runtime"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
// checkEncoding checks that colorable is output encoding agnostic as long as
|
|
||||||
// the encoding is a superset of ASCII. This implies that one byte not part of
|
|
||||||
// an ANSI sequence must give exactly one byte in output
|
|
||||||
func checkEncoding(t *testing.T, data []byte) {
|
|
||||||
// Send non-UTF8 data to colorable
|
|
||||||
b := bytes.NewBuffer(make([]byte, 0, 10))
|
|
||||||
if b.Len() != 0 {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
// TODO move colorable wrapping outside the test
|
|
||||||
c := NewNonColorable(b)
|
|
||||||
c.Write(data)
|
|
||||||
if b.Len() != len(data) {
|
|
||||||
t.Fatalf("%d bytes expected, got %d", len(data), b.Len())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEncoding(t *testing.T) {
|
|
||||||
checkEncoding(t, []byte{}) // Empty
|
|
||||||
checkEncoding(t, []byte(`abc`)) // "abc"
|
|
||||||
checkEncoding(t, []byte(`é`)) // "é" in UTF-8
|
|
||||||
checkEncoding(t, []byte{233}) // 'é' in Latin-1
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNonColorable(t *testing.T) {
|
|
||||||
var buf bytes.Buffer
|
|
||||||
want := "hello"
|
|
||||||
NewNonColorable(&buf).Write([]byte("\x1b[0m" + want + "\x1b[2J"))
|
|
||||||
got := buf.String()
|
|
||||||
if got != "hello" {
|
|
||||||
t.Fatalf("want %q but %q", want, got)
|
|
||||||
}
|
|
||||||
|
|
||||||
buf.Reset()
|
|
||||||
NewNonColorable(&buf).Write([]byte("\x1b["))
|
|
||||||
got = buf.String()
|
|
||||||
if got != "" {
|
|
||||||
t.Fatalf("want %q but %q", "", got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNonColorableNil(t *testing.T) {
|
|
||||||
paniced := false
|
|
||||||
func() {
|
|
||||||
defer func() {
|
|
||||||
recover()
|
|
||||||
paniced = true
|
|
||||||
}()
|
|
||||||
NewNonColorable(nil)
|
|
||||||
NewColorable(nil)
|
|
||||||
}()
|
|
||||||
|
|
||||||
if !paniced {
|
|
||||||
t.Fatalf("should panic")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestColorable(t *testing.T) {
|
|
||||||
if runtime.GOOS == "windows" {
|
|
||||||
t.Skipf("skip this test on windows")
|
|
||||||
}
|
|
||||||
_, ok := NewColorableStdout().(*os.File)
|
|
||||||
if !ok {
|
|
||||||
t.Fatalf("should os.Stdout on UNIX")
|
|
||||||
}
|
|
||||||
_, ok = NewColorableStderr().(*os.File)
|
|
||||||
if !ok {
|
|
||||||
t.Fatalf("should os.Stdout on UNIX")
|
|
||||||
}
|
|
||||||
_, ok = NewColorable(os.Stdout).(*os.File)
|
|
||||||
if !ok {
|
|
||||||
t.Fatalf("should os.Stdout on UNIX")
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,18 +0,0 @@
|
|||||||
package isatty_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/mattn/go-isatty"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Example() {
|
|
||||||
if isatty.IsTerminal(os.Stdout.Fd()) {
|
|
||||||
fmt.Println("Is Terminal")
|
|
||||||
} else if isatty.IsCygwinTerminal(os.Stdout.Fd()) {
|
|
||||||
fmt.Println("Is Cygwin/MSYS2 Terminal")
|
|
||||||
} else {
|
|
||||||
fmt.Println("Is Not Terminal")
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
// +build !windows
|
|
||||||
|
|
||||||
package isatty
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestTerminal(t *testing.T) {
|
|
||||||
// test for non-panic
|
|
||||||
IsTerminal(os.Stdout.Fd())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCygwinPipeName(t *testing.T) {
|
|
||||||
if IsCygwinTerminal(os.Stdout.Fd()) {
|
|
||||||
t.Fatal("should be false always")
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,35 +0,0 @@
|
|||||||
// +build windows
|
|
||||||
|
|
||||||
package isatty
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestCygwinPipeName(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
result bool
|
|
||||||
}{
|
|
||||||
{``, false},
|
|
||||||
{`\msys-`, false},
|
|
||||||
{`\cygwin-----`, false},
|
|
||||||
{`\msys-x-PTY5-pty1-from-master`, false},
|
|
||||||
{`\cygwin-x-PTY5-from-master`, false},
|
|
||||||
{`\cygwin-x-pty2-from-toaster`, false},
|
|
||||||
{`\cygwin--pty2-from-master`, false},
|
|
||||||
{`\\cygwin-x-pty2-from-master`, false},
|
|
||||||
{`\cygwin-x-pty2-from-master-`, true}, // for the feature
|
|
||||||
{`\cygwin-e022582115c10879-pty4-from-master`, true},
|
|
||||||
{`\msys-e022582115c10879-pty4-to-master`, true},
|
|
||||||
{`\cygwin-e022582115c10879-pty4-to-master`, true},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range tests {
|
|
||||||
want := test.result
|
|
||||||
got := isCygwinPipeName(test.name)
|
|
||||||
if want != got {
|
|
||||||
t.Fatalf("isatty(%q): got %v, want %v:", test.name, got, want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,8 @@
|
|||||||
|
// +build appengine
|
||||||
|
|
||||||
|
package runewidth
|
||||||
|
|
||||||
|
// IsEastAsian return true if the current locale is CJK
|
||||||
|
func IsEastAsian() bool {
|
||||||
|
return false
|
||||||
|
}
|
@ -1,10 +0,0 @@
|
|||||||
# Treat all files in this repo as binary, with no git magic updating
|
|
||||||
# line endings. Windows users contributing to Go will need to use a
|
|
||||||
# modern version of git and editors capable of LF line endings.
|
|
||||||
#
|
|
||||||
# We'll prevent accidental CRLF line endings from entering the repo
|
|
||||||
# via the git-review gofmt checks.
|
|
||||||
#
|
|
||||||
# See golang.org/issue/9281
|
|
||||||
|
|
||||||
* -text
|
|
@ -1,2 +0,0 @@
|
|||||||
# Add no patterns to .hgignore except for files generated by the build.
|
|
||||||
last-change
|
|
@ -1,26 +0,0 @@
|
|||||||
# Contributing to Go
|
|
||||||
|
|
||||||
Go is an open source project.
|
|
||||||
|
|
||||||
It is the work of hundreds of contributors. We appreciate your help!
|
|
||||||
|
|
||||||
## Filing issues
|
|
||||||
|
|
||||||
When [filing an issue](https://golang.org/issue/new), make sure to answer these five questions:
|
|
||||||
|
|
||||||
1. What version of Go are you using (`go version`)?
|
|
||||||
2. What operating system and processor architecture are you using?
|
|
||||||
3. What did you do?
|
|
||||||
4. What did you expect to see?
|
|
||||||
5. What did you see instead?
|
|
||||||
|
|
||||||
General questions should go to the [golang-nuts mailing list](https://groups.google.com/group/golang-nuts) instead of the issue tracker.
|
|
||||||
The gophers there will answer or ask you to file an issue if you've tripped over a bug.
|
|
||||||
|
|
||||||
## Contributing code
|
|
||||||
|
|
||||||
Please read the [Contribution Guidelines](https://golang.org/doc/contribute.html)
|
|
||||||
before sending patches.
|
|
||||||
|
|
||||||
Unless otherwise noted, the Go source files are distributed under
|
|
||||||
the BSD-style license found in the LICENSE file.
|
|
@ -1,16 +0,0 @@
|
|||||||
# Go Networking
|
|
||||||
|
|
||||||
This repository holds supplementary Go networking libraries.
|
|
||||||
|
|
||||||
## Download/Install
|
|
||||||
|
|
||||||
The easiest way to install is to run `go get -u golang.org/x/net`. You can
|
|
||||||
also manually git clone the repository to `$GOPATH/src/golang.org/x/net`.
|
|
||||||
|
|
||||||
## Report Issues / Send Patches
|
|
||||||
|
|
||||||
This repository uses Gerrit for code changes. To learn how to submit
|
|
||||||
changes to this repository, see https://golang.org/doc/contribute.html.
|
|
||||||
The main issue tracker for the net repository is located at
|
|
||||||
https://github.com/golang/go/issues. Prefix your issue with "x/net:" in the
|
|
||||||
subject line, so it is easy to find.
|
|
@ -1,41 +0,0 @@
|
|||||||
// Copyright 2016 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package bpf
|
|
||||||
|
|
||||||
import "fmt"
|
|
||||||
|
|
||||||
// Assemble converts insts into raw instructions suitable for loading
|
|
||||||
// into a BPF virtual machine.
|
|
||||||
//
|
|
||||||
// Currently, no optimization is attempted, the assembled program flow
|
|
||||||
// is exactly as provided.
|
|
||||||
func Assemble(insts []Instruction) ([]RawInstruction, error) {
|
|
||||||
ret := make([]RawInstruction, len(insts))
|
|
||||||
var err error
|
|
||||||
for i, inst := range insts {
|
|
||||||
ret[i], err = inst.Assemble()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("assembling instruction %d: %s", i+1, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ret, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Disassemble attempts to parse raw back into
|
|
||||||
// Instructions. Unrecognized RawInstructions are assumed to be an
|
|
||||||
// extension not implemented by this package, and are passed through
|
|
||||||
// unchanged to the output. The allDecoded value reports whether insts
|
|
||||||
// contains no RawInstructions.
|
|
||||||
func Disassemble(raw []RawInstruction) (insts []Instruction, allDecoded bool) {
|
|
||||||
insts = make([]Instruction, len(raw))
|
|
||||||
allDecoded = true
|
|
||||||
for i, r := range raw {
|
|
||||||
insts[i] = r.Disassemble()
|
|
||||||
if _, ok := insts[i].(RawInstruction); ok {
|
|
||||||
allDecoded = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return insts, allDecoded
|
|
||||||
}
|
|
@ -1,218 +0,0 @@
|
|||||||
// Copyright 2016 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package bpf
|
|
||||||
|
|
||||||
// A Register is a register of the BPF virtual machine.
|
|
||||||
type Register uint16
|
|
||||||
|
|
||||||
const (
|
|
||||||
// RegA is the accumulator register. RegA is always the
|
|
||||||
// destination register of ALU operations.
|
|
||||||
RegA Register = iota
|
|
||||||
// RegX is the indirection register, used by LoadIndirect
|
|
||||||
// operations.
|
|
||||||
RegX
|
|
||||||
)
|
|
||||||
|
|
||||||
// An ALUOp is an arithmetic or logic operation.
|
|
||||||
type ALUOp uint16
|
|
||||||
|
|
||||||
// ALU binary operation types.
|
|
||||||
const (
|
|
||||||
ALUOpAdd ALUOp = iota << 4
|
|
||||||
ALUOpSub
|
|
||||||
ALUOpMul
|
|
||||||
ALUOpDiv
|
|
||||||
ALUOpOr
|
|
||||||
ALUOpAnd
|
|
||||||
ALUOpShiftLeft
|
|
||||||
ALUOpShiftRight
|
|
||||||
aluOpNeg // Not exported because it's the only unary ALU operation, and gets its own instruction type.
|
|
||||||
ALUOpMod
|
|
||||||
ALUOpXor
|
|
||||||
)
|
|
||||||
|
|
||||||
// A JumpTest is a comparison operator used in conditional jumps.
|
|
||||||
type JumpTest uint16
|
|
||||||
|
|
||||||
// Supported operators for conditional jumps.
|
|
||||||
const (
|
|
||||||
// K == A
|
|
||||||
JumpEqual JumpTest = iota
|
|
||||||
// K != A
|
|
||||||
JumpNotEqual
|
|
||||||
// K > A
|
|
||||||
JumpGreaterThan
|
|
||||||
// K < A
|
|
||||||
JumpLessThan
|
|
||||||
// K >= A
|
|
||||||
JumpGreaterOrEqual
|
|
||||||
// K <= A
|
|
||||||
JumpLessOrEqual
|
|
||||||
// K & A != 0
|
|
||||||
JumpBitsSet
|
|
||||||
// K & A == 0
|
|
||||||
JumpBitsNotSet
|
|
||||||
)
|
|
||||||
|
|
||||||
// An Extension is a function call provided by the kernel that
|
|
||||||
// performs advanced operations that are expensive or impossible
|
|
||||||
// within the BPF virtual machine.
|
|
||||||
//
|
|
||||||
// Extensions are only implemented by the Linux kernel.
|
|
||||||
//
|
|
||||||
// TODO: should we prune this list? Some of these extensions seem
|
|
||||||
// either broken or near-impossible to use correctly, whereas other
|
|
||||||
// (len, random, ifindex) are quite useful.
|
|
||||||
type Extension int
|
|
||||||
|
|
||||||
// Extension functions available in the Linux kernel.
|
|
||||||
const (
|
|
||||||
// extOffset is the negative maximum number of instructions used
|
|
||||||
// to load instructions by overloading the K argument.
|
|
||||||
extOffset = -0x1000
|
|
||||||
// ExtLen returns the length of the packet.
|
|
||||||
ExtLen Extension = 1
|
|
||||||
// ExtProto returns the packet's L3 protocol type.
|
|
||||||
ExtProto Extension = 0
|
|
||||||
// ExtType returns the packet's type (skb->pkt_type in the kernel)
|
|
||||||
//
|
|
||||||
// TODO: better documentation. How nice an API do we want to
|
|
||||||
// provide for these esoteric extensions?
|
|
||||||
ExtType Extension = 4
|
|
||||||
// ExtPayloadOffset returns the offset of the packet payload, or
|
|
||||||
// the first protocol header that the kernel does not know how to
|
|
||||||
// parse.
|
|
||||||
ExtPayloadOffset Extension = 52
|
|
||||||
// ExtInterfaceIndex returns the index of the interface on which
|
|
||||||
// the packet was received.
|
|
||||||
ExtInterfaceIndex Extension = 8
|
|
||||||
// ExtNetlinkAttr returns the netlink attribute of type X at
|
|
||||||
// offset A.
|
|
||||||
ExtNetlinkAttr Extension = 12
|
|
||||||
// ExtNetlinkAttrNested returns the nested netlink attribute of
|
|
||||||
// type X at offset A.
|
|
||||||
ExtNetlinkAttrNested Extension = 16
|
|
||||||
// ExtMark returns the packet's mark value.
|
|
||||||
ExtMark Extension = 20
|
|
||||||
// ExtQueue returns the packet's assigned hardware queue.
|
|
||||||
ExtQueue Extension = 24
|
|
||||||
// ExtLinkLayerType returns the packet's hardware address type
|
|
||||||
// (e.g. Ethernet, Infiniband).
|
|
||||||
ExtLinkLayerType Extension = 28
|
|
||||||
// ExtRXHash returns the packets receive hash.
|
|
||||||
//
|
|
||||||
// TODO: figure out what this rxhash actually is.
|
|
||||||
ExtRXHash Extension = 32
|
|
||||||
// ExtCPUID returns the ID of the CPU processing the current
|
|
||||||
// packet.
|
|
||||||
ExtCPUID Extension = 36
|
|
||||||
// ExtVLANTag returns the packet's VLAN tag.
|
|
||||||
ExtVLANTag Extension = 44
|
|
||||||
// ExtVLANTagPresent returns non-zero if the packet has a VLAN
|
|
||||||
// tag.
|
|
||||||
//
|
|
||||||
// TODO: I think this might be a lie: it reads bit 0x1000 of the
|
|
||||||
// VLAN header, which changed meaning in recent revisions of the
|
|
||||||
// spec - this extension may now return meaningless information.
|
|
||||||
ExtVLANTagPresent Extension = 48
|
|
||||||
// ExtVLANProto returns 0x8100 if the frame has a VLAN header,
|
|
||||||
// 0x88a8 if the frame has a "Q-in-Q" double VLAN header, or some
|
|
||||||
// other value if no VLAN information is present.
|
|
||||||
ExtVLANProto Extension = 60
|
|
||||||
// ExtRand returns a uniformly random uint32.
|
|
||||||
ExtRand Extension = 56
|
|
||||||
)
|
|
||||||
|
|
||||||
// The following gives names to various bit patterns used in opcode construction.
|
|
||||||
|
|
||||||
const (
|
|
||||||
opMaskCls uint16 = 0x7
|
|
||||||
// opClsLoad masks
|
|
||||||
opMaskLoadDest = 0x01
|
|
||||||
opMaskLoadWidth = 0x18
|
|
||||||
opMaskLoadMode = 0xe0
|
|
||||||
// opClsALU
|
|
||||||
opMaskOperandSrc = 0x08
|
|
||||||
opMaskOperator = 0xf0
|
|
||||||
// opClsJump
|
|
||||||
opMaskJumpConst = 0x0f
|
|
||||||
opMaskJumpCond = 0xf0
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// +---------------+-----------------+---+---+---+
|
|
||||||
// | AddrMode (3b) | LoadWidth (2b) | 0 | 0 | 0 |
|
|
||||||
// +---------------+-----------------+---+---+---+
|
|
||||||
opClsLoadA uint16 = iota
|
|
||||||
// +---------------+-----------------+---+---+---+
|
|
||||||
// | AddrMode (3b) | LoadWidth (2b) | 0 | 0 | 1 |
|
|
||||||
// +---------------+-----------------+---+---+---+
|
|
||||||
opClsLoadX
|
|
||||||
// +---+---+---+---+---+---+---+---+
|
|
||||||
// | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
|
|
||||||
// +---+---+---+---+---+---+---+---+
|
|
||||||
opClsStoreA
|
|
||||||
// +---+---+---+---+---+---+---+---+
|
|
||||||
// | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 |
|
|
||||||
// +---+---+---+---+---+---+---+---+
|
|
||||||
opClsStoreX
|
|
||||||
// +---------------+-----------------+---+---+---+
|
|
||||||
// | Operator (4b) | OperandSrc (1b) | 1 | 0 | 0 |
|
|
||||||
// +---------------+-----------------+---+---+---+
|
|
||||||
opClsALU
|
|
||||||
// +-----------------------------+---+---+---+---+
|
|
||||||
// | TestOperator (4b) | 0 | 1 | 0 | 1 |
|
|
||||||
// +-----------------------------+---+---+---+---+
|
|
||||||
opClsJump
|
|
||||||
// +---+-------------------------+---+---+---+---+
|
|
||||||
// | 0 | 0 | 0 | RetSrc (1b) | 0 | 1 | 1 | 0 |
|
|
||||||
// +---+-------------------------+---+---+---+---+
|
|
||||||
opClsReturn
|
|
||||||
// +---+-------------------------+---+---+---+---+
|
|
||||||
// | 0 | 0 | 0 | TXAorTAX (1b) | 0 | 1 | 1 | 1 |
|
|
||||||
// +---+-------------------------+---+---+---+---+
|
|
||||||
opClsMisc
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
opAddrModeImmediate uint16 = iota << 5
|
|
||||||
opAddrModeAbsolute
|
|
||||||
opAddrModeIndirect
|
|
||||||
opAddrModeScratch
|
|
||||||
opAddrModePacketLen // actually an extension, not an addressing mode.
|
|
||||||
opAddrModeMemShift
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
opLoadWidth4 uint16 = iota << 3
|
|
||||||
opLoadWidth2
|
|
||||||
opLoadWidth1
|
|
||||||
)
|
|
||||||
|
|
||||||
// Operator defined by ALUOp*
|
|
||||||
|
|
||||||
const (
|
|
||||||
opALUSrcConstant uint16 = iota << 3
|
|
||||||
opALUSrcX
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
opJumpAlways = iota << 4
|
|
||||||
opJumpEqual
|
|
||||||
opJumpGT
|
|
||||||
opJumpGE
|
|
||||||
opJumpSet
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
opRetSrcConstant uint16 = iota << 4
|
|
||||||
opRetSrcA
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
opMiscTAX = 0x00
|
|
||||||
opMiscTXA = 0x80
|
|
||||||
)
|
|
@ -1,82 +0,0 @@
|
|||||||
// Copyright 2016 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
Package bpf implements marshaling and unmarshaling of programs for the
|
|
||||||
Berkeley Packet Filter virtual machine, and provides a Go implementation
|
|
||||||
of the virtual machine.
|
|
||||||
|
|
||||||
BPF's main use is to specify a packet filter for network taps, so that
|
|
||||||
the kernel doesn't have to expensively copy every packet it sees to
|
|
||||||
userspace. However, it's been repurposed to other areas where running
|
|
||||||
user code in-kernel is needed. For example, Linux's seccomp uses BPF
|
|
||||||
to apply security policies to system calls. For simplicity, this
|
|
||||||
documentation refers only to packets, but other uses of BPF have their
|
|
||||||
own data payloads.
|
|
||||||
|
|
||||||
BPF programs run in a restricted virtual machine. It has almost no
|
|
||||||
access to kernel functions, and while conditional branches are
|
|
||||||
allowed, they can only jump forwards, to guarantee that there are no
|
|
||||||
infinite loops.
|
|
||||||
|
|
||||||
The virtual machine
|
|
||||||
|
|
||||||
The BPF VM is an accumulator machine. Its main register, called
|
|
||||||
register A, is an implicit source and destination in all arithmetic
|
|
||||||
and logic operations. The machine also has 16 scratch registers for
|
|
||||||
temporary storage, and an indirection register (register X) for
|
|
||||||
indirect memory access. All registers are 32 bits wide.
|
|
||||||
|
|
||||||
Each run of a BPF program is given one packet, which is placed in the
|
|
||||||
VM's read-only "main memory". LoadAbsolute and LoadIndirect
|
|
||||||
instructions can fetch up to 32 bits at a time into register A for
|
|
||||||
examination.
|
|
||||||
|
|
||||||
The goal of a BPF program is to produce and return a verdict (uint32),
|
|
||||||
which tells the kernel what to do with the packet. In the context of
|
|
||||||
packet filtering, the returned value is the number of bytes of the
|
|
||||||
packet to forward to userspace, or 0 to ignore the packet. Other
|
|
||||||
contexts like seccomp define their own return values.
|
|
||||||
|
|
||||||
In order to simplify programs, attempts to read past the end of the
|
|
||||||
packet terminate the program execution with a verdict of 0 (ignore
|
|
||||||
packet). This means that the vast majority of BPF programs don't need
|
|
||||||
to do any explicit bounds checking.
|
|
||||||
|
|
||||||
In addition to the bytes of the packet, some BPF programs have access
|
|
||||||
to extensions, which are essentially calls to kernel utility
|
|
||||||
functions. Currently, the only extensions supported by this package
|
|
||||||
are the Linux packet filter extensions.
|
|
||||||
|
|
||||||
Examples
|
|
||||||
|
|
||||||
This packet filter selects all ARP packets.
|
|
||||||
|
|
||||||
bpf.Assemble([]bpf.Instruction{
|
|
||||||
// Load "EtherType" field from the ethernet header.
|
|
||||||
bpf.LoadAbsolute{Off: 12, Size: 2},
|
|
||||||
// Skip over the next instruction if EtherType is not ARP.
|
|
||||||
bpf.JumpIf{Cond: bpf.JumpNotEqual, Val: 0x0806, SkipTrue: 1},
|
|
||||||
// Verdict is "send up to 4k of the packet to userspace."
|
|
||||||
bpf.RetConstant{Val: 4096},
|
|
||||||
// Verdict is "ignore packet."
|
|
||||||
bpf.RetConstant{Val: 0},
|
|
||||||
})
|
|
||||||
|
|
||||||
This packet filter captures a random 1% sample of traffic.
|
|
||||||
|
|
||||||
bpf.Assemble([]bpf.Instruction{
|
|
||||||
// Get a 32-bit random number from the Linux kernel.
|
|
||||||
bpf.LoadExtension{Num: bpf.ExtRand},
|
|
||||||
// 1% dice roll?
|
|
||||||
bpf.JumpIf{Cond: bpf.JumpLessThan, Val: 2^32/100, SkipFalse: 1},
|
|
||||||
// Capture.
|
|
||||||
bpf.RetConstant{Val: 4096},
|
|
||||||
// Ignore.
|
|
||||||
bpf.RetConstant{Val: 0},
|
|
||||||
})
|
|
||||||
|
|
||||||
*/
|
|
||||||
package bpf // import "golang.org/x/net/bpf"
|
|
@ -1,704 +0,0 @@
|
|||||||
// Copyright 2016 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package bpf
|
|
||||||
|
|
||||||
import "fmt"
|
|
||||||
|
|
||||||
// An Instruction is one instruction executed by the BPF virtual
|
|
||||||
// machine.
|
|
||||||
type Instruction interface {
|
|
||||||
// Assemble assembles the Instruction into a RawInstruction.
|
|
||||||
Assemble() (RawInstruction, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// A RawInstruction is a raw BPF virtual machine instruction.
|
|
||||||
type RawInstruction struct {
|
|
||||||
// Operation to execute.
|
|
||||||
Op uint16
|
|
||||||
// For conditional jump instructions, the number of instructions
|
|
||||||
// to skip if the condition is true/false.
|
|
||||||
Jt uint8
|
|
||||||
Jf uint8
|
|
||||||
// Constant parameter. The meaning depends on the Op.
|
|
||||||
K uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assemble implements the Instruction Assemble method.
|
|
||||||
func (ri RawInstruction) Assemble() (RawInstruction, error) { return ri, nil }
|
|
||||||
|
|
||||||
// Disassemble parses ri into an Instruction and returns it. If ri is
|
|
||||||
// not recognized by this package, ri itself is returned.
|
|
||||||
func (ri RawInstruction) Disassemble() Instruction {
|
|
||||||
switch ri.Op & opMaskCls {
|
|
||||||
case opClsLoadA, opClsLoadX:
|
|
||||||
reg := Register(ri.Op & opMaskLoadDest)
|
|
||||||
sz := 0
|
|
||||||
switch ri.Op & opMaskLoadWidth {
|
|
||||||
case opLoadWidth4:
|
|
||||||
sz = 4
|
|
||||||
case opLoadWidth2:
|
|
||||||
sz = 2
|
|
||||||
case opLoadWidth1:
|
|
||||||
sz = 1
|
|
||||||
default:
|
|
||||||
return ri
|
|
||||||
}
|
|
||||||
switch ri.Op & opMaskLoadMode {
|
|
||||||
case opAddrModeImmediate:
|
|
||||||
if sz != 4 {
|
|
||||||
return ri
|
|
||||||
}
|
|
||||||
return LoadConstant{Dst: reg, Val: ri.K}
|
|
||||||
case opAddrModeScratch:
|
|
||||||
if sz != 4 || ri.K > 15 {
|
|
||||||
return ri
|
|
||||||
}
|
|
||||||
return LoadScratch{Dst: reg, N: int(ri.K)}
|
|
||||||
case opAddrModeAbsolute:
|
|
||||||
if ri.K > extOffset+0xffffffff {
|
|
||||||
return LoadExtension{Num: Extension(-extOffset + ri.K)}
|
|
||||||
}
|
|
||||||
return LoadAbsolute{Size: sz, Off: ri.K}
|
|
||||||
case opAddrModeIndirect:
|
|
||||||
return LoadIndirect{Size: sz, Off: ri.K}
|
|
||||||
case opAddrModePacketLen:
|
|
||||||
if sz != 4 {
|
|
||||||
return ri
|
|
||||||
}
|
|
||||||
return LoadExtension{Num: ExtLen}
|
|
||||||
case opAddrModeMemShift:
|
|
||||||
return LoadMemShift{Off: ri.K}
|
|
||||||
default:
|
|
||||||
return ri
|
|
||||||
}
|
|
||||||
|
|
||||||
case opClsStoreA:
|
|
||||||
if ri.Op != opClsStoreA || ri.K > 15 {
|
|
||||||
return ri
|
|
||||||
}
|
|
||||||
return StoreScratch{Src: RegA, N: int(ri.K)}
|
|
||||||
|
|
||||||
case opClsStoreX:
|
|
||||||
if ri.Op != opClsStoreX || ri.K > 15 {
|
|
||||||
return ri
|
|
||||||
}
|
|
||||||
return StoreScratch{Src: RegX, N: int(ri.K)}
|
|
||||||
|
|
||||||
case opClsALU:
|
|
||||||
switch op := ALUOp(ri.Op & opMaskOperator); op {
|
|
||||||
case ALUOpAdd, ALUOpSub, ALUOpMul, ALUOpDiv, ALUOpOr, ALUOpAnd, ALUOpShiftLeft, ALUOpShiftRight, ALUOpMod, ALUOpXor:
|
|
||||||
if ri.Op&opMaskOperandSrc != 0 {
|
|
||||||
return ALUOpX{Op: op}
|
|
||||||
}
|
|
||||||
return ALUOpConstant{Op: op, Val: ri.K}
|
|
||||||
case aluOpNeg:
|
|
||||||
return NegateA{}
|
|
||||||
default:
|
|
||||||
return ri
|
|
||||||
}
|
|
||||||
|
|
||||||
case opClsJump:
|
|
||||||
if ri.Op&opMaskJumpConst != opClsJump {
|
|
||||||
return ri
|
|
||||||
}
|
|
||||||
switch ri.Op & opMaskJumpCond {
|
|
||||||
case opJumpAlways:
|
|
||||||
return Jump{Skip: ri.K}
|
|
||||||
case opJumpEqual:
|
|
||||||
if ri.Jt == 0 {
|
|
||||||
return JumpIf{
|
|
||||||
Cond: JumpNotEqual,
|
|
||||||
Val: ri.K,
|
|
||||||
SkipTrue: ri.Jf,
|
|
||||||
SkipFalse: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return JumpIf{
|
|
||||||
Cond: JumpEqual,
|
|
||||||
Val: ri.K,
|
|
||||||
SkipTrue: ri.Jt,
|
|
||||||
SkipFalse: ri.Jf,
|
|
||||||
}
|
|
||||||
case opJumpGT:
|
|
||||||
if ri.Jt == 0 {
|
|
||||||
return JumpIf{
|
|
||||||
Cond: JumpLessOrEqual,
|
|
||||||
Val: ri.K,
|
|
||||||
SkipTrue: ri.Jf,
|
|
||||||
SkipFalse: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return JumpIf{
|
|
||||||
Cond: JumpGreaterThan,
|
|
||||||
Val: ri.K,
|
|
||||||
SkipTrue: ri.Jt,
|
|
||||||
SkipFalse: ri.Jf,
|
|
||||||
}
|
|
||||||
case opJumpGE:
|
|
||||||
if ri.Jt == 0 {
|
|
||||||
return JumpIf{
|
|
||||||
Cond: JumpLessThan,
|
|
||||||
Val: ri.K,
|
|
||||||
SkipTrue: ri.Jf,
|
|
||||||
SkipFalse: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return JumpIf{
|
|
||||||
Cond: JumpGreaterOrEqual,
|
|
||||||
Val: ri.K,
|
|
||||||
SkipTrue: ri.Jt,
|
|
||||||
SkipFalse: ri.Jf,
|
|
||||||
}
|
|
||||||
case opJumpSet:
|
|
||||||
return JumpIf{
|
|
||||||
Cond: JumpBitsSet,
|
|
||||||
Val: ri.K,
|
|
||||||
SkipTrue: ri.Jt,
|
|
||||||
SkipFalse: ri.Jf,
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return ri
|
|
||||||
}
|
|
||||||
|
|
||||||
case opClsReturn:
|
|
||||||
switch ri.Op {
|
|
||||||
case opClsReturn | opRetSrcA:
|
|
||||||
return RetA{}
|
|
||||||
case opClsReturn | opRetSrcConstant:
|
|
||||||
return RetConstant{Val: ri.K}
|
|
||||||
default:
|
|
||||||
return ri
|
|
||||||
}
|
|
||||||
|
|
||||||
case opClsMisc:
|
|
||||||
switch ri.Op {
|
|
||||||
case opClsMisc | opMiscTAX:
|
|
||||||
return TAX{}
|
|
||||||
case opClsMisc | opMiscTXA:
|
|
||||||
return TXA{}
|
|
||||||
default:
|
|
||||||
return ri
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
panic("unreachable") // switch is exhaustive on the bit pattern
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoadConstant loads Val into register Dst.
|
|
||||||
type LoadConstant struct {
|
|
||||||
Dst Register
|
|
||||||
Val uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assemble implements the Instruction Assemble method.
|
|
||||||
func (a LoadConstant) Assemble() (RawInstruction, error) {
|
|
||||||
return assembleLoad(a.Dst, 4, opAddrModeImmediate, a.Val)
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns the instruction in assembler notation.
|
|
||||||
func (a LoadConstant) String() string {
|
|
||||||
switch a.Dst {
|
|
||||||
case RegA:
|
|
||||||
return fmt.Sprintf("ld #%d", a.Val)
|
|
||||||
case RegX:
|
|
||||||
return fmt.Sprintf("ldx #%d", a.Val)
|
|
||||||
default:
|
|
||||||
return fmt.Sprintf("unknown instruction: %#v", a)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoadScratch loads scratch[N] into register Dst.
|
|
||||||
type LoadScratch struct {
|
|
||||||
Dst Register
|
|
||||||
N int // 0-15
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assemble implements the Instruction Assemble method.
|
|
||||||
func (a LoadScratch) Assemble() (RawInstruction, error) {
|
|
||||||
if a.N < 0 || a.N > 15 {
|
|
||||||
return RawInstruction{}, fmt.Errorf("invalid scratch slot %d", a.N)
|
|
||||||
}
|
|
||||||
return assembleLoad(a.Dst, 4, opAddrModeScratch, uint32(a.N))
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns the instruction in assembler notation.
|
|
||||||
func (a LoadScratch) String() string {
|
|
||||||
switch a.Dst {
|
|
||||||
case RegA:
|
|
||||||
return fmt.Sprintf("ld M[%d]", a.N)
|
|
||||||
case RegX:
|
|
||||||
return fmt.Sprintf("ldx M[%d]", a.N)
|
|
||||||
default:
|
|
||||||
return fmt.Sprintf("unknown instruction: %#v", a)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoadAbsolute loads packet[Off:Off+Size] as an integer value into
|
|
||||||
// register A.
|
|
||||||
type LoadAbsolute struct {
|
|
||||||
Off uint32
|
|
||||||
Size int // 1, 2 or 4
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assemble implements the Instruction Assemble method.
|
|
||||||
func (a LoadAbsolute) Assemble() (RawInstruction, error) {
|
|
||||||
return assembleLoad(RegA, a.Size, opAddrModeAbsolute, a.Off)
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns the instruction in assembler notation.
|
|
||||||
func (a LoadAbsolute) String() string {
|
|
||||||
switch a.Size {
|
|
||||||
case 1: // byte
|
|
||||||
return fmt.Sprintf("ldb [%d]", a.Off)
|
|
||||||
case 2: // half word
|
|
||||||
return fmt.Sprintf("ldh [%d]", a.Off)
|
|
||||||
case 4: // word
|
|
||||||
if a.Off > extOffset+0xffffffff {
|
|
||||||
return LoadExtension{Num: Extension(a.Off + 0x1000)}.String()
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("ld [%d]", a.Off)
|
|
||||||
default:
|
|
||||||
return fmt.Sprintf("unknown instruction: %#v", a)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoadIndirect loads packet[X+Off:X+Off+Size] as an integer value
|
|
||||||
// into register A.
|
|
||||||
type LoadIndirect struct {
|
|
||||||
Off uint32
|
|
||||||
Size int // 1, 2 or 4
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assemble implements the Instruction Assemble method.
|
|
||||||
func (a LoadIndirect) Assemble() (RawInstruction, error) {
|
|
||||||
return assembleLoad(RegA, a.Size, opAddrModeIndirect, a.Off)
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns the instruction in assembler notation.
|
|
||||||
func (a LoadIndirect) String() string {
|
|
||||||
switch a.Size {
|
|
||||||
case 1: // byte
|
|
||||||
return fmt.Sprintf("ldb [x + %d]", a.Off)
|
|
||||||
case 2: // half word
|
|
||||||
return fmt.Sprintf("ldh [x + %d]", a.Off)
|
|
||||||
case 4: // word
|
|
||||||
return fmt.Sprintf("ld [x + %d]", a.Off)
|
|
||||||
default:
|
|
||||||
return fmt.Sprintf("unknown instruction: %#v", a)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoadMemShift multiplies the first 4 bits of the byte at packet[Off]
|
|
||||||
// by 4 and stores the result in register X.
|
|
||||||
//
|
|
||||||
// This instruction is mainly useful to load into X the length of an
|
|
||||||
// IPv4 packet header in a single instruction, rather than have to do
|
|
||||||
// the arithmetic on the header's first byte by hand.
|
|
||||||
type LoadMemShift struct {
|
|
||||||
Off uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assemble implements the Instruction Assemble method.
|
|
||||||
func (a LoadMemShift) Assemble() (RawInstruction, error) {
|
|
||||||
return assembleLoad(RegX, 1, opAddrModeMemShift, a.Off)
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns the instruction in assembler notation.
|
|
||||||
func (a LoadMemShift) String() string {
|
|
||||||
return fmt.Sprintf("ldx 4*([%d]&0xf)", a.Off)
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoadExtension invokes a linux-specific extension and stores the
|
|
||||||
// result in register A.
|
|
||||||
type LoadExtension struct {
|
|
||||||
Num Extension
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assemble implements the Instruction Assemble method.
|
|
||||||
func (a LoadExtension) Assemble() (RawInstruction, error) {
|
|
||||||
if a.Num == ExtLen {
|
|
||||||
return assembleLoad(RegA, 4, opAddrModePacketLen, 0)
|
|
||||||
}
|
|
||||||
return assembleLoad(RegA, 4, opAddrModeAbsolute, uint32(extOffset+a.Num))
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns the instruction in assembler notation.
|
|
||||||
func (a LoadExtension) String() string {
|
|
||||||
switch a.Num {
|
|
||||||
case ExtLen:
|
|
||||||
return "ld #len"
|
|
||||||
case ExtProto:
|
|
||||||
return "ld #proto"
|
|
||||||
case ExtType:
|
|
||||||
return "ld #type"
|
|
||||||
case ExtPayloadOffset:
|
|
||||||
return "ld #poff"
|
|
||||||
case ExtInterfaceIndex:
|
|
||||||
return "ld #ifidx"
|
|
||||||
case ExtNetlinkAttr:
|
|
||||||
return "ld #nla"
|
|
||||||
case ExtNetlinkAttrNested:
|
|
||||||
return "ld #nlan"
|
|
||||||
case ExtMark:
|
|
||||||
return "ld #mark"
|
|
||||||
case ExtQueue:
|
|
||||||
return "ld #queue"
|
|
||||||
case ExtLinkLayerType:
|
|
||||||
return "ld #hatype"
|
|
||||||
case ExtRXHash:
|
|
||||||
return "ld #rxhash"
|
|
||||||
case ExtCPUID:
|
|
||||||
return "ld #cpu"
|
|
||||||
case ExtVLANTag:
|
|
||||||
return "ld #vlan_tci"
|
|
||||||
case ExtVLANTagPresent:
|
|
||||||
return "ld #vlan_avail"
|
|
||||||
case ExtVLANProto:
|
|
||||||
return "ld #vlan_tpid"
|
|
||||||
case ExtRand:
|
|
||||||
return "ld #rand"
|
|
||||||
default:
|
|
||||||
return fmt.Sprintf("unknown instruction: %#v", a)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// StoreScratch stores register Src into scratch[N].
|
|
||||||
type StoreScratch struct {
|
|
||||||
Src Register
|
|
||||||
N int // 0-15
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assemble implements the Instruction Assemble method.
|
|
||||||
func (a StoreScratch) Assemble() (RawInstruction, error) {
|
|
||||||
if a.N < 0 || a.N > 15 {
|
|
||||||
return RawInstruction{}, fmt.Errorf("invalid scratch slot %d", a.N)
|
|
||||||
}
|
|
||||||
var op uint16
|
|
||||||
switch a.Src {
|
|
||||||
case RegA:
|
|
||||||
op = opClsStoreA
|
|
||||||
case RegX:
|
|
||||||
op = opClsStoreX
|
|
||||||
default:
|
|
||||||
return RawInstruction{}, fmt.Errorf("invalid source register %v", a.Src)
|
|
||||||
}
|
|
||||||
|
|
||||||
return RawInstruction{
|
|
||||||
Op: op,
|
|
||||||
K: uint32(a.N),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns the instruction in assembler notation.
|
|
||||||
func (a StoreScratch) String() string {
|
|
||||||
switch a.Src {
|
|
||||||
case RegA:
|
|
||||||
return fmt.Sprintf("st M[%d]", a.N)
|
|
||||||
case RegX:
|
|
||||||
return fmt.Sprintf("stx M[%d]", a.N)
|
|
||||||
default:
|
|
||||||
return fmt.Sprintf("unknown instruction: %#v", a)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ALUOpConstant executes A = A <Op> Val.
|
|
||||||
type ALUOpConstant struct {
|
|
||||||
Op ALUOp
|
|
||||||
Val uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assemble implements the Instruction Assemble method.
|
|
||||||
func (a ALUOpConstant) Assemble() (RawInstruction, error) {
|
|
||||||
return RawInstruction{
|
|
||||||
Op: opClsALU | opALUSrcConstant | uint16(a.Op),
|
|
||||||
K: a.Val,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns the instruction in assembler notation.
|
|
||||||
func (a ALUOpConstant) String() string {
|
|
||||||
switch a.Op {
|
|
||||||
case ALUOpAdd:
|
|
||||||
return fmt.Sprintf("add #%d", a.Val)
|
|
||||||
case ALUOpSub:
|
|
||||||
return fmt.Sprintf("sub #%d", a.Val)
|
|
||||||
case ALUOpMul:
|
|
||||||
return fmt.Sprintf("mul #%d", a.Val)
|
|
||||||
case ALUOpDiv:
|
|
||||||
return fmt.Sprintf("div #%d", a.Val)
|
|
||||||
case ALUOpMod:
|
|
||||||
return fmt.Sprintf("mod #%d", a.Val)
|
|
||||||
case ALUOpAnd:
|
|
||||||
return fmt.Sprintf("and #%d", a.Val)
|
|
||||||
case ALUOpOr:
|
|
||||||
return fmt.Sprintf("or #%d", a.Val)
|
|
||||||
case ALUOpXor:
|
|
||||||
return fmt.Sprintf("xor #%d", a.Val)
|
|
||||||
case ALUOpShiftLeft:
|
|
||||||
return fmt.Sprintf("lsh #%d", a.Val)
|
|
||||||
case ALUOpShiftRight:
|
|
||||||
return fmt.Sprintf("rsh #%d", a.Val)
|
|
||||||
default:
|
|
||||||
return fmt.Sprintf("unknown instruction: %#v", a)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ALUOpX executes A = A <Op> X
|
|
||||||
type ALUOpX struct {
|
|
||||||
Op ALUOp
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assemble implements the Instruction Assemble method.
|
|
||||||
func (a ALUOpX) Assemble() (RawInstruction, error) {
|
|
||||||
return RawInstruction{
|
|
||||||
Op: opClsALU | opALUSrcX | uint16(a.Op),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns the instruction in assembler notation.
|
|
||||||
func (a ALUOpX) String() string {
|
|
||||||
switch a.Op {
|
|
||||||
case ALUOpAdd:
|
|
||||||
return "add x"
|
|
||||||
case ALUOpSub:
|
|
||||||
return "sub x"
|
|
||||||
case ALUOpMul:
|
|
||||||
return "mul x"
|
|
||||||
case ALUOpDiv:
|
|
||||||
return "div x"
|
|
||||||
case ALUOpMod:
|
|
||||||
return "mod x"
|
|
||||||
case ALUOpAnd:
|
|
||||||
return "and x"
|
|
||||||
case ALUOpOr:
|
|
||||||
return "or x"
|
|
||||||
case ALUOpXor:
|
|
||||||
return "xor x"
|
|
||||||
case ALUOpShiftLeft:
|
|
||||||
return "lsh x"
|
|
||||||
case ALUOpShiftRight:
|
|
||||||
return "rsh x"
|
|
||||||
default:
|
|
||||||
return fmt.Sprintf("unknown instruction: %#v", a)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NegateA executes A = -A.
|
|
||||||
type NegateA struct{}
|
|
||||||
|
|
||||||
// Assemble implements the Instruction Assemble method.
|
|
||||||
func (a NegateA) Assemble() (RawInstruction, error) {
|
|
||||||
return RawInstruction{
|
|
||||||
Op: opClsALU | uint16(aluOpNeg),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns the instruction in assembler notation.
|
|
||||||
func (a NegateA) String() string {
|
|
||||||
return fmt.Sprintf("neg")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Jump skips the following Skip instructions in the program.
|
|
||||||
type Jump struct {
|
|
||||||
Skip uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assemble implements the Instruction Assemble method.
|
|
||||||
func (a Jump) Assemble() (RawInstruction, error) {
|
|
||||||
return RawInstruction{
|
|
||||||
Op: opClsJump | opJumpAlways,
|
|
||||||
K: a.Skip,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns the instruction in assembler notation.
|
|
||||||
func (a Jump) String() string {
|
|
||||||
return fmt.Sprintf("ja %d", a.Skip)
|
|
||||||
}
|
|
||||||
|
|
||||||
// JumpIf skips the following Skip instructions in the program if A
|
|
||||||
// <Cond> Val is true.
|
|
||||||
type JumpIf struct {
|
|
||||||
Cond JumpTest
|
|
||||||
Val uint32
|
|
||||||
SkipTrue uint8
|
|
||||||
SkipFalse uint8
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assemble implements the Instruction Assemble method.
|
|
||||||
func (a JumpIf) Assemble() (RawInstruction, error) {
|
|
||||||
var (
|
|
||||||
cond uint16
|
|
||||||
flip bool
|
|
||||||
)
|
|
||||||
switch a.Cond {
|
|
||||||
case JumpEqual:
|
|
||||||
cond = opJumpEqual
|
|
||||||
case JumpNotEqual:
|
|
||||||
cond, flip = opJumpEqual, true
|
|
||||||
case JumpGreaterThan:
|
|
||||||
cond = opJumpGT
|
|
||||||
case JumpLessThan:
|
|
||||||
cond, flip = opJumpGE, true
|
|
||||||
case JumpGreaterOrEqual:
|
|
||||||
cond = opJumpGE
|
|
||||||
case JumpLessOrEqual:
|
|
||||||
cond, flip = opJumpGT, true
|
|
||||||
case JumpBitsSet:
|
|
||||||
cond = opJumpSet
|
|
||||||
case JumpBitsNotSet:
|
|
||||||
cond, flip = opJumpSet, true
|
|
||||||
default:
|
|
||||||
return RawInstruction{}, fmt.Errorf("unknown JumpTest %v", a.Cond)
|
|
||||||
}
|
|
||||||
jt, jf := a.SkipTrue, a.SkipFalse
|
|
||||||
if flip {
|
|
||||||
jt, jf = jf, jt
|
|
||||||
}
|
|
||||||
return RawInstruction{
|
|
||||||
Op: opClsJump | cond,
|
|
||||||
Jt: jt,
|
|
||||||
Jf: jf,
|
|
||||||
K: a.Val,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns the instruction in assembler notation.
|
|
||||||
func (a JumpIf) String() string {
|
|
||||||
switch a.Cond {
|
|
||||||
// K == A
|
|
||||||
case JumpEqual:
|
|
||||||
return conditionalJump(a, "jeq", "jneq")
|
|
||||||
// K != A
|
|
||||||
case JumpNotEqual:
|
|
||||||
return fmt.Sprintf("jneq #%d,%d", a.Val, a.SkipTrue)
|
|
||||||
// K > A
|
|
||||||
case JumpGreaterThan:
|
|
||||||
return conditionalJump(a, "jgt", "jle")
|
|
||||||
// K < A
|
|
||||||
case JumpLessThan:
|
|
||||||
return fmt.Sprintf("jlt #%d,%d", a.Val, a.SkipTrue)
|
|
||||||
// K >= A
|
|
||||||
case JumpGreaterOrEqual:
|
|
||||||
return conditionalJump(a, "jge", "jlt")
|
|
||||||
// K <= A
|
|
||||||
case JumpLessOrEqual:
|
|
||||||
return fmt.Sprintf("jle #%d,%d", a.Val, a.SkipTrue)
|
|
||||||
// K & A != 0
|
|
||||||
case JumpBitsSet:
|
|
||||||
if a.SkipFalse > 0 {
|
|
||||||
return fmt.Sprintf("jset #%d,%d,%d", a.Val, a.SkipTrue, a.SkipFalse)
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("jset #%d,%d", a.Val, a.SkipTrue)
|
|
||||||
// K & A == 0, there is no assembler instruction for JumpBitNotSet, use JumpBitSet and invert skips
|
|
||||||
case JumpBitsNotSet:
|
|
||||||
return JumpIf{Cond: JumpBitsSet, SkipTrue: a.SkipFalse, SkipFalse: a.SkipTrue, Val: a.Val}.String()
|
|
||||||
default:
|
|
||||||
return fmt.Sprintf("unknown instruction: %#v", a)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func conditionalJump(inst JumpIf, positiveJump, negativeJump string) string {
|
|
||||||
if inst.SkipTrue > 0 {
|
|
||||||
if inst.SkipFalse > 0 {
|
|
||||||
return fmt.Sprintf("%s #%d,%d,%d", positiveJump, inst.Val, inst.SkipTrue, inst.SkipFalse)
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("%s #%d,%d", positiveJump, inst.Val, inst.SkipTrue)
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("%s #%d,%d", negativeJump, inst.Val, inst.SkipFalse)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RetA exits the BPF program, returning the value of register A.
|
|
||||||
type RetA struct{}
|
|
||||||
|
|
||||||
// Assemble implements the Instruction Assemble method.
|
|
||||||
func (a RetA) Assemble() (RawInstruction, error) {
|
|
||||||
return RawInstruction{
|
|
||||||
Op: opClsReturn | opRetSrcA,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns the instruction in assembler notation.
|
|
||||||
func (a RetA) String() string {
|
|
||||||
return fmt.Sprintf("ret a")
|
|
||||||
}
|
|
||||||
|
|
||||||
// RetConstant exits the BPF program, returning a constant value.
|
|
||||||
type RetConstant struct {
|
|
||||||
Val uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assemble implements the Instruction Assemble method.
|
|
||||||
func (a RetConstant) Assemble() (RawInstruction, error) {
|
|
||||||
return RawInstruction{
|
|
||||||
Op: opClsReturn | opRetSrcConstant,
|
|
||||||
K: a.Val,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns the instruction in assembler notation.
|
|
||||||
func (a RetConstant) String() string {
|
|
||||||
return fmt.Sprintf("ret #%d", a.Val)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TXA copies the value of register X to register A.
|
|
||||||
type TXA struct{}
|
|
||||||
|
|
||||||
// Assemble implements the Instruction Assemble method.
|
|
||||||
func (a TXA) Assemble() (RawInstruction, error) {
|
|
||||||
return RawInstruction{
|
|
||||||
Op: opClsMisc | opMiscTXA,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns the instruction in assembler notation.
|
|
||||||
func (a TXA) String() string {
|
|
||||||
return fmt.Sprintf("txa")
|
|
||||||
}
|
|
||||||
|
|
||||||
// TAX copies the value of register A to register X.
|
|
||||||
type TAX struct{}
|
|
||||||
|
|
||||||
// Assemble implements the Instruction Assemble method.
|
|
||||||
func (a TAX) Assemble() (RawInstruction, error) {
|
|
||||||
return RawInstruction{
|
|
||||||
Op: opClsMisc | opMiscTAX,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns the instruction in assembler notation.
|
|
||||||
func (a TAX) String() string {
|
|
||||||
return fmt.Sprintf("tax")
|
|
||||||
}
|
|
||||||
|
|
||||||
func assembleLoad(dst Register, loadSize int, mode uint16, k uint32) (RawInstruction, error) {
|
|
||||||
var (
|
|
||||||
cls uint16
|
|
||||||
sz uint16
|
|
||||||
)
|
|
||||||
switch dst {
|
|
||||||
case RegA:
|
|
||||||
cls = opClsLoadA
|
|
||||||
case RegX:
|
|
||||||
cls = opClsLoadX
|
|
||||||
default:
|
|
||||||
return RawInstruction{}, fmt.Errorf("invalid target register %v", dst)
|
|
||||||
}
|
|
||||||
switch loadSize {
|
|
||||||
case 1:
|
|
||||||
sz = opLoadWidth1
|
|
||||||
case 2:
|
|
||||||
sz = opLoadWidth2
|
|
||||||
case 4:
|
|
||||||
sz = opLoadWidth4
|
|
||||||
default:
|
|
||||||
return RawInstruction{}, fmt.Errorf("invalid load byte length %d", sz)
|
|
||||||
}
|
|
||||||
return RawInstruction{
|
|
||||||
Op: cls | sz | mode,
|
|
||||||
K: k,
|
|
||||||
}, nil
|
|
||||||
}
|
|
@ -1,525 +0,0 @@
|
|||||||
// Copyright 2016 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package bpf
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"reflect"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
// This is a direct translation of the program in
|
|
||||||
// testdata/all_instructions.txt.
|
|
||||||
var allInstructions = []Instruction{
|
|
||||||
LoadConstant{Dst: RegA, Val: 42},
|
|
||||||
LoadConstant{Dst: RegX, Val: 42},
|
|
||||||
|
|
||||||
LoadScratch{Dst: RegA, N: 3},
|
|
||||||
LoadScratch{Dst: RegX, N: 3},
|
|
||||||
|
|
||||||
LoadAbsolute{Off: 42, Size: 1},
|
|
||||||
LoadAbsolute{Off: 42, Size: 2},
|
|
||||||
LoadAbsolute{Off: 42, Size: 4},
|
|
||||||
|
|
||||||
LoadIndirect{Off: 42, Size: 1},
|
|
||||||
LoadIndirect{Off: 42, Size: 2},
|
|
||||||
LoadIndirect{Off: 42, Size: 4},
|
|
||||||
|
|
||||||
LoadMemShift{Off: 42},
|
|
||||||
|
|
||||||
LoadExtension{Num: ExtLen},
|
|
||||||
LoadExtension{Num: ExtProto},
|
|
||||||
LoadExtension{Num: ExtType},
|
|
||||||
LoadExtension{Num: ExtRand},
|
|
||||||
|
|
||||||
StoreScratch{Src: RegA, N: 3},
|
|
||||||
StoreScratch{Src: RegX, N: 3},
|
|
||||||
|
|
||||||
ALUOpConstant{Op: ALUOpAdd, Val: 42},
|
|
||||||
ALUOpConstant{Op: ALUOpSub, Val: 42},
|
|
||||||
ALUOpConstant{Op: ALUOpMul, Val: 42},
|
|
||||||
ALUOpConstant{Op: ALUOpDiv, Val: 42},
|
|
||||||
ALUOpConstant{Op: ALUOpOr, Val: 42},
|
|
||||||
ALUOpConstant{Op: ALUOpAnd, Val: 42},
|
|
||||||
ALUOpConstant{Op: ALUOpShiftLeft, Val: 42},
|
|
||||||
ALUOpConstant{Op: ALUOpShiftRight, Val: 42},
|
|
||||||
ALUOpConstant{Op: ALUOpMod, Val: 42},
|
|
||||||
ALUOpConstant{Op: ALUOpXor, Val: 42},
|
|
||||||
|
|
||||||
ALUOpX{Op: ALUOpAdd},
|
|
||||||
ALUOpX{Op: ALUOpSub},
|
|
||||||
ALUOpX{Op: ALUOpMul},
|
|
||||||
ALUOpX{Op: ALUOpDiv},
|
|
||||||
ALUOpX{Op: ALUOpOr},
|
|
||||||
ALUOpX{Op: ALUOpAnd},
|
|
||||||
ALUOpX{Op: ALUOpShiftLeft},
|
|
||||||
ALUOpX{Op: ALUOpShiftRight},
|
|
||||||
ALUOpX{Op: ALUOpMod},
|
|
||||||
ALUOpX{Op: ALUOpXor},
|
|
||||||
|
|
||||||
NegateA{},
|
|
||||||
|
|
||||||
Jump{Skip: 10},
|
|
||||||
JumpIf{Cond: JumpEqual, Val: 42, SkipTrue: 8, SkipFalse: 9},
|
|
||||||
JumpIf{Cond: JumpNotEqual, Val: 42, SkipTrue: 8},
|
|
||||||
JumpIf{Cond: JumpLessThan, Val: 42, SkipTrue: 7},
|
|
||||||
JumpIf{Cond: JumpLessOrEqual, Val: 42, SkipTrue: 6},
|
|
||||||
JumpIf{Cond: JumpGreaterThan, Val: 42, SkipTrue: 4, SkipFalse: 5},
|
|
||||||
JumpIf{Cond: JumpGreaterOrEqual, Val: 42, SkipTrue: 3, SkipFalse: 4},
|
|
||||||
JumpIf{Cond: JumpBitsSet, Val: 42, SkipTrue: 2, SkipFalse: 3},
|
|
||||||
|
|
||||||
TAX{},
|
|
||||||
TXA{},
|
|
||||||
|
|
||||||
RetA{},
|
|
||||||
RetConstant{Val: 42},
|
|
||||||
}
|
|
||||||
var allInstructionsExpected = "testdata/all_instructions.bpf"
|
|
||||||
|
|
||||||
// Check that we produce the same output as the canonical bpf_asm
|
|
||||||
// linux kernel tool.
|
|
||||||
func TestInterop(t *testing.T) {
|
|
||||||
out, err := Assemble(allInstructions)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("assembly of allInstructions program failed: %s", err)
|
|
||||||
}
|
|
||||||
t.Logf("Assembled program is %d instructions long", len(out))
|
|
||||||
|
|
||||||
bs, err := ioutil.ReadFile(allInstructionsExpected)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("reading %s: %s", allInstructionsExpected, err)
|
|
||||||
}
|
|
||||||
// First statement is the number of statements, last statement is
|
|
||||||
// empty. We just ignore both and rely on slice length.
|
|
||||||
stmts := strings.Split(string(bs), ",")
|
|
||||||
if len(stmts)-2 != len(out) {
|
|
||||||
t.Fatalf("test program lengths don't match: %s has %d, Go implementation has %d", allInstructionsExpected, len(stmts)-2, len(allInstructions))
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, stmt := range stmts[1 : len(stmts)-2] {
|
|
||||||
nums := strings.Split(stmt, " ")
|
|
||||||
if len(nums) != 4 {
|
|
||||||
t.Fatalf("malformed instruction %d in %s: %s", i+1, allInstructionsExpected, stmt)
|
|
||||||
}
|
|
||||||
|
|
||||||
actual := out[i]
|
|
||||||
|
|
||||||
op, err := strconv.ParseUint(nums[0], 10, 16)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("malformed opcode %s in instruction %d of %s", nums[0], i+1, allInstructionsExpected)
|
|
||||||
}
|
|
||||||
if actual.Op != uint16(op) {
|
|
||||||
t.Errorf("opcode mismatch on instruction %d (%#v): got 0x%02x, want 0x%02x", i+1, allInstructions[i], actual.Op, op)
|
|
||||||
}
|
|
||||||
|
|
||||||
jt, err := strconv.ParseUint(nums[1], 10, 8)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("malformed jt offset %s in instruction %d of %s", nums[1], i+1, allInstructionsExpected)
|
|
||||||
}
|
|
||||||
if actual.Jt != uint8(jt) {
|
|
||||||
t.Errorf("jt mismatch on instruction %d (%#v): got %d, want %d", i+1, allInstructions[i], actual.Jt, jt)
|
|
||||||
}
|
|
||||||
|
|
||||||
jf, err := strconv.ParseUint(nums[2], 10, 8)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("malformed jf offset %s in instruction %d of %s", nums[2], i+1, allInstructionsExpected)
|
|
||||||
}
|
|
||||||
if actual.Jf != uint8(jf) {
|
|
||||||
t.Errorf("jf mismatch on instruction %d (%#v): got %d, want %d", i+1, allInstructions[i], actual.Jf, jf)
|
|
||||||
}
|
|
||||||
|
|
||||||
k, err := strconv.ParseUint(nums[3], 10, 32)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("malformed constant %s in instruction %d of %s", nums[3], i+1, allInstructionsExpected)
|
|
||||||
}
|
|
||||||
if actual.K != uint32(k) {
|
|
||||||
t.Errorf("constant mismatch on instruction %d (%#v): got %d, want %d", i+1, allInstructions[i], actual.K, k)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check that assembly and disassembly match each other.
|
|
||||||
func TestAsmDisasm(t *testing.T) {
|
|
||||||
prog1, err := Assemble(allInstructions)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("assembly of allInstructions program failed: %s", err)
|
|
||||||
}
|
|
||||||
t.Logf("Assembled program is %d instructions long", len(prog1))
|
|
||||||
|
|
||||||
got, allDecoded := Disassemble(prog1)
|
|
||||||
if !allDecoded {
|
|
||||||
t.Errorf("Disassemble(Assemble(allInstructions)) produced unrecognized instructions:")
|
|
||||||
for i, inst := range got {
|
|
||||||
if r, ok := inst.(RawInstruction); ok {
|
|
||||||
t.Logf(" insn %d, %#v --> %#v", i+1, allInstructions[i], r)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(allInstructions) != len(got) {
|
|
||||||
t.Fatalf("disassembly changed program size: %d insns before, %d insns after", len(allInstructions), len(got))
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(allInstructions, got) {
|
|
||||||
t.Errorf("program mutated by disassembly:")
|
|
||||||
for i := range got {
|
|
||||||
if !reflect.DeepEqual(allInstructions[i], got[i]) {
|
|
||||||
t.Logf(" insn %d, s: %#v, p1: %#v, got: %#v", i+1, allInstructions[i], prog1[i], got[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type InvalidInstruction struct{}
|
|
||||||
|
|
||||||
func (a InvalidInstruction) Assemble() (RawInstruction, error) {
|
|
||||||
return RawInstruction{}, fmt.Errorf("Invalid Instruction")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a InvalidInstruction) String() string {
|
|
||||||
return fmt.Sprintf("unknown instruction: %#v", a)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestString(t *testing.T) {
|
|
||||||
testCases := []struct {
|
|
||||||
instruction Instruction
|
|
||||||
assembler string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
instruction: LoadConstant{Dst: RegA, Val: 42},
|
|
||||||
assembler: "ld #42",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
instruction: LoadConstant{Dst: RegX, Val: 42},
|
|
||||||
assembler: "ldx #42",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
instruction: LoadConstant{Dst: 0xffff, Val: 42},
|
|
||||||
assembler: "unknown instruction: bpf.LoadConstant{Dst:0xffff, Val:0x2a}",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
instruction: LoadScratch{Dst: RegA, N: 3},
|
|
||||||
assembler: "ld M[3]",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
instruction: LoadScratch{Dst: RegX, N: 3},
|
|
||||||
assembler: "ldx M[3]",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
instruction: LoadScratch{Dst: 0xffff, N: 3},
|
|
||||||
assembler: "unknown instruction: bpf.LoadScratch{Dst:0xffff, N:3}",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
instruction: LoadAbsolute{Off: 42, Size: 1},
|
|
||||||
assembler: "ldb [42]",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
instruction: LoadAbsolute{Off: 42, Size: 2},
|
|
||||||
assembler: "ldh [42]",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
instruction: LoadAbsolute{Off: 42, Size: 4},
|
|
||||||
assembler: "ld [42]",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
instruction: LoadAbsolute{Off: 42, Size: -1},
|
|
||||||
assembler: "unknown instruction: bpf.LoadAbsolute{Off:0x2a, Size:-1}",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
instruction: LoadIndirect{Off: 42, Size: 1},
|
|
||||||
assembler: "ldb [x + 42]",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
instruction: LoadIndirect{Off: 42, Size: 2},
|
|
||||||
assembler: "ldh [x + 42]",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
instruction: LoadIndirect{Off: 42, Size: 4},
|
|
||||||
assembler: "ld [x + 42]",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
instruction: LoadIndirect{Off: 42, Size: -1},
|
|
||||||
assembler: "unknown instruction: bpf.LoadIndirect{Off:0x2a, Size:-1}",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
instruction: LoadMemShift{Off: 42},
|
|
||||||
assembler: "ldx 4*([42]&0xf)",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
instruction: LoadExtension{Num: ExtLen},
|
|
||||||
assembler: "ld #len",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
instruction: LoadExtension{Num: ExtProto},
|
|
||||||
assembler: "ld #proto",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
instruction: LoadExtension{Num: ExtType},
|
|
||||||
assembler: "ld #type",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
instruction: LoadExtension{Num: ExtPayloadOffset},
|
|
||||||
assembler: "ld #poff",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
instruction: LoadExtension{Num: ExtInterfaceIndex},
|
|
||||||
assembler: "ld #ifidx",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
instruction: LoadExtension{Num: ExtNetlinkAttr},
|
|
||||||
assembler: "ld #nla",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
instruction: LoadExtension{Num: ExtNetlinkAttrNested},
|
|
||||||
assembler: "ld #nlan",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
instruction: LoadExtension{Num: ExtMark},
|
|
||||||
assembler: "ld #mark",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
instruction: LoadExtension{Num: ExtQueue},
|
|
||||||
assembler: "ld #queue",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
instruction: LoadExtension{Num: ExtLinkLayerType},
|
|
||||||
assembler: "ld #hatype",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
instruction: LoadExtension{Num: ExtRXHash},
|
|
||||||
assembler: "ld #rxhash",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
instruction: LoadExtension{Num: ExtCPUID},
|
|
||||||
assembler: "ld #cpu",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
instruction: LoadExtension{Num: ExtVLANTag},
|
|
||||||
assembler: "ld #vlan_tci",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
instruction: LoadExtension{Num: ExtVLANTagPresent},
|
|
||||||
assembler: "ld #vlan_avail",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
instruction: LoadExtension{Num: ExtVLANProto},
|
|
||||||
assembler: "ld #vlan_tpid",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
instruction: LoadExtension{Num: ExtRand},
|
|
||||||
assembler: "ld #rand",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
instruction: LoadAbsolute{Off: 0xfffff038, Size: 4},
|
|
||||||
assembler: "ld #rand",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
instruction: LoadExtension{Num: 0xfff},
|
|
||||||
assembler: "unknown instruction: bpf.LoadExtension{Num:4095}",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
instruction: StoreScratch{Src: RegA, N: 3},
|
|
||||||
assembler: "st M[3]",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
instruction: StoreScratch{Src: RegX, N: 3},
|
|
||||||
assembler: "stx M[3]",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
instruction: StoreScratch{Src: 0xffff, N: 3},
|
|
||||||
assembler: "unknown instruction: bpf.StoreScratch{Src:0xffff, N:3}",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
instruction: ALUOpConstant{Op: ALUOpAdd, Val: 42},
|
|
||||||
assembler: "add #42",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
instruction: ALUOpConstant{Op: ALUOpSub, Val: 42},
|
|
||||||
assembler: "sub #42",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
instruction: ALUOpConstant{Op: ALUOpMul, Val: 42},
|
|
||||||
assembler: "mul #42",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
instruction: ALUOpConstant{Op: ALUOpDiv, Val: 42},
|
|
||||||
assembler: "div #42",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
instruction: ALUOpConstant{Op: ALUOpOr, Val: 42},
|
|
||||||
assembler: "or #42",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
instruction: ALUOpConstant{Op: ALUOpAnd, Val: 42},
|
|
||||||
assembler: "and #42",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
instruction: ALUOpConstant{Op: ALUOpShiftLeft, Val: 42},
|
|
||||||
assembler: "lsh #42",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
instruction: ALUOpConstant{Op: ALUOpShiftRight, Val: 42},
|
|
||||||
assembler: "rsh #42",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
instruction: ALUOpConstant{Op: ALUOpMod, Val: 42},
|
|
||||||
assembler: "mod #42",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
instruction: ALUOpConstant{Op: ALUOpXor, Val: 42},
|
|
||||||
assembler: "xor #42",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
instruction: ALUOpConstant{Op: 0xffff, Val: 42},
|
|
||||||
assembler: "unknown instruction: bpf.ALUOpConstant{Op:0xffff, Val:0x2a}",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
instruction: ALUOpX{Op: ALUOpAdd},
|
|
||||||
assembler: "add x",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
instruction: ALUOpX{Op: ALUOpSub},
|
|
||||||
assembler: "sub x",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
instruction: ALUOpX{Op: ALUOpMul},
|
|
||||||
assembler: "mul x",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
instruction: ALUOpX{Op: ALUOpDiv},
|
|
||||||
assembler: "div x",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
instruction: ALUOpX{Op: ALUOpOr},
|
|
||||||
assembler: "or x",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
instruction: ALUOpX{Op: ALUOpAnd},
|
|
||||||
assembler: "and x",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
instruction: ALUOpX{Op: ALUOpShiftLeft},
|
|
||||||
assembler: "lsh x",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
instruction: ALUOpX{Op: ALUOpShiftRight},
|
|
||||||
assembler: "rsh x",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
instruction: ALUOpX{Op: ALUOpMod},
|
|
||||||
assembler: "mod x",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
instruction: ALUOpX{Op: ALUOpXor},
|
|
||||||
assembler: "xor x",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
instruction: ALUOpX{Op: 0xffff},
|
|
||||||
assembler: "unknown instruction: bpf.ALUOpX{Op:0xffff}",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
instruction: NegateA{},
|
|
||||||
assembler: "neg",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
instruction: Jump{Skip: 10},
|
|
||||||
assembler: "ja 10",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
instruction: JumpIf{Cond: JumpEqual, Val: 42, SkipTrue: 8, SkipFalse: 9},
|
|
||||||
assembler: "jeq #42,8,9",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
instruction: JumpIf{Cond: JumpEqual, Val: 42, SkipTrue: 8},
|
|
||||||
assembler: "jeq #42,8",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
instruction: JumpIf{Cond: JumpEqual, Val: 42, SkipFalse: 8},
|
|
||||||
assembler: "jneq #42,8",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
instruction: JumpIf{Cond: JumpNotEqual, Val: 42, SkipTrue: 8},
|
|
||||||
assembler: "jneq #42,8",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
instruction: JumpIf{Cond: JumpLessThan, Val: 42, SkipTrue: 7},
|
|
||||||
assembler: "jlt #42,7",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
instruction: JumpIf{Cond: JumpLessOrEqual, Val: 42, SkipTrue: 6},
|
|
||||||
assembler: "jle #42,6",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
instruction: JumpIf{Cond: JumpGreaterThan, Val: 42, SkipTrue: 4, SkipFalse: 5},
|
|
||||||
assembler: "jgt #42,4,5",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
instruction: JumpIf{Cond: JumpGreaterThan, Val: 42, SkipTrue: 4},
|
|
||||||
assembler: "jgt #42,4",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
instruction: JumpIf{Cond: JumpGreaterOrEqual, Val: 42, SkipTrue: 3, SkipFalse: 4},
|
|
||||||
assembler: "jge #42,3,4",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
instruction: JumpIf{Cond: JumpGreaterOrEqual, Val: 42, SkipTrue: 3},
|
|
||||||
assembler: "jge #42,3",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
instruction: JumpIf{Cond: JumpBitsSet, Val: 42, SkipTrue: 2, SkipFalse: 3},
|
|
||||||
assembler: "jset #42,2,3",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
instruction: JumpIf{Cond: JumpBitsSet, Val: 42, SkipTrue: 2},
|
|
||||||
assembler: "jset #42,2",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
instruction: JumpIf{Cond: JumpBitsNotSet, Val: 42, SkipTrue: 2, SkipFalse: 3},
|
|
||||||
assembler: "jset #42,3,2",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
instruction: JumpIf{Cond: JumpBitsNotSet, Val: 42, SkipTrue: 2},
|
|
||||||
assembler: "jset #42,0,2",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
instruction: JumpIf{Cond: 0xffff, Val: 42, SkipTrue: 1, SkipFalse: 2},
|
|
||||||
assembler: "unknown instruction: bpf.JumpIf{Cond:0xffff, Val:0x2a, SkipTrue:0x1, SkipFalse:0x2}",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
instruction: TAX{},
|
|
||||||
assembler: "tax",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
instruction: TXA{},
|
|
||||||
assembler: "txa",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
instruction: RetA{},
|
|
||||||
assembler: "ret a",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
instruction: RetConstant{Val: 42},
|
|
||||||
assembler: "ret #42",
|
|
||||||
},
|
|
||||||
// Invalid instruction
|
|
||||||
{
|
|
||||||
instruction: InvalidInstruction{},
|
|
||||||
assembler: "unknown instruction: bpf.InvalidInstruction{}",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, testCase := range testCases {
|
|
||||||
if input, ok := testCase.instruction.(fmt.Stringer); ok {
|
|
||||||
got := input.String()
|
|
||||||
if got != testCase.assembler {
|
|
||||||
t.Errorf("String did not return expected assembler notation, expected: %s, got: %s", testCase.assembler, got)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
t.Errorf("Instruction %#v is not a fmt.Stringer", testCase.instruction)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,10 +0,0 @@
|
|||||||
// Copyright 2017 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package bpf
|
|
||||||
|
|
||||||
// A Setter is a type which can attach a compiled BPF filter to itself.
|
|
||||||
type Setter interface {
|
|
||||||
SetBPF(filter []RawInstruction) error
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
50,0 0 0 42,1 0 0 42,96 0 0 3,97 0 0 3,48 0 0 42,40 0 0 42,32 0 0 42,80 0 0 42,72 0 0 42,64 0 0 42,177 0 0 42,128 0 0 0,32 0 0 4294963200,32 0 0 4294963204,32 0 0 4294963256,2 0 0 3,3 0 0 3,4 0 0 42,20 0 0 42,36 0 0 42,52 0 0 42,68 0 0 42,84 0 0 42,100 0 0 42,116 0 0 42,148 0 0 42,164 0 0 42,12 0 0 0,28 0 0 0,44 0 0 0,60 0 0 0,76 0 0 0,92 0 0 0,108 0 0 0,124 0 0 0,156 0 0 0,172 0 0 0,132 0 0 0,5 0 0 10,21 8 9 42,21 0 8 42,53 0 7 42,37 0 6 42,37 4 5 42,53 3 4 42,69 2 3 42,7 0 0 0,135 0 0 0,22 0 0 0,6 0 0 0,
|
|
@ -1,79 +0,0 @@
|
|||||||
# This filter is compiled to all_instructions.bpf by the `bpf_asm`
|
|
||||||
# tool, which can be found in the linux kernel source tree under
|
|
||||||
# tools/net.
|
|
||||||
|
|
||||||
# Load immediate
|
|
||||||
ld #42
|
|
||||||
ldx #42
|
|
||||||
|
|
||||||
# Load scratch
|
|
||||||
ld M[3]
|
|
||||||
ldx M[3]
|
|
||||||
|
|
||||||
# Load absolute
|
|
||||||
ldb [42]
|
|
||||||
ldh [42]
|
|
||||||
ld [42]
|
|
||||||
|
|
||||||
# Load indirect
|
|
||||||
ldb [x + 42]
|
|
||||||
ldh [x + 42]
|
|
||||||
ld [x + 42]
|
|
||||||
|
|
||||||
# Load IPv4 header length
|
|
||||||
ldx 4*([42]&0xf)
|
|
||||||
|
|
||||||
# Run extension function
|
|
||||||
ld #len
|
|
||||||
ld #proto
|
|
||||||
ld #type
|
|
||||||
ld #rand
|
|
||||||
|
|
||||||
# Store scratch
|
|
||||||
st M[3]
|
|
||||||
stx M[3]
|
|
||||||
|
|
||||||
# A <op> constant
|
|
||||||
add #42
|
|
||||||
sub #42
|
|
||||||
mul #42
|
|
||||||
div #42
|
|
||||||
or #42
|
|
||||||
and #42
|
|
||||||
lsh #42
|
|
||||||
rsh #42
|
|
||||||
mod #42
|
|
||||||
xor #42
|
|
||||||
|
|
||||||
# A <op> X
|
|
||||||
add x
|
|
||||||
sub x
|
|
||||||
mul x
|
|
||||||
div x
|
|
||||||
or x
|
|
||||||
and x
|
|
||||||
lsh x
|
|
||||||
rsh x
|
|
||||||
mod x
|
|
||||||
xor x
|
|
||||||
|
|
||||||
# !A
|
|
||||||
neg
|
|
||||||
|
|
||||||
# Jumps
|
|
||||||
ja end
|
|
||||||
jeq #42,prev,end
|
|
||||||
jne #42,end
|
|
||||||
jlt #42,end
|
|
||||||
jle #42,end
|
|
||||||
jgt #42,prev,end
|
|
||||||
jge #42,prev,end
|
|
||||||
jset #42,prev,end
|
|
||||||
|
|
||||||
# Register transfers
|
|
||||||
tax
|
|
||||||
txa
|
|
||||||
|
|
||||||
# Returns
|
|
||||||
prev: ret a
|
|
||||||
end: ret #42
|
|
@ -1,140 +0,0 @@
|
|||||||
// Copyright 2016 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package bpf
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
// A VM is an emulated BPF virtual machine.
|
|
||||||
type VM struct {
|
|
||||||
filter []Instruction
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewVM returns a new VM using the input BPF program.
|
|
||||||
func NewVM(filter []Instruction) (*VM, error) {
|
|
||||||
if len(filter) == 0 {
|
|
||||||
return nil, errors.New("one or more Instructions must be specified")
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, ins := range filter {
|
|
||||||
check := len(filter) - (i + 1)
|
|
||||||
switch ins := ins.(type) {
|
|
||||||
// Check for out-of-bounds jumps in instructions
|
|
||||||
case Jump:
|
|
||||||
if check <= int(ins.Skip) {
|
|
||||||
return nil, fmt.Errorf("cannot jump %d instructions; jumping past program bounds", ins.Skip)
|
|
||||||
}
|
|
||||||
case JumpIf:
|
|
||||||
if check <= int(ins.SkipTrue) {
|
|
||||||
return nil, fmt.Errorf("cannot jump %d instructions in true case; jumping past program bounds", ins.SkipTrue)
|
|
||||||
}
|
|
||||||
if check <= int(ins.SkipFalse) {
|
|
||||||
return nil, fmt.Errorf("cannot jump %d instructions in false case; jumping past program bounds", ins.SkipFalse)
|
|
||||||
}
|
|
||||||
// Check for division or modulus by zero
|
|
||||||
case ALUOpConstant:
|
|
||||||
if ins.Val != 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
switch ins.Op {
|
|
||||||
case ALUOpDiv, ALUOpMod:
|
|
||||||
return nil, errors.New("cannot divide by zero using ALUOpConstant")
|
|
||||||
}
|
|
||||||
// Check for unknown extensions
|
|
||||||
case LoadExtension:
|
|
||||||
switch ins.Num {
|
|
||||||
case ExtLen:
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("extension %d not implemented", ins.Num)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure last instruction is a return instruction
|
|
||||||
switch filter[len(filter)-1].(type) {
|
|
||||||
case RetA, RetConstant:
|
|
||||||
default:
|
|
||||||
return nil, errors.New("BPF program must end with RetA or RetConstant")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Though our VM works using disassembled instructions, we
|
|
||||||
// attempt to assemble the input filter anyway to ensure it is compatible
|
|
||||||
// with an operating system VM.
|
|
||||||
_, err := Assemble(filter)
|
|
||||||
|
|
||||||
return &VM{
|
|
||||||
filter: filter,
|
|
||||||
}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run runs the VM's BPF program against the input bytes.
|
|
||||||
// Run returns the number of bytes accepted by the BPF program, and any errors
|
|
||||||
// which occurred while processing the program.
|
|
||||||
func (v *VM) Run(in []byte) (int, error) {
|
|
||||||
var (
|
|
||||||
// Registers of the virtual machine
|
|
||||||
regA uint32
|
|
||||||
regX uint32
|
|
||||||
regScratch [16]uint32
|
|
||||||
|
|
||||||
// OK is true if the program should continue processing the next
|
|
||||||
// instruction, or false if not, causing the loop to break
|
|
||||||
ok = true
|
|
||||||
)
|
|
||||||
|
|
||||||
// TODO(mdlayher): implement:
|
|
||||||
// - NegateA:
|
|
||||||
// - would require a change from uint32 registers to int32
|
|
||||||
// registers
|
|
||||||
|
|
||||||
// TODO(mdlayher): add interop tests that check signedness of ALU
|
|
||||||
// operations against kernel implementation, and make sure Go
|
|
||||||
// implementation matches behavior
|
|
||||||
|
|
||||||
for i := 0; i < len(v.filter) && ok; i++ {
|
|
||||||
ins := v.filter[i]
|
|
||||||
|
|
||||||
switch ins := ins.(type) {
|
|
||||||
case ALUOpConstant:
|
|
||||||
regA = aluOpConstant(ins, regA)
|
|
||||||
case ALUOpX:
|
|
||||||
regA, ok = aluOpX(ins, regA, regX)
|
|
||||||
case Jump:
|
|
||||||
i += int(ins.Skip)
|
|
||||||
case JumpIf:
|
|
||||||
jump := jumpIf(ins, regA)
|
|
||||||
i += jump
|
|
||||||
case LoadAbsolute:
|
|
||||||
regA, ok = loadAbsolute(ins, in)
|
|
||||||
case LoadConstant:
|
|
||||||
regA, regX = loadConstant(ins, regA, regX)
|
|
||||||
case LoadExtension:
|
|
||||||
regA = loadExtension(ins, in)
|
|
||||||
case LoadIndirect:
|
|
||||||
regA, ok = loadIndirect(ins, in, regX)
|
|
||||||
case LoadMemShift:
|
|
||||||
regX, ok = loadMemShift(ins, in)
|
|
||||||
case LoadScratch:
|
|
||||||
regA, regX = loadScratch(ins, regScratch, regA, regX)
|
|
||||||
case RetA:
|
|
||||||
return int(regA), nil
|
|
||||||
case RetConstant:
|
|
||||||
return int(ins.Val), nil
|
|
||||||
case StoreScratch:
|
|
||||||
regScratch = storeScratch(ins, regScratch, regA, regX)
|
|
||||||
case TAX:
|
|
||||||
regX = regA
|
|
||||||
case TXA:
|
|
||||||
regA = regX
|
|
||||||
default:
|
|
||||||
return 0, fmt.Errorf("unknown Instruction at index %d: %T", i, ins)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
@ -1,512 +0,0 @@
|
|||||||
// Copyright 2016 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package bpf_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"golang.org/x/net/bpf"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestVMALUOpAdd(t *testing.T) {
|
|
||||||
vm, done, err := testVM(t, []bpf.Instruction{
|
|
||||||
bpf.LoadAbsolute{
|
|
||||||
Off: 8,
|
|
||||||
Size: 1,
|
|
||||||
},
|
|
||||||
bpf.ALUOpConstant{
|
|
||||||
Op: bpf.ALUOpAdd,
|
|
||||||
Val: 3,
|
|
||||||
},
|
|
||||||
bpf.RetA{},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to load BPF program: %v", err)
|
|
||||||
}
|
|
||||||
defer done()
|
|
||||||
|
|
||||||
out, err := vm.Run([]byte{
|
|
||||||
0xff, 0xff, 0xff, 0xff,
|
|
||||||
0xff, 0xff, 0xff, 0xff,
|
|
||||||
8, 2, 3,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error while running program: %v", err)
|
|
||||||
}
|
|
||||||
if want, got := 3, out; want != got {
|
|
||||||
t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
|
|
||||||
want, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVMALUOpSub(t *testing.T) {
|
|
||||||
vm, done, err := testVM(t, []bpf.Instruction{
|
|
||||||
bpf.LoadAbsolute{
|
|
||||||
Off: 8,
|
|
||||||
Size: 1,
|
|
||||||
},
|
|
||||||
bpf.TAX{},
|
|
||||||
bpf.ALUOpX{
|
|
||||||
Op: bpf.ALUOpSub,
|
|
||||||
},
|
|
||||||
bpf.RetA{},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to load BPF program: %v", err)
|
|
||||||
}
|
|
||||||
defer done()
|
|
||||||
|
|
||||||
out, err := vm.Run([]byte{
|
|
||||||
0xff, 0xff, 0xff, 0xff,
|
|
||||||
0xff, 0xff, 0xff, 0xff,
|
|
||||||
1, 2, 3,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error while running program: %v", err)
|
|
||||||
}
|
|
||||||
if want, got := 0, out; want != got {
|
|
||||||
t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
|
|
||||||
want, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVMALUOpMul(t *testing.T) {
|
|
||||||
vm, done, err := testVM(t, []bpf.Instruction{
|
|
||||||
bpf.LoadAbsolute{
|
|
||||||
Off: 8,
|
|
||||||
Size: 1,
|
|
||||||
},
|
|
||||||
bpf.ALUOpConstant{
|
|
||||||
Op: bpf.ALUOpMul,
|
|
||||||
Val: 2,
|
|
||||||
},
|
|
||||||
bpf.RetA{},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to load BPF program: %v", err)
|
|
||||||
}
|
|
||||||
defer done()
|
|
||||||
|
|
||||||
out, err := vm.Run([]byte{
|
|
||||||
0xff, 0xff, 0xff, 0xff,
|
|
||||||
0xff, 0xff, 0xff, 0xff,
|
|
||||||
6, 2, 3, 4,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error while running program: %v", err)
|
|
||||||
}
|
|
||||||
if want, got := 4, out; want != got {
|
|
||||||
t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
|
|
||||||
want, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVMALUOpDiv(t *testing.T) {
|
|
||||||
vm, done, err := testVM(t, []bpf.Instruction{
|
|
||||||
bpf.LoadAbsolute{
|
|
||||||
Off: 8,
|
|
||||||
Size: 1,
|
|
||||||
},
|
|
||||||
bpf.ALUOpConstant{
|
|
||||||
Op: bpf.ALUOpDiv,
|
|
||||||
Val: 2,
|
|
||||||
},
|
|
||||||
bpf.RetA{},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to load BPF program: %v", err)
|
|
||||||
}
|
|
||||||
defer done()
|
|
||||||
|
|
||||||
out, err := vm.Run([]byte{
|
|
||||||
0xff, 0xff, 0xff, 0xff,
|
|
||||||
0xff, 0xff, 0xff, 0xff,
|
|
||||||
20, 2, 3, 4,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error while running program: %v", err)
|
|
||||||
}
|
|
||||||
if want, got := 2, out; want != got {
|
|
||||||
t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
|
|
||||||
want, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVMALUOpDivByZeroALUOpConstant(t *testing.T) {
|
|
||||||
_, _, err := testVM(t, []bpf.Instruction{
|
|
||||||
bpf.ALUOpConstant{
|
|
||||||
Op: bpf.ALUOpDiv,
|
|
||||||
Val: 0,
|
|
||||||
},
|
|
||||||
bpf.RetA{},
|
|
||||||
})
|
|
||||||
if errStr(err) != "cannot divide by zero using ALUOpConstant" {
|
|
||||||
t.Fatalf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVMALUOpDivByZeroALUOpX(t *testing.T) {
|
|
||||||
vm, done, err := testVM(t, []bpf.Instruction{
|
|
||||||
// Load byte 0 into X
|
|
||||||
bpf.LoadAbsolute{
|
|
||||||
Off: 8,
|
|
||||||
Size: 1,
|
|
||||||
},
|
|
||||||
bpf.TAX{},
|
|
||||||
// Load byte 1 into A
|
|
||||||
bpf.LoadAbsolute{
|
|
||||||
Off: 9,
|
|
||||||
Size: 1,
|
|
||||||
},
|
|
||||||
// Attempt to perform 1/0
|
|
||||||
bpf.ALUOpX{
|
|
||||||
Op: bpf.ALUOpDiv,
|
|
||||||
},
|
|
||||||
// Return 4 bytes if program does not terminate
|
|
||||||
bpf.LoadConstant{
|
|
||||||
Val: 12,
|
|
||||||
},
|
|
||||||
bpf.RetA{},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to load BPF program: %v", err)
|
|
||||||
}
|
|
||||||
defer done()
|
|
||||||
|
|
||||||
out, err := vm.Run([]byte{
|
|
||||||
0xff, 0xff, 0xff, 0xff,
|
|
||||||
0xff, 0xff, 0xff, 0xff,
|
|
||||||
0, 1, 3, 4,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error while running program: %v", err)
|
|
||||||
}
|
|
||||||
if want, got := 0, out; want != got {
|
|
||||||
t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
|
|
||||||
want, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVMALUOpOr(t *testing.T) {
|
|
||||||
vm, done, err := testVM(t, []bpf.Instruction{
|
|
||||||
bpf.LoadAbsolute{
|
|
||||||
Off: 8,
|
|
||||||
Size: 2,
|
|
||||||
},
|
|
||||||
bpf.ALUOpConstant{
|
|
||||||
Op: bpf.ALUOpOr,
|
|
||||||
Val: 0x01,
|
|
||||||
},
|
|
||||||
bpf.RetA{},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to load BPF program: %v", err)
|
|
||||||
}
|
|
||||||
defer done()
|
|
||||||
|
|
||||||
out, err := vm.Run([]byte{
|
|
||||||
0xff, 0xff, 0xff, 0xff,
|
|
||||||
0xff, 0xff, 0xff, 0xff,
|
|
||||||
0x00, 0x10, 0x03, 0x04,
|
|
||||||
0x05, 0x06, 0x07, 0x08,
|
|
||||||
0x09, 0xff,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error while running program: %v", err)
|
|
||||||
}
|
|
||||||
if want, got := 9, out; want != got {
|
|
||||||
t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
|
|
||||||
want, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVMALUOpAnd(t *testing.T) {
|
|
||||||
vm, done, err := testVM(t, []bpf.Instruction{
|
|
||||||
bpf.LoadAbsolute{
|
|
||||||
Off: 8,
|
|
||||||
Size: 2,
|
|
||||||
},
|
|
||||||
bpf.ALUOpConstant{
|
|
||||||
Op: bpf.ALUOpAnd,
|
|
||||||
Val: 0x0019,
|
|
||||||
},
|
|
||||||
bpf.RetA{},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to load BPF program: %v", err)
|
|
||||||
}
|
|
||||||
defer done()
|
|
||||||
|
|
||||||
out, err := vm.Run([]byte{
|
|
||||||
0xff, 0xff, 0xff, 0xff,
|
|
||||||
0xff, 0xff, 0xff, 0xff,
|
|
||||||
0xaa, 0x09,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error while running program: %v", err)
|
|
||||||
}
|
|
||||||
if want, got := 1, out; want != got {
|
|
||||||
t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
|
|
||||||
want, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVMALUOpShiftLeft(t *testing.T) {
|
|
||||||
vm, done, err := testVM(t, []bpf.Instruction{
|
|
||||||
bpf.LoadAbsolute{
|
|
||||||
Off: 8,
|
|
||||||
Size: 1,
|
|
||||||
},
|
|
||||||
bpf.ALUOpConstant{
|
|
||||||
Op: bpf.ALUOpShiftLeft,
|
|
||||||
Val: 0x01,
|
|
||||||
},
|
|
||||||
bpf.JumpIf{
|
|
||||||
Cond: bpf.JumpEqual,
|
|
||||||
Val: 0x02,
|
|
||||||
SkipTrue: 1,
|
|
||||||
},
|
|
||||||
bpf.RetConstant{
|
|
||||||
Val: 0,
|
|
||||||
},
|
|
||||||
bpf.RetConstant{
|
|
||||||
Val: 9,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to load BPF program: %v", err)
|
|
||||||
}
|
|
||||||
defer done()
|
|
||||||
|
|
||||||
out, err := vm.Run([]byte{
|
|
||||||
0xff, 0xff, 0xff, 0xff,
|
|
||||||
0xff, 0xff, 0xff, 0xff,
|
|
||||||
0x01, 0xaa,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error while running program: %v", err)
|
|
||||||
}
|
|
||||||
if want, got := 1, out; want != got {
|
|
||||||
t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
|
|
||||||
want, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVMALUOpShiftRight(t *testing.T) {
|
|
||||||
vm, done, err := testVM(t, []bpf.Instruction{
|
|
||||||
bpf.LoadAbsolute{
|
|
||||||
Off: 8,
|
|
||||||
Size: 1,
|
|
||||||
},
|
|
||||||
bpf.ALUOpConstant{
|
|
||||||
Op: bpf.ALUOpShiftRight,
|
|
||||||
Val: 0x01,
|
|
||||||
},
|
|
||||||
bpf.JumpIf{
|
|
||||||
Cond: bpf.JumpEqual,
|
|
||||||
Val: 0x04,
|
|
||||||
SkipTrue: 1,
|
|
||||||
},
|
|
||||||
bpf.RetConstant{
|
|
||||||
Val: 0,
|
|
||||||
},
|
|
||||||
bpf.RetConstant{
|
|
||||||
Val: 9,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to load BPF program: %v", err)
|
|
||||||
}
|
|
||||||
defer done()
|
|
||||||
|
|
||||||
out, err := vm.Run([]byte{
|
|
||||||
0xff, 0xff, 0xff, 0xff,
|
|
||||||
0xff, 0xff, 0xff, 0xff,
|
|
||||||
0x08, 0xff, 0xff,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error while running program: %v", err)
|
|
||||||
}
|
|
||||||
if want, got := 1, out; want != got {
|
|
||||||
t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
|
|
||||||
want, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVMALUOpMod(t *testing.T) {
|
|
||||||
vm, done, err := testVM(t, []bpf.Instruction{
|
|
||||||
bpf.LoadAbsolute{
|
|
||||||
Off: 8,
|
|
||||||
Size: 1,
|
|
||||||
},
|
|
||||||
bpf.ALUOpConstant{
|
|
||||||
Op: bpf.ALUOpMod,
|
|
||||||
Val: 20,
|
|
||||||
},
|
|
||||||
bpf.RetA{},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to load BPF program: %v", err)
|
|
||||||
}
|
|
||||||
defer done()
|
|
||||||
|
|
||||||
out, err := vm.Run([]byte{
|
|
||||||
0xff, 0xff, 0xff, 0xff,
|
|
||||||
0xff, 0xff, 0xff, 0xff,
|
|
||||||
30, 0, 0,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error while running program: %v", err)
|
|
||||||
}
|
|
||||||
if want, got := 2, out; want != got {
|
|
||||||
t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
|
|
||||||
want, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVMALUOpModByZeroALUOpConstant(t *testing.T) {
|
|
||||||
_, _, err := testVM(t, []bpf.Instruction{
|
|
||||||
bpf.LoadAbsolute{
|
|
||||||
Off: 8,
|
|
||||||
Size: 1,
|
|
||||||
},
|
|
||||||
bpf.ALUOpConstant{
|
|
||||||
Op: bpf.ALUOpMod,
|
|
||||||
Val: 0,
|
|
||||||
},
|
|
||||||
bpf.RetA{},
|
|
||||||
})
|
|
||||||
if errStr(err) != "cannot divide by zero using ALUOpConstant" {
|
|
||||||
t.Fatalf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVMALUOpModByZeroALUOpX(t *testing.T) {
|
|
||||||
vm, done, err := testVM(t, []bpf.Instruction{
|
|
||||||
// Load byte 0 into X
|
|
||||||
bpf.LoadAbsolute{
|
|
||||||
Off: 8,
|
|
||||||
Size: 1,
|
|
||||||
},
|
|
||||||
bpf.TAX{},
|
|
||||||
// Load byte 1 into A
|
|
||||||
bpf.LoadAbsolute{
|
|
||||||
Off: 9,
|
|
||||||
Size: 1,
|
|
||||||
},
|
|
||||||
// Attempt to perform 1%0
|
|
||||||
bpf.ALUOpX{
|
|
||||||
Op: bpf.ALUOpMod,
|
|
||||||
},
|
|
||||||
// Return 4 bytes if program does not terminate
|
|
||||||
bpf.LoadConstant{
|
|
||||||
Val: 12,
|
|
||||||
},
|
|
||||||
bpf.RetA{},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to load BPF program: %v", err)
|
|
||||||
}
|
|
||||||
defer done()
|
|
||||||
|
|
||||||
out, err := vm.Run([]byte{
|
|
||||||
0xff, 0xff, 0xff, 0xff,
|
|
||||||
0xff, 0xff, 0xff, 0xff,
|
|
||||||
0, 1, 3, 4,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error while running program: %v", err)
|
|
||||||
}
|
|
||||||
if want, got := 0, out; want != got {
|
|
||||||
t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
|
|
||||||
want, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVMALUOpXor(t *testing.T) {
|
|
||||||
vm, done, err := testVM(t, []bpf.Instruction{
|
|
||||||
bpf.LoadAbsolute{
|
|
||||||
Off: 8,
|
|
||||||
Size: 1,
|
|
||||||
},
|
|
||||||
bpf.ALUOpConstant{
|
|
||||||
Op: bpf.ALUOpXor,
|
|
||||||
Val: 0x0a,
|
|
||||||
},
|
|
||||||
bpf.JumpIf{
|
|
||||||
Cond: bpf.JumpEqual,
|
|
||||||
Val: 0x01,
|
|
||||||
SkipTrue: 1,
|
|
||||||
},
|
|
||||||
bpf.RetConstant{
|
|
||||||
Val: 0,
|
|
||||||
},
|
|
||||||
bpf.RetConstant{
|
|
||||||
Val: 9,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to load BPF program: %v", err)
|
|
||||||
}
|
|
||||||
defer done()
|
|
||||||
|
|
||||||
out, err := vm.Run([]byte{
|
|
||||||
0xff, 0xff, 0xff, 0xff,
|
|
||||||
0xff, 0xff, 0xff, 0xff,
|
|
||||||
0x0b, 0x00, 0x00, 0x00,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error while running program: %v", err)
|
|
||||||
}
|
|
||||||
if want, got := 1, out; want != got {
|
|
||||||
t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
|
|
||||||
want, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVMALUOpUnknown(t *testing.T) {
|
|
||||||
vm, done, err := testVM(t, []bpf.Instruction{
|
|
||||||
bpf.LoadAbsolute{
|
|
||||||
Off: 8,
|
|
||||||
Size: 1,
|
|
||||||
},
|
|
||||||
bpf.ALUOpConstant{
|
|
||||||
Op: bpf.ALUOpAdd,
|
|
||||||
Val: 1,
|
|
||||||
},
|
|
||||||
// Verify that an unknown operation is a no-op
|
|
||||||
bpf.ALUOpConstant{
|
|
||||||
Op: 100,
|
|
||||||
},
|
|
||||||
bpf.JumpIf{
|
|
||||||
Cond: bpf.JumpEqual,
|
|
||||||
Val: 0x02,
|
|
||||||
SkipTrue: 1,
|
|
||||||
},
|
|
||||||
bpf.RetConstant{
|
|
||||||
Val: 0,
|
|
||||||
},
|
|
||||||
bpf.RetConstant{
|
|
||||||
Val: 9,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to load BPF program: %v", err)
|
|
||||||
}
|
|
||||||
defer done()
|
|
||||||
|
|
||||||
out, err := vm.Run([]byte{
|
|
||||||
0xff, 0xff, 0xff, 0xff,
|
|
||||||
0xff, 0xff, 0xff, 0xff,
|
|
||||||
1,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error while running program: %v", err)
|
|
||||||
}
|
|
||||||
if want, got := 1, out; want != got {
|
|
||||||
t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
|
|
||||||
want, got)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,192 +0,0 @@
|
|||||||
// Copyright 2016 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package bpf_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"runtime"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"golang.org/x/net/bpf"
|
|
||||||
"golang.org/x/net/ipv4"
|
|
||||||
)
|
|
||||||
|
|
||||||
// A virtualMachine is a BPF virtual machine which can process an
|
|
||||||
// input packet against a BPF program and render a verdict.
|
|
||||||
type virtualMachine interface {
|
|
||||||
Run(in []byte) (int, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// canUseOSVM indicates if the OS BPF VM is available on this platform.
|
|
||||||
func canUseOSVM() bool {
|
|
||||||
// OS BPF VM can only be used on platforms where x/net/ipv4 supports
|
|
||||||
// attaching a BPF program to a socket.
|
|
||||||
switch runtime.GOOS {
|
|
||||||
case "linux":
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// All BPF tests against both the Go VM and OS VM are assumed to
|
|
||||||
// be used with a UDP socket. As a result, the entire contents
|
|
||||||
// of a UDP datagram is sent through the BPF program, but only
|
|
||||||
// the body after the UDP header will ever be returned in output.
|
|
||||||
|
|
||||||
// testVM sets up a Go BPF VM, and if available, a native OS BPF VM
|
|
||||||
// for integration testing.
|
|
||||||
func testVM(t *testing.T, filter []bpf.Instruction) (virtualMachine, func(), error) {
|
|
||||||
goVM, err := bpf.NewVM(filter)
|
|
||||||
if err != nil {
|
|
||||||
// Some tests expect an error, so this error must be returned
|
|
||||||
// instead of fatally exiting the test
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
mvm := &multiVirtualMachine{
|
|
||||||
goVM: goVM,
|
|
||||||
|
|
||||||
t: t,
|
|
||||||
}
|
|
||||||
|
|
||||||
// If available, add the OS VM for tests which verify that both the Go
|
|
||||||
// VM and OS VM have exactly the same output for the same input program
|
|
||||||
// and packet.
|
|
||||||
done := func() {}
|
|
||||||
if canUseOSVM() {
|
|
||||||
osVM, osVMDone := testOSVM(t, filter)
|
|
||||||
done = func() { osVMDone() }
|
|
||||||
mvm.osVM = osVM
|
|
||||||
}
|
|
||||||
|
|
||||||
return mvm, done, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// udpHeaderLen is the length of a UDP header.
|
|
||||||
const udpHeaderLen = 8
|
|
||||||
|
|
||||||
// A multiVirtualMachine is a virtualMachine which can call out to both the Go VM
|
|
||||||
// and the native OS VM, if the OS VM is available.
|
|
||||||
type multiVirtualMachine struct {
|
|
||||||
goVM virtualMachine
|
|
||||||
osVM virtualMachine
|
|
||||||
|
|
||||||
t *testing.T
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mvm *multiVirtualMachine) Run(in []byte) (int, error) {
|
|
||||||
if len(in) < udpHeaderLen {
|
|
||||||
mvm.t.Fatalf("input must be at least length of UDP header (%d), got: %d",
|
|
||||||
udpHeaderLen, len(in))
|
|
||||||
}
|
|
||||||
|
|
||||||
// All tests have a UDP header as part of input, because the OS VM
|
|
||||||
// packets always will. For the Go VM, this output is trimmed before
|
|
||||||
// being sent back to tests.
|
|
||||||
goOut, goErr := mvm.goVM.Run(in)
|
|
||||||
if goOut >= udpHeaderLen {
|
|
||||||
goOut -= udpHeaderLen
|
|
||||||
}
|
|
||||||
|
|
||||||
// If Go output is larger than the size of the packet, packet filtering
|
|
||||||
// interop tests must trim the output bytes to the length of the packet.
|
|
||||||
// The BPF VM should not do this on its own, as other uses of it do
|
|
||||||
// not trim the output byte count.
|
|
||||||
trim := len(in) - udpHeaderLen
|
|
||||||
if goOut > trim {
|
|
||||||
goOut = trim
|
|
||||||
}
|
|
||||||
|
|
||||||
// When the OS VM is not available, process using the Go VM alone
|
|
||||||
if mvm.osVM == nil {
|
|
||||||
return goOut, goErr
|
|
||||||
}
|
|
||||||
|
|
||||||
// The OS VM will apply its own UDP header, so remove the pseudo header
|
|
||||||
// that the Go VM needs.
|
|
||||||
osOut, err := mvm.osVM.Run(in[udpHeaderLen:])
|
|
||||||
if err != nil {
|
|
||||||
mvm.t.Fatalf("error while running OS VM: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify both VMs return same number of bytes
|
|
||||||
var mismatch bool
|
|
||||||
if goOut != osOut {
|
|
||||||
mismatch = true
|
|
||||||
mvm.t.Logf("output byte count does not match:\n- go: %v\n- os: %v", goOut, osOut)
|
|
||||||
}
|
|
||||||
|
|
||||||
if mismatch {
|
|
||||||
mvm.t.Fatal("Go BPF and OS BPF packet outputs do not match")
|
|
||||||
}
|
|
||||||
|
|
||||||
return goOut, goErr
|
|
||||||
}
|
|
||||||
|
|
||||||
// An osVirtualMachine is a virtualMachine which uses the OS's BPF VM for
|
|
||||||
// processing BPF programs.
|
|
||||||
type osVirtualMachine struct {
|
|
||||||
l net.PacketConn
|
|
||||||
s net.Conn
|
|
||||||
}
|
|
||||||
|
|
||||||
// testOSVM creates a virtualMachine which uses the OS's BPF VM by injecting
|
|
||||||
// packets into a UDP listener with a BPF program attached to it.
|
|
||||||
func testOSVM(t *testing.T, filter []bpf.Instruction) (virtualMachine, func()) {
|
|
||||||
l, err := net.ListenPacket("udp4", "127.0.0.1:0")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to open OS VM UDP listener: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
prog, err := bpf.Assemble(filter)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to compile BPF program: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
p := ipv4.NewPacketConn(l)
|
|
||||||
if err = p.SetBPF(prog); err != nil {
|
|
||||||
t.Fatalf("failed to attach BPF program to listener: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
s, err := net.Dial("udp4", l.LocalAddr().String())
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to dial connection to listener: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
done := func() {
|
|
||||||
_ = s.Close()
|
|
||||||
_ = l.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
return &osVirtualMachine{
|
|
||||||
l: l,
|
|
||||||
s: s,
|
|
||||||
}, done
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run sends the input bytes into the OS's BPF VM and returns its verdict.
|
|
||||||
func (vm *osVirtualMachine) Run(in []byte) (int, error) {
|
|
||||||
go func() {
|
|
||||||
_, _ = vm.s.Write(in)
|
|
||||||
}()
|
|
||||||
|
|
||||||
vm.l.SetDeadline(time.Now().Add(50 * time.Millisecond))
|
|
||||||
|
|
||||||
var b [512]byte
|
|
||||||
n, _, err := vm.l.ReadFrom(b[:])
|
|
||||||
if err != nil {
|
|
||||||
// A timeout indicates that BPF filtered out the packet, and thus,
|
|
||||||
// no input should be returned.
|
|
||||||
if nerr, ok := err.(net.Error); ok && nerr.Timeout() {
|
|
||||||
return n, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return n, nil
|
|
||||||
}
|
|
@ -1,49 +0,0 @@
|
|||||||
// Copyright 2016 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package bpf_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"golang.org/x/net/bpf"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestVMLoadExtensionNotImplemented(t *testing.T) {
|
|
||||||
_, _, err := testVM(t, []bpf.Instruction{
|
|
||||||
bpf.LoadExtension{
|
|
||||||
Num: 100,
|
|
||||||
},
|
|
||||||
bpf.RetA{},
|
|
||||||
})
|
|
||||||
if errStr(err) != "extension 100 not implemented" {
|
|
||||||
t.Fatalf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVMLoadExtensionExtLen(t *testing.T) {
|
|
||||||
vm, done, err := testVM(t, []bpf.Instruction{
|
|
||||||
bpf.LoadExtension{
|
|
||||||
Num: bpf.ExtLen,
|
|
||||||
},
|
|
||||||
bpf.RetA{},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to load BPF program: %v", err)
|
|
||||||
}
|
|
||||||
defer done()
|
|
||||||
|
|
||||||
out, err := vm.Run([]byte{
|
|
||||||
0xff, 0xff, 0xff, 0xff,
|
|
||||||
0xff, 0xff, 0xff, 0xff,
|
|
||||||
0, 1, 2, 3,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error while running program: %v", err)
|
|
||||||
}
|
|
||||||
if want, got := 4, out; want != got {
|
|
||||||
t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
|
|
||||||
want, got)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,174 +0,0 @@
|
|||||||
// Copyright 2016 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package bpf
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
func aluOpConstant(ins ALUOpConstant, regA uint32) uint32 {
|
|
||||||
return aluOpCommon(ins.Op, regA, ins.Val)
|
|
||||||
}
|
|
||||||
|
|
||||||
func aluOpX(ins ALUOpX, regA uint32, regX uint32) (uint32, bool) {
|
|
||||||
// Guard against division or modulus by zero by terminating
|
|
||||||
// the program, as the OS BPF VM does
|
|
||||||
if regX == 0 {
|
|
||||||
switch ins.Op {
|
|
||||||
case ALUOpDiv, ALUOpMod:
|
|
||||||
return 0, false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return aluOpCommon(ins.Op, regA, regX), true
|
|
||||||
}
|
|
||||||
|
|
||||||
func aluOpCommon(op ALUOp, regA uint32, value uint32) uint32 {
|
|
||||||
switch op {
|
|
||||||
case ALUOpAdd:
|
|
||||||
return regA + value
|
|
||||||
case ALUOpSub:
|
|
||||||
return regA - value
|
|
||||||
case ALUOpMul:
|
|
||||||
return regA * value
|
|
||||||
case ALUOpDiv:
|
|
||||||
// Division by zero not permitted by NewVM and aluOpX checks
|
|
||||||
return regA / value
|
|
||||||
case ALUOpOr:
|
|
||||||
return regA | value
|
|
||||||
case ALUOpAnd:
|
|
||||||
return regA & value
|
|
||||||
case ALUOpShiftLeft:
|
|
||||||
return regA << value
|
|
||||||
case ALUOpShiftRight:
|
|
||||||
return regA >> value
|
|
||||||
case ALUOpMod:
|
|
||||||
// Modulus by zero not permitted by NewVM and aluOpX checks
|
|
||||||
return regA % value
|
|
||||||
case ALUOpXor:
|
|
||||||
return regA ^ value
|
|
||||||
default:
|
|
||||||
return regA
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func jumpIf(ins JumpIf, value uint32) int {
|
|
||||||
var ok bool
|
|
||||||
inV := uint32(ins.Val)
|
|
||||||
|
|
||||||
switch ins.Cond {
|
|
||||||
case JumpEqual:
|
|
||||||
ok = value == inV
|
|
||||||
case JumpNotEqual:
|
|
||||||
ok = value != inV
|
|
||||||
case JumpGreaterThan:
|
|
||||||
ok = value > inV
|
|
||||||
case JumpLessThan:
|
|
||||||
ok = value < inV
|
|
||||||
case JumpGreaterOrEqual:
|
|
||||||
ok = value >= inV
|
|
||||||
case JumpLessOrEqual:
|
|
||||||
ok = value <= inV
|
|
||||||
case JumpBitsSet:
|
|
||||||
ok = (value & inV) != 0
|
|
||||||
case JumpBitsNotSet:
|
|
||||||
ok = (value & inV) == 0
|
|
||||||
}
|
|
||||||
|
|
||||||
if ok {
|
|
||||||
return int(ins.SkipTrue)
|
|
||||||
}
|
|
||||||
|
|
||||||
return int(ins.SkipFalse)
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadAbsolute(ins LoadAbsolute, in []byte) (uint32, bool) {
|
|
||||||
offset := int(ins.Off)
|
|
||||||
size := int(ins.Size)
|
|
||||||
|
|
||||||
return loadCommon(in, offset, size)
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadConstant(ins LoadConstant, regA uint32, regX uint32) (uint32, uint32) {
|
|
||||||
switch ins.Dst {
|
|
||||||
case RegA:
|
|
||||||
regA = ins.Val
|
|
||||||
case RegX:
|
|
||||||
regX = ins.Val
|
|
||||||
}
|
|
||||||
|
|
||||||
return regA, regX
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadExtension(ins LoadExtension, in []byte) uint32 {
|
|
||||||
switch ins.Num {
|
|
||||||
case ExtLen:
|
|
||||||
return uint32(len(in))
|
|
||||||
default:
|
|
||||||
panic(fmt.Sprintf("unimplemented extension: %d", ins.Num))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadIndirect(ins LoadIndirect, in []byte, regX uint32) (uint32, bool) {
|
|
||||||
offset := int(ins.Off) + int(regX)
|
|
||||||
size := int(ins.Size)
|
|
||||||
|
|
||||||
return loadCommon(in, offset, size)
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadMemShift(ins LoadMemShift, in []byte) (uint32, bool) {
|
|
||||||
offset := int(ins.Off)
|
|
||||||
|
|
||||||
if !inBounds(len(in), offset, 0) {
|
|
||||||
return 0, false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mask off high 4 bits and multiply low 4 bits by 4
|
|
||||||
return uint32(in[offset]&0x0f) * 4, true
|
|
||||||
}
|
|
||||||
|
|
||||||
func inBounds(inLen int, offset int, size int) bool {
|
|
||||||
return offset+size <= inLen
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadCommon(in []byte, offset int, size int) (uint32, bool) {
|
|
||||||
if !inBounds(len(in), offset, size) {
|
|
||||||
return 0, false
|
|
||||||
}
|
|
||||||
|
|
||||||
switch size {
|
|
||||||
case 1:
|
|
||||||
return uint32(in[offset]), true
|
|
||||||
case 2:
|
|
||||||
return uint32(binary.BigEndian.Uint16(in[offset : offset+size])), true
|
|
||||||
case 4:
|
|
||||||
return uint32(binary.BigEndian.Uint32(in[offset : offset+size])), true
|
|
||||||
default:
|
|
||||||
panic(fmt.Sprintf("invalid load size: %d", size))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadScratch(ins LoadScratch, regScratch [16]uint32, regA uint32, regX uint32) (uint32, uint32) {
|
|
||||||
switch ins.Dst {
|
|
||||||
case RegA:
|
|
||||||
regA = regScratch[ins.N]
|
|
||||||
case RegX:
|
|
||||||
regX = regScratch[ins.N]
|
|
||||||
}
|
|
||||||
|
|
||||||
return regA, regX
|
|
||||||
}
|
|
||||||
|
|
||||||
func storeScratch(ins StoreScratch, regScratch [16]uint32, regA uint32, regX uint32) [16]uint32 {
|
|
||||||
switch ins.Src {
|
|
||||||
case RegA:
|
|
||||||
regScratch[ins.N] = regA
|
|
||||||
case RegX:
|
|
||||||
regScratch[ins.N] = regX
|
|
||||||
}
|
|
||||||
|
|
||||||
return regScratch
|
|
||||||
}
|
|
@ -1,380 +0,0 @@
|
|||||||
// Copyright 2016 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package bpf_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"golang.org/x/net/bpf"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestVMJumpOne(t *testing.T) {
|
|
||||||
vm, done, err := testVM(t, []bpf.Instruction{
|
|
||||||
bpf.LoadAbsolute{
|
|
||||||
Off: 8,
|
|
||||||
Size: 1,
|
|
||||||
},
|
|
||||||
bpf.Jump{
|
|
||||||
Skip: 1,
|
|
||||||
},
|
|
||||||
bpf.RetConstant{
|
|
||||||
Val: 0,
|
|
||||||
},
|
|
||||||
bpf.RetConstant{
|
|
||||||
Val: 9,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to load BPF program: %v", err)
|
|
||||||
}
|
|
||||||
defer done()
|
|
||||||
|
|
||||||
out, err := vm.Run([]byte{
|
|
||||||
0xff, 0xff, 0xff, 0xff,
|
|
||||||
0xff, 0xff, 0xff, 0xff,
|
|
||||||
1,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error while running program: %v", err)
|
|
||||||
}
|
|
||||||
if want, got := 1, out; want != got {
|
|
||||||
t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
|
|
||||||
want, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVMJumpOutOfProgram(t *testing.T) {
|
|
||||||
_, _, err := testVM(t, []bpf.Instruction{
|
|
||||||
bpf.Jump{
|
|
||||||
Skip: 1,
|
|
||||||
},
|
|
||||||
bpf.RetA{},
|
|
||||||
})
|
|
||||||
if errStr(err) != "cannot jump 1 instructions; jumping past program bounds" {
|
|
||||||
t.Fatalf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVMJumpIfTrueOutOfProgram(t *testing.T) {
|
|
||||||
_, _, err := testVM(t, []bpf.Instruction{
|
|
||||||
bpf.JumpIf{
|
|
||||||
Cond: bpf.JumpEqual,
|
|
||||||
SkipTrue: 2,
|
|
||||||
},
|
|
||||||
bpf.RetA{},
|
|
||||||
})
|
|
||||||
if errStr(err) != "cannot jump 2 instructions in true case; jumping past program bounds" {
|
|
||||||
t.Fatalf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVMJumpIfFalseOutOfProgram(t *testing.T) {
|
|
||||||
_, _, err := testVM(t, []bpf.Instruction{
|
|
||||||
bpf.JumpIf{
|
|
||||||
Cond: bpf.JumpEqual,
|
|
||||||
SkipFalse: 3,
|
|
||||||
},
|
|
||||||
bpf.RetA{},
|
|
||||||
})
|
|
||||||
if errStr(err) != "cannot jump 3 instructions in false case; jumping past program bounds" {
|
|
||||||
t.Fatalf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVMJumpIfEqual(t *testing.T) {
|
|
||||||
vm, done, err := testVM(t, []bpf.Instruction{
|
|
||||||
bpf.LoadAbsolute{
|
|
||||||
Off: 8,
|
|
||||||
Size: 1,
|
|
||||||
},
|
|
||||||
bpf.JumpIf{
|
|
||||||
Cond: bpf.JumpEqual,
|
|
||||||
Val: 1,
|
|
||||||
SkipTrue: 1,
|
|
||||||
},
|
|
||||||
bpf.RetConstant{
|
|
||||||
Val: 0,
|
|
||||||
},
|
|
||||||
bpf.RetConstant{
|
|
||||||
Val: 9,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to load BPF program: %v", err)
|
|
||||||
}
|
|
||||||
defer done()
|
|
||||||
|
|
||||||
out, err := vm.Run([]byte{
|
|
||||||
0xff, 0xff, 0xff, 0xff,
|
|
||||||
0xff, 0xff, 0xff, 0xff,
|
|
||||||
1,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error while running program: %v", err)
|
|
||||||
}
|
|
||||||
if want, got := 1, out; want != got {
|
|
||||||
t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
|
|
||||||
want, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVMJumpIfNotEqual(t *testing.T) {
|
|
||||||
vm, done, err := testVM(t, []bpf.Instruction{
|
|
||||||
bpf.LoadAbsolute{
|
|
||||||
Off: 8,
|
|
||||||
Size: 1,
|
|
||||||
},
|
|
||||||
bpf.JumpIf{
|
|
||||||
Cond: bpf.JumpNotEqual,
|
|
||||||
Val: 1,
|
|
||||||
SkipFalse: 1,
|
|
||||||
},
|
|
||||||
bpf.RetConstant{
|
|
||||||
Val: 0,
|
|
||||||
},
|
|
||||||
bpf.RetConstant{
|
|
||||||
Val: 9,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to load BPF program: %v", err)
|
|
||||||
}
|
|
||||||
defer done()
|
|
||||||
|
|
||||||
out, err := vm.Run([]byte{
|
|
||||||
0xff, 0xff, 0xff, 0xff,
|
|
||||||
0xff, 0xff, 0xff, 0xff,
|
|
||||||
1,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error while running program: %v", err)
|
|
||||||
}
|
|
||||||
if want, got := 1, out; want != got {
|
|
||||||
t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
|
|
||||||
want, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVMJumpIfGreaterThan(t *testing.T) {
|
|
||||||
vm, done, err := testVM(t, []bpf.Instruction{
|
|
||||||
bpf.LoadAbsolute{
|
|
||||||
Off: 8,
|
|
||||||
Size: 4,
|
|
||||||
},
|
|
||||||
bpf.JumpIf{
|
|
||||||
Cond: bpf.JumpGreaterThan,
|
|
||||||
Val: 0x00010202,
|
|
||||||
SkipTrue: 1,
|
|
||||||
},
|
|
||||||
bpf.RetConstant{
|
|
||||||
Val: 0,
|
|
||||||
},
|
|
||||||
bpf.RetConstant{
|
|
||||||
Val: 12,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to load BPF program: %v", err)
|
|
||||||
}
|
|
||||||
defer done()
|
|
||||||
|
|
||||||
out, err := vm.Run([]byte{
|
|
||||||
0xff, 0xff, 0xff, 0xff,
|
|
||||||
0xff, 0xff, 0xff, 0xff,
|
|
||||||
0, 1, 2, 3,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error while running program: %v", err)
|
|
||||||
}
|
|
||||||
if want, got := 4, out; want != got {
|
|
||||||
t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
|
|
||||||
want, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVMJumpIfLessThan(t *testing.T) {
|
|
||||||
vm, done, err := testVM(t, []bpf.Instruction{
|
|
||||||
bpf.LoadAbsolute{
|
|
||||||
Off: 8,
|
|
||||||
Size: 4,
|
|
||||||
},
|
|
||||||
bpf.JumpIf{
|
|
||||||
Cond: bpf.JumpLessThan,
|
|
||||||
Val: 0xff010203,
|
|
||||||
SkipTrue: 1,
|
|
||||||
},
|
|
||||||
bpf.RetConstant{
|
|
||||||
Val: 0,
|
|
||||||
},
|
|
||||||
bpf.RetConstant{
|
|
||||||
Val: 12,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to load BPF program: %v", err)
|
|
||||||
}
|
|
||||||
defer done()
|
|
||||||
|
|
||||||
out, err := vm.Run([]byte{
|
|
||||||
0xff, 0xff, 0xff, 0xff,
|
|
||||||
0xff, 0xff, 0xff, 0xff,
|
|
||||||
0, 1, 2, 3,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error while running program: %v", err)
|
|
||||||
}
|
|
||||||
if want, got := 4, out; want != got {
|
|
||||||
t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
|
|
||||||
want, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVMJumpIfGreaterOrEqual(t *testing.T) {
|
|
||||||
vm, done, err := testVM(t, []bpf.Instruction{
|
|
||||||
bpf.LoadAbsolute{
|
|
||||||
Off: 8,
|
|
||||||
Size: 4,
|
|
||||||
},
|
|
||||||
bpf.JumpIf{
|
|
||||||
Cond: bpf.JumpGreaterOrEqual,
|
|
||||||
Val: 0x00010203,
|
|
||||||
SkipTrue: 1,
|
|
||||||
},
|
|
||||||
bpf.RetConstant{
|
|
||||||
Val: 0,
|
|
||||||
},
|
|
||||||
bpf.RetConstant{
|
|
||||||
Val: 12,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to load BPF program: %v", err)
|
|
||||||
}
|
|
||||||
defer done()
|
|
||||||
|
|
||||||
out, err := vm.Run([]byte{
|
|
||||||
0xff, 0xff, 0xff, 0xff,
|
|
||||||
0xff, 0xff, 0xff, 0xff,
|
|
||||||
0, 1, 2, 3,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error while running program: %v", err)
|
|
||||||
}
|
|
||||||
if want, got := 4, out; want != got {
|
|
||||||
t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
|
|
||||||
want, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVMJumpIfLessOrEqual(t *testing.T) {
|
|
||||||
vm, done, err := testVM(t, []bpf.Instruction{
|
|
||||||
bpf.LoadAbsolute{
|
|
||||||
Off: 8,
|
|
||||||
Size: 4,
|
|
||||||
},
|
|
||||||
bpf.JumpIf{
|
|
||||||
Cond: bpf.JumpLessOrEqual,
|
|
||||||
Val: 0xff010203,
|
|
||||||
SkipTrue: 1,
|
|
||||||
},
|
|
||||||
bpf.RetConstant{
|
|
||||||
Val: 0,
|
|
||||||
},
|
|
||||||
bpf.RetConstant{
|
|
||||||
Val: 12,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to load BPF program: %v", err)
|
|
||||||
}
|
|
||||||
defer done()
|
|
||||||
|
|
||||||
out, err := vm.Run([]byte{
|
|
||||||
0xff, 0xff, 0xff, 0xff,
|
|
||||||
0xff, 0xff, 0xff, 0xff,
|
|
||||||
0, 1, 2, 3,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error while running program: %v", err)
|
|
||||||
}
|
|
||||||
if want, got := 4, out; want != got {
|
|
||||||
t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
|
|
||||||
want, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVMJumpIfBitsSet(t *testing.T) {
|
|
||||||
vm, done, err := testVM(t, []bpf.Instruction{
|
|
||||||
bpf.LoadAbsolute{
|
|
||||||
Off: 8,
|
|
||||||
Size: 2,
|
|
||||||
},
|
|
||||||
bpf.JumpIf{
|
|
||||||
Cond: bpf.JumpBitsSet,
|
|
||||||
Val: 0x1122,
|
|
||||||
SkipTrue: 1,
|
|
||||||
},
|
|
||||||
bpf.RetConstant{
|
|
||||||
Val: 0,
|
|
||||||
},
|
|
||||||
bpf.RetConstant{
|
|
||||||
Val: 10,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to load BPF program: %v", err)
|
|
||||||
}
|
|
||||||
defer done()
|
|
||||||
|
|
||||||
out, err := vm.Run([]byte{
|
|
||||||
0xff, 0xff, 0xff, 0xff,
|
|
||||||
0xff, 0xff, 0xff, 0xff,
|
|
||||||
0x01, 0x02,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error while running program: %v", err)
|
|
||||||
}
|
|
||||||
if want, got := 2, out; want != got {
|
|
||||||
t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
|
|
||||||
want, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVMJumpIfBitsNotSet(t *testing.T) {
|
|
||||||
vm, done, err := testVM(t, []bpf.Instruction{
|
|
||||||
bpf.LoadAbsolute{
|
|
||||||
Off: 8,
|
|
||||||
Size: 2,
|
|
||||||
},
|
|
||||||
bpf.JumpIf{
|
|
||||||
Cond: bpf.JumpBitsNotSet,
|
|
||||||
Val: 0x1221,
|
|
||||||
SkipTrue: 1,
|
|
||||||
},
|
|
||||||
bpf.RetConstant{
|
|
||||||
Val: 0,
|
|
||||||
},
|
|
||||||
bpf.RetConstant{
|
|
||||||
Val: 10,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to load BPF program: %v", err)
|
|
||||||
}
|
|
||||||
defer done()
|
|
||||||
|
|
||||||
out, err := vm.Run([]byte{
|
|
||||||
0xff, 0xff, 0xff, 0xff,
|
|
||||||
0xff, 0xff, 0xff, 0xff,
|
|
||||||
0x01, 0x02,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error while running program: %v", err)
|
|
||||||
}
|
|
||||||
if want, got := 2, out; want != got {
|
|
||||||
t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
|
|
||||||
want, got)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,246 +0,0 @@
|
|||||||
// Copyright 2016 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package bpf_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"golang.org/x/net/bpf"
|
|
||||||
"golang.org/x/net/ipv4"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestVMLoadAbsoluteOffsetOutOfBounds(t *testing.T) {
|
|
||||||
vm, done, err := testVM(t, []bpf.Instruction{
|
|
||||||
bpf.LoadAbsolute{
|
|
||||||
Off: 100,
|
|
||||||
Size: 2,
|
|
||||||
},
|
|
||||||
bpf.RetA{},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to load BPF program: %v", err)
|
|
||||||
}
|
|
||||||
defer done()
|
|
||||||
|
|
||||||
out, err := vm.Run([]byte{
|
|
||||||
0xff, 0xff, 0xff, 0xff,
|
|
||||||
0xff, 0xff, 0xff, 0xff,
|
|
||||||
0, 1, 2, 3,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error while running program: %v", err)
|
|
||||||
}
|
|
||||||
if want, got := 0, out; want != got {
|
|
||||||
t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
|
|
||||||
want, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVMLoadAbsoluteOffsetPlusSizeOutOfBounds(t *testing.T) {
|
|
||||||
vm, done, err := testVM(t, []bpf.Instruction{
|
|
||||||
bpf.LoadAbsolute{
|
|
||||||
Off: 8,
|
|
||||||
Size: 2,
|
|
||||||
},
|
|
||||||
bpf.RetA{},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to load BPF program: %v", err)
|
|
||||||
}
|
|
||||||
defer done()
|
|
||||||
|
|
||||||
out, err := vm.Run([]byte{
|
|
||||||
0xff, 0xff, 0xff, 0xff,
|
|
||||||
0xff, 0xff, 0xff, 0xff,
|
|
||||||
0,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error while running program: %v", err)
|
|
||||||
}
|
|
||||||
if want, got := 0, out; want != got {
|
|
||||||
t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
|
|
||||||
want, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVMLoadAbsoluteBadInstructionSize(t *testing.T) {
|
|
||||||
_, _, err := testVM(t, []bpf.Instruction{
|
|
||||||
bpf.LoadAbsolute{
|
|
||||||
Size: 5,
|
|
||||||
},
|
|
||||||
bpf.RetA{},
|
|
||||||
})
|
|
||||||
if errStr(err) != "assembling instruction 1: invalid load byte length 0" {
|
|
||||||
t.Fatalf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVMLoadConstantOK(t *testing.T) {
|
|
||||||
vm, done, err := testVM(t, []bpf.Instruction{
|
|
||||||
bpf.LoadConstant{
|
|
||||||
Dst: bpf.RegX,
|
|
||||||
Val: 9,
|
|
||||||
},
|
|
||||||
bpf.TXA{},
|
|
||||||
bpf.RetA{},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to load BPF program: %v", err)
|
|
||||||
}
|
|
||||||
defer done()
|
|
||||||
|
|
||||||
out, err := vm.Run([]byte{
|
|
||||||
0xff, 0xff, 0xff, 0xff,
|
|
||||||
0xff, 0xff, 0xff, 0xff,
|
|
||||||
0,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error while running program: %v", err)
|
|
||||||
}
|
|
||||||
if want, got := 1, out; want != got {
|
|
||||||
t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
|
|
||||||
want, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVMLoadIndirectOutOfBounds(t *testing.T) {
|
|
||||||
vm, done, err := testVM(t, []bpf.Instruction{
|
|
||||||
bpf.LoadIndirect{
|
|
||||||
Off: 100,
|
|
||||||
Size: 1,
|
|
||||||
},
|
|
||||||
bpf.RetA{},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to load BPF program: %v", err)
|
|
||||||
}
|
|
||||||
defer done()
|
|
||||||
|
|
||||||
out, err := vm.Run([]byte{
|
|
||||||
0xff, 0xff, 0xff, 0xff,
|
|
||||||
0xff, 0xff, 0xff, 0xff,
|
|
||||||
0,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error while running program: %v", err)
|
|
||||||
}
|
|
||||||
if want, got := 0, out; want != got {
|
|
||||||
t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
|
|
||||||
want, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVMLoadMemShiftOutOfBounds(t *testing.T) {
|
|
||||||
vm, done, err := testVM(t, []bpf.Instruction{
|
|
||||||
bpf.LoadMemShift{
|
|
||||||
Off: 100,
|
|
||||||
},
|
|
||||||
bpf.RetA{},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to load BPF program: %v", err)
|
|
||||||
}
|
|
||||||
defer done()
|
|
||||||
|
|
||||||
out, err := vm.Run([]byte{
|
|
||||||
0xff, 0xff, 0xff, 0xff,
|
|
||||||
0xff, 0xff, 0xff, 0xff,
|
|
||||||
0,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error while running program: %v", err)
|
|
||||||
}
|
|
||||||
if want, got := 0, out; want != got {
|
|
||||||
t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
|
|
||||||
want, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
dhcp4Port = 53
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestVMLoadMemShiftLoadIndirectNoResult(t *testing.T) {
|
|
||||||
vm, in, done := testDHCPv4(t)
|
|
||||||
defer done()
|
|
||||||
|
|
||||||
// Append mostly empty UDP header with incorrect DHCPv4 port
|
|
||||||
in = append(in, []byte{
|
|
||||||
0, 0,
|
|
||||||
0, dhcp4Port + 1,
|
|
||||||
0, 0,
|
|
||||||
0, 0,
|
|
||||||
}...)
|
|
||||||
|
|
||||||
out, err := vm.Run(in)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error while running program: %v", err)
|
|
||||||
}
|
|
||||||
if want, got := 0, out; want != got {
|
|
||||||
t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
|
|
||||||
want, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVMLoadMemShiftLoadIndirectOK(t *testing.T) {
|
|
||||||
vm, in, done := testDHCPv4(t)
|
|
||||||
defer done()
|
|
||||||
|
|
||||||
// Append mostly empty UDP header with correct DHCPv4 port
|
|
||||||
in = append(in, []byte{
|
|
||||||
0, 0,
|
|
||||||
0, dhcp4Port,
|
|
||||||
0, 0,
|
|
||||||
0, 0,
|
|
||||||
}...)
|
|
||||||
|
|
||||||
out, err := vm.Run(in)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error while running program: %v", err)
|
|
||||||
}
|
|
||||||
if want, got := len(in)-8, out; want != got {
|
|
||||||
t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
|
|
||||||
want, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func testDHCPv4(t *testing.T) (virtualMachine, []byte, func()) {
|
|
||||||
// DHCPv4 test data courtesy of David Anderson:
|
|
||||||
// https://github.com/google/netboot/blob/master/dhcp4/conn_linux.go#L59-L70
|
|
||||||
vm, done, err := testVM(t, []bpf.Instruction{
|
|
||||||
// Load IPv4 packet length
|
|
||||||
bpf.LoadMemShift{Off: 8},
|
|
||||||
// Get UDP dport
|
|
||||||
bpf.LoadIndirect{Off: 8 + 2, Size: 2},
|
|
||||||
// Correct dport?
|
|
||||||
bpf.JumpIf{Cond: bpf.JumpEqual, Val: dhcp4Port, SkipFalse: 1},
|
|
||||||
// Accept
|
|
||||||
bpf.RetConstant{Val: 1500},
|
|
||||||
// Ignore
|
|
||||||
bpf.RetConstant{Val: 0},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to load BPF program: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Minimal requirements to make a valid IPv4 header
|
|
||||||
h := &ipv4.Header{
|
|
||||||
Len: ipv4.HeaderLen,
|
|
||||||
Src: net.IPv4(192, 168, 1, 1),
|
|
||||||
Dst: net.IPv4(192, 168, 1, 2),
|
|
||||||
}
|
|
||||||
hb, err := h.Marshal()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to marshal IPv4 header: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
hb = append([]byte{
|
|
||||||
0xff, 0xff, 0xff, 0xff,
|
|
||||||
0xff, 0xff, 0xff, 0xff,
|
|
||||||
}, hb...)
|
|
||||||
|
|
||||||
return vm, hb, done
|
|
||||||
}
|
|
@ -1,115 +0,0 @@
|
|||||||
// Copyright 2016 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package bpf_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"golang.org/x/net/bpf"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestVMRetA(t *testing.T) {
|
|
||||||
vm, done, err := testVM(t, []bpf.Instruction{
|
|
||||||
bpf.LoadAbsolute{
|
|
||||||
Off: 8,
|
|
||||||
Size: 1,
|
|
||||||
},
|
|
||||||
bpf.RetA{},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to load BPF program: %v", err)
|
|
||||||
}
|
|
||||||
defer done()
|
|
||||||
|
|
||||||
out, err := vm.Run([]byte{
|
|
||||||
0xff, 0xff, 0xff, 0xff,
|
|
||||||
0xff, 0xff, 0xff, 0xff,
|
|
||||||
9,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error while running program: %v", err)
|
|
||||||
}
|
|
||||||
if want, got := 1, out; want != got {
|
|
||||||
t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
|
|
||||||
want, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVMRetALargerThanInput(t *testing.T) {
|
|
||||||
vm, done, err := testVM(t, []bpf.Instruction{
|
|
||||||
bpf.LoadAbsolute{
|
|
||||||
Off: 8,
|
|
||||||
Size: 2,
|
|
||||||
},
|
|
||||||
bpf.RetA{},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to load BPF program: %v", err)
|
|
||||||
}
|
|
||||||
defer done()
|
|
||||||
|
|
||||||
out, err := vm.Run([]byte{
|
|
||||||
0xff, 0xff, 0xff, 0xff,
|
|
||||||
0xff, 0xff, 0xff, 0xff,
|
|
||||||
0, 255,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error while running program: %v", err)
|
|
||||||
}
|
|
||||||
if want, got := 2, out; want != got {
|
|
||||||
t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
|
|
||||||
want, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVMRetConstant(t *testing.T) {
|
|
||||||
vm, done, err := testVM(t, []bpf.Instruction{
|
|
||||||
bpf.RetConstant{
|
|
||||||
Val: 9,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to load BPF program: %v", err)
|
|
||||||
}
|
|
||||||
defer done()
|
|
||||||
|
|
||||||
out, err := vm.Run([]byte{
|
|
||||||
0xff, 0xff, 0xff, 0xff,
|
|
||||||
0xff, 0xff, 0xff, 0xff,
|
|
||||||
0, 1,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error while running program: %v", err)
|
|
||||||
}
|
|
||||||
if want, got := 1, out; want != got {
|
|
||||||
t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
|
|
||||||
want, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVMRetConstantLargerThanInput(t *testing.T) {
|
|
||||||
vm, done, err := testVM(t, []bpf.Instruction{
|
|
||||||
bpf.RetConstant{
|
|
||||||
Val: 16,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to load BPF program: %v", err)
|
|
||||||
}
|
|
||||||
defer done()
|
|
||||||
|
|
||||||
out, err := vm.Run([]byte{
|
|
||||||
0xff, 0xff, 0xff, 0xff,
|
|
||||||
0xff, 0xff, 0xff, 0xff,
|
|
||||||
0, 1,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error while running program: %v", err)
|
|
||||||
}
|
|
||||||
if want, got := 2, out; want != got {
|
|
||||||
t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
|
|
||||||
want, got)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,247 +0,0 @@
|
|||||||
// Copyright 2016 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package bpf_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"golang.org/x/net/bpf"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestVMStoreScratchInvalidScratchRegisterTooSmall(t *testing.T) {
|
|
||||||
_, _, err := testVM(t, []bpf.Instruction{
|
|
||||||
bpf.StoreScratch{
|
|
||||||
Src: bpf.RegA,
|
|
||||||
N: -1,
|
|
||||||
},
|
|
||||||
bpf.RetA{},
|
|
||||||
})
|
|
||||||
if errStr(err) != "assembling instruction 1: invalid scratch slot -1" {
|
|
||||||
t.Fatalf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVMStoreScratchInvalidScratchRegisterTooLarge(t *testing.T) {
|
|
||||||
_, _, err := testVM(t, []bpf.Instruction{
|
|
||||||
bpf.StoreScratch{
|
|
||||||
Src: bpf.RegA,
|
|
||||||
N: 16,
|
|
||||||
},
|
|
||||||
bpf.RetA{},
|
|
||||||
})
|
|
||||||
if errStr(err) != "assembling instruction 1: invalid scratch slot 16" {
|
|
||||||
t.Fatalf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVMStoreScratchUnknownSourceRegister(t *testing.T) {
|
|
||||||
_, _, err := testVM(t, []bpf.Instruction{
|
|
||||||
bpf.StoreScratch{
|
|
||||||
Src: 100,
|
|
||||||
N: 0,
|
|
||||||
},
|
|
||||||
bpf.RetA{},
|
|
||||||
})
|
|
||||||
if errStr(err) != "assembling instruction 1: invalid source register 100" {
|
|
||||||
t.Fatalf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVMLoadScratchInvalidScratchRegisterTooSmall(t *testing.T) {
|
|
||||||
_, _, err := testVM(t, []bpf.Instruction{
|
|
||||||
bpf.LoadScratch{
|
|
||||||
Dst: bpf.RegX,
|
|
||||||
N: -1,
|
|
||||||
},
|
|
||||||
bpf.RetA{},
|
|
||||||
})
|
|
||||||
if errStr(err) != "assembling instruction 1: invalid scratch slot -1" {
|
|
||||||
t.Fatalf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVMLoadScratchInvalidScratchRegisterTooLarge(t *testing.T) {
|
|
||||||
_, _, err := testVM(t, []bpf.Instruction{
|
|
||||||
bpf.LoadScratch{
|
|
||||||
Dst: bpf.RegX,
|
|
||||||
N: 16,
|
|
||||||
},
|
|
||||||
bpf.RetA{},
|
|
||||||
})
|
|
||||||
if errStr(err) != "assembling instruction 1: invalid scratch slot 16" {
|
|
||||||
t.Fatalf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVMLoadScratchUnknownDestinationRegister(t *testing.T) {
|
|
||||||
_, _, err := testVM(t, []bpf.Instruction{
|
|
||||||
bpf.LoadScratch{
|
|
||||||
Dst: 100,
|
|
||||||
N: 0,
|
|
||||||
},
|
|
||||||
bpf.RetA{},
|
|
||||||
})
|
|
||||||
if errStr(err) != "assembling instruction 1: invalid target register 100" {
|
|
||||||
t.Fatalf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVMStoreScratchLoadScratchOneValue(t *testing.T) {
|
|
||||||
vm, done, err := testVM(t, []bpf.Instruction{
|
|
||||||
// Load byte 255
|
|
||||||
bpf.LoadAbsolute{
|
|
||||||
Off: 8,
|
|
||||||
Size: 1,
|
|
||||||
},
|
|
||||||
// Copy to X and store in scratch[0]
|
|
||||||
bpf.TAX{},
|
|
||||||
bpf.StoreScratch{
|
|
||||||
Src: bpf.RegX,
|
|
||||||
N: 0,
|
|
||||||
},
|
|
||||||
// Load byte 1
|
|
||||||
bpf.LoadAbsolute{
|
|
||||||
Off: 9,
|
|
||||||
Size: 1,
|
|
||||||
},
|
|
||||||
// Overwrite 1 with 255 from scratch[0]
|
|
||||||
bpf.LoadScratch{
|
|
||||||
Dst: bpf.RegA,
|
|
||||||
N: 0,
|
|
||||||
},
|
|
||||||
// Return 255
|
|
||||||
bpf.RetA{},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to load BPF program: %v", err)
|
|
||||||
}
|
|
||||||
defer done()
|
|
||||||
|
|
||||||
out, err := vm.Run([]byte{
|
|
||||||
0xff, 0xff, 0xff, 0xff,
|
|
||||||
0xff, 0xff, 0xff, 0xff,
|
|
||||||
255, 1, 2,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error while running program: %v", err)
|
|
||||||
}
|
|
||||||
if want, got := 3, out; want != got {
|
|
||||||
t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
|
|
||||||
want, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVMStoreScratchLoadScratchMultipleValues(t *testing.T) {
|
|
||||||
vm, done, err := testVM(t, []bpf.Instruction{
|
|
||||||
// Load byte 10
|
|
||||||
bpf.LoadAbsolute{
|
|
||||||
Off: 8,
|
|
||||||
Size: 1,
|
|
||||||
},
|
|
||||||
// Store in scratch[0]
|
|
||||||
bpf.StoreScratch{
|
|
||||||
Src: bpf.RegA,
|
|
||||||
N: 0,
|
|
||||||
},
|
|
||||||
// Load byte 20
|
|
||||||
bpf.LoadAbsolute{
|
|
||||||
Off: 9,
|
|
||||||
Size: 1,
|
|
||||||
},
|
|
||||||
// Store in scratch[1]
|
|
||||||
bpf.StoreScratch{
|
|
||||||
Src: bpf.RegA,
|
|
||||||
N: 1,
|
|
||||||
},
|
|
||||||
// Load byte 30
|
|
||||||
bpf.LoadAbsolute{
|
|
||||||
Off: 10,
|
|
||||||
Size: 1,
|
|
||||||
},
|
|
||||||
// Store in scratch[2]
|
|
||||||
bpf.StoreScratch{
|
|
||||||
Src: bpf.RegA,
|
|
||||||
N: 2,
|
|
||||||
},
|
|
||||||
// Load byte 1
|
|
||||||
bpf.LoadAbsolute{
|
|
||||||
Off: 11,
|
|
||||||
Size: 1,
|
|
||||||
},
|
|
||||||
// Store in scratch[3]
|
|
||||||
bpf.StoreScratch{
|
|
||||||
Src: bpf.RegA,
|
|
||||||
N: 3,
|
|
||||||
},
|
|
||||||
// Load in byte 10 to X
|
|
||||||
bpf.LoadScratch{
|
|
||||||
Dst: bpf.RegX,
|
|
||||||
N: 0,
|
|
||||||
},
|
|
||||||
// Copy X -> A
|
|
||||||
bpf.TXA{},
|
|
||||||
// Verify value is 10
|
|
||||||
bpf.JumpIf{
|
|
||||||
Cond: bpf.JumpEqual,
|
|
||||||
Val: 10,
|
|
||||||
SkipTrue: 1,
|
|
||||||
},
|
|
||||||
// Fail test if incorrect
|
|
||||||
bpf.RetConstant{
|
|
||||||
Val: 0,
|
|
||||||
},
|
|
||||||
// Load in byte 20 to A
|
|
||||||
bpf.LoadScratch{
|
|
||||||
Dst: bpf.RegA,
|
|
||||||
N: 1,
|
|
||||||
},
|
|
||||||
// Verify value is 20
|
|
||||||
bpf.JumpIf{
|
|
||||||
Cond: bpf.JumpEqual,
|
|
||||||
Val: 20,
|
|
||||||
SkipTrue: 1,
|
|
||||||
},
|
|
||||||
// Fail test if incorrect
|
|
||||||
bpf.RetConstant{
|
|
||||||
Val: 0,
|
|
||||||
},
|
|
||||||
// Load in byte 30 to A
|
|
||||||
bpf.LoadScratch{
|
|
||||||
Dst: bpf.RegA,
|
|
||||||
N: 2,
|
|
||||||
},
|
|
||||||
// Verify value is 30
|
|
||||||
bpf.JumpIf{
|
|
||||||
Cond: bpf.JumpEqual,
|
|
||||||
Val: 30,
|
|
||||||
SkipTrue: 1,
|
|
||||||
},
|
|
||||||
// Fail test if incorrect
|
|
||||||
bpf.RetConstant{
|
|
||||||
Val: 0,
|
|
||||||
},
|
|
||||||
// Return first two bytes on success
|
|
||||||
bpf.RetConstant{
|
|
||||||
Val: 10,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to load BPF program: %v", err)
|
|
||||||
}
|
|
||||||
defer done()
|
|
||||||
|
|
||||||
out, err := vm.Run([]byte{
|
|
||||||
0xff, 0xff, 0xff, 0xff,
|
|
||||||
0xff, 0xff, 0xff, 0xff,
|
|
||||||
10, 20, 30, 1,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error while running program: %v", err)
|
|
||||||
}
|
|
||||||
if want, got := 2, out; want != got {
|
|
||||||
t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
|
|
||||||
want, got)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,144 +0,0 @@
|
|||||||
// Copyright 2016 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package bpf_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"golang.org/x/net/bpf"
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ bpf.Instruction = unknown{}
|
|
||||||
|
|
||||||
type unknown struct{}
|
|
||||||
|
|
||||||
func (unknown) Assemble() (bpf.RawInstruction, error) {
|
|
||||||
return bpf.RawInstruction{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVMUnknownInstruction(t *testing.T) {
|
|
||||||
vm, done, err := testVM(t, []bpf.Instruction{
|
|
||||||
bpf.LoadConstant{
|
|
||||||
Dst: bpf.RegA,
|
|
||||||
Val: 100,
|
|
||||||
},
|
|
||||||
// Should terminate the program with an error immediately
|
|
||||||
unknown{},
|
|
||||||
bpf.RetA{},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
defer done()
|
|
||||||
|
|
||||||
_, err = vm.Run([]byte{
|
|
||||||
0xff, 0xff, 0xff, 0xff,
|
|
||||||
0xff, 0xff, 0xff, 0xff,
|
|
||||||
0x00, 0x00,
|
|
||||||
})
|
|
||||||
if errStr(err) != "unknown Instruction at index 1: bpf_test.unknown" {
|
|
||||||
t.Fatalf("unexpected error while running program: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVMNoReturnInstruction(t *testing.T) {
|
|
||||||
_, _, err := testVM(t, []bpf.Instruction{
|
|
||||||
bpf.LoadConstant{
|
|
||||||
Dst: bpf.RegA,
|
|
||||||
Val: 1,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if errStr(err) != "BPF program must end with RetA or RetConstant" {
|
|
||||||
t.Fatalf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVMNoInputInstructions(t *testing.T) {
|
|
||||||
_, _, err := testVM(t, []bpf.Instruction{})
|
|
||||||
if errStr(err) != "one or more Instructions must be specified" {
|
|
||||||
t.Fatalf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExampleNewVM demonstrates usage of a VM, using an Ethernet frame
|
|
||||||
// as input and checking its EtherType to determine if it should be accepted.
|
|
||||||
func ExampleNewVM() {
|
|
||||||
// Offset | Length | Comment
|
|
||||||
// -------------------------
|
|
||||||
// 00 | 06 | Ethernet destination MAC address
|
|
||||||
// 06 | 06 | Ethernet source MAC address
|
|
||||||
// 12 | 02 | Ethernet EtherType
|
|
||||||
const (
|
|
||||||
etOff = 12
|
|
||||||
etLen = 2
|
|
||||||
|
|
||||||
etARP = 0x0806
|
|
||||||
)
|
|
||||||
|
|
||||||
// Set up a VM to filter traffic based on if its EtherType
|
|
||||||
// matches the ARP EtherType.
|
|
||||||
vm, err := bpf.NewVM([]bpf.Instruction{
|
|
||||||
// Load EtherType value from Ethernet header
|
|
||||||
bpf.LoadAbsolute{
|
|
||||||
Off: etOff,
|
|
||||||
Size: etLen,
|
|
||||||
},
|
|
||||||
// If EtherType is equal to the ARP EtherType, jump to allow
|
|
||||||
// packet to be accepted
|
|
||||||
bpf.JumpIf{
|
|
||||||
Cond: bpf.JumpEqual,
|
|
||||||
Val: etARP,
|
|
||||||
SkipTrue: 1,
|
|
||||||
},
|
|
||||||
// EtherType does not match the ARP EtherType
|
|
||||||
bpf.RetConstant{
|
|
||||||
Val: 0,
|
|
||||||
},
|
|
||||||
// EtherType matches the ARP EtherType, accept up to 1500
|
|
||||||
// bytes of packet
|
|
||||||
bpf.RetConstant{
|
|
||||||
Val: 1500,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
panic(fmt.Sprintf("failed to load BPF program: %v", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create an Ethernet frame with the ARP EtherType for testing
|
|
||||||
frame := []byte{
|
|
||||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
||||||
0x00, 0x11, 0x22, 0x33, 0x44, 0x55,
|
|
||||||
0x08, 0x06,
|
|
||||||
// Payload omitted for brevity
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run our VM's BPF program using the Ethernet frame as input
|
|
||||||
out, err := vm.Run(frame)
|
|
||||||
if err != nil {
|
|
||||||
panic(fmt.Sprintf("failed to accept Ethernet frame: %v", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
// BPF VM can return a byte count greater than the number of input
|
|
||||||
// bytes, so trim the output to match the input byte length
|
|
||||||
if out > len(frame) {
|
|
||||||
out = len(frame)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("out: %d bytes", out)
|
|
||||||
|
|
||||||
// Output:
|
|
||||||
// out: 14 bytes
|
|
||||||
}
|
|
||||||
|
|
||||||
// errStr returns the string representation of an error, or
|
|
||||||
// "<nil>" if it is nil.
|
|
||||||
func errStr(err error) string {
|
|
||||||
if err == nil {
|
|
||||||
return "<nil>"
|
|
||||||
}
|
|
||||||
|
|
||||||
return err.Error()
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
issuerepo: golang/go
|
|
@ -1,56 +0,0 @@
|
|||||||
// Copyright 2014 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// Package context defines the Context type, which carries deadlines,
|
|
||||||
// cancelation signals, and other request-scoped values across API boundaries
|
|
||||||
// and between processes.
|
|
||||||
// As of Go 1.7 this package is available in the standard library under the
|
|
||||||
// name context. https://golang.org/pkg/context.
|
|
||||||
//
|
|
||||||
// Incoming requests to a server should create a Context, and outgoing calls to
|
|
||||||
// servers should accept a Context. The chain of function calls between must
|
|
||||||
// propagate the Context, optionally replacing it with a modified copy created
|
|
||||||
// using WithDeadline, WithTimeout, WithCancel, or WithValue.
|
|
||||||
//
|
|
||||||
// Programs that use Contexts should follow these rules to keep interfaces
|
|
||||||
// consistent across packages and enable static analysis tools to check context
|
|
||||||
// propagation:
|
|
||||||
//
|
|
||||||
// Do not store Contexts inside a struct type; instead, pass a Context
|
|
||||||
// explicitly to each function that needs it. The Context should be the first
|
|
||||||
// parameter, typically named ctx:
|
|
||||||
//
|
|
||||||
// func DoSomething(ctx context.Context, arg Arg) error {
|
|
||||||
// // ... use ctx ...
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// Do not pass a nil Context, even if a function permits it. Pass context.TODO
|
|
||||||
// if you are unsure about which Context to use.
|
|
||||||
//
|
|
||||||
// Use context Values only for request-scoped data that transits processes and
|
|
||||||
// APIs, not for passing optional parameters to functions.
|
|
||||||
//
|
|
||||||
// The same Context may be passed to functions running in different goroutines;
|
|
||||||
// Contexts are safe for simultaneous use by multiple goroutines.
|
|
||||||
//
|
|
||||||
// See http://blog.golang.org/context for example code for a server that uses
|
|
||||||
// Contexts.
|
|
||||||
package context // import "golang.org/x/net/context"
|
|
||||||
|
|
||||||
// Background returns a non-nil, empty Context. It is never canceled, has no
|
|
||||||
// values, and has no deadline. It is typically used by the main function,
|
|
||||||
// initialization, and tests, and as the top-level Context for incoming
|
|
||||||
// requests.
|
|
||||||
func Background() Context {
|
|
||||||
return background
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO returns a non-nil, empty Context. Code should use context.TODO when
|
|
||||||
// it's unclear which Context to use or it is not yet available (because the
|
|
||||||
// surrounding function has not yet been extended to accept a Context
|
|
||||||
// parameter). TODO is recognized by static analysis tools that determine
|
|
||||||
// whether Contexts are propagated correctly in a program.
|
|
||||||
func TODO() Context {
|
|
||||||
return todo
|
|
||||||
}
|
|
@ -1,583 +0,0 @@
|
|||||||
// Copyright 2014 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build !go1.7
|
|
||||||
|
|
||||||
package context
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"math/rand"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// otherContext is a Context that's not one of the types defined in context.go.
|
|
||||||
// This lets us test code paths that differ based on the underlying type of the
|
|
||||||
// Context.
|
|
||||||
type otherContext struct {
|
|
||||||
Context
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBackground(t *testing.T) {
|
|
||||||
c := Background()
|
|
||||||
if c == nil {
|
|
||||||
t.Fatalf("Background returned nil")
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case x := <-c.Done():
|
|
||||||
t.Errorf("<-c.Done() == %v want nothing (it should block)", x)
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
if got, want := fmt.Sprint(c), "context.Background"; got != want {
|
|
||||||
t.Errorf("Background().String() = %q want %q", got, want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTODO(t *testing.T) {
|
|
||||||
c := TODO()
|
|
||||||
if c == nil {
|
|
||||||
t.Fatalf("TODO returned nil")
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case x := <-c.Done():
|
|
||||||
t.Errorf("<-c.Done() == %v want nothing (it should block)", x)
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
if got, want := fmt.Sprint(c), "context.TODO"; got != want {
|
|
||||||
t.Errorf("TODO().String() = %q want %q", got, want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWithCancel(t *testing.T) {
|
|
||||||
c1, cancel := WithCancel(Background())
|
|
||||||
|
|
||||||
if got, want := fmt.Sprint(c1), "context.Background.WithCancel"; got != want {
|
|
||||||
t.Errorf("c1.String() = %q want %q", got, want)
|
|
||||||
}
|
|
||||||
|
|
||||||
o := otherContext{c1}
|
|
||||||
c2, _ := WithCancel(o)
|
|
||||||
contexts := []Context{c1, o, c2}
|
|
||||||
|
|
||||||
for i, c := range contexts {
|
|
||||||
if d := c.Done(); d == nil {
|
|
||||||
t.Errorf("c[%d].Done() == %v want non-nil", i, d)
|
|
||||||
}
|
|
||||||
if e := c.Err(); e != nil {
|
|
||||||
t.Errorf("c[%d].Err() == %v want nil", i, e)
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
case x := <-c.Done():
|
|
||||||
t.Errorf("<-c.Done() == %v want nothing (it should block)", x)
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cancel()
|
|
||||||
time.Sleep(100 * time.Millisecond) // let cancelation propagate
|
|
||||||
|
|
||||||
for i, c := range contexts {
|
|
||||||
select {
|
|
||||||
case <-c.Done():
|
|
||||||
default:
|
|
||||||
t.Errorf("<-c[%d].Done() blocked, but shouldn't have", i)
|
|
||||||
}
|
|
||||||
if e := c.Err(); e != Canceled {
|
|
||||||
t.Errorf("c[%d].Err() == %v want %v", i, e, Canceled)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParentFinishesChild(t *testing.T) {
|
|
||||||
// Context tree:
|
|
||||||
// parent -> cancelChild
|
|
||||||
// parent -> valueChild -> timerChild
|
|
||||||
parent, cancel := WithCancel(Background())
|
|
||||||
cancelChild, stop := WithCancel(parent)
|
|
||||||
defer stop()
|
|
||||||
valueChild := WithValue(parent, "key", "value")
|
|
||||||
timerChild, stop := WithTimeout(valueChild, 10000*time.Hour)
|
|
||||||
defer stop()
|
|
||||||
|
|
||||||
select {
|
|
||||||
case x := <-parent.Done():
|
|
||||||
t.Errorf("<-parent.Done() == %v want nothing (it should block)", x)
|
|
||||||
case x := <-cancelChild.Done():
|
|
||||||
t.Errorf("<-cancelChild.Done() == %v want nothing (it should block)", x)
|
|
||||||
case x := <-timerChild.Done():
|
|
||||||
t.Errorf("<-timerChild.Done() == %v want nothing (it should block)", x)
|
|
||||||
case x := <-valueChild.Done():
|
|
||||||
t.Errorf("<-valueChild.Done() == %v want nothing (it should block)", x)
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
|
|
||||||
// The parent's children should contain the two cancelable children.
|
|
||||||
pc := parent.(*cancelCtx)
|
|
||||||
cc := cancelChild.(*cancelCtx)
|
|
||||||
tc := timerChild.(*timerCtx)
|
|
||||||
pc.mu.Lock()
|
|
||||||
if len(pc.children) != 2 || !pc.children[cc] || !pc.children[tc] {
|
|
||||||
t.Errorf("bad linkage: pc.children = %v, want %v and %v",
|
|
||||||
pc.children, cc, tc)
|
|
||||||
}
|
|
||||||
pc.mu.Unlock()
|
|
||||||
|
|
||||||
if p, ok := parentCancelCtx(cc.Context); !ok || p != pc {
|
|
||||||
t.Errorf("bad linkage: parentCancelCtx(cancelChild.Context) = %v, %v want %v, true", p, ok, pc)
|
|
||||||
}
|
|
||||||
if p, ok := parentCancelCtx(tc.Context); !ok || p != pc {
|
|
||||||
t.Errorf("bad linkage: parentCancelCtx(timerChild.Context) = %v, %v want %v, true", p, ok, pc)
|
|
||||||
}
|
|
||||||
|
|
||||||
cancel()
|
|
||||||
|
|
||||||
pc.mu.Lock()
|
|
||||||
if len(pc.children) != 0 {
|
|
||||||
t.Errorf("pc.cancel didn't clear pc.children = %v", pc.children)
|
|
||||||
}
|
|
||||||
pc.mu.Unlock()
|
|
||||||
|
|
||||||
// parent and children should all be finished.
|
|
||||||
check := func(ctx Context, name string) {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
default:
|
|
||||||
t.Errorf("<-%s.Done() blocked, but shouldn't have", name)
|
|
||||||
}
|
|
||||||
if e := ctx.Err(); e != Canceled {
|
|
||||||
t.Errorf("%s.Err() == %v want %v", name, e, Canceled)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
check(parent, "parent")
|
|
||||||
check(cancelChild, "cancelChild")
|
|
||||||
check(valueChild, "valueChild")
|
|
||||||
check(timerChild, "timerChild")
|
|
||||||
|
|
||||||
// WithCancel should return a canceled context on a canceled parent.
|
|
||||||
precanceledChild := WithValue(parent, "key", "value")
|
|
||||||
select {
|
|
||||||
case <-precanceledChild.Done():
|
|
||||||
default:
|
|
||||||
t.Errorf("<-precanceledChild.Done() blocked, but shouldn't have")
|
|
||||||
}
|
|
||||||
if e := precanceledChild.Err(); e != Canceled {
|
|
||||||
t.Errorf("precanceledChild.Err() == %v want %v", e, Canceled)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestChildFinishesFirst(t *testing.T) {
|
|
||||||
cancelable, stop := WithCancel(Background())
|
|
||||||
defer stop()
|
|
||||||
for _, parent := range []Context{Background(), cancelable} {
|
|
||||||
child, cancel := WithCancel(parent)
|
|
||||||
|
|
||||||
select {
|
|
||||||
case x := <-parent.Done():
|
|
||||||
t.Errorf("<-parent.Done() == %v want nothing (it should block)", x)
|
|
||||||
case x := <-child.Done():
|
|
||||||
t.Errorf("<-child.Done() == %v want nothing (it should block)", x)
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
|
|
||||||
cc := child.(*cancelCtx)
|
|
||||||
pc, pcok := parent.(*cancelCtx) // pcok == false when parent == Background()
|
|
||||||
if p, ok := parentCancelCtx(cc.Context); ok != pcok || (ok && pc != p) {
|
|
||||||
t.Errorf("bad linkage: parentCancelCtx(cc.Context) = %v, %v want %v, %v", p, ok, pc, pcok)
|
|
||||||
}
|
|
||||||
|
|
||||||
if pcok {
|
|
||||||
pc.mu.Lock()
|
|
||||||
if len(pc.children) != 1 || !pc.children[cc] {
|
|
||||||
t.Errorf("bad linkage: pc.children = %v, cc = %v", pc.children, cc)
|
|
||||||
}
|
|
||||||
pc.mu.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
cancel()
|
|
||||||
|
|
||||||
if pcok {
|
|
||||||
pc.mu.Lock()
|
|
||||||
if len(pc.children) != 0 {
|
|
||||||
t.Errorf("child's cancel didn't remove self from pc.children = %v", pc.children)
|
|
||||||
}
|
|
||||||
pc.mu.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
// child should be finished.
|
|
||||||
select {
|
|
||||||
case <-child.Done():
|
|
||||||
default:
|
|
||||||
t.Errorf("<-child.Done() blocked, but shouldn't have")
|
|
||||||
}
|
|
||||||
if e := child.Err(); e != Canceled {
|
|
||||||
t.Errorf("child.Err() == %v want %v", e, Canceled)
|
|
||||||
}
|
|
||||||
|
|
||||||
// parent should not be finished.
|
|
||||||
select {
|
|
||||||
case x := <-parent.Done():
|
|
||||||
t.Errorf("<-parent.Done() == %v want nothing (it should block)", x)
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
if e := parent.Err(); e != nil {
|
|
||||||
t.Errorf("parent.Err() == %v want nil", e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func testDeadline(c Context, wait time.Duration, t *testing.T) {
|
|
||||||
select {
|
|
||||||
case <-time.After(wait):
|
|
||||||
t.Fatalf("context should have timed out")
|
|
||||||
case <-c.Done():
|
|
||||||
}
|
|
||||||
if e := c.Err(); e != DeadlineExceeded {
|
|
||||||
t.Errorf("c.Err() == %v want %v", e, DeadlineExceeded)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDeadline(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
const timeUnit = 500 * time.Millisecond
|
|
||||||
c, _ := WithDeadline(Background(), time.Now().Add(1*timeUnit))
|
|
||||||
if got, prefix := fmt.Sprint(c), "context.Background.WithDeadline("; !strings.HasPrefix(got, prefix) {
|
|
||||||
t.Errorf("c.String() = %q want prefix %q", got, prefix)
|
|
||||||
}
|
|
||||||
testDeadline(c, 2*timeUnit, t)
|
|
||||||
|
|
||||||
c, _ = WithDeadline(Background(), time.Now().Add(1*timeUnit))
|
|
||||||
o := otherContext{c}
|
|
||||||
testDeadline(o, 2*timeUnit, t)
|
|
||||||
|
|
||||||
c, _ = WithDeadline(Background(), time.Now().Add(1*timeUnit))
|
|
||||||
o = otherContext{c}
|
|
||||||
c, _ = WithDeadline(o, time.Now().Add(3*timeUnit))
|
|
||||||
testDeadline(c, 2*timeUnit, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTimeout(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
const timeUnit = 500 * time.Millisecond
|
|
||||||
c, _ := WithTimeout(Background(), 1*timeUnit)
|
|
||||||
if got, prefix := fmt.Sprint(c), "context.Background.WithDeadline("; !strings.HasPrefix(got, prefix) {
|
|
||||||
t.Errorf("c.String() = %q want prefix %q", got, prefix)
|
|
||||||
}
|
|
||||||
testDeadline(c, 2*timeUnit, t)
|
|
||||||
|
|
||||||
c, _ = WithTimeout(Background(), 1*timeUnit)
|
|
||||||
o := otherContext{c}
|
|
||||||
testDeadline(o, 2*timeUnit, t)
|
|
||||||
|
|
||||||
c, _ = WithTimeout(Background(), 1*timeUnit)
|
|
||||||
o = otherContext{c}
|
|
||||||
c, _ = WithTimeout(o, 3*timeUnit)
|
|
||||||
testDeadline(c, 2*timeUnit, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCanceledTimeout(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
const timeUnit = 500 * time.Millisecond
|
|
||||||
c, _ := WithTimeout(Background(), 2*timeUnit)
|
|
||||||
o := otherContext{c}
|
|
||||||
c, cancel := WithTimeout(o, 4*timeUnit)
|
|
||||||
cancel()
|
|
||||||
time.Sleep(1 * timeUnit) // let cancelation propagate
|
|
||||||
select {
|
|
||||||
case <-c.Done():
|
|
||||||
default:
|
|
||||||
t.Errorf("<-c.Done() blocked, but shouldn't have")
|
|
||||||
}
|
|
||||||
if e := c.Err(); e != Canceled {
|
|
||||||
t.Errorf("c.Err() == %v want %v", e, Canceled)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type key1 int
|
|
||||||
type key2 int
|
|
||||||
|
|
||||||
var k1 = key1(1)
|
|
||||||
var k2 = key2(1) // same int as k1, different type
|
|
||||||
var k3 = key2(3) // same type as k2, different int
|
|
||||||
|
|
||||||
func TestValues(t *testing.T) {
|
|
||||||
check := func(c Context, nm, v1, v2, v3 string) {
|
|
||||||
if v, ok := c.Value(k1).(string); ok == (len(v1) == 0) || v != v1 {
|
|
||||||
t.Errorf(`%s.Value(k1).(string) = %q, %t want %q, %t`, nm, v, ok, v1, len(v1) != 0)
|
|
||||||
}
|
|
||||||
if v, ok := c.Value(k2).(string); ok == (len(v2) == 0) || v != v2 {
|
|
||||||
t.Errorf(`%s.Value(k2).(string) = %q, %t want %q, %t`, nm, v, ok, v2, len(v2) != 0)
|
|
||||||
}
|
|
||||||
if v, ok := c.Value(k3).(string); ok == (len(v3) == 0) || v != v3 {
|
|
||||||
t.Errorf(`%s.Value(k3).(string) = %q, %t want %q, %t`, nm, v, ok, v3, len(v3) != 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c0 := Background()
|
|
||||||
check(c0, "c0", "", "", "")
|
|
||||||
|
|
||||||
c1 := WithValue(Background(), k1, "c1k1")
|
|
||||||
check(c1, "c1", "c1k1", "", "")
|
|
||||||
|
|
||||||
if got, want := fmt.Sprint(c1), `context.Background.WithValue(1, "c1k1")`; got != want {
|
|
||||||
t.Errorf("c.String() = %q want %q", got, want)
|
|
||||||
}
|
|
||||||
|
|
||||||
c2 := WithValue(c1, k2, "c2k2")
|
|
||||||
check(c2, "c2", "c1k1", "c2k2", "")
|
|
||||||
|
|
||||||
c3 := WithValue(c2, k3, "c3k3")
|
|
||||||
check(c3, "c2", "c1k1", "c2k2", "c3k3")
|
|
||||||
|
|
||||||
c4 := WithValue(c3, k1, nil)
|
|
||||||
check(c4, "c4", "", "c2k2", "c3k3")
|
|
||||||
|
|
||||||
o0 := otherContext{Background()}
|
|
||||||
check(o0, "o0", "", "", "")
|
|
||||||
|
|
||||||
o1 := otherContext{WithValue(Background(), k1, "c1k1")}
|
|
||||||
check(o1, "o1", "c1k1", "", "")
|
|
||||||
|
|
||||||
o2 := WithValue(o1, k2, "o2k2")
|
|
||||||
check(o2, "o2", "c1k1", "o2k2", "")
|
|
||||||
|
|
||||||
o3 := otherContext{c4}
|
|
||||||
check(o3, "o3", "", "c2k2", "c3k3")
|
|
||||||
|
|
||||||
o4 := WithValue(o3, k3, nil)
|
|
||||||
check(o4, "o4", "", "c2k2", "")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAllocs(t *testing.T) {
|
|
||||||
bg := Background()
|
|
||||||
for _, test := range []struct {
|
|
||||||
desc string
|
|
||||||
f func()
|
|
||||||
limit float64
|
|
||||||
gccgoLimit float64
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
desc: "Background()",
|
|
||||||
f: func() { Background() },
|
|
||||||
limit: 0,
|
|
||||||
gccgoLimit: 0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: fmt.Sprintf("WithValue(bg, %v, nil)", k1),
|
|
||||||
f: func() {
|
|
||||||
c := WithValue(bg, k1, nil)
|
|
||||||
c.Value(k1)
|
|
||||||
},
|
|
||||||
limit: 3,
|
|
||||||
gccgoLimit: 3,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "WithTimeout(bg, 15*time.Millisecond)",
|
|
||||||
f: func() {
|
|
||||||
c, _ := WithTimeout(bg, 15*time.Millisecond)
|
|
||||||
<-c.Done()
|
|
||||||
},
|
|
||||||
limit: 8,
|
|
||||||
gccgoLimit: 16,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "WithCancel(bg)",
|
|
||||||
f: func() {
|
|
||||||
c, cancel := WithCancel(bg)
|
|
||||||
cancel()
|
|
||||||
<-c.Done()
|
|
||||||
},
|
|
||||||
limit: 5,
|
|
||||||
gccgoLimit: 8,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "WithTimeout(bg, 100*time.Millisecond)",
|
|
||||||
f: func() {
|
|
||||||
c, cancel := WithTimeout(bg, 100*time.Millisecond)
|
|
||||||
cancel()
|
|
||||||
<-c.Done()
|
|
||||||
},
|
|
||||||
limit: 8,
|
|
||||||
gccgoLimit: 25,
|
|
||||||
},
|
|
||||||
} {
|
|
||||||
limit := test.limit
|
|
||||||
if runtime.Compiler == "gccgo" {
|
|
||||||
// gccgo does not yet do escape analysis.
|
|
||||||
// TODO(iant): Remove this when gccgo does do escape analysis.
|
|
||||||
limit = test.gccgoLimit
|
|
||||||
}
|
|
||||||
if n := testing.AllocsPerRun(100, test.f); n > limit {
|
|
||||||
t.Errorf("%s allocs = %f want %d", test.desc, n, int(limit))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSimultaneousCancels(t *testing.T) {
|
|
||||||
root, cancel := WithCancel(Background())
|
|
||||||
m := map[Context]CancelFunc{root: cancel}
|
|
||||||
q := []Context{root}
|
|
||||||
// Create a tree of contexts.
|
|
||||||
for len(q) != 0 && len(m) < 100 {
|
|
||||||
parent := q[0]
|
|
||||||
q = q[1:]
|
|
||||||
for i := 0; i < 4; i++ {
|
|
||||||
ctx, cancel := WithCancel(parent)
|
|
||||||
m[ctx] = cancel
|
|
||||||
q = append(q, ctx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Start all the cancels in a random order.
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
wg.Add(len(m))
|
|
||||||
for _, cancel := range m {
|
|
||||||
go func(cancel CancelFunc) {
|
|
||||||
cancel()
|
|
||||||
wg.Done()
|
|
||||||
}(cancel)
|
|
||||||
}
|
|
||||||
// Wait on all the contexts in a random order.
|
|
||||||
for ctx := range m {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
case <-time.After(1 * time.Second):
|
|
||||||
buf := make([]byte, 10<<10)
|
|
||||||
n := runtime.Stack(buf, true)
|
|
||||||
t.Fatalf("timed out waiting for <-ctx.Done(); stacks:\n%s", buf[:n])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Wait for all the cancel functions to return.
|
|
||||||
done := make(chan struct{})
|
|
||||||
go func() {
|
|
||||||
wg.Wait()
|
|
||||||
close(done)
|
|
||||||
}()
|
|
||||||
select {
|
|
||||||
case <-done:
|
|
||||||
case <-time.After(1 * time.Second):
|
|
||||||
buf := make([]byte, 10<<10)
|
|
||||||
n := runtime.Stack(buf, true)
|
|
||||||
t.Fatalf("timed out waiting for cancel functions; stacks:\n%s", buf[:n])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestInterlockedCancels(t *testing.T) {
|
|
||||||
parent, cancelParent := WithCancel(Background())
|
|
||||||
child, cancelChild := WithCancel(parent)
|
|
||||||
go func() {
|
|
||||||
parent.Done()
|
|
||||||
cancelChild()
|
|
||||||
}()
|
|
||||||
cancelParent()
|
|
||||||
select {
|
|
||||||
case <-child.Done():
|
|
||||||
case <-time.After(1 * time.Second):
|
|
||||||
buf := make([]byte, 10<<10)
|
|
||||||
n := runtime.Stack(buf, true)
|
|
||||||
t.Fatalf("timed out waiting for child.Done(); stacks:\n%s", buf[:n])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLayersCancel(t *testing.T) {
|
|
||||||
testLayers(t, time.Now().UnixNano(), false)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLayersTimeout(t *testing.T) {
|
|
||||||
testLayers(t, time.Now().UnixNano(), true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func testLayers(t *testing.T, seed int64, testTimeout bool) {
|
|
||||||
rand.Seed(seed)
|
|
||||||
errorf := func(format string, a ...interface{}) {
|
|
||||||
t.Errorf(fmt.Sprintf("seed=%d: %s", seed, format), a...)
|
|
||||||
}
|
|
||||||
const (
|
|
||||||
timeout = 200 * time.Millisecond
|
|
||||||
minLayers = 30
|
|
||||||
)
|
|
||||||
type value int
|
|
||||||
var (
|
|
||||||
vals []*value
|
|
||||||
cancels []CancelFunc
|
|
||||||
numTimers int
|
|
||||||
ctx = Background()
|
|
||||||
)
|
|
||||||
for i := 0; i < minLayers || numTimers == 0 || len(cancels) == 0 || len(vals) == 0; i++ {
|
|
||||||
switch rand.Intn(3) {
|
|
||||||
case 0:
|
|
||||||
v := new(value)
|
|
||||||
ctx = WithValue(ctx, v, v)
|
|
||||||
vals = append(vals, v)
|
|
||||||
case 1:
|
|
||||||
var cancel CancelFunc
|
|
||||||
ctx, cancel = WithCancel(ctx)
|
|
||||||
cancels = append(cancels, cancel)
|
|
||||||
case 2:
|
|
||||||
var cancel CancelFunc
|
|
||||||
ctx, cancel = WithTimeout(ctx, timeout)
|
|
||||||
cancels = append(cancels, cancel)
|
|
||||||
numTimers++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
checkValues := func(when string) {
|
|
||||||
for _, key := range vals {
|
|
||||||
if val := ctx.Value(key).(*value); key != val {
|
|
||||||
errorf("%s: ctx.Value(%p) = %p want %p", when, key, val, key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
errorf("ctx should not be canceled yet")
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
if s, prefix := fmt.Sprint(ctx), "context.Background."; !strings.HasPrefix(s, prefix) {
|
|
||||||
t.Errorf("ctx.String() = %q want prefix %q", s, prefix)
|
|
||||||
}
|
|
||||||
t.Log(ctx)
|
|
||||||
checkValues("before cancel")
|
|
||||||
if testTimeout {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
case <-time.After(timeout + 100*time.Millisecond):
|
|
||||||
errorf("ctx should have timed out")
|
|
||||||
}
|
|
||||||
checkValues("after timeout")
|
|
||||||
} else {
|
|
||||||
cancel := cancels[rand.Intn(len(cancels))]
|
|
||||||
cancel()
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
default:
|
|
||||||
errorf("ctx should be canceled")
|
|
||||||
}
|
|
||||||
checkValues("after cancel")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCancelRemoves(t *testing.T) {
|
|
||||||
checkChildren := func(when string, ctx Context, want int) {
|
|
||||||
if got := len(ctx.(*cancelCtx).children); got != want {
|
|
||||||
t.Errorf("%s: context has %d children, want %d", when, got, want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx, _ := WithCancel(Background())
|
|
||||||
checkChildren("after creation", ctx, 0)
|
|
||||||
_, cancel := WithCancel(ctx)
|
|
||||||
checkChildren("with WithCancel child ", ctx, 1)
|
|
||||||
cancel()
|
|
||||||
checkChildren("after cancelling WithCancel child", ctx, 0)
|
|
||||||
|
|
||||||
ctx, _ = WithCancel(Background())
|
|
||||||
checkChildren("after creation", ctx, 0)
|
|
||||||
_, cancel = WithTimeout(ctx, 60*time.Minute)
|
|
||||||
checkChildren("with WithTimeout child ", ctx, 1)
|
|
||||||
cancel()
|
|
||||||
checkChildren("after cancelling WithTimeout child", ctx, 0)
|
|
||||||
}
|
|
@ -1,74 +0,0 @@
|
|||||||
// Copyright 2016 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build go1.7
|
|
||||||
|
|
||||||
// Package ctxhttp provides helper functions for performing context-aware HTTP requests.
|
|
||||||
package ctxhttp // import "golang.org/x/net/context/ctxhttp"
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Do sends an HTTP request with the provided http.Client and returns
|
|
||||||
// an HTTP response.
|
|
||||||
//
|
|
||||||
// If the client is nil, http.DefaultClient is used.
|
|
||||||
//
|
|
||||||
// The provided ctx must be non-nil. If it is canceled or times out,
|
|
||||||
// ctx.Err() will be returned.
|
|
||||||
func Do(ctx context.Context, client *http.Client, req *http.Request) (*http.Response, error) {
|
|
||||||
if client == nil {
|
|
||||||
client = http.DefaultClient
|
|
||||||
}
|
|
||||||
resp, err := client.Do(req.WithContext(ctx))
|
|
||||||
// If we got an error, and the context has been canceled,
|
|
||||||
// the context's error is probably more useful.
|
|
||||||
if err != nil {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
err = ctx.Err()
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get issues a GET request via the Do function.
|
|
||||||
func Get(ctx context.Context, client *http.Client, url string) (*http.Response, error) {
|
|
||||||
req, err := http.NewRequest("GET", url, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return Do(ctx, client, req)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Head issues a HEAD request via the Do function.
|
|
||||||
func Head(ctx context.Context, client *http.Client, url string) (*http.Response, error) {
|
|
||||||
req, err := http.NewRequest("HEAD", url, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return Do(ctx, client, req)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Post issues a POST request via the Do function.
|
|
||||||
func Post(ctx context.Context, client *http.Client, url string, bodyType string, body io.Reader) (*http.Response, error) {
|
|
||||||
req, err := http.NewRequest("POST", url, body)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
req.Header.Set("Content-Type", bodyType)
|
|
||||||
return Do(ctx, client, req)
|
|
||||||
}
|
|
||||||
|
|
||||||
// PostForm issues a POST request via the Do function.
|
|
||||||
func PostForm(ctx context.Context, client *http.Client, url string, data url.Values) (*http.Response, error) {
|
|
||||||
return Post(ctx, client, url, "application/x-www-form-urlencoded", strings.NewReader(data.Encode()))
|
|
||||||
}
|
|
@ -1,29 +0,0 @@
|
|||||||
// Copyright 2015 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build !plan9,go1.7
|
|
||||||
|
|
||||||
package ctxhttp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"net/http/httptest"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"context"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestGo17Context(t *testing.T) {
|
|
||||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
io.WriteString(w, "ok")
|
|
||||||
}))
|
|
||||||
defer ts.Close()
|
|
||||||
ctx := context.Background()
|
|
||||||
resp, err := Get(ctx, http.DefaultClient, ts.URL)
|
|
||||||
if resp == nil || err != nil {
|
|
||||||
t.Fatalf("error received from client: %v %v", err, resp)
|
|
||||||
}
|
|
||||||
resp.Body.Close()
|
|
||||||
}
|
|
@ -1,147 +0,0 @@
|
|||||||
// Copyright 2015 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build !go1.7
|
|
||||||
|
|
||||||
package ctxhttp // import "golang.org/x/net/context/ctxhttp"
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
)
|
|
||||||
|
|
||||||
func nop() {}
|
|
||||||
|
|
||||||
var (
|
|
||||||
testHookContextDoneBeforeHeaders = nop
|
|
||||||
testHookDoReturned = nop
|
|
||||||
testHookDidBodyClose = nop
|
|
||||||
)
|
|
||||||
|
|
||||||
// Do sends an HTTP request with the provided http.Client and returns an HTTP response.
|
|
||||||
// If the client is nil, http.DefaultClient is used.
|
|
||||||
// If the context is canceled or times out, ctx.Err() will be returned.
|
|
||||||
func Do(ctx context.Context, client *http.Client, req *http.Request) (*http.Response, error) {
|
|
||||||
if client == nil {
|
|
||||||
client = http.DefaultClient
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(djd): Respect any existing value of req.Cancel.
|
|
||||||
cancel := make(chan struct{})
|
|
||||||
req.Cancel = cancel
|
|
||||||
|
|
||||||
type responseAndError struct {
|
|
||||||
resp *http.Response
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
result := make(chan responseAndError, 1)
|
|
||||||
|
|
||||||
// Make local copies of test hooks closed over by goroutines below.
|
|
||||||
// Prevents data races in tests.
|
|
||||||
testHookDoReturned := testHookDoReturned
|
|
||||||
testHookDidBodyClose := testHookDidBodyClose
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
resp, err := client.Do(req)
|
|
||||||
testHookDoReturned()
|
|
||||||
result <- responseAndError{resp, err}
|
|
||||||
}()
|
|
||||||
|
|
||||||
var resp *http.Response
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
testHookContextDoneBeforeHeaders()
|
|
||||||
close(cancel)
|
|
||||||
// Clean up after the goroutine calling client.Do:
|
|
||||||
go func() {
|
|
||||||
if r := <-result; r.resp != nil {
|
|
||||||
testHookDidBodyClose()
|
|
||||||
r.resp.Body.Close()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
return nil, ctx.Err()
|
|
||||||
case r := <-result:
|
|
||||||
var err error
|
|
||||||
resp, err = r.resp, r.err
|
|
||||||
if err != nil {
|
|
||||||
return resp, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c := make(chan struct{})
|
|
||||||
go func() {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
close(cancel)
|
|
||||||
case <-c:
|
|
||||||
// The response's Body is closed.
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
resp.Body = ¬ifyingReader{resp.Body, c}
|
|
||||||
|
|
||||||
return resp, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get issues a GET request via the Do function.
|
|
||||||
func Get(ctx context.Context, client *http.Client, url string) (*http.Response, error) {
|
|
||||||
req, err := http.NewRequest("GET", url, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return Do(ctx, client, req)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Head issues a HEAD request via the Do function.
|
|
||||||
func Head(ctx context.Context, client *http.Client, url string) (*http.Response, error) {
|
|
||||||
req, err := http.NewRequest("HEAD", url, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return Do(ctx, client, req)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Post issues a POST request via the Do function.
|
|
||||||
func Post(ctx context.Context, client *http.Client, url string, bodyType string, body io.Reader) (*http.Response, error) {
|
|
||||||
req, err := http.NewRequest("POST", url, body)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
req.Header.Set("Content-Type", bodyType)
|
|
||||||
return Do(ctx, client, req)
|
|
||||||
}
|
|
||||||
|
|
||||||
// PostForm issues a POST request via the Do function.
|
|
||||||
func PostForm(ctx context.Context, client *http.Client, url string, data url.Values) (*http.Response, error) {
|
|
||||||
return Post(ctx, client, url, "application/x-www-form-urlencoded", strings.NewReader(data.Encode()))
|
|
||||||
}
|
|
||||||
|
|
||||||
// notifyingReader is an io.ReadCloser that closes the notify channel after
|
|
||||||
// Close is called or a Read fails on the underlying ReadCloser.
|
|
||||||
type notifyingReader struct {
|
|
||||||
io.ReadCloser
|
|
||||||
notify chan<- struct{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *notifyingReader) Read(p []byte) (int, error) {
|
|
||||||
n, err := r.ReadCloser.Read(p)
|
|
||||||
if err != nil && r.notify != nil {
|
|
||||||
close(r.notify)
|
|
||||||
r.notify = nil
|
|
||||||
}
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *notifyingReader) Close() error {
|
|
||||||
err := r.ReadCloser.Close()
|
|
||||||
if r.notify != nil {
|
|
||||||
close(r.notify)
|
|
||||||
r.notify = nil
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
@ -1,79 +0,0 @@
|
|||||||
// Copyright 2015 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build !plan9,!go1.7
|
|
||||||
|
|
||||||
package ctxhttp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"net/http/httptest"
|
|
||||||
"sync"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
)
|
|
||||||
|
|
||||||
// golang.org/issue/14065
|
|
||||||
func TestClosesResponseBodyOnCancel(t *testing.T) {
|
|
||||||
defer func() { testHookContextDoneBeforeHeaders = nop }()
|
|
||||||
defer func() { testHookDoReturned = nop }()
|
|
||||||
defer func() { testHookDidBodyClose = nop }()
|
|
||||||
|
|
||||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
|
|
||||||
defer ts.Close()
|
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
|
|
||||||
// closed when Do enters select case <-ctx.Done()
|
|
||||||
enteredDonePath := make(chan struct{})
|
|
||||||
|
|
||||||
testHookContextDoneBeforeHeaders = func() {
|
|
||||||
close(enteredDonePath)
|
|
||||||
}
|
|
||||||
|
|
||||||
testHookDoReturned = func() {
|
|
||||||
// We now have the result (the Flush'd headers) at least,
|
|
||||||
// so we can cancel the request.
|
|
||||||
cancel()
|
|
||||||
|
|
||||||
// But block the client.Do goroutine from sending
|
|
||||||
// until Do enters into the <-ctx.Done() path, since
|
|
||||||
// otherwise if both channels are readable, select
|
|
||||||
// picks a random one.
|
|
||||||
<-enteredDonePath
|
|
||||||
}
|
|
||||||
|
|
||||||
sawBodyClose := make(chan struct{})
|
|
||||||
testHookDidBodyClose = func() { close(sawBodyClose) }
|
|
||||||
|
|
||||||
tr := &http.Transport{}
|
|
||||||
defer tr.CloseIdleConnections()
|
|
||||||
c := &http.Client{Transport: tr}
|
|
||||||
req, _ := http.NewRequest("GET", ts.URL, nil)
|
|
||||||
_, doErr := Do(ctx, c, req)
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-sawBodyClose:
|
|
||||||
case <-time.After(5 * time.Second):
|
|
||||||
t.Fatal("timeout waiting for body to close")
|
|
||||||
}
|
|
||||||
|
|
||||||
if doErr != ctx.Err() {
|
|
||||||
t.Errorf("Do error = %v; want %v", doErr, ctx.Err())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type noteCloseConn struct {
|
|
||||||
net.Conn
|
|
||||||
onceClose sync.Once
|
|
||||||
closefn func()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *noteCloseConn) Close() error {
|
|
||||||
c.onceClose.Do(c.closefn)
|
|
||||||
return c.Conn.Close()
|
|
||||||
}
|
|
@ -1,105 +0,0 @@
|
|||||||
// Copyright 2015 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build !plan9
|
|
||||||
|
|
||||||
package ctxhttp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
|
||||||
"net/http/httptest"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
requestDuration = 100 * time.Millisecond
|
|
||||||
requestBody = "ok"
|
|
||||||
)
|
|
||||||
|
|
||||||
func okHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
time.Sleep(requestDuration)
|
|
||||||
io.WriteString(w, requestBody)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNoTimeout(t *testing.T) {
|
|
||||||
ts := httptest.NewServer(http.HandlerFunc(okHandler))
|
|
||||||
defer ts.Close()
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
res, err := Get(ctx, nil, ts.URL)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer res.Body.Close()
|
|
||||||
slurp, err := ioutil.ReadAll(res.Body)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if string(slurp) != requestBody {
|
|
||||||
t.Errorf("body = %q; want %q", slurp, requestBody)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCancelBeforeHeaders(t *testing.T) {
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
|
|
||||||
blockServer := make(chan struct{})
|
|
||||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
cancel()
|
|
||||||
<-blockServer
|
|
||||||
io.WriteString(w, requestBody)
|
|
||||||
}))
|
|
||||||
defer ts.Close()
|
|
||||||
defer close(blockServer)
|
|
||||||
|
|
||||||
res, err := Get(ctx, nil, ts.URL)
|
|
||||||
if err == nil {
|
|
||||||
res.Body.Close()
|
|
||||||
t.Fatal("Get returned unexpected nil error")
|
|
||||||
}
|
|
||||||
if err != context.Canceled {
|
|
||||||
t.Errorf("err = %v; want %v", err, context.Canceled)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCancelAfterHangingRequest(t *testing.T) {
|
|
||||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
w.(http.Flusher).Flush()
|
|
||||||
<-w.(http.CloseNotifier).CloseNotify()
|
|
||||||
}))
|
|
||||||
defer ts.Close()
|
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
resp, err := Get(ctx, nil, ts.URL)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error in Get: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cancel befer reading the body.
|
|
||||||
// Reading Request.Body should fail, since the request was
|
|
||||||
// canceled before anything was written.
|
|
||||||
cancel()
|
|
||||||
|
|
||||||
done := make(chan struct{})
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
b, err := ioutil.ReadAll(resp.Body)
|
|
||||||
if len(b) != 0 || err == nil {
|
|
||||||
t.Errorf(`Read got (%q, %v); want ("", error)`, b, err)
|
|
||||||
}
|
|
||||||
close(done)
|
|
||||||
}()
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-time.After(1 * time.Second):
|
|
||||||
t.Errorf("Test timed out")
|
|
||||||
case <-done:
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,72 +0,0 @@
|
|||||||
// Copyright 2016 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build go1.7
|
|
||||||
|
|
||||||
package context
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context" // standard library's context, as of Go 1.7
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
todo = context.TODO()
|
|
||||||
background = context.Background()
|
|
||||||
)
|
|
||||||
|
|
||||||
// Canceled is the error returned by Context.Err when the context is canceled.
|
|
||||||
var Canceled = context.Canceled
|
|
||||||
|
|
||||||
// DeadlineExceeded is the error returned by Context.Err when the context's
|
|
||||||
// deadline passes.
|
|
||||||
var DeadlineExceeded = context.DeadlineExceeded
|
|
||||||
|
|
||||||
// WithCancel returns a copy of parent with a new Done channel. The returned
|
|
||||||
// context's Done channel is closed when the returned cancel function is called
|
|
||||||
// or when the parent context's Done channel is closed, whichever happens first.
|
|
||||||
//
|
|
||||||
// Canceling this context releases resources associated with it, so code should
|
|
||||||
// call cancel as soon as the operations running in this Context complete.
|
|
||||||
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
|
|
||||||
ctx, f := context.WithCancel(parent)
|
|
||||||
return ctx, CancelFunc(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithDeadline returns a copy of the parent context with the deadline adjusted
|
|
||||||
// to be no later than d. If the parent's deadline is already earlier than d,
|
|
||||||
// WithDeadline(parent, d) is semantically equivalent to parent. The returned
|
|
||||||
// context's Done channel is closed when the deadline expires, when the returned
|
|
||||||
// cancel function is called, or when the parent context's Done channel is
|
|
||||||
// closed, whichever happens first.
|
|
||||||
//
|
|
||||||
// Canceling this context releases resources associated with it, so code should
|
|
||||||
// call cancel as soon as the operations running in this Context complete.
|
|
||||||
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) {
|
|
||||||
ctx, f := context.WithDeadline(parent, deadline)
|
|
||||||
return ctx, CancelFunc(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithTimeout returns WithDeadline(parent, time.Now().Add(timeout)).
|
|
||||||
//
|
|
||||||
// Canceling this context releases resources associated with it, so code should
|
|
||||||
// call cancel as soon as the operations running in this Context complete:
|
|
||||||
//
|
|
||||||
// func slowOperationWithTimeout(ctx context.Context) (Result, error) {
|
|
||||||
// ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond)
|
|
||||||
// defer cancel() // releases resources if slowOperation completes before timeout elapses
|
|
||||||
// return slowOperation(ctx)
|
|
||||||
// }
|
|
||||||
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
|
|
||||||
return WithDeadline(parent, time.Now().Add(timeout))
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithValue returns a copy of parent in which the value associated with key is
|
|
||||||
// val.
|
|
||||||
//
|
|
||||||
// Use context Values only for request-scoped data that transits processes and
|
|
||||||
// APIs, not for passing optional parameters to functions.
|
|
||||||
func WithValue(parent Context, key interface{}, val interface{}) Context {
|
|
||||||
return context.WithValue(parent, key, val)
|
|
||||||
}
|
|
@ -1,20 +0,0 @@
|
|||||||
// Copyright 2017 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build go1.9
|
|
||||||
|
|
||||||
package context
|
|
||||||
|
|
||||||
import "context" // standard library's context, as of Go 1.7
|
|
||||||
|
|
||||||
// A Context carries a deadline, a cancelation signal, and other values across
|
|
||||||
// API boundaries.
|
|
||||||
//
|
|
||||||
// Context's methods may be called by multiple goroutines simultaneously.
|
|
||||||
type Context = context.Context
|
|
||||||
|
|
||||||
// A CancelFunc tells an operation to abandon its work.
|
|
||||||
// A CancelFunc does not wait for the work to stop.
|
|
||||||
// After the first call, subsequent calls to a CancelFunc do nothing.
|
|
||||||
type CancelFunc = context.CancelFunc
|
|
@ -1,300 +0,0 @@
|
|||||||
// Copyright 2014 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build !go1.7
|
|
||||||
|
|
||||||
package context
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// An emptyCtx is never canceled, has no values, and has no deadline. It is not
|
|
||||||
// struct{}, since vars of this type must have distinct addresses.
|
|
||||||
type emptyCtx int
|
|
||||||
|
|
||||||
func (*emptyCtx) Deadline() (deadline time.Time, ok bool) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*emptyCtx) Done() <-chan struct{} {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*emptyCtx) Err() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*emptyCtx) Value(key interface{}) interface{} {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *emptyCtx) String() string {
|
|
||||||
switch e {
|
|
||||||
case background:
|
|
||||||
return "context.Background"
|
|
||||||
case todo:
|
|
||||||
return "context.TODO"
|
|
||||||
}
|
|
||||||
return "unknown empty Context"
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
background = new(emptyCtx)
|
|
||||||
todo = new(emptyCtx)
|
|
||||||
)
|
|
||||||
|
|
||||||
// Canceled is the error returned by Context.Err when the context is canceled.
|
|
||||||
var Canceled = errors.New("context canceled")
|
|
||||||
|
|
||||||
// DeadlineExceeded is the error returned by Context.Err when the context's
|
|
||||||
// deadline passes.
|
|
||||||
var DeadlineExceeded = errors.New("context deadline exceeded")
|
|
||||||
|
|
||||||
// WithCancel returns a copy of parent with a new Done channel. The returned
|
|
||||||
// context's Done channel is closed when the returned cancel function is called
|
|
||||||
// or when the parent context's Done channel is closed, whichever happens first.
|
|
||||||
//
|
|
||||||
// Canceling this context releases resources associated with it, so code should
|
|
||||||
// call cancel as soon as the operations running in this Context complete.
|
|
||||||
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
|
|
||||||
c := newCancelCtx(parent)
|
|
||||||
propagateCancel(parent, c)
|
|
||||||
return c, func() { c.cancel(true, Canceled) }
|
|
||||||
}
|
|
||||||
|
|
||||||
// newCancelCtx returns an initialized cancelCtx.
|
|
||||||
func newCancelCtx(parent Context) *cancelCtx {
|
|
||||||
return &cancelCtx{
|
|
||||||
Context: parent,
|
|
||||||
done: make(chan struct{}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// propagateCancel arranges for child to be canceled when parent is.
|
|
||||||
func propagateCancel(parent Context, child canceler) {
|
|
||||||
if parent.Done() == nil {
|
|
||||||
return // parent is never canceled
|
|
||||||
}
|
|
||||||
if p, ok := parentCancelCtx(parent); ok {
|
|
||||||
p.mu.Lock()
|
|
||||||
if p.err != nil {
|
|
||||||
// parent has already been canceled
|
|
||||||
child.cancel(false, p.err)
|
|
||||||
} else {
|
|
||||||
if p.children == nil {
|
|
||||||
p.children = make(map[canceler]bool)
|
|
||||||
}
|
|
||||||
p.children[child] = true
|
|
||||||
}
|
|
||||||
p.mu.Unlock()
|
|
||||||
} else {
|
|
||||||
go func() {
|
|
||||||
select {
|
|
||||||
case <-parent.Done():
|
|
||||||
child.cancel(false, parent.Err())
|
|
||||||
case <-child.Done():
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// parentCancelCtx follows a chain of parent references until it finds a
|
|
||||||
// *cancelCtx. This function understands how each of the concrete types in this
|
|
||||||
// package represents its parent.
|
|
||||||
func parentCancelCtx(parent Context) (*cancelCtx, bool) {
|
|
||||||
for {
|
|
||||||
switch c := parent.(type) {
|
|
||||||
case *cancelCtx:
|
|
||||||
return c, true
|
|
||||||
case *timerCtx:
|
|
||||||
return c.cancelCtx, true
|
|
||||||
case *valueCtx:
|
|
||||||
parent = c.Context
|
|
||||||
default:
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// removeChild removes a context from its parent.
|
|
||||||
func removeChild(parent Context, child canceler) {
|
|
||||||
p, ok := parentCancelCtx(parent)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
p.mu.Lock()
|
|
||||||
if p.children != nil {
|
|
||||||
delete(p.children, child)
|
|
||||||
}
|
|
||||||
p.mu.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
// A canceler is a context type that can be canceled directly. The
|
|
||||||
// implementations are *cancelCtx and *timerCtx.
|
|
||||||
type canceler interface {
|
|
||||||
cancel(removeFromParent bool, err error)
|
|
||||||
Done() <-chan struct{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// A cancelCtx can be canceled. When canceled, it also cancels any children
|
|
||||||
// that implement canceler.
|
|
||||||
type cancelCtx struct {
|
|
||||||
Context
|
|
||||||
|
|
||||||
done chan struct{} // closed by the first cancel call.
|
|
||||||
|
|
||||||
mu sync.Mutex
|
|
||||||
children map[canceler]bool // set to nil by the first cancel call
|
|
||||||
err error // set to non-nil by the first cancel call
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *cancelCtx) Done() <-chan struct{} {
|
|
||||||
return c.done
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *cancelCtx) Err() error {
|
|
||||||
c.mu.Lock()
|
|
||||||
defer c.mu.Unlock()
|
|
||||||
return c.err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *cancelCtx) String() string {
|
|
||||||
return fmt.Sprintf("%v.WithCancel", c.Context)
|
|
||||||
}
|
|
||||||
|
|
||||||
// cancel closes c.done, cancels each of c's children, and, if
|
|
||||||
// removeFromParent is true, removes c from its parent's children.
|
|
||||||
func (c *cancelCtx) cancel(removeFromParent bool, err error) {
|
|
||||||
if err == nil {
|
|
||||||
panic("context: internal error: missing cancel error")
|
|
||||||
}
|
|
||||||
c.mu.Lock()
|
|
||||||
if c.err != nil {
|
|
||||||
c.mu.Unlock()
|
|
||||||
return // already canceled
|
|
||||||
}
|
|
||||||
c.err = err
|
|
||||||
close(c.done)
|
|
||||||
for child := range c.children {
|
|
||||||
// NOTE: acquiring the child's lock while holding parent's lock.
|
|
||||||
child.cancel(false, err)
|
|
||||||
}
|
|
||||||
c.children = nil
|
|
||||||
c.mu.Unlock()
|
|
||||||
|
|
||||||
if removeFromParent {
|
|
||||||
removeChild(c.Context, c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithDeadline returns a copy of the parent context with the deadline adjusted
|
|
||||||
// to be no later than d. If the parent's deadline is already earlier than d,
|
|
||||||
// WithDeadline(parent, d) is semantically equivalent to parent. The returned
|
|
||||||
// context's Done channel is closed when the deadline expires, when the returned
|
|
||||||
// cancel function is called, or when the parent context's Done channel is
|
|
||||||
// closed, whichever happens first.
|
|
||||||
//
|
|
||||||
// Canceling this context releases resources associated with it, so code should
|
|
||||||
// call cancel as soon as the operations running in this Context complete.
|
|
||||||
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) {
|
|
||||||
if cur, ok := parent.Deadline(); ok && cur.Before(deadline) {
|
|
||||||
// The current deadline is already sooner than the new one.
|
|
||||||
return WithCancel(parent)
|
|
||||||
}
|
|
||||||
c := &timerCtx{
|
|
||||||
cancelCtx: newCancelCtx(parent),
|
|
||||||
deadline: deadline,
|
|
||||||
}
|
|
||||||
propagateCancel(parent, c)
|
|
||||||
d := deadline.Sub(time.Now())
|
|
||||||
if d <= 0 {
|
|
||||||
c.cancel(true, DeadlineExceeded) // deadline has already passed
|
|
||||||
return c, func() { c.cancel(true, Canceled) }
|
|
||||||
}
|
|
||||||
c.mu.Lock()
|
|
||||||
defer c.mu.Unlock()
|
|
||||||
if c.err == nil {
|
|
||||||
c.timer = time.AfterFunc(d, func() {
|
|
||||||
c.cancel(true, DeadlineExceeded)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return c, func() { c.cancel(true, Canceled) }
|
|
||||||
}
|
|
||||||
|
|
||||||
// A timerCtx carries a timer and a deadline. It embeds a cancelCtx to
|
|
||||||
// implement Done and Err. It implements cancel by stopping its timer then
|
|
||||||
// delegating to cancelCtx.cancel.
|
|
||||||
type timerCtx struct {
|
|
||||||
*cancelCtx
|
|
||||||
timer *time.Timer // Under cancelCtx.mu.
|
|
||||||
|
|
||||||
deadline time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *timerCtx) Deadline() (deadline time.Time, ok bool) {
|
|
||||||
return c.deadline, true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *timerCtx) String() string {
|
|
||||||
return fmt.Sprintf("%v.WithDeadline(%s [%s])", c.cancelCtx.Context, c.deadline, c.deadline.Sub(time.Now()))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *timerCtx) cancel(removeFromParent bool, err error) {
|
|
||||||
c.cancelCtx.cancel(false, err)
|
|
||||||
if removeFromParent {
|
|
||||||
// Remove this timerCtx from its parent cancelCtx's children.
|
|
||||||
removeChild(c.cancelCtx.Context, c)
|
|
||||||
}
|
|
||||||
c.mu.Lock()
|
|
||||||
if c.timer != nil {
|
|
||||||
c.timer.Stop()
|
|
||||||
c.timer = nil
|
|
||||||
}
|
|
||||||
c.mu.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithTimeout returns WithDeadline(parent, time.Now().Add(timeout)).
|
|
||||||
//
|
|
||||||
// Canceling this context releases resources associated with it, so code should
|
|
||||||
// call cancel as soon as the operations running in this Context complete:
|
|
||||||
//
|
|
||||||
// func slowOperationWithTimeout(ctx context.Context) (Result, error) {
|
|
||||||
// ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond)
|
|
||||||
// defer cancel() // releases resources if slowOperation completes before timeout elapses
|
|
||||||
// return slowOperation(ctx)
|
|
||||||
// }
|
|
||||||
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
|
|
||||||
return WithDeadline(parent, time.Now().Add(timeout))
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithValue returns a copy of parent in which the value associated with key is
|
|
||||||
// val.
|
|
||||||
//
|
|
||||||
// Use context Values only for request-scoped data that transits processes and
|
|
||||||
// APIs, not for passing optional parameters to functions.
|
|
||||||
func WithValue(parent Context, key interface{}, val interface{}) Context {
|
|
||||||
return &valueCtx{parent, key, val}
|
|
||||||
}
|
|
||||||
|
|
||||||
// A valueCtx carries a key-value pair. It implements Value for that key and
|
|
||||||
// delegates all other calls to the embedded Context.
|
|
||||||
type valueCtx struct {
|
|
||||||
Context
|
|
||||||
key, val interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *valueCtx) String() string {
|
|
||||||
return fmt.Sprintf("%v.WithValue(%#v, %#v)", c.Context, c.key, c.val)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *valueCtx) Value(key interface{}) interface{} {
|
|
||||||
if c.key == key {
|
|
||||||
return c.val
|
|
||||||
}
|
|
||||||
return c.Context.Value(key)
|
|
||||||
}
|
|
@ -1,109 +0,0 @@
|
|||||||
// Copyright 2014 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build !go1.9
|
|
||||||
|
|
||||||
package context
|
|
||||||
|
|
||||||
import "time"
|
|
||||||
|
|
||||||
// A Context carries a deadline, a cancelation signal, and other values across
|
|
||||||
// API boundaries.
|
|
||||||
//
|
|
||||||
// Context's methods may be called by multiple goroutines simultaneously.
|
|
||||||
type Context interface {
|
|
||||||
// Deadline returns the time when work done on behalf of this context
|
|
||||||
// should be canceled. Deadline returns ok==false when no deadline is
|
|
||||||
// set. Successive calls to Deadline return the same results.
|
|
||||||
Deadline() (deadline time.Time, ok bool)
|
|
||||||
|
|
||||||
// Done returns a channel that's closed when work done on behalf of this
|
|
||||||
// context should be canceled. Done may return nil if this context can
|
|
||||||
// never be canceled. Successive calls to Done return the same value.
|
|
||||||
//
|
|
||||||
// WithCancel arranges for Done to be closed when cancel is called;
|
|
||||||
// WithDeadline arranges for Done to be closed when the deadline
|
|
||||||
// expires; WithTimeout arranges for Done to be closed when the timeout
|
|
||||||
// elapses.
|
|
||||||
//
|
|
||||||
// Done is provided for use in select statements:
|
|
||||||
//
|
|
||||||
// // Stream generates values with DoSomething and sends them to out
|
|
||||||
// // until DoSomething returns an error or ctx.Done is closed.
|
|
||||||
// func Stream(ctx context.Context, out chan<- Value) error {
|
|
||||||
// for {
|
|
||||||
// v, err := DoSomething(ctx)
|
|
||||||
// if err != nil {
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
// select {
|
|
||||||
// case <-ctx.Done():
|
|
||||||
// return ctx.Err()
|
|
||||||
// case out <- v:
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// See http://blog.golang.org/pipelines for more examples of how to use
|
|
||||||
// a Done channel for cancelation.
|
|
||||||
Done() <-chan struct{}
|
|
||||||
|
|
||||||
// Err returns a non-nil error value after Done is closed. Err returns
|
|
||||||
// Canceled if the context was canceled or DeadlineExceeded if the
|
|
||||||
// context's deadline passed. No other values for Err are defined.
|
|
||||||
// After Done is closed, successive calls to Err return the same value.
|
|
||||||
Err() error
|
|
||||||
|
|
||||||
// Value returns the value associated with this context for key, or nil
|
|
||||||
// if no value is associated with key. Successive calls to Value with
|
|
||||||
// the same key returns the same result.
|
|
||||||
//
|
|
||||||
// Use context values only for request-scoped data that transits
|
|
||||||
// processes and API boundaries, not for passing optional parameters to
|
|
||||||
// functions.
|
|
||||||
//
|
|
||||||
// A key identifies a specific value in a Context. Functions that wish
|
|
||||||
// to store values in Context typically allocate a key in a global
|
|
||||||
// variable then use that key as the argument to context.WithValue and
|
|
||||||
// Context.Value. A key can be any type that supports equality;
|
|
||||||
// packages should define keys as an unexported type to avoid
|
|
||||||
// collisions.
|
|
||||||
//
|
|
||||||
// Packages that define a Context key should provide type-safe accessors
|
|
||||||
// for the values stores using that key:
|
|
||||||
//
|
|
||||||
// // Package user defines a User type that's stored in Contexts.
|
|
||||||
// package user
|
|
||||||
//
|
|
||||||
// import "golang.org/x/net/context"
|
|
||||||
//
|
|
||||||
// // User is the type of value stored in the Contexts.
|
|
||||||
// type User struct {...}
|
|
||||||
//
|
|
||||||
// // key is an unexported type for keys defined in this package.
|
|
||||||
// // This prevents collisions with keys defined in other packages.
|
|
||||||
// type key int
|
|
||||||
//
|
|
||||||
// // userKey is the key for user.User values in Contexts. It is
|
|
||||||
// // unexported; clients use user.NewContext and user.FromContext
|
|
||||||
// // instead of using this key directly.
|
|
||||||
// var userKey key = 0
|
|
||||||
//
|
|
||||||
// // NewContext returns a new Context that carries value u.
|
|
||||||
// func NewContext(ctx context.Context, u *User) context.Context {
|
|
||||||
// return context.WithValue(ctx, userKey, u)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // FromContext returns the User value stored in ctx, if any.
|
|
||||||
// func FromContext(ctx context.Context) (*User, bool) {
|
|
||||||
// u, ok := ctx.Value(userKey).(*User)
|
|
||||||
// return u, ok
|
|
||||||
// }
|
|
||||||
Value(key interface{}) interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// A CancelFunc tells an operation to abandon its work.
|
|
||||||
// A CancelFunc does not wait for the work to stop.
|
|
||||||
// After the first call, subsequent calls to a CancelFunc do nothing.
|
|
||||||
type CancelFunc func()
|
|
@ -1,31 +0,0 @@
|
|||||||
// Copyright 2014 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package context_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
)
|
|
||||||
|
|
||||||
// This example passes a context with a timeout to tell a blocking function that
|
|
||||||
// it should abandon its work after the timeout elapses.
|
|
||||||
func ExampleWithTimeout() {
|
|
||||||
// Pass a context with a timeout to tell a blocking function that it
|
|
||||||
// should abandon its work after the timeout elapses.
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-time.After(1 * time.Second):
|
|
||||||
fmt.Println("overslept")
|
|
||||||
case <-ctx.Done():
|
|
||||||
fmt.Println(ctx.Err()) // prints "context deadline exceeded"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Output:
|
|
||||||
// context deadline exceeded
|
|
||||||
}
|
|
@ -1,210 +0,0 @@
|
|||||||
// Copyright 2010 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// Package dict implements the Dictionary Server Protocol
|
|
||||||
// as defined in RFC 2229.
|
|
||||||
package dict // import "golang.org/x/net/dict"
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/textproto"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// A Client represents a client connection to a dictionary server.
|
|
||||||
type Client struct {
|
|
||||||
text *textproto.Conn
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dial returns a new client connected to a dictionary server at
|
|
||||||
// addr on the given network.
|
|
||||||
func Dial(network, addr string) (*Client, error) {
|
|
||||||
text, err := textproto.Dial(network, addr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
_, _, err = text.ReadCodeLine(220)
|
|
||||||
if err != nil {
|
|
||||||
text.Close()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &Client{text: text}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close closes the connection to the dictionary server.
|
|
||||||
func (c *Client) Close() error {
|
|
||||||
return c.text.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
// A Dict represents a dictionary available on the server.
|
|
||||||
type Dict struct {
|
|
||||||
Name string // short name of dictionary
|
|
||||||
Desc string // long description
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dicts returns a list of the dictionaries available on the server.
|
|
||||||
func (c *Client) Dicts() ([]Dict, error) {
|
|
||||||
id, err := c.text.Cmd("SHOW DB")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
c.text.StartResponse(id)
|
|
||||||
defer c.text.EndResponse(id)
|
|
||||||
|
|
||||||
_, _, err = c.text.ReadCodeLine(110)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
lines, err := c.text.ReadDotLines()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
_, _, err = c.text.ReadCodeLine(250)
|
|
||||||
|
|
||||||
dicts := make([]Dict, len(lines))
|
|
||||||
for i := range dicts {
|
|
||||||
d := &dicts[i]
|
|
||||||
a, _ := fields(lines[i])
|
|
||||||
if len(a) < 2 {
|
|
||||||
return nil, textproto.ProtocolError("invalid dictionary: " + lines[i])
|
|
||||||
}
|
|
||||||
d.Name = a[0]
|
|
||||||
d.Desc = a[1]
|
|
||||||
}
|
|
||||||
return dicts, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// A Defn represents a definition.
|
|
||||||
type Defn struct {
|
|
||||||
Dict Dict // Dict where definition was found
|
|
||||||
Word string // Word being defined
|
|
||||||
Text []byte // Definition text, typically multiple lines
|
|
||||||
}
|
|
||||||
|
|
||||||
// Define requests the definition of the given word.
|
|
||||||
// The argument dict names the dictionary to use,
|
|
||||||
// the Name field of a Dict returned by Dicts.
|
|
||||||
//
|
|
||||||
// The special dictionary name "*" means to look in all the
|
|
||||||
// server's dictionaries.
|
|
||||||
// The special dictionary name "!" means to look in all the
|
|
||||||
// server's dictionaries in turn, stopping after finding the word
|
|
||||||
// in one of them.
|
|
||||||
func (c *Client) Define(dict, word string) ([]*Defn, error) {
|
|
||||||
id, err := c.text.Cmd("DEFINE %s %q", dict, word)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
c.text.StartResponse(id)
|
|
||||||
defer c.text.EndResponse(id)
|
|
||||||
|
|
||||||
_, line, err := c.text.ReadCodeLine(150)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
a, _ := fields(line)
|
|
||||||
if len(a) < 1 {
|
|
||||||
return nil, textproto.ProtocolError("malformed response: " + line)
|
|
||||||
}
|
|
||||||
n, err := strconv.Atoi(a[0])
|
|
||||||
if err != nil {
|
|
||||||
return nil, textproto.ProtocolError("invalid definition count: " + a[0])
|
|
||||||
}
|
|
||||||
def := make([]*Defn, n)
|
|
||||||
for i := 0; i < n; i++ {
|
|
||||||
_, line, err = c.text.ReadCodeLine(151)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
a, _ := fields(line)
|
|
||||||
if len(a) < 3 {
|
|
||||||
// skip it, to keep protocol in sync
|
|
||||||
i--
|
|
||||||
n--
|
|
||||||
def = def[0:n]
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
d := &Defn{Word: a[0], Dict: Dict{a[1], a[2]}}
|
|
||||||
d.Text, err = c.text.ReadDotBytes()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
def[i] = d
|
|
||||||
}
|
|
||||||
_, _, err = c.text.ReadCodeLine(250)
|
|
||||||
return def, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fields returns the fields in s.
|
|
||||||
// Fields are space separated unquoted words
|
|
||||||
// or quoted with single or double quote.
|
|
||||||
func fields(s string) ([]string, error) {
|
|
||||||
var v []string
|
|
||||||
i := 0
|
|
||||||
for {
|
|
||||||
for i < len(s) && (s[i] == ' ' || s[i] == '\t') {
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
if i >= len(s) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if s[i] == '"' || s[i] == '\'' {
|
|
||||||
q := s[i]
|
|
||||||
// quoted string
|
|
||||||
var j int
|
|
||||||
for j = i + 1; ; j++ {
|
|
||||||
if j >= len(s) {
|
|
||||||
return nil, textproto.ProtocolError("malformed quoted string")
|
|
||||||
}
|
|
||||||
if s[j] == '\\' {
|
|
||||||
j++
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if s[j] == q {
|
|
||||||
j++
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
v = append(v, unquote(s[i+1:j-1]))
|
|
||||||
i = j
|
|
||||||
} else {
|
|
||||||
// atom
|
|
||||||
var j int
|
|
||||||
for j = i; j < len(s); j++ {
|
|
||||||
if s[j] == ' ' || s[j] == '\t' || s[j] == '\\' || s[j] == '"' || s[j] == '\'' {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
v = append(v, s[i:j])
|
|
||||||
i = j
|
|
||||||
}
|
|
||||||
if i < len(s) {
|
|
||||||
c := s[i]
|
|
||||||
if c != ' ' && c != '\t' {
|
|
||||||
return nil, textproto.ProtocolError("quotes not on word boundaries")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return v, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func unquote(s string) string {
|
|
||||||
if strings.Index(s, "\\") < 0 {
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
b := []byte(s)
|
|
||||||
w := 0
|
|
||||||
for r := 0; r < len(b); r++ {
|
|
||||||
c := b[r]
|
|
||||||
if c == '\\' {
|
|
||||||
r++
|
|
||||||
c = b[r]
|
|
||||||
}
|
|
||||||
b[w] = c
|
|
||||||
w++
|
|
||||||
}
|
|
||||||
return string(b[0:w])
|
|
||||||
}
|
|
@ -1,132 +0,0 @@
|
|||||||
// Copyright 2017 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package dnsmessage_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"golang.org/x/net/dns/dnsmessage"
|
|
||||||
)
|
|
||||||
|
|
||||||
func mustNewName(name string) dnsmessage.Name {
|
|
||||||
n, err := dnsmessage.NewName(name)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return n
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleParser() {
|
|
||||||
msg := dnsmessage.Message{
|
|
||||||
Header: dnsmessage.Header{Response: true, Authoritative: true},
|
|
||||||
Questions: []dnsmessage.Question{
|
|
||||||
{
|
|
||||||
Name: mustNewName("foo.bar.example.com."),
|
|
||||||
Type: dnsmessage.TypeA,
|
|
||||||
Class: dnsmessage.ClassINET,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: mustNewName("bar.example.com."),
|
|
||||||
Type: dnsmessage.TypeA,
|
|
||||||
Class: dnsmessage.ClassINET,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Answers: []dnsmessage.Resource{
|
|
||||||
{
|
|
||||||
Header: dnsmessage.ResourceHeader{
|
|
||||||
Name: mustNewName("foo.bar.example.com."),
|
|
||||||
Type: dnsmessage.TypeA,
|
|
||||||
Class: dnsmessage.ClassINET,
|
|
||||||
},
|
|
||||||
Body: &dnsmessage.AResource{A: [4]byte{127, 0, 0, 1}},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Header: dnsmessage.ResourceHeader{
|
|
||||||
Name: mustNewName("bar.example.com."),
|
|
||||||
Type: dnsmessage.TypeA,
|
|
||||||
Class: dnsmessage.ClassINET,
|
|
||||||
},
|
|
||||||
Body: &dnsmessage.AResource{A: [4]byte{127, 0, 0, 2}},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
buf, err := msg.Pack()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
wantName := "bar.example.com."
|
|
||||||
|
|
||||||
var p dnsmessage.Parser
|
|
||||||
if _, err := p.Start(buf); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for {
|
|
||||||
q, err := p.Question()
|
|
||||||
if err == dnsmessage.ErrSectionDone {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if q.Name.String() != wantName {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println("Found question for name", wantName)
|
|
||||||
if err := p.SkipAllQuestions(); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
var gotIPs []net.IP
|
|
||||||
for {
|
|
||||||
h, err := p.AnswerHeader()
|
|
||||||
if err == dnsmessage.ErrSectionDone {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (h.Type != dnsmessage.TypeA && h.Type != dnsmessage.TypeAAAA) || h.Class != dnsmessage.ClassINET {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if !strings.EqualFold(h.Name.String(), wantName) {
|
|
||||||
if err := p.SkipAnswer(); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
switch h.Type {
|
|
||||||
case dnsmessage.TypeA:
|
|
||||||
r, err := p.AResource()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
gotIPs = append(gotIPs, r.A[:])
|
|
||||||
case dnsmessage.TypeAAAA:
|
|
||||||
r, err := p.AAAAResource()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
gotIPs = append(gotIPs, r.AAAA[:])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("Found A/AAAA records for name %s: %v\n", wantName, gotIPs)
|
|
||||||
|
|
||||||
// Output:
|
|
||||||
// Found question for name bar.example.com.
|
|
||||||
// Found A/AAAA records for name bar.example.com.: [127.0.0.2]
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,109 +0,0 @@
|
|||||||
// Copyright 2012 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package atom
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sort"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestKnown(t *testing.T) {
|
|
||||||
for _, s := range testAtomList {
|
|
||||||
if atom := Lookup([]byte(s)); atom.String() != s {
|
|
||||||
t.Errorf("Lookup(%q) = %#x (%q)", s, uint32(atom), atom.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHits(t *testing.T) {
|
|
||||||
for _, a := range table {
|
|
||||||
if a == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
got := Lookup([]byte(a.String()))
|
|
||||||
if got != a {
|
|
||||||
t.Errorf("Lookup(%q) = %#x, want %#x", a.String(), uint32(got), uint32(a))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMisses(t *testing.T) {
|
|
||||||
testCases := []string{
|
|
||||||
"",
|
|
||||||
"\x00",
|
|
||||||
"\xff",
|
|
||||||
"A",
|
|
||||||
"DIV",
|
|
||||||
"Div",
|
|
||||||
"dIV",
|
|
||||||
"aa",
|
|
||||||
"a\x00",
|
|
||||||
"ab",
|
|
||||||
"abb",
|
|
||||||
"abbr0",
|
|
||||||
"abbr ",
|
|
||||||
" abbr",
|
|
||||||
" a",
|
|
||||||
"acceptcharset",
|
|
||||||
"acceptCharset",
|
|
||||||
"accept_charset",
|
|
||||||
"h0",
|
|
||||||
"h1h2",
|
|
||||||
"h7",
|
|
||||||
"onClick",
|
|
||||||
"λ",
|
|
||||||
// The following string has the same hash (0xa1d7fab7) as "onmouseover".
|
|
||||||
"\x00\x00\x00\x00\x00\x50\x18\xae\x38\xd0\xb7",
|
|
||||||
}
|
|
||||||
for _, tc := range testCases {
|
|
||||||
got := Lookup([]byte(tc))
|
|
||||||
if got != 0 {
|
|
||||||
t.Errorf("Lookup(%q): got %d, want 0", tc, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestForeignObject(t *testing.T) {
|
|
||||||
const (
|
|
||||||
afo = Foreignobject
|
|
||||||
afO = ForeignObject
|
|
||||||
sfo = "foreignobject"
|
|
||||||
sfO = "foreignObject"
|
|
||||||
)
|
|
||||||
if got := Lookup([]byte(sfo)); got != afo {
|
|
||||||
t.Errorf("Lookup(%q): got %#v, want %#v", sfo, got, afo)
|
|
||||||
}
|
|
||||||
if got := Lookup([]byte(sfO)); got != afO {
|
|
||||||
t.Errorf("Lookup(%q): got %#v, want %#v", sfO, got, afO)
|
|
||||||
}
|
|
||||||
if got := afo.String(); got != sfo {
|
|
||||||
t.Errorf("Atom(%#v).String(): got %q, want %q", afo, got, sfo)
|
|
||||||
}
|
|
||||||
if got := afO.String(); got != sfO {
|
|
||||||
t.Errorf("Atom(%#v).String(): got %q, want %q", afO, got, sfO)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkLookup(b *testing.B) {
|
|
||||||
sortedTable := make([]string, 0, len(table))
|
|
||||||
for _, a := range table {
|
|
||||||
if a != 0 {
|
|
||||||
sortedTable = append(sortedTable, a.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sort.Strings(sortedTable)
|
|
||||||
|
|
||||||
x := make([][]byte, 1000)
|
|
||||||
for i := range x {
|
|
||||||
x[i] = []byte(sortedTable[i%len(sortedTable)])
|
|
||||||
}
|
|
||||||
|
|
||||||
b.ResetTimer()
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
for _, s := range x {
|
|
||||||
Lookup(s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,376 +0,0 @@
|
|||||||
// Code generated by go generate gen.go; DO NOT EDIT.
|
|
||||||
|
|
||||||
//go:generate go run gen.go -test
|
|
||||||
|
|
||||||
package atom
|
|
||||||
|
|
||||||
var testAtomList = []string{
|
|
||||||
"a",
|
|
||||||
"abbr",
|
|
||||||
"accept",
|
|
||||||
"accept-charset",
|
|
||||||
"accesskey",
|
|
||||||
"acronym",
|
|
||||||
"action",
|
|
||||||
"address",
|
|
||||||
"align",
|
|
||||||
"allowfullscreen",
|
|
||||||
"allowpaymentrequest",
|
|
||||||
"allowusermedia",
|
|
||||||
"alt",
|
|
||||||
"annotation",
|
|
||||||
"annotation-xml",
|
|
||||||
"applet",
|
|
||||||
"area",
|
|
||||||
"article",
|
|
||||||
"as",
|
|
||||||
"aside",
|
|
||||||
"async",
|
|
||||||
"audio",
|
|
||||||
"autocomplete",
|
|
||||||
"autofocus",
|
|
||||||
"autoplay",
|
|
||||||
"b",
|
|
||||||
"base",
|
|
||||||
"basefont",
|
|
||||||
"bdi",
|
|
||||||
"bdo",
|
|
||||||
"bgsound",
|
|
||||||
"big",
|
|
||||||
"blink",
|
|
||||||
"blockquote",
|
|
||||||
"body",
|
|
||||||
"br",
|
|
||||||
"button",
|
|
||||||
"canvas",
|
|
||||||
"caption",
|
|
||||||
"center",
|
|
||||||
"challenge",
|
|
||||||
"charset",
|
|
||||||
"checked",
|
|
||||||
"cite",
|
|
||||||
"class",
|
|
||||||
"code",
|
|
||||||
"col",
|
|
||||||
"colgroup",
|
|
||||||
"color",
|
|
||||||
"cols",
|
|
||||||
"colspan",
|
|
||||||
"command",
|
|
||||||
"content",
|
|
||||||
"contenteditable",
|
|
||||||
"contextmenu",
|
|
||||||
"controls",
|
|
||||||
"coords",
|
|
||||||
"crossorigin",
|
|
||||||
"data",
|
|
||||||
"datalist",
|
|
||||||
"datetime",
|
|
||||||
"dd",
|
|
||||||
"default",
|
|
||||||
"defer",
|
|
||||||
"del",
|
|
||||||
"desc",
|
|
||||||
"details",
|
|
||||||
"dfn",
|
|
||||||
"dialog",
|
|
||||||
"dir",
|
|
||||||
"dirname",
|
|
||||||
"disabled",
|
|
||||||
"div",
|
|
||||||
"dl",
|
|
||||||
"download",
|
|
||||||
"draggable",
|
|
||||||
"dropzone",
|
|
||||||
"dt",
|
|
||||||
"em",
|
|
||||||
"embed",
|
|
||||||
"enctype",
|
|
||||||
"face",
|
|
||||||
"fieldset",
|
|
||||||
"figcaption",
|
|
||||||
"figure",
|
|
||||||
"font",
|
|
||||||
"footer",
|
|
||||||
"for",
|
|
||||||
"foreignObject",
|
|
||||||
"foreignobject",
|
|
||||||
"form",
|
|
||||||
"formaction",
|
|
||||||
"formenctype",
|
|
||||||
"formmethod",
|
|
||||||
"formnovalidate",
|
|
||||||
"formtarget",
|
|
||||||
"frame",
|
|
||||||
"frameset",
|
|
||||||
"h1",
|
|
||||||
"h2",
|
|
||||||
"h3",
|
|
||||||
"h4",
|
|
||||||
"h5",
|
|
||||||
"h6",
|
|
||||||
"head",
|
|
||||||
"header",
|
|
||||||
"headers",
|
|
||||||
"height",
|
|
||||||
"hgroup",
|
|
||||||
"hidden",
|
|
||||||
"high",
|
|
||||||
"hr",
|
|
||||||
"href",
|
|
||||||
"hreflang",
|
|
||||||
"html",
|
|
||||||
"http-equiv",
|
|
||||||
"i",
|
|
||||||
"icon",
|
|
||||||
"id",
|
|
||||||
"iframe",
|
|
||||||
"image",
|
|
||||||
"img",
|
|
||||||
"input",
|
|
||||||
"inputmode",
|
|
||||||
"ins",
|
|
||||||
"integrity",
|
|
||||||
"is",
|
|
||||||
"isindex",
|
|
||||||
"ismap",
|
|
||||||
"itemid",
|
|
||||||
"itemprop",
|
|
||||||
"itemref",
|
|
||||||
"itemscope",
|
|
||||||
"itemtype",
|
|
||||||
"kbd",
|
|
||||||
"keygen",
|
|
||||||
"keytype",
|
|
||||||
"kind",
|
|
||||||
"label",
|
|
||||||
"lang",
|
|
||||||
"legend",
|
|
||||||
"li",
|
|
||||||
"link",
|
|
||||||
"list",
|
|
||||||
"listing",
|
|
||||||
"loop",
|
|
||||||
"low",
|
|
||||||
"main",
|
|
||||||
"malignmark",
|
|
||||||
"manifest",
|
|
||||||
"map",
|
|
||||||
"mark",
|
|
||||||
"marquee",
|
|
||||||
"math",
|
|
||||||
"max",
|
|
||||||
"maxlength",
|
|
||||||
"media",
|
|
||||||
"mediagroup",
|
|
||||||
"menu",
|
|
||||||
"menuitem",
|
|
||||||
"meta",
|
|
||||||
"meter",
|
|
||||||
"method",
|
|
||||||
"mglyph",
|
|
||||||
"mi",
|
|
||||||
"min",
|
|
||||||
"minlength",
|
|
||||||
"mn",
|
|
||||||
"mo",
|
|
||||||
"ms",
|
|
||||||
"mtext",
|
|
||||||
"multiple",
|
|
||||||
"muted",
|
|
||||||
"name",
|
|
||||||
"nav",
|
|
||||||
"nobr",
|
|
||||||
"noembed",
|
|
||||||
"noframes",
|
|
||||||
"nomodule",
|
|
||||||
"nonce",
|
|
||||||
"noscript",
|
|
||||||
"novalidate",
|
|
||||||
"object",
|
|
||||||
"ol",
|
|
||||||
"onabort",
|
|
||||||
"onafterprint",
|
|
||||||
"onautocomplete",
|
|
||||||
"onautocompleteerror",
|
|
||||||
"onauxclick",
|
|
||||||
"onbeforeprint",
|
|
||||||
"onbeforeunload",
|
|
||||||
"onblur",
|
|
||||||
"oncancel",
|
|
||||||
"oncanplay",
|
|
||||||
"oncanplaythrough",
|
|
||||||
"onchange",
|
|
||||||
"onclick",
|
|
||||||
"onclose",
|
|
||||||
"oncontextmenu",
|
|
||||||
"oncopy",
|
|
||||||
"oncuechange",
|
|
||||||
"oncut",
|
|
||||||
"ondblclick",
|
|
||||||
"ondrag",
|
|
||||||
"ondragend",
|
|
||||||
"ondragenter",
|
|
||||||
"ondragexit",
|
|
||||||
"ondragleave",
|
|
||||||
"ondragover",
|
|
||||||
"ondragstart",
|
|
||||||
"ondrop",
|
|
||||||
"ondurationchange",
|
|
||||||
"onemptied",
|
|
||||||
"onended",
|
|
||||||
"onerror",
|
|
||||||
"onfocus",
|
|
||||||
"onhashchange",
|
|
||||||
"oninput",
|
|
||||||
"oninvalid",
|
|
||||||
"onkeydown",
|
|
||||||
"onkeypress",
|
|
||||||
"onkeyup",
|
|
||||||
"onlanguagechange",
|
|
||||||
"onload",
|
|
||||||
"onloadeddata",
|
|
||||||
"onloadedmetadata",
|
|
||||||
"onloadend",
|
|
||||||
"onloadstart",
|
|
||||||
"onmessage",
|
|
||||||
"onmessageerror",
|
|
||||||
"onmousedown",
|
|
||||||
"onmouseenter",
|
|
||||||
"onmouseleave",
|
|
||||||
"onmousemove",
|
|
||||||
"onmouseout",
|
|
||||||
"onmouseover",
|
|
||||||
"onmouseup",
|
|
||||||
"onmousewheel",
|
|
||||||
"onoffline",
|
|
||||||
"ononline",
|
|
||||||
"onpagehide",
|
|
||||||
"onpageshow",
|
|
||||||
"onpaste",
|
|
||||||
"onpause",
|
|
||||||
"onplay",
|
|
||||||
"onplaying",
|
|
||||||
"onpopstate",
|
|
||||||
"onprogress",
|
|
||||||
"onratechange",
|
|
||||||
"onrejectionhandled",
|
|
||||||
"onreset",
|
|
||||||
"onresize",
|
|
||||||
"onscroll",
|
|
||||||
"onsecuritypolicyviolation",
|
|
||||||
"onseeked",
|
|
||||||
"onseeking",
|
|
||||||
"onselect",
|
|
||||||
"onshow",
|
|
||||||
"onsort",
|
|
||||||
"onstalled",
|
|
||||||
"onstorage",
|
|
||||||
"onsubmit",
|
|
||||||
"onsuspend",
|
|
||||||
"ontimeupdate",
|
|
||||||
"ontoggle",
|
|
||||||
"onunhandledrejection",
|
|
||||||
"onunload",
|
|
||||||
"onvolumechange",
|
|
||||||
"onwaiting",
|
|
||||||
"onwheel",
|
|
||||||
"open",
|
|
||||||
"optgroup",
|
|
||||||
"optimum",
|
|
||||||
"option",
|
|
||||||
"output",
|
|
||||||
"p",
|
|
||||||
"param",
|
|
||||||
"pattern",
|
|
||||||
"picture",
|
|
||||||
"ping",
|
|
||||||
"placeholder",
|
|
||||||
"plaintext",
|
|
||||||
"playsinline",
|
|
||||||
"poster",
|
|
||||||
"pre",
|
|
||||||
"preload",
|
|
||||||
"progress",
|
|
||||||
"prompt",
|
|
||||||
"public",
|
|
||||||
"q",
|
|
||||||
"radiogroup",
|
|
||||||
"rb",
|
|
||||||
"readonly",
|
|
||||||
"referrerpolicy",
|
|
||||||
"rel",
|
|
||||||
"required",
|
|
||||||
"reversed",
|
|
||||||
"rows",
|
|
||||||
"rowspan",
|
|
||||||
"rp",
|
|
||||||
"rt",
|
|
||||||
"rtc",
|
|
||||||
"ruby",
|
|
||||||
"s",
|
|
||||||
"samp",
|
|
||||||
"sandbox",
|
|
||||||
"scope",
|
|
||||||
"scoped",
|
|
||||||
"script",
|
|
||||||
"seamless",
|
|
||||||
"section",
|
|
||||||
"select",
|
|
||||||
"selected",
|
|
||||||
"shape",
|
|
||||||
"size",
|
|
||||||
"sizes",
|
|
||||||
"slot",
|
|
||||||
"small",
|
|
||||||
"sortable",
|
|
||||||
"sorted",
|
|
||||||
"source",
|
|
||||||
"spacer",
|
|
||||||
"span",
|
|
||||||
"spellcheck",
|
|
||||||
"src",
|
|
||||||
"srcdoc",
|
|
||||||
"srclang",
|
|
||||||
"srcset",
|
|
||||||
"start",
|
|
||||||
"step",
|
|
||||||
"strike",
|
|
||||||
"strong",
|
|
||||||
"style",
|
|
||||||
"sub",
|
|
||||||
"summary",
|
|
||||||
"sup",
|
|
||||||
"svg",
|
|
||||||
"system",
|
|
||||||
"tabindex",
|
|
||||||
"table",
|
|
||||||
"target",
|
|
||||||
"tbody",
|
|
||||||
"td",
|
|
||||||
"template",
|
|
||||||
"textarea",
|
|
||||||
"tfoot",
|
|
||||||
"th",
|
|
||||||
"thead",
|
|
||||||
"time",
|
|
||||||
"title",
|
|
||||||
"tr",
|
|
||||||
"track",
|
|
||||||
"translate",
|
|
||||||
"tt",
|
|
||||||
"type",
|
|
||||||
"typemustmatch",
|
|
||||||
"u",
|
|
||||||
"ul",
|
|
||||||
"updateviacache",
|
|
||||||
"usemap",
|
|
||||||
"value",
|
|
||||||
"var",
|
|
||||||
"video",
|
|
||||||
"wbr",
|
|
||||||
"width",
|
|
||||||
"workertype",
|
|
||||||
"wrap",
|
|
||||||
"xmp",
|
|
||||||
}
|
|
@ -1,257 +0,0 @@
|
|||||||
// Copyright 2013 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// Package charset provides common text encodings for HTML documents.
|
|
||||||
//
|
|
||||||
// The mapping from encoding labels to encodings is defined at
|
|
||||||
// https://encoding.spec.whatwg.org/.
|
|
||||||
package charset // import "golang.org/x/net/html/charset"
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"mime"
|
|
||||||
"strings"
|
|
||||||
"unicode/utf8"
|
|
||||||
|
|
||||||
"golang.org/x/net/html"
|
|
||||||
"golang.org/x/text/encoding"
|
|
||||||
"golang.org/x/text/encoding/charmap"
|
|
||||||
"golang.org/x/text/encoding/htmlindex"
|
|
||||||
"golang.org/x/text/transform"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Lookup returns the encoding with the specified label, and its canonical
|
|
||||||
// name. It returns nil and the empty string if label is not one of the
|
|
||||||
// standard encodings for HTML. Matching is case-insensitive and ignores
|
|
||||||
// leading and trailing whitespace. Encoders will use HTML escape sequences for
|
|
||||||
// runes that are not supported by the character set.
|
|
||||||
func Lookup(label string) (e encoding.Encoding, name string) {
|
|
||||||
e, err := htmlindex.Get(label)
|
|
||||||
if err != nil {
|
|
||||||
return nil, ""
|
|
||||||
}
|
|
||||||
name, _ = htmlindex.Name(e)
|
|
||||||
return &htmlEncoding{e}, name
|
|
||||||
}
|
|
||||||
|
|
||||||
type htmlEncoding struct{ encoding.Encoding }
|
|
||||||
|
|
||||||
func (h *htmlEncoding) NewEncoder() *encoding.Encoder {
|
|
||||||
// HTML requires a non-terminating legacy encoder. We use HTML escapes to
|
|
||||||
// substitute unsupported code points.
|
|
||||||
return encoding.HTMLEscapeUnsupported(h.Encoding.NewEncoder())
|
|
||||||
}
|
|
||||||
|
|
||||||
// DetermineEncoding determines the encoding of an HTML document by examining
|
|
||||||
// up to the first 1024 bytes of content and the declared Content-Type.
|
|
||||||
//
|
|
||||||
// See http://www.whatwg.org/specs/web-apps/current-work/multipage/parsing.html#determining-the-character-encoding
|
|
||||||
func DetermineEncoding(content []byte, contentType string) (e encoding.Encoding, name string, certain bool) {
|
|
||||||
if len(content) > 1024 {
|
|
||||||
content = content[:1024]
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, b := range boms {
|
|
||||||
if bytes.HasPrefix(content, b.bom) {
|
|
||||||
e, name = Lookup(b.enc)
|
|
||||||
return e, name, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, params, err := mime.ParseMediaType(contentType); err == nil {
|
|
||||||
if cs, ok := params["charset"]; ok {
|
|
||||||
if e, name = Lookup(cs); e != nil {
|
|
||||||
return e, name, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(content) > 0 {
|
|
||||||
e, name = prescan(content)
|
|
||||||
if e != nil {
|
|
||||||
return e, name, false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to detect UTF-8.
|
|
||||||
// First eliminate any partial rune at the end.
|
|
||||||
for i := len(content) - 1; i >= 0 && i > len(content)-4; i-- {
|
|
||||||
b := content[i]
|
|
||||||
if b < 0x80 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if utf8.RuneStart(b) {
|
|
||||||
content = content[:i]
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
hasHighBit := false
|
|
||||||
for _, c := range content {
|
|
||||||
if c >= 0x80 {
|
|
||||||
hasHighBit = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if hasHighBit && utf8.Valid(content) {
|
|
||||||
return encoding.Nop, "utf-8", false
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: change default depending on user's locale?
|
|
||||||
return charmap.Windows1252, "windows-1252", false
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewReader returns an io.Reader that converts the content of r to UTF-8.
|
|
||||||
// It calls DetermineEncoding to find out what r's encoding is.
|
|
||||||
func NewReader(r io.Reader, contentType string) (io.Reader, error) {
|
|
||||||
preview := make([]byte, 1024)
|
|
||||||
n, err := io.ReadFull(r, preview)
|
|
||||||
switch {
|
|
||||||
case err == io.ErrUnexpectedEOF:
|
|
||||||
preview = preview[:n]
|
|
||||||
r = bytes.NewReader(preview)
|
|
||||||
case err != nil:
|
|
||||||
return nil, err
|
|
||||||
default:
|
|
||||||
r = io.MultiReader(bytes.NewReader(preview), r)
|
|
||||||
}
|
|
||||||
|
|
||||||
if e, _, _ := DetermineEncoding(preview, contentType); e != encoding.Nop {
|
|
||||||
r = transform.NewReader(r, e.NewDecoder())
|
|
||||||
}
|
|
||||||
return r, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewReaderLabel returns a reader that converts from the specified charset to
|
|
||||||
// UTF-8. It uses Lookup to find the encoding that corresponds to label, and
|
|
||||||
// returns an error if Lookup returns nil. It is suitable for use as
|
|
||||||
// encoding/xml.Decoder's CharsetReader function.
|
|
||||||
func NewReaderLabel(label string, input io.Reader) (io.Reader, error) {
|
|
||||||
e, _ := Lookup(label)
|
|
||||||
if e == nil {
|
|
||||||
return nil, fmt.Errorf("unsupported charset: %q", label)
|
|
||||||
}
|
|
||||||
return transform.NewReader(input, e.NewDecoder()), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func prescan(content []byte) (e encoding.Encoding, name string) {
|
|
||||||
z := html.NewTokenizer(bytes.NewReader(content))
|
|
||||||
for {
|
|
||||||
switch z.Next() {
|
|
||||||
case html.ErrorToken:
|
|
||||||
return nil, ""
|
|
||||||
|
|
||||||
case html.StartTagToken, html.SelfClosingTagToken:
|
|
||||||
tagName, hasAttr := z.TagName()
|
|
||||||
if !bytes.Equal(tagName, []byte("meta")) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
attrList := make(map[string]bool)
|
|
||||||
gotPragma := false
|
|
||||||
|
|
||||||
const (
|
|
||||||
dontKnow = iota
|
|
||||||
doNeedPragma
|
|
||||||
doNotNeedPragma
|
|
||||||
)
|
|
||||||
needPragma := dontKnow
|
|
||||||
|
|
||||||
name = ""
|
|
||||||
e = nil
|
|
||||||
for hasAttr {
|
|
||||||
var key, val []byte
|
|
||||||
key, val, hasAttr = z.TagAttr()
|
|
||||||
ks := string(key)
|
|
||||||
if attrList[ks] {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
attrList[ks] = true
|
|
||||||
for i, c := range val {
|
|
||||||
if 'A' <= c && c <= 'Z' {
|
|
||||||
val[i] = c + 0x20
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch ks {
|
|
||||||
case "http-equiv":
|
|
||||||
if bytes.Equal(val, []byte("content-type")) {
|
|
||||||
gotPragma = true
|
|
||||||
}
|
|
||||||
|
|
||||||
case "content":
|
|
||||||
if e == nil {
|
|
||||||
name = fromMetaElement(string(val))
|
|
||||||
if name != "" {
|
|
||||||
e, name = Lookup(name)
|
|
||||||
if e != nil {
|
|
||||||
needPragma = doNeedPragma
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case "charset":
|
|
||||||
e, name = Lookup(string(val))
|
|
||||||
needPragma = doNotNeedPragma
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if needPragma == dontKnow || needPragma == doNeedPragma && !gotPragma {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.HasPrefix(name, "utf-16") {
|
|
||||||
name = "utf-8"
|
|
||||||
e = encoding.Nop
|
|
||||||
}
|
|
||||||
|
|
||||||
if e != nil {
|
|
||||||
return e, name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func fromMetaElement(s string) string {
|
|
||||||
for s != "" {
|
|
||||||
csLoc := strings.Index(s, "charset")
|
|
||||||
if csLoc == -1 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
s = s[csLoc+len("charset"):]
|
|
||||||
s = strings.TrimLeft(s, " \t\n\f\r")
|
|
||||||
if !strings.HasPrefix(s, "=") {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
s = s[1:]
|
|
||||||
s = strings.TrimLeft(s, " \t\n\f\r")
|
|
||||||
if s == "" {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
if q := s[0]; q == '"' || q == '\'' {
|
|
||||||
s = s[1:]
|
|
||||||
closeQuote := strings.IndexRune(s, rune(q))
|
|
||||||
if closeQuote == -1 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return s[:closeQuote]
|
|
||||||
}
|
|
||||||
|
|
||||||
end := strings.IndexAny(s, "; \t\n\f\r")
|
|
||||||
if end == -1 {
|
|
||||||
end = len(s)
|
|
||||||
}
|
|
||||||
return s[:end]
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
var boms = []struct {
|
|
||||||
bom []byte
|
|
||||||
enc string
|
|
||||||
}{
|
|
||||||
{[]byte{0xfe, 0xff}, "utf-16be"},
|
|
||||||
{[]byte{0xff, 0xfe}, "utf-16le"},
|
|
||||||
{[]byte{0xef, 0xbb, 0xbf}, "utf-8"},
|
|
||||||
}
|
|
@ -1,237 +0,0 @@
|
|||||||
// Copyright 2013 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package charset
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/xml"
|
|
||||||
"io/ioutil"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"golang.org/x/text/transform"
|
|
||||||
)
|
|
||||||
|
|
||||||
func transformString(t transform.Transformer, s string) (string, error) {
|
|
||||||
r := transform.NewReader(strings.NewReader(s), t)
|
|
||||||
b, err := ioutil.ReadAll(r)
|
|
||||||
return string(b), err
|
|
||||||
}
|
|
||||||
|
|
||||||
type testCase struct {
|
|
||||||
utf8, other, otherEncoding string
|
|
||||||
}
|
|
||||||
|
|
||||||
// testCases for encoding and decoding.
|
|
||||||
var testCases = []testCase{
|
|
||||||
{"Résumé", "Résumé", "utf8"},
|
|
||||||
{"Résumé", "R\xe9sum\xe9", "latin1"},
|
|
||||||
{"これは漢字です。", "S0\x8c0o0\"oW[g0Y0\x020", "UTF-16LE"},
|
|
||||||
{"これは漢字です。", "0S0\x8c0oo\"[W0g0Y0\x02", "UTF-16BE"},
|
|
||||||
{"Hello, world", "Hello, world", "ASCII"},
|
|
||||||
{"Gdańsk", "Gda\xf1sk", "ISO-8859-2"},
|
|
||||||
{"Ââ Čč Đđ Ŋŋ Õõ Šš Žž Åå Ää", "\xc2\xe2 \xc8\xe8 \xa9\xb9 \xaf\xbf \xd5\xf5 \xaa\xba \xac\xbc \xc5\xe5 \xc4\xe4", "ISO-8859-10"},
|
|
||||||
{"สำหรับ", "\xca\xd3\xcb\xc3\u047a", "ISO-8859-11"},
|
|
||||||
{"latviešu", "latvie\xf0u", "ISO-8859-13"},
|
|
||||||
{"Seònaid", "Se\xf2naid", "ISO-8859-14"},
|
|
||||||
{"€1 is cheap", "\xa41 is cheap", "ISO-8859-15"},
|
|
||||||
{"românește", "rom\xe2ne\xbate", "ISO-8859-16"},
|
|
||||||
{"nutraĵo", "nutra\xbco", "ISO-8859-3"},
|
|
||||||
{"Kalâdlit", "Kal\xe2dlit", "ISO-8859-4"},
|
|
||||||
{"русский", "\xe0\xe3\xe1\xe1\xda\xd8\xd9", "ISO-8859-5"},
|
|
||||||
{"ελληνικά", "\xe5\xeb\xeb\xe7\xed\xe9\xea\xdc", "ISO-8859-7"},
|
|
||||||
{"Kağan", "Ka\xf0an", "ISO-8859-9"},
|
|
||||||
{"Résumé", "R\x8esum\x8e", "macintosh"},
|
|
||||||
{"Gdańsk", "Gda\xf1sk", "windows-1250"},
|
|
||||||
{"русский", "\xf0\xf3\xf1\xf1\xea\xe8\xe9", "windows-1251"},
|
|
||||||
{"Résumé", "R\xe9sum\xe9", "windows-1252"},
|
|
||||||
{"ελληνικά", "\xe5\xeb\xeb\xe7\xed\xe9\xea\xdc", "windows-1253"},
|
|
||||||
{"Kağan", "Ka\xf0an", "windows-1254"},
|
|
||||||
{"עִבְרִית", "\xf2\xc4\xe1\xc0\xf8\xc4\xe9\xfa", "windows-1255"},
|
|
||||||
{"العربية", "\xc7\xe1\xda\xd1\xc8\xed\xc9", "windows-1256"},
|
|
||||||
{"latviešu", "latvie\xf0u", "windows-1257"},
|
|
||||||
{"Việt", "Vi\xea\xf2t", "windows-1258"},
|
|
||||||
{"สำหรับ", "\xca\xd3\xcb\xc3\u047a", "windows-874"},
|
|
||||||
{"русский", "\xd2\xd5\xd3\xd3\xcb\xc9\xca", "KOI8-R"},
|
|
||||||
{"українська", "\xd5\xcb\xd2\xc1\xa7\xce\xd3\xd8\xcb\xc1", "KOI8-U"},
|
|
||||||
{"Hello 常用國字標準字體表", "Hello \xb1`\xa5\u03b0\xea\xa6r\xbc\u0437\u01e6r\xc5\xe9\xaa\xed", "big5"},
|
|
||||||
{"Hello 常用國字標準字體表", "Hello \xb3\xa3\xd3\xc3\x87\xf8\xd7\xd6\x98\xcb\x9c\xca\xd7\xd6\xf3\x77\xb1\xed", "gbk"},
|
|
||||||
{"Hello 常用國字標準字體表", "Hello \xb3\xa3\xd3\xc3\x87\xf8\xd7\xd6\x98\xcb\x9c\xca\xd7\xd6\xf3\x77\xb1\xed", "gb18030"},
|
|
||||||
{"עִבְרִית", "\x81\x30\xfb\x30\x81\x30\xf6\x34\x81\x30\xf9\x33\x81\x30\xf6\x30\x81\x30\xfb\x36\x81\x30\xf6\x34\x81\x30\xfa\x31\x81\x30\xfb\x38", "gb18030"},
|
|
||||||
{"㧯", "\x82\x31\x89\x38", "gb18030"},
|
|
||||||
{"これは漢字です。", "\x82\xb1\x82\xea\x82\xcd\x8a\xbf\x8e\x9a\x82\xc5\x82\xb7\x81B", "SJIS"},
|
|
||||||
{"Hello, 世界!", "Hello, \x90\xa2\x8aE!", "SJIS"},
|
|
||||||
{"イウエオカ", "\xb2\xb3\xb4\xb5\xb6", "SJIS"},
|
|
||||||
{"これは漢字です。", "\xa4\xb3\xa4\xec\xa4\u03f4\xc1\xbb\xfa\xa4\u01e4\xb9\xa1\xa3", "EUC-JP"},
|
|
||||||
{"Hello, 世界!", "Hello, \x1b$B@$3&\x1b(B!", "ISO-2022-JP"},
|
|
||||||
{"다음과 같은 조건을 따라야 합니다: 저작자표시", "\xb4\xd9\xc0\xbd\xb0\xfa \xb0\xb0\xc0\xba \xc1\xb6\xb0\xc7\xc0\xbb \xb5\xfb\xb6\xf3\xbe\xdf \xc7մϴ\xd9: \xc0\xfa\xc0\xdb\xc0\xdaǥ\xbd\xc3", "EUC-KR"},
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDecode(t *testing.T) {
|
|
||||||
testCases := append(testCases, []testCase{
|
|
||||||
// Replace multi-byte maximum subpart of ill-formed subsequence with
|
|
||||||
// single replacement character (WhatWG requirement).
|
|
||||||
{"Rés\ufffdumé", "Rés\xe1\x80umé", "utf8"},
|
|
||||||
}...)
|
|
||||||
for _, tc := range testCases {
|
|
||||||
e, _ := Lookup(tc.otherEncoding)
|
|
||||||
if e == nil {
|
|
||||||
t.Errorf("%s: not found", tc.otherEncoding)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
s, err := transformString(e.NewDecoder(), tc.other)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("%s: decode %q: %v", tc.otherEncoding, tc.other, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if s != tc.utf8 {
|
|
||||||
t.Errorf("%s: got %q, want %q", tc.otherEncoding, s, tc.utf8)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEncode(t *testing.T) {
|
|
||||||
testCases := append(testCases, []testCase{
|
|
||||||
// Use Go-style replacement.
|
|
||||||
{"Rés\xe1\x80umé", "Rés\ufffd\ufffdumé", "utf8"},
|
|
||||||
// U+0144 LATIN SMALL LETTER N WITH ACUTE not supported by encoding.
|
|
||||||
{"Gdańsk", "Gdańsk", "ISO-8859-11"},
|
|
||||||
{"\ufffd", "�", "ISO-8859-11"},
|
|
||||||
{"a\xe1\x80b", "a��b", "ISO-8859-11"},
|
|
||||||
}...)
|
|
||||||
for _, tc := range testCases {
|
|
||||||
e, _ := Lookup(tc.otherEncoding)
|
|
||||||
if e == nil {
|
|
||||||
t.Errorf("%s: not found", tc.otherEncoding)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
s, err := transformString(e.NewEncoder(), tc.utf8)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("%s: encode %q: %s", tc.otherEncoding, tc.utf8, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if s != tc.other {
|
|
||||||
t.Errorf("%s: got %q, want %q", tc.otherEncoding, s, tc.other)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var sniffTestCases = []struct {
|
|
||||||
filename, declared, want string
|
|
||||||
}{
|
|
||||||
{"HTTP-charset.html", "text/html; charset=iso-8859-15", "iso-8859-15"},
|
|
||||||
{"UTF-16LE-BOM.html", "", "utf-16le"},
|
|
||||||
{"UTF-16BE-BOM.html", "", "utf-16be"},
|
|
||||||
{"meta-content-attribute.html", "text/html", "iso-8859-15"},
|
|
||||||
{"meta-charset-attribute.html", "text/html", "iso-8859-15"},
|
|
||||||
{"No-encoding-declaration.html", "text/html", "utf-8"},
|
|
||||||
{"HTTP-vs-UTF-8-BOM.html", "text/html; charset=iso-8859-15", "utf-8"},
|
|
||||||
{"HTTP-vs-meta-content.html", "text/html; charset=iso-8859-15", "iso-8859-15"},
|
|
||||||
{"HTTP-vs-meta-charset.html", "text/html; charset=iso-8859-15", "iso-8859-15"},
|
|
||||||
{"UTF-8-BOM-vs-meta-content.html", "text/html", "utf-8"},
|
|
||||||
{"UTF-8-BOM-vs-meta-charset.html", "text/html", "utf-8"},
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSniff(t *testing.T) {
|
|
||||||
switch runtime.GOOS {
|
|
||||||
case "nacl": // platforms that don't permit direct file system access
|
|
||||||
t.Skipf("not supported on %q", runtime.GOOS)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range sniffTestCases {
|
|
||||||
content, err := ioutil.ReadFile("testdata/" + tc.filename)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("%s: error reading file: %v", tc.filename, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
_, name, _ := DetermineEncoding(content, tc.declared)
|
|
||||||
if name != tc.want {
|
|
||||||
t.Errorf("%s: got %q, want %q", tc.filename, name, tc.want)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestReader(t *testing.T) {
|
|
||||||
switch runtime.GOOS {
|
|
||||||
case "nacl": // platforms that don't permit direct file system access
|
|
||||||
t.Skipf("not supported on %q", runtime.GOOS)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range sniffTestCases {
|
|
||||||
content, err := ioutil.ReadFile("testdata/" + tc.filename)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("%s: error reading file: %v", tc.filename, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
r, err := NewReader(bytes.NewReader(content), tc.declared)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("%s: error creating reader: %v", tc.filename, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
got, err := ioutil.ReadAll(r)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("%s: error reading from charset.NewReader: %v", tc.filename, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
e, _ := Lookup(tc.want)
|
|
||||||
want, err := ioutil.ReadAll(transform.NewReader(bytes.NewReader(content), e.NewDecoder()))
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("%s: error decoding with hard-coded charset name: %v", tc.filename, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if !bytes.Equal(got, want) {
|
|
||||||
t.Errorf("%s: got %q, want %q", tc.filename, got, want)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var metaTestCases = []struct {
|
|
||||||
meta, want string
|
|
||||||
}{
|
|
||||||
{"", ""},
|
|
||||||
{"text/html", ""},
|
|
||||||
{"text/html; charset utf-8", ""},
|
|
||||||
{"text/html; charset=latin-2", "latin-2"},
|
|
||||||
{"text/html; charset; charset = utf-8", "utf-8"},
|
|
||||||
{`charset="big5"`, "big5"},
|
|
||||||
{"charset='shift_jis'", "shift_jis"},
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFromMeta(t *testing.T) {
|
|
||||||
for _, tc := range metaTestCases {
|
|
||||||
got := fromMetaElement(tc.meta)
|
|
||||||
if got != tc.want {
|
|
||||||
t.Errorf("%q: got %q, want %q", tc.meta, got, tc.want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestXML(t *testing.T) {
|
|
||||||
const s = "<?xml version=\"1.0\" encoding=\"windows-1252\"?><a><Word>r\xe9sum\xe9</Word></a>"
|
|
||||||
|
|
||||||
d := xml.NewDecoder(strings.NewReader(s))
|
|
||||||
d.CharsetReader = NewReaderLabel
|
|
||||||
|
|
||||||
var a struct {
|
|
||||||
Word string
|
|
||||||
}
|
|
||||||
err := d.Decode(&a)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Decode: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
want := "résumé"
|
|
||||||
if a.Word != want {
|
|
||||||
t.Errorf("got %q, want %q", a.Word, want)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,48 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en" >
|
|
||||||
<head>
|
|
||||||
<title>HTTP charset</title>
|
|
||||||
<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
|
|
||||||
<link rel='help' href='http://www.w3.org/TR/html5/syntax.html#the-input-byte-stream'>
|
|
||||||
<link rel="stylesheet" type="text/css" href="./generatedtests.css">
|
|
||||||
<script src="http://w3c-test.org/resources/testharness.js"></script>
|
|
||||||
<script src="http://w3c-test.org/resources/testharnessreport.js"></script>
|
|
||||||
<meta name='flags' content='http'>
|
|
||||||
<meta name="assert" content="The character encoding of a page can be set using the HTTP header charset declaration.">
|
|
||||||
<style type='text/css'>
|
|
||||||
.test div { width: 50px; }</style>
|
|
||||||
<link rel="stylesheet" type="text/css" href="the-input-byte-stream/support/encodingtests-15.css">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<p class='title'>HTTP charset</p>
|
|
||||||
|
|
||||||
|
|
||||||
<div id='log'></div>
|
|
||||||
|
|
||||||
|
|
||||||
<div class='test'><div id='box' class='ýäè'> </div></div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div class='description'>
|
|
||||||
<p class="assertion" title="Assertion">The character encoding of a page can be set using the HTTP header charset declaration.</p>
|
|
||||||
<div class="notes"><p><p>The test contains a div with a class name that contains the following sequence of bytes: 0xC3 0xBD 0xC3 0xA4 0xC3 0xA8. These represent different sequences of characters in ISO 8859-15, ISO 8859-1 and UTF-8. The external, UTF-8-encoded stylesheet contains a selector <code>.test div.ÜÀÚ</code>. This matches the sequence of bytes above when they are interpreted as ISO 8859-15. If the class name matches the selector then the test will pass.</p><p>The only character encoding declaration for this HTML file is in the HTTP header, which sets the encoding to ISO 8859-15.</p></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="nexttest"><div><a href="generate?test=the-input-byte-stream-003">Next test</a></div><div class="doctype">HTML5</div>
|
|
||||||
<p class="jump">the-input-byte-stream-001<br /><a href="/International/tests/html5/the-input-byte-stream/results-basics#basics" target="_blank">Result summary & related tests</a><br /><a href="http://w3c-test.org/framework/details/i18n-html5/the-input-byte-stream-001" target="_blank">Detailed results for this test</a><br/> <a href="http://www.w3.org/TR/html5/syntax.html#the-input-byte-stream" target="_blank">Link to spec</a></p>
|
|
||||||
<div class='prereq'>Assumptions: <ul><li>The default encoding for the browser you are testing is not set to ISO 8859-15.</li>
|
|
||||||
<li>The test is read from a server that supports HTTP.</li></ul></div>
|
|
||||||
</div>
|
|
||||||
<script>
|
|
||||||
test(function() {
|
|
||||||
assert_equals(document.getElementById('box').offsetWidth, 100);
|
|
||||||
}, " ");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
||||||
|
|
@ -1,49 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en" >
|
|
||||||
<head>
|
|
||||||
<meta charset="iso-8859-1" > <title>HTTP vs meta charset</title>
|
|
||||||
<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
|
|
||||||
<link rel='help' href='http://www.w3.org/TR/html5/syntax.html#the-input-byte-stream'>
|
|
||||||
<link rel="stylesheet" type="text/css" href="./generatedtests.css">
|
|
||||||
<script src="http://w3c-test.org/resources/testharness.js"></script>
|
|
||||||
<script src="http://w3c-test.org/resources/testharnessreport.js"></script>
|
|
||||||
<meta name='flags' content='http'>
|
|
||||||
<meta name="assert" content="The HTTP header has a higher precedence than an encoding declaration in a meta charset attribute.">
|
|
||||||
<style type='text/css'>
|
|
||||||
.test div { width: 50px; }.test div { width: 90px; }
|
|
||||||
</style>
|
|
||||||
<link rel="stylesheet" type="text/css" href="the-input-byte-stream/support/encodingtests-15.css">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<p class='title'>HTTP vs meta charset</p>
|
|
||||||
|
|
||||||
|
|
||||||
<div id='log'></div>
|
|
||||||
|
|
||||||
|
|
||||||
<div class='test'><div id='box' class='ýäè'> </div></div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div class='description'>
|
|
||||||
<p class="assertion" title="Assertion">The HTTP header has a higher precedence than an encoding declaration in a meta charset attribute.</p>
|
|
||||||
<div class="notes"><p><p>The HTTP header attempts to set the character encoding to ISO 8859-15. The page contains an encoding declaration in a meta charset attribute that attempts to set the character encoding to ISO 8859-1.</p><p>The test contains a div with a class name that contains the following sequence of bytes: 0xC3 0xBD 0xC3 0xA4 0xC3 0xA8. These represent different sequences of characters in ISO 8859-15, ISO 8859-1 and UTF-8. The external, UTF-8-encoded stylesheet contains a selector <code>.test div.ÜÀÚ</code>. This matches the sequence of bytes above when they are interpreted as ISO 8859-15. If the class name matches the selector then the test will pass.</p></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="nexttest"><div><a href="generate?test=the-input-byte-stream-037">Next test</a></div><div class="doctype">HTML5</div>
|
|
||||||
<p class="jump">the-input-byte-stream-018<br /><a href="/International/tests/html5/the-input-byte-stream/results-basics#precedence" target="_blank">Result summary & related tests</a><br /><a href="http://w3c-test.org/framework/details/i18n-html5/the-input-byte-stream-018" target="_blank">Detailed results for this test</a><br/> <a href="http://www.w3.org/TR/html5/syntax.html#the-input-byte-stream" target="_blank">Link to spec</a></p>
|
|
||||||
<div class='prereq'>Assumptions: <ul><li>The default encoding for the browser you are testing is not set to ISO 8859-15.</li>
|
|
||||||
<li>The test is read from a server that supports HTTP.</li></ul></div>
|
|
||||||
</div>
|
|
||||||
<script>
|
|
||||||
test(function() {
|
|
||||||
assert_equals(document.getElementById('box').offsetWidth, 100);
|
|
||||||
}, " ");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
||||||
|
|
@ -1,49 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en" >
|
|
||||||
<head>
|
|
||||||
<meta http-equiv="content-type" content="text/html;charset=iso-8859-1" > <title>HTTP vs meta content</title>
|
|
||||||
<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
|
|
||||||
<link rel='help' href='http://www.w3.org/TR/html5/syntax.html#the-input-byte-stream'>
|
|
||||||
<link rel="stylesheet" type="text/css" href="./generatedtests.css">
|
|
||||||
<script src="http://w3c-test.org/resources/testharness.js"></script>
|
|
||||||
<script src="http://w3c-test.org/resources/testharnessreport.js"></script>
|
|
||||||
<meta name='flags' content='http'>
|
|
||||||
<meta name="assert" content="The HTTP header has a higher precedence than an encoding declaration in a meta content attribute.">
|
|
||||||
<style type='text/css'>
|
|
||||||
.test div { width: 50px; }.test div { width: 90px; }
|
|
||||||
</style>
|
|
||||||
<link rel="stylesheet" type="text/css" href="the-input-byte-stream/support/encodingtests-15.css">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<p class='title'>HTTP vs meta content</p>
|
|
||||||
|
|
||||||
|
|
||||||
<div id='log'></div>
|
|
||||||
|
|
||||||
|
|
||||||
<div class='test'><div id='box' class='ýäè'> </div></div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div class='description'>
|
|
||||||
<p class="assertion" title="Assertion">The HTTP header has a higher precedence than an encoding declaration in a meta content attribute.</p>
|
|
||||||
<div class="notes"><p><p>The HTTP header attempts to set the character encoding to ISO 8859-15. The page contains an encoding declaration in a meta content attribute that attempts to set the character encoding to ISO 8859-1.</p><p>The test contains a div with a class name that contains the following sequence of bytes: 0xC3 0xBD 0xC3 0xA4 0xC3 0xA8. These represent different sequences of characters in ISO 8859-15, ISO 8859-1 and UTF-8. The external, UTF-8-encoded stylesheet contains a selector <code>.test div.ÜÀÚ</code>. This matches the sequence of bytes above when they are interpreted as ISO 8859-15. If the class name matches the selector then the test will pass.</p></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="nexttest"><div><a href="generate?test=the-input-byte-stream-018">Next test</a></div><div class="doctype">HTML5</div>
|
|
||||||
<p class="jump">the-input-byte-stream-016<br /><a href="/International/tests/html5/the-input-byte-stream/results-basics#precedence" target="_blank">Result summary & related tests</a><br /><a href="http://w3c-test.org/framework/details/i18n-html5/the-input-byte-stream-016" target="_blank">Detailed results for this test</a><br/> <a href="http://www.w3.org/TR/html5/syntax.html#the-input-byte-stream" target="_blank">Link to spec</a></p>
|
|
||||||
<div class='prereq'>Assumptions: <ul><li>The default encoding for the browser you are testing is not set to ISO 8859-15.</li>
|
|
||||||
<li>The test is read from a server that supports HTTP.</li></ul></div>
|
|
||||||
</div>
|
|
||||||
<script>
|
|
||||||
test(function() {
|
|
||||||
assert_equals(document.getElementById('box').offsetWidth, 100);
|
|
||||||
}, " ");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
||||||
|
|
@ -1,47 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en" >
|
|
||||||
<head>
|
|
||||||
<title>No encoding declaration</title>
|
|
||||||
<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
|
|
||||||
<link rel='help' href='http://www.w3.org/TR/html5/syntax.html#the-input-byte-stream'>
|
|
||||||
<link rel="stylesheet" type="text/css" href="./generatedtests.css">
|
|
||||||
<script src="http://w3c-test.org/resources/testharness.js"></script>
|
|
||||||
<script src="http://w3c-test.org/resources/testharnessreport.js"></script>
|
|
||||||
<meta name='flags' content='http'>
|
|
||||||
<meta name="assert" content="A page with no encoding information in HTTP, BOM, XML declaration or meta element will be treated as UTF-8.">
|
|
||||||
<style type='text/css'>
|
|
||||||
.test div { width: 50px; }</style>
|
|
||||||
<link rel="stylesheet" type="text/css" href="the-input-byte-stream/support/encodingtests-utf8.css">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<p class='title'>No encoding declaration</p>
|
|
||||||
|
|
||||||
|
|
||||||
<div id='log'></div>
|
|
||||||
|
|
||||||
|
|
||||||
<div class='test'><div id='box' class='ýäè'> </div></div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div class='description'>
|
|
||||||
<p class="assertion" title="Assertion">A page with no encoding information in HTTP, BOM, XML declaration or meta element will be treated as UTF-8.</p>
|
|
||||||
<div class="notes"><p><p>The test on this page contains a div with a class name that contains the following sequence of bytes: 0xC3 0xBD 0xC3 0xA4 0xC3 0xA8. These represent different sequences of characters in ISO 8859-15, ISO 8859-1 and UTF-8. The external, UTF-8-encoded stylesheet contains a selector <code>.test div.ýäè</code>. This matches the sequence of bytes above when they are interpreted as UTF-8. If the class name matches the selector then the test will pass.</p></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="nexttest"><div><a href="generate?test=the-input-byte-stream-034">Next test</a></div><div class="doctype">HTML5</div>
|
|
||||||
<p class="jump">the-input-byte-stream-015<br /><a href="/International/tests/html5/the-input-byte-stream/results-basics#basics" target="_blank">Result summary & related tests</a><br /><a href="http://w3c-test.org/framework/details/i18n-html5/the-input-byte-stream-015" target="_blank">Detailed results for this test</a><br/> <a href="http://www.w3.org/TR/html5/syntax.html#the-input-byte-stream" target="_blank">Link to spec</a></p>
|
|
||||||
<div class='prereq'>Assumptions: <ul><li>The test is read from a server that supports HTTP.</li></ul></div>
|
|
||||||
</div>
|
|
||||||
<script>
|
|
||||||
test(function() {
|
|
||||||
assert_equals(document.getElementById('box').offsetWidth, 100);
|
|
||||||
}, " ");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
|||||||
These test cases come from
|
|
||||||
http://www.w3.org/International/tests/repository/html5/the-input-byte-stream/results-basics
|
|
||||||
|
|
||||||
Distributed under both the W3C Test Suite License
|
|
||||||
(http://www.w3.org/Consortium/Legal/2008/04-testsuite-license)
|
|
||||||
and the W3C 3-clause BSD License
|
|
||||||
(http://www.w3.org/Consortium/Legal/2008/03-bsd-license).
|
|
||||||
To contribute to a W3C Test Suite, see the policies and contribution
|
|
||||||
forms (http://www.w3.org/2004/10/27-testcases).
|
|
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue