commit cbf886fb7e4ec31fb7212ca3be83fcb5bc9cdad2 Author: Bel LaPointe Date: Mon Feb 18 09:31:35 2019 -0700 stealing diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..96ef3e9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +lz4 +rclone +rcloner +Go +cloudly +dockfile +compile.sh +*.swp +*.swo +*pycache* +enc_conf diff --git a/main.go b/main.go new file mode 100644 index 0000000..d39e298 --- /dev/null +++ b/main.go @@ -0,0 +1,18 @@ +package main + +import ( + "flag" + "local/s2sa/s2sa/server" +) + +func main() { + flag.Parse() + + server := server.New("") + if err := server.Routes(); err != nil { + panic(err) + } + if err := server.Run(); err != nil { + panic(err) + } +} diff --git a/server/new.go b/server/new.go new file mode 100644 index 0000000..2da9d8b --- /dev/null +++ b/server/new.go @@ -0,0 +1,30 @@ +package server + +import ( + "local/s2sa/s2sa/server/router" + "local/s2sa/s2sa/services" + "local/s2sa/s2sa/storage" +) + +func New(path string) *Server { + var db storage.DB + db = storage.NewMap() + if len(path) > 0 { + var err error + db, err = storage.NewBolt(path) + if err != nil { + return nil + } + } + authdb := storage.NewMap() + s := &Server{ + db: services.New(db), + authdb: services.New(authdb), + router: router.New(), + addr: ":18341", + } + if err := s.authdb.Register(serverNS); err != nil { + panic(err) + } + return s +} diff --git a/server/new_test.go b/server/new_test.go new file mode 100644 index 0000000..97e766f --- /dev/null +++ b/server/new_test.go @@ -0,0 +1,11 @@ +package server + +import ( + "os" + "testing" +) + +func TestServerNew(t *testing.T) { + New("") + New(os.DevNull) +} diff --git a/server/routes.go b/server/routes.go new file mode 100644 index 0000000..78591dc --- /dev/null +++ b/server/routes.go @@ -0,0 +1,197 @@ +package server + +import ( + "encoding/json" + "local/s2sa/s2sa/server/router" + "local/s2sa/s2sa/token" + "net/http" + "path" + "strings" +) + +const clientsNS = "clients" +const accessorsNS = "accessors" +const wildcard = "{}" +const serverNS = "server" + +func (s *Server) Routes() error { + appendWildcards := func(s string, cnt int) string { + s = strings.Trim(s, "/") + return path.Join(s, strings.Repeat("/"+wildcard, cnt)) + } + paths := []struct { + base string + wildcards int + method http.HandlerFunc + }{ + { + base: "admin/register", + wildcards: 1, + method: s.adminRegister, + }, + { + base: "register", + wildcards: 1, + method: s.authenticate(s.registerClient), + }, + { + base: "generate", + wildcards: 2, + method: s.authenticate(s.generateToken), + }, + { + base: "retrieve", + wildcards: 3, + method: s.authenticate(s.retrieveToken), + }, + { + base: "revoke", + wildcards: 2, + method: s.authenticate(s.revokeToken), + }, + { + base: "lookup", + wildcards: 2, + method: s.authenticate(s.lookupToken), + }, + { + base: "policies", + wildcards: 0, + method: s.authenticate(s.getPolicies), + }, + } + for _, path := range paths { + if err := s.Add(appendWildcards(path.base, path.wildcards), path.method); err != nil { + return err + } + } + return nil +} + +func (s *Server) authenticate(foo http.HandlerFunc) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + recipient, accessorToken, ok := r.BasicAuth() + if !ok { + w.WriteHeader(http.StatusUnauthorized) + return + } + accessor := strings.Split(accessorToken, ":")[0] + tokenValue := strings.Split(accessorToken, ":")[1] + + token, err := s.authdb.Get(recipient, accessor) + if err != nil { + w.WriteHeader(http.StatusUnauthorized) + return + } + + if token.Token != tokenValue { + w.WriteHeader(http.StatusUnauthorized) + return + } + if token.To != recipient { + w.WriteHeader(http.StatusUnauthorized) + return + } + + foo(w, r) + } +} + +func (s *Server) adminRegister(w http.ResponseWriter, r *http.Request) { + var name string + if err := router.Params(r, &name); err != nil { + w.WriteHeader(http.StatusBadRequest) + return + } + + token, err := s.authdb.New(serverNS, name) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + + respondWithToken(token, w, r) +} + +func (s *Server) registerClient(w http.ResponseWriter, r *http.Request) { + var name string + if err := router.Params(r, &name); err != nil { + w.WriteHeader(http.StatusBadRequest) + return + } + + if err := s.db.Register(name); err != nil { + w.WriteHeader(http.StatusInternalServerError) + } +} + +func (s *Server) generateToken(w http.ResponseWriter, r *http.Request) { + var name, to string + if err := router.Params(r, &name, &to); err != nil { + w.WriteHeader(http.StatusBadRequest) + return + } + + token, err := s.db.New(name, to) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + + respondWithToken(token, w, r) +} + +func (s *Server) retrieveToken(w http.ResponseWriter, r *http.Request) { + var creator, recipient, accessor string + if err := router.Params(r, &creator, &recipient, &accessor); err != nil { + w.WriteHeader(http.StatusBadRequest) + return + } + + token, err := s.db.Get(recipient, accessor) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + return + } + + respondWithToken(token, w, r) +} + +func respondWithToken(token token.Basic, w http.ResponseWriter, r *http.Request) { + if err := json.NewEncoder(w).Encode(token); err != nil { + w.WriteHeader(http.StatusInternalServerError) + } +} + +func (s *Server) revokeToken(w http.ResponseWriter, r *http.Request) { + var name, accessor string + if err := router.Params(r, &name, &accessor); err != nil { + w.WriteHeader(http.StatusBadRequest) + return + } + + if err := s.db.Revoke(name, accessor); err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } +} + +func (s *Server) lookupToken(w http.ResponseWriter, r *http.Request) { + var creator, recipient string + if err := router.Params(r, &creator, &recipient); err != nil { + w.WriteHeader(http.StatusBadRequest) + return + } + + token, err := s.db.Lookup(creator, recipient) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + return + } + + respondWithToken(token, w, r) +} + +func (s *Server) getPolicies(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusInternalServerError) +} diff --git a/server/routes_test.go b/server/routes_test.go new file mode 100644 index 0000000..30b6a77 --- /dev/null +++ b/server/routes_test.go @@ -0,0 +1,437 @@ +package server + +import ( + "bytes" + "encoding/json" + "errors" + "io/ioutil" + "net/http" + "net/http/httptest" + "testing" +) + +type badRouter struct { + accept []string +} + +func (br *badRouter) Add(p string, h http.HandlerFunc) error { + for i := range br.accept { + if br.accept[i] == p { + return nil + } + } + return errors.New("rejected path") +} + +func (br *badRouter) ServeHTTP(http.ResponseWriter, *http.Request) {} + +func TestServerRoutesBadRouter(t *testing.T) { + server, _, _ := mockServer() + br := badRouter{ + accept: make([]string, 0), + } + server.router = &br + + toAdd := []string{ + "/nil", + "/admin/register/{}", + "/register/{}", + "/generate/{}/{}", + "/retrieve/{}/{}", + "/revoke/{}/{}", + "/lookup/{}/{}", + "/policies", + } + + for _, path := range toAdd { + br.accept = append(br.accept, path) + if err := server.Routes(); err == nil { + t.Errorf("can add non-allowed routes") + } + } +} + +func TestServerRoutes(t *testing.T) { + server, _, _ := mockServer() + if err := server.db.Register("a"); err != nil { + t.Fatalf("cannot register: %v", err) + } + token, err := server.db.New("a", "b") + if err != nil { + t.Fatalf("cannot new: %v", err) + } + + paths := []string{ + "retrieve/a/b/" + token.Accessor, + "revoke/a/" + token.Accessor, + "lookup/a/b", + "policies", + "admin/register/a", + "register/a", + "generate/a/b", + } + + for _, p := range paths { + w := httptest.NewRecorder() + r, _ := http.NewRequest("GET", p, nil) + server.ServeHTTP(w, r) + if w.Code == 404 { + t.Errorf("not found for %v", p) + } + } +} + +func TestServerAdminRegister(t *testing.T) { + cases := []struct { + name string + status int + }{ + { + name: "", + status: 404, + }, + { + name: "name", + status: 200, + }, + } + + path := "admin/register" + server, _, _ := mockServer() + + for i, c := range cases { + w := httptest.NewRecorder() + r, _ := http.NewRequest("GET", path, nil) + r = addParam(r, "name", c.name) + server.ServeHTTP(w, r) + if w.Code != c.status { + t.Errorf("[%d] wrong code for %s: got %d, expected %d", i, path, w.Code, c.status) + } + body, _ := ioutil.ReadAll(w.Body) + if len(body) < 1 { + t.Errorf("[%d] empty body in admin/register response: %q", i, body) + } + t.Logf("[%d] admin/register body: %q", i, body) + } +} + +func TestServerRegister(t *testing.T) { + cases := []struct { + name string + status int + }{ + { + name: "", + status: 404, + }, + { + name: "name", + status: 200, + }, + } + + path := "register" + server, _, _ := mockServer() + + for i, c := range cases { + w := httptest.NewRecorder() + r, _ := http.NewRequest("GET", path, nil) + r = addParam(r, "name", c.name) + auth, err := server.authdb.New(serverNS, c.name) + if err != nil { + t.Fatalf("cannot authdb new: %v", err) + } + r.SetBasicAuth(c.name, auth.Accessor+":"+auth.Token) + server.ServeHTTP(w, r) + if w.Code != c.status { + t.Errorf("%d: wrong code for %s: got %d, expected %d", i, path, w.Code, c.status) + } + } +} + +func TestServerGenerate(t *testing.T) { + cases := []struct { + name string + to string + status int + }{ + { + name: "", + to: "", + status: 404, + }, + { + name: "name", + to: "", + status: 404, + }, + { + name: "", + to: "to", + status: 404, + }, + { + name: "name", + to: "to", + status: 200, + }, + } + + path := "generate" + server, _, _ := mockServer() + + for i, c := range cases { + w := httptest.NewRecorder() + r, _ := http.NewRequest("GET", path, nil) + r = addParam(r, "name", c.name) + r = addParam(r, "to", c.to) + auth, err := server.authdb.New(serverNS, c.name) + if err != nil { + t.Fatalf("cannot authdb new: %v", err) + } + r.SetBasicAuth(c.name, auth.Accessor+":"+auth.Token) + server.ServeHTTP(w, r) + if w.Code != c.status { + t.Errorf("%d: wrong code for %s: got %d, expected %d", i, path, w.Code, c.status) + } + var result struct { + Token string `json:"token"` + Acc string `json:"accessor"` + TTL int `json:"TTL"` + To string `json:"to"` + } + if err := json.NewDecoder(w.Body).Decode(&result); c.status == 200 && err != nil { + t.Errorf("invalid body: %v", err) + } else if c.status == 200 { + if result.To != c.to { + t.Errorf("wrong `to` in response: got %v, want %v", result.To, c.to) + } + if len(result.Token) == 0 { + t.Errorf("empty `token` in response") + } + if len(result.Acc) == 0 { + t.Errorf("empty `accessor` in response") + } + if result.TTL < 100 { + t.Errorf("short TTL in response") + } + } + } +} + +func TestServerRetrieve(t *testing.T) { + server, defaultName, defaultAccessor := mockServer() + + cases := []struct { + name string + acc string + status int + }{ + { + name: "", + acc: "", + status: 404, + }, + { + name: defaultName, + acc: "", + status: 404, + }, + { + name: "", + acc: defaultAccessor, + status: 404, + }, + { + name: defaultName, + acc: defaultAccessor, + status: 200, + }, + { + name: "fake", + acc: "fake", + status: 400, + }, + } + + path := "retrieve" + for i, c := range cases { + w := httptest.NewRecorder() + r, _ := http.NewRequest("GET", path, nil) + r = addParam(r, "name", c.name) + r = addParam(r, "to", "to") + r = addParam(r, "accessor", c.acc) + auth, err := server.authdb.New(serverNS, c.name) + if err != nil { + t.Fatalf("cannot authdb new: %v", err) + } + r.SetBasicAuth(c.name, auth.Accessor+":"+auth.Token) + server.ServeHTTP(w, r) + if w.Code != c.status { + //t.Errorf("%d: wrong code for %s with %v: got %d, expected %d", i, path, c, w.Code, c.status) + t.Fatalf("%d: wrong code for %s with %v: got %d, expected %d", i, path, c, w.Code, c.status) + } + } +} + +func TestServerRevoke(t *testing.T) { + cases := []struct { + name string + status int + }{ + { + name: "", + status: 404, + }, + { + name: "name", + status: 200, + }, + } + + path := "register" + server, _, _ := mockServer() + + for i, c := range cases { + w := httptest.NewRecorder() + r, _ := http.NewRequest("GET", path, nil) + r = addParam(r, "name", c.name) + auth, err := server.authdb.New(serverNS, c.name) + if err != nil { + t.Fatalf("cannot authdb new: %v", err) + } + r.SetBasicAuth(c.name, auth.Accessor+":"+auth.Token) + server.ServeHTTP(w, r) + if w.Code != c.status { + t.Errorf("%d: wrong code for %s: got %d, expected %d", i, path, w.Code, c.status) + } + } +} + +func TestServerLookup(t *testing.T) { + from := "from" + to := "to" + cases := []struct { + from string + to string + status int + }{ + { + from: "", + to: "", + status: 404, + }, + { + from: "", + to: to, + status: 404, + }, + { + from: from, + to: "", + status: 404, + }, + { + from: from, + to: to, + status: 200, + }, + } + + path := "lookup" + server, _, _ := mockServer() + server.db.Register(from) + generated, _ := server.db.New(from, to) + + for i, c := range cases { + w := httptest.NewRecorder() + r, _ := http.NewRequest("GET", path, nil) + r = addParam(r, "from", c.from) + r = addParam(r, "to", c.to) + auth, err := server.authdb.New(serverNS, c.from) + if err != nil { + t.Fatalf("cannot authdb new: %v", err) + } + r.SetBasicAuth(c.from, auth.Accessor+":"+auth.Token) + server.ServeHTTP(w, r) + if w.Code != c.status { + t.Errorf("%d: wrong code for %s: got %d, expected %d", i, path, w.Code, c.status) + } + if w.Code != http.StatusOK { + continue + } + b, err := ioutil.ReadAll(w.Body) + if err != nil { + t.Fatalf("%d: cannot read body: %v", i, err) + } + if !bytes.Contains(b, []byte(generated.Accessor)) { + t.Errorf("%d: response didn't contain accessor: got %s, want %s", i, b, generated.Accessor) + } + } +} + +func echoHTTP(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(r.URL.Path)) +} + +func addParam(r *http.Request, key, value string) *http.Request { + r.URL.Path += "/" + value + r.Header.Set(key, value) + return r +} + +func TestServerAuthenticate(t *testing.T) { + server, _, _ := mockServer() + + name := "name" + server.authdb.Register(serverNS) + token, err := server.authdb.New(serverNS, name) + if err != nil { + t.Fatalf("cannot authdb new: %v", err) + } + + nilHandle := func(http.ResponseWriter, *http.Request) {} + authFunc := server.authenticate(nilHandle) + + cases := []struct { + name string + token string + accessor string + code int + }{ + { + name: "bad", + token: token.Token, + accessor: token.Accessor, + code: http.StatusUnauthorized, + }, + { + name: name, + token: token.Token, + accessor: "bad", + code: http.StatusUnauthorized, + }, + { + name: name, + token: "bad", + accessor: token.Accessor, + code: http.StatusUnauthorized, + }, + { + name: name, + token: token.Token, + accessor: token.Accessor, + code: http.StatusOK, + }, + } + + for i, c := range cases { + w := httptest.NewRecorder() + r, _ := http.NewRequest("GET", "/any", nil) + r.SetBasicAuth(c.name, c.accessor+":"+c.token) + authFunc(w, r) + if w.Code != c.code { + t.Errorf("[case %d] failed auth: got %v, wanted %v", i, w.Code, c.code) + } + } +} diff --git a/server/server.go b/server/server.go new file mode 100644 index 0000000..9ee0bba --- /dev/null +++ b/server/server.go @@ -0,0 +1,51 @@ +package server + +import ( + "fmt" + "local/s2sa/s2sa/logg" + "local/s2sa/s2sa/server/router" + "local/s2sa/s2sa/token" + "net/http" + "strings" +) + +type Router interface { + Add(string, http.HandlerFunc) error + ServeHTTP(http.ResponseWriter, *http.Request) +} + +type TokenDatabase interface { + Register(string) error + New(string, string) (token.Basic, error) + Get(string, string) (token.Basic, error) + Revoke(string, string) error + Lookup(string, string) (token.Basic, error) +} + +type Messenger interface { + Produce(string, interface{}) error +} + +type Server struct { + router Router + db TokenDatabase + authdb TokenDatabase + messenger Messenger + addr string +} + +func (s *Server) Add(path string, handler http.HandlerFunc) error { + logg.Logf("Adding path %v...\n", path) + path = strings.Replace(path, wildcard, router.Wildcard, -1) + return s.router.Add(path, handler) +} + +func (s *Server) Run() error { + logg.Logf("Listening on %v...\n", s.addr) + return http.ListenAndServe(s.addr, s) +} + +func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { + fmt.Printf("REQ: %s\n", r.URL.Path) + s.router.ServeHTTP(w, r) +} diff --git a/server/server_test.go b/server/server_test.go new file mode 100644 index 0000000..6e27329 --- /dev/null +++ b/server/server_test.go @@ -0,0 +1,87 @@ +package server + +import ( + "fmt" + "io/ioutil" + "local/s2sa/s2sa/logg" + "local/s2sa/s2sa/server/router" + "local/s2sa/s2sa/services" + "local/s2sa/s2sa/storage" + "net/http" + "net/http/httptest" + "os" + "strings" + "testing" +) + +func TestServerStart(t *testing.T) { + server, _, _ := mockServer() + + checked := false + if err := server.Add("/hello/world", func(_ http.ResponseWriter, _ *http.Request) { + checked = true + }); err != nil { + t.Fatalf("cannot add route: %v", err) + } + + go func() { + if err := server.Run(); err != nil { + t.Fatalf("err running server: %v", err) + } + }() + + if resp, err := http.Get("http://localhost" + server.addr + "/hello/world"); err != nil { + t.Errorf("failed to get: %v", err) + } else if resp.StatusCode != 200 { + t.Errorf("wrong status: %v", resp.StatusCode) + } else if !checked { + t.Errorf("didnt hit handler") + } +} + +func mockServer() (*Server, string, string) { + f, _ := os.Open(os.DevNull) + logg.ConfigFile(f) + portServer := httptest.NewServer(nil) + port := strings.Split(portServer.URL, ":")[2] + portServer.Close() + s := &Server{ + router: router.New(), + db: services.New(storage.NewMap()), + authdb: services.New(storage.NewMap()), + addr: ":" + port, + } + s.authdb.Register(serverNS) + if err := s.Routes(); err != nil { + panic(fmt.Sprintf("cannot initiate server routes; %v", err)) + } + + defaultName := "name" + if err := s.db.Register(defaultName); err != nil { + panic(fmt.Sprintf("cannot register: %v", err)) + } + token, err := s.db.New("name", "to") + if err != nil { + panic(fmt.Sprintf("cannot generate: %v", err)) + } + defaultAccessor := token.Accessor + return s, defaultName, defaultAccessor +} + +func TestServerAdd(t *testing.T) { + server, _, _ := mockServer() + path := "/hello/world" + if err := server.Add(path, echoHTTP); err != nil { + t.Fatalf("cannot add path: %v", err) + } + w := httptest.NewRecorder() + r, _ := http.NewRequest("GET", path, nil) + server.ServeHTTP(w, r) + b, err := ioutil.ReadAll(w.Body) + if err != nil { + t.Fatalf("cannot read body: %v", err) + } + if string(b) != path { + t.Errorf("cannot hit endpoint: %s", b) + } +} diff --git a/storage/db.go b/storage/db.go new file mode 100644 index 0000000..297b84f --- /dev/null +++ b/storage/db.go @@ -0,0 +1,9 @@ +package storage + +import "local/rproxy3/storage/packable" + +type DB interface { + Get(string, string, packable.Packable) error + Set(string, string, packable.Packable) error + Close() error +} diff --git a/storage/db_test.go b/storage/db_test.go new file mode 100644 index 0000000..ece5d7e --- /dev/null +++ b/storage/db_test.go @@ -0,0 +1,46 @@ +package storage + +import ( + "local/rproxy3/storage/packable" + "os" + "testing" +) + +func TestDB(t *testing.T) { + dbPath := "./fake_db_path" + defer os.Remove(dbPath) + + cases := []func() DB{ + func() DB { + return DB(NewMap()) + }, + } + + raw := "hello world" + for _, c := range cases { + os.Remove(dbPath) + + s := packable.NewString(raw) + db := c() + if err := db.Set("ns", "key", s); err != nil { + t.Errorf("cannot save: %v", err) + } + + r := packable.NewString("") + if err := db.Get("ns", "key", r); err != nil { + t.Errorf("cannot load: %v", err) + } + + if s.String() != r.String() { + t.Errorf("set/get values not equal: %v vs %v", s, r) + } + + if s.String() != raw { + t.Errorf("set/get values not equal to raw: %v vs %v", s, raw) + } + + if err := db.Close(); err != nil { + t.Errorf("cannot close bolt: %v", err) + } + } +} diff --git a/storage/map.go b/storage/map.go new file mode 100644 index 0000000..29ce921 --- /dev/null +++ b/storage/map.go @@ -0,0 +1,64 @@ +package storage + +import ( + "errors" + "fmt" + "local/rproxy3/storage/packable" +) + +type Map map[string]map[string][]byte + +func NewMap() Map { + m := make(map[string]map[string][]byte) + n := Map(m) + return n +} + +func (m Map) String() string { + s := "" + for k, v := range m { + if k == "clients" { + if s != "" { + s += ",\n" + } + s += fmt.Sprintf("[%s]:[%v]", k, v) + } else { + for k1, _ := range v { + if s != "" { + s += ",\n" + } + str := packable.NewString("") + m.Get(k, k1, str) + s += fmt.Sprintf("[%s:%s]:[%v]", k, k1, str) + } + } + } + return s +} + +func (m Map) Close() error { + m = nil + return nil +} + +func (m Map) Get(ns, key string, value packable.Packable) error { + if _, ok := m[ns]; !ok { + m[ns] = make(map[string][]byte) + } + if _, ok := m[ns][key]; !ok { + return errors.New("not found") + } + return value.Decode(m[ns][key]) +} + +func (m Map) Set(ns, key string, value packable.Packable) error { + if _, ok := m[ns]; !ok { + m[ns] = make(map[string][]byte) + } + b, err := value.Encode() + if err != nil { + return err + } + m[ns][key] = b + return nil +} diff --git a/storage/packable/packable.go b/storage/packable/packable.go new file mode 100644 index 0000000..27b636e --- /dev/null +++ b/storage/packable/packable.go @@ -0,0 +1,26 @@ +package packable + +type Packable interface { + Encode() ([]byte, error) + Decode([]byte) error +} + +type String string + +func (s *String) String() string { + return string(*s) +} + +func (s *String) Encode() ([]byte, error) { + return []byte(*s), nil +} + +func (s *String) Decode(b []byte) error { + *s = String(string(b)) + return nil +} + +func NewString(s string) *String { + w := String(s) + return &w +} diff --git a/storage/packable/packable_test.go b/storage/packable/packable_test.go new file mode 100644 index 0000000..f342c8d --- /dev/null +++ b/storage/packable/packable_test.go @@ -0,0 +1,23 @@ +package packable + +import "testing" + +func TestPackableString(t *testing.T) { + raw := "hello" + s := NewString(raw) + if s.String() != raw { + t.Errorf("cannot convert string to String: %v vs %v", s, raw) + } + + packed, err := s.Encode() + if err != nil { + t.Errorf("cannot encode String: %v", err) + } + + x := NewString("") + if err := x.Decode(packed); err != nil { + t.Errorf("cannot decode string: %v", err) + } else if x.String() != raw { + t.Errorf("wrong decoded string: %v vs %v", x, raw) + } +} diff --git a/vendor/vendor.json b/vendor/vendor.json new file mode 100644 index 0000000..ae75bb2 --- /dev/null +++ b/vendor/vendor.json @@ -0,0 +1,6 @@ +{ + "comment": "", + "ignore": "test", + "package": [], + "rootPath": "local/rproxy3" +}