summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBen Burwell <ben@benburwell.com>2019-09-16 15:56:31 -0400
committerBen Burwell <ben@benburwell.com>2019-09-16 16:29:16 -0400
commit679a62a9407c09be0cfb4e22455dca5ae694ce01 (patch)
treea99e8d61ada1677aee6f63a78545463308c5badd
parent528352cddbe0290653c56a27a4134637ad0624e5 (diff)
Flesh out
-rw-r--r--caesar.go15
-rw-r--r--caesar_test.go21
-rw-r--r--client/client.go43
-rw-r--r--client/client_test.go66
-rw-r--r--example_test.go22
-rw-r--r--fuzz_test.go68
-rw-r--r--go.mod8
-rw-r--r--go.sum13
-rw-r--r--server/auth.go15
-rw-r--r--server/main.go34
-rw-r--r--server/main_test.go58
-rwxr-xr-xserver/serverbin0 -> 7349148 bytes
-rw-r--r--testdata/tests.txt3
-rw-r--r--unusual_test.go11
14 files changed, 365 insertions, 12 deletions
diff --git a/caesar.go b/caesar.go
index d62a26e..191c5b8 100644
--- a/caesar.go
+++ b/caesar.go
@@ -21,16 +21,13 @@ type RuneRange struct {
End rune
}
-// Contains checks whether r is in the rune range.
-func (rr RuneRange) Contains(r rune) bool {
+// contains checks whether r is in the rune range.
+func (rr RuneRange) contains(r rune) bool {
return r >= rr.Start && r <= rr.End
}
-// Shift shifts r by d within the range, modulo the size of the range.
-func (rr RuneRange) Shift(r rune, d int) rune {
- if !rr.Contains(r) {
- return r
- }
+// shift shifts r by d within the range, modulo the size of the range.
+func (rr RuneRange) shift(r rune, d int) rune {
return rr.Start + (r - rr.Start + rune(d)%(rr.End-rr.Start))
}
@@ -62,8 +59,8 @@ type shiftFunc func(rune) rune
func (c Coder) shifter(delta int) shiftFunc {
return func(r rune) rune {
for _, rr := range c.Ranges {
- if rr.Contains(r) {
- return rr.Shift(r, delta)
+ if rr.contains(r) {
+ return rr.shift(r, delta)
}
}
return r
diff --git a/caesar_test.go b/caesar_test.go
index e7dc3c0..04b853b 100644
--- a/caesar_test.go
+++ b/caesar_test.go
@@ -4,14 +4,31 @@ import (
"testing"
)
+func TestEncode(t *testing.T) {
+ msg := "Attack at dawn"
+ if Encode(msg) != "Dwwdfn dw gdzq" {
+ t.Fail()
+ }
+}
+
func TestCaesar(t *testing.T) {
+ if testing.Short() {
+ t.Skip()
+ }
msg := "Attack at dawn"
+ t.Logf("testing encoding message %s", msg)
encoded := Encode(msg)
if encoded == msg {
- t.Errorf("expected ciphertext and plaintext to differ")
+ t.Log("expected ciphertext and plaintext to differ")
+ t.Fail()
}
decoded := Decode(encoded)
if decoded != msg {
- t.Errorf("expected recovered plaintext to match message, but got: %s", decoded)
+ t.Logf("expected recovered plaintext to match message, but got: %s", decoded)
+ t.Fail()
}
}
+
+func BenchmarkCaesar(b *testing.B) {
+ b.Fail()
+}
diff --git a/client/client.go b/client/client.go
new file mode 100644
index 0000000..e39a6a1
--- /dev/null
+++ b/client/client.go
@@ -0,0 +1,43 @@
+package client
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+ "net/http"
+ "sync"
+ "time"
+)
+
+type CaesarClient struct {
+ Endpoint string
+ Token string
+
+ c *http.Client
+ once sync.Once
+}
+
+func (c *CaesarClient) EncodeMessage(r io.Reader) (io.Reader, error) {
+ c.once.Do(func() {
+ c.c = &http.Client{Timeout: 10 * time.Second}
+ })
+ req, err := http.NewRequest("POST", c.Endpoint, r)
+ if err != nil {
+ return nil, err
+ }
+ req.Header.Set("authorization", "token "+c.Token)
+ req.Header.Set("user-agent", "caesar-client/1.0")
+ resp, err := c.c.Do(req)
+ if err != nil {
+ return nil, err
+ }
+ if resp.StatusCode != http.StatusOK {
+ return nil, fmt.Errorf("http status %s", resp.Status)
+ }
+ var body bytes.Buffer
+ defer resp.Body.Close()
+ if _, err := io.Copy(&body, resp.Body); err != nil {
+ return nil, err
+ }
+ return &body, nil
+}
diff --git a/client/client_test.go b/client/client_test.go
new file mode 100644
index 0000000..fb1701c
--- /dev/null
+++ b/client/client_test.go
@@ -0,0 +1,66 @@
+package client
+
+import (
+ "io/ioutil"
+ "net/http"
+ "net/http/httptest"
+ "strings"
+ "testing"
+)
+
+func TestBadAuth(t *testing.T) {
+ var hf http.HandlerFunc = func(w http.ResponseWriter, r *http.Request) {
+ if r.Header.Get("authorization") != "token TEST-TOKEN" {
+ w.WriteHeader(http.StatusUnauthorized)
+ return
+ }
+ w.Write([]byte("encoded yay"))
+ }
+ tests := []struct {
+ name string
+ tok string
+ expectErr bool
+ expectBody bool
+ }{
+ {"valid token", "TEST-TOKEN", false, true},
+ {"empty token", "", true, false},
+ {"invalid token", "bad token", true, false},
+ }
+ server := httptest.NewServer(hf)
+ defer server.Close()
+ c := CaesarClient{Endpoint: server.URL}
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ r, err := c.EncodeMessage(strings.NewReader("secret message"))
+ if err == nil {
+ t.Errorf("should have gotten error")
+ }
+ if r != nil {
+ t.Errorf("should not have gotten a reader")
+ }
+ })
+ }
+}
+
+func TestEncode(t *testing.T) {
+ var hf http.HandlerFunc = func(w http.ResponseWriter, r *http.Request) {
+ w.Write([]byte("response"))
+ }
+ server := httptest.NewServer(hf)
+ defer server.Close()
+ c := CaesarClient{Endpoint: server.URL}
+ r, err := c.EncodeMessage(strings.NewReader("secret message"))
+ if err != nil {
+ t.Errorf("should not have gotten error: %v", err)
+ }
+ if r == nil {
+ t.Errorf("should have gotten a reader")
+ }
+ resp, err := ioutil.ReadAll(r)
+ if err != nil {
+ t.Fatalf("could not read message: %v", err)
+ }
+ if string(resp) != "response" {
+ t.Errorf("expected 'response' but got '%s'", resp)
+ }
+}
diff --git a/example_test.go b/example_test.go
new file mode 100644
index 0000000..2ccf83b
--- /dev/null
+++ b/example_test.go
@@ -0,0 +1,22 @@
+package caesar
+
+import (
+ "fmt"
+)
+
+func ExampleEncode() {
+ secret := Encode("attack at dawn")
+ fmt.Println(secret)
+ // Output: dwwdfn dw gdzq
+}
+
+func ExampleCoder() {
+ c := Coder{
+ Key: 5,
+ Ranges: []RuneRange{
+ {Start: 'A', End: 'Z'},
+ },
+ }
+ fmt.Println(c.Encode("aAbBcCdD"))
+ // Output: aFbGcHdI
+}
diff --git a/fuzz_test.go b/fuzz_test.go
new file mode 100644
index 0000000..71fdcb1
--- /dev/null
+++ b/fuzz_test.go
@@ -0,0 +1,68 @@
+package caesar
+
+import (
+ "bytes"
+ "flag"
+ "io"
+ "io/ioutil"
+ "os"
+ "testing"
+)
+
+var (
+ runFuzz bool
+ fuzzInput string
+)
+
+func TestMain(m *testing.M) {
+ flag.BoolVar(&runFuzz, "fuzz", false, "run the fuzz tests")
+ flag.StringVar(&fuzzInput, "fuzz.input", "", "input file for fuzz test")
+ flag.Parse()
+ os.Exit(m.Run())
+}
+
+func TestFuzz(t *testing.T) {
+ if !runFuzz {
+ t.Skip("skipping fuzz test")
+ }
+ f, err := os.Open(fuzzInput)
+ if err != nil {
+ t.Fatalf("could not open fuzz input: %v", err)
+ }
+ defer f.Close()
+ data, err := ioutil.ReadAll(f)
+ if err != nil {
+ t.Fatalf("could not read fuzz input: %v", err)
+ }
+ buf := bytes.NewBuffer(data)
+ for {
+ // read input
+ inp, err := buf.ReadBytes('\t')
+ if err == io.EOF {
+ return
+ } else if err != nil {
+ t.Fatalf("read error: %v", err)
+ }
+
+ // read expected output
+ exp, err := buf.ReadBytes('\n')
+ if err == io.EOF {
+ t.Fatalf("found input with no matching output")
+ } else if err != nil {
+ t.Fatalf("read error: %v", err)
+ }
+
+ if len(inp) < 2 || len(exp) < 2 {
+ t.Fatalf("malformed input")
+ }
+
+ result := Encode(string(inp[:len(inp)-2]))
+ if result != string(exp[:len(exp)-2]) {
+ t.Logf("input: %s", inp)
+ t.Logf("output: %s", result)
+ t.Logf("expected: %s", exp)
+ t.Fail()
+ }
+ }
+ Encode(string(data))
+}
diff --git a/go.mod b/go.mod
index 89c5530..5c4dc27 100644
--- a/go.mod
+++ b/go.mod
@@ -1,3 +1,9 @@
-module go.burwell.io/caesar
+module bnbl.io/caesar
go 1.13
+
+require (
+ github.com/emersion/go-maildir v0.0.0-20190727102040-941194b0ac70 // indirect
+ github.com/ogier/pflag v0.0.1 // indirect
+ golang.org/x/tools v0.0.0-20190916173350-3512ebf57407 // indirect
+)
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..a9ced96
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,13 @@
+github.com/emersion/go-maildir v0.0.0-20190727102040-941194b0ac70 h1:aUiPu6/iCjcsnNe/WkhsnMOq7vPmkYo9kFaMX5FiNZU=
+github.com/emersion/go-maildir v0.0.0-20190727102040-941194b0ac70/go.mod h1:I2j27lND/SRLgxROe50Vam81MSaqPFvJ0OHNnDZ7n84=
+github.com/ogier/pflag v0.0.1 h1:RW6JSWSu/RkSatfcLtogGfFgpim5p7ARQ10ECk5O750=
+github.com/ogier/pflag v0.0.1/go.mod h1:zkFki7tvTa0tafRvTBIZTvzYyAu6kQhPZFnshFFPE+g=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/tools v0.0.0-20190916173350-3512ebf57407 h1:8t/stSbOaNHdu6dn/zC9OeTTqND69t0sA6uEkZVcILg=
+golang.org/x/tools v0.0.0-20190916173350-3512ebf57407/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
diff --git a/server/auth.go b/server/auth.go
new file mode 100644
index 0000000..15ea164
--- /dev/null
+++ b/server/auth.go
@@ -0,0 +1,15 @@
+package main
+
+import (
+ "net/http"
+)
+
+// An Authenticator takes an HTTP request and returns true iff it is allowed to
+// use our service.
+type Authenticator func(req *http.Request) bool
+
+func TokenAuthenticator() Authenticator {
+ return func(req *http.Request) bool {
+ return req.Header.Get("authorization") == "magic"
+ }
+}
diff --git a/server/main.go b/server/main.go
new file mode 100644
index 0000000..23f2959
--- /dev/null
+++ b/server/main.go
@@ -0,0 +1,34 @@
+package main
+
+import (
+ "io/ioutil"
+ "net/http"
+
+ "bnbl.io/caesar"
+)
+
+func main() {
+ server := handleCaesar(TokenAuthenticator())
+ http.ListenAndServe(":8088", server)
+}
+
+func handleCaesar(auth Authenticator) http.HandlerFunc {
+ return func(w http.ResponseWriter, r *http.Request) {
+ if !auth(r) {
+ w.WriteHeader(http.StatusUnauthorized)
+ return
+ }
+ if r.Method != http.MethodPost {
+ w.WriteHeader(http.StatusBadRequest)
+ return
+ }
+ body, err := ioutil.ReadAll(r.Body)
+ defer r.Body.Close()
+ if err != nil {
+ w.WriteHeader(http.StatusInternalServerError)
+ return
+ }
+ encoded := caesar.Encode(string(body))
+ w.Write([]byte(encoded))
+ }
+}
diff --git a/server/main_test.go b/server/main_test.go
new file mode 100644
index 0000000..75ddcdb
--- /dev/null
+++ b/server/main_test.go
@@ -0,0 +1,58 @@
+package main
+
+import (
+ "net/http"
+ "net/http/httptest"
+ "strings"
+ "testing"
+)
+
+func TestHandleCaesarAllowed(t *testing.T) {
+ allowAll := func(r *http.Request) bool { return true }
+ bodyReader := strings.NewReader("hello world")
+ req := httptest.NewRequest("POST", "/", bodyReader)
+ resp := httptest.NewRecorder()
+
+ handler := handleCaesar(allowAll)
+ handler(resp, req)
+
+ if resp.Code != http.StatusOK {
+ t.Errorf("expected OK status %d but got %d", http.StatusOK, resp.Code)
+ }
+
+ body := string(resp.Body.Bytes())
+ if body != "khoor zruog" {
+ t.Errorf("got unexpected body %s", body)
+ }
+}
+
+func TestCaesarNotAllowed(t *testing.T) {
+ denyAll := func(r *http.Request) bool { return false }
+ req := httptest.NewRequest("POST", "/", nil)
+ resp := httptest.NewRecorder()
+
+ handler := handleCaesar(denyAll)
+ handler(resp, req)
+
+ if resp.Code != http.StatusUnauthorized {
+ t.Errorf("should not have been authorized")
+ }
+
+ if len(resp.Body.Bytes()) != 0 {
+ t.Errorf("should not have gotten a response body")
+ }
+}
+
+func TestCaesarBadRequest(t *testing.T) {
+ allowAll := func(r *http.Request) bool { return true }
+ req := httptest.NewRequest("GET", "/", nil)
+ resp := httptest.NewRecorder()
+ handler := handleCaesar(allowAll)
+ handler(resp, req)
+ if resp.Code != http.StatusBadRequest {
+ t.Errorf("status should have been bad request, got %d", resp.Code)
+ }
+ if len(resp.Body.Bytes()) != 0 {
+ t.Errorf("should not have gotten a response body")
+ }
+}
diff --git a/server/server b/server/server
new file mode 100755
index 0000000..979db70
--- /dev/null
+++ b/server/server
Binary files differ
diff --git a/testdata/tests.txt b/testdata/tests.txt
new file mode 100644
index 0000000..49b590e
--- /dev/null
+++ b/testdata/tests.txt
@@ -0,0 +1,3 @@
+inp lqs
+a d
+Z C
diff --git a/unusual_test.go b/unusual_test.go
new file mode 100644
index 0000000..8faa0af
--- /dev/null
+++ b/unusual_test.go
@@ -0,0 +1,11 @@
+// +build unusual
+
+package caesar
+
+import (
+ "testing"
+)
+
+func TestUnusual(t *testing.T) {
+ t.Fatalf("Here is a test I never really want to run")
+}