Browse Source

Initial commit

Teran McKinney 3 years ago
commit
da07723944
  1. 24
      LICENSE.md
  2. 13
      README.md
  3. 62
      cmd/settlers/main.go
  4. 55
      cmd/settlersd/add.go
  5. 59
      cmd/settlersd/balance.go
  6. 91
      cmd/settlersd/cmd_test.go
  7. 37
      cmd/settlersd/configuration.go
  8. 21
      cmd/settlersd/database.go
  9. 54
      cmd/settlersd/enable.go
  10. 26
      cmd/settlersd/enable_test.go
  11. 16
      cmd/settlersd/http.go
  12. 23
      cmd/settlersd/main.go
  13. 55
      cmd/settlersd/subtract.go
  14. 65
      cmd/settlersd/web.go
  15. 10
      errors.go
  16. 52
      onion.go
  17. 73
      onion_test.go
  18. 20
      request_types.go
  19. 1
      sample_configuration/invalid.json
  20. 1
      sample_configuration/valid.json
  21. 1
      sample_configuration/valid_with_file_database.json
  22. 152
      settlers.go
  23. 43
      settlers_test.go
  24. 69
      test.sh

24
LICENSE.md

@ -0,0 +1,24 @@
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
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 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.
For more information, please refer to <http://unlicense.org>

13
README.md

@ -0,0 +1,13 @@
# (Settlers) of Cryptotan
Atomic unsigned integer database.
Client library, CLI, daemon.
## Install
* `go get github.com/teran-mckinney/settlers/...`
## License
Public domain / Unlicense

62
cmd/settlers/main.go

