package main import ( "crypto/ed25519" "encoding/base64" "encoding/gob" "errors" "log" "net/http" "os" "strings" ) type user struct { dhtVal []byte phase int64 servers []string log []string nextIndex map[string]int } var users map[string]*user // Verify that a body was signed by this ID func verify(id string, body []byte) error { b, err := base64.RawURLEncoding.DecodeString(id) if err != nil { return err } if len(body) < ed25519.SignatureSize { return errors.New("body too short") } message := body[:len(body)-ed25519.SignatureSize] sig := body[len(body)-ed25519.SignatureSize:] if !ed25519.Verify(ed25519.PublicKey(b), message, sig) { return errors.New("signature verification failed") } return nil } // Persist a user's data to disk func persist(id string) { writer, err := os.OpenFile(dataDir+"/"+id+"/gob", os.O_CREATE|os.O_WRONLY, 0644) if err != nil { return } enc := gob.NewEncoder(writer) enc.Encode(users[id]) } // Reconfigure a user based on a DHT get func reconfigure(id string, dhtVal []byte) { mu.Lock() defer mu.Unlock() user := users[id] if timestamp(dhtVal) < timestamp(user.dhtVal) { return } user.dhtVal = dhtVal servers := strings.Split(string(dhtVal[8:len(dhtVal)-ed25519.SignatureSize]), "\n") log.Printf("Reconfiguring %s %s", id, servers) user.servers = servers if servers[0] == me { if user.nextIndex == nil { user.nextIndex = make(map[string]int) } for i, server := range servers { if _, ok := user.nextIndex[server]; !ok && i > 0 { user.nextIndex[server] = len(user.log) go replicate(id, server) } } } inServers := false for _, server := range servers { if server == me { inServers = true } } persist(id) if !inServers { delete(users, id) _ = os.RemoveAll(dataDir + "/" + id) } } // Handle user configuration changes func userHandler(w http.ResponseWriter, r *http.Request) { id := r.URL.Path[6:] // Resolve ID to server list val := dhtGet(id, false) if verify(id, val) != nil { w.WriteHeader(http.StatusNotFound) return } mu.Lock() if _, ok := users[id]; !ok { // Add user users[id] = &user{dhtVal: val, phase: 0} os.Mkdir(dataDir+"/"+id, 0755) persist(id) } mu.Unlock() reconfigure(id, val) w.WriteHeader(http.StatusOK) }