mirror of
https://github.com/matrix-org/dendrite.git
synced 2026-01-12 16:43:09 -06:00
Merge main
This commit is contained in:
parent
48dd4347a8
commit
d9ff7c818b
20
CHANGES.md
20
CHANGES.md
|
|
@ -1,5 +1,25 @@
|
|||
# Changelog
|
||||
|
||||
## Dendrite 0.9.6 (2022-09-01)
|
||||
|
||||
### Features
|
||||
|
||||
* The appservice API has been refactored for improved performance and stability
|
||||
* The appservice database has been deprecated, as the roomserver output stream is now used as the data source instead
|
||||
* The `generate-config` tool has been updated to support additional scenarios, i.e. for CI configuration generation and generating both monolith and polylith skeleton config files
|
||||
|
||||
### Fixes
|
||||
|
||||
* The username length check has been fixed on new account creation
|
||||
* The length of the `type`, `sender`, `state_key` and `room_id` fields in events are now verified by number of codepoints rather than bytes, fixing the "Cat Overflow" bug
|
||||
* UTF-16 surrogate handling in the canonical JSON implementation has been fixed
|
||||
* A race condition when starting the keyserver has been fixed
|
||||
* A race condition when configuring HTTP servers and routing at startup has been fixed
|
||||
* A bug where the incorrect limit was used for lazy-loading memberships has been fixed
|
||||
* The number of push notifications will now be sent to the push gateway
|
||||
* A missing index causing slow performance on the sync API send-to-device table has been added (contributed by [PiotrKozimor](https://github.com/PiotrKozimor))
|
||||
* Event auth will now correctly check for the existence of the `"creator"` field in create events
|
||||
|
||||
## Dendrite 0.9.5 (2022-08-25)
|
||||
|
||||
### Fixes
|
||||
|
|
|
|||
|
|
@ -1,10 +0,0 @@
|
|||
# Application Service
|
||||
|
||||
This component interfaces with external [Application
|
||||
Services](https://matrix.org/docs/spec/application_service/unstable.html).
|
||||
This includes any HTTP endpoints that application services call, as well as talking
|
||||
to any HTTP endpoints that application services provide themselves.
|
||||
|
||||
## Consumers
|
||||
|
||||
This component consumes and filters events from the Roomserver Kafka stream, passing on any necessary events to subscribing application services.
|
||||
|
|
@ -18,7 +18,6 @@ import (
|
|||
"context"
|
||||
"crypto/tls"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
|
|
@ -28,9 +27,6 @@ import (
|
|||
"github.com/matrix-org/dendrite/appservice/consumers"
|
||||
"github.com/matrix-org/dendrite/appservice/inthttp"
|
||||
"github.com/matrix-org/dendrite/appservice/query"
|
||||
"github.com/matrix-org/dendrite/appservice/storage"
|
||||
"github.com/matrix-org/dendrite/appservice/types"
|
||||
"github.com/matrix-org/dendrite/appservice/workers"
|
||||
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
||||
"github.com/matrix-org/dendrite/setup/base"
|
||||
"github.com/matrix-org/dendrite/setup/config"
|
||||
|
|
@ -59,57 +55,40 @@ func NewInternalAPI(
|
|||
Proxy: http.ProxyFromEnvironment,
|
||||
},
|
||||
}
|
||||
js, _ := base.NATS.Prepare(base.ProcessContext, &base.Cfg.Global.JetStream)
|
||||
// Create appserivce query API with an HTTP client that will be used for all
|
||||
// outbound and inbound requests (inbound only for the internal API)
|
||||
appserviceQueryAPI := &query.AppServiceQueryAPI{
|
||||
HTTPClient: client,
|
||||
Cfg: &base.Cfg.AppServiceAPI,
|
||||
}
|
||||
|
||||
// Create a connection to the appservice postgres DB
|
||||
appserviceDB, err := storage.NewDatabase(base, &base.Cfg.AppServiceAPI.Database)
|
||||
if err != nil {
|
||||
logrus.WithError(err).Panicf("failed to connect to appservice db")
|
||||
if len(base.Cfg.Derived.ApplicationServices) == 0 {
|
||||
return appserviceQueryAPI
|
||||
}
|
||||
|
||||
// Wrap application services in a type that relates the application service and
|
||||
// a sync.Cond object that can be used to notify workers when there are new
|
||||
// events to be sent out.
|
||||
workerStates := make([]types.ApplicationServiceWorkerState, len(base.Cfg.Derived.ApplicationServices))
|
||||
for i, appservice := range base.Cfg.Derived.ApplicationServices {
|
||||
m := sync.Mutex{}
|
||||
ws := types.ApplicationServiceWorkerState{
|
||||
AppService: appservice,
|
||||
Cond: sync.NewCond(&m),
|
||||
}
|
||||
workerStates[i] = ws
|
||||
|
||||
for _, appservice := range base.Cfg.Derived.ApplicationServices {
|
||||
// Create bot account for this AS if it doesn't already exist
|
||||
if err = generateAppServiceAccount(userAPI, appservice); err != nil {
|
||||
if err := generateAppServiceAccount(userAPI, appservice); err != nil {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"appservice": appservice.ID,
|
||||
}).WithError(err).Panicf("failed to generate bot account for appservice")
|
||||
}
|
||||
}
|
||||
|
||||
// Create appserivce query API with an HTTP client that will be used for all
|
||||
// outbound and inbound requests (inbound only for the internal API)
|
||||
appserviceQueryAPI := &query.AppServiceQueryAPI{
|
||||
HTTPClient: client,
|
||||
Cfg: base.Cfg,
|
||||
}
|
||||
|
||||
// Only consume if we actually have ASes to track, else we'll just chew cycles needlessly.
|
||||
// We can't add ASes at runtime so this is safe to do.
|
||||
if len(workerStates) > 0 {
|
||||
consumer := consumers.NewOutputRoomEventConsumer(
|
||||
base.ProcessContext, base.Cfg, js, appserviceDB,
|
||||
rsAPI, workerStates,
|
||||
)
|
||||
if err := consumer.Start(); err != nil {
|
||||
logrus.WithError(err).Panicf("failed to start appservice roomserver consumer")
|
||||
}
|
||||
js, _ := base.NATS.Prepare(base.ProcessContext, &base.Cfg.Global.JetStream)
|
||||
consumer := consumers.NewOutputRoomEventConsumer(
|
||||
base.ProcessContext, &base.Cfg.AppServiceAPI,
|
||||
client, js, rsAPI,
|
||||
)
|
||||
if err := consumer.Start(); err != nil {
|
||||
logrus.WithError(err).Panicf("failed to start appservice roomserver consumer")
|
||||
}
|
||||
|
||||
// Create application service transaction workers
|
||||
if err := workers.SetupTransactionWorkers(client, appserviceDB, workerStates); err != nil {
|
||||
logrus.WithError(err).Panicf("failed to start app service transaction workers")
|
||||
}
|
||||
return appserviceQueryAPI
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -15,14 +15,18 @@
|
|||
package consumers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
"github.com/nats-io/nats.go"
|
||||
|
||||
"github.com/matrix-org/dendrite/appservice/storage"
|
||||
"github.com/matrix-org/dendrite/appservice/types"
|
||||
"github.com/matrix-org/dendrite/roomserver/api"
|
||||
"github.com/matrix-org/dendrite/setup/config"
|
||||
"github.com/matrix-org/dendrite/setup/jetstream"
|
||||
|
|
@ -33,177 +37,192 @@ import (
|
|||
|
||||
// OutputRoomEventConsumer consumes events that originated in the room server.
|
||||
type OutputRoomEventConsumer struct {
|
||||
ctx context.Context
|
||||
jetstream nats.JetStreamContext
|
||||
durable string
|
||||
topic string
|
||||
asDB storage.Database
|
||||
rsAPI api.AppserviceRoomserverAPI
|
||||
serverName string
|
||||
workerStates []types.ApplicationServiceWorkerState
|
||||
ctx context.Context
|
||||
cfg *config.AppServiceAPI
|
||||
client *http.Client
|
||||
jetstream nats.JetStreamContext
|
||||
topic string
|
||||
rsAPI api.AppserviceRoomserverAPI
|
||||
}
|
||||
|
||||
type appserviceState struct {
|
||||
*config.ApplicationService
|
||||
backoff int
|
||||
}
|
||||
|
||||
// NewOutputRoomEventConsumer creates a new OutputRoomEventConsumer. Call
|
||||
// Start() to begin consuming from room servers.
|
||||
func NewOutputRoomEventConsumer(
|
||||
process *process.ProcessContext,
|
||||
cfg *config.Dendrite,
|
||||
cfg *config.AppServiceAPI,
|
||||
client *http.Client,
|
||||
js nats.JetStreamContext,
|
||||
appserviceDB storage.Database,
|
||||
rsAPI api.AppserviceRoomserverAPI,
|
||||
workerStates []types.ApplicationServiceWorkerState,
|
||||
) *OutputRoomEventConsumer {
|
||||
return &OutputRoomEventConsumer{
|
||||
ctx: process.Context(),
|
||||
jetstream: js,
|
||||
durable: cfg.Global.JetStream.Durable("AppserviceRoomserverConsumer"),
|
||||
topic: cfg.Global.JetStream.Prefixed(jetstream.OutputRoomEvent),
|
||||
asDB: appserviceDB,
|
||||
rsAPI: rsAPI,
|
||||
serverName: string(cfg.Global.ServerName),
|
||||
workerStates: workerStates,
|
||||
ctx: process.Context(),
|
||||
cfg: cfg,
|
||||
client: client,
|
||||
jetstream: js,
|
||||
topic: cfg.Matrix.JetStream.Prefixed(jetstream.OutputRoomEvent),
|
||||
rsAPI: rsAPI,
|
||||
}
|
||||
}
|
||||
|
||||
// Start consuming from room servers
|
||||
func (s *OutputRoomEventConsumer) Start() error {
|
||||
return jetstream.JetStreamConsumer(
|
||||
s.ctx, s.jetstream, s.topic, s.durable, s.onMessage,
|
||||
nats.DeliverAll(), nats.ManualAck(),
|
||||
)
|
||||
for _, as := range s.cfg.Derived.ApplicationServices {
|
||||
appsvc := as
|
||||
state := &appserviceState{
|
||||
ApplicationService: &appsvc,
|
||||
}
|
||||
token := jetstream.Tokenise(as.ID)
|
||||
if err := jetstream.JetStreamConsumer(
|
||||
s.ctx, s.jetstream, s.topic,
|
||||
s.cfg.Matrix.JetStream.Durable("Appservice_"+token),
|
||||
50, // maximum number of events to send in a single transaction
|
||||
func(ctx context.Context, msgs []*nats.Msg) bool {
|
||||
return s.onMessage(ctx, state, msgs)
|
||||
},
|
||||
nats.DeliverNew(), nats.ManualAck(),
|
||||
); err != nil {
|
||||
return fmt.Errorf("failed to create %q consumer: %w", token, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// onMessage is called when the appservice component receives a new event from
|
||||
// the room server output log.
|
||||
func (s *OutputRoomEventConsumer) onMessage(ctx context.Context, msg *nats.Msg) bool {
|
||||
// Parse out the event JSON
|
||||
var output api.OutputEvent
|
||||
if err := json.Unmarshal(msg.Data, &output); err != nil {
|
||||
// If the message was invalid, log it and move on to the next message in the stream
|
||||
log.WithError(err).Errorf("roomserver output log: message parse failure")
|
||||
return true
|
||||
}
|
||||
|
||||
log.WithFields(log.Fields{
|
||||
"type": output.Type,
|
||||
}).Debug("Got a message in OutputRoomEventConsumer")
|
||||
|
||||
events := []*gomatrixserverlib.HeaderedEvent{}
|
||||
if output.Type == api.OutputTypeNewRoomEvent && output.NewRoomEvent != nil {
|
||||
newEventID := output.NewRoomEvent.Event.EventID()
|
||||
events = append(events, output.NewRoomEvent.Event)
|
||||
if len(output.NewRoomEvent.AddsStateEventIDs) > 0 {
|
||||
eventsReq := &api.QueryEventsByIDRequest{
|
||||
EventIDs: make([]string, 0, len(output.NewRoomEvent.AddsStateEventIDs)),
|
||||
}
|
||||
eventsRes := &api.QueryEventsByIDResponse{}
|
||||
for _, eventID := range output.NewRoomEvent.AddsStateEventIDs {
|
||||
if eventID != newEventID {
|
||||
eventsReq.EventIDs = append(eventsReq.EventIDs, eventID)
|
||||
}
|
||||
}
|
||||
if len(eventsReq.EventIDs) > 0 {
|
||||
if err := s.rsAPI.QueryEventsByID(s.ctx, eventsReq, eventsRes); err != nil {
|
||||
log.WithError(err).Errorf("s.rsAPI.QueryEventsByID failed")
|
||||
return false
|
||||
}
|
||||
events = append(events, eventsRes.Events...)
|
||||
}
|
||||
func (s *OutputRoomEventConsumer) onMessage(
|
||||
ctx context.Context, state *appserviceState, msgs []*nats.Msg,
|
||||
) bool {
|
||||
log.WithField("appservice", state.ID).Tracef("Appservice worker received %d message(s) from roomserver", len(msgs))
|
||||
events := make([]*gomatrixserverlib.HeaderedEvent, 0, len(msgs))
|
||||
for _, msg := range msgs {
|
||||
// Parse out the event JSON
|
||||
var output api.OutputEvent
|
||||
if err := json.Unmarshal(msg.Data, &output); err != nil {
|
||||
// If the message was invalid, log it and move on to the next message in the stream
|
||||
log.WithField("appservice", state.ID).WithError(err).Errorf("Appservice failed to parse message, ignoring")
|
||||
continue
|
||||
}
|
||||
} else if output.Type == api.OutputTypeNewInviteEvent && output.NewInviteEvent != nil {
|
||||
events = append(events, output.NewInviteEvent.Event)
|
||||
} else {
|
||||
log.WithFields(log.Fields{
|
||||
"type": output.Type,
|
||||
}).Debug("appservice OutputRoomEventConsumer ignoring event", string(msg.Data))
|
||||
switch output.Type {
|
||||
case api.OutputTypeNewRoomEvent:
|
||||
if output.NewRoomEvent == nil || !s.appserviceIsInterestedInEvent(ctx, output.NewRoomEvent.Event, state.ApplicationService) {
|
||||
continue
|
||||
}
|
||||
events = append(events, output.NewRoomEvent.Event)
|
||||
if len(output.NewRoomEvent.AddsStateEventIDs) > 0 {
|
||||
newEventID := output.NewRoomEvent.Event.EventID()
|
||||
eventsReq := &api.QueryEventsByIDRequest{
|
||||
EventIDs: make([]string, 0, len(output.NewRoomEvent.AddsStateEventIDs)),
|
||||
}
|
||||
eventsRes := &api.QueryEventsByIDResponse{}
|
||||
for _, eventID := range output.NewRoomEvent.AddsStateEventIDs {
|
||||
if eventID != newEventID {
|
||||
eventsReq.EventIDs = append(eventsReq.EventIDs, eventID)
|
||||
}
|
||||
}
|
||||
if len(eventsReq.EventIDs) > 0 {
|
||||
if err := s.rsAPI.QueryEventsByID(s.ctx, eventsReq, eventsRes); err != nil {
|
||||
log.WithError(err).Errorf("s.rsAPI.QueryEventsByID failed")
|
||||
return false
|
||||
}
|
||||
events = append(events, eventsRes.Events...)
|
||||
}
|
||||
}
|
||||
|
||||
case api.OutputTypeNewInviteEvent:
|
||||
if output.NewInviteEvent == nil || !s.appserviceIsInterestedInEvent(ctx, output.NewInviteEvent.Event, state.ApplicationService) {
|
||||
continue
|
||||
}
|
||||
events = append(events, output.NewInviteEvent.Event)
|
||||
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// If there are no events selected for sending then we should
|
||||
// ack the messages so that we don't get sent them again in the
|
||||
// future.
|
||||
if len(events) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
// Send event to any relevant application services
|
||||
if err := s.filterRoomserverEvents(context.TODO(), events); err != nil {
|
||||
log.WithError(err).Errorf("roomserver output log: filter error")
|
||||
return true
|
||||
}
|
||||
|
||||
return true
|
||||
// Send event to any relevant application services. If we hit
|
||||
// an error here, return false, so that we negatively ack.
|
||||
log.WithField("appservice", state.ID).Debugf("Appservice worker sending %d events(s) from roomserver", len(events))
|
||||
return s.sendEvents(ctx, state, events) == nil
|
||||
}
|
||||
|
||||
// filterRoomserverEvents takes in events and decides whether any of them need
|
||||
// to be passed on to an external application service. It does this by checking
|
||||
// each namespace of each registered application service, and if there is a
|
||||
// match, adds the event to the queue for events to be sent to a particular
|
||||
// application service.
|
||||
func (s *OutputRoomEventConsumer) filterRoomserverEvents(
|
||||
ctx context.Context,
|
||||
// sendEvents passes events to the appservice by using the transactions
|
||||
// endpoint. It will block for the backoff period if necessary.
|
||||
func (s *OutputRoomEventConsumer) sendEvents(
|
||||
ctx context.Context, state *appserviceState,
|
||||
events []*gomatrixserverlib.HeaderedEvent,
|
||||
) error {
|
||||
for _, ws := range s.workerStates {
|
||||
for _, event := range events {
|
||||
// Check if this event is interesting to this application service
|
||||
if s.appserviceIsInterestedInEvent(ctx, event, ws.AppService) {
|
||||
// Queue this event to be sent off to the application service
|
||||
if err := s.asDB.StoreEvent(ctx, ws.AppService.ID, event); err != nil {
|
||||
log.WithError(err).Warn("failed to insert incoming event into appservices database")
|
||||
return err
|
||||
} else {
|
||||
// Tell our worker to send out new messages by updating remaining message
|
||||
// count and waking them up with a broadcast
|
||||
ws.NotifyNewEvents()
|
||||
}
|
||||
}
|
||||
}
|
||||
// Create the transaction body.
|
||||
transaction, err := json.Marshal(
|
||||
gomatrixserverlib.ApplicationServiceTransaction{
|
||||
Events: gomatrixserverlib.HeaderedToClientEvents(events, gomatrixserverlib.FormatAll),
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO: We should probably be more intelligent and pick something not
|
||||
// in the control of the event. A NATS timestamp header or something maybe.
|
||||
txnID := events[0].Event.OriginServerTS()
|
||||
|
||||
// Send the transaction to the appservice.
|
||||
// https://matrix.org/docs/spec/application_service/r0.1.2#put-matrix-app-v1-transactions-txnid
|
||||
address := fmt.Sprintf("%s/transactions/%d?access_token=%s", state.URL, txnID, url.QueryEscape(state.HSToken))
|
||||
req, err := http.NewRequestWithContext(ctx, "PUT", address, bytes.NewBuffer(transaction))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
resp, err := s.client.Do(req)
|
||||
if err != nil {
|
||||
return state.backoffAndPause(err)
|
||||
}
|
||||
|
||||
// If the response was fine then we can clear any backoffs in place and
|
||||
// report that everything was OK. Otherwise, back off for a while.
|
||||
switch resp.StatusCode {
|
||||
case http.StatusOK:
|
||||
state.backoff = 0
|
||||
default:
|
||||
return state.backoffAndPause(fmt.Errorf("received HTTP status code %d from appservice", resp.StatusCode))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// appserviceJoinedAtEvent returns a boolean depending on whether a given
|
||||
// appservice has membership at the time a given event was created.
|
||||
func (s *OutputRoomEventConsumer) appserviceJoinedAtEvent(ctx context.Context, event *gomatrixserverlib.HeaderedEvent, appservice config.ApplicationService) bool {
|
||||
// TODO: This is only checking the current room state, not the state at
|
||||
// the event in question. Pretty sure this is what Synapse does too, but
|
||||
// until we have a lighter way of checking the state before the event that
|
||||
// doesn't involve state res, then this is probably OK.
|
||||
membershipReq := &api.QueryMembershipsForRoomRequest{
|
||||
RoomID: event.RoomID(),
|
||||
JoinedOnly: true,
|
||||
// backoff pauses the calling goroutine for a 2^some backoff exponent seconds
|
||||
func (s *appserviceState) backoffAndPause(err error) error {
|
||||
if s.backoff < 6 {
|
||||
s.backoff++
|
||||
}
|
||||
membershipRes := &api.QueryMembershipsForRoomResponse{}
|
||||
|
||||
// XXX: This could potentially race if the state for the event is not known yet
|
||||
// e.g. the event came over federation but we do not have the full state persisted.
|
||||
if err := s.rsAPI.QueryMembershipsForRoom(ctx, membershipReq, membershipRes); err == nil {
|
||||
for _, ev := range membershipRes.JoinEvents {
|
||||
var membership gomatrixserverlib.MemberContent
|
||||
if err = json.Unmarshal(ev.Content, &membership); err != nil || ev.StateKey == nil {
|
||||
continue
|
||||
}
|
||||
if appservice.IsInterestedInUserID(*ev.StateKey) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.WithFields(log.Fields{
|
||||
"room_id": event.RoomID(),
|
||||
}).WithError(err).Errorf("Unable to get membership for room")
|
||||
}
|
||||
return false
|
||||
duration := time.Second * time.Duration(math.Pow(2, float64(s.backoff)))
|
||||
log.WithField("appservice", s.ID).WithError(err).Errorf("Unable to send transaction to appservice, backing off for %s", duration.String())
|
||||
time.Sleep(duration)
|
||||
return err
|
||||
}
|
||||
|
||||
// appserviceIsInterestedInEvent returns a boolean depending on whether a given
|
||||
// event falls within one of a given application service's namespaces.
|
||||
//
|
||||
// TODO: This should be cached, see https://github.com/matrix-org/dendrite/issues/1682
|
||||
func (s *OutputRoomEventConsumer) appserviceIsInterestedInEvent(ctx context.Context, event *gomatrixserverlib.HeaderedEvent, appservice config.ApplicationService) bool {
|
||||
// No reason to queue events if they'll never be sent to the application
|
||||
// service
|
||||
if appservice.URL == "" {
|
||||
func (s *OutputRoomEventConsumer) appserviceIsInterestedInEvent(ctx context.Context, event *gomatrixserverlib.HeaderedEvent, appservice *config.ApplicationService) bool {
|
||||
switch {
|
||||
case appservice.URL == "":
|
||||
return false
|
||||
}
|
||||
|
||||
// Check Room ID and Sender of the event
|
||||
if appservice.IsInterestedInUserID(event.Sender()) ||
|
||||
appservice.IsInterestedInRoomID(event.RoomID()) {
|
||||
case appservice.IsInterestedInUserID(event.Sender()):
|
||||
return true
|
||||
case appservice.IsInterestedInRoomID(event.RoomID()):
|
||||
return true
|
||||
}
|
||||
|
||||
|
|
@ -224,10 +243,54 @@ func (s *OutputRoomEventConsumer) appserviceIsInterestedInEvent(ctx context.Cont
|
|||
}
|
||||
} else {
|
||||
log.WithFields(log.Fields{
|
||||
"room_id": event.RoomID(),
|
||||
"appservice": appservice.ID,
|
||||
"room_id": event.RoomID(),
|
||||
}).WithError(err).Errorf("Unable to get aliases for room")
|
||||
}
|
||||
|
||||
// Check if any of the members in the room match the appservice
|
||||
return s.appserviceJoinedAtEvent(ctx, event, appservice)
|
||||
}
|
||||
|
||||
// appserviceJoinedAtEvent returns a boolean depending on whether a given
|
||||
// appservice has membership at the time a given event was created.
|
||||
func (s *OutputRoomEventConsumer) appserviceJoinedAtEvent(ctx context.Context, event *gomatrixserverlib.HeaderedEvent, appservice *config.ApplicationService) bool {
|
||||
// TODO: This is only checking the current room state, not the state at
|
||||
// the event in question. Pretty sure this is what Synapse does too, but
|
||||
// until we have a lighter way of checking the state before the event that
|
||||
// doesn't involve state res, then this is probably OK.
|
||||
membershipReq := &api.QueryMembershipsForRoomRequest{
|
||||
RoomID: event.RoomID(),
|
||||
JoinedOnly: true,
|
||||
}
|
||||
membershipRes := &api.QueryMembershipsForRoomResponse{}
|
||||
|
||||
// XXX: This could potentially race if the state for the event is not known yet
|
||||
// e.g. the event came over federation but we do not have the full state persisted.
|
||||
if err := s.rsAPI.QueryMembershipsForRoom(ctx, membershipReq, membershipRes); err == nil {
|
||||
for _, ev := range membershipRes.JoinEvents {
|
||||
switch {
|
||||
case ev.StateKey == nil:
|
||||
continue
|
||||
case ev.Type != gomatrixserverlib.MRoomMember:
|
||||
continue
|
||||
}
|
||||
var membership gomatrixserverlib.MemberContent
|
||||
err = json.Unmarshal(ev.Content, &membership)
|
||||
switch {
|
||||
case err != nil:
|
||||
continue
|
||||
case membership.Membership == gomatrixserverlib.Join:
|
||||
if appservice.IsInterestedInUserID(*ev.StateKey) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.WithFields(log.Fields{
|
||||
"appservice": appservice.ID,
|
||||
"room_id": event.RoomID(),
|
||||
}).WithError(err).Errorf("Unable to get membership for room")
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ const userIDExistsPath = "/users/"
|
|||
// AppServiceQueryAPI is an implementation of api.AppServiceQueryAPI
|
||||
type AppServiceQueryAPI struct {
|
||||
HTTPClient *http.Client
|
||||
Cfg *config.Dendrite
|
||||
Cfg *config.AppServiceAPI
|
||||
}
|
||||
|
||||
// RoomAliasExists performs a request to '/room/{roomAlias}' on all known
|
||||
|
|
|
|||
|
|
@ -1,30 +0,0 @@
|
|||
// Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
)
|
||||
|
||||
type Database interface {
|
||||
StoreEvent(ctx context.Context, appServiceID string, event *gomatrixserverlib.HeaderedEvent) error
|
||||
GetEventsWithAppServiceID(ctx context.Context, appServiceID string, limit int) (int, int, []gomatrixserverlib.HeaderedEvent, bool, error)
|
||||
CountEventsWithAppServiceID(ctx context.Context, appServiceID string) (int, error)
|
||||
UpdateTxnIDForEvents(ctx context.Context, appserviceID string, maxID, txnID int) error
|
||||
RemoveEventsBeforeAndIncludingID(ctx context.Context, appserviceID string, eventTableID int) error
|
||||
GetLatestTxnID(ctx context.Context) (int, error)
|
||||
}
|
||||
|
|
@ -1,256 +0,0 @@
|
|||
// Copyright 2018 New Vector Ltd
|
||||
// Copyright 2019-2020 The Matrix.org Foundation C.I.C.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const appserviceEventsSchema = `
|
||||
-- Stores events to be sent to application services
|
||||
CREATE TABLE IF NOT EXISTS appservice_events (
|
||||
-- An auto-incrementing id unique to each event in the table
|
||||
id BIGSERIAL NOT NULL PRIMARY KEY,
|
||||
-- The ID of the application service the event will be sent to
|
||||
as_id TEXT NOT NULL,
|
||||
-- JSON representation of the event
|
||||
headered_event_json TEXT NOT NULL,
|
||||
-- The ID of the transaction that this event is a part of
|
||||
txn_id BIGINT NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS appservice_events_as_id ON appservice_events(as_id);
|
||||
`
|
||||
|
||||
const selectEventsByApplicationServiceIDSQL = "" +
|
||||
"SELECT id, headered_event_json, txn_id " +
|
||||
"FROM appservice_events WHERE as_id = $1 ORDER BY txn_id DESC, id ASC"
|
||||
|
||||
const countEventsByApplicationServiceIDSQL = "" +
|
||||
"SELECT COUNT(id) FROM appservice_events WHERE as_id = $1"
|
||||
|
||||
const insertEventSQL = "" +
|
||||
"INSERT INTO appservice_events(as_id, headered_event_json, txn_id) " +
|
||||
"VALUES ($1, $2, $3)"
|
||||
|
||||
const updateTxnIDForEventsSQL = "" +
|
||||
"UPDATE appservice_events SET txn_id = $1 WHERE as_id = $2 AND id <= $3"
|
||||
|
||||
const deleteEventsBeforeAndIncludingIDSQL = "" +
|
||||
"DELETE FROM appservice_events WHERE as_id = $1 AND id <= $2"
|
||||
|
||||
const (
|
||||
// A transaction ID number that no transaction should ever have. Used for
|
||||
// checking again the default value.
|
||||
invalidTxnID = -2
|
||||
)
|
||||
|
||||
type eventsStatements struct {
|
||||
selectEventsByApplicationServiceIDStmt *sql.Stmt
|
||||
countEventsByApplicationServiceIDStmt *sql.Stmt
|
||||
insertEventStmt *sql.Stmt
|
||||
updateTxnIDForEventsStmt *sql.Stmt
|
||||
deleteEventsBeforeAndIncludingIDStmt *sql.Stmt
|
||||
}
|
||||
|
||||
func (s *eventsStatements) prepare(db *sql.DB) (err error) {
|
||||
_, err = db.Exec(appserviceEventsSchema)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if s.selectEventsByApplicationServiceIDStmt, err = db.Prepare(selectEventsByApplicationServiceIDSQL); err != nil {
|
||||
return
|
||||
}
|
||||
if s.countEventsByApplicationServiceIDStmt, err = db.Prepare(countEventsByApplicationServiceIDSQL); err != nil {
|
||||
return
|
||||
}
|
||||
if s.insertEventStmt, err = db.Prepare(insertEventSQL); err != nil {
|
||||
return
|
||||
}
|
||||
if s.updateTxnIDForEventsStmt, err = db.Prepare(updateTxnIDForEventsSQL); err != nil {
|
||||
return
|
||||
}
|
||||
if s.deleteEventsBeforeAndIncludingIDStmt, err = db.Prepare(deleteEventsBeforeAndIncludingIDSQL); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// selectEventsByApplicationServiceID takes in an application service ID and
|
||||
// returns a slice of events that need to be sent to that application service,
|
||||
// as well as an int later used to remove these same events from the database
|
||||
// once successfully sent to an application service.
|
||||
func (s *eventsStatements) selectEventsByApplicationServiceID(
|
||||
ctx context.Context,
|
||||
applicationServiceID string,
|
||||
limit int,
|
||||
) (
|
||||
txnID, maxID int,
|
||||
events []gomatrixserverlib.HeaderedEvent,
|
||||
eventsRemaining bool,
|
||||
err error,
|
||||
) {
|
||||
defer func() {
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"appservice": applicationServiceID,
|
||||
}).WithError(err).Fatalf("appservice unable to select new events to send")
|
||||
}
|
||||
}()
|
||||
// Retrieve events from the database. Unsuccessfully sent events first
|
||||
eventRows, err := s.selectEventsByApplicationServiceIDStmt.QueryContext(ctx, applicationServiceID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer checkNamedErr(eventRows.Close, &err)
|
||||
events, maxID, txnID, eventsRemaining, err = retrieveEvents(eventRows, limit)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// checkNamedErr calls fn and overwrite err if it was nil and fn returned non-nil
|
||||
func checkNamedErr(fn func() error, err *error) {
|
||||
if e := fn(); e != nil && *err == nil {
|
||||
*err = e
|
||||
}
|
||||
}
|
||||
|
||||
func retrieveEvents(eventRows *sql.Rows, limit int) (events []gomatrixserverlib.HeaderedEvent, maxID, txnID int, eventsRemaining bool, err error) {
|
||||
// Get current time for use in calculating event age
|
||||
nowMilli := time.Now().UnixNano() / int64(time.Millisecond)
|
||||
|
||||
// Iterate through each row and store event contents
|
||||
// If txn_id changes dramatically, we've switched from collecting old events to
|
||||
// new ones. Send back those events first.
|
||||
lastTxnID := invalidTxnID
|
||||
for eventsProcessed := 0; eventRows.Next(); {
|
||||
var event gomatrixserverlib.HeaderedEvent
|
||||
var eventJSON []byte
|
||||
var id int
|
||||
err = eventRows.Scan(
|
||||
&id,
|
||||
&eventJSON,
|
||||
&txnID,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, 0, 0, false, err
|
||||
}
|
||||
|
||||
// Unmarshal eventJSON
|
||||
if err = json.Unmarshal(eventJSON, &event); err != nil {
|
||||
return nil, 0, 0, false, err
|
||||
}
|
||||
|
||||
// If txnID has changed on this event from the previous event, then we've
|
||||
// reached the end of a transaction's events. Return only those events.
|
||||
if lastTxnID > invalidTxnID && lastTxnID != txnID {
|
||||
return events, maxID, lastTxnID, true, nil
|
||||
}
|
||||
lastTxnID = txnID
|
||||
|
||||
// Limit events that aren't part of an old transaction
|
||||
if txnID == -1 {
|
||||
// Return if we've hit the limit
|
||||
if eventsProcessed++; eventsProcessed > limit {
|
||||
return events, maxID, lastTxnID, true, nil
|
||||
}
|
||||
}
|
||||
|
||||
if id > maxID {
|
||||
maxID = id
|
||||
}
|
||||
|
||||
// Portion of the event that is unsigned due to rapid change
|
||||
// TODO: Consider removing age as not many app services use it
|
||||
if err = event.SetUnsignedField("age", nowMilli-int64(event.OriginServerTS())); err != nil {
|
||||
return nil, 0, 0, false, err
|
||||
}
|
||||
|
||||
events = append(events, event)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// countEventsByApplicationServiceID inserts an event mapped to its corresponding application service
|
||||
// IDs into the db.
|
||||
func (s *eventsStatements) countEventsByApplicationServiceID(
|
||||
ctx context.Context,
|
||||
appServiceID string,
|
||||
) (int, error) {
|
||||
var count int
|
||||
err := s.countEventsByApplicationServiceIDStmt.QueryRowContext(ctx, appServiceID).Scan(&count)
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return count, nil
|
||||
}
|
||||
|
||||
// insertEvent inserts an event mapped to its corresponding application service
|
||||
// IDs into the db.
|
||||
func (s *eventsStatements) insertEvent(
|
||||
ctx context.Context,
|
||||
appServiceID string,
|
||||
event *gomatrixserverlib.HeaderedEvent,
|
||||
) (err error) {
|
||||
// Convert event to JSON before inserting
|
||||
eventJSON, err := json.Marshal(event)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = s.insertEventStmt.ExecContext(
|
||||
ctx,
|
||||
appServiceID,
|
||||
eventJSON,
|
||||
-1, // No transaction ID yet
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
// updateTxnIDForEvents sets the transactionID for a collection of events. Done
|
||||
// before sending them to an AppService. Referenced before sending to make sure
|
||||
// we aren't constructing multiple transactions with the same events.
|
||||
func (s *eventsStatements) updateTxnIDForEvents(
|
||||
ctx context.Context,
|
||||
appserviceID string,
|
||||
maxID, txnID int,
|
||||
) (err error) {
|
||||
_, err = s.updateTxnIDForEventsStmt.ExecContext(ctx, txnID, appserviceID, maxID)
|
||||
return
|
||||
}
|
||||
|
||||
// deleteEventsBeforeAndIncludingID removes events matching given IDs from the database.
|
||||
func (s *eventsStatements) deleteEventsBeforeAndIncludingID(
|
||||
ctx context.Context,
|
||||
appserviceID string,
|
||||
eventTableID int,
|
||||
) (err error) {
|
||||
_, err = s.deleteEventsBeforeAndIncludingIDStmt.ExecContext(ctx, appserviceID, eventTableID)
|
||||
return
|
||||
}
|
||||
|
|
@ -1,115 +0,0 @@
|
|||
// Copyright 2018 New Vector Ltd
|
||||
// Copyright 2019-2020 The Matrix.org Foundation C.I.C.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
|
||||
// Import postgres database driver
|
||||
_ "github.com/lib/pq"
|
||||
"github.com/matrix-org/dendrite/internal/sqlutil"
|
||||
"github.com/matrix-org/dendrite/setup/base"
|
||||
"github.com/matrix-org/dendrite/setup/config"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
)
|
||||
|
||||
// Database stores events intended to be later sent to application services
|
||||
type Database struct {
|
||||
events eventsStatements
|
||||
txnID txnStatements
|
||||
db *sql.DB
|
||||
writer sqlutil.Writer
|
||||
}
|
||||
|
||||
// NewDatabase opens a new database
|
||||
func NewDatabase(base *base.BaseDendrite, dbProperties *config.DatabaseOptions) (*Database, error) {
|
||||
var result Database
|
||||
var err error
|
||||
if result.db, result.writer, err = base.DatabaseConnection(dbProperties, sqlutil.NewDummyWriter()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = result.prepare(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
func (d *Database) prepare() error {
|
||||
if err := d.events.prepare(d.db); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return d.txnID.prepare(d.db)
|
||||
}
|
||||
|
||||
// StoreEvent takes in a gomatrixserverlib.HeaderedEvent and stores it in the database
|
||||
// for a transaction worker to pull and later send to an application service.
|
||||
func (d *Database) StoreEvent(
|
||||
ctx context.Context,
|
||||
appServiceID string,
|
||||
event *gomatrixserverlib.HeaderedEvent,
|
||||
) error {
|
||||
return d.events.insertEvent(ctx, appServiceID, event)
|
||||
}
|
||||
|
||||
// GetEventsWithAppServiceID returns a slice of events and their IDs intended to
|
||||
// be sent to an application service given its ID.
|
||||
func (d *Database) GetEventsWithAppServiceID(
|
||||
ctx context.Context,
|
||||
appServiceID string,
|
||||
limit int,
|
||||
) (int, int, []gomatrixserverlib.HeaderedEvent, bool, error) {
|
||||
return d.events.selectEventsByApplicationServiceID(ctx, appServiceID, limit)
|
||||
}
|
||||
|
||||
// CountEventsWithAppServiceID returns the number of events destined for an
|
||||
// application service given its ID.
|
||||
func (d *Database) CountEventsWithAppServiceID(
|
||||
ctx context.Context,
|
||||
appServiceID string,
|
||||
) (int, error) {
|
||||
return d.events.countEventsByApplicationServiceID(ctx, appServiceID)
|
||||
}
|
||||
|
||||
// UpdateTxnIDForEvents takes in an application service ID and a
|
||||
// and stores them in the DB, unless the pair already exists, in
|
||||
// which case it updates them.
|
||||
func (d *Database) UpdateTxnIDForEvents(
|
||||
ctx context.Context,
|
||||
appserviceID string,
|
||||
maxID, txnID int,
|
||||
) error {
|
||||
return d.events.updateTxnIDForEvents(ctx, appserviceID, maxID, txnID)
|
||||
}
|
||||
|
||||
// RemoveEventsBeforeAndIncludingID removes all events from the database that
|
||||
// are less than or equal to a given maximum ID. IDs here are implemented as a
|
||||
// serial, thus this should always delete events in chronological order.
|
||||
func (d *Database) RemoveEventsBeforeAndIncludingID(
|
||||
ctx context.Context,
|
||||
appserviceID string,
|
||||
eventTableID int,
|
||||
) error {
|
||||
return d.events.deleteEventsBeforeAndIncludingID(ctx, appserviceID, eventTableID)
|
||||
}
|
||||
|
||||
// GetLatestTxnID returns the latest available transaction id
|
||||
func (d *Database) GetLatestTxnID(
|
||||
ctx context.Context,
|
||||
) (int, error) {
|
||||
return d.txnID.selectTxnID(ctx)
|
||||
}
|
||||
|
|
@ -1,53 +0,0 @@
|
|||
// Copyright 2018 New Vector Ltd
|
||||
// Copyright 2019-2020 The Matrix.org Foundation C.I.C.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
)
|
||||
|
||||
const txnIDSchema = `
|
||||
-- Keeps a count of the current transaction ID
|
||||
CREATE SEQUENCE IF NOT EXISTS txn_id_counter START 1;
|
||||
`
|
||||
|
||||
const selectTxnIDSQL = "SELECT nextval('txn_id_counter')"
|
||||
|
||||
type txnStatements struct {
|
||||
selectTxnIDStmt *sql.Stmt
|
||||
}
|
||||
|
||||
func (s *txnStatements) prepare(db *sql.DB) (err error) {
|
||||
_, err = db.Exec(txnIDSchema)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if s.selectTxnIDStmt, err = db.Prepare(selectTxnIDSQL); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// selectTxnID selects the latest ascending transaction ID
|
||||
func (s *txnStatements) selectTxnID(
|
||||
ctx context.Context,
|
||||
) (txnID int, err error) {
|
||||
err = s.selectTxnIDStmt.QueryRowContext(ctx).Scan(&txnID)
|
||||
return
|
||||
}
|
||||
|
|
@ -1,267 +0,0 @@
|
|||
// Copyright 2018 New Vector Ltd
|
||||
// Copyright 2019-2020 The Matrix.org Foundation C.I.C.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package sqlite3
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/matrix-org/dendrite/internal/sqlutil"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const appserviceEventsSchema = `
|
||||
-- Stores events to be sent to application services
|
||||
CREATE TABLE IF NOT EXISTS appservice_events (
|
||||
-- An auto-incrementing id unique to each event in the table
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
-- The ID of the application service the event will be sent to
|
||||
as_id TEXT NOT NULL,
|
||||
-- JSON representation of the event
|
||||
headered_event_json TEXT NOT NULL,
|
||||
-- The ID of the transaction that this event is a part of
|
||||
txn_id INTEGER NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS appservice_events_as_id ON appservice_events(as_id);
|
||||
`
|
||||
|
||||
const selectEventsByApplicationServiceIDSQL = "" +
|
||||
"SELECT id, headered_event_json, txn_id " +
|
||||
"FROM appservice_events WHERE as_id = $1 ORDER BY txn_id DESC, id ASC"
|
||||
|
||||
const countEventsByApplicationServiceIDSQL = "" +
|
||||
"SELECT COUNT(id) FROM appservice_events WHERE as_id = $1"
|
||||
|
||||
const insertEventSQL = "" +
|
||||
"INSERT INTO appservice_events(as_id, headered_event_json, txn_id) " +
|
||||
"VALUES ($1, $2, $3)"
|
||||
|
||||
const updateTxnIDForEventsSQL = "" +
|
||||
"UPDATE appservice_events SET txn_id = $1 WHERE as_id = $2 AND id <= $3"
|
||||
|
||||
const deleteEventsBeforeAndIncludingIDSQL = "" +
|
||||
"DELETE FROM appservice_events WHERE as_id = $1 AND id <= $2"
|
||||
|
||||
const (
|
||||
// A transaction ID number that no transaction should ever have. Used for
|
||||
// checking again the default value.
|
||||
invalidTxnID = -2
|
||||
)
|
||||
|
||||
type eventsStatements struct {
|
||||
db *sql.DB
|
||||
writer sqlutil.Writer
|
||||
selectEventsByApplicationServiceIDStmt *sql.Stmt
|
||||
countEventsByApplicationServiceIDStmt *sql.Stmt
|
||||
insertEventStmt *sql.Stmt
|
||||
updateTxnIDForEventsStmt *sql.Stmt
|
||||
deleteEventsBeforeAndIncludingIDStmt *sql.Stmt
|
||||
}
|
||||
|
||||
func (s *eventsStatements) prepare(db *sql.DB, writer sqlutil.Writer) (err error) {
|
||||
s.db = db
|
||||
s.writer = writer
|
||||
_, err = db.Exec(appserviceEventsSchema)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if s.selectEventsByApplicationServiceIDStmt, err = db.Prepare(selectEventsByApplicationServiceIDSQL); err != nil {
|
||||
return
|
||||
}
|
||||
if s.countEventsByApplicationServiceIDStmt, err = db.Prepare(countEventsByApplicationServiceIDSQL); err != nil {
|
||||
return
|
||||
}
|
||||
if s.insertEventStmt, err = db.Prepare(insertEventSQL); err != nil {
|
||||
return
|
||||
}
|
||||
if s.updateTxnIDForEventsStmt, err = db.Prepare(updateTxnIDForEventsSQL); err != nil {
|
||||
return
|
||||
}
|
||||
if s.deleteEventsBeforeAndIncludingIDStmt, err = db.Prepare(deleteEventsBeforeAndIncludingIDSQL); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// selectEventsByApplicationServiceID takes in an application service ID and
|
||||
// returns a slice of events that need to be sent to that application service,
|
||||
// as well as an int later used to remove these same events from the database
|
||||
// once successfully sent to an application service.
|
||||
func (s *eventsStatements) selectEventsByApplicationServiceID(
|
||||
ctx context.Context,
|
||||
applicationServiceID string,
|
||||
limit int,
|
||||
) (
|
||||
txnID, maxID int,
|
||||
events []gomatrixserverlib.HeaderedEvent,
|
||||
eventsRemaining bool,
|
||||
err error,
|
||||
) {
|
||||
defer func() {
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"appservice": applicationServiceID,
|
||||
}).WithError(err).Fatalf("appservice unable to select new events to send")
|
||||
}
|
||||
}()
|
||||
// Retrieve events from the database. Unsuccessfully sent events first
|
||||
eventRows, err := s.selectEventsByApplicationServiceIDStmt.QueryContext(ctx, applicationServiceID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer checkNamedErr(eventRows.Close, &err)
|
||||
events, maxID, txnID, eventsRemaining, err = retrieveEvents(eventRows, limit)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// checkNamedErr calls fn and overwrite err if it was nil and fn returned non-nil
|
||||
func checkNamedErr(fn func() error, err *error) {
|
||||
if e := fn(); e != nil && *err == nil {
|
||||
*err = e
|
||||
}
|
||||
}
|
||||
|
||||
func retrieveEvents(eventRows *sql.Rows, limit int) (events []gomatrixserverlib.HeaderedEvent, maxID, txnID int, eventsRemaining bool, err error) {
|
||||
// Get current time for use in calculating event age
|
||||
nowMilli := time.Now().UnixNano() / int64(time.Millisecond)
|
||||
|
||||
// Iterate through each row and store event contents
|
||||
// If txn_id changes dramatically, we've switched from collecting old events to
|
||||
// new ones. Send back those events first.
|
||||
lastTxnID := invalidTxnID
|
||||
for eventsProcessed := 0; eventRows.Next(); {
|
||||
var event gomatrixserverlib.HeaderedEvent
|
||||
var eventJSON []byte
|
||||
var id int
|
||||
err = eventRows.Scan(
|
||||
&id,
|
||||
&eventJSON,
|
||||
&txnID,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, 0, 0, false, err
|
||||
}
|
||||
|
||||
// Unmarshal eventJSON
|
||||
if err = json.Unmarshal(eventJSON, &event); err != nil {
|
||||
return nil, 0, 0, false, err
|
||||
}
|
||||
|
||||
// If txnID has changed on this event from the previous event, then we've
|
||||
// reached the end of a transaction's events. Return only those events.
|
||||
if lastTxnID > invalidTxnID && lastTxnID != txnID {
|
||||
return events, maxID, lastTxnID, true, nil
|
||||
}
|
||||
lastTxnID = txnID
|
||||
|
||||
// Limit events that aren't part of an old transaction
|
||||
if txnID == -1 {
|
||||
// Return if we've hit the limit
|
||||
if eventsProcessed++; eventsProcessed > limit {
|
||||
return events, maxID, lastTxnID, true, nil
|
||||
}
|
||||
}
|
||||
|
||||
if id > maxID {
|
||||
maxID = id
|
||||
}
|
||||
|
||||
// Portion of the event that is unsigned due to rapid change
|
||||
// TODO: Consider removing age as not many app services use it
|
||||
if err = event.SetUnsignedField("age", nowMilli-int64(event.OriginServerTS())); err != nil {
|
||||
return nil, 0, 0, false, err
|
||||
}
|
||||
|
||||
events = append(events, event)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// countEventsByApplicationServiceID inserts an event mapped to its corresponding application service
|
||||
// IDs into the db.
|
||||
func (s *eventsStatements) countEventsByApplicationServiceID(
|
||||
ctx context.Context,
|
||||
appServiceID string,
|
||||
) (int, error) {
|
||||
var count int
|
||||
err := s.countEventsByApplicationServiceIDStmt.QueryRowContext(ctx, appServiceID).Scan(&count)
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return count, nil
|
||||
}
|
||||
|
||||
// insertEvent inserts an event mapped to its corresponding application service
|
||||
// IDs into the db.
|
||||
func (s *eventsStatements) insertEvent(
|
||||
ctx context.Context,
|
||||
appServiceID string,
|
||||
event *gomatrixserverlib.HeaderedEvent,
|
||||
) (err error) {
|
||||
// Convert event to JSON before inserting
|
||||
eventJSON, err := json.Marshal(event)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return s.writer.Do(s.db, nil, func(txn *sql.Tx) error {
|
||||
_, err := s.insertEventStmt.ExecContext(
|
||||
ctx,
|
||||
appServiceID,
|
||||
eventJSON,
|
||||
-1, // No transaction ID yet
|
||||
)
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
// updateTxnIDForEvents sets the transactionID for a collection of events. Done
|
||||
// before sending them to an AppService. Referenced before sending to make sure
|
||||
// we aren't constructing multiple transactions with the same events.
|
||||
func (s *eventsStatements) updateTxnIDForEvents(
|
||||
ctx context.Context,
|
||||
appserviceID string,
|
||||
maxID, txnID int,
|
||||
) (err error) {
|
||||
return s.writer.Do(s.db, nil, func(txn *sql.Tx) error {
|
||||
_, err := s.updateTxnIDForEventsStmt.ExecContext(ctx, txnID, appserviceID, maxID)
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
// deleteEventsBeforeAndIncludingID removes events matching given IDs from the database.
|
||||
func (s *eventsStatements) deleteEventsBeforeAndIncludingID(
|
||||
ctx context.Context,
|
||||
appserviceID string,
|
||||
eventTableID int,
|
||||
) (err error) {
|
||||
return s.writer.Do(s.db, nil, func(txn *sql.Tx) error {
|
||||
_, err := s.deleteEventsBeforeAndIncludingIDStmt.ExecContext(ctx, appserviceID, eventTableID)
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
|
@ -1,114 +0,0 @@
|
|||
// Copyright 2018 New Vector Ltd
|
||||
// Copyright 2019-2020 The Matrix.org Foundation C.I.C.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package sqlite3
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
|
||||
// Import SQLite database driver
|
||||
"github.com/matrix-org/dendrite/internal/sqlutil"
|
||||
"github.com/matrix-org/dendrite/setup/base"
|
||||
"github.com/matrix-org/dendrite/setup/config"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
)
|
||||
|
||||
// Database stores events intended to be later sent to application services
|
||||
type Database struct {
|
||||
events eventsStatements
|
||||
txnID txnStatements
|
||||
db *sql.DB
|
||||
writer sqlutil.Writer
|
||||
}
|
||||
|
||||
// NewDatabase opens a new database
|
||||
func NewDatabase(base *base.BaseDendrite, dbProperties *config.DatabaseOptions) (*Database, error) {
|
||||
var result Database
|
||||
var err error
|
||||
if result.db, result.writer, err = base.DatabaseConnection(dbProperties, sqlutil.NewExclusiveWriter()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = result.prepare(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
func (d *Database) prepare() error {
|
||||
if err := d.events.prepare(d.db, d.writer); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return d.txnID.prepare(d.db, d.writer)
|
||||
}
|
||||
|
||||
// StoreEvent takes in a gomatrixserverlib.HeaderedEvent and stores it in the database
|
||||
// for a transaction worker to pull and later send to an application service.
|
||||
func (d *Database) StoreEvent(
|
||||
ctx context.Context,
|
||||
appServiceID string,
|
||||
event *gomatrixserverlib.HeaderedEvent,
|
||||
) error {
|
||||
return d.events.insertEvent(ctx, appServiceID, event)
|
||||
}
|
||||
|
||||
// GetEventsWithAppServiceID returns a slice of events and their IDs intended to
|
||||
// be sent to an application service given its ID.
|
||||
func (d *Database) GetEventsWithAppServiceID(
|
||||
ctx context.Context,
|
||||
appServiceID string,
|
||||
limit int,
|
||||
) (int, int, []gomatrixserverlib.HeaderedEvent, bool, error) {
|
||||
return d.events.selectEventsByApplicationServiceID(ctx, appServiceID, limit)
|
||||
}
|
||||
|
||||
// CountEventsWithAppServiceID returns the number of events destined for an
|
||||
// application service given its ID.
|
||||
func (d *Database) CountEventsWithAppServiceID(
|
||||
ctx context.Context,
|
||||
appServiceID string,
|
||||
) (int, error) {
|
||||
return d.events.countEventsByApplicationServiceID(ctx, appServiceID)
|
||||
}
|
||||
|
||||
// UpdateTxnIDForEvents takes in an application service ID and a
|
||||
// and stores them in the DB, unless the pair already exists, in
|
||||
// which case it updates them.
|
||||
func (d *Database) UpdateTxnIDForEvents(
|
||||
ctx context.Context,
|
||||
appserviceID string,
|
||||
maxID, txnID int,
|
||||
) error {
|
||||
return d.events.updateTxnIDForEvents(ctx, appserviceID, maxID, txnID)
|
||||
}
|
||||
|
||||
// RemoveEventsBeforeAndIncludingID removes all events from the database that
|
||||
// are less than or equal to a given maximum ID. IDs here are implemented as a
|
||||
// serial, thus this should always delete events in chronological order.
|
||||
func (d *Database) RemoveEventsBeforeAndIncludingID(
|
||||
ctx context.Context,
|
||||
appserviceID string,
|
||||
eventTableID int,
|
||||
) error {
|
||||
return d.events.deleteEventsBeforeAndIncludingID(ctx, appserviceID, eventTableID)
|
||||
}
|
||||
|
||||
// GetLatestTxnID returns the latest available transaction id
|
||||
func (d *Database) GetLatestTxnID(
|
||||
ctx context.Context,
|
||||
) (int, error) {
|
||||
return d.txnID.selectTxnID(ctx)
|
||||
}
|
||||
|
|
@ -1,82 +0,0 @@
|
|||
// Copyright 2018 New Vector Ltd
|
||||
// Copyright 2019-2020 The Matrix.org Foundation C.I.C.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package sqlite3
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
|
||||
"github.com/matrix-org/dendrite/internal/sqlutil"
|
||||
)
|
||||
|
||||
const txnIDSchema = `
|
||||
-- Keeps a count of the current transaction ID
|
||||
CREATE TABLE IF NOT EXISTS appservice_counters (
|
||||
name TEXT PRIMARY KEY NOT NULL,
|
||||
last_id INTEGER DEFAULT 1
|
||||
);
|
||||
INSERT OR IGNORE INTO appservice_counters (name, last_id) VALUES('txn_id', 1);
|
||||
`
|
||||
|
||||
const selectTxnIDSQL = `
|
||||
SELECT last_id FROM appservice_counters WHERE name='txn_id'
|
||||
`
|
||||
|
||||
const updateTxnIDSQL = `
|
||||
UPDATE appservice_counters SET last_id=last_id+1 WHERE name='txn_id'
|
||||
`
|
||||
|
||||
type txnStatements struct {
|
||||
db *sql.DB
|
||||
writer sqlutil.Writer
|
||||
selectTxnIDStmt *sql.Stmt
|
||||
updateTxnIDStmt *sql.Stmt
|
||||
}
|
||||
|
||||
func (s *txnStatements) prepare(db *sql.DB, writer sqlutil.Writer) (err error) {
|
||||
s.db = db
|
||||
s.writer = writer
|
||||
_, err = db.Exec(txnIDSchema)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if s.selectTxnIDStmt, err = db.Prepare(selectTxnIDSQL); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if s.updateTxnIDStmt, err = db.Prepare(updateTxnIDSQL); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// selectTxnID selects the latest ascending transaction ID
|
||||
func (s *txnStatements) selectTxnID(
|
||||
ctx context.Context,
|
||||
) (txnID int, err error) {
|
||||
err = s.writer.Do(s.db, nil, func(txn *sql.Tx) error {
|
||||
err := s.selectTxnIDStmt.QueryRowContext(ctx).Scan(&txnID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = s.updateTxnIDStmt.ExecContext(ctx)
|
||||
return err
|
||||
})
|
||||
return
|
||||
}
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
// Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:build !wasm
|
||||
// +build !wasm
|
||||
|
||||
package storage
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/matrix-org/dendrite/appservice/storage/postgres"
|
||||
"github.com/matrix-org/dendrite/appservice/storage/sqlite3"
|
||||
"github.com/matrix-org/dendrite/setup/base"
|
||||
"github.com/matrix-org/dendrite/setup/config"
|
||||
)
|
||||
|
||||
// NewDatabase opens a new Postgres or Sqlite database (based on dataSourceName scheme)
|
||||
// and sets DB connection parameters
|
||||
func NewDatabase(base *base.BaseDendrite, dbProperties *config.DatabaseOptions) (Database, error) {
|
||||
switch {
|
||||
case dbProperties.ConnectionString.IsSQLite():
|
||||
return sqlite3.NewDatabase(base, dbProperties)
|
||||
case dbProperties.ConnectionString.IsPostgres():
|
||||
return postgres.NewDatabase(base, dbProperties)
|
||||
default:
|
||||
return nil, fmt.Errorf("unexpected database type")
|
||||
}
|
||||
}
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
// Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package storage
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/matrix-org/dendrite/appservice/storage/sqlite3"
|
||||
"github.com/matrix-org/dendrite/setup/base"
|
||||
"github.com/matrix-org/dendrite/setup/config"
|
||||
)
|
||||
|
||||
func NewDatabase(base *base.BaseDendrite, dbProperties *config.DatabaseOptions) (Database, error) {
|
||||
switch {
|
||||
case dbProperties.ConnectionString.IsSQLite():
|
||||
return sqlite3.NewDatabase(base, dbProperties)
|
||||
case dbProperties.ConnectionString.IsPostgres():
|
||||
return nil, fmt.Errorf("can't use Postgres implementation")
|
||||
default:
|
||||
return nil, fmt.Errorf("unexpected database type")
|
||||
}
|
||||
}
|
||||
|
|
@ -1,64 +0,0 @@
|
|||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/matrix-org/dendrite/setup/config"
|
||||
)
|
||||
|
||||
const (
|
||||
// AppServiceDeviceID is the AS dummy device ID
|
||||
AppServiceDeviceID = "AS_Device"
|
||||
)
|
||||
|
||||
// ApplicationServiceWorkerState is a type that couples an application service,
|
||||
// a lockable condition as well as some other state variables, allowing the
|
||||
// roomserver to notify appservice workers when there are events ready to send
|
||||
// externally to application services.
|
||||
type ApplicationServiceWorkerState struct {
|
||||
AppService config.ApplicationService
|
||||
Cond *sync.Cond
|
||||
// Events ready to be sent
|
||||
EventsReady bool
|
||||
// Backoff exponent (2^x secs). Max 6, aka 64s.
|
||||
Backoff int
|
||||
}
|
||||
|
||||
// NotifyNewEvents wakes up all waiting goroutines, notifying that events remain
|
||||
// in the event queue for this application service worker.
|
||||
func (a *ApplicationServiceWorkerState) NotifyNewEvents() {
|
||||
a.Cond.L.Lock()
|
||||
a.EventsReady = true
|
||||
a.Cond.Broadcast()
|
||||
a.Cond.L.Unlock()
|
||||
}
|
||||
|
||||
// FinishEventProcessing marks all events of this worker as being sent to the
|
||||
// application service.
|
||||
func (a *ApplicationServiceWorkerState) FinishEventProcessing() {
|
||||
a.Cond.L.Lock()
|
||||
a.EventsReady = false
|
||||
a.Cond.L.Unlock()
|
||||
}
|
||||
|
||||
// WaitForNewEvents causes the calling goroutine to wait on the worker state's
|
||||
// condition for a broadcast or similar wakeup, if there are no events ready.
|
||||
func (a *ApplicationServiceWorkerState) WaitForNewEvents() {
|
||||
a.Cond.L.Lock()
|
||||
if !a.EventsReady {
|
||||
a.Cond.Wait()
|
||||
}
|
||||
a.Cond.L.Unlock()
|
||||
}
|
||||
|
|
@ -1,236 +0,0 @@
|
|||
// Copyright 2018 Vector Creations Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package workers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/matrix-org/dendrite/appservice/storage"
|
||||
"github.com/matrix-org/dendrite/appservice/types"
|
||||
"github.com/matrix-org/dendrite/setup/config"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var (
|
||||
// Maximum size of events sent in each transaction.
|
||||
transactionBatchSize = 50
|
||||
)
|
||||
|
||||
// SetupTransactionWorkers spawns a separate goroutine for each application
|
||||
// service. Each of these "workers" handle taking all events intended for their
|
||||
// app service, batch them up into a single transaction (up to a max transaction
|
||||
// size), then send that off to the AS's /transactions/{txnID} endpoint. It also
|
||||
// handles exponentially backing off in case the AS isn't currently available.
|
||||
func SetupTransactionWorkers(
|
||||
client *http.Client,
|
||||
appserviceDB storage.Database,
|
||||
workerStates []types.ApplicationServiceWorkerState,
|
||||
) error {
|
||||
// Create a worker that handles transmitting events to a single homeserver
|
||||
for _, workerState := range workerStates {
|
||||
// Don't create a worker if this AS doesn't want to receive events
|
||||
if workerState.AppService.URL != "" {
|
||||
go worker(client, appserviceDB, workerState)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// worker is a goroutine that sends any queued events to the application service
|
||||
// it is given.
|
||||
func worker(client *http.Client, db storage.Database, ws types.ApplicationServiceWorkerState) {
|
||||
log.WithFields(log.Fields{
|
||||
"appservice": ws.AppService.ID,
|
||||
}).Info("Starting application service")
|
||||
ctx := context.Background()
|
||||
|
||||
// Initial check for any leftover events to send from last time
|
||||
eventCount, err := db.CountEventsWithAppServiceID(ctx, ws.AppService.ID)
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"appservice": ws.AppService.ID,
|
||||
}).WithError(err).Fatal("appservice worker unable to read queued events from DB")
|
||||
return
|
||||
}
|
||||
if eventCount > 0 {
|
||||
ws.NotifyNewEvents()
|
||||
}
|
||||
|
||||
// Loop forever and keep waiting for more events to send
|
||||
for {
|
||||
// Wait for more events if we've sent all the events in the database
|
||||
ws.WaitForNewEvents()
|
||||
|
||||
// Batch events up into a transaction
|
||||
transactionJSON, txnID, maxEventID, eventsRemaining, err := createTransaction(ctx, db, ws.AppService.ID)
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"appservice": ws.AppService.ID,
|
||||
}).WithError(err).Fatal("appservice worker unable to create transaction")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Send the events off to the application service
|
||||
// Backoff if the application service does not respond
|
||||
err = send(client, ws.AppService, txnID, transactionJSON)
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"appservice": ws.AppService.ID,
|
||||
}).WithError(err).Error("unable to send event")
|
||||
// Backoff
|
||||
backoff(&ws, err)
|
||||
continue
|
||||
}
|
||||
|
||||
// We sent successfully, hooray!
|
||||
ws.Backoff = 0
|
||||
|
||||
// Transactions have a maximum event size, so there may still be some events
|
||||
// left over to send. Keep sending until none are left
|
||||
if !eventsRemaining {
|
||||
ws.FinishEventProcessing()
|
||||
}
|
||||
|
||||
// Remove sent events from the DB
|
||||
err = db.RemoveEventsBeforeAndIncludingID(ctx, ws.AppService.ID, maxEventID)
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"appservice": ws.AppService.ID,
|
||||
}).WithError(err).Fatal("unable to remove appservice events from the database")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// backoff pauses the calling goroutine for a 2^some backoff exponent seconds
|
||||
func backoff(ws *types.ApplicationServiceWorkerState, err error) {
|
||||
// Calculate how long to backoff for
|
||||
backoffDuration := time.Duration(math.Pow(2, float64(ws.Backoff)))
|
||||
backoffSeconds := time.Second * backoffDuration
|
||||
|
||||
log.WithFields(log.Fields{
|
||||
"appservice": ws.AppService.ID,
|
||||
}).WithError(err).Warnf("unable to send transactions successfully, backing off for %ds",
|
||||
backoffDuration)
|
||||
|
||||
ws.Backoff++
|
||||
if ws.Backoff > 6 {
|
||||
ws.Backoff = 6
|
||||
}
|
||||
|
||||
// Backoff
|
||||
time.Sleep(backoffSeconds)
|
||||
}
|
||||
|
||||
// createTransaction takes in a slice of AS events, stores them in an AS
|
||||
// transaction, and JSON-encodes the results.
|
||||
func createTransaction(
|
||||
ctx context.Context,
|
||||
db storage.Database,
|
||||
appserviceID string,
|
||||
) (
|
||||
transactionJSON []byte,
|
||||
txnID, maxID int,
|
||||
eventsRemaining bool,
|
||||
err error,
|
||||
) {
|
||||
// Retrieve the latest events from the DB (will return old events if they weren't successfully sent)
|
||||
txnID, maxID, events, eventsRemaining, err := db.GetEventsWithAppServiceID(ctx, appserviceID, transactionBatchSize)
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"appservice": appserviceID,
|
||||
}).WithError(err).Fatalf("appservice worker unable to read queued events from DB")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Check if these events do not already have a transaction ID
|
||||
if txnID == -1 {
|
||||
// If not, grab next available ID from the DB
|
||||
txnID, err = db.GetLatestTxnID(ctx)
|
||||
if err != nil {
|
||||
return nil, 0, 0, false, err
|
||||
}
|
||||
|
||||
// Mark new events with current transactionID
|
||||
if err = db.UpdateTxnIDForEvents(ctx, appserviceID, maxID, txnID); err != nil {
|
||||
return nil, 0, 0, false, err
|
||||
}
|
||||
}
|
||||
|
||||
var ev []*gomatrixserverlib.HeaderedEvent
|
||||
for i := range events {
|
||||
ev = append(ev, &events[i])
|
||||
}
|
||||
|
||||
// Create a transaction and store the events inside
|
||||
transaction := gomatrixserverlib.ApplicationServiceTransaction{
|
||||
Events: gomatrixserverlib.HeaderedToClientEvents(ev, gomatrixserverlib.FormatAll),
|
||||
}
|
||||
|
||||
transactionJSON, err = json.Marshal(transaction)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// send sends events to an application service. Returns an error if an OK was not
|
||||
// received back from the application service or the request timed out.
|
||||
func send(
|
||||
client *http.Client,
|
||||
appservice config.ApplicationService,
|
||||
txnID int,
|
||||
transaction []byte,
|
||||
) (err error) {
|
||||
// PUT a transaction to our AS
|
||||
// https://matrix.org/docs/spec/application_service/r0.1.2#put-matrix-app-v1-transactions-txnid
|
||||
address := fmt.Sprintf("%s/transactions/%d?access_token=%s", appservice.URL, txnID, url.QueryEscape(appservice.HSToken))
|
||||
req, err := http.NewRequest("PUT", address, bytes.NewBuffer(transaction))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer checkNamedErr(resp.Body.Close, &err)
|
||||
|
||||
// Check the AS received the events correctly
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
// TODO: Handle non-200 error codes from application services
|
||||
return fmt.Errorf("non-OK status code %d returned from AS", resp.StatusCode)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// checkNamedErr calls fn and overwrite err if it was nil and fn returned non-nil
|
||||
func checkNamedErr(fn func() error, err *error) {
|
||||
if e := fn(); e != nil && *err == nil {
|
||||
*err = e
|
||||
}
|
||||
}
|
||||
|
|
@ -22,10 +22,10 @@ import (
|
|||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
|
@ -45,6 +45,7 @@ import (
|
|||
"github.com/matrix-org/dendrite/setup/base"
|
||||
"github.com/matrix-org/dendrite/setup/config"
|
||||
"github.com/matrix-org/dendrite/setup/process"
|
||||
"github.com/matrix-org/dendrite/test"
|
||||
"github.com/matrix-org/dendrite/userapi"
|
||||
userapiAPI "github.com/matrix-org/dendrite/userapi/api"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
|
|
@ -204,27 +205,37 @@ func (m *DendriteMonolith) RegisterDevice(localpart, deviceID string) (string, e
|
|||
|
||||
// nolint:gocyclo
|
||||
func (m *DendriteMonolith) Start() {
|
||||
var err error
|
||||
var sk ed25519.PrivateKey
|
||||
var pk ed25519.PublicKey
|
||||
keyfile := fmt.Sprintf("%s/p2p.key", m.StorageDirectory)
|
||||
if _, err = os.Stat(keyfile); os.IsNotExist(err) {
|
||||
if pk, sk, err = ed25519.GenerateKey(nil); err != nil {
|
||||
panic(err)
|
||||
|
||||
keyfile := filepath.Join(m.StorageDirectory, "p2p.pem")
|
||||
if _, err := os.Stat(keyfile); os.IsNotExist(err) {
|
||||
oldkeyfile := filepath.Join(m.StorageDirectory, "p2p.key")
|
||||
if _, err = os.Stat(oldkeyfile); os.IsNotExist(err) {
|
||||
if err = test.NewMatrixKey(keyfile); err != nil {
|
||||
panic("failed to generate a new PEM key: " + err.Error())
|
||||
}
|
||||
if _, sk, err = config.LoadMatrixKey(keyfile, os.ReadFile); err != nil {
|
||||
panic("failed to load PEM key: " + err.Error())
|
||||
}
|
||||
} else {
|
||||
if sk, err = os.ReadFile(oldkeyfile); err != nil {
|
||||
panic("failed to read the old private key: " + err.Error())
|
||||
}
|
||||
if len(sk) != ed25519.PrivateKeySize {
|
||||
panic("the private key is not long enough")
|
||||
}
|
||||
if err = test.SaveMatrixKey(keyfile, sk); err != nil {
|
||||
panic("failed to convert the private key to PEM format: " + err.Error())
|
||||
}
|
||||
}
|
||||
if err = ioutil.WriteFile(keyfile, sk, 0644); err != nil {
|
||||
panic(err)
|
||||
} else {
|
||||
if _, sk, err = config.LoadMatrixKey(keyfile, os.ReadFile); err != nil {
|
||||
panic("failed to load PEM key: " + err.Error())
|
||||
}
|
||||
} else if err == nil {
|
||||
if sk, err = ioutil.ReadFile(keyfile); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if len(sk) != ed25519.PrivateKeySize {
|
||||
panic("the private key is not long enough")
|
||||
}
|
||||
pk = sk.Public().(ed25519.PublicKey)
|
||||
}
|
||||
|
||||
var err error
|
||||
m.listener, err = net.Listen("tcp", "localhost:65432")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
|
@ -243,7 +254,10 @@ func (m *DendriteMonolith) Start() {
|
|||
|
||||
prefix := hex.EncodeToString(pk)
|
||||
cfg := &config.Dendrite{}
|
||||
cfg.Defaults(true)
|
||||
cfg.Defaults(config.DefaultOpts{
|
||||
Generate: true,
|
||||
Monolithic: true,
|
||||
})
|
||||
cfg.Global.ServerName = gomatrixserverlib.ServerName(hex.EncodeToString(pk))
|
||||
cfg.Global.PrivateKey = sk
|
||||
cfg.Global.KeyID = gomatrixserverlib.KeyID(signing.KeyID)
|
||||
|
|
@ -255,7 +269,6 @@ func (m *DendriteMonolith) Start() {
|
|||
cfg.RoomServer.Database.ConnectionString = config.DataSource(fmt.Sprintf("file:%s/%s-roomserver.db", m.StorageDirectory, prefix))
|
||||
cfg.KeyServer.Database.ConnectionString = config.DataSource(fmt.Sprintf("file:%s/%s-keyserver.db", m.StorageDirectory, prefix))
|
||||
cfg.FederationAPI.Database.ConnectionString = config.DataSource(fmt.Sprintf("file:%s/%s-federationsender.db", m.StorageDirectory, prefix))
|
||||
cfg.AppServiceAPI.Database.ConnectionString = config.DataSource(fmt.Sprintf("file:%s/%s-appservice.db", m.StorageDirectory, prefix))
|
||||
cfg.MediaAPI.BasePath = config.Path(fmt.Sprintf("%s/media", m.CacheDirectory))
|
||||
cfg.MediaAPI.AbsBasePath = config.Path(fmt.Sprintf("%s/media", m.CacheDirectory))
|
||||
cfg.MSCs.MSCs = []string{"msc2836", "msc2946"}
|
||||
|
|
|
|||
|
|
@ -2,10 +2,14 @@ package gobind
|
|||
|
||||
import (
|
||||
"context"
|
||||
"crypto/ed25519"
|
||||
"crypto/tls"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
|
|
@ -22,6 +26,7 @@ import (
|
|||
"github.com/matrix-org/dendrite/setup/base"
|
||||
"github.com/matrix-org/dendrite/setup/config"
|
||||
"github.com/matrix-org/dendrite/setup/process"
|
||||
"github.com/matrix-org/dendrite/test"
|
||||
"github.com/matrix-org/dendrite/userapi"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
|
@ -63,28 +68,62 @@ func (m *DendriteMonolith) DisconnectMulticastPeers() {
|
|||
}
|
||||
|
||||
func (m *DendriteMonolith) Start() {
|
||||
var pk ed25519.PublicKey
|
||||
var sk ed25519.PrivateKey
|
||||
|
||||
m.logger = logrus.Logger{
|
||||
Out: BindLogger{},
|
||||
}
|
||||
m.logger.SetOutput(BindLogger{})
|
||||
logrus.SetOutput(BindLogger{})
|
||||
|
||||
keyfile := filepath.Join(m.StorageDirectory, "p2p.pem")
|
||||
if _, err := os.Stat(keyfile); os.IsNotExist(err) {
|
||||
oldkeyfile := filepath.Join(m.StorageDirectory, "p2p.key")
|
||||
if _, err = os.Stat(oldkeyfile); os.IsNotExist(err) {
|
||||
if err = test.NewMatrixKey(keyfile); err != nil {
|
||||
panic("failed to generate a new PEM key: " + err.Error())
|
||||
}
|
||||
if _, sk, err = config.LoadMatrixKey(keyfile, os.ReadFile); err != nil {
|
||||
panic("failed to load PEM key: " + err.Error())
|
||||
}
|
||||
} else {
|
||||
if sk, err = os.ReadFile(oldkeyfile); err != nil {
|
||||
panic("failed to read the old private key: " + err.Error())
|
||||
}
|
||||
if len(sk) != ed25519.PrivateKeySize {
|
||||
panic("the private key is not long enough")
|
||||
}
|
||||
if err := test.SaveMatrixKey(keyfile, sk); err != nil {
|
||||
panic("failed to convert the private key to PEM format: " + err.Error())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
var err error
|
||||
if _, sk, err = config.LoadMatrixKey(keyfile, os.ReadFile); err != nil {
|
||||
panic("failed to load PEM key: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
var err error
|
||||
m.listener, err = net.Listen("tcp", "localhost:65432")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
ygg, err := yggconn.Setup("dendrite", m.StorageDirectory, "")
|
||||
ygg, err := yggconn.Setup(sk, "dendrite", m.StorageDirectory, "", "")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
m.YggdrasilNode = ygg
|
||||
|
||||
cfg := &config.Dendrite{}
|
||||
cfg.Defaults(true)
|
||||
cfg.Global.ServerName = gomatrixserverlib.ServerName(ygg.DerivedServerName())
|
||||
cfg.Global.PrivateKey = ygg.PrivateKey()
|
||||
cfg.Defaults(config.DefaultOpts{
|
||||
Generate: true,
|
||||
Monolithic: true,
|
||||
})
|
||||
cfg.Global.ServerName = gomatrixserverlib.ServerName(hex.EncodeToString(pk))
|
||||
cfg.Global.PrivateKey = sk
|
||||
cfg.Global.KeyID = gomatrixserverlib.KeyID(signing.KeyID)
|
||||
cfg.Global.JetStream.StoragePath = config.Path(fmt.Sprintf("%s/", m.StorageDirectory))
|
||||
cfg.Global.JetStream.InMemory = true
|
||||
|
|
@ -94,7 +133,6 @@ func (m *DendriteMonolith) Start() {
|
|||
cfg.RoomServer.Database.ConnectionString = config.DataSource(fmt.Sprintf("file:%s/dendrite-p2p-roomserver.db", m.StorageDirectory))
|
||||
cfg.KeyServer.Database.ConnectionString = config.DataSource(fmt.Sprintf("file:%s/dendrite-p2p-keyserver.db", m.StorageDirectory))
|
||||
cfg.FederationAPI.Database.ConnectionString = config.DataSource(fmt.Sprintf("file:%s/dendrite-p2p-federationsender.db", m.StorageDirectory))
|
||||
cfg.AppServiceAPI.Database.ConnectionString = config.DataSource(fmt.Sprintf("file:%s/dendrite-p2p-appservice.db", m.StorageDirectory))
|
||||
cfg.MediaAPI.BasePath = config.Path(fmt.Sprintf("%s/tmp", m.StorageDirectory))
|
||||
cfg.MediaAPI.AbsBasePath = config.Path(fmt.Sprintf("%s/tmp", m.StorageDirectory))
|
||||
cfg.ClientAPI.RegistrationDisabled = false
|
||||
|
|
|
|||
|
|
@ -46,9 +46,8 @@ EXPOSE 8008 8448
|
|||
# At runtime, generate TLS cert based on the CA now mounted at /ca
|
||||
# At runtime, replace the SERVER_NAME with what we are told
|
||||
CMD /build/run_postgres.sh && ./generate-keys --keysize 1024 --server $SERVER_NAME --tls-cert server.crt --tls-key server.key --tls-authority-cert /complement/ca/ca.crt --tls-authority-key /complement/ca/ca.key && \
|
||||
./generate-config -server $SERVER_NAME --ci > dendrite.yaml && \
|
||||
# Replace the connection string with a single postgres DB, using user/db = 'postgres' and no password, bump max_conns
|
||||
sed -i "s%connection_string:.*$%connection_string: postgresql://postgres@localhost/postgres?sslmode=disable%g" dendrite.yaml && \
|
||||
sed -i 's/max_open_conns:.*$/max_open_conns: 100/g' dendrite.yaml && \
|
||||
./generate-config -server $SERVER_NAME --ci --db postgresql://postgres@localhost/postgres?sslmode=disable > dendrite.yaml && \
|
||||
# Bump max_open_conns up here in the global database config
|
||||
sed -i 's/max_open_conns:.*$/max_open_conns: 1990/g' dendrite.yaml && \
|
||||
cp /complement/ca/ca.crt /usr/local/share/ca-certificates/ && update-ca-certificates && \
|
||||
exec ./dendrite-monolith-server --really-enable-open-registration --tls-cert server.crt --tls-key server.key --config dendrite.yaml -api=${API:-0}
|
||||
|
|
@ -181,7 +181,10 @@ func TestValidationOfApplicationServices(t *testing.T) {
|
|||
|
||||
// Set up a config
|
||||
fakeConfig := &config.Dendrite{}
|
||||
fakeConfig.Defaults(true)
|
||||
fakeConfig.Defaults(config.DefaultOpts{
|
||||
Generate: true,
|
||||
Monolithic: true,
|
||||
})
|
||||
fakeConfig.Global.ServerName = "localhost"
|
||||
fakeConfig.ClientAPI.Derived.ApplicationServices = []config.ApplicationService{fakeApplicationService}
|
||||
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import (
|
|||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
|
|
@ -42,6 +43,7 @@ import (
|
|||
"github.com/matrix-org/dendrite/setup"
|
||||
"github.com/matrix-org/dendrite/setup/base"
|
||||
"github.com/matrix-org/dendrite/setup/config"
|
||||
"github.com/matrix-org/dendrite/test"
|
||||
"github.com/matrix-org/dendrite/userapi"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
|
||||
|
|
@ -70,31 +72,86 @@ func main() {
|
|||
var pk ed25519.PublicKey
|
||||
var sk ed25519.PrivateKey
|
||||
|
||||
keyfile := *instanceName + ".key"
|
||||
if _, err := os.Stat(keyfile); os.IsNotExist(err) {
|
||||
if pk, sk, err = ed25519.GenerateKey(nil); err != nil {
|
||||
panic(err)
|
||||
// iterate through the cli args and check if the config flag was set
|
||||
configFlagSet := false
|
||||
for _, arg := range os.Args {
|
||||
if arg == "--config" || arg == "-config" {
|
||||
configFlagSet = true
|
||||
break
|
||||
}
|
||||
if err = os.WriteFile(keyfile, sk, 0644); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
} else if err == nil {
|
||||
if sk, err = os.ReadFile(keyfile); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if len(sk) != ed25519.PrivateKeySize {
|
||||
panic("the private key is not long enough")
|
||||
}
|
||||
pk = sk.Public().(ed25519.PublicKey)
|
||||
}
|
||||
|
||||
cfg := &config.Dendrite{}
|
||||
|
||||
// use custom config if config flag is set
|
||||
if configFlagSet {
|
||||
cfg = setup.ParseFlags(true)
|
||||
sk = cfg.Global.PrivateKey
|
||||
} else {
|
||||
keyfile := *instanceName + ".pem"
|
||||
if _, err := os.Stat(keyfile); os.IsNotExist(err) {
|
||||
oldkeyfile := *instanceName + ".key"
|
||||
if _, err = os.Stat(oldkeyfile); os.IsNotExist(err) {
|
||||
if err = test.NewMatrixKey(keyfile); err != nil {
|
||||
panic("failed to generate a new PEM key: " + err.Error())
|
||||
}
|
||||
if _, sk, err = config.LoadMatrixKey(keyfile, os.ReadFile); err != nil {
|
||||
panic("failed to load PEM key: " + err.Error())
|
||||
}
|
||||
} else {
|
||||
if sk, err = os.ReadFile(oldkeyfile); err != nil {
|
||||
panic("failed to read the old private key: " + err.Error())
|
||||
}
|
||||
if len(sk) != ed25519.PrivateKeySize {
|
||||
panic("the private key is not long enough")
|
||||
}
|
||||
if err := test.SaveMatrixKey(keyfile, sk); err != nil {
|
||||
panic("failed to convert the private key to PEM format: " + err.Error())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
var err error
|
||||
if _, sk, err = config.LoadMatrixKey(keyfile, os.ReadFile); err != nil {
|
||||
panic("failed to load PEM key: " + err.Error())
|
||||
}
|
||||
}
|
||||
cfg.Defaults(config.DefaultOpts{
|
||||
Generate: true,
|
||||
Monolithic: true,
|
||||
})
|
||||
cfg.Global.PrivateKey = sk
|
||||
cfg.Global.JetStream.StoragePath = config.Path(fmt.Sprintf("%s/", *instanceName))
|
||||
cfg.UserAPI.AccountDatabase.ConnectionString = config.DataSource(fmt.Sprintf("file:%s-account.db", *instanceName))
|
||||
cfg.MediaAPI.Database.ConnectionString = config.DataSource(fmt.Sprintf("file:%s-mediaapi.db", *instanceName))
|
||||
cfg.SyncAPI.Database.ConnectionString = config.DataSource(fmt.Sprintf("file:%s-syncapi.db", *instanceName))
|
||||
cfg.RoomServer.Database.ConnectionString = config.DataSource(fmt.Sprintf("file:%s-roomserver.db", *instanceName))
|
||||
cfg.KeyServer.Database.ConnectionString = config.DataSource(fmt.Sprintf("file:%s-keyserver.db", *instanceName))
|
||||
cfg.FederationAPI.Database.ConnectionString = config.DataSource(fmt.Sprintf("file:%s-federationapi.db", *instanceName))
|
||||
cfg.MSCs.MSCs = []string{"msc2836", "msc2946"}
|
||||
cfg.MSCs.Database.ConnectionString = config.DataSource(fmt.Sprintf("file:%s-mscs.db", *instanceName))
|
||||
cfg.ClientAPI.RegistrationDisabled = false
|
||||
cfg.ClientAPI.OpenRegistrationWithoutVerificationEnabled = true
|
||||
if err := cfg.Derive(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
pk = sk.Public().(ed25519.PublicKey)
|
||||
cfg.Global.ServerName = gomatrixserverlib.ServerName(hex.EncodeToString(pk))
|
||||
cfg.Global.KeyID = gomatrixserverlib.KeyID(signing.KeyID)
|
||||
|
||||
base := base.NewBaseDendrite(cfg, "Monolith")
|
||||
defer base.Close() // nolint: errcheck
|
||||
|
||||
pRouter := pineconeRouter.NewRouter(logrus.WithField("pinecone", "router"), sk, false)
|
||||
pQUIC := pineconeSessions.NewSessions(logrus.WithField("pinecone", "sessions"), pRouter, []string{"matrix"})
|
||||
pMulticast := pineconeMulticast.NewMulticast(logrus.WithField("pinecone", "multicast"), pRouter)
|
||||
pManager := pineconeConnections.NewConnectionManager(pRouter, nil)
|
||||
pMulticast.Start()
|
||||
if instancePeer != nil && *instancePeer != "" {
|
||||
pManager.AddPeer(*instancePeer)
|
||||
for _, peer := range strings.Split(*instancePeer, ",") {
|
||||
pManager.AddPeer(strings.Trim(peer, " \t\r\n"))
|
||||
}
|
||||
}
|
||||
|
||||
go func() {
|
||||
|
|
@ -125,29 +182,6 @@ func main() {
|
|||
}
|
||||
}()
|
||||
|
||||
cfg := &config.Dendrite{}
|
||||
cfg.Defaults(true)
|
||||
cfg.Global.ServerName = gomatrixserverlib.ServerName(hex.EncodeToString(pk))
|
||||
cfg.Global.PrivateKey = sk
|
||||
cfg.Global.KeyID = gomatrixserverlib.KeyID(signing.KeyID)
|
||||
cfg.Global.JetStream.StoragePath = config.Path(fmt.Sprintf("%s/", *instanceName))
|
||||
cfg.UserAPI.AccountDatabase.ConnectionString = config.DataSource(fmt.Sprintf("file:%s-account.db", *instanceName))
|
||||
cfg.MediaAPI.Database.ConnectionString = config.DataSource(fmt.Sprintf("file:%s-mediaapi.db", *instanceName))
|
||||
cfg.SyncAPI.Database.ConnectionString = config.DataSource(fmt.Sprintf("file:%s-syncapi.db", *instanceName))
|
||||
cfg.RoomServer.Database.ConnectionString = config.DataSource(fmt.Sprintf("file:%s-roomserver.db", *instanceName))
|
||||
cfg.KeyServer.Database.ConnectionString = config.DataSource(fmt.Sprintf("file:%s-keyserver.db", *instanceName))
|
||||
cfg.FederationAPI.Database.ConnectionString = config.DataSource(fmt.Sprintf("file:%s-federationapi.db", *instanceName))
|
||||
cfg.AppServiceAPI.Database.ConnectionString = config.DataSource(fmt.Sprintf("file:%s-appservice.db", *instanceName))
|
||||
cfg.MSCs.MSCs = []string{"msc2836", "msc2946"}
|
||||
cfg.ClientAPI.RegistrationDisabled = false
|
||||
cfg.ClientAPI.OpenRegistrationWithoutVerificationEnabled = true
|
||||
if err := cfg.Derive(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
base := base.NewBaseDendrite(cfg, "Monolith")
|
||||
defer base.Close() // nolint: errcheck
|
||||
|
||||
federation := conn.CreateFederationClient(base, pQUIC)
|
||||
|
||||
serverKeyAPI := &signing.YggdrasilKeys{}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,9 @@ package main
|
|||
|
||||
import (
|
||||
"context"
|
||||
"crypto/ed25519"
|
||||
"crypto/tls"
|
||||
"encoding/hex"
|
||||
"flag"
|
||||
"fmt"
|
||||
"net"
|
||||
|
|
@ -42,6 +44,7 @@ import (
|
|||
"github.com/matrix-org/dendrite/setup/base"
|
||||
"github.com/matrix-org/dendrite/setup/config"
|
||||
"github.com/matrix-org/dendrite/setup/mscs"
|
||||
"github.com/matrix-org/dendrite/test"
|
||||
"github.com/matrix-org/dendrite/userapi"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
|
|
@ -49,19 +52,18 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
instanceName = flag.String("name", "dendrite-p2p-ygg", "the name of this P2P demo instance")
|
||||
instancePort = flag.Int("port", 8008, "the port that the client API will listen on")
|
||||
instancePeer = flag.String("peer", "", "an internet Yggdrasil peer to connect to")
|
||||
instanceName = flag.String("name", "dendrite-p2p-ygg", "the name of this P2P demo instance")
|
||||
instancePort = flag.Int("port", 8008, "the port that the client API will listen on")
|
||||
instancePeer = flag.String("peer", "", "the static Yggdrasil peers to connect to, comma separated-list")
|
||||
instanceListen = flag.String("listen", "tcp://:0", "the port Yggdrasil peers can connect to")
|
||||
)
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
internal.SetupPprof()
|
||||
|
||||
ygg, err := yggconn.Setup(*instanceName, ".", *instancePeer)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
var pk ed25519.PublicKey
|
||||
var sk ed25519.PrivateKey
|
||||
|
||||
// iterate through the cli args and check if the config flag was set
|
||||
configFlagSet := false
|
||||
|
|
@ -74,11 +76,43 @@ func main() {
|
|||
|
||||
cfg := &config.Dendrite{}
|
||||
|
||||
keyfile := *instanceName + ".pem"
|
||||
if _, err := os.Stat(keyfile); os.IsNotExist(err) {
|
||||
oldkeyfile := *instanceName + ".key"
|
||||
if _, err = os.Stat(oldkeyfile); os.IsNotExist(err) {
|
||||
if err = test.NewMatrixKey(keyfile); err != nil {
|
||||
panic("failed to generate a new PEM key: " + err.Error())
|
||||
}
|
||||
if _, sk, err = config.LoadMatrixKey(keyfile, os.ReadFile); err != nil {
|
||||
panic("failed to load PEM key: " + err.Error())
|
||||
}
|
||||
} else {
|
||||
if sk, err = os.ReadFile(oldkeyfile); err != nil {
|
||||
panic("failed to read the old private key: " + err.Error())
|
||||
}
|
||||
if len(sk) != ed25519.PrivateKeySize {
|
||||
panic("the private key is not long enough")
|
||||
}
|
||||
if err := test.SaveMatrixKey(keyfile, sk); err != nil {
|
||||
panic("failed to convert the private key to PEM format: " + err.Error())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
var err error
|
||||
if _, sk, err = config.LoadMatrixKey(keyfile, os.ReadFile); err != nil {
|
||||
panic("failed to load PEM key: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// use custom config if config flag is set
|
||||
if configFlagSet {
|
||||
cfg = setup.ParseFlags(true)
|
||||
} else {
|
||||
cfg.Defaults(true)
|
||||
cfg.Defaults(config.DefaultOpts{
|
||||
Generate: true,
|
||||
Monolithic: true,
|
||||
})
|
||||
cfg.Global.PrivateKey = sk
|
||||
cfg.Global.JetStream.StoragePath = config.Path(fmt.Sprintf("%s/", *instanceName))
|
||||
cfg.UserAPI.AccountDatabase.ConnectionString = config.DataSource(fmt.Sprintf("file:%s-account.db", *instanceName))
|
||||
cfg.MediaAPI.Database.ConnectionString = config.DataSource(fmt.Sprintf("file:%s-mediaapi.db", *instanceName))
|
||||
|
|
@ -86,24 +120,27 @@ func main() {
|
|||
cfg.RoomServer.Database.ConnectionString = config.DataSource(fmt.Sprintf("file:%s-roomserver.db", *instanceName))
|
||||
cfg.KeyServer.Database.ConnectionString = config.DataSource(fmt.Sprintf("file:%s-keyserver.db", *instanceName))
|
||||
cfg.FederationAPI.Database.ConnectionString = config.DataSource(fmt.Sprintf("file:%s-federationapi.db", *instanceName))
|
||||
cfg.AppServiceAPI.Database.ConnectionString = config.DataSource(fmt.Sprintf("file:%s-appservice.db", *instanceName))
|
||||
cfg.MSCs.MSCs = []string{"msc2836"}
|
||||
cfg.MSCs.Database.ConnectionString = config.DataSource(fmt.Sprintf("file:%s-mscs.db", *instanceName))
|
||||
cfg.ClientAPI.RegistrationDisabled = false
|
||||
cfg.ClientAPI.OpenRegistrationWithoutVerificationEnabled = true
|
||||
if err = cfg.Derive(); err != nil {
|
||||
if err := cfg.Derive(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// always override ServerName, PrivateKey and KeyID
|
||||
cfg.Global.ServerName = gomatrixserverlib.ServerName(ygg.DerivedServerName())
|
||||
cfg.Global.PrivateKey = ygg.PrivateKey()
|
||||
cfg.Global.KeyID = signing.KeyID
|
||||
pk = sk.Public().(ed25519.PublicKey)
|
||||
cfg.Global.ServerName = gomatrixserverlib.ServerName(hex.EncodeToString(pk))
|
||||
cfg.Global.KeyID = gomatrixserverlib.KeyID(signing.KeyID)
|
||||
|
||||
base := base.NewBaseDendrite(cfg, "Monolith")
|
||||
defer base.Close() // nolint: errcheck
|
||||
|
||||
ygg, err := yggconn.Setup(sk, *instanceName, ".", *instancePeer, *instanceListen)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
federation := ygg.CreateFederationClient(base)
|
||||
|
||||
serverKeyAPI := &signing.YggdrasilKeys{}
|
||||
|
|
|
|||
|
|
@ -18,15 +18,13 @@ import (
|
|||
"context"
|
||||
"crypto/ed25519"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
"github.com/neilalexander/utp"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
ironwoodtypes "github.com/Arceliar/ironwood/types"
|
||||
yggdrasilconfig "github.com/yggdrasil-network/yggdrasil-go/src/config"
|
||||
|
|
@ -57,48 +55,38 @@ func (n *Node) DialerContext(ctx context.Context, _, address string) (net.Conn,
|
|||
return n.utpSocket.DialAddrContext(ctx, pk)
|
||||
}
|
||||
|
||||
func Setup(instanceName, storageDirectory, peerURI string) (*Node, error) {
|
||||
func Setup(sk ed25519.PrivateKey, instanceName, storageDirectory, peerURI, listenURI string) (*Node, error) {
|
||||
n := &Node{
|
||||
core: &yggdrasilcore.Core{},
|
||||
config: yggdrasildefaults.GenerateConfig(),
|
||||
multicast: &yggdrasilmulticast.Multicast{},
|
||||
log: gologme.New(os.Stdout, "YGG ", log.Flags()),
|
||||
log: gologme.New(logrus.StandardLogger().Writer(), "", 0),
|
||||
incoming: make(chan net.Conn),
|
||||
}
|
||||
|
||||
yggfile := fmt.Sprintf("%s/%s-yggdrasil.conf", storageDirectory, instanceName)
|
||||
if _, err := os.Stat(yggfile); !os.IsNotExist(err) {
|
||||
yggconf, e := os.ReadFile(yggfile)
|
||||
if e != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := json.Unmarshal([]byte(yggconf), &n.config); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
options := []yggdrasilcore.SetupOption{
|
||||
yggdrasilcore.AdminListenAddress("none"),
|
||||
}
|
||||
if listenURI != "" {
|
||||
options = append(options, yggdrasilcore.ListenAddress(listenURI))
|
||||
}
|
||||
|
||||
n.config.Peers = []string{}
|
||||
if peerURI != "" {
|
||||
n.config.Peers = append(n.config.Peers, peerURI)
|
||||
for _, uri := range strings.Split(peerURI, ",") {
|
||||
options = append(options, yggdrasilcore.Peer{
|
||||
URI: uri,
|
||||
})
|
||||
}
|
||||
}
|
||||
n.config.AdminListen = "none"
|
||||
|
||||
j, err := json.MarshalIndent(n.config, "", " ")
|
||||
if err != nil {
|
||||
var err error
|
||||
if n.core, err = yggdrasilcore.New(sk, options...); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if e := os.WriteFile(yggfile, j, 0600); e != nil {
|
||||
n.log.Printf("Couldn't write private key to file '%s': %s\n", yggfile, e)
|
||||
}
|
||||
|
||||
n.log.EnableLevel("error")
|
||||
n.log.EnableLevel("warn")
|
||||
n.log.EnableLevel("info")
|
||||
if err = n.core.Start(n.config, n.log); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
n.utpSocket, err = utp.NewSocketFromPacketConnNoClose(n.core)
|
||||
if err != nil {
|
||||
n.core.SetLogger(n.log)
|
||||
if n.utpSocket, err = utp.NewSocketFromPacketConnNoClose(n.core); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err = n.multicast.Init(n.core, n.config, n.log, nil); err != nil {
|
||||
|
|
@ -108,7 +96,7 @@ func Setup(instanceName, storageDirectory, peerURI string) (*Node, error) {
|
|||
panic(err)
|
||||
}
|
||||
|
||||
n.log.Println("Public key:", n.core.PublicKey())
|
||||
n.log.Printf("Public key: %x", n.core.PublicKey())
|
||||
go n.listenFromYgg()
|
||||
|
||||
return n, nil
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package main
|
|||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/matrix-org/dendrite/setup/config"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
|
|
@ -11,91 +12,81 @@ import (
|
|||
)
|
||||
|
||||
func main() {
|
||||
defaultsForCI := flag.Bool("ci", false, "sane defaults for CI testing")
|
||||
defaultsForCI := flag.Bool("ci", false, "Populate the configuration with sane defaults for use in CI")
|
||||
serverName := flag.String("server", "", "The domain name of the server if not 'localhost'")
|
||||
dbURI := flag.String("db", "", "The DB URI to use for all components if not SQLite files")
|
||||
dbURI := flag.String("db", "", "The DB URI to use for all components (PostgreSQL only)")
|
||||
dirPath := flag.String("dir", "./", "The folder to use for paths (like SQLite databases, media storage)")
|
||||
normalise := flag.String("normalise", "", "Normalise an existing configuration file by adding new/missing options and defaults")
|
||||
polylith := flag.Bool("polylith", false, "Generate a config that makes sense for polylith deployments")
|
||||
flag.Parse()
|
||||
|
||||
cfg := &config.Dendrite{
|
||||
Version: config.Version,
|
||||
}
|
||||
cfg.Defaults(true)
|
||||
if *serverName != "" {
|
||||
cfg.Global.ServerName = gomatrixserverlib.ServerName(*serverName)
|
||||
}
|
||||
if *dbURI != "" {
|
||||
cfg.AppServiceAPI.Database.ConnectionString = config.DataSource(*dbURI)
|
||||
cfg.FederationAPI.Database.ConnectionString = config.DataSource(*dbURI)
|
||||
cfg.KeyServer.Database.ConnectionString = config.DataSource(*dbURI)
|
||||
cfg.MSCs.Database.ConnectionString = config.DataSource(*dbURI)
|
||||
cfg.MediaAPI.Database.ConnectionString = config.DataSource(*dbURI)
|
||||
cfg.RoomServer.Database.ConnectionString = config.DataSource(*dbURI)
|
||||
cfg.SyncAPI.Database.ConnectionString = config.DataSource(*dbURI)
|
||||
cfg.UserAPI.AccountDatabase.ConnectionString = config.DataSource(*dbURI)
|
||||
}
|
||||
cfg.Global.TrustedIDServers = []string{
|
||||
"matrix.org",
|
||||
"vector.im",
|
||||
}
|
||||
cfg.Logging = []config.LogrusHook{
|
||||
{
|
||||
Type: "file",
|
||||
Level: "info",
|
||||
Params: map[string]interface{}{
|
||||
"path": "/var/log/dendrite",
|
||||
},
|
||||
},
|
||||
}
|
||||
cfg.FederationAPI.KeyPerspectives = config.KeyPerspectives{
|
||||
{
|
||||
ServerName: "matrix.org",
|
||||
Keys: []config.KeyPerspectiveTrustKey{
|
||||
{
|
||||
KeyID: "ed25519:auto",
|
||||
PublicKey: "Noi6WqcDj0QmPxCNQqgezwTlBKrfqehY1u2FyWP9uYw",
|
||||
},
|
||||
{
|
||||
KeyID: "ed25519:a_RXGa",
|
||||
PublicKey: "l8Hft5qXKn1vfHrg3p4+W8gELQVo8N13JkluMfmn2sQ",
|
||||
var cfg *config.Dendrite
|
||||
if *normalise == "" {
|
||||
cfg = &config.Dendrite{
|
||||
Version: config.Version,
|
||||
}
|
||||
cfg.Defaults(config.DefaultOpts{
|
||||
Generate: true,
|
||||
Monolithic: !*polylith,
|
||||
})
|
||||
if *serverName != "" {
|
||||
cfg.Global.ServerName = gomatrixserverlib.ServerName(*serverName)
|
||||
}
|
||||
uri := config.DataSource(*dbURI)
|
||||
if *polylith || uri.IsSQLite() || uri == "" {
|
||||
for name, db := range map[string]*config.DatabaseOptions{
|
||||
"federationapi": &cfg.FederationAPI.Database,
|
||||
"keyserver": &cfg.KeyServer.Database,
|
||||
"mscs": &cfg.MSCs.Database,
|
||||
"mediaapi": &cfg.MediaAPI.Database,
|
||||
"roomserver": &cfg.RoomServer.Database,
|
||||
"syncapi": &cfg.SyncAPI.Database,
|
||||
"userapi": &cfg.UserAPI.AccountDatabase,
|
||||
} {
|
||||
if uri == "" {
|
||||
path := filepath.Join(*dirPath, fmt.Sprintf("dendrite_%s.db", name))
|
||||
db.ConnectionString = config.DataSource(fmt.Sprintf("file:%s", path))
|
||||
} else {
|
||||
db.ConnectionString = uri
|
||||
}
|
||||
}
|
||||
} else {
|
||||
cfg.Global.DatabaseOptions.ConnectionString = uri
|
||||
}
|
||||
cfg.Logging = []config.LogrusHook{
|
||||
{
|
||||
Type: "file",
|
||||
Level: "info",
|
||||
Params: map[string]interface{}{
|
||||
"path": filepath.Join(*dirPath, "log"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
cfg.MediaAPI.ThumbnailSizes = []config.ThumbnailSize{
|
||||
{
|
||||
Width: 32,
|
||||
Height: 32,
|
||||
ResizeMethod: "crop",
|
||||
},
|
||||
{
|
||||
Width: 96,
|
||||
Height: 96,
|
||||
ResizeMethod: "crop",
|
||||
},
|
||||
{
|
||||
Width: 640,
|
||||
Height: 480,
|
||||
ResizeMethod: "scale",
|
||||
},
|
||||
}
|
||||
|
||||
if *defaultsForCI {
|
||||
cfg.AppServiceAPI.DisableTLSValidation = true
|
||||
cfg.ClientAPI.RateLimiting.Enabled = false
|
||||
cfg.FederationAPI.DisableTLSValidation = false
|
||||
// don't hit matrix.org when running tests!!!
|
||||
cfg.FederationAPI.KeyPerspectives = config.KeyPerspectives{}
|
||||
cfg.MSCs.MSCs = []string{"msc2836", "msc2946", "msc2444", "msc2753"}
|
||||
cfg.Logging[0].Level = "trace"
|
||||
cfg.Logging[0].Type = "std"
|
||||
cfg.UserAPI.BCryptCost = bcrypt.MinCost
|
||||
cfg.Global.JetStream.InMemory = true
|
||||
cfg.ClientAPI.RegistrationDisabled = false
|
||||
cfg.ClientAPI.OpenRegistrationWithoutVerificationEnabled = true
|
||||
cfg.ClientAPI.RegistrationSharedSecret = "complement"
|
||||
cfg.Global.Presence = config.PresenceOptions{
|
||||
EnableInbound: true,
|
||||
EnableOutbound: true,
|
||||
}
|
||||
if *defaultsForCI {
|
||||
cfg.AppServiceAPI.DisableTLSValidation = true
|
||||
cfg.ClientAPI.RateLimiting.Enabled = false
|
||||
cfg.FederationAPI.DisableTLSValidation = false
|
||||
// don't hit matrix.org when running tests!!!
|
||||
cfg.FederationAPI.KeyPerspectives = config.KeyPerspectives{}
|
||||
cfg.MediaAPI.BasePath = config.Path(filepath.Join(*dirPath, "media"))
|
||||
cfg.MSCs.MSCs = []string{"msc2836", "msc2946", "msc2444", "msc2753"}
|
||||
cfg.Logging[0].Level = "trace"
|
||||
cfg.Logging[0].Type = "std"
|
||||
cfg.UserAPI.BCryptCost = bcrypt.MinCost
|
||||
cfg.Global.JetStream.InMemory = true
|
||||
cfg.Global.JetStream.StoragePath = config.Path(*dirPath)
|
||||
cfg.ClientAPI.RegistrationDisabled = false
|
||||
cfg.ClientAPI.OpenRegistrationWithoutVerificationEnabled = true
|
||||
cfg.ClientAPI.RegistrationSharedSecret = "complement"
|
||||
cfg.Global.Presence = config.PresenceOptions{
|
||||
EnableInbound: true,
|
||||
EnableOutbound: true,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
var err error
|
||||
if cfg, err = config.Load(*normalise, !*polylith); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -76,9 +76,14 @@ func main() {
|
|||
panic(err)
|
||||
}
|
||||
|
||||
var eventNIDs []types.EventNID
|
||||
eventNIDMap := map[types.EventNID]struct{}{}
|
||||
for _, entry := range append(removed, added...) {
|
||||
eventNIDs = append(eventNIDs, entry.EventNID)
|
||||
eventNIDMap[entry.EventNID] = struct{}{}
|
||||
}
|
||||
|
||||
eventNIDs := make([]types.EventNID, 0, len(eventNIDMap))
|
||||
for eventNID := range eventNIDMap {
|
||||
eventNIDs = append(eventNIDs, eventNID)
|
||||
}
|
||||
|
||||
var eventEntries []types.Event
|
||||
|
|
@ -129,12 +134,17 @@ func main() {
|
|||
stateEntries = append(stateEntries, entries...)
|
||||
}
|
||||
|
||||
var eventNIDs []types.EventNID
|
||||
eventNIDMap := map[types.EventNID]struct{}{}
|
||||
for _, entry := range stateEntries {
|
||||
eventNIDs = append(eventNIDs, entry.EventNID)
|
||||
eventNIDMap[entry.EventNID] = struct{}{}
|
||||
}
|
||||
|
||||
fmt.Println("Fetching", len(eventNIDs), "state events")
|
||||
eventNIDs := make([]types.EventNID, 0, len(eventNIDMap))
|
||||
for eventNID := range eventNIDMap {
|
||||
eventNIDs = append(eventNIDs, eventNID)
|
||||
}
|
||||
|
||||
fmt.Println("Fetching", len(eventNIDMap), "state events")
|
||||
eventEntries, err := roomserverDB.Events(ctx, eventNIDs)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
|
|
|||
|
|
@ -132,13 +132,6 @@ app_service_api:
|
|||
listen: http://[::]:7777 # The listen address for incoming API requests
|
||||
connect: http://app_service_api:7777 # The connect address for other components to use
|
||||
|
||||
# Database configuration for this component.
|
||||
database:
|
||||
connection_string: postgresql://username:password@hostname/dendrite_appservice?sslmode=disable
|
||||
max_open_conns: 10
|
||||
max_idle_conns: 2
|
||||
conn_max_lifetime: -1
|
||||
|
||||
# Disable the validation of TLS certificates of appservices. This is
|
||||
# not recommended in production since it may allow appservice traffic
|
||||
# to be sent to an insecure endpoint.
|
||||
|
|
|
|||
|
|
@ -67,14 +67,15 @@ func NewKeyChangeConsumer(
|
|||
// Start consuming from key servers
|
||||
func (t *KeyChangeConsumer) Start() error {
|
||||
return jetstream.JetStreamConsumer(
|
||||
t.ctx, t.jetstream, t.topic, t.durable, t.onMessage,
|
||||
nats.DeliverAll(), nats.ManualAck(),
|
||||
t.ctx, t.jetstream, t.topic, t.durable, 1,
|
||||
t.onMessage, nats.DeliverAll(), nats.ManualAck(),
|
||||
)
|
||||
}
|
||||
|
||||
// onMessage is called in response to a message received on the
|
||||
// key change events topic from the key server.
|
||||
func (t *KeyChangeConsumer) onMessage(ctx context.Context, msg *nats.Msg) bool {
|
||||
func (t *KeyChangeConsumer) onMessage(ctx context.Context, msgs []*nats.Msg) bool {
|
||||
msg := msgs[0] // Guaranteed to exist if onMessage is called
|
||||
var m api.DeviceMessage
|
||||
if err := json.Unmarshal(msg.Data, &m); err != nil {
|
||||
logrus.WithError(err).Errorf("failed to read device message from key change topic")
|
||||
|
|
|
|||
|
|
@ -69,14 +69,15 @@ func (t *OutputPresenceConsumer) Start() error {
|
|||
return nil
|
||||
}
|
||||
return jetstream.JetStreamConsumer(
|
||||
t.ctx, t.jetstream, t.topic, t.durable, t.onMessage,
|
||||
t.ctx, t.jetstream, t.topic, t.durable, 1, t.onMessage,
|
||||
nats.DeliverAll(), nats.ManualAck(), nats.HeadersOnly(),
|
||||
)
|
||||
}
|
||||
|
||||
// onMessage is called in response to a message received on the presence
|
||||
// events topic from the client api.
|
||||
func (t *OutputPresenceConsumer) onMessage(ctx context.Context, msg *nats.Msg) bool {
|
||||
func (t *OutputPresenceConsumer) onMessage(ctx context.Context, msgs []*nats.Msg) bool {
|
||||
msg := msgs[0] // Guaranteed to exist if onMessage is called
|
||||
// only send presence events which originated from us
|
||||
userID := msg.Header.Get(jetstream.UserID)
|
||||
_, serverName, err := gomatrixserverlib.SplitID('@', userID)
|
||||
|
|
|
|||
|
|
@ -65,14 +65,15 @@ func NewOutputReceiptConsumer(
|
|||
// Start consuming from the clientapi
|
||||
func (t *OutputReceiptConsumer) Start() error {
|
||||
return jetstream.JetStreamConsumer(
|
||||
t.ctx, t.jetstream, t.topic, t.durable, t.onMessage,
|
||||
t.ctx, t.jetstream, t.topic, t.durable, 1, t.onMessage,
|
||||
nats.DeliverAll(), nats.ManualAck(), nats.HeadersOnly(),
|
||||
)
|
||||
}
|
||||
|
||||
// onMessage is called in response to a message received on the receipt
|
||||
// events topic from the client api.
|
||||
func (t *OutputReceiptConsumer) onMessage(ctx context.Context, msg *nats.Msg) bool {
|
||||
func (t *OutputReceiptConsumer) onMessage(ctx context.Context, msgs []*nats.Msg) bool {
|
||||
msg := msgs[0] // Guaranteed to exist if onMessage is called
|
||||
receipt := syncTypes.OutputReceiptEvent{
|
||||
UserID: msg.Header.Get(jetstream.UserID),
|
||||
RoomID: msg.Header.Get(jetstream.RoomID),
|
||||
|
|
|
|||
|
|
@ -68,8 +68,8 @@ func NewOutputRoomEventConsumer(
|
|||
// Start consuming from room servers
|
||||
func (s *OutputRoomEventConsumer) Start() error {
|
||||
return jetstream.JetStreamConsumer(
|
||||
s.ctx, s.jetstream, s.topic, s.durable, s.onMessage,
|
||||
nats.DeliverAll(), nats.ManualAck(),
|
||||
s.ctx, s.jetstream, s.topic, s.durable, 1,
|
||||
s.onMessage, nats.DeliverAll(), nats.ManualAck(),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -77,7 +77,8 @@ func (s *OutputRoomEventConsumer) Start() error {
|
|||
// It is unsafe to call this with messages for the same room in multiple gorountines
|
||||
// because updates it will likely fail with a types.EventIDMismatchError when it
|
||||
// realises that it cannot update the room state using the deltas.
|
||||
func (s *OutputRoomEventConsumer) onMessage(ctx context.Context, msg *nats.Msg) bool {
|
||||
func (s *OutputRoomEventConsumer) onMessage(ctx context.Context, msgs []*nats.Msg) bool {
|
||||
msg := msgs[0] // Guaranteed to exist if onMessage is called
|
||||
// Parse out the event JSON
|
||||
var output api.OutputEvent
|
||||
if err := json.Unmarshal(msg.Data, &output); err != nil {
|
||||
|
|
|
|||
|
|
@ -63,14 +63,15 @@ func NewOutputSendToDeviceConsumer(
|
|||
// Start consuming from the client api
|
||||
func (t *OutputSendToDeviceConsumer) Start() error {
|
||||
return jetstream.JetStreamConsumer(
|
||||
t.ctx, t.jetstream, t.topic, t.durable, t.onMessage,
|
||||
nats.DeliverAll(), nats.ManualAck(),
|
||||
t.ctx, t.jetstream, t.topic, t.durable, 1,
|
||||
t.onMessage, nats.DeliverAll(), nats.ManualAck(),
|
||||
)
|
||||
}
|
||||
|
||||
// onMessage is called in response to a message received on the
|
||||
// send-to-device events topic from the client api.
|
||||
func (t *OutputSendToDeviceConsumer) onMessage(ctx context.Context, msg *nats.Msg) bool {
|
||||
func (t *OutputSendToDeviceConsumer) onMessage(ctx context.Context, msgs []*nats.Msg) bool {
|
||||
msg := msgs[0] // Guaranteed to exist if onMessage is called
|
||||
// only send send-to-device events which originated from us
|
||||
sender := msg.Header.Get("sender")
|
||||
_, originServerName, err := gomatrixserverlib.SplitID('@', sender)
|
||||
|
|
|
|||
|
|
@ -62,14 +62,15 @@ func NewOutputTypingConsumer(
|
|||
// Start consuming from the clientapi
|
||||
func (t *OutputTypingConsumer) Start() error {
|
||||
return jetstream.JetStreamConsumer(
|
||||
t.ctx, t.jetstream, t.topic, t.durable, t.onMessage,
|
||||
t.ctx, t.jetstream, t.topic, t.durable, 1, t.onMessage,
|
||||
nats.DeliverAll(), nats.ManualAck(), nats.HeadersOnly(),
|
||||
)
|
||||
}
|
||||
|
||||
// onMessage is called in response to a message received on the typing
|
||||
// events topic from the client api.
|
||||
func (t *OutputTypingConsumer) onMessage(ctx context.Context, msg *nats.Msg) bool {
|
||||
func (t *OutputTypingConsumer) onMessage(ctx context.Context, msgs []*nats.Msg) bool {
|
||||
msg := msgs[0] // Guaranteed to exist if onMessage is called
|
||||
// Extract the typing event from msg.
|
||||
roomID := msg.Header.Get(jetstream.RoomID)
|
||||
userID := msg.Header.Get(jetstream.UserID)
|
||||
|
|
|
|||
|
|
@ -75,7 +75,10 @@ func TestMain(m *testing.M) {
|
|||
// Draw up just enough Dendrite config for the server key
|
||||
// API to work.
|
||||
cfg := &config.Dendrite{}
|
||||
cfg.Defaults(true)
|
||||
cfg.Defaults(config.DefaultOpts{
|
||||
Generate: true,
|
||||
Monolithic: true,
|
||||
})
|
||||
cfg.Global.ServerName = gomatrixserverlib.ServerName(s.name)
|
||||
cfg.Global.PrivateKey = testPriv
|
||||
cfg.Global.JetStream.InMemory = true
|
||||
|
|
|
|||
|
|
@ -263,7 +263,10 @@ func testFederationAPIJoinThenKeyUpdate(t *testing.T, dbType test.DBType) {
|
|||
func TestRoomsV3URLEscapeDoNot404(t *testing.T) {
|
||||
_, privKey, _ := ed25519.GenerateKey(nil)
|
||||
cfg := &config.Dendrite{}
|
||||
cfg.Defaults(true)
|
||||
cfg.Defaults(config.DefaultOpts{
|
||||
Generate: true,
|
||||
Monolithic: true,
|
||||
})
|
||||
cfg.Global.KeyID = gomatrixserverlib.KeyID("ed25519:auto")
|
||||
cfg.Global.ServerName = gomatrixserverlib.ServerName("localhost")
|
||||
cfg.Global.PrivateKey = privKey
|
||||
|
|
|
|||
17
go.mod
17
go.mod
|
|
@ -1,7 +1,7 @@
|
|||
module github.com/matrix-org/dendrite
|
||||
|
||||
require (
|
||||
github.com/Arceliar/ironwood v0.0.0-20220306165321-319147a02d98
|
||||
github.com/Arceliar/ironwood v0.0.0-20220409035209-b7f71f05435a
|
||||
github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.0
|
||||
github.com/MFAshby/stdemuxerhook v1.0.0
|
||||
|
|
@ -41,12 +41,12 @@ require (
|
|||
github.com/tidwall/sjson v1.2.4
|
||||
github.com/uber/jaeger-client-go v2.30.0+incompatible
|
||||
github.com/uber/jaeger-lib v2.4.1+incompatible
|
||||
github.com/yggdrasil-network/yggdrasil-go v0.4.3
|
||||
github.com/yggdrasil-network/yggdrasil-go v0.4.5-0.20220901155642-4f2abece817c
|
||||
go.uber.org/atomic v1.9.0
|
||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa
|
||||
golang.org/x/image v0.0.0-20220413100746-70e8d0d3baa9
|
||||
golang.org/x/mobile v0.0.0-20220518205345-8578da9835fd
|
||||
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e
|
||||
golang.org/x/mobile v0.0.0-20220722155234-aaac322e2105
|
||||
golang.org/x/net v0.0.0-20220728211354-c7608f3a8462
|
||||
golang.org/x/term v0.0.0-20220526004731-065cf7ba2467
|
||||
gopkg.in/h2non/bimg.v1 v1.1.9
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
|
|
@ -82,7 +82,7 @@ require (
|
|||
github.com/docker/go-units v0.4.0 // indirect
|
||||
github.com/dustin/go-humanize v1.0.0 // indirect
|
||||
github.com/frankban/quicktest v1.14.3 // indirect
|
||||
github.com/fsnotify/fsnotify v1.4.9 // indirect
|
||||
github.com/fsnotify/fsnotify v1.5.4 // indirect
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 // indirect
|
||||
|
|
@ -111,7 +111,7 @@ require (
|
|||
github.com/nats-io/nkeys v0.3.0 // indirect
|
||||
github.com/nats-io/nuid v1.0.1 // indirect
|
||||
github.com/nxadm/tail v1.4.8 // indirect
|
||||
github.com/onsi/ginkgo v1.16.4 // indirect
|
||||
github.com/onsi/ginkgo v1.16.5 // indirect
|
||||
github.com/onsi/gomega v1.17.0 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 // indirect
|
||||
|
|
@ -123,12 +123,11 @@ require (
|
|||
github.com/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/pretty v1.2.0 // indirect
|
||||
go.etcd.io/bbolt v1.3.5 // indirect
|
||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
|
||||
golang.org/x/sys v0.0.0-20220731174439-a90be440212d // indirect
|
||||
golang.org/x/text v0.3.8-0.20211004125949-5bd84dd9b33b // indirect
|
||||
golang.org/x/time v0.0.0-20220411224347-583f2d630306 // indirect
|
||||
golang.org/x/tools v0.1.10 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||
golang.org/x/tools v0.1.12 // indirect
|
||||
google.golang.org/protobuf v1.27.1 // indirect
|
||||
gopkg.in/macaroon.v2 v2.1.0 // indirect
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
|
||||
|
|
|
|||
62
go.sum
62
go.sum
|
|
@ -38,9 +38,8 @@ dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBr
|
|||
dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4=
|
||||
dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=
|
||||
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
|
||||
github.com/Arceliar/ironwood v0.0.0-20211125050254-8951369625d0/go.mod h1:RP72rucOFm5udrnEzTmIWLRVGQiV/fSUAQXJ0RST/nk=
|
||||
github.com/Arceliar/ironwood v0.0.0-20220306165321-319147a02d98 h1:PsaZb47k7WB1V+AlGpb+W7SM+ZOhp16vVevg5gl9YkU=
|
||||
github.com/Arceliar/ironwood v0.0.0-20220306165321-319147a02d98/go.mod h1:RP72rucOFm5udrnEzTmIWLRVGQiV/fSUAQXJ0RST/nk=
|
||||
github.com/Arceliar/ironwood v0.0.0-20220409035209-b7f71f05435a h1:yfbnOyqPcx2gi5cFIJ2rlPz5M6rFPHT/c8FgZmFjCdc=
|
||||
github.com/Arceliar/ironwood v0.0.0-20220409035209-b7f71f05435a/go.mod h1:RP72rucOFm5udrnEzTmIWLRVGQiV/fSUAQXJ0RST/nk=
|
||||
github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979 h1:WndgpSW13S32VLQ3ugUxx2EnnWmgba1kCqPkd4Gk1yQ=
|
||||
github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979/go.mod h1:6Lkn+/zJilRMsKmbmG1RPoamiArC6HS73xbwRyp3UyI=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210608223527-2377c96fe795/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
|
||||
|
|
@ -63,8 +62,6 @@ github.com/RoaringBitmap/roaring v0.9.4 h1:ckvZSX5gwCRaJYBNe7syNawCU5oruY9gQmjXl
|
|||
github.com/RoaringBitmap/roaring v0.9.4/go.mod h1:icnadbWcNyfEHlYdr+tDlOTih1Bf/h+rzPpv4sbomAA=
|
||||
github.com/RyanCarrier/dijkstra v1.0.0/go.mod h1:5agGUBNEtUAGIANmbw09fuO3a2htPEkc1jNH01qxCWA=
|
||||
github.com/RyanCarrier/dijkstra-1 v0.0.0-20170512020943-0e5801a26345/go.mod h1:OK4EvWJ441LQqGzed5NGB6vKBAE34n3z7iayPcEwr30=
|
||||
github.com/VividCortex/ewma v1.1.1/go.mod h1:2Tkkvm3sRDVXaiyucHiACn4cqf7DpdyLvmxzcbUokwA=
|
||||
github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4=
|
||||
github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
|
||||
github.com/albertorestifo/dijkstra v0.0.0-20160910063646-aba76f725f72/go.mod h1:o+JdB7VetTHjLhU0N57x18B9voDBQe0paApdEAEoEfw=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
|
|
@ -139,7 +136,6 @@ github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cb
|
|||
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE=
|
||||
github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ=
|
||||
github.com/cheggaaa/pb/v3 v3.0.8/go.mod h1:UICbiLec/XO6Hw6k+BHEtHeQFzzBH4i2/qk/ow1EJTA=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
|
|
@ -179,8 +175,6 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF
|
|||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
|
||||
github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
|
||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
||||
github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
|
||||
github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=
|
||||
|
|
@ -189,8 +183,9 @@ github.com/frankban/quicktest v1.7.2/go.mod h1:jaStnuzAqU1AJdCO0l53JDCJrVDKcS03D
|
|||
github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
|
||||
github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
|
||||
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
|
||||
github.com/getsentry/sentry-go v0.13.0 h1:20dgTiUSfxRB/EhMPtxcL9ZEbM1ZdR+W/7f7NWD+xWo=
|
||||
github.com/getsentry/sentry-go v0.13.0/go.mod h1:EOsfu5ZdvKPfeHYV6pTVQnsjfp30+XA7//UooKNumH0=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
|
|
@ -269,7 +264,6 @@ github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiu
|
|||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
|
||||
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/gologme/log v1.2.0/go.mod h1:gq31gQ8wEHkR+WekdWsqDuf8pXTUZA9BnnzTuPz1Y9U=
|
||||
github.com/gologme/log v1.3.0 h1:l781G4dE+pbigClDSDzSaaYKtiueHCILUa/qSDsmHAo=
|
||||
github.com/gologme/log v1.3.0/go.mod h1:yKT+DvIPdDdDoPtqFrFxheooyVmoqi0BAsw+erN3wA4=
|
||||
github.com/google/btree v0.0.0-20180124185431-e89373fe6b4a/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
|
|
@ -321,11 +315,9 @@ github.com/h2non/filetype v1.1.3 h1:FKkx9QbD7HR/zjK1Ia5XiBsq9zdLi5Kf3zGyFTAFkGg=
|
|||
github.com/h2non/filetype v1.1.3/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY=
|
||||
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw=
|
||||
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
|
||||
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/hjson/hjson-go v3.1.0+incompatible/go.mod h1:qsetwF8NlsTsOTwZTApNlTCerV+b2GjYRRcIk4JMFio=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/huandu/xstrings v1.0.0 h1:pO2K/gKgKaat5LdpAhxhluX2GPQMaI3W5FUz/I/UnWk=
|
||||
github.com/huandu/xstrings v1.0.0/go.mod h1:4qWG/gcEcfX4z/mBDHJ++3ReCw9ibxbsNJbcucJdbSo=
|
||||
|
|
@ -379,8 +371,6 @@ github.com/lib/pq v1.10.5/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
|||
github.com/lucas-clemente/quic-go v0.28.1 h1:Uo0lvVxWg5la9gflIF9lwa39ONq85Xq2D91YNEIslzU=
|
||||
github.com/lucas-clemente/quic-go v0.28.1/go.mod h1:oGz5DKK41cJt5+773+BSO9BXDsREY4HLf7+0odGAPO0=
|
||||
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
|
||||
github.com/lxn/walk v0.0.0-20210112085537-c389da54e794/go.mod h1:E23UucZGqpuUANJooIbHWCufXvOcT6E7Stq81gU+CSQ=
|
||||
github.com/lxn/win v0.0.0-20210218163916-a377121e959e/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk=
|
||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/marten-seemann/qpack v0.2.1/go.mod h1:F7Gl5L1jIgN1D11ucXefiuJS9UMVP2opoCp2jDKb7wc=
|
||||
|
|
@ -406,12 +396,8 @@ github.com/matrix-org/pinecone v0.0.0-20220803093810-b7a830c08fb9/go.mod h1:P4Mq
|
|||
github.com/matrix-org/util v0.0.0-20190711121626-527ce5ddefc7/go.mod h1:vVQlW/emklohkZnOPwD3LrZUBqdfsbiyO3p1lNV8F6U=
|
||||
github.com/matrix-org/util v0.0.0-20200807132607-55161520e1d4 h1:eCEHXWDv9Rm335MSuB49mFUK44bwZPFSDde3ORE3syk=
|
||||
github.com/matrix-org/util v0.0.0-20200807132607-55161520e1d4/go.mod h1:vVQlW/emklohkZnOPwD3LrZUBqdfsbiyO3p1lNV8F6U=
|
||||
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
|
||||
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-sqlite3 v1.14.13 h1:1tj15ngiFfcZzii7yd82foL+ks+ouQcj8j/TPq3fk1I=
|
||||
github.com/mattn/go-sqlite3 v1.14.13/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||
github.com/mattomatic/dijkstra v0.0.0-20130617153013-6f6d134eb237/go.mod h1:UOnLAUmVG5paym8pD3C4B9BQylUDC2vXFJJpT7JrlEA=
|
||||
|
|
@ -426,7 +412,6 @@ github.com/minio/highwayhash v1.0.2 h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA
|
|||
github.com/minio/highwayhash v1.0.2/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/moby/term v0.0.0-20210610120745-9d4ed1856297 h1:yH0SvLzcbZxcJXho2yh7CqdENGMQe73Cw3woZBpPli0=
|
||||
github.com/moby/term v0.0.0-20210610120745-9d4ed1856297/go.mod h1:vgPCkQMyxTZ7IDy8SXRufE172gr8+K/JE/7hHFxHW3A=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
|
|
@ -471,8 +456,9 @@ github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W
|
|||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||
github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
|
||||
github.com/onsi/ginkgo v1.16.2/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E=
|
||||
github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc=
|
||||
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
|
||||
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
|
||||
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
|
||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
|
|
@ -522,8 +508,6 @@ github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O
|
|||
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||
github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU=
|
||||
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
|
||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
|
|
@ -604,11 +588,10 @@ github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49u
|
|||
github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM=
|
||||
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
|
||||
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
|
||||
github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
|
||||
github.com/willf/bitset v1.1.9/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
|
||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||
github.com/yggdrasil-network/yggdrasil-go v0.4.3 h1:LNS7kNpKzFlxQ9xmD5tfmMEvzwa+utBoD6pV9t2a8q4=
|
||||
github.com/yggdrasil-network/yggdrasil-go v0.4.3/go.mod h1:A1/8kOQT7vzBxlkQtLf1KzJR0cbfL/2zjOCiYOAdjjo=
|
||||
github.com/yggdrasil-network/yggdrasil-go v0.4.5-0.20220901155642-4f2abece817c h1:/cTmA6pV2Z20BT/FGSmnb5BmJ8eRbDP0HbCB5IO1aKw=
|
||||
github.com/yggdrasil-network/yggdrasil-go v0.4.5-0.20220901155642-4f2abece817c/go.mod h1:cIwhYwX9yT9Bcei59O0oOBSaj+kQP+9aVQUMWHh5R00=
|
||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
|
|
@ -676,10 +659,9 @@ golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPI
|
|||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
golang.org/x/mobile v0.0.0-20220112015953-858099ff7816/go.mod h1:pe2sM7Uk+2Su1y7u/6Z8KJ24D7lepUjFZbhFOrmDfuQ=
|
||||
golang.org/x/mobile v0.0.0-20220325161704-447654d348e3/go.mod h1:pe2sM7Uk+2Su1y7u/6Z8KJ24D7lepUjFZbhFOrmDfuQ=
|
||||
golang.org/x/mobile v0.0.0-20220518205345-8578da9835fd h1:x1GptNaTtxPAlTVIAJk61fuXg0y17h09DTxyb+VNC/k=
|
||||
golang.org/x/mobile v0.0.0-20220518205345-8578da9835fd/go.mod h1:pe2sM7Uk+2Su1y7u/6Z8KJ24D7lepUjFZbhFOrmDfuQ=
|
||||
golang.org/x/mobile v0.0.0-20220722155234-aaac322e2105 h1:3vUV5x5+3LfQbgk7paCM6INOaJG9xXQbn79xoNkwfIk=
|
||||
golang.org/x/mobile v0.0.0-20220722155234-aaac322e2105/go.mod h1:pe2sM7Uk+2Su1y7u/6Z8KJ24D7lepUjFZbhFOrmDfuQ=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
|
|
@ -687,8 +669,8 @@ golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzB
|
|||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
|
|
@ -731,10 +713,9 @@ golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qx
|
|||
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210927181540-4e4d966f7476/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211011170408-caeb26a5c8c0/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211101193420-4a448f8816b3/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e h1:TsQ7F31D3bUCLeqPT0u+yjp1guoArKaNKmCr22PYgTQ=
|
||||
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.0.0-20220728211354-c7608f3a8462 h1:UreQrH7DbFXSi9ZFox6FNT3WBooWmdANpU+IfkT1T4I=
|
||||
golang.org/x/net v0.0.0-20220728211354-c7608f3a8462/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
|
|
@ -789,7 +770,6 @@ golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
|
@ -805,12 +785,10 @@ golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
|
|
@ -819,10 +797,9 @@ golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211102192858-4dd72447c267/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220405052023-b1e9470b6e64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220731174439-a90be440212d h1:Sv5ogFZatcgIMMtBSTTAgMYsicp25MXBubjXNDKwm80=
|
||||
|
|
@ -897,19 +874,14 @@ golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4f
|
|||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
|
||||
golang.org/x/tools v0.1.8-0.20211022200916-316ba0b74098/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
|
||||
golang.org/x/tools v0.1.10 h1:QjFRCZxdOhBJ/UNgnBZLbNV13DlbnK0quyivTnXJM20=
|
||||
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
|
||||
golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20210927201915-bb745b2ea326/go.mod h1:SDoazCvdy7RDjBPNEMBwrXhomlmtG7svs8mgwWEqtVI=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20211012062646-82d2aa87aa62/go.mod h1:id8Oh3eCCmpj9uVGWVjsUAl6UPX5ysMLzu6QxJU2UOU=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20211017052713-f87e87af0d9a/go.mod h1:id8Oh3eCCmpj9uVGWVjsUAl6UPX5ysMLzu6QxJU2UOU=
|
||||
golang.zx2c4.com/wireguard/windows v0.4.12/go.mod h1:PW4y+d9oY83XU9rRwRwrJDwEMuhVjMxu2gfD1cfzS7w=
|
||||
gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo=
|
||||
gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0=
|
||||
gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
|
||||
|
|
|
|||
|
|
@ -60,6 +60,9 @@ func MakeAuthAPI(
|
|||
// add the user to Sentry, if enabled
|
||||
hub := sentry.GetHubFromContext(req.Context())
|
||||
if hub != nil {
|
||||
hub.Scope().SetUser(sentry.User{
|
||||
Username: device.UserID,
|
||||
})
|
||||
hub.Scope().SetTag("user_id", device.UserID)
|
||||
hub.Scope().SetTag("device_id", device.ID)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ var build string
|
|||
const (
|
||||
VersionMajor = 0
|
||||
VersionMinor = 9
|
||||
VersionPatch = 5
|
||||
VersionPatch = 6
|
||||
VersionTag = "" // example: "rc1"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -55,14 +55,15 @@ func NewDeviceListUpdateConsumer(
|
|||
// Start consuming from key servers
|
||||
func (t *DeviceListUpdateConsumer) Start() error {
|
||||
return jetstream.JetStreamConsumer(
|
||||
t.ctx, t.jetstream, t.topic, t.durable, t.onMessage,
|
||||
nats.DeliverAll(), nats.ManualAck(),
|
||||
t.ctx, t.jetstream, t.topic, t.durable, 1,
|
||||
t.onMessage, nats.DeliverAll(), nats.ManualAck(),
|
||||
)
|
||||
}
|
||||
|
||||
// onMessage is called in response to a message received on the
|
||||
// key change events topic from the key server.
|
||||
func (t *DeviceListUpdateConsumer) onMessage(ctx context.Context, msg *nats.Msg) bool {
|
||||
func (t *DeviceListUpdateConsumer) onMessage(ctx context.Context, msgs []*nats.Msg) bool {
|
||||
msg := msgs[0] // Guaranteed to exist if onMessage is called
|
||||
var m gomatrixserverlib.DeviceListUpdateEvent
|
||||
if err := json.Unmarshal(msg.Data, &m); err != nil {
|
||||
logrus.WithError(err).Errorf("Failed to read from device list update input topic")
|
||||
|
|
|
|||
148
keyserver/consumers/roomevent.go
Normal file
148
keyserver/consumers/roomevent.go
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
// Copyright 2017 Vector Creations Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package consumers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
"github.com/nats-io/nats.go"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/tidwall/gjson"
|
||||
|
||||
fedapi "github.com/matrix-org/dendrite/federationapi/api"
|
||||
"github.com/matrix-org/dendrite/keyserver/producers"
|
||||
"github.com/matrix-org/dendrite/keyserver/storage"
|
||||
"github.com/matrix-org/dendrite/roomserver/api"
|
||||
"github.com/matrix-org/dendrite/setup/config"
|
||||
"github.com/matrix-org/dendrite/setup/jetstream"
|
||||
"github.com/matrix-org/dendrite/setup/process"
|
||||
)
|
||||
|
||||
// OutputRoomEventConsumer consumes events that originated in the room server.
|
||||
type OutputRoomEventConsumer struct {
|
||||
ctx context.Context
|
||||
cfg *config.KeyServer
|
||||
jetstream nats.JetStreamContext
|
||||
durable string
|
||||
db storage.Database
|
||||
topic string
|
||||
producer *producers.KeyChange
|
||||
fedClient fedapi.KeyserverFederationAPI
|
||||
}
|
||||
|
||||
// NewOutputRoomEventConsumer creates a new OutputRoomEventConsumer. Call Start() to begin consuming from room servers.
|
||||
func NewOutputRoomEventConsumer(
|
||||
process *process.ProcessContext,
|
||||
cfg *config.KeyServer,
|
||||
js nats.JetStreamContext,
|
||||
store storage.Database,
|
||||
keyChangeProducer *producers.KeyChange,
|
||||
fedClient fedapi.KeyserverFederationAPI,
|
||||
|
||||
) *OutputRoomEventConsumer {
|
||||
return &OutputRoomEventConsumer{
|
||||
ctx: process.Context(),
|
||||
cfg: cfg,
|
||||
jetstream: js,
|
||||
db: store,
|
||||
durable: cfg.Matrix.JetStream.Durable("KeyserverAPIRoomServerConsumer"),
|
||||
topic: cfg.Matrix.JetStream.Prefixed(jetstream.OutputRoomEvent),
|
||||
producer: keyChangeProducer,
|
||||
fedClient: fedClient,
|
||||
}
|
||||
}
|
||||
|
||||
// Start consuming from room servers
|
||||
func (s *OutputRoomEventConsumer) Start() error {
|
||||
return jetstream.JetStreamConsumer(
|
||||
s.ctx, s.jetstream, s.topic, s.durable, 1,
|
||||
s.onMessage, nats.DeliverAll(), nats.ManualAck(),
|
||||
)
|
||||
}
|
||||
|
||||
// onMessage is called when the federation server receives a new event from the room server output log.
|
||||
// It is unsafe to call this with messages for the same room in multiple gorountines
|
||||
// because updates it will likely fail with a types.EventIDMismatchError when it
|
||||
// realises that it cannot update the room state using the deltas.
|
||||
func (s *OutputRoomEventConsumer) onMessage(ctx context.Context, msgs []*nats.Msg) bool {
|
||||
msg := msgs[0] // Guaranteed to exist if onMessage is called
|
||||
// Parse out the event JSON
|
||||
var output api.OutputEvent
|
||||
if err := json.Unmarshal(msg.Data, &output); err != nil {
|
||||
// If the message was invalid, log it and move on to the next message in the stream
|
||||
log.WithError(err).Errorf("roomserver output log: message parse failure")
|
||||
return true
|
||||
}
|
||||
|
||||
switch output.Type {
|
||||
case api.OutputTypeNewRoomEvent:
|
||||
log.Debugf("XXX: got room event")
|
||||
ev := output.NewRoomEvent.Event
|
||||
// Only handle m.room.member events
|
||||
if ev.Type() != gomatrixserverlib.MRoomMember || ev.StateKey() == nil {
|
||||
log.Debugf("XXX: ignoring, not a membership event")
|
||||
return true
|
||||
}
|
||||
|
||||
_, domain, err := gomatrixserverlib.SplitID('@', *ev.StateKey())
|
||||
if err != nil {
|
||||
log.WithError(err).Errorf("unable to split statekey")
|
||||
return true
|
||||
}
|
||||
|
||||
// ignore membership changes from other servers
|
||||
if domain != s.cfg.Matrix.ServerName {
|
||||
log.Debugf("XXX: ignoring event from different server")
|
||||
return true
|
||||
}
|
||||
|
||||
curMembership, err := ev.Membership()
|
||||
// we only care about join events
|
||||
if err != nil {
|
||||
log.WithError(err).Errorf("unable to parse membership event")
|
||||
return true
|
||||
}
|
||||
if curMembership != gomatrixserverlib.Join {
|
||||
log.Debugf("XXX: ignoring event, current membership is %s", curMembership)
|
||||
return true
|
||||
}
|
||||
prevMembership := gjson.GetBytes(ev.Unsigned(), "prev_content.membership").Str
|
||||
|
||||
switch prevMembership {
|
||||
case gomatrixserverlib.Invite:
|
||||
fallthrough
|
||||
case gomatrixserverlib.Knock:
|
||||
fallthrough
|
||||
case gomatrixserverlib.Leave:
|
||||
fallthrough
|
||||
case "": // there's no prev membership, inform remote
|
||||
devMessages, err := s.db.DeviceKeysForUser(ctx, *ev.StateKey(), []string{}, true)
|
||||
if err != nil {
|
||||
log.WithError(err).Errorf("unable to get device keys for user")
|
||||
return false // we want to retry this
|
||||
}
|
||||
log.Debugf("XXX: producing key changes")
|
||||
|
||||
return s.producer.ProduceKeyChanges(devMessages) == nil
|
||||
default:
|
||||
log.Debugf("ignoring unexpected prev membership: '%s'", prevMembership)
|
||||
return true
|
||||
}
|
||||
default: // ignore all other events
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
|
@ -188,6 +188,9 @@ func (w *worker) _next() {
|
|||
// Look up what the next event is that's waiting to be processed.
|
||||
ctx, cancel := context.WithTimeout(w.r.ProcessContext.Context(), time.Minute)
|
||||
defer cancel()
|
||||
if scope := sentry.CurrentHub().Scope(); scope != nil {
|
||||
scope.SetTag("room_id", w.roomID)
|
||||
}
|
||||
msgs, err := w.subscription.Fetch(1, nats.Context(ctx))
|
||||
switch err {
|
||||
case nil:
|
||||
|
|
@ -239,6 +242,9 @@ func (w *worker) _next() {
|
|||
return
|
||||
}
|
||||
|
||||
if scope := sentry.CurrentHub().Scope(); scope != nil {
|
||||
scope.SetTag("event_id", inputRoomEvent.Event.EventID())
|
||||
}
|
||||
roomserverInputBackpressure.With(prometheus.Labels{"room_id": w.roomID}).Inc()
|
||||
defer roomserverInputBackpressure.With(prometheus.Labels{"room_id": w.roomID}).Dec()
|
||||
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/getsentry/sentry-go"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
"github.com/matrix-org/util"
|
||||
"github.com/opentracing/opentracing-go"
|
||||
|
|
@ -178,6 +179,10 @@ func (u *latestEventsUpdater) doUpdateLatestEvents() error {
|
|||
u.newStateNID = u.oldStateNID
|
||||
}
|
||||
|
||||
if err = u.updater.SetLatestEvents(u.roomInfo.RoomNID, u.latest, u.stateAtEvent.EventNID, u.newStateNID); err != nil {
|
||||
return fmt.Errorf("u.updater.SetLatestEvents: %w", err)
|
||||
}
|
||||
|
||||
update, err := u.makeOutputNewRoomEvent()
|
||||
if err != nil {
|
||||
return fmt.Errorf("u.makeOutputNewRoomEvent: %w", err)
|
||||
|
|
@ -196,10 +201,6 @@ func (u *latestEventsUpdater) doUpdateLatestEvents() error {
|
|||
return fmt.Errorf("u.api.WriteOutputEvents: %w", err)
|
||||
}
|
||||
|
||||
if err = u.updater.SetLatestEvents(u.roomInfo.RoomNID, u.latest, u.stateAtEvent.EventNID, u.newStateNID); err != nil {
|
||||
return fmt.Errorf("u.updater.SetLatestEvents: %w", err)
|
||||
}
|
||||
|
||||
if err = u.updater.MarkEventAsSent(u.stateAtEvent.EventNID); err != nil {
|
||||
return fmt.Errorf("u.updater.MarkEventAsSent: %w", err)
|
||||
}
|
||||
|
|
@ -275,7 +276,7 @@ func (u *latestEventsUpdater) latestState() error {
|
|||
return fmt.Errorf("roomState.DifferenceBetweenStateSnapshots: %w", err)
|
||||
}
|
||||
|
||||
if removed := len(u.removed) - len(u.added); removed > 0 {
|
||||
if removed := len(u.removed) - len(u.added); !u.rewritesState && removed > 0 {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"event_id": u.event.EventID(),
|
||||
"room_id": u.event.RoomID(),
|
||||
|
|
@ -283,7 +284,16 @@ func (u *latestEventsUpdater) latestState() error {
|
|||
"new_state_nid": u.newStateNID,
|
||||
"old_latest": u.oldLatest.EventIDs(),
|
||||
"new_latest": u.latest.EventIDs(),
|
||||
}).Errorf("Unexpected state deletion (removing %d events)", removed)
|
||||
}).Warnf("State reset detected (removing %d events)", removed)
|
||||
sentry.WithScope(func(scope *sentry.Scope) {
|
||||
scope.SetLevel("warning")
|
||||
scope.SetTag("event_id", u.event.EventID())
|
||||
scope.SetTag("old_state_nid", fmt.Sprintf("%d", u.oldStateNID))
|
||||
scope.SetTag("new_state_nid", fmt.Sprintf("%d", u.newStateNID))
|
||||
scope.SetTag("old_latest", u.oldLatest.EventIDs())
|
||||
scope.SetTag("new_latest", u.latest.EventIDs())
|
||||
sentry.CaptureMessage("State reset detected")
|
||||
})
|
||||
}
|
||||
|
||||
// Also work out the state before the event removes and the event
|
||||
|
|
|
|||
|
|
@ -25,22 +25,24 @@ import (
|
|||
_ "net/http/pprof"
|
||||
"os"
|
||||
"os/signal"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/getsentry/sentry-go"
|
||||
sentryhttp "github.com/getsentry/sentry-go/http"
|
||||
"github.com/matrix-org/dendrite/internal/caching"
|
||||
"github.com/matrix-org/dendrite/internal/fulltext"
|
||||
"github.com/matrix-org/dendrite/internal/httputil"
|
||||
"github.com/matrix-org/dendrite/internal/pushgateway"
|
||||
"github.com/matrix-org/dendrite/internal/sqlutil"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
"go.uber.org/atomic"
|
||||
"golang.org/x/net/http2"
|
||||
"golang.org/x/net/http2/h2c"
|
||||
|
||||
"github.com/matrix-org/dendrite/internal/caching"
|
||||
"github.com/matrix-org/dendrite/internal/fulltext"
|
||||
"github.com/matrix-org/dendrite/internal/httputil"
|
||||
"github.com/matrix-org/dendrite/internal/pushgateway"
|
||||
"github.com/matrix-org/dendrite/internal/sqlutil"
|
||||
|
||||
"github.com/matrix-org/dendrite/internal"
|
||||
"github.com/matrix-org/dendrite/setup/jetstream"
|
||||
"github.com/matrix-org/dendrite/setup/process"
|
||||
|
|
@ -48,6 +50,8 @@ import (
|
|||
"github.com/gorilla/mux"
|
||||
"github.com/kardianos/minwinsvc"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
appserviceAPI "github.com/matrix-org/dendrite/appservice/api"
|
||||
asinthttp "github.com/matrix-org/dendrite/appservice/inthttp"
|
||||
federationAPI "github.com/matrix-org/dendrite/federationapi/api"
|
||||
|
|
@ -59,7 +63,6 @@ import (
|
|||
"github.com/matrix-org/dendrite/setup/config"
|
||||
userapi "github.com/matrix-org/dendrite/userapi/api"
|
||||
userapiinthttp "github.com/matrix-org/dendrite/userapi/inthttp"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// BaseDendrite is a base for creating new instances of dendrite. It parses
|
||||
|
|
@ -88,6 +91,7 @@ type BaseDendrite struct {
|
|||
Database *sql.DB
|
||||
DatabaseWriter sqlutil.Writer
|
||||
EnableMetrics bool
|
||||
startupLock sync.Mutex
|
||||
Fulltext *fulltext.Search
|
||||
}
|
||||
|
||||
|
|
@ -406,6 +410,9 @@ func (b *BaseDendrite) SetupAndServeHTTP(
|
|||
internalHTTPAddr, externalHTTPAddr config.HTTPAddress,
|
||||
certFile, keyFile *string,
|
||||
) {
|
||||
// Manually unlocked right before actually serving requests,
|
||||
// as we don't return from this method (defer doesn't work).
|
||||
b.startupLock.Lock()
|
||||
internalAddr, _ := internalHTTPAddr.Address()
|
||||
externalAddr, _ := externalHTTPAddr.Address()
|
||||
|
||||
|
|
@ -484,6 +491,7 @@ func (b *BaseDendrite) SetupAndServeHTTP(
|
|||
externalRouter.PathPrefix(httputil.PublicMediaPathPrefix).Handler(b.PublicMediaAPIMux)
|
||||
externalRouter.PathPrefix(httputil.PublicWellKnownPrefix).Handler(b.PublicWellKnownAPIMux)
|
||||
|
||||
b.startupLock.Unlock()
|
||||
if internalAddr != NoListener && internalAddr != externalAddr {
|
||||
go func() {
|
||||
var internalShutdown atomic.Bool // RegisterOnShutdown can be called more than once
|
||||
|
|
|
|||
|
|
@ -211,7 +211,10 @@ func loadConfig(
|
|||
monolithic bool,
|
||||
) (*Dendrite, error) {
|
||||
var c Dendrite
|
||||
c.Defaults(false)
|
||||
c.Defaults(DefaultOpts{
|
||||
Generate: false,
|
||||
Monolithic: monolithic,
|
||||
})
|
||||
c.IsMonolith = monolithic
|
||||
|
||||
var err error
|
||||
|
|
@ -224,12 +227,7 @@ func loadConfig(
|
|||
}
|
||||
|
||||
privateKeyPath := absPath(basePath, c.Global.PrivateKeyPath)
|
||||
privateKeyData, err := readFile(privateKeyPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if c.Global.KeyID, c.Global.PrivateKey, err = readKeyPEM(privateKeyPath, privateKeyData, true); err != nil {
|
||||
if c.Global.KeyID, c.Global.PrivateKey, err = LoadMatrixKey(privateKeyPath, readFile); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
|
@ -265,6 +263,14 @@ func loadConfig(
|
|||
return &c, nil
|
||||
}
|
||||
|
||||
func LoadMatrixKey(privateKeyPath string, readFile func(string) ([]byte, error)) (gomatrixserverlib.KeyID, ed25519.PrivateKey, error) {
|
||||
privateKeyData, err := readFile(privateKeyPath)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
return readKeyPEM(privateKeyPath, privateKeyData, true)
|
||||
}
|
||||
|
||||
// Derive generates data that is derived from various values provided in
|
||||
// the config file.
|
||||
func (config *Dendrite) Derive() error {
|
||||
|
|
@ -292,21 +298,25 @@ func (config *Dendrite) Derive() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
type DefaultOpts struct {
|
||||
Generate bool
|
||||
Monolithic bool
|
||||
}
|
||||
|
||||
// SetDefaults sets default config values if they are not explicitly set.
|
||||
func (c *Dendrite) Defaults(generate bool) {
|
||||
func (c *Dendrite) Defaults(opts DefaultOpts) {
|
||||
c.Version = Version
|
||||
|
||||
c.Global.Defaults(generate)
|
||||
c.ClientAPI.Defaults(generate)
|
||||
c.FederationAPI.Defaults(generate)
|
||||
c.KeyServer.Defaults(generate)
|
||||
c.MediaAPI.Defaults(generate)
|
||||
c.RoomServer.Defaults(generate)
|
||||
c.SyncAPI.Defaults(generate)
|
||||
c.UserAPI.Defaults(generate)
|
||||
c.AppServiceAPI.Defaults(generate)
|
||||
c.MSCs.Defaults(generate)
|
||||
|
||||
c.Global.Defaults(opts)
|
||||
c.ClientAPI.Defaults(opts)
|
||||
c.FederationAPI.Defaults(opts)
|
||||
c.KeyServer.Defaults(opts)
|
||||
c.MediaAPI.Defaults(opts)
|
||||
c.RoomServer.Defaults(opts)
|
||||
c.SyncAPI.Defaults(opts)
|
||||
c.UserAPI.Defaults(opts)
|
||||
c.AppServiceAPI.Defaults(opts)
|
||||
c.MSCs.Defaults(opts)
|
||||
c.Wiring()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -29,9 +29,7 @@ type AppServiceAPI struct {
|
|||
Matrix *Global `yaml:"-"`
|
||||
Derived *Derived `yaml:"-"` // TODO: Nuke Derived from orbit
|
||||
|
||||
InternalAPI InternalAPIOptions `yaml:"internal_api"`
|
||||
|
||||
Database DatabaseOptions `yaml:"database"`
|
||||
InternalAPI InternalAPIOptions `yaml:"internal_api,omitempty"`
|
||||
|
||||
// DisableTLSValidation disables the validation of X.509 TLS certs
|
||||
// on appservice endpoints. This is not recommended in production!
|
||||
|
|
@ -40,19 +38,14 @@ type AppServiceAPI struct {
|
|||
ConfigFiles []string `yaml:"config_files"`
|
||||
}
|
||||
|
||||
func (c *AppServiceAPI) Defaults(generate bool) {
|
||||
c.InternalAPI.Listen = "http://localhost:7777"
|
||||
c.InternalAPI.Connect = "http://localhost:7777"
|
||||
c.Database.Defaults(5)
|
||||
if generate {
|
||||
c.Database.ConnectionString = "file:appservice.db"
|
||||
func (c *AppServiceAPI) Defaults(opts DefaultOpts) {
|
||||
if !opts.Monolithic {
|
||||
c.InternalAPI.Listen = "http://localhost:7777"
|
||||
c.InternalAPI.Connect = "http://localhost:7777"
|
||||
}
|
||||
}
|
||||
|
||||
func (c *AppServiceAPI) Verify(configErrs *ConfigErrors, isMonolith bool) {
|
||||
if c.Matrix.DatabaseOptions.ConnectionString == "" {
|
||||
checkNotEmpty(configErrs, "app_service_api.database.connection_string", string(c.Database.ConnectionString))
|
||||
}
|
||||
if isMonolith { // polylith required configs below
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,8 +9,8 @@ type ClientAPI struct {
|
|||
Matrix *Global `yaml:"-"`
|
||||
Derived *Derived `yaml:"-"` // TODO: Nuke Derived from orbit
|
||||
|
||||
InternalAPI InternalAPIOptions `yaml:"internal_api"`
|
||||
ExternalAPI ExternalAPIOptions `yaml:"external_api"`
|
||||
InternalAPI InternalAPIOptions `yaml:"internal_api,omitempty"`
|
||||
ExternalAPI ExternalAPIOptions `yaml:"external_api,omitempty"`
|
||||
|
||||
// If set disables new users from registering (except via shared
|
||||
// secrets)
|
||||
|
|
@ -48,13 +48,15 @@ type ClientAPI struct {
|
|||
// Rate-limiting options
|
||||
RateLimiting RateLimiting `yaml:"rate_limiting"`
|
||||
|
||||
MSCs *MSCs `yaml:"mscs"`
|
||||
MSCs *MSCs `yaml:"-"`
|
||||
}
|
||||
|
||||
func (c *ClientAPI) Defaults(generate bool) {
|
||||
c.InternalAPI.Listen = "http://localhost:7771"
|
||||
c.InternalAPI.Connect = "http://localhost:7771"
|
||||
c.ExternalAPI.Listen = "http://[::]:8071"
|
||||
func (c *ClientAPI) Defaults(opts DefaultOpts) {
|
||||
if !opts.Monolithic {
|
||||
c.InternalAPI.Listen = "http://localhost:7771"
|
||||
c.InternalAPI.Connect = "http://localhost:7771"
|
||||
c.ExternalAPI.Listen = "http://[::]:8071"
|
||||
}
|
||||
c.RegistrationSharedSecret = ""
|
||||
c.RecaptchaPublicKey = ""
|
||||
c.RecaptchaPrivateKey = ""
|
||||
|
|
|
|||
|
|
@ -5,12 +5,12 @@ import "github.com/matrix-org/gomatrixserverlib"
|
|||
type FederationAPI struct {
|
||||
Matrix *Global `yaml:"-"`
|
||||
|
||||
InternalAPI InternalAPIOptions `yaml:"internal_api"`
|
||||
ExternalAPI ExternalAPIOptions `yaml:"external_api"`
|
||||
InternalAPI InternalAPIOptions `yaml:"internal_api,omitempty"`
|
||||
ExternalAPI ExternalAPIOptions `yaml:"external_api,omitempty"`
|
||||
|
||||
// The database stores information used by the federation destination queues to
|
||||
// send transactions to remote servers.
|
||||
Database DatabaseOptions `yaml:"database"`
|
||||
Database DatabaseOptions `yaml:"database,omitempty"`
|
||||
|
||||
// Federation failure threshold. How many consecutive failures that we should
|
||||
// tolerate when sending federation requests to a specific server. The backoff
|
||||
|
|
@ -30,25 +30,44 @@ type FederationAPI struct {
|
|||
PreferDirectFetch bool `yaml:"prefer_direct_fetch"`
|
||||
}
|
||||
|
||||
func (c *FederationAPI) Defaults(generate bool) {
|
||||
c.InternalAPI.Listen = "http://localhost:7772"
|
||||
c.InternalAPI.Connect = "http://localhost:7772"
|
||||
c.ExternalAPI.Listen = "http://[::]:8072"
|
||||
func (c *FederationAPI) Defaults(opts DefaultOpts) {
|
||||
if !opts.Monolithic {
|
||||
c.InternalAPI.Listen = "http://localhost:7772"
|
||||
c.InternalAPI.Connect = "http://localhost:7772"
|
||||
c.ExternalAPI.Listen = "http://[::]:8072"
|
||||
c.Database.Defaults(10)
|
||||
}
|
||||
c.FederationMaxRetries = 16
|
||||
c.DisableTLSValidation = false
|
||||
c.Database.Defaults(10)
|
||||
if generate {
|
||||
c.Database.ConnectionString = "file:federationapi.db"
|
||||
if opts.Generate {
|
||||
c.KeyPerspectives = KeyPerspectives{
|
||||
{
|
||||
ServerName: "matrix.org",
|
||||
Keys: []KeyPerspectiveTrustKey{
|
||||
{
|
||||
KeyID: "ed25519:auto",
|
||||
PublicKey: "Noi6WqcDj0QmPxCNQqgezwTlBKrfqehY1u2FyWP9uYw",
|
||||
},
|
||||
{
|
||||
KeyID: "ed25519:a_RXGa",
|
||||
PublicKey: "l8Hft5qXKn1vfHrg3p4+W8gELQVo8N13JkluMfmn2sQ",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
if !opts.Monolithic {
|
||||
c.Database.ConnectionString = "file:federationapi.db"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *FederationAPI) Verify(configErrs *ConfigErrors, isMonolith bool) {
|
||||
if c.Matrix.DatabaseOptions.ConnectionString == "" {
|
||||
checkNotEmpty(configErrs, "federation_api.database.connection_string", string(c.Database.ConnectionString))
|
||||
}
|
||||
if isMonolith { // polylith required configs below
|
||||
return
|
||||
}
|
||||
if c.Matrix.DatabaseOptions.ConnectionString == "" {
|
||||
checkNotEmpty(configErrs, "federation_api.database.connection_string", string(c.Database.ConnectionString))
|
||||
}
|
||||
checkURL(configErrs, "federation_api.external_api.listen", string(c.ExternalAPI.Listen))
|
||||
checkURL(configErrs, "federation_api.internal_api.listen", string(c.InternalAPI.Listen))
|
||||
checkURL(configErrs, "federation_api.internal_api.connect", string(c.InternalAPI.Connect))
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ type Global struct {
|
|||
// connections will be used instead. This way we don't have to manage connection
|
||||
// counts on a per-component basis, but can instead do it for the entire monolith.
|
||||
// In a polylith deployment, this will be ignored.
|
||||
DatabaseOptions DatabaseOptions `yaml:"database"`
|
||||
DatabaseOptions DatabaseOptions `yaml:"database,omitempty"`
|
||||
|
||||
// The server name to delegate server-server communications to, with optional port
|
||||
WellKnownServerName string `yaml:"well_known_server_name"`
|
||||
|
|
@ -83,22 +83,28 @@ type Global struct {
|
|||
Cache Cache `yaml:"cache"`
|
||||
}
|
||||
|
||||
func (c *Global) Defaults(generate bool) {
|
||||
if generate {
|
||||
func (c *Global) Defaults(opts DefaultOpts) {
|
||||
if opts.Generate {
|
||||
c.ServerName = "localhost"
|
||||
c.PrivateKeyPath = "matrix_key.pem"
|
||||
_, c.PrivateKey, _ = ed25519.GenerateKey(rand.New(rand.NewSource(0)))
|
||||
c.KeyID = "ed25519:auto"
|
||||
c.TrustedIDServers = []string{
|
||||
"matrix.org",
|
||||
"vector.im",
|
||||
}
|
||||
}
|
||||
c.KeyValidityPeriod = time.Hour * 24 * 7
|
||||
|
||||
c.JetStream.Defaults(generate)
|
||||
c.Metrics.Defaults(generate)
|
||||
if opts.Monolithic {
|
||||
c.DatabaseOptions.Defaults(90)
|
||||
}
|
||||
c.JetStream.Defaults(opts)
|
||||
c.Metrics.Defaults(opts)
|
||||
c.DNSCache.Defaults()
|
||||
c.Sentry.Defaults()
|
||||
c.ServerNotices.Defaults(generate)
|
||||
c.ServerNotices.Defaults(opts)
|
||||
c.ReportStats.Defaults()
|
||||
c.Cache.Defaults(generate)
|
||||
c.Cache.Defaults()
|
||||
}
|
||||
|
||||
func (c *Global) Verify(configErrs *ConfigErrors, isMonolith bool) {
|
||||
|
|
@ -142,9 +148,9 @@ type Metrics struct {
|
|||
} `yaml:"basic_auth"`
|
||||
}
|
||||
|
||||
func (c *Metrics) Defaults(generate bool) {
|
||||
func (c *Metrics) Defaults(opts DefaultOpts) {
|
||||
c.Enabled = false
|
||||
if generate {
|
||||
if opts.Generate {
|
||||
c.BasicAuth.Username = "metrics"
|
||||
c.BasicAuth.Password = "metrics"
|
||||
}
|
||||
|
|
@ -166,8 +172,8 @@ type ServerNotices struct {
|
|||
RoomName string `yaml:"room_name"`
|
||||
}
|
||||
|
||||
func (c *ServerNotices) Defaults(generate bool) {
|
||||
if generate {
|
||||
func (c *ServerNotices) Defaults(opts DefaultOpts) {
|
||||
if opts.Generate {
|
||||
c.Enabled = true
|
||||
c.LocalPart = "_server"
|
||||
c.DisplayName = "Server Alert"
|
||||
|
|
@ -183,7 +189,7 @@ type Cache struct {
|
|||
MaxAge time.Duration `yaml:"max_age"`
|
||||
}
|
||||
|
||||
func (c *Cache) Defaults(generate bool) {
|
||||
func (c *Cache) Defaults() {
|
||||
c.EstimatedMaxSize = 1024 * 1024 * 1024 // 1GB
|
||||
c.MaxAge = time.Hour
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,10 +31,10 @@ func (c *JetStream) Durable(name string) string {
|
|||
return c.Prefixed(name)
|
||||
}
|
||||
|
||||
func (c *JetStream) Defaults(generate bool) {
|
||||
func (c *JetStream) Defaults(opts DefaultOpts) {
|
||||
c.Addresses = []string{}
|
||||
c.TopicPrefix = "Dendrite"
|
||||
if generate {
|
||||
if opts.Generate {
|
||||
c.StoragePath = Path("./")
|
||||
c.NoLog = true
|
||||
c.DisableTLSValidation = true
|
||||
|
|
|
|||
|
|
@ -3,27 +3,31 @@ package config
|
|||
type KeyServer struct {
|
||||
Matrix *Global `yaml:"-"`
|
||||
|
||||
InternalAPI InternalAPIOptions `yaml:"internal_api"`
|
||||
InternalAPI InternalAPIOptions `yaml:"internal_api,omitempty"`
|
||||
|
||||
Database DatabaseOptions `yaml:"database"`
|
||||
Database DatabaseOptions `yaml:"database,omitempty"`
|
||||
}
|
||||
|
||||
func (c *KeyServer) Defaults(generate bool) {
|
||||
c.InternalAPI.Listen = "http://localhost:7779"
|
||||
c.InternalAPI.Connect = "http://localhost:7779"
|
||||
c.Database.Defaults(10)
|
||||
if generate {
|
||||
c.Database.ConnectionString = "file:keyserver.db"
|
||||
func (c *KeyServer) Defaults(opts DefaultOpts) {
|
||||
if !opts.Monolithic {
|
||||
c.InternalAPI.Listen = "http://localhost:7779"
|
||||
c.InternalAPI.Connect = "http://localhost:7779"
|
||||
c.Database.Defaults(10)
|
||||
}
|
||||
if opts.Generate {
|
||||
if !opts.Monolithic {
|
||||
c.Database.ConnectionString = "file:keyserver.db"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *KeyServer) Verify(configErrs *ConfigErrors, isMonolith bool) {
|
||||
if c.Matrix.DatabaseOptions.ConnectionString == "" {
|
||||
checkNotEmpty(configErrs, "key_server.database.connection_string", string(c.Database.ConnectionString))
|
||||
}
|
||||
if isMonolith { // polylith required configs below
|
||||
return
|
||||
}
|
||||
if c.Matrix.DatabaseOptions.ConnectionString == "" {
|
||||
checkNotEmpty(configErrs, "key_server.database.connection_string", string(c.Database.ConnectionString))
|
||||
}
|
||||
checkURL(configErrs, "key_server.internal_api.listen", string(c.InternalAPI.Listen))
|
||||
checkURL(configErrs, "key_server.internal_api.connect", string(c.InternalAPI.Connect))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,12 +7,12 @@ import (
|
|||
type MediaAPI struct {
|
||||
Matrix *Global `yaml:"-"`
|
||||
|
||||
InternalAPI InternalAPIOptions `yaml:"internal_api"`
|
||||
ExternalAPI ExternalAPIOptions `yaml:"external_api"`
|
||||
InternalAPI InternalAPIOptions `yaml:"internal_api,omitempty"`
|
||||
ExternalAPI ExternalAPIOptions `yaml:"external_api,omitempty"`
|
||||
|
||||
// The MediaAPI database stores information about files uploaded and downloaded
|
||||
// by local users. It is only accessed by the MediaAPI.
|
||||
Database DatabaseOptions `yaml:"database"`
|
||||
Database DatabaseOptions `yaml:"database,omitempty"`
|
||||
|
||||
// The base path to where the media files will be stored. May be relative or absolute.
|
||||
BasePath Path `yaml:"base_path"`
|
||||
|
|
@ -38,23 +38,41 @@ type MediaAPI struct {
|
|||
// DefaultMaxFileSizeBytes defines the default file size allowed in transfers
|
||||
var DefaultMaxFileSizeBytes = FileSizeBytes(10485760)
|
||||
|
||||
func (c *MediaAPI) Defaults(generate bool) {
|
||||
c.InternalAPI.Listen = "http://localhost:7774"
|
||||
c.InternalAPI.Connect = "http://localhost:7774"
|
||||
c.ExternalAPI.Listen = "http://[::]:8074"
|
||||
func (c *MediaAPI) Defaults(opts DefaultOpts) {
|
||||
if !opts.Monolithic {
|
||||
c.InternalAPI.Listen = "http://localhost:7774"
|
||||
c.InternalAPI.Connect = "http://localhost:7774"
|
||||
c.ExternalAPI.Listen = "http://[::]:8074"
|
||||
c.Database.Defaults(5)
|
||||
}
|
||||
c.MaxFileSizeBytes = DefaultMaxFileSizeBytes
|
||||
c.MaxThumbnailGenerators = 10
|
||||
c.Database.Defaults(5)
|
||||
if generate {
|
||||
c.Database.ConnectionString = "file:mediaapi.db"
|
||||
if opts.Generate {
|
||||
c.ThumbnailSizes = []ThumbnailSize{
|
||||
{
|
||||
Width: 32,
|
||||
Height: 32,
|
||||
ResizeMethod: "crop",
|
||||
},
|
||||
{
|
||||
Width: 96,
|
||||
Height: 96,
|
||||
ResizeMethod: "crop",
|
||||
},
|
||||
{
|
||||
Width: 640,
|
||||
Height: 480,
|
||||
ResizeMethod: "scale",
|
||||
},
|
||||
}
|
||||
if !opts.Monolithic {
|
||||
c.Database.ConnectionString = "file:mediaapi.db"
|
||||
}
|
||||
c.BasePath = "./media_store"
|
||||
}
|
||||
}
|
||||
|
||||
func (c *MediaAPI) Verify(configErrs *ConfigErrors, isMonolith bool) {
|
||||
if c.Matrix.DatabaseOptions.ConnectionString == "" {
|
||||
checkNotEmpty(configErrs, "media_api.database.connection_string", string(c.Database.ConnectionString))
|
||||
}
|
||||
checkNotEmpty(configErrs, "media_api.base_path", string(c.BasePath))
|
||||
checkPositive(configErrs, "media_api.max_file_size_bytes", int64(c.MaxFileSizeBytes))
|
||||
checkPositive(configErrs, "media_api.max_thumbnail_generators", int64(c.MaxThumbnailGenerators))
|
||||
|
|
@ -66,6 +84,9 @@ func (c *MediaAPI) Verify(configErrs *ConfigErrors, isMonolith bool) {
|
|||
if isMonolith { // polylith required configs below
|
||||
return
|
||||
}
|
||||
if c.Matrix.DatabaseOptions.ConnectionString == "" {
|
||||
checkNotEmpty(configErrs, "media_api.database.connection_string", string(c.Database.ConnectionString))
|
||||
}
|
||||
checkURL(configErrs, "media_api.internal_api.listen", string(c.InternalAPI.Listen))
|
||||
checkURL(configErrs, "media_api.internal_api.connect", string(c.InternalAPI.Connect))
|
||||
checkURL(configErrs, "media_api.external_api.listen", string(c.ExternalAPI.Listen))
|
||||
|
|
|
|||
|
|
@ -10,13 +10,17 @@ type MSCs struct {
|
|||
// 'msc2946': Spaces Summary - https://github.com/matrix-org/matrix-doc/pull/2946
|
||||
MSCs []string `yaml:"mscs"`
|
||||
|
||||
Database DatabaseOptions `yaml:"database"`
|
||||
Database DatabaseOptions `yaml:"database,omitempty"`
|
||||
}
|
||||
|
||||
func (c *MSCs) Defaults(generate bool) {
|
||||
c.Database.Defaults(5)
|
||||
if generate {
|
||||
c.Database.ConnectionString = "file:mscs.db"
|
||||
func (c *MSCs) Defaults(opts DefaultOpts) {
|
||||
if !opts.Monolithic {
|
||||
c.Database.Defaults(5)
|
||||
}
|
||||
if opts.Generate {
|
||||
if !opts.Monolithic {
|
||||
c.Database.ConnectionString = "file:mscs.db"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -31,6 +35,9 @@ func (c *MSCs) Enabled(msc string) bool {
|
|||
}
|
||||
|
||||
func (c *MSCs) Verify(configErrs *ConfigErrors, isMonolith bool) {
|
||||
if isMonolith { // polylith required configs below
|
||||
return
|
||||
}
|
||||
if c.Matrix.DatabaseOptions.ConnectionString == "" {
|
||||
checkNotEmpty(configErrs, "mscs.database.connection_string", string(c.Database.ConnectionString))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,27 +3,31 @@ package config
|
|||
type RoomServer struct {
|
||||
Matrix *Global `yaml:"-"`
|
||||
|
||||
InternalAPI InternalAPIOptions `yaml:"internal_api"`
|
||||
InternalAPI InternalAPIOptions `yaml:"internal_api,omitempty"`
|
||||
|
||||
Database DatabaseOptions `yaml:"database"`
|
||||
Database DatabaseOptions `yaml:"database,omitempty"`
|
||||
}
|
||||
|
||||
func (c *RoomServer) Defaults(generate bool) {
|
||||
c.InternalAPI.Listen = "http://localhost:7770"
|
||||
c.InternalAPI.Connect = "http://localhost:7770"
|
||||
c.Database.Defaults(10)
|
||||
if generate {
|
||||
c.Database.ConnectionString = "file:roomserver.db"
|
||||
func (c *RoomServer) Defaults(opts DefaultOpts) {
|
||||
if !opts.Monolithic {
|
||||
c.InternalAPI.Listen = "http://localhost:7770"
|
||||
c.InternalAPI.Connect = "http://localhost:7770"
|
||||
c.Database.Defaults(20)
|
||||
}
|
||||
if opts.Generate {
|
||||
if !opts.Monolithic {
|
||||
c.Database.ConnectionString = "file:roomserver.db"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *RoomServer) Verify(configErrs *ConfigErrors, isMonolith bool) {
|
||||
if c.Matrix.DatabaseOptions.ConnectionString == "" {
|
||||
checkNotEmpty(configErrs, "room_server.database.connection_string", string(c.Database.ConnectionString))
|
||||
}
|
||||
if isMonolith { // polylith required configs below
|
||||
return
|
||||
}
|
||||
if c.Matrix.DatabaseOptions.ConnectionString == "" {
|
||||
checkNotEmpty(configErrs, "room_server.database.connection_string", string(c.Database.ConnectionString))
|
||||
}
|
||||
checkURL(configErrs, "room_server.internal_api.listen", string(c.InternalAPI.Listen))
|
||||
checkURL(configErrs, "room_server.internal_ap.connect", string(c.InternalAPI.Connect))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,35 +3,38 @@ package config
|
|||
type SyncAPI struct {
|
||||
Matrix *Global `yaml:"-"`
|
||||
|
||||
InternalAPI InternalAPIOptions `yaml:"internal_api"`
|
||||
ExternalAPI ExternalAPIOptions `yaml:"external_api"`
|
||||
InternalAPI InternalAPIOptions `yaml:"internal_api,omitempty"`
|
||||
ExternalAPI ExternalAPIOptions `yaml:"external_api,omitempty"`
|
||||
|
||||
Database DatabaseOptions `yaml:"database"`
|
||||
Database DatabaseOptions `yaml:"database,omitempty"`
|
||||
|
||||
RealIPHeader string `yaml:"real_ip_header"`
|
||||
|
||||
Fulltext Fulltext `yaml:"fulltext"`
|
||||
}
|
||||
|
||||
func (c *SyncAPI) Defaults(generate bool) {
|
||||
c.InternalAPI.Listen = "http://localhost:7773"
|
||||
c.InternalAPI.Connect = "http://localhost:7773"
|
||||
c.ExternalAPI.Listen = "http://localhost:8073"
|
||||
c.Database.Defaults(10)
|
||||
c.Fulltext.Defaults(generate)
|
||||
if generate {
|
||||
c.Database.ConnectionString = "file:syncapi.db"
|
||||
func (c *SyncAPI) Defaults(opts DefaultOpts) {
|
||||
if !opts.Monolithic {
|
||||
c.InternalAPI.Listen = "http://localhost:7773"
|
||||
c.InternalAPI.Connect = "http://localhost:7773"
|
||||
c.ExternalAPI.Listen = "http://localhost:8073"
|
||||
c.Database.Defaults(20)
|
||||
}
|
||||
c.Fulltext.Defaults(opts)
|
||||
if opts.Generate {
|
||||
if !opts.Monolithic {
|
||||
c.Database.ConnectionString = "file:syncapi.db"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *SyncAPI) Verify(configErrs *ConfigErrors, isMonolith bool) {
|
||||
c.Fulltext.Verify(configErrs, isMonolith)
|
||||
if c.Matrix.DatabaseOptions.ConnectionString == "" {
|
||||
checkNotEmpty(configErrs, "sync_api.database", string(c.Database.ConnectionString))
|
||||
}
|
||||
if isMonolith { // polylith required configs below
|
||||
return
|
||||
}
|
||||
if c.Matrix.DatabaseOptions.ConnectionString == "" {
|
||||
checkNotEmpty(configErrs, "sync_api.database", string(c.Database.ConnectionString))
|
||||
}
|
||||
checkURL(configErrs, "sync_api.internal_api.listen", string(c.InternalAPI.Listen))
|
||||
checkURL(configErrs, "sync_api.internal_api.connect", string(c.InternalAPI.Connect))
|
||||
checkURL(configErrs, "sync_api.external_api.listen", string(c.ExternalAPI.Listen))
|
||||
|
|
@ -44,11 +47,11 @@ type Fulltext struct {
|
|||
Language string `yaml:"language"` // the language to use when analysing content
|
||||
}
|
||||
|
||||
func (f *Fulltext) Defaults(generate bool) {
|
||||
func (f *Fulltext) Defaults(opts DefaultOpts) {
|
||||
f.Enabled = false
|
||||
f.IndexPath = "./fulltextindex"
|
||||
f.Language = "en"
|
||||
if generate {
|
||||
if opts.Generate {
|
||||
f.Enabled = true
|
||||
f.InMemory = true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import "golang.org/x/crypto/bcrypt"
|
|||
type UserAPI struct {
|
||||
Matrix *Global `yaml:"-"`
|
||||
|
||||
InternalAPI InternalAPIOptions `yaml:"internal_api"`
|
||||
InternalAPI InternalAPIOptions `yaml:"internal_api,omitempty"`
|
||||
|
||||
// The cost when hashing passwords.
|
||||
BCryptCost int `yaml:"bcrypt_cost"`
|
||||
|
|
@ -18,30 +18,34 @@ type UserAPI struct {
|
|||
|
||||
// The Account database stores the login details and account information
|
||||
// for local users. It is accessed by the UserAPI.
|
||||
AccountDatabase DatabaseOptions `yaml:"account_database"`
|
||||
AccountDatabase DatabaseOptions `yaml:"account_database,omitempty"`
|
||||
}
|
||||
|
||||
const DefaultOpenIDTokenLifetimeMS = 3600000 // 60 minutes
|
||||
|
||||
func (c *UserAPI) Defaults(generate bool) {
|
||||
c.InternalAPI.Listen = "http://localhost:7781"
|
||||
c.InternalAPI.Connect = "http://localhost:7781"
|
||||
func (c *UserAPI) Defaults(opts DefaultOpts) {
|
||||
if !opts.Monolithic {
|
||||
c.InternalAPI.Listen = "http://localhost:7781"
|
||||
c.InternalAPI.Connect = "http://localhost:7781"
|
||||
c.AccountDatabase.Defaults(10)
|
||||
}
|
||||
c.BCryptCost = bcrypt.DefaultCost
|
||||
c.OpenIDTokenLifetimeMS = DefaultOpenIDTokenLifetimeMS
|
||||
c.AccountDatabase.Defaults(10)
|
||||
if generate {
|
||||
c.AccountDatabase.ConnectionString = "file:userapi_accounts.db"
|
||||
if opts.Generate {
|
||||
if !opts.Monolithic {
|
||||
c.AccountDatabase.ConnectionString = "file:userapi_accounts.db"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *UserAPI) Verify(configErrs *ConfigErrors, isMonolith bool) {
|
||||
checkPositive(configErrs, "user_api.openid_token_lifetime_ms", c.OpenIDTokenLifetimeMS)
|
||||
if c.Matrix.DatabaseOptions.ConnectionString == "" {
|
||||
checkNotEmpty(configErrs, "user_api.account_database.connection_string", string(c.AccountDatabase.ConnectionString))
|
||||
}
|
||||
if isMonolith { // polylith required configs below
|
||||
return
|
||||
}
|
||||
if c.Matrix.DatabaseOptions.ConnectionString == "" {
|
||||
checkNotEmpty(configErrs, "user_api.account_database.connection_string", string(c.AccountDatabase.ConnectionString))
|
||||
}
|
||||
checkURL(configErrs, "user_api.internal_api.listen", string(c.InternalAPI.Listen))
|
||||
checkURL(configErrs, "user_api.internal_api.connect", string(c.InternalAPI.Connect))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,9 +9,16 @@ import (
|
|||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// JetStreamConsumer starts a durable consumer on the given subject with the
|
||||
// given durable name. The function will be called when one or more messages
|
||||
// is available, up to the maximum batch size specified. If the batch is set to
|
||||
// 1 then messages will be delivered one at a time. If the function is called,
|
||||
// the messages array is guaranteed to be at least 1 in size. Any provided NATS
|
||||
// options will be passed through to the pull subscriber creation. The consumer
|
||||
// will continue to run until the context expires, at which point it will stop.
|
||||
func JetStreamConsumer(
|
||||
ctx context.Context, js nats.JetStreamContext, subj, durable string,
|
||||
f func(ctx context.Context, msg *nats.Msg) bool,
|
||||
ctx context.Context, js nats.JetStreamContext, subj, durable string, batch int,
|
||||
f func(ctx context.Context, msgs []*nats.Msg) bool,
|
||||
opts ...nats.SubOpt,
|
||||
) error {
|
||||
defer func() {
|
||||
|
|
@ -50,7 +57,7 @@ func JetStreamConsumer(
|
|||
// enforce its own deadline (roughly 5 seconds by default). Therefore
|
||||
// it is our responsibility to check whether our context expired or
|
||||
// not when a context error is returned. Footguns. Footguns everywhere.
|
||||
msgs, err := sub.Fetch(1, nats.Context(ctx))
|
||||
msgs, err := sub.Fetch(batch, nats.Context(ctx))
|
||||
if err != nil {
|
||||
if err == context.Canceled || err == context.DeadlineExceeded {
|
||||
// Work out whether it was the JetStream context that expired
|
||||
|
|
@ -74,21 +81,26 @@ func JetStreamConsumer(
|
|||
if len(msgs) < 1 {
|
||||
continue
|
||||
}
|
||||
msg := msgs[0]
|
||||
if err = msg.InProgress(nats.Context(ctx)); err != nil {
|
||||
logrus.WithContext(ctx).WithField("subject", subj).Warn(fmt.Errorf("msg.InProgress: %w", err))
|
||||
sentry.CaptureException(err)
|
||||
continue
|
||||
}
|
||||
if f(ctx, msg) {
|
||||
if err = msg.AckSync(nats.Context(ctx)); err != nil {
|
||||
logrus.WithContext(ctx).WithField("subject", subj).Warn(fmt.Errorf("msg.AckSync: %w", err))
|
||||
for _, msg := range msgs {
|
||||
if err = msg.InProgress(nats.Context(ctx)); err != nil {
|
||||
logrus.WithContext(ctx).WithField("subject", subj).Warn(fmt.Errorf("msg.InProgress: %w", err))
|
||||
sentry.CaptureException(err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
if f(ctx, msgs) {
|
||||
for _, msg := range msgs {
|
||||
if err = msg.AckSync(nats.Context(ctx)); err != nil {
|
||||
logrus.WithContext(ctx).WithField("subject", subj).Warn(fmt.Errorf("msg.AckSync: %w", err))
|
||||
sentry.CaptureException(err)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if err = msg.Nak(nats.Context(ctx)); err != nil {
|
||||
logrus.WithContext(ctx).WithField("subject", subj).Warn(fmt.Errorf("msg.Nak: %w", err))
|
||||
sentry.CaptureException(err)
|
||||
for _, msg := range msgs {
|
||||
if err = msg.Nak(nats.Context(ctx)); err != nil {
|
||||
logrus.WithContext(ctx).WithField("subject", subj).Warn(fmt.Errorf("msg.Nak: %w", err))
|
||||
sentry.CaptureException(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -183,6 +183,7 @@ func setupNATS(process *process.ProcessContext, cfg *config.JetStream, nc *natsc
|
|||
OutputReceiptEvent: {"SyncAPIEDUServerReceiptConsumer", "FederationAPIEDUServerConsumer"},
|
||||
OutputSendToDeviceEvent: {"SyncAPIEDUServerSendToDeviceConsumer", "FederationAPIEDUServerConsumer"},
|
||||
OutputTypingEvent: {"SyncAPIEDUServerTypingConsumer", "FederationAPIEDUServerConsumer"},
|
||||
OutputRoomEvent: {"AppserviceRoomserverConsumer"},
|
||||
} {
|
||||
streamName := cfg.Matrix.JetStream.Prefixed(stream)
|
||||
for _, consumer := range consumers {
|
||||
|
|
|
|||
|
|
@ -547,7 +547,10 @@ func (r *testRoomserverAPI) QueryMembershipForUser(ctx context.Context, req *roo
|
|||
func injectEvents(t *testing.T, userAPI userapi.UserInternalAPI, rsAPI roomserver.RoomserverInternalAPI, events []*gomatrixserverlib.HeaderedEvent) *mux.Router {
|
||||
t.Helper()
|
||||
cfg := &config.Dendrite{}
|
||||
cfg.Defaults(true)
|
||||
cfg.Defaults(config.DefaultOpts{
|
||||
Generate: true,
|
||||
Monolithic: true,
|
||||
})
|
||||
cfg.Global.ServerName = "localhost"
|
||||
cfg.MSCs.Database.ConnectionString = "file:msc2836_test.db"
|
||||
cfg.MSCs.MSCs = []string{"msc2836"}
|
||||
|
|
|
|||
|
|
@ -152,15 +152,16 @@ func (s *OutputClientDataConsumer) Start() error {
|
|||
return err
|
||||
}
|
||||
return jetstream.JetStreamConsumer(
|
||||
s.ctx, s.jetstream, s.topic, s.durable, s.onMessage,
|
||||
nats.DeliverAll(), nats.ManualAck(),
|
||||
s.ctx, s.jetstream, s.topic, s.durable, 1,
|
||||
s.onMessage, nats.DeliverAll(), nats.ManualAck(),
|
||||
)
|
||||
}
|
||||
|
||||
// onMessage is called when the sync server receives a new event from the client API server output log.
|
||||
// It is not safe for this function to be called from multiple goroutines, or else the
|
||||
// sync stream position may race and be incorrectly calculated.
|
||||
func (s *OutputClientDataConsumer) onMessage(ctx context.Context, msg *nats.Msg) bool {
|
||||
func (s *OutputClientDataConsumer) onMessage(ctx context.Context, msgs []*nats.Msg) bool {
|
||||
msg := msgs[0] // Guaranteed to exist if onMessage is called
|
||||
// Parse out the event JSON
|
||||
userID := msg.Header.Get(jetstream.UserID)
|
||||
var output eventutil.AccountData
|
||||
|
|
|
|||
|
|
@ -75,12 +75,13 @@ func NewOutputKeyChangeEventConsumer(
|
|||
// Start consuming from the key server
|
||||
func (s *OutputKeyChangeEventConsumer) Start() error {
|
||||
return jetstream.JetStreamConsumer(
|
||||
s.ctx, s.jetstream, s.topic, s.durable, s.onMessage,
|
||||
nats.DeliverAll(), nats.ManualAck(),
|
||||
s.ctx, s.jetstream, s.topic, s.durable, 1,
|
||||
s.onMessage, nats.DeliverAll(), nats.ManualAck(),
|
||||
)
|
||||
}
|
||||
|
||||
func (s *OutputKeyChangeEventConsumer) onMessage(ctx context.Context, msg *nats.Msg) bool {
|
||||
func (s *OutputKeyChangeEventConsumer) onMessage(ctx context.Context, msgs []*nats.Msg) bool {
|
||||
msg := msgs[0] // Guaranteed to exist if onMessage is called
|
||||
var m api.DeviceMessage
|
||||
if err := json.Unmarshal(msg.Data, &m); err != nil {
|
||||
logrus.WithError(err).Errorf("failed to read device message from key change topic")
|
||||
|
|
|
|||
|
|
@ -128,12 +128,13 @@ func (s *PresenceConsumer) Start() error {
|
|||
return nil
|
||||
}
|
||||
return jetstream.JetStreamConsumer(
|
||||
s.ctx, s.jetstream, s.presenceTopic, s.durable, s.onMessage,
|
||||
s.ctx, s.jetstream, s.presenceTopic, s.durable, 1, s.onMessage,
|
||||
nats.DeliverAll(), nats.ManualAck(), nats.HeadersOnly(),
|
||||
)
|
||||
}
|
||||
|
||||
func (s *PresenceConsumer) onMessage(ctx context.Context, msg *nats.Msg) bool {
|
||||
func (s *PresenceConsumer) onMessage(ctx context.Context, msgs []*nats.Msg) bool {
|
||||
msg := msgs[0] // Guaranteed to exist if onMessage is called
|
||||
userID := msg.Header.Get(jetstream.UserID)
|
||||
presence := msg.Header.Get("presence")
|
||||
timestamp := msg.Header.Get("last_active_ts")
|
||||
|
|
|
|||
|
|
@ -74,12 +74,13 @@ func NewOutputReceiptEventConsumer(
|
|||
// Start consuming receipts events.
|
||||
func (s *OutputReceiptEventConsumer) Start() error {
|
||||
return jetstream.JetStreamConsumer(
|
||||
s.ctx, s.jetstream, s.topic, s.durable, s.onMessage,
|
||||
nats.DeliverAll(), nats.ManualAck(),
|
||||
s.ctx, s.jetstream, s.topic, s.durable, 1,
|
||||
s.onMessage, nats.DeliverAll(), nats.ManualAck(),
|
||||
)
|
||||
}
|
||||
|
||||
func (s *OutputReceiptEventConsumer) onMessage(ctx context.Context, msg *nats.Msg) bool {
|
||||
func (s *OutputReceiptEventConsumer) onMessage(ctx context.Context, msgs []*nats.Msg) bool {
|
||||
msg := msgs[0] // Guaranteed to exist if onMessage is called
|
||||
output := types.OutputReceiptEvent{
|
||||
UserID: msg.Header.Get(jetstream.UserID),
|
||||
RoomID: msg.Header.Get(jetstream.RoomID),
|
||||
|
|
|
|||
|
|
@ -84,15 +84,16 @@ func NewOutputRoomEventConsumer(
|
|||
// Start consuming from room servers
|
||||
func (s *OutputRoomEventConsumer) Start() error {
|
||||
return jetstream.JetStreamConsumer(
|
||||
s.ctx, s.jetstream, s.topic, s.durable, s.onMessage,
|
||||
nats.DeliverAll(), nats.ManualAck(),
|
||||
s.ctx, s.jetstream, s.topic, s.durable, 1,
|
||||
s.onMessage, nats.DeliverAll(), nats.ManualAck(),
|
||||
)
|
||||
}
|
||||
|
||||
// onMessage is called when the sync server receives a new event from the room server output log.
|
||||
// It is not safe for this function to be called from multiple goroutines, or else the
|
||||
// sync stream position may race and be incorrectly calculated.
|
||||
func (s *OutputRoomEventConsumer) onMessage(ctx context.Context, msg *nats.Msg) bool {
|
||||
func (s *OutputRoomEventConsumer) onMessage(ctx context.Context, msgs []*nats.Msg) bool {
|
||||
msg := msgs[0] // Guaranteed to exist if onMessage is called
|
||||
// Parse out the event JSON
|
||||
var err error
|
||||
var output api.OutputEvent
|
||||
|
|
|
|||
|
|
@ -68,12 +68,13 @@ func NewOutputSendToDeviceEventConsumer(
|
|||
// Start consuming send-to-device events.
|
||||
func (s *OutputSendToDeviceEventConsumer) Start() error {
|
||||
return jetstream.JetStreamConsumer(
|
||||
s.ctx, s.jetstream, s.topic, s.durable, s.onMessage,
|
||||
nats.DeliverAll(), nats.ManualAck(),
|
||||
s.ctx, s.jetstream, s.topic, s.durable, 1,
|
||||
s.onMessage, nats.DeliverAll(), nats.ManualAck(),
|
||||
)
|
||||
}
|
||||
|
||||
func (s *OutputSendToDeviceEventConsumer) onMessage(ctx context.Context, msg *nats.Msg) bool {
|
||||
func (s *OutputSendToDeviceEventConsumer) onMessage(ctx context.Context, msgs []*nats.Msg) bool {
|
||||
msg := msgs[0] // Guaranteed to exist if onMessage is called
|
||||
userID := msg.Header.Get(jetstream.UserID)
|
||||
_, domain, err := gomatrixserverlib.SplitID('@', userID)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -64,12 +64,13 @@ func NewOutputTypingEventConsumer(
|
|||
// Start consuming typing events.
|
||||
func (s *OutputTypingEventConsumer) Start() error {
|
||||
return jetstream.JetStreamConsumer(
|
||||
s.ctx, s.jetstream, s.topic, s.durable, s.onMessage,
|
||||
nats.DeliverAll(), nats.ManualAck(),
|
||||
s.ctx, s.jetstream, s.topic, s.durable, 1,
|
||||
s.onMessage, nats.DeliverAll(), nats.ManualAck(),
|
||||
)
|
||||
}
|
||||
|
||||
func (s *OutputTypingEventConsumer) onMessage(ctx context.Context, msg *nats.Msg) bool {
|
||||
func (s *OutputTypingEventConsumer) onMessage(ctx context.Context, msgs []*nats.Msg) bool {
|
||||
msg := msgs[0] // Guaranteed to exist if onMessage is called
|
||||
roomID := msg.Header.Get(jetstream.RoomID)
|
||||
userID := msg.Header.Get(jetstream.UserID)
|
||||
typing, err := strconv.ParseBool(msg.Header.Get("typing"))
|
||||
|
|
|
|||
|
|
@ -67,8 +67,8 @@ func NewOutputNotificationDataConsumer(
|
|||
// Start starts consumption.
|
||||
func (s *OutputNotificationDataConsumer) Start() error {
|
||||
return jetstream.JetStreamConsumer(
|
||||
s.ctx, s.jetstream, s.topic, s.durable, s.onMessage,
|
||||
nats.DeliverAll(), nats.ManualAck(),
|
||||
s.ctx, s.jetstream, s.topic, s.durable, 1,
|
||||
s.onMessage, nats.DeliverAll(), nats.ManualAck(),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -76,7 +76,8 @@ func (s *OutputNotificationDataConsumer) Start() error {
|
|||
// the push server. It is not safe for this function to be called from
|
||||
// multiple goroutines, or else the sync stream position may race and
|
||||
// be incorrectly calculated.
|
||||
func (s *OutputNotificationDataConsumer) onMessage(ctx context.Context, msg *nats.Msg) bool {
|
||||
func (s *OutputNotificationDataConsumer) onMessage(ctx context.Context, msgs []*nats.Msg) bool {
|
||||
msg := msgs[0] // Guaranteed to exist if onMessage is called
|
||||
userID := string(msg.Header.Get(jetstream.UserID))
|
||||
|
||||
// Parse out the event JSON
|
||||
|
|
|
|||
|
|
@ -41,6 +41,8 @@ CREATE TABLE IF NOT EXISTS syncapi_send_to_device (
|
|||
-- The event content JSON.
|
||||
content TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS syncapi_send_to_device_user_id_device_id_idx ON syncapi_send_to_device(user_id, device_id);
|
||||
`
|
||||
|
||||
const insertSendToDeviceMessageSQL = `
|
||||
|
|
|
|||
|
|
@ -39,6 +39,8 @@ CREATE TABLE IF NOT EXISTS syncapi_send_to_device (
|
|||
-- The event content JSON.
|
||||
content TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS syncapi_send_to_device_user_id_device_id_idx ON syncapi_send_to_device(user_id, device_id);
|
||||
`
|
||||
|
||||
const insertSendToDeviceMessageSQL = `
|
||||
|
|
|
|||
|
|
@ -303,7 +303,7 @@ func (p *PDUStreamProvider) addRoomDeltaToResponse(
|
|||
|
||||
if stateFilter.LazyLoadMembers {
|
||||
delta.StateEvents, err = p.lazyLoadMembers(
|
||||
ctx, delta.RoomID, true, limited, stateFilter.IncludeRedundantMembers,
|
||||
ctx, delta.RoomID, true, limited, stateFilter,
|
||||
device, recentEvents, delta.StateEvents,
|
||||
)
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
|
|
@ -532,7 +532,7 @@ func (p *PDUStreamProvider) getJoinResponseForCompleteSync(
|
|||
return nil, err
|
||||
}
|
||||
stateEvents, err = p.lazyLoadMembers(ctx, roomID,
|
||||
false, limited, stateFilter.IncludeRedundantMembers,
|
||||
false, limited, stateFilter,
|
||||
device, recentEvents, stateEvents,
|
||||
)
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
|
|
@ -551,7 +551,7 @@ func (p *PDUStreamProvider) getJoinResponseForCompleteSync(
|
|||
|
||||
func (p *PDUStreamProvider) lazyLoadMembers(
|
||||
ctx context.Context, roomID string,
|
||||
incremental, limited, includeRedundant bool,
|
||||
incremental, limited bool, stateFilter *gomatrixserverlib.StateFilter,
|
||||
device *userapi.Device,
|
||||
timelineEvents, stateEvents []*gomatrixserverlib.HeaderedEvent,
|
||||
) ([]*gomatrixserverlib.HeaderedEvent, error) {
|
||||
|
|
@ -581,7 +581,7 @@ func (p *PDUStreamProvider) lazyLoadMembers(
|
|||
stateKey := *event.StateKey()
|
||||
if _, ok := timelineUsers[stateKey]; ok || isGappedIncremental {
|
||||
newStateEvents = append(newStateEvents, event)
|
||||
if !includeRedundant {
|
||||
if !stateFilter.IncludeRedundantMembers {
|
||||
p.lazyLoadCache.StoreLazyLoadedUser(device, roomID, stateKey, event.EventID())
|
||||
}
|
||||
delete(timelineUsers, stateKey)
|
||||
|
|
@ -596,6 +596,7 @@ func (p *PDUStreamProvider) lazyLoadMembers(
|
|||
}
|
||||
// Query missing membership events
|
||||
filter := gomatrixserverlib.DefaultStateFilter()
|
||||
filter.Limit = stateFilter.Limit
|
||||
filter.Senders = &wantUsers
|
||||
filter.Types = &[]string{gomatrixserverlib.MRoomMember}
|
||||
memberships, err := p.DB.GetStateEventsForRoom(ctx, roomID, &filter)
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
package test
|
||||
|
||||
import (
|
||||
"crypto/ed25519"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
|
|
@ -44,6 +45,10 @@ func NewMatrixKey(matrixKeyPath string) (err error) {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return SaveMatrixKey(matrixKeyPath, data[3:])
|
||||
}
|
||||
|
||||
func SaveMatrixKey(matrixKeyPath string, data ed25519.PrivateKey) error {
|
||||
keyOut, err := os.OpenFile(matrixKeyPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -62,7 +67,7 @@ func NewMatrixKey(matrixKeyPath string) (err error) {
|
|||
Headers: map[string]string{
|
||||
"Key-ID": fmt.Sprintf("ed25519:%s", keyID[:6]),
|
||||
},
|
||||
Bytes: data[3:],
|
||||
Bytes: data,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,13 +31,33 @@ import (
|
|||
|
||||
func CreateBaseDendrite(t *testing.T, dbType test.DBType) (*base.BaseDendrite, func()) {
|
||||
var cfg config.Dendrite
|
||||
cfg.Defaults(false)
|
||||
cfg.Defaults(config.DefaultOpts{
|
||||
Generate: false,
|
||||
Monolithic: true,
|
||||
})
|
||||
cfg.Global.JetStream.InMemory = true
|
||||
switch dbType {
|
||||
case test.DBTypePostgres:
|
||||
cfg.Global.Defaults(true) // autogen a signing key
|
||||
cfg.MediaAPI.Defaults(true) // autogen a media path
|
||||
cfg.SyncAPI.Fulltext.Defaults(true) // use in memory fts
|
||||
cfg.Global.Defaults(config.DefaultOpts{ // autogen a signing key
|
||||
Generate: true,
|
||||
Monolithic: true,
|
||||
}) // autogen a signing key
|
||||
cfg.MediaAPI.Defaults(config.DefaultOpts{ // autogen a signing key
|
||||
Generate: true,
|
||||
Monolithic: true,
|
||||
}) // autogen a media path
|
||||
cfg.SyncAPI.Fulltext.Defaults(config.DefaultOpts{
|
||||
Generate: true,
|
||||
Monolithic: true,
|
||||
}) // use in memory fts
|
||||
cfg.Global.Defaults(config.DefaultOpts{ // autogen a signing key
|
||||
Generate: true,
|
||||
Monolithic: true,
|
||||
})
|
||||
cfg.MediaAPI.Defaults(config.DefaultOpts{ // autogen a media path
|
||||
Generate: true,
|
||||
Monolithic: true,
|
||||
})
|
||||
cfg.Global.ServerName = "test"
|
||||
// use a distinct prefix else concurrent postgres/sqlite runs will clash since NATS will use
|
||||
// the file system event with InMemory=true :(
|
||||
|
|
@ -51,7 +71,10 @@ func CreateBaseDendrite(t *testing.T, dbType test.DBType) (*base.BaseDendrite, f
|
|||
}
|
||||
return base.NewBaseDendrite(&cfg, "Test", base.DisableMetrics), close
|
||||
case test.DBTypeSQLite:
|
||||
cfg.Defaults(true) // sets a sqlite db per component
|
||||
cfg.Defaults(config.DefaultOpts{
|
||||
Generate: true,
|
||||
Monolithic: false, // because we need a database per component
|
||||
})
|
||||
cfg.Global.ServerName = "test"
|
||||
// use a distinct prefix else concurrent postgres/sqlite runs will clash since NATS will use
|
||||
// the file system event with InMemory=true :(
|
||||
|
|
@ -59,7 +82,6 @@ func CreateBaseDendrite(t *testing.T, dbType test.DBType) (*base.BaseDendrite, f
|
|||
return base.NewBaseDendrite(&cfg, "Test", base.DisableMetrics), func() {
|
||||
// cleanup db files. This risks getting out of sync as we add more database strings :(
|
||||
dbFiles := []config.DataSource{
|
||||
cfg.AppServiceAPI.Database.ConnectionString,
|
||||
cfg.FederationAPI.Database.ConnectionString,
|
||||
cfg.KeyServer.Database.ConnectionString,
|
||||
cfg.MSCs.Database.ConnectionString,
|
||||
|
|
@ -85,7 +107,10 @@ func CreateBaseDendrite(t *testing.T, dbType test.DBType) (*base.BaseDendrite, f
|
|||
func Base(cfg *config.Dendrite) (*base.BaseDendrite, nats.JetStreamContext, *nats.Conn) {
|
||||
if cfg == nil {
|
||||
cfg = &config.Dendrite{}
|
||||
cfg.Defaults(true)
|
||||
cfg.Defaults(config.DefaultOpts{
|
||||
Generate: true,
|
||||
Monolithic: true,
|
||||
})
|
||||
}
|
||||
cfg.Global.JetStream.InMemory = true
|
||||
cfg.SyncAPI.Fulltext.InMemory = true
|
||||
|
|
|
|||
|
|
@ -56,15 +56,16 @@ func NewOutputReadUpdateConsumer(
|
|||
|
||||
func (s *OutputReadUpdateConsumer) Start() error {
|
||||
if err := jetstream.JetStreamConsumer(
|
||||
s.ctx, s.jetstream, s.topic, s.durable, s.onMessage,
|
||||
nats.DeliverAll(), nats.ManualAck(),
|
||||
s.ctx, s.jetstream, s.topic, s.durable, 1,
|
||||
s.onMessage, nats.DeliverAll(), nats.ManualAck(),
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *OutputReadUpdateConsumer) onMessage(ctx context.Context, msg *nats.Msg) bool {
|
||||
func (s *OutputReadUpdateConsumer) onMessage(ctx context.Context, msgs []*nats.Msg) bool {
|
||||
msg := msgs[0] // Guaranteed to exist if onMessage is called
|
||||
var read types.ReadUpdate
|
||||
if err := json.Unmarshal(msg.Data, &read); err != nil {
|
||||
log.WithError(err).Error("userapi clientapi consumer: message parse failure")
|
||||
|
|
|
|||
|
|
@ -7,6 +7,10 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
"github.com/nats-io/nats.go"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/matrix-org/dendrite/internal/eventutil"
|
||||
"github.com/matrix-org/dendrite/internal/pushgateway"
|
||||
"github.com/matrix-org/dendrite/internal/pushrules"
|
||||
|
|
@ -20,9 +24,6 @@ import (
|
|||
"github.com/matrix-org/dendrite/userapi/storage"
|
||||
"github.com/matrix-org/dendrite/userapi/storage/tables"
|
||||
"github.com/matrix-org/dendrite/userapi/util"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
"github.com/nats-io/nats.go"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type OutputStreamEventConsumer struct {
|
||||
|
|
@ -64,15 +65,16 @@ func NewOutputStreamEventConsumer(
|
|||
|
||||
func (s *OutputStreamEventConsumer) Start() error {
|
||||
if err := jetstream.JetStreamConsumer(
|
||||
s.ctx, s.jetstream, s.topic, s.durable, s.onMessage,
|
||||
nats.DeliverAll(), nats.ManualAck(),
|
||||
s.ctx, s.jetstream, s.topic, s.durable, 1,
|
||||
s.onMessage, nats.DeliverAll(), nats.ManualAck(),
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *OutputStreamEventConsumer) onMessage(ctx context.Context, msg *nats.Msg) bool {
|
||||
func (s *OutputStreamEventConsumer) onMessage(ctx context.Context, msgs []*nats.Msg) bool {
|
||||
msg := msgs[0] // Guaranteed to exist if onMessage is called
|
||||
var output types.StreamedEvent
|
||||
output.Event = &gomatrixserverlib.HeaderedEvent{}
|
||||
if err := json.Unmarshal(msg.Data, &output); err != nil {
|
||||
|
|
@ -529,7 +531,9 @@ func (s *OutputStreamEventConsumer) notifyHTTP(ctx context.Context, event *gomat
|
|||
case "event_id_only":
|
||||
req = pushgateway.NotifyRequest{
|
||||
Notification: pushgateway.Notification{
|
||||
Counts: &pushgateway.Counts{},
|
||||
Counts: &pushgateway.Counts{
|
||||
Unread: userNumUnreadNotifs,
|
||||
},
|
||||
Devices: devices,
|
||||
EventID: event.EventID(),
|
||||
RoomID: event.RoomID(),
|
||||
|
|
|
|||
|
|
@ -28,7 +28,6 @@ import (
|
|||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
|
||||
"github.com/matrix-org/dendrite/appservice/types"
|
||||
"github.com/matrix-org/dendrite/clientapi/userutil"
|
||||
"github.com/matrix-org/dendrite/internal/eventutil"
|
||||
"github.com/matrix-org/dendrite/internal/pushrules"
|
||||
|
|
@ -454,7 +453,7 @@ func (a *UserInternalAPI) queryAppServiceToken(ctx context.Context, token, appSe
|
|||
// Create a dummy device for AS user
|
||||
dev := api.Device{
|
||||
// Use AS dummy device ID
|
||||
ID: types.AppServiceDeviceID,
|
||||
ID: "AS_Device",
|
||||
// AS dummy device has AS's token.
|
||||
AccessToken: token,
|
||||
AppserviceID: appService.ID,
|
||||
|
|
|
|||
Loading…
Reference in a new issue