@ -0,0 +1,62 @@
package main
import (
"fmt"
"os"
"strconv"
"github.com/teran-mckinney/settlers"
)
func usage() {
fmt.Fprintln(os.Stderr, "Usage: settlers <command> [arguments]")
fmt.Fprintln(os.Stderr, "Command: enable <endpoint> <admin token> <customer token> <business token>")
fmt.Fprintln(os.Stderr, "Command: balance <endpoint> <customer token> <business token>")
fmt.Fprintln(os.Stderr, "Command: add <endpoint> <customer token> <business token> <amount>")
fmt.Fprintln(os.Stderr, "Command: subtract <endpoint> <customer token> <business token> <amount>")
os.Exit(2)
}
func exactlyArguments(arguments int) {
if len(os.Args) != arguments {
usage()
}
}
func fatalError(err error) {
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
func main() {
var err error
var amount uint64
if len(os.Args) <= 1 {
usage()
}
switch os.Args[1] {
case "enable":
exactlyArguments(2 + 4)
fatalError(settlers.Enable(os.Args[1+1], os.Args[1+2], os.Args[1+3], os.Args[1+4]))
case "balance":
exactlyArguments(2 + 3)
amount, err = settlers.Balance(os.Args[1+1], os.Args[1+2], os.Args[1+3])
fatalError(err)
fmt.Println(amount)
case "add":
exactlyArguments(2 + 4)
amount, err = strconv.ParseUint(os.Args[1+4], 10, 64)
fatalError(err)
fatalError(settlers.Add(os.Args[1+1], os.Args[1+2], os.Args[1+3], amount))
case "subtract":
exactlyArguments(2 + 4)
amount, err = strconv.ParseUint(os.Args[1+4], 10, 64)
fatalError(err)
fatalError(settlers.Subtract(os.Args[1+1], os.Args[1+2], os.Args[1+3], amount))
default:
usage()
}
}

55
cmd/settlersd/add.go

@ -0,0 +1,55 @@
package main
import (
"encoding/json"
"net/http"
"github.com/bvinc/go-sqlite-lite/sqlite3"
"github.com/teran-mckinney/settlers"
)
func add(token string, amount uint64, db *sqlite3.Conn) (userErr, err error) {
if userErr = settlers.ValidateToken(token); userErr != nil {
return
}
// Check if account is enabled.
_, userErr, err = balance(token, db)
if userErr != nil || err != nil {
return
}
// sqlite3 only supports signed integers
var signedAmount int64
signedAmount = int64(amount)
err = db.Exec("UPDATE balances SET balance = balance + ? WHERE combined_token = ?", signedAmount, token)
if err != nil {
return
}
return
}
func httpAdd(w http.ResponseWriter, r *http.Request, db *sqlite3.Conn) {
defer r.Body.Close()
var request settlers.AddRequest
decoder := json.NewDecoder(r.Body)
decoder.DisallowUnknownFields()
userErr := decoder.Decode(&request)
if userErr != nil {
httpHandle400(w, userErr)
return
}
userErr, err := add(request.CombinedToken, request.Amount, db)
if userErr != nil {
httpHandle400(w, userErr)
return
}
if err != nil {
httpHandle500(w, err)
return
}
return
}

59
cmd/settlersd/balance.go

@ -0,0 +1,59 @@
package main
import (
"encoding/json"
"net/http"
"strings"
"github.com/bvinc/go-sqlite-lite/sqlite3"
"github.com/teran-mckinney/settlers"
)
func balance(token string, db *sqlite3.Conn) (amount uint64, userErr, err error) {
if userErr = settlers.ValidateToken(token); userErr != nil {
return
}
statement, err := db.Prepare("SELECT balance FROM balances WHERE combined_token = ?", token)
if err != nil {
return
}
defer statement.Close()
hasRow, err := statement.Step()
if err != nil {
return
}
if !hasRow {
userErr = settlers.ErrorAccountNotEnabled
return
}
// sqlite3 only supports signed integers
var signedAmount int64
if err = statement.Scan(&signedAmount); err != nil {
return
}
amount = uint64(signedAmount)
return
}
func httpBalance(w http.ResponseWriter, r *http.Request, db *sqlite3.Conn) {
defer r.Body.Close()
pathParts := strings.Split(r.URL.Path, "/")
token := pathParts[len(pathParts)-1]
amount, userErr, err := balance(token, db)
if err != nil {
httpHandle500(w, err)
return
}
if userErr != nil {
httpHandle400(w, userErr)
return
}
responseStruct := settlers.BalanceResponse{Amount: amount}
err = json.NewEncoder(w).Encode(responseStruct)
if err != nil {
httpHandle500(w, err)
return
}
return
}

91
cmd/settlersd/cmd_test.go

@ -0,0 +1,91 @@
package main
import (
"log"
"testing"
"github.com/teran-mckinney/settlers"
)
const testAdminToken = "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"
const testToken = "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
var testConfiguration = Configuration{SqliteFilename: inMemoryDatabase, AdminToken: testAdminToken, Port: ":2828"}
func TestSettlers(t *testing.T) {
db, err := dbConnect(testConfiguration)
if err != nil {
return
}
defer db.Close()
_, userErr, err := balance(testToken, db)
if err != nil {
t.Error(err.Error())
}
if userErr != settlers.ErrorAccountNotEnabled {
t.Error("Account should not be enabled.")
} else {
log.Print("Non-enabled account balance shows not enabled.")
}
err, userErr = enable(testToken, testAdminToken, db, testConfiguration)
if err != nil {
t.Error(err.Error())
}
if userErr != nil {
t.Error(userErr.Error())
}
amount, userErr, err := balance(testToken, db)
if err != nil {
t.Error(err.Error())
}
if userErr != nil {
t.Error(userErr.Error())
}
if amount != startingBalance {
t.Errorf("Balance is not %d, it's %d.", startingBalance, amount)
} else {
log.Print("Balance is 0.")
}
userErr, err = add(testToken, 100, db)
if err != nil {
t.Error(err.Error())
}
if userErr != nil {
t.Error(userErr.Error())
} else {
log.Print("Able to add 100 to token.")
}
userErr, err = subtract(testToken, 40, db)
if err != nil {
t.Error(err.Error())
}
if userErr != nil {
t.Error(userErr.Error())
} else {
log.Print("Able to subtract 40 from token.")
}
amount, userErr, err = balance(testToken, db)
if err != nil {
t.Error(err.Error())
}
if userErr != nil {
t.Error(userErr.Error())
}
if amount != 60 {
t.Errorf("Balance is not %d, it's %d.", 60, amount)
}
userErr, err = subtract(testToken, 61, db)
if err != nil {
t.Error(err.Error())
}
if userErr != settlers.ErrorInsufficientBalance {
t.Error("Should have errored insufficient balance.")
}
}

37
cmd/settlersd/configuration.go

@ -0,0 +1,37 @@
package main
import (
"bytes"
"encoding/json"
"io/ioutil"
"github.com/teran-mckinney/settlers"
)
// SqliteFilename of :memory: uses an in-memory database.
type Configuration struct {
AdminToken string `json:"admin_token"`
SqliteFilename string `json:"sqlite_filename"`
Port string `json:"port"`
}
func validateConfiguration(jsonData []byte) (conf Configuration, err error) {
// We can't use json.Unmarshall and DisallowUnknownFields(), so we use NewDecoder
decoder := json.NewDecoder(bytes.NewReader(jsonData))
decoder.DisallowUnknownFields()
if err = decoder.Decode(&conf); err != nil {
return
}
if err = settlers.ValidateToken(conf.AdminToken); err != nil {
return
}
return
}
func configuration(configurationFile string) (conf Configuration, err error) {
jsonData, err := ioutil.ReadFile(configurationFile)
if err != nil {
return
}
return validateConfiguration(jsonData)
}

21
cmd/settlersd/database.go

@ -0,0 +1,21 @@
package main
import "github.com/bvinc/go-sqlite-lite/sqlite3"
const inMemoryDatabase = ":memory:"
func dbPrep(connection *sqlite3.Conn) error {
return connection.Exec(`CREATE TABLE balances (combined_token TEXT NOT NULL UNIQUE, balance INTEGER CHECK (balance >= 0))`)
}
func dbConnect(conf Configuration) (connection *sqlite3.Conn, err error) {
connection, err = sqlite3.Open(conf.SqliteFilename)
if err != nil {
return
}
// To make this "just work", we always prep and ignore if it's already been prepared.
dbPrep(connection)
return
}

54
cmd/settlersd/enable.go

@ -0,0 +1,54 @@
package main
import (
"encoding/json"
"net/http"
"github.com/bvinc/go-sqlite-lite/sqlite3"
"github.com/teran-mckinney/settlers"
)
const startingBalance = 0
func enable(token, adminToken string, db *sqlite3.Conn, conf Configuration) (userErr, err error) {
// We should consider doing balance first to know if it's a DB error
// or a user error.
if userErr = settlers.ValidateToken(token); userErr != nil {
return
}
if userErr = settlers.ValidateToken(adminToken); userErr != nil {
return
}
if adminToken != conf.AdminToken {
userErr = settlers.ErrorIncorrectAdminToken
return
}
err = db.Exec("INSERT INTO balances (combined_token, balance) VALUES (?, ?)", token, startingBalance)
return
}
func httpEnable(w http.ResponseWriter, r *http.Request, db *sqlite3.Conn, conf Configuration) {
defer r.Body.Close()
var request settlers.EnableRequest
decoder := json.NewDecoder(r.Body)
decoder.DisallowUnknownFields()
userErr := decoder.Decode(&request)
if userErr != nil {
httpHandle400(w, userErr)
return
}
userErr, err := enable(request.CombinedToken, request.AdminToken, db, conf)
if userErr != nil {
httpHandle400(w, userErr)
return
}
if err != nil {
httpHandle500(w, err)
return
}
return
}

26
cmd/settlersd/enable_test.go

@ -0,0 +1,26 @@
package main
import (
"log"
"testing"
"github.com/teran-mckinney/settlers"
)
func TestEnable(t *testing.T) {
db, err := dbConnect(testConfiguration)
if err != nil {
return
}
defer db.Close()
userErr, err := enable(testToken, testToken, db, testConfiguration)
if err != nil {
t.Error(err.Error())
}
if userErr != settlers.ErrorIncorrectAdminToken {
t.Error("Did not give incorrect admin token error for invalid token.")
} else {
log.Print("We secure enable properly.")
}
}

16
cmd/settlersd/http.go

@ -0,0 +1,16 @@
package main
import (
"log"
"net/http"
)
func httpHandle400(w http.ResponseWriter, userErr error) {
log.Print("User error: ", userErr.Error())
http.Error(w, userErr.Error(), http.StatusInternalServerError)
}
func httpHandle500(w http.ResponseWriter, err error) {
log.Print(err.Error())
http.Error(w, "Something broke in Settlers.", http.StatusInternalServerError)
}

23
cmd/settlersd/main.go

@ -0,0 +1,23 @@
// settlers...
package main
import (
"fmt"
"log"
"os"
)
func main() {
var conf Configuration
var err error
if len(os.Args) != 2 {
fmt.Printf("Usage: %s <configuration file .json>", os.Args[0])
os.Exit(2)
} else {
conf, err = configuration(os.Args[1])
if err != nil {
panic(err)
}
}
log.Fatal(web(conf))
}

55
cmd/settlersd/subtract.go

@ -0,0 +1,55 @@
package main
import (
"encoding/json"
"net/http"
"github.com/bvinc/go-sqlite-lite/sqlite3"
"github.com/teran-mckinney/settlers"
)
func subtract(token string, amount uint64, db *sqlite3.Conn) (userErr, err error) {
if userErr = settlers.ValidateToken(token); userErr != nil {
return
}
// Check if account is enabled.
_, userErr, err = balance(token, db)
if userErr != nil || err != nil {
return
}
// sqlite3 only supports signed integers
var signedAmount int64
signedAmount = int64(amount)
userErr = db.Exec("UPDATE balances SET balance = balance - ? WHERE combined_token = ?", signedAmount, token)
if userErr != nil {
userErr = settlers.ErrorInsufficientBalance
}
return
}
func httpSubtract(w http.ResponseWriter, r *http.Request, db *sqlite3.Conn) {
defer r.Body.Close()
var request settlers.SubtractRequest
decoder := json.NewDecoder(r.Body)
decoder.DisallowUnknownFields()
userErr := decoder.Decode(&request)
if userErr != nil {
httpHandle400(w, userErr)
return
}
userErr, err := subtract(request.CombinedToken, request.Amount, db)
if userErr != nil {
httpHandle400(w, userErr)
return
}
if err != nil {
httpHandle500(w, err)
return
}
return
}

65
cmd/settlersd/web.go

@ -0,0 +1,65 @@
// settlers...
package main
import (
"log"
"net/http"
"gopkg.in/alexcesaro/statsd.v2"
)
func web(conf Configuration) (err error) {
/* Statsd statistics. This works fine with or without. */
s, err := statsd.New(statsd.Prefix("settlers"))
if err != nil {
log.Print("settlers connection to statsd failed. This is not a problem unless you want statsd.")
// This should be non-fatal.
log.Print(err)
} else {
log.Print("settlers connected to statsd.")
}
defer s.Close()
db, err := dbConnect(conf)
if err != nil {
return
}
defer db.Close()
http.HandleFunc("/balance/", func(w http.ResponseWriter, r *http.Request) {
s.Increment("balance.hit")
defer s.NewTiming().Send("balance")
httpBalance(w, r, db)
return
})
http.HandleFunc("/add", func(w http.ResponseWriter, r *http.Request) {
s.Increment("add.hit")
defer s.NewTiming().Send("add")
httpAdd(w, r, db)
return
})
http.HandleFunc("/subtract", func(w http.ResponseWriter, r *http.Request) {
s.Increment("subtract.hit")
defer s.NewTiming().Send("subtract")
httpSubtract(w, r, db)
return
})
http.HandleFunc("/enable", func(w http.ResponseWriter, r *http.Request) {
s.Increment("enable.hit")
defer s.NewTiming().Send("enable")
httpEnable(w, r, db, conf)
return
})
http.HandleFunc("/SettlersAlive", func(w http.ResponseWriter, r *http.Request) {
s.Increment("SettlersAlive.hit")
defer s.NewTiming().Send("SettlersAlive")
return
})
err = http.ListenAndServe(conf.Port, nil)
return
}

10
errors.go

@ -0,0 +1,10 @@
package settlers
import (
"errors"
)
var ErrorAccountNotEnabled = errors.New("Account not enabled.")
var ErrorInsufficientBalance = errors.New("Balance insufficient.")
var ErrorIncorrectAdminToken = errors.New("Incorrect admin token.")
var ErrorInvalidToken = errors.New("Token must be exactly 64 lowercase hex characters in length.")

52
onion.go

@ -0,0 +1,52 @@
package settlers
import (
"net/http"
"net/url"
"strings"
"golang.org/x/net/proxy"
)
func isOnion(someURL string) (bool, error) {
parsedURL, err := url.Parse(someURL)
if err != nil {
return true, err
}
urlParts := strings.Split(parsedURL.Host, ".")
tld := urlParts[len(urlParts)-1]
if tld == "onion" {
return true, err
} else {
return false, err
}
}
func onionHTTP() (client *http.Client, err error) {
torProxyURL, err := url.Parse("socks5://127.0.0.1:9050")
if err != nil {
return
}
torDialer, err := proxy.FromURL(torProxyURL, proxy.Direct)
if err != nil {
return
}
torTransport := &http.Transport{Dial: torDialer.Dial}
client = &http.Client{Transport: torTransport}
return
}
func clearnetOrOnionHTTP(url string) (client *http.Client, err error) {
// Returns a http.Client using a local Tor SOCKS proxy if .onion,
// plain old http.Client if not.
reallyIsOnion, err := isOnion(url)
if err != nil {
return
}
if reallyIsOnion {
client, err = onionHTTP()
} else {
client = &http.Client{}
}
return
}

73
onion_test.go

@ -0,0 +1,73 @@
package settlers
import (
"log"
"testing"
)
func TestIsOnion(t *testing.T) {
url := "http://google.com"
status, err := isOnion(url)
if err != nil {
t.Errorf("We got error: %s", err.Error())
} else {
log.Printf("url: %s", url)
}
if status == false {
log.Printf("We know %s is not a .onion", url)
} else {
log.Printf("We think this is a .onion but it's not. %s", url)
}
url = "http://localhost"
status, err = isOnion(url)
if err != nil {
t.Errorf("We got error: %s", err.Error())
} else {
log.Printf("url: %s", url)
}
if status == false {
log.Printf("We know %s is not a .onion", url)
} else {
log.Printf("We think this is a .onion but it's not. %s", url)
}
url = "http://127.0.0.1:2323"
status, err = isOnion(url)
if err != nil {
t.Errorf("We got error: %s", err.Error())
} else {
log.Printf("url: %s", url)
}
if status == false {
log.Printf("We know %s is not a .onion", url)
} else {
log.Printf("We think this is a .onion but it's not. %s", url)
}
url = "http://somesite.onion"
status, err = isOnion(url)
if err != nil {
t.Errorf("We got error: %s", err.Error())
} else {
log.Printf("url: %s", url)
}
if status == true {
log.Printf("We know %s is a .onion", url)
} else {
log.Printf("We think this is not a .onion but it is. %s", url)
}
url = "http://somesite.onion/somestuff/thing.txt"
status, err = isOnion(url)
if err != nil {
t.Errorf("We got error: %s", err.Error())
} else {
log.Printf("url: %s", url)
}
if status == true {
log.Printf("We know %s is a .onion", url)
} else {
log.Printf("We think this is not a .onion but it is. %s", url)
}
}

20
request_types.go

@ -0,0 +1,20 @@
package settlers
type BalanceResponse struct {
Amount uint64 `json:"amount"`
}
type AddRequest struct {
CombinedToken string `json:"combined_token"`
Amount uint64 `json:"amount"`
}
type SubtractRequest struct {
CombinedToken string `json:"combined_token"`
Amount uint64 `json:"amount"`
}
type EnableRequest struct {
CombinedToken string `json:"combined_token"`
AdminToken string `json:"admin_token"`
}

1
sample_configuration/invalid.json

@ -0,0 +1 @@
{}

1
sample_configuration/valid.json

@ -0,0 +1 @@
{"sqlite_filename": ":memory:", "admin_token": "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef", "port": ":2828"}

1
sample_configuration/valid_with_file_database.json

@ -0,0 +1 @@
{"sqlite_filename": "testing.sqlite", "admin_token": "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef", "port": ":2828"}

152
settlers.go

@ -0,0 +1,152 @@
package settlers
import (
"bytes"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"errors"
"io/ioutil"
"net/http"
"strings"
)
func Hash(toHash string) string {
hash := sha256.Sum256([]byte(toHash))
return hex.EncodeToString(hash[:])
}
func ValidateToken(token string) (err error) {
err = ErrorInvalidToken
if len(token) != 64 {
return
}
for _, character := range token {
if !strings.Contains("abcdef01234567890", string(character)) {
return
}
}
err = nil
return
}
func CombineToken(customerToken, businessToken string) string {
return Hash(customerToken + " for " + businessToken)
}
func Enable(endpouint64, adminToken, customerToken, businessToken string) (err error) {
if err = ValidateToken(adminToken); err != nil {
return
}
if err = ValidateToken(customerToken); err != nil {
return
}
if err = ValidateToken(businessToken); err != nil {
return
}
combinedToken := CombineToken(customerToken, businessToken)
requestJson, err := json.Marshal(EnableRequest{AdminToken: adminToken, CombinedToken: combinedToken})
if err != nil {
return
}
ourURL := endpouint64 + "/enable"
ourHTTP, err := clearnetOrOnionHTTP(ourURL)
if err != nil {
return
}
_, err = handleResponse(ourHTTP.Post(ourURL, "application/json", bytes.NewReader(requestJson)))
return
}
func Balance(endpouint64, customerToken, businessToken string) (amount uint64, err error) {
if err = ValidateToken(customerToken); err != nil {
return
}
if err = ValidateToken(businessToken); err != nil {
return
}
combinedToken := CombineToken(customerToken, businessToken)
ourURL := endpouint64 + "/balance/" + combinedToken
ourHTTP, err := clearnetOrOnionHTTP(ourURL)
if err != nil {
return
}
body, err := handleResponse(ourHTTP.Get(ourURL))
if err != nil {
return
}
var jsonResponse BalanceResponse
decoder := json.NewDecoder(bytes.NewReader(body))
decoder.DisallowUnknownFields()
err = decoder.Decode(&jsonResponse)
if err != nil {
return
}
amount = jsonResponse.Amount
return
}
func Add(endpouint64, customerToken, businessToken string, amount uint64) (err error) {
if err = ValidateToken(customerToken); err != nil {
return
}
if err = ValidateToken(businessToken); err != nil {
return
}
combinedToken := CombineToken(customerToken, businessToken)
requestJson, err := json.Marshal(AddRequest{CombinedToken: combinedToken, Amount: amount})
if err != nil {
return
}
ourURL := endpouint64 + "/add"
ourHTTP, err := clearnetOrOnionHTTP(ourURL)
if err != nil {
return
}
_, err = handleResponse(ourHTTP.Post(ourURL, "application/json", bytes.NewReader(requestJson)))
return
}
func Subtract(endpouint64, customerToken, businessToken string, amount uint64) (err error) {
if err = ValidateToken(customerToken); err != nil {
return
}
if err = ValidateToken(businessToken); err != nil {
return
}
combinedToken := CombineToken(customerToken, businessToken)
requestJson, err := json.Marshal(SubtractRequest{CombinedToken: combinedToken, Amount: amount})
if err != nil {
return
}
ourURL := endpouint64 + "/subtract"
ourHTTP, err := clearnetOrOnionHTTP(ourURL)
if err != nil {
return
}
_, err = handleResponse(ourHTTP.Post(ourURL, "application/json", bytes.NewReader(requestJson)))
return
}
func handleResponse(response *http.Response, httpError error) (body []byte, err error) {
if httpError != nil {
err = httpError
return
}
body, err = ioutil.ReadAll(response.Body)
if err != nil {
return
}
response.Body.Close()
if response.StatusCode/100 != 2 {
err = errors.New(string(body))
}
return
}

43
settlers_test.go

@ -0,0 +1,43 @@
package settlers
import (
"log"
"testing"
)
// Testing constants
const validToken = "ee2ec014a1194908209736c6e89c843fe61e94f8763afef7bb12999c5da02b55"
// Non hex token
const invalidNonHexToken = "ze2ec014a1194908209736c6e89c843fe61e94f8763afef7bb12999c5da02b55"
func TestValidateToken(t *testing.T) {
var err error
err = ValidateToken(validToken)
if err != nil {
t.Errorf("Got error: %s", err.Error())
} else {
log.Print("Valid token is valid.")
}
err = ValidateToken("")
if err == nil {
t.Error("Didn't give error for empty string token.")
} else {
log.Print("Empty token invalid: ", err.Error())
}
err = ValidateToken(validToken + "a")
if err == nil {
t.Error("Didn't give error for too long of a token.")
} else {
log.Print("Too long of a token invalid: ", err.Error())
}
err = ValidateToken(invalidNonHexToken)
if err == nil {
t.Error("Didn't give error for token with bad letter.")
} else {
log.Print("Non-hex token invalid: ", err.Error())
}
}

69
test.sh

@ -0,0 +1,69 @@
#!/usr/bin/env bash
# No real need to use stderr here since it's just tests.
set -eE
shellcheck "$0"
# Before we build...
go fmt
go doc
go test
go fmt ./cmd/settlersd/
go test ./cmd/settlersd/ -v
go doc ./cmd/settlersd/
go fmt ./cmd/settlers/
go test ./cmd/settlers/ -v
go doc ./cmd/settlers/
go build
go build ./cmd/settlersd/
go build ./cmd/settlers/
strip -s settlersd
strip -s settlers
./settlersd sample_configuration/valid.json &
PID=$!
cleanup() {
echo "Cleaning up."
kill "$PID"
echo "Cleaning up."
}
trap cleanup $(seq 1 64) ERR
fail() {
echo "FAIL: $1"
cleanup
exit 1
}
curl -s --show-error --fail "http://localhost:2828/SettlersAlive" || fail "Should 200"
# Should 404
curl -s --show-error --fail "http://localhost:2828/404" && fail "Should 404"
# Should fail because bad token
curl -s --show-error --fail "http://localhost:2828/balance/badtoken" && fail "Should be bad token"
# Should fail because bad token
curl -s --show-error --fail "http://localhost:2828/balance/01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b" && fail "Should be not enabled"
./settlers enable http://localhost:2828 deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef 01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b 53c234e5e8472b6ac51c1ae1cab3fe06fad053beb8ebfd8977b010655bfdd3c3 || fail "Cannot enable"
./settlers enable http://localhost:2828 deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef 01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b 53c234e5e8472b6ac51c1ae1cab3fe06fad053beb8ebfd8977b010655bfdd3c3 && fail "Should not be able to enable twice."
./settlers balance http://localhost:2828 01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b 53c234e5e8472b6ac51c1ae1cab3fe06fad053beb8ebfd8977b010655bfdd3c3 || fail "Cannot get balance"
./settlers add http://localhost:2828 01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b 53c234e5e8472b6ac51c1ae1cab3fe06fad053beb8ebfd8977b010655bfdd3c3 100 || fail "Cannot add"
./settlers subtract http://localhost:2828 01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b 53c234e5e8472b6ac51c1ae1cab3fe06fad053beb8ebfd8977b010655bfdd3c3 40 || fail "Cannot subtract"
./settlers balance http://localhost:2828 01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b 53c234e5e8472b6ac51c1ae1cab3fe06fad053beb8ebfd8977b010655bfdd3c3 || fail "Cannot get balance"
./settlers subtract http://localhost:2828 01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b 53c234e5e8472b6ac51c1ae1cab3fe06fad053beb8ebfd8977b010655bfdd3c3 61 && fail "Can subtract too much."
cleanup
sleep 0.1
echo Success.
Loading…
Cancel
Save