This repository has been archived on 2024-03-28. You can view files and clone it, but cannot push or open issues or pull requests.
chunkvalidator/main.go

150 lines
3.7 KiB
Go

package main
import (
"crypto/sha256"
"encoding/hex"
"encoding/json"
"flag"
"fmt"
"log"
"math/rand"
"net/http"
"strings"
"time"
)
var chunkSize uint = 2
var currentChunk string
func init() {
rand.Seed(time.Now().UnixNano())
}
func newChunk() {
bid := make([]byte, 4)
rand.Read(bid)
currentChunk = hex.EncodeToString(bid)[:chunkSize]
}
type uploadChunk struct {
Proof string `json:"proof"`
Login string `json:"login"`
}
func checkChunk(rndb string) bool {
rnd, err := hex.DecodeString(rndb)
if err != nil {
return false
}
sum := sha256.Sum256(append(rnd, currentChunk[0]))
return strings.HasPrefix(hex.EncodeToString(sum[:]), currentChunk[1:])
}
func ServeChunk(w http.ResponseWriter, r *http.Request) {
if addr := r.Header.Get("X-Forwarded-For"); addr != "" {
r.RemoteAddr = addr
}
log.Printf("Handling %s request from %s: %s [%s]\n", r.Method, r.RemoteAddr, r.URL.Path, r.UserAgent())
w.Header().Set("Content-Type", "text/plain")
if r.Method == "POST" {
if r.ContentLength < 0 || r.ContentLength > 6553600 {
http.Error(w, fmt.Sprintf("{errmsg:\"Request too large or request size unknown\"}"), http.StatusRequestEntityTooLarge)
return
}
var body []byte
if r.ContentLength > 0 {
tmp := make([]byte, 1024)
for {
n, err := r.Body.Read(tmp)
for j := 0; j < n; j++ {
body = append(body, tmp[j])
}
if err != nil || n <= 0 {
break
}
}
}
var uc uploadChunk
if err := json.Unmarshal(body, &uc); err != nil {
log.Println(err)
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(err.Error()))
} else if checkChunk(uc.Proof) {
newChunk()
log.Println("Good chunk from", uc.Login)
if _, err := DBExec("INSERT INTO chunks (time, username, chunk, proof) VALUES (?, ?, ?, ?)", time.Now(), uc.Login, currentChunk, uc.Proof); err != nil {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(err.Error()))
} else {
w.WriteHeader(http.StatusOK)
w.Write([]byte("true"))
}
} else {
log.Println("BAD chunk from", uc.Login)
}
} else {
w.WriteHeader(http.StatusOK)
w.Write([]byte(currentChunk))
}
}
func ServeScores(w http.ResponseWriter, r *http.Request) {
if addr := r.Header.Get("X-Forwarded-For"); addr != "" {
r.RemoteAddr = addr
}
log.Printf("Handling %s request from %s: %s [%s]\n", r.Method, r.RemoteAddr, r.URL.Path, r.UserAgent())
w.Header().Set("Content-Type", "text/plain")
if rows, err := DBQuery("SELECT username, COUNT(id_chunk) AS nbchunk, MAX(time) AS time FROM chunks GROUP BY username ORDER BY nbchunk DESC"); err != nil {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(err.Error()))
} else {
defer rows.Close()
w.WriteHeader(http.StatusOK)
for rows.Next() {
var login string
var nbchunk int
var time time.Time
if err := rows.Scan(&login, &nbchunk, &time); err == nil {
w.Write([]byte(fmt.Sprintf("%q,%d,%q\n", login, nbchunk, time)))
}
}
}
}
func main() {
var bind = flag.String("bind", "0.0.0.0:8081", "Bind port/socket")
var dsn = flag.String("dsn", DSNGenerator(), "DSN to connect to the MySQL server")
flag.UintVar(&chunkSize, "chunkSize", chunkSize, "Taille du chunk à trouver")
flag.Parse()
newChunk()
// Database connection
log.Println("Opening database...")
if err := DBInit(*dsn); err != nil {
log.Fatal("Cannot open the database: ", err)
}
defer DBClose()
log.Println("Creating database...")
if err := DBCreate(); err != nil {
log.Fatal("Cannot create database: ", err)
}
log.Println("Registering handlers...")
http.HandleFunc("/scores", ServeScores)
http.HandleFunc("/chunk", ServeChunk)
log.Println(fmt.Sprintf("Ready, listening on %s", *bind))
if err := http.ListenAndServe(*bind, nil); err != nil {
log.Fatal("Unable to listen and serve: ", err)
}
}