move all nmdc-specific content to a separate NmdcProtocol class
--HG-- branch : adc
This commit is contained in:
parent
276ece1cf1
commit
7543d7058f
@ -27,9 +27,22 @@ func (this *HubAddress) parse() url.URL {
|
|||||||
func (this *HubAddress) IsSecure() bool {
|
func (this *HubAddress) IsSecure() bool {
|
||||||
parsed := this.parse()
|
parsed := this.parse()
|
||||||
|
|
||||||
return parsed.Scheme == "nmdcs" || parsed.Scheme == "dchubs"
|
return parsed.Scheme == "nmdcs" || parsed.Scheme == "dchubs" || parsed.Scheme == "adcs"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *HubAddress) GetHostOnly() string {
|
func (this *HubAddress) GetHostOnly() string {
|
||||||
return this.parse().Host
|
return this.parse().Host
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (this *HubAddress) GetProtocol() HubProtocol {
|
||||||
|
parsed := this.parse()
|
||||||
|
|
||||||
|
switch parsed.Scheme {
|
||||||
|
case "nmdc", "dchub", "nmdcs", "dchubs":
|
||||||
|
return HubProtocolNmdc
|
||||||
|
case "adc", "adcs":
|
||||||
|
return HubProtocolAdc
|
||||||
|
default:
|
||||||
|
return HubProtocolAutodetect
|
||||||
|
}
|
||||||
|
}
|
||||||
|
269
HubConnection.go
269
HubConnection.go
@ -4,8 +4,7 @@ import (
|
|||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"strconv"
|
"regexp"
|
||||||
"strings"
|
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@ -20,6 +19,8 @@ type HubConnection struct {
|
|||||||
users map[string]UserInfo
|
users map[string]UserInfo
|
||||||
userLock sync.RWMutex
|
userLock sync.RWMutex
|
||||||
|
|
||||||
|
proto Protocol
|
||||||
|
|
||||||
// Streamed events
|
// Streamed events
|
||||||
processEvent func(HubEvent)
|
processEvent func(HubEvent)
|
||||||
OnEvent chan HubEvent
|
OnEvent chan HubEvent
|
||||||
@ -27,11 +28,8 @@ type HubConnection struct {
|
|||||||
// Private state
|
// Private state
|
||||||
conn net.Conn // this is an interface
|
conn net.Conn // this is an interface
|
||||||
connValid bool
|
connValid bool
|
||||||
sentOurHello bool
|
|
||||||
autoReconnect bool
|
autoReconnect bool
|
||||||
lastDataRecieved time.Time
|
lastDataRecieved time.Time
|
||||||
|
|
||||||
supports map[string]struct{}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Thread-safe user accessor.
|
// Thread-safe user accessor.
|
||||||
@ -43,15 +41,11 @@ func (this *HubConnection) Users(cb func(*map[string]UserInfo) error) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (this *HubConnection) SayPublic(message string) {
|
func (this *HubConnection) SayPublic(message string) {
|
||||||
this.SayRaw("<" + this.Hco.Self.Nick + "> " + Escape(message) + "|")
|
this.proto.SayPublic(message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *HubConnection) SayPrivate(recipient string, message string) {
|
func (this *HubConnection) SayPrivate(recipient string, message string) {
|
||||||
this.SayRaw("$To: " + recipient + " From: " + this.Hco.Self.Nick + " $<" + this.Hco.Self.Nick + "> " + Escape(message) + "|")
|
this.proto.SayPrivate(recipient, message)
|
||||||
}
|
|
||||||
|
|
||||||
func (this *HubConnection) SayInfo() {
|
|
||||||
this.SayRaw(this.Hco.Self.toMyINFO() + "|")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *HubConnection) UserExists(nick string) bool {
|
func (this *HubConnection) UserExists(nick string) bool {
|
||||||
@ -99,250 +93,12 @@ func (this *HubConnection) userJoined_Full(uinf *UserInfo) {
|
|||||||
// Note that protocol messages are transmitted on the caller thread, not from
|
// Note that protocol messages are transmitted on the caller thread, not from
|
||||||
// any internal libnmdc thread.
|
// any internal libnmdc thread.
|
||||||
func (this *HubConnection) SayRaw(protocolCommand string) error {
|
func (this *HubConnection) SayRaw(protocolCommand string) error {
|
||||||
if this.connValid {
|
if !this.connValid {
|
||||||
_, err := this.conn.Write([]byte(protocolCommand))
|
|
||||||
return err
|
|
||||||
} else {
|
|
||||||
return ErrNotConnected
|
return ErrNotConnected
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
func (this *HubConnection) processProtocolMessage(message string) {
|
_, err := this.conn.Write([]byte(protocolCommand))
|
||||||
|
return err
|
||||||
// Zero-length protocol message
|
|
||||||
// ````````````````````````````
|
|
||||||
if len(message) == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Public chat
|
|
||||||
// ```````````
|
|
||||||
if rx_publicChat.MatchString(message) {
|
|
||||||
pubchat_parts := rx_publicChat.FindStringSubmatch(message)
|
|
||||||
this.processEvent(HubEvent{EventType: EVENT_PUBLIC, Nick: pubchat_parts[1], Message: Unescape(pubchat_parts[2])})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// System messages
|
|
||||||
// ```````````````
|
|
||||||
if message[0] != '$' {
|
|
||||||
this.processEvent(HubEvent{EventType: EVENT_SYSTEM_MESSAGE_FROM_HUB, Nick: this.HubName, Message: Unescape(message)})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Protocol messages
|
|
||||||
// `````````````````
|
|
||||||
|
|
||||||
commandParts := strings.SplitN(message, " ", 2)
|
|
||||||
switch commandParts[0] {
|
|
||||||
|
|
||||||
case "$Lock":
|
|
||||||
this.SayRaw("$Supports NoHello NoGetINFO UserCommand UserIP2 QuickList ChatOnly|" +
|
|
||||||
"$Key " + unlock([]byte(commandParts[1])) + "|")
|
|
||||||
this.sentOurHello = false
|
|
||||||
|
|
||||||
case "$Hello":
|
|
||||||
if commandParts[1] == this.Hco.Self.Nick && !this.sentOurHello {
|
|
||||||
this.SayRaw("$Version 1,0091|")
|
|
||||||
this.SayRaw("$GetNickList|")
|
|
||||||
this.SayInfo()
|
|
||||||
this.sentOurHello = true
|
|
||||||
|
|
||||||
} else {
|
|
||||||
this.userJoined_NameOnly(commandParts[1])
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
case "$HubName":
|
|
||||||
this.HubName = commandParts[1]
|
|
||||||
this.processEvent(HubEvent{EventType: EVENT_HUBNAME_CHANGED, Nick: commandParts[1]})
|
|
||||||
|
|
||||||
case "$ValidateDenide": // sic
|
|
||||||
if len(this.Hco.NickPassword) > 0 {
|
|
||||||
this.processEvent(HubEvent{EventType: EVENT_SYSTEM_MESSAGE_FROM_CONN, Message: "Incorrect password."})
|
|
||||||
} else {
|
|
||||||
this.processEvent(HubEvent{EventType: EVENT_SYSTEM_MESSAGE_FROM_CONN, Message: "Nick already in use."})
|
|
||||||
}
|
|
||||||
|
|
||||||
case "$HubIsFull":
|
|
||||||
this.processEvent(HubEvent{EventType: EVENT_SYSTEM_MESSAGE_FROM_CONN, Message: "Hub is full."})
|
|
||||||
|
|
||||||
case "$BadPass":
|
|
||||||
this.processEvent(HubEvent{EventType: EVENT_SYSTEM_MESSAGE_FROM_CONN, Message: "Incorrect password."})
|
|
||||||
|
|
||||||
case "$GetPass":
|
|
||||||
if len(this.Hco.NickPassword) == 0 {
|
|
||||||
// We've got a problem. MyPass with no arguments is a syntax error with no message = instant close
|
|
||||||
// Just drop the connection
|
|
||||||
this.processEvent(HubEvent{EventType: EVENT_SYSTEM_MESSAGE_FROM_CONN, Message: "This account is passworded."})
|
|
||||||
this.Disconnect()
|
|
||||||
} else {
|
|
||||||
this.SayRaw("$MyPass " + Escape(this.Hco.NickPassword) + "|")
|
|
||||||
}
|
|
||||||
|
|
||||||
case "$Quit":
|
|
||||||
this.userLock.Lock()
|
|
||||||
delete(this.users, commandParts[1])
|
|
||||||
this.userLock.Unlock() // Don't lock over a processEvent boundary
|
|
||||||
|
|
||||||
this.processEvent(HubEvent{EventType: EVENT_USER_PART, Nick: commandParts[1]})
|
|
||||||
|
|
||||||
case "$MyINFO":
|
|
||||||
u := UserInfo{}
|
|
||||||
err := u.fromMyINFO(commandParts[1])
|
|
||||||
if err != nil {
|
|
||||||
this.processEvent(HubEvent{EventType: EVENT_DEBUG_MESSAGE, Message: err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
this.userJoined_Full(&u)
|
|
||||||
|
|
||||||
case "$NickList":
|
|
||||||
nicklist := strings.Split(commandParts[1], "$$")
|
|
||||||
for _, nick := range nicklist {
|
|
||||||
if len(nick) > 0 {
|
|
||||||
this.userJoined_NameOnly(nick)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case "$OpList":
|
|
||||||
oplist := strings.Split(commandParts[1], "$$")
|
|
||||||
opmap := map[string]struct{}{}
|
|
||||||
|
|
||||||
// Organise/sort the list, and ensure we're not meeting an operator for
|
|
||||||
// the first time
|
|
||||||
for _, nick := range oplist {
|
|
||||||
if len(nick) > 0 {
|
|
||||||
opmap[nick] = struct{}{}
|
|
||||||
this.userJoined_NameOnly(nick) // assert existence; noop otherwise
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mark all mentioned nicks as being operators, and all unmentioned nicks
|
|
||||||
// as being /not/ an operator. (second pass minimises RW mutex use)
|
|
||||||
func() {
|
|
||||||
this.userLock.Lock()
|
|
||||||
defer this.userLock.Unlock()
|
|
||||||
|
|
||||||
for nick, userinfo := range this.users {
|
|
||||||
_, isop := opmap[nick]
|
|
||||||
|
|
||||||
userinfo.IsOperator = isop
|
|
||||||
this.users[nick] = userinfo
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
case "$To:":
|
|
||||||
valid := false
|
|
||||||
if rx_incomingTo.MatchString(commandParts[1]) {
|
|
||||||
txparts := rx_incomingTo.FindStringSubmatch(commandParts[1])
|
|
||||||
if txparts[1] == this.Hco.Self.Nick && txparts[2] == txparts[3] {
|
|
||||||
this.processEvent(HubEvent{EventType: EVENT_PRIVATE, Nick: txparts[2], Message: Unescape(txparts[4])})
|
|
||||||
valid = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !valid {
|
|
||||||
this.processEvent(HubEvent{EventType: EVENT_DEBUG_MESSAGE, Message: "Malformed private message '" + commandParts[1] + "'"})
|
|
||||||
}
|
|
||||||
|
|
||||||
case "$UserIP":
|
|
||||||
this.userLock.Lock()
|
|
||||||
|
|
||||||
pairs := strings.Split(commandParts[1], "$$")
|
|
||||||
notifyOfUpdate := make([]string, 0, len(pairs))
|
|
||||||
|
|
||||||
nextIPPair:
|
|
||||||
for _, pair := range pairs {
|
|
||||||
parts := strings.SplitN(pair, " ", 2)
|
|
||||||
if len(parts) != 2 {
|
|
||||||
// ????
|
|
||||||
continue nextIPPair
|
|
||||||
}
|
|
||||||
|
|
||||||
ip2nick := parts[0]
|
|
||||||
ip2addr := parts[1]
|
|
||||||
|
|
||||||
uinfo, ok := this.users[ip2nick]
|
|
||||||
if !ok {
|
|
||||||
this.processEvent(HubEvent{EventType: EVENT_DEBUG_MESSAGE, Message: "Recieved IP '" + ip2addr + "' for unknown user '" + ip2nick + "'"})
|
|
||||||
continue nextIPPair
|
|
||||||
}
|
|
||||||
|
|
||||||
if uinfo.IPAddress != ip2addr {
|
|
||||||
uinfo.IPAddress = ip2addr
|
|
||||||
notifyOfUpdate = append(notifyOfUpdate, ip2nick)
|
|
||||||
this.users[ip2nick] = uinfo
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.userLock.Unlock()
|
|
||||||
|
|
||||||
for _, nick := range notifyOfUpdate {
|
|
||||||
this.processEvent(HubEvent{EventType: EVENT_USER_UPDATED_INFO, Nick: nick})
|
|
||||||
}
|
|
||||||
|
|
||||||
case "$ForceMove":
|
|
||||||
this.Hco.Address = HubAddress(commandParts[1])
|
|
||||||
this.conn.Close() // we'll reconnect onto the new address
|
|
||||||
|
|
||||||
case "$UserCommand":
|
|
||||||
// $UserCommand 1 1 Group chat\New group chat$<%[mynick]> !groupchat_new||
|
|
||||||
if rx_userCommand.MatchString(commandParts[1]) {
|
|
||||||
usc := rx_userCommand.FindStringSubmatch(commandParts[1])
|
|
||||||
|
|
||||||
typeInt, _ := strconv.Atoi(usc[1])
|
|
||||||
contextInt, _ := strconv.Atoi(usc[2])
|
|
||||||
|
|
||||||
uscStruct := UserCommand{
|
|
||||||
Type: UserCommandType(typeInt),
|
|
||||||
Context: UserCommandContext(contextInt),
|
|
||||||
Message: usc[3],
|
|
||||||
Command: Unescape(usc[4]),
|
|
||||||
}
|
|
||||||
|
|
||||||
this.processEvent(HubEvent{EventType: EVENT_USERCOMMAND, UserCommand: &uscStruct})
|
|
||||||
|
|
||||||
} else {
|
|
||||||
this.processEvent(HubEvent{EventType: EVENT_DEBUG_MESSAGE, Message: "Malformed usercommand '" + commandParts[1] + "'"})
|
|
||||||
}
|
|
||||||
|
|
||||||
case "$Supports":
|
|
||||||
this.supports = make(map[string]struct{})
|
|
||||||
for _, s := range strings.Split(commandParts[1], " ") {
|
|
||||||
this.supports[s] = struct{}{}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !this.sentOurHello {
|
|
||||||
|
|
||||||
// Need to log in.
|
|
||||||
// If the hub supports QuickList, we can skip one network roundtrip
|
|
||||||
if _, ok := this.supports["QuickList"]; ok {
|
|
||||||
this.SayInfo()
|
|
||||||
this.SayRaw("$GetNickList|")
|
|
||||||
} else {
|
|
||||||
this.SayRaw("$ValidateNick " + Escape(this.Hco.Self.Nick) + "|")
|
|
||||||
}
|
|
||||||
|
|
||||||
// This also counts as the end of the handshake from our POV. Consider
|
|
||||||
// ourselves logged in
|
|
||||||
this.sentOurHello = true
|
|
||||||
if this.State != CONNECTIONSTATE_CONNECTED {
|
|
||||||
this.processEvent(HubEvent{EventType: EVENT_CONNECTION_STATE_CHANGED, StateChange: CONNECTIONSTATE_CONNECTED})
|
|
||||||
this.State = CONNECTIONSTATE_CONNECTED
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// IGNORABLE COMMANDS
|
|
||||||
case "$HubTopic":
|
|
||||||
case "$Search":
|
|
||||||
case "$ConnectToMe":
|
|
||||||
|
|
||||||
default:
|
|
||||||
this.processEvent(HubEvent{EventType: EVENT_DEBUG_MESSAGE, Message: "Unhandled protocol command '" + commandParts[0] + "'"})
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *HubConnection) Disconnect() {
|
func (this *HubConnection) Disconnect() {
|
||||||
@ -396,7 +152,7 @@ func (this *HubConnection) worker() {
|
|||||||
err = nil
|
err = nil
|
||||||
|
|
||||||
// Send KA packet
|
// Send KA packet
|
||||||
_, err = this.conn.Write([]byte("|"))
|
err = this.proto.SayKeepalive()
|
||||||
}
|
}
|
||||||
|
|
||||||
if nbytes > 0 {
|
if nbytes > 0 {
|
||||||
@ -405,14 +161,17 @@ func (this *HubConnection) worker() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rxSeparator := regexp.QuoteMeta(this.proto.ProtoMessageSeparator())
|
||||||
|
rxProtocolMessage := regexp.MustCompile(`(?ms)\A[^` + rxSeparator + `]*` + rxSeparator)
|
||||||
|
|
||||||
// Attempt to parse a message block
|
// Attempt to parse a message block
|
||||||
for len(fullBuffer) > 0 {
|
for len(fullBuffer) > 0 {
|
||||||
for len(fullBuffer) > 0 && fullBuffer[0] == '|' {
|
for len(fullBuffer) > 0 && fullBuffer[0] == '|' {
|
||||||
fullBuffer = fullBuffer[1:]
|
fullBuffer = fullBuffer[1:]
|
||||||
}
|
}
|
||||||
protocolMessage := rx_protocolMessage.FindString(fullBuffer)
|
protocolMessage := rxProtocolMessage.FindString(fullBuffer)
|
||||||
if len(protocolMessage) > 0 {
|
if len(protocolMessage) > 0 {
|
||||||
this.processProtocolMessage(protocolMessage[:len(protocolMessage)-1])
|
this.proto.ProcessCommand(protocolMessage[:len(protocolMessage)-1])
|
||||||
fullBuffer = fullBuffer[len(protocolMessage):]
|
fullBuffer = fullBuffer[len(protocolMessage):]
|
||||||
} else {
|
} else {
|
||||||
break
|
break
|
||||||
|
413
NmdcProtocol.go
Normal file
413
NmdcProtocol.go
Normal file
@ -0,0 +1,413 @@
|
|||||||
|
package libnmdc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type NmdcProtocol struct {
|
||||||
|
hc *HubConnection
|
||||||
|
sentOurHello bool
|
||||||
|
supports map[string]struct{}
|
||||||
|
|
||||||
|
rxPublicChat *regexp.Regexp
|
||||||
|
rxIncomingTo *regexp.Regexp
|
||||||
|
rxUserCommand *regexp.Regexp
|
||||||
|
rxMyInfo *regexp.Regexp
|
||||||
|
rxMyInfoNoTag *regexp.Regexp
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Protocol = &NmdcProtocol{} // Assert that we implement the interface
|
||||||
|
|
||||||
|
func NewNmdcProtocol(hc *HubConnection) *NmdcProtocol {
|
||||||
|
proto := NmdcProtocol{}
|
||||||
|
proto.hc = hc
|
||||||
|
|
||||||
|
// With the `m` flag, use \A instead of ^ to anchor to start
|
||||||
|
// This fixes accidentally finding a better match in the middle of a multi-line message
|
||||||
|
|
||||||
|
proto.rxPublicChat = regexp.MustCompile(`(?ms)\A<([^>]*)> (.*)$`)
|
||||||
|
proto.rxIncomingTo = regexp.MustCompile(`(?ms)\A([^ ]+) From: ([^ ]+) \$<([^>]*)> (.*)`)
|
||||||
|
proto.rxUserCommand = regexp.MustCompile(`(?ms)\A(\d+) (\d+)\s?([^\$]*)\$?(.*)`)
|
||||||
|
|
||||||
|
// Format: $ALL <nick> <description>$ $<connection><flag>$<e-mail>$<sharesize>$
|
||||||
|
|
||||||
|
HEAD := `(?ms)^\$ALL ([^ ]+) `
|
||||||
|
FOOT := `\$.\$([^$]+)\$([^$]*)\$([0-9]*)\$$`
|
||||||
|
|
||||||
|
proto.rxMyInfo = regexp.MustCompile(HEAD + `([^<]*)<(.+?) V:([^,]+),M:(.),H:([0-9]+)/([0-9]+)/([0-9]+),S:([0-9]+)>` + FOOT)
|
||||||
|
proto.rxMyInfoNoTag = regexp.MustCompile(HEAD + `([^$]*)` + FOOT) // Fallback for no tag
|
||||||
|
|
||||||
|
// Done
|
||||||
|
return &proto
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *NmdcProtocol) ProcessCommand(message string) {
|
||||||
|
|
||||||
|
// Zero-length protocol message
|
||||||
|
// ````````````````````````````
|
||||||
|
if len(message) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Public chat
|
||||||
|
// ```````````
|
||||||
|
if this.rxPublicChat.MatchString(message) {
|
||||||
|
pubchat_parts := this.rxPublicChat.FindStringSubmatch(message)
|
||||||
|
this.hc.processEvent(HubEvent{EventType: EVENT_PUBLIC, Nick: pubchat_parts[1], Message: this.Unescape(pubchat_parts[2])})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// System messages
|
||||||
|
// ```````````````
|
||||||
|
if message[0] != '$' {
|
||||||
|
this.hc.processEvent(HubEvent{EventType: EVENT_SYSTEM_MESSAGE_FROM_HUB, Nick: this.hc.HubName, Message: this.Unescape(message)})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Protocol messages
|
||||||
|
// `````````````````
|
||||||
|
|
||||||
|
commandParts := strings.SplitN(message, " ", 2)
|
||||||
|
switch commandParts[0] {
|
||||||
|
|
||||||
|
case "$Lock":
|
||||||
|
this.hc.SayRaw("$Supports NoHello NoGetINFO UserCommand UserIP2 QuickList ChatOnly|" +
|
||||||
|
"$Key " + this.unlock([]byte(commandParts[1])) + "|")
|
||||||
|
this.sentOurHello = false
|
||||||
|
|
||||||
|
case "$Hello":
|
||||||
|
if commandParts[1] == this.hc.Hco.Self.Nick && !this.sentOurHello {
|
||||||
|
this.hc.SayRaw("$Version 1,0091|")
|
||||||
|
this.hc.SayRaw("$GetNickList|")
|
||||||
|
this.sayInfo()
|
||||||
|
this.sentOurHello = true
|
||||||
|
|
||||||
|
} else {
|
||||||
|
this.hc.userJoined_NameOnly(commandParts[1])
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
case "$HubName":
|
||||||
|
this.hc.HubName = commandParts[1]
|
||||||
|
this.hc.processEvent(HubEvent{EventType: EVENT_HUBNAME_CHANGED, Nick: commandParts[1]})
|
||||||
|
|
||||||
|
case "$ValidateDenide": // sic
|
||||||
|
if len(this.hc.Hco.NickPassword) > 0 {
|
||||||
|
this.hc.processEvent(HubEvent{EventType: EVENT_SYSTEM_MESSAGE_FROM_CONN, Message: "Incorrect password."})
|
||||||
|
} else {
|
||||||
|
this.hc.processEvent(HubEvent{EventType: EVENT_SYSTEM_MESSAGE_FROM_CONN, Message: "Nick already in use."})
|
||||||
|
}
|
||||||
|
|
||||||
|
case "$HubIsFull":
|
||||||
|
this.hc.processEvent(HubEvent{EventType: EVENT_SYSTEM_MESSAGE_FROM_CONN, Message: "Hub is full."})
|
||||||
|
|
||||||
|
case "$BadPass":
|
||||||
|
this.hc.processEvent(HubEvent{EventType: EVENT_SYSTEM_MESSAGE_FROM_CONN, Message: "Incorrect password."})
|
||||||
|
|
||||||
|
case "$GetPass":
|
||||||
|
if len(this.hc.Hco.NickPassword) == 0 {
|
||||||
|
// We've got a problem. MyPass with no arguments is a syntax error with no message = instant close
|
||||||
|
// Just drop the connection
|
||||||
|
this.hc.processEvent(HubEvent{EventType: EVENT_SYSTEM_MESSAGE_FROM_CONN, Message: "This account is passworded."})
|
||||||
|
this.hc.Disconnect()
|
||||||
|
} else {
|
||||||
|
this.hc.SayRaw("$MyPass " + this.Escape(this.hc.Hco.NickPassword) + "|")
|
||||||
|
}
|
||||||
|
|
||||||
|
case "$Quit":
|
||||||
|
this.hc.userLock.Lock()
|
||||||
|
delete(this.hc.users, commandParts[1])
|
||||||
|
this.hc.userLock.Unlock() // Don't lock over a processEvent boundary
|
||||||
|
|
||||||
|
this.hc.processEvent(HubEvent{EventType: EVENT_USER_PART, Nick: commandParts[1]})
|
||||||
|
|
||||||
|
case "$MyINFO":
|
||||||
|
u, err := this.parseMyINFO(commandParts[1])
|
||||||
|
if err != nil {
|
||||||
|
this.hc.processEvent(HubEvent{EventType: EVENT_DEBUG_MESSAGE, Message: err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.hc.userJoined_Full(u)
|
||||||
|
|
||||||
|
case "$NickList":
|
||||||
|
nicklist := strings.Split(commandParts[1], "$$")
|
||||||
|
for _, nick := range nicklist {
|
||||||
|
if len(nick) > 0 {
|
||||||
|
this.hc.userJoined_NameOnly(nick)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case "$OpList":
|
||||||
|
oplist := strings.Split(commandParts[1], "$$")
|
||||||
|
opmap := map[string]struct{}{}
|
||||||
|
|
||||||
|
// Organise/sort the list, and ensure we're not meeting an operator for
|
||||||
|
// the first time
|
||||||
|
for _, nick := range oplist {
|
||||||
|
if len(nick) > 0 {
|
||||||
|
opmap[nick] = struct{}{}
|
||||||
|
this.hc.userJoined_NameOnly(nick) // assert existence; noop otherwise
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark all mentioned nicks as being operators, and all unmentioned nicks
|
||||||
|
// as being /not/ an operator. (second pass minimises RW mutex use)
|
||||||
|
func() {
|
||||||
|
this.hc.userLock.Lock()
|
||||||
|
defer this.hc.userLock.Unlock()
|
||||||
|
|
||||||
|
for nick, userinfo := range this.hc.users {
|
||||||
|
_, isop := opmap[nick]
|
||||||
|
|
||||||
|
userinfo.IsOperator = isop
|
||||||
|
this.hc.users[nick] = userinfo
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
case "$To:":
|
||||||
|
valid := false
|
||||||
|
if this.rxIncomingTo.MatchString(commandParts[1]) {
|
||||||
|
txparts := this.rxIncomingTo.FindStringSubmatch(commandParts[1])
|
||||||
|
if txparts[1] == this.hc.Hco.Self.Nick && txparts[2] == txparts[3] {
|
||||||
|
this.hc.processEvent(HubEvent{EventType: EVENT_PRIVATE, Nick: txparts[2], Message: this.Unescape(txparts[4])})
|
||||||
|
valid = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !valid {
|
||||||
|
this.hc.processEvent(HubEvent{EventType: EVENT_DEBUG_MESSAGE, Message: "Malformed private message '" + commandParts[1] + "'"})
|
||||||
|
}
|
||||||
|
|
||||||
|
case "$UserIP":
|
||||||
|
this.hc.userLock.Lock()
|
||||||
|
|
||||||
|
pairs := strings.Split(commandParts[1], "$$")
|
||||||
|
notifyOfUpdate := make([]string, 0, len(pairs))
|
||||||
|
|
||||||
|
nextIPPair:
|
||||||
|
for _, pair := range pairs {
|
||||||
|
parts := strings.SplitN(pair, " ", 2)
|
||||||
|
if len(parts) != 2 {
|
||||||
|
// ????
|
||||||
|
continue nextIPPair
|
||||||
|
}
|
||||||
|
|
||||||
|
ip2nick := parts[0]
|
||||||
|
ip2addr := parts[1]
|
||||||
|
|
||||||
|
uinfo, ok := this.hc.users[ip2nick]
|
||||||
|
if !ok {
|
||||||
|
this.hc.processEvent(HubEvent{EventType: EVENT_DEBUG_MESSAGE, Message: "Recieved IP '" + ip2addr + "' for unknown user '" + ip2nick + "'"})
|
||||||
|
continue nextIPPair
|
||||||
|
}
|
||||||
|
|
||||||
|
if uinfo.IPAddress != ip2addr {
|
||||||
|
uinfo.IPAddress = ip2addr
|
||||||
|
notifyOfUpdate = append(notifyOfUpdate, ip2nick)
|
||||||
|
this.hc.users[ip2nick] = uinfo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.hc.userLock.Unlock()
|
||||||
|
|
||||||
|
for _, nick := range notifyOfUpdate {
|
||||||
|
this.hc.processEvent(HubEvent{EventType: EVENT_USER_UPDATED_INFO, Nick: nick})
|
||||||
|
}
|
||||||
|
|
||||||
|
case "$ForceMove":
|
||||||
|
this.hc.Hco.Address = HubAddress(commandParts[1])
|
||||||
|
this.hc.conn.Close() // we'll reconnect onto the new address
|
||||||
|
|
||||||
|
case "$UserCommand":
|
||||||
|
// $UserCommand 1 1 Group chat\New group chat$<%[mynick]> !groupchat_new||
|
||||||
|
if this.rxUserCommand.MatchString(commandParts[1]) {
|
||||||
|
usc := this.rxUserCommand.FindStringSubmatch(commandParts[1])
|
||||||
|
|
||||||
|
typeInt, _ := strconv.Atoi(usc[1])
|
||||||
|
contextInt, _ := strconv.Atoi(usc[2])
|
||||||
|
|
||||||
|
uscStruct := UserCommand{
|
||||||
|
Type: UserCommandType(typeInt),
|
||||||
|
Context: UserCommandContext(contextInt),
|
||||||
|
Message: usc[3],
|
||||||
|
Command: this.Unescape(usc[4]),
|
||||||
|
}
|
||||||
|
|
||||||
|
this.hc.processEvent(HubEvent{EventType: EVENT_USERCOMMAND, UserCommand: &uscStruct})
|
||||||
|
|
||||||
|
} else {
|
||||||
|
this.hc.processEvent(HubEvent{EventType: EVENT_DEBUG_MESSAGE, Message: "Malformed usercommand '" + commandParts[1] + "'"})
|
||||||
|
}
|
||||||
|
|
||||||
|
case "$Supports":
|
||||||
|
this.supports = make(map[string]struct{})
|
||||||
|
for _, s := range strings.Split(commandParts[1], " ") {
|
||||||
|
this.supports[s] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !this.sentOurHello {
|
||||||
|
|
||||||
|
// Need to log in.
|
||||||
|
// If the hub supports QuickList, we can skip one network roundtrip
|
||||||
|
if _, ok := this.supports["QuickList"]; ok {
|
||||||
|
this.sayInfo()
|
||||||
|
this.hc.SayRaw("$GetNickList|")
|
||||||
|
} else {
|
||||||
|
this.hc.SayRaw("$ValidateNick " + this.Escape(this.hc.Hco.Self.Nick) + "|")
|
||||||
|
}
|
||||||
|
|
||||||
|
// This also counts as the end of the handshake from our POV. Consider
|
||||||
|
// ourselves logged in
|
||||||
|
this.sentOurHello = true
|
||||||
|
if this.hc.State != CONNECTIONSTATE_CONNECTED {
|
||||||
|
this.hc.processEvent(HubEvent{EventType: EVENT_CONNECTION_STATE_CHANGED, StateChange: CONNECTIONSTATE_CONNECTED})
|
||||||
|
this.hc.State = CONNECTIONSTATE_CONNECTED
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// IGNORABLE COMMANDS
|
||||||
|
case "$HubTopic":
|
||||||
|
case "$Search":
|
||||||
|
case "$ConnectToMe":
|
||||||
|
|
||||||
|
default:
|
||||||
|
this.hc.processEvent(HubEvent{EventType: EVENT_DEBUG_MESSAGE, Message: "Unhandled protocol command '" + commandParts[0] + "'"})
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *NmdcProtocol) Escape(plaintext string) string {
|
||||||
|
v1 := strings.Replace(plaintext, "&", "&", -1)
|
||||||
|
v2 := strings.Replace(v1, "|", "|", -1)
|
||||||
|
return strings.Replace(v2, "$", "$", -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *NmdcProtocol) Unescape(encoded string) string {
|
||||||
|
v1 := strings.Replace(encoded, "$", "$", -1)
|
||||||
|
v2 := strings.Replace(v1, "|", "|", -1)
|
||||||
|
return strings.Replace(v2, "&", "&", -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *NmdcProtocol) SayPublic(message string) {
|
||||||
|
this.hc.SayRaw("<" + this.hc.Hco.Self.Nick + "> " + this.Escape(message) + "|")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *NmdcProtocol) SayPrivate(recipient, message string) {
|
||||||
|
this.hc.SayRaw("$To: " + recipient + " From: " + this.hc.Hco.Self.Nick + " $<" + this.hc.Hco.Self.Nick + "> " + this.Escape(message) + "|")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *NmdcProtocol) sayInfo() {
|
||||||
|
this.hc.SayRaw(this.getUserMyINFO(&this.hc.Hco.Self) + "|")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *NmdcProtocol) parseMyINFO(protomsg string) (*UserInfo, error) {
|
||||||
|
ret := UserInfo{}
|
||||||
|
|
||||||
|
// Normal format (with tag in exact V/M/H/S order)
|
||||||
|
matches := this.rxMyInfo.FindStringSubmatch(protomsg)
|
||||||
|
if matches != nil {
|
||||||
|
ret.Nick = matches[1]
|
||||||
|
ret.Description = this.Unescape(matches[2])
|
||||||
|
ret.ClientTag = this.Unescape(matches[3])
|
||||||
|
ret.ClientVersion = matches[4]
|
||||||
|
ret.ConnectionMode = ConnectionMode(matches[5][0])
|
||||||
|
maybeParse(matches[6], &ret.HubsUnregistered, 0)
|
||||||
|
maybeParse(matches[7], &ret.HubsRegistered, 0)
|
||||||
|
maybeParse(matches[8], &ret.HubsOperator, 0)
|
||||||
|
maybeParse(matches[9], &ret.Slots, 0)
|
||||||
|
if len(matches[10]) > 1 {
|
||||||
|
ret.Speed = matches[10][:len(matches[10])-2]
|
||||||
|
} else {
|
||||||
|
ret.Speed = ""
|
||||||
|
}
|
||||||
|
ret.Flag = UserFlag(matches[10][len(matches[10])-1])
|
||||||
|
ret.Email = this.Unescape(matches[11])
|
||||||
|
maybeParse(matches[12], &ret.ShareSize, 0)
|
||||||
|
|
||||||
|
return &ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// No-tag format, used in early connection
|
||||||
|
matches = this.rxMyInfoNoTag.FindStringSubmatch(protomsg)
|
||||||
|
if matches != nil {
|
||||||
|
ret.Nick = matches[1]
|
||||||
|
ret.Description = this.Unescape(matches[2])
|
||||||
|
ret.ClientTag = ""
|
||||||
|
ret.ClientVersion = "0"
|
||||||
|
ret.ConnectionMode = CONNECTIONMODE_PASSIVE
|
||||||
|
ret.HubsUnregistered = 0
|
||||||
|
ret.HubsRegistered = 0
|
||||||
|
ret.HubsOperator = 0
|
||||||
|
ret.Slots = 0
|
||||||
|
|
||||||
|
if len(matches[3]) > 1 {
|
||||||
|
ret.Speed = matches[3][:len(matches[3])-2]
|
||||||
|
} else {
|
||||||
|
ret.Speed = ""
|
||||||
|
}
|
||||||
|
ret.Flag = UserFlag(matches[3][len(matches[3])-1])
|
||||||
|
ret.Email = this.Unescape(matches[4])
|
||||||
|
maybeParse(matches[5], &ret.ShareSize, 0)
|
||||||
|
|
||||||
|
return &ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Couldn't get anything out of it...
|
||||||
|
return nil, errors.New("Malformed MyINFO")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the MyINFO command, WITH leading $MyINFO, and WITHOUT trailing pipe
|
||||||
|
func (this *NmdcProtocol) getUserMyINFO(u *UserInfo) string {
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"$MyINFO $ALL %s %s<%s V:%s,M:%c,H:%d/%d/%d,S:%d>$ $%s%c$%s$%d$",
|
||||||
|
u.Nick,
|
||||||
|
u.Description,
|
||||||
|
u.ClientTag,
|
||||||
|
strings.Replace(u.ClientVersion, ",", "-", -1), // just in case
|
||||||
|
u.ConnectionMode,
|
||||||
|
u.HubsUnregistered,
|
||||||
|
u.HubsRegistered,
|
||||||
|
u.HubsOperator,
|
||||||
|
u.Slots,
|
||||||
|
u.Speed,
|
||||||
|
u.Flag,
|
||||||
|
u.Email,
|
||||||
|
u.ShareSize,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *NmdcProtocol) SayKeepalive() error {
|
||||||
|
return this.hc.SayRaw("|")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *NmdcProtocol) ProtoMessageSeparator() string {
|
||||||
|
return "|"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *NmdcProtocol) unlock(lock []byte) string {
|
||||||
|
|
||||||
|
nibble_swap := func(b byte) byte {
|
||||||
|
return ((b << 4) & 0xF0) | ((b >> 4) & 0x0F)
|
||||||
|
}
|
||||||
|
|
||||||
|
chr := func(b byte) string {
|
||||||
|
if b == 0 || b == 5 || b == 36 || b == 96 || b == 124 || b == 126 {
|
||||||
|
return fmt.Sprintf("/%%DCN%04d%%/", b)
|
||||||
|
} else {
|
||||||
|
return string(b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
key := chr(nibble_swap(lock[0] ^ lock[len(lock)-2] ^ lock[len(lock)-3] ^ 5))
|
||||||
|
for i := 1; i < len(lock); i += 1 {
|
||||||
|
key += chr(nibble_swap(lock[i] ^ lock[i-1]))
|
||||||
|
}
|
||||||
|
|
||||||
|
return key
|
||||||
|
}
|
17
Protocol.go
Normal file
17
Protocol.go
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package libnmdc
|
||||||
|
|
||||||
|
type Protocol interface {
|
||||||
|
ProcessCommand(msg string)
|
||||||
|
|
||||||
|
Escape(string) string
|
||||||
|
|
||||||
|
Unescape(string) string
|
||||||
|
|
||||||
|
SayPublic(string)
|
||||||
|
|
||||||
|
SayPrivate(user, message string)
|
||||||
|
|
||||||
|
SayKeepalive() error
|
||||||
|
|
||||||
|
ProtoMessageSeparator() string
|
||||||
|
}
|
105
UserInfo.go
105
UserInfo.go
@ -1,13 +1,5 @@
|
|||||||
package libnmdc
|
package libnmdc
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"regexp"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// This structure represents a user connected to a hub.
|
// This structure represents a user connected to a hub.
|
||||||
type UserInfo struct {
|
type UserInfo struct {
|
||||||
Nick string
|
Nick string
|
||||||
@ -27,19 +19,6 @@ type UserInfo struct {
|
|||||||
IPAddress string
|
IPAddress string
|
||||||
}
|
}
|
||||||
|
|
||||||
var rx_myinfo *regexp.Regexp
|
|
||||||
var rx_myinfo_notag *regexp.Regexp
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
// Format: $ALL <nick> <description>$ $<connection><flag>$<e-mail>$<sharesize>$
|
|
||||||
|
|
||||||
HEAD := `(?ms)^\$ALL ([^ ]+) `
|
|
||||||
FOOT := `\$.\$([^$]+)\$([^$]*)\$([0-9]*)\$$`
|
|
||||||
|
|
||||||
rx_myinfo = regexp.MustCompile(HEAD + `([^<]*)<(.+?) V:([^,]+),M:(.),H:([0-9]+)/([0-9]+)/([0-9]+),S:([0-9]+)>` + FOOT)
|
|
||||||
rx_myinfo_notag = regexp.MustCompile(HEAD + `([^$]*)` + FOOT) // Fallback for no tag
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewUserInfo(username string) *UserInfo {
|
func NewUserInfo(username string) *UserInfo {
|
||||||
return &UserInfo{
|
return &UserInfo{
|
||||||
Nick: username,
|
Nick: username,
|
||||||
@ -47,87 +26,3 @@ func NewUserInfo(username string) *UserInfo {
|
|||||||
HubsUnregistered: 1,
|
HubsUnregistered: 1,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func maybeParse(str string, dest *uint64, default_val uint64) {
|
|
||||||
sz, err := strconv.ParseUint(str, 10, 64)
|
|
||||||
if err == nil {
|
|
||||||
*dest = sz
|
|
||||||
} else {
|
|
||||||
*dest = default_val
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *UserInfo) fromMyINFO(protomsg string) error {
|
|
||||||
|
|
||||||
// Normal format (with tag in exact V/M/H/S order)
|
|
||||||
matches := rx_myinfo.FindStringSubmatch(protomsg)
|
|
||||||
if matches != nil {
|
|
||||||
this.Nick = matches[1]
|
|
||||||
this.Description = Unescape(matches[2])
|
|
||||||
this.ClientTag = Unescape(matches[3])
|
|
||||||
this.ClientVersion = matches[4]
|
|
||||||
this.ConnectionMode = ConnectionMode(matches[5][0])
|
|
||||||
maybeParse(matches[6], &this.HubsUnregistered, 0)
|
|
||||||
maybeParse(matches[7], &this.HubsRegistered, 0)
|
|
||||||
maybeParse(matches[8], &this.HubsOperator, 0)
|
|
||||||
maybeParse(matches[9], &this.Slots, 0)
|
|
||||||
if len(matches[10]) > 1 {
|
|
||||||
this.Speed = matches[10][:len(matches[10])-2]
|
|
||||||
} else {
|
|
||||||
this.Speed = ""
|
|
||||||
}
|
|
||||||
this.Flag = UserFlag(matches[10][len(matches[10])-1])
|
|
||||||
this.Email = Unescape(matches[11])
|
|
||||||
maybeParse(matches[12], &this.ShareSize, 0)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// No-tag format, used in early connection
|
|
||||||
matches = rx_myinfo_notag.FindStringSubmatch(protomsg)
|
|
||||||
if matches != nil {
|
|
||||||
this.Nick = matches[1]
|
|
||||||
this.Description = Unescape(matches[2])
|
|
||||||
this.ClientTag = ""
|
|
||||||
this.ClientVersion = "0"
|
|
||||||
this.ConnectionMode = CONNECTIONMODE_PASSIVE
|
|
||||||
this.HubsUnregistered = 0
|
|
||||||
this.HubsRegistered = 0
|
|
||||||
this.HubsOperator = 0
|
|
||||||
this.Slots = 0
|
|
||||||
|
|
||||||
if len(matches[3]) > 1 {
|
|
||||||
this.Speed = matches[3][:len(matches[3])-2]
|
|
||||||
} else {
|
|
||||||
this.Speed = ""
|
|
||||||
}
|
|
||||||
this.Flag = UserFlag(matches[3][len(matches[3])-1])
|
|
||||||
this.Email = Unescape(matches[4])
|
|
||||||
maybeParse(matches[5], &this.ShareSize, 0)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Couldn't get anything out of it...
|
|
||||||
return errors.New("Malformed MyINFO")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns the MyINFO command, WITH leading $MyINFO, and WITHOUT trailing pipe
|
|
||||||
func (this *UserInfo) toMyINFO() string {
|
|
||||||
return fmt.Sprintf(
|
|
||||||
"$MyINFO $ALL %s %s<%s V:%s,M:%c,H:%d/%d/%d,S:%d>$ $%s%c$%s$%d$",
|
|
||||||
this.Nick,
|
|
||||||
this.Description,
|
|
||||||
this.ClientTag,
|
|
||||||
strings.Replace(this.ClientVersion, ",", "-", -1), // just in case
|
|
||||||
this.ConnectionMode,
|
|
||||||
this.HubsUnregistered,
|
|
||||||
this.HubsRegistered,
|
|
||||||
this.HubsOperator,
|
|
||||||
this.Slots,
|
|
||||||
this.Speed,
|
|
||||||
this.Flag,
|
|
||||||
this.Email,
|
|
||||||
this.ShareSize,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
57
libnmdc.go
57
libnmdc.go
@ -2,9 +2,7 @@ package libnmdc
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"strconv"
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -17,52 +15,21 @@ const (
|
|||||||
RECONNECT_IF_NO_DATA_RECIEVED_IN time.Duration = 24 * time.Hour // we expect keepalives wayyyy more frequently than this
|
RECONNECT_IF_NO_DATA_RECIEVED_IN time.Duration = 24 * time.Hour // we expect keepalives wayyyy more frequently than this
|
||||||
)
|
)
|
||||||
|
|
||||||
var rx_protocolMessage *regexp.Regexp
|
|
||||||
var rx_publicChat *regexp.Regexp
|
|
||||||
var rx_incomingTo *regexp.Regexp
|
|
||||||
var rx_userCommand *regexp.Regexp
|
|
||||||
var ErrNotConnected error = errors.New("Not connected")
|
var ErrNotConnected error = errors.New("Not connected")
|
||||||
|
|
||||||
func init() {
|
type HubProtocol int
|
||||||
// With the `m` flag, use \A instead of ^ to anchor to start
|
|
||||||
// This fixes accidentally finding a better match in the middle of a multi-line message
|
|
||||||
|
|
||||||
rx_protocolMessage = regexp.MustCompile(`(?ms)\A[^|]*\|`)
|
const (
|
||||||
rx_publicChat = regexp.MustCompile(`(?ms)\A<([^>]*)> (.*)$`)
|
HubProtocolAutodetect = 0
|
||||||
rx_incomingTo = regexp.MustCompile(`(?ms)\A([^ ]+) From: ([^ ]+) \$<([^>]*)> (.*)`)
|
HubProtocolNmdc = 1
|
||||||
rx_userCommand = regexp.MustCompile(`(?ms)\A(\d+) (\d+)\s?([^\$]*)\$?(.*)`)
|
HubProtocolAdc = 2
|
||||||
}
|
)
|
||||||
|
|
||||||
func Unescape(encoded string) string {
|
func maybeParse(str string, dest *uint64, default_val uint64) {
|
||||||
v1 := strings.Replace(encoded, "$", "$", -1)
|
sz, err := strconv.ParseUint(str, 10, 64)
|
||||||
v2 := strings.Replace(v1, "|", "|", -1)
|
if err == nil {
|
||||||
return strings.Replace(v2, "&", "&", -1)
|
*dest = sz
|
||||||
}
|
|
||||||
|
|
||||||
func Escape(plaintext string) string {
|
|
||||||
v1 := strings.Replace(plaintext, "&", "&", -1)
|
|
||||||
v2 := strings.Replace(v1, "|", "|", -1)
|
|
||||||
return strings.Replace(v2, "$", "$", -1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func unlock(lock []byte) string {
|
|
||||||
|
|
||||||
nibble_swap := func(b byte) byte {
|
|
||||||
return ((b << 4) & 0xF0) | ((b >> 4) & 0x0F)
|
|
||||||
}
|
|
||||||
|
|
||||||
chr := func(b byte) string {
|
|
||||||
if b == 0 || b == 5 || b == 36 || b == 96 || b == 124 || b == 126 {
|
|
||||||
return fmt.Sprintf("/%%DCN%04d%%/", b)
|
|
||||||
} else {
|
} else {
|
||||||
return string(b)
|
*dest = default_val
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
key := chr(nibble_swap(lock[0] ^ lock[len(lock)-2] ^ lock[len(lock)-3] ^ 5))
|
|
||||||
for i := 1; i < len(lock); i += 1 {
|
|
||||||
key += chr(nibble_swap(lock[i] ^ lock[i-1]))
|
|
||||||
}
|
|
||||||
|
|
||||||
return key
|
|
||||||
}
|
|
||||||
|
Loading…
Reference in New Issue
Block a user