go-ssb-room/muxrpc/handlers/tunnel/server/state.go

172 lines
3.7 KiB
Go

// SPDX-FileCopyrightText: 2021 The NGI Pointer Secure-Scuttlebutt Team of 2020/2021
//
// SPDX-License-Identifier: MIT
package server
import (
"context"
"encoding/json"
"errors"
"fmt"
"sync"
"time"
"github.com/ssbc/go-ssb-room/v2/internal/network"
"github.com/ssbc/go-ssb-room/v2/roomdb"
"github.com/ssbc/go-ssb-room/v2/roomstate"
"github.com/ssbc/go-muxrpc/v2"
kitlog "go.mindeco.de/log"
)
type Handler struct {
logger kitlog.Logger
netInfo network.ServerEndpointDetails
state *roomstate.Manager
membersdb roomdb.MembersService
config roomdb.RoomConfig
}
type MetadataReply struct {
Name string `json:"name"`
Membership bool `json:"membership"`
Features []string `json:"features"`
}
func (h *Handler) metadata(ctx context.Context, req *muxrpc.Request) (interface{}, error) {
ref, err := network.GetFeedRefFromAddr(req.RemoteAddr())
if err != nil {
return nil, err
}
pm, err := h.config.GetPrivacyMode(ctx)
if err != nil {
return nil, err
}
var reply MetadataReply
reply.Name = h.netInfo.Domain
// check if caller is a member
if _, err := h.membersdb.GetByFeed(ctx, ref); err != nil {
if !errors.Is(err, roomdb.ErrNotFound) {
return nil, err
}
// already initialized as false, just to be clear
reply.Membership = false
} else {
reply.Membership = true
}
// always-on features
reply.Features = []string{
"tunnel",
"httpAuth",
"httpInvite",
// TODO: add "room2" once implemented
}
if pm == roomdb.ModeOpen {
reply.Features = append(reply.Features, "room1")
}
if pm == roomdb.ModeOpen || pm == roomdb.ModeCommunity {
reply.Features = append(reply.Features, "alias")
}
return reply, nil
}
func (h *Handler) ping(context.Context, *muxrpc.Request) (interface{}, error) {
now := time.Now().UnixNano() / 1000
return now, nil
}
func (h *Handler) announce(_ context.Context, req *muxrpc.Request) (interface{}, error) {
ref, err := network.GetFeedRefFromAddr(req.RemoteAddr())
if err != nil {
return nil, err
}
h.state.AddEndpoint(ref, req.Endpoint())
return true, nil
}
func (h *Handler) leave(_ context.Context, req *muxrpc.Request) (interface{}, error) {
ref, err := network.GetFeedRefFromAddr(req.RemoteAddr())
if err != nil {
return nil, err
}
h.state.Remove(ref)
return true, nil
}
func (h *Handler) endpoints(ctx context.Context, req *muxrpc.Request, snk *muxrpc.ByteSink) error {
// get public key from the calling peer
peer, err := network.GetFeedRefFromAddr(req.RemoteAddr())
if err != nil {
return err
}
pm, err := h.config.GetPrivacyMode(ctx)
if err != nil {
return fmt.Errorf("running with unknown privacy mode")
}
switch pm {
case roomdb.ModeCommunity:
fallthrough
case roomdb.ModeRestricted:
_, err := h.membersdb.GetByFeed(ctx, peer)
if err != nil {
return fmt.Errorf("external user are not allowed to enumerate members")
}
}
// for future updates
toPeer := newEndpointsForwarder(snk)
h.state.RegisterLegacyEndpoints(toPeer)
// add the peer to the room state if they arent already
h.state.AlreadyAdded(peer, req.Endpoint())
// update the peer with
toPeer.Update(h.state.List())
return nil
}
// a muxrpc json encoder for endpoints broadcasts
type endpointsJSONEncoder struct {
mu sync.Mutex // only one caller to forwarder at a time
snk *muxrpc.ByteSink
enc *json.Encoder
}
func newEndpointsForwarder(snk *muxrpc.ByteSink) *endpointsJSONEncoder {
enc := json.NewEncoder(snk)
snk.SetEncoding(muxrpc.TypeJSON)
return &endpointsJSONEncoder{
snk: snk,
enc: enc,
}
}
func (uf *endpointsJSONEncoder) Update(members []string) error {
uf.mu.Lock()
defer uf.mu.Unlock()
return uf.enc.Encode(members)
}
func (uf *endpointsJSONEncoder) Close() error {
uf.mu.Lock()
defer uf.mu.Unlock()
return uf.snk.Close()
}