package main import ( "crypto/sha256" "encoding/hex" "encoding/json" "flag" "fmt" "log" "math/rand" "net/http" "os" "strconv" "strings" "time" ) var chunkSize = 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() if uc.Login == "root" { uc.Login = "nemunaire" log.Println("Good chunk from root given to", uc.Login) } else { 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)) } } type Score struct { Login string `json:"login"` NbChunk int `json:"chunks"` Time time.Time `json:"time"` } 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()) isJson := false if strings.HasSuffix(r.URL.Path, "/scores.json") { w.Header().Set("Content-Type", "application/json") isJson = true } else { 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) var scores []Score for rows.Next() { var score Score if err := rows.Scan(&score.Login, &score.NbChunk, &score.Time); err == nil { if isJson { scores = append(scores, score) } else { w.Write([]byte(fmt.Sprintf("%q,%d,%q\n", score.Login, score.NbChunk, score.Time))) } } } if isJson { res, _ := json.Marshal(scores) w.Write(res) } } } func main() { if v, exists := os.LookupEnv("CHUNKSIZE"); exists { if cs, err := strconv.Atoi(v); err == nil { chunkSize = cs } } 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.IntVar(&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("/scores.json", 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) } }