taler-mailbox

Service for asynchronous wallet-to-wallet payment messages
Log | Files | Refs | Submodules | README | LICENSE

commit 78a90d5d629d3aa8ca85d6c5aa4ee6dd5f76642e
parent ebb1f9a89b279fd6fdf0297c9c52cdf3a08ba1cf
Author: Martin Schanzenbach <schanzen@gnunet.org>
Date:   Tue, 22 Apr 2025 14:05:37 +0200

make body size configurable

Diffstat:
Mpkg/rest/mailbox.go | 111++++++++++++++++++++++++++++++++++---------------------------------------------
1 file changed, 48 insertions(+), 63 deletions(-)

diff --git a/pkg/rest/mailbox.go b/pkg/rest/mailbox.go @@ -28,7 +28,7 @@ import ( "log" "net/http" "os" - "strings" + "strings" "time" gnunetutil "git.gnunet.org/gnunet-go.git/pkg/util" @@ -42,6 +42,8 @@ import ( talerutil "taler.net/taler-go.git/pkg/util" ) +type TalerMailboxBoxySize int + // Mailbox is the primary object of the Mailbox service type Mailbox struct { @@ -54,27 +56,13 @@ type Mailbox struct { // Our configuration from the config.json Cfg *ini.File + // Fixed size of message bodies + MessageBodyBytes int64 `json:"message_body_bytes"` + // Merchant object Merchant merchant.Merchant } -type IdentityMessage struct { - // Internal ID primary key (GORM thingy). - // Not returned in JSON - ID int64 `json:"-"` - - // Public DH key used to encrypt the body. Must be fresh - // and only used once (ephemeral). - // FIXME: Do we want to use HPKE DHKEM X25519 + ChaChaPoly? - EphemeralKey string `json:"ephemeral_key"` - - // Encrypted message. Must be exactly 256-32 bytes long. - Body string - - // Order ID, if the client recently paid for this message. - //order_id?: string; -} - // VersionResponse is the JSON response of the /config enpoint type VersionResponse struct { // libtool-style representation of the Merchant protocol version, see @@ -88,9 +76,12 @@ type VersionResponse struct { // fee for one month of registration MessageFee string `json:"message_fee"` + // Fixed size of message bodies + MessageBodyBytes int64 `json:"message_body_bytes"` + // How long will the service store a message // before giving up - DeliveryPeriod uint64 `josn:"delivery_period"` + DeliveryPeriod uint64 `json:"delivery_period"` } // MessageDeletionRequest is used to request the deletion of already received @@ -123,7 +114,7 @@ type MailboxRateLimitedResponse struct { Hint string `json:"hint"` } -type inboxEntry struct { +type InboxEntry struct { // ORM gorm.Model `json:"-"` @@ -131,7 +122,7 @@ type inboxEntry struct { EphemeralKey string `json:"ephemeral_key"` // Encrypted message. Must be exactly 256-32 bytes long. - Body string + Body []byte // Hash of the inbox for this entry HMailbox string @@ -144,17 +135,18 @@ type inboxEntry struct { } func (m *Mailbox) configResponse(w http.ResponseWriter, r *http.Request) { - dpStr := m.Cfg.Section("mailbox").Key("delivery_period").MustString("1w") + dpStr := m.Cfg.Section("mailbox").Key("delivery_period").MustString("72h") dp, err := time.ParseDuration(dpStr) if err != nil { log.Fatal(err) } cfg := VersionResponse{ - Version: "0:0:0", - Name: "taler-mailbox", - MessageFee: m.Cfg.Section("mailbox").Key("message_fee").MustString("KUDOS:1"), - DeliveryPeriod: uint64(dp.Microseconds()), + Version: "0:0:0", + Name: "taler-mailbox", + MessageBodyBytes: m.MessageBodyBytes, + MessageFee: m.Cfg.Section("mailbox").Key("message_fee").MustString("KUDOS:1"), + DeliveryPeriod: uint64(dp.Microseconds()), } w.Header().Set("Content-Type", "application/json") response, _ := json.Marshal(cfg) @@ -164,7 +156,7 @@ func (m *Mailbox) configResponse(w http.ResponseWriter, r *http.Request) { func (m *Mailbox) getMessagesResponse(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) //to, toSet := vars["timeout_ms"] - var entries []inboxEntry + var entries []InboxEntry // FIXME rate limit // FIXME timeout // FIXME possibly limit results here @@ -177,39 +169,38 @@ func (m *Mailbox) getMessagesResponse(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNoContent) return } - // Add ETag of first message ID - etag := entries[0].ID - w.Header().Add("ETag", fmt.Sprintf("%d", etag)) + // Add ETag of first message ID + etag := entries[0].ID + w.Header().Add("ETag", fmt.Sprintf("%d", etag)) for _, entry := range entries { eph, err := gnunetutil.DecodeStringToBinary(entry.EphemeralKey, 32) if err != nil { w.WriteHeader(http.StatusInternalServerError) return } - body, err := gnunetutil.DecodeStringToBinary(entry.Body, 256-32) - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - return - } w.Write(eph) - w.Write(body) + w.Write(entry.Body) } w.WriteHeader(http.StatusOK) } func (m *Mailbox) sendMessageResponse(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) - var msg IdentityMessage - var entry inboxEntry + var entry InboxEntry + var body = make ([]byte, m.MessageBodyBytes) if r.Body == nil { http.Error(w, "No request body", 400) return } - err := json.NewDecoder(r.Body).Decode(&msg) - if err != nil { - w.WriteHeader(http.StatusInternalServerError) + if r.ContentLength != m.MessageBodyBytes { + w.WriteHeader(http.StatusBadRequest) return - } + } + _, err := r.Body.Read(body) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + return + } tx := m.Db.Where("h_mailbox = ?", vars["h_mailbox"]) // FIXME max messages from config // FIXME unclear if this is how the API is defined @@ -217,13 +208,12 @@ func (m *Mailbox) sendMessageResponse(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusTooManyRequests) return } - err = m.Db.First(&entry, "h_mailbox = ? AND ephemeral_key = ? AND body = ?", vars["h_mailbox"], msg.EphemeralKey, msg.Body).Error + err = m.Db.First(&entry, "h_mailbox = ? AND body = ?", vars["h_mailbox"], body).Error // FIXME actual cost cost, _ := talerutil.ParseAmount("KUDOS:1") if err != nil { entry.HMailbox = vars["h_mailbox"] - entry.EphemeralKey = msg.EphemeralKey - entry.Body = msg.Body + entry.Body = body entry.Read = false } if len(entry.OrderID) == 0 { @@ -258,7 +248,7 @@ func (m *Mailbox) sendMessageResponse(w http.ResponseWriter, r *http.Request) { func (m *Mailbox) deleteMessagesResponse(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) var msg MessageDeletionRequest - var entries []inboxEntry + var entries []InboxEntry if r.Body == nil { http.Error(w, "No request body", 400) return @@ -268,15 +258,15 @@ func (m *Mailbox) deleteMessagesResponse(w http.ResponseWriter, r *http.Request) w.WriteHeader(http.StatusInternalServerError) return } - etag_expected := r.Header.Get("If-Match") - if etag_expected == "" { - http.Error(w, "If-Match header missing", 400) - return - } - if strings.Contains(etag_expected, ",") { - http.Error(w, "If-Match contains multiple values", 400) - return - } + etag_expected := r.Header.Get("If-Match") + if etag_expected == "" { + http.Error(w, "If-Match header missing", 400) + return + } + if strings.Contains(etag_expected, ",") { + http.Error(w, "If-Match contains multiple values", 400) + return + } pkey, err := gnunetutil.DecodeStringToBinary(vars["mailbox"], 32) if err != nil { w.WriteHeader(http.StatusBadRequest) @@ -324,13 +314,8 @@ func (m *Mailbox) deleteMessagesResponse(w http.ResponseWriter, r *http.Request) w.WriteHeader(http.StatusInternalServerError) return } - body, err := gnunetutil.DecodeStringToBinary(entry.Body, 256-32) - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - return - } h_all.Write(eph) - h_all.Write(body) + h_all.Write(entry.Body) } if !bytes.Equal(h_all.Sum(nil), checksum) { w.WriteHeader(http.StatusNotFound) @@ -375,7 +360,7 @@ func (m *Mailbox) Initialize(cfgfile string) { if m.Cfg.Section("mailbox").Key("production").MustBool(false) { fmt.Println("Production mode enabled") } - + m.MessageBodyBytes = m.Cfg.Section("mailbox").Key("message_body_bytes").MustInt64(256) psqlconn := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable", m.Cfg.Section("mailbox-pq").Key("host").MustString("localhost"), m.Cfg.Section("mailbox-pq").Key("port").MustInt64(5432), @@ -389,7 +374,7 @@ func (m *Mailbox) Initialize(cfgfile string) { panic(err) } m.Db = _db - if err := m.Db.AutoMigrate(&inboxEntry{}); err != nil { + if err := m.Db.AutoMigrate(&InboxEntry{}); err != nil { panic(err) }