package server import ( "encoding/json" "errors" "net/http" "time" peering "forge.cadoles.com/wpetit/go-http-peering" "forge.cadoles.com/wpetit/go-http-peering/crypto" ) var ( ErrInvalidAdvertisingRequest = errors.New("invalid advertising request") ErrInvalidUpdateRequest = errors.New("invalid update request") ErrPeerRejected = errors.New("peer rejected") ErrPeerIDAlreadyInUse = errors.New("peer id already in use") ErrUnauthorized = errors.New("unauthorized") ) func AdvertiseHandler(store peering.Store, funcs ...OptionFunc) http.HandlerFunc { options := createOptions(funcs...) logger := options.Logger handler := func(w http.ResponseWriter, r *http.Request) { advertising := &peering.AdvertisingRequest{} decoder := json.NewDecoder(r.Body) if err := decoder.Decode(advertising); err != nil { logger.Printf("[ERROR] %s", err) options.ErrorHandler(w, r, ErrInvalidAdvertisingRequest) return } if !options.PeerIDValidator(advertising.ID) { logger.Printf("[ERROR] %s", ErrInvalidAdvertisingRequest) options.ErrorHandler(w, r, ErrInvalidAdvertisingRequest) return } if _, err := crypto.DecodePEMToPublicKey(advertising.PublicKey); err != nil { logger.Printf("[ERROR] %s", err) options.ErrorHandler(w, r, ErrInvalidAdvertisingRequest) return } peer, err := store.Get(advertising.ID) if err == nil { logger.Printf("[ERROR] %s", ErrPeerIDAlreadyInUse) options.ErrorHandler(w, r, ErrPeerIDAlreadyInUse) return } if err != peering.ErrPeerNotFound { logger.Printf("[ERROR] %s", err) options.ErrorHandler(w, r, err) return } attrs := filterAttributes(options.PeerAttributes, advertising.Attributes) peer, err = store.Create(advertising.ID, attrs) if err != nil { logger.Printf("[ERROR] %s", err) options.ErrorHandler(w, r, err) return } if err := store.UpdateLastContact(peer.ID, r.RemoteAddr, time.Now()); err != nil { logger.Printf("[ERROR] %s", err) options.ErrorHandler(w, r, err) return } if err := store.UpdatePublicKey(peer.ID, advertising.PublicKey); err != nil { logger.Printf("[ERROR] %s", err) options.ErrorHandler(w, r, err) return } w.WriteHeader(http.StatusCreated) } return handler } func UpdateHandler(store peering.Store, funcs ...OptionFunc) http.HandlerFunc { options := createOptions(funcs...) logger := options.Logger handler := func(w http.ResponseWriter, r *http.Request) { update := &peering.UpdateRequest{} decoder := json.NewDecoder(r.Body) if err := decoder.Decode(update); err != nil { options.ErrorHandler(w, r, ErrInvalidUpdateRequest) return } peerID, err := GetPeerID(r) if err != nil { logger.Printf("[ERROR] %s", err) options.ErrorHandler(w, r, err) return } peer, err := store.Get(peerID) if err != nil { logger.Printf("[ERROR] %s", err) options.ErrorHandler(w, r, err) return } if peer == nil { logger.Printf("[ERROR] %s", ErrUnauthorized) options.ErrorHandler(w, r, ErrUnauthorized) return } if peer.Status == peering.StatusRejected { logger.Printf("[ERROR] %s", ErrPeerRejected) options.ErrorHandler(w, r, ErrPeerRejected) return } if err := store.UpdateLastContact(peer.ID, r.RemoteAddr, time.Now()); err != nil { logger.Printf("[ERROR] %s", err) options.ErrorHandler(w, r, err) return } attrs := filterAttributes(options.PeerAttributes, update.Attributes) if err := store.UpdateAttributes(peer.ID, attrs); err != nil { logger.Printf("[ERROR] %s", err) options.ErrorHandler(w, r, err) return } w.WriteHeader(http.StatusNoContent) } return handler } func PingHandler(store peering.Store, funcs ...OptionFunc) http.HandlerFunc { options := createOptions(funcs...) logger := options.Logger handler := func(w http.ResponseWriter, r *http.Request) { update := &peering.UpdateRequest{} decoder := json.NewDecoder(r.Body) if err := decoder.Decode(update); err != nil { options.ErrorHandler(w, r, ErrInvalidUpdateRequest) return } peerID, err := GetPeerID(r) if err != nil { logger.Printf("[ERROR] %s", err) options.ErrorHandler(w, r, err) return } peer, err := store.Get(peerID) if err != nil { logger.Printf("[ERROR] %s", err) options.ErrorHandler(w, r, err) return } if peer == nil { logger.Printf("[ERROR] %s", ErrUnauthorized) options.ErrorHandler(w, r, ErrUnauthorized) return } if peer.Status == peering.StatusRejected { logger.Printf("[ERROR] %s", ErrPeerRejected) options.ErrorHandler(w, r, ErrPeerRejected) return } if err := store.UpdateLastContact(peer.ID, r.RemoteAddr, time.Now()); err != nil { logger.Printf("[ERROR] %s", err) options.ErrorHandler(w, r, err) return } w.WriteHeader(http.StatusNoContent) } return handler } func DefaultErrorHandler(w http.ResponseWriter, r *http.Request, err error) { switch err { case ErrInvalidAdvertisingRequest: http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) case ErrPeerIDAlreadyInUse: http.Error(w, http.StatusText(http.StatusConflict), http.StatusConflict) case ErrUnauthorized: http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) case ErrPeerRejected: http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden) default: http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) } } func DefaultPeerIDValidator(id peering.PeerID) bool { return string(id) != "" } func filterAttributes(filters []string, attrs peering.PeerAttributes) peering.PeerAttributes { filtered := peering.PeerAttributes{} for _, key := range filters { if _, exists := attrs[key]; exists { filtered[key] = attrs[key] } } return filtered }