aboutsummaryrefslogtreecommitdiff
path: root/content.go
diff options
context:
space:
mode:
Diffstat (limited to 'content.go')
-rw-r--r--content.go380
1 files changed, 380 insertions, 0 deletions
diff --git a/content.go b/content.go
new file mode 100644
index 0000000..2e01e46
--- /dev/null
+++ b/content.go
@@ -0,0 +1,380 @@
+package main
+
+import (
+ "errors"
+ "fmt"
+ "log"
+ "net"
+ "sync"
+ "time"
+
+ "github.com/HimbeerserverDE/srp"
+ "github.com/anon55555/mt"
+)
+
+type mediaFile struct {
+ name string
+ base64SHA1 string
+ data []byte
+}
+
+type contentConn struct {
+ mt.Peer
+
+ state clientState
+ name, userName string
+ doneCh chan struct{}
+
+ auth struct {
+ method mt.AuthMethods
+ salt, srpA, a, srpK []byte
+ }
+
+ itemDefs []mt.ItemDef
+ aliases []struct{ Alias, Orig string }
+
+ nodeDefs []mt.NodeDef
+
+ media []mediaFile
+}
+
+func (cc *contentConn) done() <-chan struct{} { return cc.doneCh }
+
+func (cc *contentConn) log(dir, msg string) {
+ log.Printf("{←|⇶} %s {%s} %s", dir, cc.name, msg)
+}
+
+func handleContent(cc *contentConn) {
+ defer close(cc.doneCh)
+
+ go func() {
+ for cc.state == csCreated {
+ cc.SendCmd(&mt.ToSrvInit{
+ SerializeVer: latestSerializeVer,
+ MinProtoVer: latestProtoVer,
+ MaxProtoVer: latestProtoVer,
+ PlayerName: cc.userName,
+ })
+ time.Sleep(500 * time.Millisecond)
+ }
+ }()
+
+ for {
+ pkt, err := cc.Recv()
+ if err != nil {
+ if errors.Is(err, net.ErrClosed) {
+ cc.log("<->", "disconnect")
+ break
+ }
+
+ cc.log("-->", err.Error())
+ continue
+ }
+
+ switch cmd := pkt.Cmd.(type) {
+ case *mt.ToCltHello:
+ if cc.auth.method != 0 {
+ cc.log("<--", "unexpected authentication")
+ cc.Close()
+ break
+ }
+
+ cc.state++
+
+ if cmd.AuthMethods&mt.FirstSRP == mt.FirstSRP {
+ cc.auth.method = mt.FirstSRP
+ } else {
+ cc.auth.method = mt.SRP
+ }
+
+ if cmd.SerializeVer != latestSerializeVer {
+ cc.log("<--", "invalid serializeVer")
+ break
+ }
+
+ switch cc.auth.method {
+ case mt.SRP:
+ cc.auth.srpA, cc.auth.a, err = srp.InitiateHandshake()
+ if err != nil {
+ cc.log("-->", err.Error())
+ break
+ }
+
+ cc.SendCmd(&mt.ToSrvSRPBytesA{
+ A: cc.auth.srpA,
+ NoSHA1: true,
+ })
+ case mt.FirstSRP:
+ salt, verifier, err := srp.NewClient([]byte(cc.userName), []byte{})
+ if err != nil {
+ cc.log("-->", err.Error())
+ break
+ }
+
+ cc.SendCmd(&mt.ToSrvFirstSRP{
+ Salt: salt,
+ Verifier: verifier,
+ EmptyPasswd: true,
+ })
+ default:
+ cc.log("<->", "invalid auth method")
+ cc.Close()
+ }
+ case *mt.ToCltSRPBytesSaltB:
+ if cc.auth.method != mt.SRP {
+ cc.log("<--", "multiple authentication attempts")
+ break
+ }
+
+ cc.auth.srpK, err = srp.CompleteHandshake(cc.auth.srpA, cc.auth.a, []byte(cc.userName), []byte{}, cmd.Salt, cmd.B)
+ if err != nil {
+ cc.log("-->", err.Error())
+ break
+ }
+
+ M := srp.ClientProof([]byte(cc.userName), cmd.Salt, cc.auth.srpA, cmd.B, cc.auth.srpK)
+ if M == nil {
+ cc.log("<--", "SRP safety check fail")
+ break
+ }
+
+ cc.SendCmd(&mt.ToSrvSRPBytesM{
+ M: M,
+ })
+ case *mt.ToCltDisco:
+ cc.log("<--", fmt.Sprintf("deny access %+v", cmd))
+ case *mt.ToCltAcceptAuth:
+ cc.auth.method = 0
+ cc.SendCmd(&mt.ToSrvInit2{})
+ case *mt.ToCltItemDefs:
+ for _, def := range cmd.Defs {
+ cc.itemDefs = append(cc.itemDefs, def)
+ }
+ cc.aliases = cmd.Aliases
+ case *mt.ToCltNodeDefs:
+ for _, def := range cmd.Defs {
+ cc.nodeDefs = append(cc.nodeDefs, def)
+ }
+ case *mt.ToCltAnnounceMedia:
+ // ToDo: OOB media support
+ var filenames []string
+
+ for _, f := range cmd.Files {
+ cc.media = append(cc.media, mediaFile{
+ name: f.Name,
+ base64SHA1: f.Base64SHA1,
+ })
+
+ filenames = append(filenames, f.Name)
+ }
+
+ cc.SendCmd(&mt.ToSrvReqMedia{Filenames: filenames})
+ case *mt.ToCltMedia:
+ for _, f := range cmd.Files {
+ for _, af := range cc.media {
+ if af.name == f.Name {
+ af.data = f.Data
+ break
+ }
+ }
+ }
+
+ if cmd.I == cmd.N-1 {
+ cc.Close()
+ }
+ }
+ }
+}
+
+func (cc *clientConn) sendMedia(filenames []string) {
+ var bunches [][]struct {
+ Name string
+ Data []byte
+ }
+ bunches = append(bunches, []struct {
+ Name string
+ Data []byte
+ }{})
+
+ var bunchSize int
+ for _, filename := range filenames {
+ var known bool
+ for _, f := range cc.media {
+ if f.name == filename {
+ mfile := struct {
+ Name string
+ Data []byte
+ }{
+ Name: f.name,
+ Data: f.data,
+ }
+ bunches[len(bunches)-1] = append(bunches[len(bunches)-1], mfile)
+
+ bunchSize += len(f.data)
+ if bunchSize >= bytesPerMediaBunch {
+ bunches = append(bunches, []struct {
+ Name string
+ Data []byte
+ }{})
+ bunchSize = 0
+ }
+
+ known = true
+ break
+ }
+ }
+
+ if !known {
+ cc.log("-->", "request unknown media file")
+ continue
+ }
+ }
+
+ for i := uint16(0); i < uint16(len(bunches)); i++ {
+ cc.SendCmd(&mt.ToCltMedia{
+ N: uint16(len(bunches)),
+ I: i,
+ Files: bunches[i],
+ })
+ }
+}
+
+type param0Map map[string]map[mt.Content]mt.Content
+type param0SrvMap map[mt.Content]struct {
+ name string
+ param0 mt.Content
+}
+
+func muxItemDefs(conns []*contentConn) ([]mt.ItemDef, []struct{ Alias, Orig string }) {
+ var itemDefs []mt.ItemDef
+ var aliases []struct{ Alias, Orig string }
+ var wg sync.WaitGroup
+
+ for _, cc := range conns {
+ wg.Add(1)
+ go func() {
+ <-cc.done()
+ for _, def := range cc.itemDefs {
+ def.Name = cc.name + "_" + def.Name
+ itemDefs = append(itemDefs, def)
+ // ToDo: Hand mux
+ }
+
+ for _, alias := range cc.aliases {
+ aliases = append(aliases, struct{ Alias, Orig string }{
+ Alias: cc.name + "_" + alias.Alias,
+ Orig: cc.name + "_" + alias.Orig,
+ })
+ }
+
+ wg.Done()
+ }()
+ }
+
+ wg.Wait()
+ return itemDefs, aliases
+}
+
+func muxNodeDefs(conns []*contentConn) (nodeDefs []mt.NodeDef, p0Map param0Map, p0SrvMap param0SrvMap) {
+ var wg sync.WaitGroup
+ var param0 mt.Content
+
+ p0Map = make(param0Map)
+ p0SrvMap = make(param0SrvMap)
+
+ for _, cc := range conns {
+ wg.Add(1)
+ go func() {
+ <-cc.done()
+ for _, def := range cc.nodeDefs {
+ if p0Map[cc.name] == nil {
+ p0Map[cc.name] = make(map[mt.Content]mt.Content)
+ }
+
+ p0Map[cc.name][def.Param0] = param0
+ p0SrvMap[param0] = struct {
+ name string
+ param0 mt.Content
+ }{
+ name: cc.name,
+ param0: def.Param0,
+ }
+
+ def.Param0 = param0
+ def.Name = cc.name + "_" + def.Name
+ nodeDefs = append(nodeDefs, def)
+
+ param0++
+ if param0 >= mt.Unknown || param0 <= mt.Ignore {
+ param0 = mt.Ignore + 1
+ }
+ }
+
+ wg.Done()
+ }()
+ }
+
+ wg.Wait()
+ return
+}
+
+func muxMedia(conns []*contentConn) []mediaFile {
+ var media []mediaFile
+ var wg sync.WaitGroup
+
+ for _, cc := range conns {
+ wg.Add(1)
+ go func() {
+ <-cc.done()
+ for _, f := range cc.media {
+ f.name = cc.name + "_" + f.name
+ media = append(media, f)
+ }
+
+ wg.Done()
+ }()
+ }
+
+ wg.Wait()
+ return media
+}
+
+func muxContent(userName string) (itemDefs []mt.ItemDef, aliases []struct{ Alias, Orig string }, nodeDefs []mt.NodeDef, p0Map param0Map, p0SrvMap param0SrvMap, media []mediaFile, err error) {
+ var conns []*contentConn
+ for _, srv := range conf.Servers {
+ var addr *net.UDPAddr
+ addr, err = net.ResolveUDPAddr("udp", srv.Addr)
+ if err != nil {
+ return
+ }
+
+ var conn *net.UDPConn
+ conn, err = net.DialUDP("udp", nil, addr)
+ if err != nil {
+ return
+ }
+
+ conns = append(conns, connectContent(conn, srv.Name, userName))
+ }
+
+ var wg sync.WaitGroup
+ wg.Add(3)
+
+ go func() {
+ defer wg.Done()
+ itemDefs, aliases = muxItemDefs(conns)
+ }()
+
+ go func() {
+ defer wg.Done()
+ nodeDefs, p0Map, p0SrvMap = muxNodeDefs(conns)
+ }()
+
+ go func() {
+ defer wg.Done()
+ media = muxMedia(conns)
+ }()
+
+ wg.Wait()
+ return
+}