summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--client/client.go12
-rw-r--r--cmd/client/main.go12
-rw-r--r--core/data.go6
-rw-r--r--core/payload.go49
-rw-r--r--docs/rfc.html68
-rw-r--r--docs/rfc.md26
-rw-r--r--docs/rfc.txt37
-rw-r--r--server/storage.go6
-rw-r--r--server/user.go22
-rw-r--r--storage/storage.go29
10 files changed, 258 insertions, 9 deletions
diff --git a/client/client.go b/client/client.go
index ba53ec9..a9dfb2d 100644
--- a/client/client.go
+++ b/client/client.go
@@ -12,6 +12,7 @@ import (
type Handler interface {
HandleMessage(msg core.Message)
+ HandleListItem(list core.ListItem)
HandleError(err error)
}
@@ -110,6 +111,15 @@ func (c *Client) GetHistory(channel string, since time.Time, count, offset int)
return core.Send(c.conn, hist)
}
+func (c *Client) GetList(count, offset int) error {
+ list := core.List{
+ Count: int64(count),
+ Offset: int64(offset),
+ }
+
+ return core.Send(c.conn, list)
+}
+
func (c *Client) read() {
for {
payload, err := core.Read(c.conn)
@@ -128,5 +138,7 @@ func (c *Client) handlePayload(payload any) {
switch v := payload.(type) {
case core.Message:
c.h.HandleMessage(v)
+ case core.ListItem:
+ c.h.HandleListItem(v)
}
}
diff --git a/cmd/client/main.go b/cmd/client/main.go
index f135a69..e15d917 100644
--- a/cmd/client/main.go
+++ b/cmd/client/main.go
@@ -24,6 +24,10 @@ func (h *Handler) HandleMessage(msg core.Message) {
fmt.Printf("%s\t%s -> %s %s\n", msg.Timestamp.Format("2006/01/02 15:04:05.000"), msg.Source, msg.Target, msg.Content)
}
+func (h *Handler) HandleListItem(li core.ListItem) {
+ fmt.Println(li.Address)
+}
+
func (h *Handler) HandleError(err error) {
log.Println("client error:", err)
}
@@ -33,6 +37,7 @@ func main() {
prompt.Commands["join"] = join
prompt.Commands["leave"] = leave
prompt.Commands["history"] = history
+ prompt.Commands["list"] = list
var cfg client.Config
@@ -81,3 +86,10 @@ func history(args []string) {
log.Println("cannot read channel history:", err)
}
}
+
+func list(args []string) {
+ err := c.GetList(99999999, 0)
+ if err != nil {
+ log.Println("cannot channels list:", err)
+ }
+}
diff --git a/core/data.go b/core/data.go
index 6871a89..4705c75 100644
--- a/core/data.go
+++ b/core/data.go
@@ -25,6 +25,8 @@ const (
PayloadServerAuth = 0x06
PayloadUsermode = 0x07
PayloadHistory = 0x08
+ PayloadList = 0x09
+ PayloadListItem = 0x10
PayloadTest = 0xFF
)
@@ -146,6 +148,10 @@ func Decode(buf io.Reader) (any, error) {
return DecodeUsermode(buf)
case PayloadHistory:
return DecodeHistory(buf)
+ case PayloadList:
+ return DecodeList(buf)
+ case PayloadListItem:
+ return DecodeListItem(buf)
case PayloadTest:
return DecodeTest(buf)
default:
diff --git a/core/payload.go b/core/payload.go
index be9d844..aad21ee 100644
--- a/core/payload.go
+++ b/core/payload.go
@@ -226,6 +226,55 @@ func DecodeHistory(buf io.Reader) (History, error) {
return h, nil
}
+type List struct {
+ Count int64
+ Offset int64
+}
+
+func (l List) Wrap() (PayloadType, []any) {
+ return PayloadList, []any{
+ l.Count,
+ l.Offset,
+ }
+}
+
+func DecodeList(buf io.Reader) (List, error) {
+ var l List
+
+ err := decodeNumeric(buf, &l.Count)
+ if err != nil {
+ return l, err
+ }
+
+ err = decodeNumeric(buf, &l.Offset)
+ if err != nil {
+ return l, err
+ }
+
+ return l, nil
+}
+
+type ListItem struct {
+ Address string
+}
+
+func (l ListItem) Wrap() (PayloadType, []any) {
+ return PayloadListItem, []any{
+ l.Address,
+ }
+}
+
+func DecodeListItem(buf io.Reader) (ListItem, error) {
+ var l ListItem
+
+ err := decodeString(buf, &l.Address)
+ if err != nil {
+ return l, err
+ }
+
+ return l, nil
+}
+
type Test struct {
Num1 uint8
Time1 time.Time
diff --git a/docs/rfc.html b/docs/rfc.html
index 373526e..297f593 100644
--- a/docs/rfc.html
+++ b/docs/rfc.html
@@ -1367,6 +1367,12 @@ SOLEC system.<a href="#section-abstract-1" class="pilcrow">¶</a></p>
<li class="compact toc ulBare ulEmpty" id="section-toc.1-1.2.2.4.2.9">
<p id="section-toc.1-1.2.2.4.2.9.1"><a href="#section-2.4.9" class="auto internal xref">2.4.9</a>.  <a href="#name-test" class="internal xref">Test</a></p>
</li>
+ <li class="compact toc ulBare ulEmpty" id="section-toc.1-1.2.2.4.2.10">
+ <p id="section-toc.1-1.2.2.4.2.10.1"><a href="#section-2.4.10" class="auto internal xref">2.4.10</a>. <a href="#name-list" class="internal xref">List</a></p>
+</li>
+ <li class="compact toc ulBare ulEmpty" id="section-toc.1-1.2.2.4.2.11">
+ <p id="section-toc.1-1.2.2.4.2.11.1"><a href="#section-2.4.11" class="auto internal xref">2.4.11</a>. <a href="#name-listitem" class="internal xref">ListItem</a></p>
+</li>
</ul>
</li>
<li class="compact toc ulBare ulEmpty" id="section-toc.1-1.2.2.5">
@@ -1623,10 +1629,20 @@ Text is encoded using UTF-8.<a href="#section-2.3.3-1" class="pilcrow">¶</a></p
</tr>
<tr>
<td class="text-left" rowspan="1" colspan="1">0x08</td>
- <td class="text-left" rowspan="1" colspan="1">History.</td>
+ <td class="text-left" rowspan="1" colspan="1">History</td>
<td class="text-left" rowspan="1" colspan="1">C</td>
</tr>
<tr>
+ <td class="text-left" rowspan="1" colspan="1">0x09</td>
+ <td class="text-left" rowspan="1" colspan="1">List</td>
+ <td class="text-left" rowspan="1" colspan="1">CE</td>
+ </tr>
+ <tr>
+ <td class="text-left" rowspan="1" colspan="1">0x10</td>
+ <td class="text-left" rowspan="1" colspan="1">ListItem</td>
+ <td class="text-left" rowspan="1" colspan="1">S</td>
+ </tr>
+ <tr>
<td class="text-left" rowspan="1" colspan="1">0xFF</td>
<td class="text-left" rowspan="1" colspan="1">Test</td>
<td class="text-left" rowspan="1" colspan="1">R</td>
@@ -1981,6 +1997,56 @@ should ignore this kind of payload.<a href="#section-2.4.9-1" class="pilcrow">¶
</table>
</section>
</div>
+<div id="list">
+<section id="section-2.4.10">
+ <h4 id="name-list">
+<a href="#section-2.4.10" class="section-number selfRef">2.4.10. </a><a href="#name-list" class="section-name selfRef">List</a>
+ </h4>
+<p id="section-2.4.10-1">Request list of channels and users that client can send messages to. Number of retrieved items can be limited using <em>count</em> and <em>offset</em> fields.<a href="#section-2.4.10-1" class="pilcrow">¶</a></p>
+<table class="center" id="table-13">
+ <caption><a href="#table-13" class="selfRef">Table 13</a></caption>
+<thead>
+ <tr>
+ <th class="text-left" rowspan="1" colspan="1">Type</th>
+ <th class="text-left" rowspan="1" colspan="1">Name</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td class="text-left" rowspan="1" colspan="1">int64</td>
+ <td class="text-left" rowspan="1" colspan="1">count</td>
+ </tr>
+ <tr>
+ <td class="text-left" rowspan="1" colspan="1">int64</td>
+ <td class="text-left" rowspan="1" colspan="1">offset</td>
+ </tr>
+ </tbody>
+ </table>
+</section>
+</div>
+<div id="listitem">
+<section id="section-2.4.11">
+ <h4 id="name-listitem">
+<a href="#section-2.4.11" class="section-number selfRef">2.4.11. </a><a href="#name-listitem" class="section-name selfRef">ListItem</a>
+ </h4>
+<p id="section-2.4.11-1"><em>ListItem</em> is send as a reply to <em>List</em> request. Multiple list items are sent in separate payloads.<a href="#section-2.4.11-1" class="pilcrow">¶</a></p>
+<table class="center" id="table-14">
+ <caption><a href="#table-14" class="selfRef">Table 14</a></caption>
+<thead>
+ <tr>
+ <th class="text-left" rowspan="1" colspan="1">Type</th>
+ <th class="text-left" rowspan="1" colspan="1">Name</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td class="text-left" rowspan="1" colspan="1">string</td>
+ <td class="text-left" rowspan="1" colspan="1">address</td>
+ </tr>
+ </tbody>
+ </table>
+</section>
+</div>
</section>
</div>
<div id="sequential-operations">
diff --git a/docs/rfc.md b/docs/rfc.md
index d6c8085..ecfbdc0 100644
--- a/docs/rfc.md
+++ b/docs/rfc.md
@@ -161,9 +161,11 @@ Payload type attributes describes following characteristics:
| 0x03 | Handshake | SC |
| 0x04 | UserAuth | C |
| 0x05 | Message | SC |
-| 0x06 | ServerAuth | S |
-| 0x07 | UserMode | C |
-| 0x08 | History. | C |
+| 0x06 | ServerAuth | S |
+| 0x07 | UserMode | C |
+| 0x08 | History | C |
+| 0x09 | List | CE |
+| 0x10 | ListItem | S |
| 0xFF | Test | R |
### Success
@@ -273,6 +275,24 @@ should ignore this kind of payload.
| string | str3 |
| uint64 | num4 |
+### List
+
+Request list of channels and users that client can send messages to. Number of retrieved items can be limited using *count* and *offset* fields.
+
+| Type | Name |
+|-----------|-----------------|
+| int64 | count |
+| int64 | offset |
+
+### ListItem
+
+*ListItem* is send as a reply to *List* request. Multiple list items are sent in separate payloads.
+
+| Type | Name |
+|--------|---------|
+| string | address |
+
+
## Sequential operations
Some operations require multiple rounds of communication.
diff --git a/docs/rfc.txt b/docs/rfc.txt
index 335ea3d..173c1bd 100644
--- a/docs/rfc.txt
+++ b/docs/rfc.txt
@@ -35,6 +35,8 @@ Table of Contents
2.4.7. Usermode
2.4.8. History
2.4.9. Test
+ 2.4.10. List
+ 2.4.11. ListItem
2.5. Sequential operations
2.6. Client-Server connection initialisation
2.7. Exchanging messages between servers
@@ -180,7 +182,11 @@ Table of Contents
+------+------------+------------+
| 0x07 | UserMode | C |
+------+------------+------------+
- | 0x08 | History. | C |
+ | 0x08 | History | C |
+ +------+------------+------------+
+ | 0x09 | List | CE |
+ +------+------------+------------+
+ | 0x10 | ListItem | S |
+------+------------+------------+
| 0xFF | Test | R |
+------+------------+------------+
@@ -368,6 +374,35 @@ Table of Contents
Table 12
+2.4.10. List
+
+ Request list of channels and users that client can send messages to.
+ Number of retrieved items can be limited using _count_ and _offset_
+ fields.
+
+ +=======+========+
+ | Type | Name |
+ +=======+========+
+ | int64 | count |
+ +-------+--------+
+ | int64 | offset |
+ +-------+--------+
+
+ Table 13
+
+2.4.11. ListItem
+
+ _ListItem_ is send as a reply to _List_ request. Multiple list items
+ are sent in separate payloads.
+
+ +========+=========+
+ | Type | Name |
+ +========+=========+
+ | string | address |
+ +--------+---------+
+
+ Table 14
+
2.5. Sequential operations
Some operations require multiple rounds of communication. In this
diff --git a/server/storage.go b/server/storage.go
index 99402c8..bffb783 100644
--- a/server/storage.go
+++ b/server/storage.go
@@ -8,8 +8,8 @@ import (
type Storage interface {
AddMessage(msg core.Message) (err error)
- GetHistory(target string, since time.Time, num, offset int) (history []core.Message, err error)
- GetHistoryUser(user1, user2 string, since time.Time, num, offset int) (history []core.Message, err error)
+ GetHistory(target string, since time.Time, count, offset int) (history []core.Message, err error)
+ GetHistoryUser(user1, user2 string, since time.Time, count, offset int) (history []core.Message, err error)
SetUser(user core.UserData) error
DelUser(name string) error
@@ -17,5 +17,7 @@ type Storage interface {
SetPermission(data core.PermissionData) error
GetPermission(user, channel string) (core.PermissionData, error)
+
GetChannelUsers(channel string) ([]string, error)
+ GetUserChannels(user string, count, offset int) ([]string, error)
}
diff --git a/server/user.go b/server/user.go
index 69b7ced..5e29c8d 100644
--- a/server/user.go
+++ b/server/user.go
@@ -126,6 +126,8 @@ func (s *Server) handleUserPayload(user *User, sender net.Conn, payload any) err
return s.handleUsermode(user, sender, v)
case core.History:
return s.handleHistory(user, sender, v)
+ case core.List:
+ return s.handleList(user, sender, v)
default:
return core.ErrUnexpectedPayloadType
}
@@ -206,3 +208,23 @@ func (s *Server) handleHistory(user *User, conn net.Conn, hist core.History) err
return nil
}
+
+func (s *Server) handleList(user *User, conn net.Conn, list core.List) error {
+ channels, err := s.Storage.GetUserChannels(user.Addr, int(list.Count), int(list.Offset))
+ if err != nil {
+ return fmt.Errorf("cannot get user channels list: %v", err)
+ }
+
+ for _, c := range channels {
+ li := core.ListItem{c}
+ data, err := core.Encode(li)
+ if err != nil {
+ return err
+ }
+ if _, err := conn.Write(data); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
diff --git a/storage/storage.go b/storage/storage.go
index a32c4a0..18a968d 100644
--- a/storage/storage.go
+++ b/storage/storage.go
@@ -36,7 +36,7 @@ func InitDb(path string) (*Database, error) {
func (db *Database) AddMessage(msg core.Message) (err error) {
_, err = db.Exec(
"INSERT INTO messages (source, target, timestamp, content) VALUES (?, ?, ?, ?);",
- msg.Source, msg.Target, msg.Timestamp.Unix(), msg.Content,
+ msg.Source, msg.Target, msg.Timestamp.UnixMilli(), msg.Content,
)
return err
@@ -84,7 +84,7 @@ func (db *Database) getHistory(rows *sql.Rows, err error) ([]core.Message, error
if err = rows.Scan(&msg.Source, &msg.Target, &timestamp, &msg.Content); err != nil {
return history, err
}
- msg.Timestamp = time.Unix(timestamp, 0)
+ msg.Timestamp = time.UnixMilli(timestamp)
history = append(history, msg)
}
@@ -169,6 +169,31 @@ func (db *Database) GetChannelUsers(channel string) (users []string, err error)
return users, nil
}
+func (db *Database) GetUserChannels(user string, count, offset int) (channels []string, err error) {
+ rows, err := db.Query("SELECT channel FROM permissions WHERE user = ? AND write = 1;", user)
+ defer func() {
+ if rows == nil {
+ return
+ }
+ if err := rows.Close(); err != nil {
+ log.Println("cannot close database row:", err)
+ }
+ }()
+ if err != nil {
+ return channels, err
+ }
+
+ for rows.Next() {
+ var channel string
+ if err := rows.Scan(&channel); err != nil {
+ return channels, err
+ }
+ channels = append(channels, channel)
+ }
+
+ return channels, nil
+}
+
func itob(v int) bool {
if v == 1 {
return true