move gotd fork into repo. (#111)
- update to latest telegram layer - remove some references to fields in tg.Entities that don't exist in the schema - originally added here: https://github.com/beeper/td/commit/820929062a2ba0104397bc01235ab58a9cff780e - referenced here - https://github.com/mautrix/telegramgo/commit/124f0967ed195b5a380c9bd02e170ada9710dde3 - https://github.com/mautrix/telegramgo/commit/4205047aab2e0639217148b5d125bfaab668bd8e
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
// Package cluster contains Telegram multi-DC setup utilities.
|
||||
package cluster
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"go.mau.fi/mautrix-telegram/pkg/gotd/exchange"
|
||||
"go.mau.fi/mautrix-telegram/pkg/gotd/tdsync"
|
||||
"go.mau.fi/mautrix-telegram/pkg/gotd/telegram/dcs"
|
||||
"go.mau.fi/mautrix-telegram/pkg/gotd/tg"
|
||||
"go.mau.fi/mautrix-telegram/pkg/gotd/tgtest"
|
||||
"go.mau.fi/mautrix-telegram/pkg/gotd/tgtest/services/config"
|
||||
)
|
||||
|
||||
type setup struct {
|
||||
srv *tgtest.Server
|
||||
dispatch *tgtest.Dispatcher
|
||||
}
|
||||
|
||||
// Cluster is a cluster of multiple servers, representing multiple Telegram datacenters.
|
||||
type Cluster struct {
|
||||
// denotes to use websocket listener
|
||||
web bool
|
||||
|
||||
setups map[int]setup
|
||||
keys []exchange.PublicKey
|
||||
|
||||
// DCs config state.
|
||||
cfg tg.Config
|
||||
cdnCfg tg.CDNConfig
|
||||
domains map[int]string
|
||||
|
||||
// Signal for readiness.
|
||||
ready *tdsync.Ready
|
||||
|
||||
// RPC dispatcher.
|
||||
common *tgtest.Dispatcher
|
||||
|
||||
log *zap.Logger
|
||||
random io.Reader
|
||||
protocol dcs.Protocol
|
||||
}
|
||||
|
||||
// NewCluster creates new server Cluster.
|
||||
func NewCluster(opts Options) *Cluster {
|
||||
opts.setDefaults()
|
||||
|
||||
q := &Cluster{
|
||||
web: opts.Web,
|
||||
setups: map[int]setup{},
|
||||
keys: nil,
|
||||
cfg: opts.Config,
|
||||
cdnCfg: opts.CDNConfig,
|
||||
domains: map[int]string{},
|
||||
ready: tdsync.NewReady(),
|
||||
common: tgtest.NewDispatcher(),
|
||||
log: opts.Logger,
|
||||
random: opts.Random,
|
||||
protocol: opts.Protocol,
|
||||
}
|
||||
config.NewService(&q.cfg, &q.cdnCfg).Register(q.common)
|
||||
q.common.Fallback(q.fallback())
|
||||
|
||||
return q
|
||||
}
|
||||
|
||||
// List returns DCs list.
|
||||
func (c *Cluster) List() dcs.List {
|
||||
return dcs.List{
|
||||
Options: c.cfg.DCOptions,
|
||||
Domains: c.domains,
|
||||
}
|
||||
}
|
||||
|
||||
// Resolver returns dcs.Resolver to use.
|
||||
func (c *Cluster) Resolver() dcs.Resolver {
|
||||
if c.web {
|
||||
return dcs.Websocket(dcs.WebsocketOptions{})
|
||||
}
|
||||
|
||||
return dcs.Plain(dcs.PlainOptions{
|
||||
Protocol: c.protocol,
|
||||
})
|
||||
}
|
||||
|
||||
// Keys returns all servers public keys.
|
||||
func (c *Cluster) Keys() []exchange.PublicKey {
|
||||
return c.keys
|
||||
}
|
||||
|
||||
// Ready returns signal channel to await readiness.
|
||||
func (c *Cluster) Ready() <-chan struct{} {
|
||||
return c.ready.Ready()
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package cluster
|
||||
|
||||
import (
|
||||
"crypto/rsa"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"go.mau.fi/mautrix-telegram/pkg/gotd/crypto"
|
||||
"go.mau.fi/mautrix-telegram/pkg/gotd/exchange"
|
||||
"go.mau.fi/mautrix-telegram/pkg/gotd/tgtest"
|
||||
"go.mau.fi/mautrix-telegram/pkg/gotd/tgtest/services"
|
||||
"go.mau.fi/mautrix-telegram/pkg/gotd/transport"
|
||||
)
|
||||
|
||||
// Common returns common dispatcher.
|
||||
func (c *Cluster) Common() *tgtest.Dispatcher {
|
||||
return c.common
|
||||
}
|
||||
|
||||
func (c *Cluster) getCodec() (codec func() transport.Codec) {
|
||||
if !c.web {
|
||||
codec = c.protocol.Codec
|
||||
}
|
||||
return codec
|
||||
}
|
||||
|
||||
// DC registers new server and returns it.
|
||||
func (c *Cluster) DC(id int, name string) (*tgtest.Server, *tgtest.Dispatcher) {
|
||||
if s, ok := c.setups[id]; ok {
|
||||
return s.srv, s.dispatch
|
||||
}
|
||||
|
||||
key, err := rsa.GenerateKey(c.random, crypto.RSAKeyBits)
|
||||
if err != nil {
|
||||
// TODO(tdakkota): Return error instead.
|
||||
panic(err)
|
||||
}
|
||||
privateKey := exchange.PrivateKey{
|
||||
RSA: key,
|
||||
}
|
||||
|
||||
d := tgtest.NewDispatcher()
|
||||
server := tgtest.NewServer(privateKey, tgtest.UnpackInvoke(d), tgtest.ServerOptions{
|
||||
DC: id,
|
||||
Logger: c.log.Named(name).With(zap.Int("dc_id", id)),
|
||||
Codec: c.getCodec(),
|
||||
})
|
||||
c.setups[id] = setup{
|
||||
srv: server,
|
||||
dispatch: d,
|
||||
}
|
||||
c.keys = append(c.keys, server.Key())
|
||||
|
||||
// We set server fallback handler to dispatch request in order
|
||||
// 1) Explicit DC handler
|
||||
// 2) Explicit common handler
|
||||
// 3) Common fallback
|
||||
d.Fallback(c.Common())
|
||||
return server, d
|
||||
}
|
||||
|
||||
// Dispatch registers new server and returns its dispatcher.
|
||||
func (c *Cluster) Dispatch(id int, name string) *tgtest.Dispatcher {
|
||||
_, d := c.DC(id, name)
|
||||
return d
|
||||
}
|
||||
|
||||
func (c *Cluster) fallback() tgtest.HandlerFunc {
|
||||
return services.NotImplemented
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package cluster
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
|
||||
"github.com/go-faster/errors"
|
||||
)
|
||||
|
||||
func newLocalListener(ctx context.Context) (net.Listener, error) {
|
||||
cfg := net.ListenConfig{}
|
||||
l, err := cfg.Listen(ctx, "tcp4", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "listen")
|
||||
}
|
||||
return l, nil
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package cluster
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"go.mau.fi/mautrix-telegram/pkg/gotd/crypto"
|
||||
"go.mau.fi/mautrix-telegram/pkg/gotd/telegram/dcs"
|
||||
"go.mau.fi/mautrix-telegram/pkg/gotd/tg"
|
||||
"go.mau.fi/mautrix-telegram/pkg/gotd/transport"
|
||||
)
|
||||
|
||||
// Options of Cluster.
|
||||
type Options struct {
|
||||
// Web denotes to use websocket listener.
|
||||
Web bool
|
||||
// Random is random source. Used to generate RSA keys.
|
||||
// Defaults to rand.Reader.
|
||||
Random io.Reader
|
||||
// Logger is instance of zap.Logger. No logs by default.
|
||||
Logger *zap.Logger
|
||||
// Codec constructor.
|
||||
// Defaults to nil (underlying transport server detects protocol automatically).
|
||||
Protocol dcs.Protocol
|
||||
// Config is an initial cluster config.
|
||||
Config tg.Config
|
||||
// CDNConfig is an initial cluster CDN config.
|
||||
CDNConfig tg.CDNConfig
|
||||
}
|
||||
|
||||
func (opt *Options) setDefaults() {
|
||||
// It's okay to use zero value Web.
|
||||
if opt.Random == nil {
|
||||
opt.Random = crypto.DefaultRand()
|
||||
}
|
||||
if opt.Logger == nil {
|
||||
opt.Logger = zap.NewNop()
|
||||
}
|
||||
if opt.Protocol == nil {
|
||||
opt.Protocol = transport.Intermediate
|
||||
}
|
||||
// It's okay to use zero value Config.
|
||||
// It's okay to use zero value CDNConfig.
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package cluster
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/go-faster/errors"
|
||||
|
||||
"go.mau.fi/mautrix-telegram/pkg/gotd/tdsync"
|
||||
"go.mau.fi/mautrix-telegram/pkg/gotd/tg"
|
||||
"go.mau.fi/mautrix-telegram/pkg/gotd/transport"
|
||||
)
|
||||
|
||||
// Up runs all servers in a cluster.
|
||||
func (c *Cluster) Up(ctx context.Context) error {
|
||||
g := tdsync.NewCancellableGroup(ctx)
|
||||
|
||||
listen := func(ctx context.Context, _ int) (net.Listener, error) {
|
||||
return newLocalListener(ctx)
|
||||
}
|
||||
if c.web {
|
||||
// Create local random listener
|
||||
l, err := newLocalListener(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mux := http.NewServeMux()
|
||||
srv := http.Server{
|
||||
ReadHeaderTimeout: time.Second * 10,
|
||||
Handler: mux,
|
||||
BaseContext: func(net.Listener) context.Context {
|
||||
return ctx
|
||||
},
|
||||
}
|
||||
g.Go(func(ctx context.Context) error {
|
||||
if err := srv.Serve(l); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||
return errors.Wrap(err, "serve")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
g.Go(func(ctx context.Context) error {
|
||||
<-ctx.Done()
|
||||
|
||||
return srv.Close()
|
||||
})
|
||||
|
||||
baseURL := url.URL{
|
||||
Scheme: "http",
|
||||
Host: l.Addr().String(),
|
||||
}
|
||||
listen = func(ctx context.Context, dc int) (net.Listener, error) {
|
||||
listener, handler := transport.WebsocketListener(l.Addr())
|
||||
|
||||
path := fmt.Sprintf("/dc/%d", dc)
|
||||
mux.Handle(path, handler)
|
||||
|
||||
dcURL := baseURL
|
||||
dcURL.Path = path
|
||||
c.domains[dc] = dcURL.String()
|
||||
return listener, nil
|
||||
}
|
||||
}
|
||||
|
||||
for dcID, s := range c.setups {
|
||||
l, err := listen(ctx, dcID)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "DC %d: listen port", dcID)
|
||||
}
|
||||
|
||||
if !c.web {
|
||||
// Add TCP listeners to config.
|
||||
if addr, ok := l.Addr().(*net.TCPAddr); ok {
|
||||
c.cfg.DCOptions = append(c.cfg.DCOptions, tg.DCOption{
|
||||
Ipv6: addr.IP.To16() != nil,
|
||||
Static: true,
|
||||
ID: dcID,
|
||||
IPAddress: addr.IP.String(),
|
||||
Port: addr.Port,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Copy iteration value.
|
||||
srv := s.srv
|
||||
g.Go(func(ctx context.Context) error {
|
||||
return srv.Serve(ctx, transport.ListenCodec(nil, l))
|
||||
})
|
||||
}
|
||||
c.ready.Signal()
|
||||
|
||||
return g.Wait()
|
||||
}
|
||||
Reference in New Issue
Block a user