diff options
Diffstat (limited to 'content.go')
-rw-r--r-- | content.go | 380 |
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 +} |