diff --git a/client.go b/client.go index b16befe..b32e278 100644 --- a/client.go +++ b/client.go @@ -285,6 +285,8 @@ func (c *Client) exec(line string) { c.fetchKey(parts[1:]) case "msg/send": c.sendMessage(parts[1:]) + case "msg/list": + c.listMessages(parts[1:]) default: c.err("unrecognized client command: %s", parts[0]) } @@ -497,6 +499,42 @@ func (c *Client) sendMessage(args []string) { c.info("%v", <-res) } +func (c *Client) listMessages(args []string) { + r := &ListMessages{N: 10} + promise, err := c.sendRequest(r) + if err != nil { + c.err("%v", err) + } + env := <-promise + + writeMessageId := func(id int, from string) { + c.mu.Lock() + defer c.mu.Unlock() + c.trunc() + fmt.Printf("%d\t%s\n", id, from) + c.renderLine() + } + + var res ListMessagesResponse + if err := json.Unmarshal(env.Body, &res); err != nil { + c.err("couldn't read list messages response: %v", err) + return + } + for _, item := range res { + key, err := c.rsaDecrypt(item.Key) + if err != nil { + c.err("unable to read aes key: %v", err) + return + } + from, err := c.aesDecrypt(key, item.From) + if err != nil { + c.err("unable to read message sender: %v", err) + return + } + writeMessageId(item.Id, string(from)) + } +} + func (c *Client) readTextBlock() ([]rune, error) { // god dammit what have i gotten myself into msg := make([]rune, 0, 400) @@ -642,6 +680,10 @@ func (c *Client) aesEncrypt(key []byte, ptxt []byte) ([]byte, error) { return ctxt, nil } +func (c *Client) rsaDecrypt(ctext []byte) ([]byte, error) { + return rsa.DecryptPKCS1v15(rand.Reader, c.key, ctext) +} + func connect() { if !terminal.IsTerminal(0) { exit(1, "yeah, this only works from a TTY for now, sry.") diff --git a/db.go b/db.go index bf54b9a..cda1e3f 100644 --- a/db.go +++ b/db.go @@ -1,6 +1,7 @@ package main import ( + "bytes" "crypto/rsa" "encoding/json" "fmt" @@ -51,6 +52,39 @@ func (db *userdb) nextKey(prefix string) (string, error) { return fmt.Sprintf("%s%s", prefix, encodeInt(id)), nil } +// iterates through a range of values, starting with a prefix, parsing the +// lexnum part on each key, and calling the callback for each value with the +// value's associated number in its lexical series +func (db *userdb) collect(prefix []byte, n int, fn func(n int, v []byte) error) error { + r := util.BytesPrefix(prefix) + it := db.NewIterator(r, nil) + defer it.Release() + + var step func() bool + if n < 0 { + if !it.Last() { + return fmt.Errorf("collect unable to advance iterator to last") + } + step = it.Prev + n = -n + } else { + step = it.Next + } + + for i := 0; it.Valid() && i < n; i++ { + id_s := string(bytes.TrimPrefix(it.Key(), prefix)) + id, err := decodeInt(id_s) + if err != nil { + return fmt.Errorf("unable to collect on prefix %s: %v", prefix, err) + } + if err := fn(id, it.Value()); err != nil { + return fmt.Errorf("callback error in collect: %v", err) + } + step() + } + return nil +} + func getUserDB(nick string, create bool) (*userdb, error) { if db, ok := openDBs[nick]; ok { return &db, nil diff --git a/server.go b/server.go index b78f64f..8beb82e 100644 --- a/server.go +++ b/server.go @@ -54,6 +54,8 @@ func (s *serverConnection) handleRequest(request Envelope) error { return s.handleKeyRequest(request.Id, request.Body) case "message": return s.handleMessageRequest(request.Id, request.Body) + case "list-messages": + return s.handleListMessagesRequest(request.Id, request.Body) default: return fmt.Errorf("no such request type: %v", request.Kind) } @@ -233,6 +235,33 @@ func (s *serverConnection) handleMessageRequest(requestId int, body json.RawMess return s.sendResponse(requestId, Bool(true)) } +func (s *serverConnection) handleListMessagesRequest(requestId int, body json.RawMessage) error { + var req ListMessages + if err := json.Unmarshal(body, &req); err != nil { + error_log.Printf("unable to read message request: %v", err) + return err + } + + prefix := []byte("messages/") + messages := make(ListMessagesResponse, 0, 10) + fn := func(n int, v []byte) error { + var msg Message + if err := json.Unmarshal(v, &msg); err != nil { + return fmt.Errorf("unable to parse message blob: %v", err) + } + messages = append(messages, ListMessagesResponseItem{ + Id: n, + Key: msg.Key, + From: msg.From, + }) + return nil + } + if err := s.db.collect(prefix, -10, fn); err != nil { + return fmt.Errorf("error handling listmessages request: %v", err) + } + return s.sendResponse(requestId, messages) +} + func (s *serverConnection) openDB() error { db, err := getUserDB(s.nick, true) if err != nil {