361 lines
8.0 KiB
Go
361 lines
8.0 KiB
Go
package libnmdc
|
|
|
|
import (
|
|
"encoding/base32"
|
|
"fmt"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
type adcState int
|
|
|
|
const (
|
|
adcStateProtocol adcState = 0
|
|
adcStateIdentify adcState = 1
|
|
adcStateVerify adcState = 2
|
|
adcStateNormal adcState = 3
|
|
adcStateData adcState = 4
|
|
)
|
|
|
|
type AdcProtocol struct {
|
|
hc *HubConnection
|
|
state adcState
|
|
sid, pid, cid string // all in base32 encoding
|
|
supports map[string]struct{}
|
|
}
|
|
|
|
const (
|
|
// extra extensions that aren't flagged in SUPPORTS
|
|
adcSeparateApVe string = "SEPARATE_AP_VE" // we invented this string
|
|
)
|
|
|
|
func NewAdcProtocol(hc *HubConnection) Protocol {
|
|
proto := AdcProtocol{
|
|
hc: hc,
|
|
state: adcStateProtocol,
|
|
supports: make(map[string]struct{}),
|
|
}
|
|
|
|
rxPid := regexp.MustCompile("^[A-Z2-7]{39}$")
|
|
if !rxPid.MatchString(hc.Hco.AdcPID) {
|
|
hc.processEvent(HubEvent{EventType: EVENT_SYSTEM_MESSAGE_FROM_CONN, Message: "Invalid custom PID, regenerating"})
|
|
hc.Hco.AdcPID = NewPID()
|
|
}
|
|
|
|
pid_base32 := hc.Hco.AdcPID
|
|
cid_base32, err := proto.pid2cid(pid_base32)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
proto.cid = cid_base32
|
|
proto.pid = pid_base32
|
|
|
|
// Start logging in
|
|
hc.SayRaw("HSUP ADBASE ADTIGR\n")
|
|
|
|
return &proto
|
|
}
|
|
|
|
func (this *AdcProtocol) pid2cid(pid_base32 string) (string, error) {
|
|
|
|
pid_raw, err := base32.StdEncoding.DecodeString(pid_base32 + "=")
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
cid_raw := Tiger(string(pid_raw))
|
|
cid_base32 := Base32(cid_raw)
|
|
|
|
return cid_base32, nil
|
|
}
|
|
|
|
func (this *AdcProtocol) ProcessCommand(msg string) {
|
|
|
|
if len(msg) == 0 {
|
|
return
|
|
}
|
|
|
|
parts := strings.Split(msg, " ")
|
|
switch parts[0] {
|
|
|
|
case "ISUP":
|
|
if !(this.state == adcStateProtocol || this.state == adcStateNormal) {
|
|
this.malformed(parts)
|
|
return
|
|
}
|
|
for _, supportflag := range parts[1:] {
|
|
if len(supportflag) < 2 {
|
|
this.malformed(parts)
|
|
return
|
|
}
|
|
|
|
if supportflag[0:2] == "AD" {
|
|
this.supports[supportflag[2:]] = struct{}{}
|
|
} else if supportflag[0:2] == "RM" {
|
|
delete(this.supports, supportflag[2:])
|
|
} else {
|
|
this.malformed(parts)
|
|
return
|
|
}
|
|
}
|
|
if this.state == adcStateProtocol {
|
|
this.state = adcStateIdentify
|
|
}
|
|
|
|
case "ISID":
|
|
if this.state != adcStateIdentify {
|
|
this.malformed(parts)
|
|
return
|
|
}
|
|
this.sid = parts[1]
|
|
|
|
case "IINF":
|
|
flags := make(map[string]string)
|
|
for _, flag := range parts[1:] {
|
|
if len(flag) < 2 {
|
|
this.malformed(parts)
|
|
return
|
|
}
|
|
flags[flag[0:2]] = flag[2:]
|
|
}
|
|
|
|
// Possibilities: User MyINFO (Normal state), or, hub telling information about itself (Identify->Normal state)
|
|
if (this.state == adcStateIdentify || this.state == adcStateVerify) && flags["CT"] == "32" {
|
|
// Hub telling information about itself
|
|
this.handleInfo(flags)
|
|
|
|
// Transition to state VERIFY and send our own info
|
|
this.hc.SayRaw("BINF " + this.escape(this.sid) + " " + this.ourINFO(true) + "\n")
|
|
this.state = adcStateVerify
|
|
|
|
} else if this.state == adcStateNormal {
|
|
// OK
|
|
this.handleInfo(flags)
|
|
|
|
} else {
|
|
this.malformed(parts)
|
|
return
|
|
}
|
|
|
|
case "ISTA":
|
|
// Message from the hub
|
|
if len(parts) < 3 {
|
|
this.malformed(parts)
|
|
return
|
|
}
|
|
|
|
code, _ := strconv.Atoi(parts[1])
|
|
msg := this.unescape(parts[2])
|
|
this.hc.processEvent(HubEvent{EventType: EVENT_SYSTEM_MESSAGE_FROM_HUB, Message: this.ErrorMessage(code, msg)})
|
|
|
|
case "IGPA":
|
|
//
|
|
|
|
default:
|
|
this.hc.processEvent(HubEvent{EventType: EVENT_DEBUG_MESSAGE, Message: msg})
|
|
this.malformed(parts)
|
|
}
|
|
}
|
|
|
|
func (this *AdcProtocol) infoFlagsFor(u *UserInfo) map[string]string {
|
|
parts := map[string]string{
|
|
"NI": u.Nick,
|
|
"SS": fmt.Sprintf("%d", u.ShareSize),
|
|
"SF": fmt.Sprintf("%d", u.SharedFiles),
|
|
"US": fmt.Sprintf("%d", u.UploadSpeedBps),
|
|
"DS": fmt.Sprintf("%d", u.DownloadSpeedBps),
|
|
"SL": fmt.Sprintf("%d", u.Slots),
|
|
"FS": fmt.Sprintf("%d", u.Slots), // Free slots - NOT IN ADC DOCUMENTATION
|
|
"HN": fmt.Sprintf("%d", u.HubsUnregistered),
|
|
"HR": fmt.Sprintf("%d", u.HubsRegistered),
|
|
"HO": fmt.Sprintf("%d", u.HubsOperator),
|
|
}
|
|
|
|
if _, ok := this.supports[adcSeparateApVe]; ok {
|
|
parts["AP"] = u.ClientTag
|
|
parts["VE"] = u.ClientVersion
|
|
} else {
|
|
parts["VE"] = fmt.Sprintf("%s %s", u.ClientVersion, u.ClientTag)
|
|
}
|
|
|
|
ct := 0 // 1=bot, 2=registered user, 4=operator, 8=super user, 16=hub owner, 32=hub
|
|
if u.IsBot {
|
|
ct |= 1
|
|
}
|
|
if u.IsRegistered {
|
|
ct |= 2
|
|
}
|
|
if u.IsOperator {
|
|
ct |= 4
|
|
}
|
|
if u.IsSuperUser {
|
|
ct |= 8
|
|
}
|
|
if u.IsHubOwner {
|
|
ct |= 16
|
|
}
|
|
parts["CT"] = fmt.Sprintf("%d", ct)
|
|
|
|
return parts
|
|
}
|
|
|
|
func (this *AdcProtocol) ourINFO(includePid bool) string {
|
|
parts := this.infoFlagsFor(this.hc.Hco.Self)
|
|
parts["ID"] = this.cid
|
|
if includePid {
|
|
parts["PD"] = this.pid
|
|
}
|
|
|
|
ret := ""
|
|
for k, v := range parts {
|
|
ret += " " + k + this.escape(v)
|
|
}
|
|
return ret[1:]
|
|
}
|
|
|
|
func (this *AdcProtocol) handleInfo(flags map[string]string) {
|
|
if flags["CT"] == "32" {
|
|
|
|
// IINF DEADCH++\sTest\shub VE2.12.1\s(r"[unknown]")\sRelease HI1 NIADCH++ APADCH++ CT32
|
|
// AP: extension 3.24 "Application and version separation in INF"
|
|
// HI:
|
|
|
|
// Hub properties updated
|
|
|
|
// Special SUPPORT that is only indicated in IINF
|
|
if _, ok := flags["AP"]; ok {
|
|
this.supports[adcSeparateApVe] = struct{}{}
|
|
}
|
|
|
|
hubName, ok := flags["DE"]
|
|
if ok {
|
|
this.hc.HubName = this.unescape(hubName)
|
|
this.hc.processEvent(HubEvent{EventType: EVENT_HUBNAME_CHANGED, Nick: this.hc.HubName})
|
|
}
|
|
|
|
} else {
|
|
// User MyINFO
|
|
// TODO
|
|
|
|
}
|
|
}
|
|
|
|
func (this *AdcProtocol) enterNormalState() {
|
|
this.state = adcStateNormal
|
|
this.hc.processEvent(HubEvent{EventType: EVENT_CONNECTION_STATE_CHANGED, StateChange: CONNECTIONSTATE_CONNECTED})
|
|
this.hc.State = CONNECTIONSTATE_CONNECTED
|
|
}
|
|
|
|
func (this *AdcProtocol) malformed(parts []string) {
|
|
this.hc.processEvent(HubEvent{EventType: EVENT_DEBUG_MESSAGE, Message: "Ignoring malformed, unhandled, or out-of-state protocol command '" + parts[0] + "'"})
|
|
}
|
|
|
|
func (this *AdcProtocol) escape(plaintext string) string {
|
|
// The string "\s" escapes space, "\n" newline and "\\" backslash. This version of the protocol reserves all other escapes for future use; any message containing unknown escapes must be discarded.
|
|
v1 := strings.Replace(plaintext, `\`, `\\`, -1)
|
|
v2 := strings.Replace(v1, "\n", `\n`, -1)
|
|
return strings.Replace(v2, " ", `\s`, -1)
|
|
}
|
|
|
|
func (this *AdcProtocol) unescape(encoded string) string {
|
|
v1 := strings.Replace(encoded, `\s`, " ", -1)
|
|
v2 := strings.Replace(v1, `\n`, "\n", -1)
|
|
return strings.Replace(v2, `\\`, `\`, -1)
|
|
}
|
|
|
|
func (this *AdcProtocol) SayPublic(msg string) {
|
|
}
|
|
|
|
func (this *AdcProtocol) SayPrivate(user, message string) {
|
|
}
|
|
|
|
func (this *AdcProtocol) ProtoMessageSeparator() string {
|
|
return "\n"
|
|
}
|
|
|
|
func (this *AdcProtocol) ErrorMessage(code int, msg string) string {
|
|
severity := code / 100
|
|
category := (code % 100) / 10
|
|
cat_sub := (code % 100)
|
|
|
|
formatSeverity := func(severity int) string {
|
|
switch severity {
|
|
case 0:
|
|
return "OK"
|
|
case 1:
|
|
return "Warning"
|
|
case 2:
|
|
return "Error"
|
|
default:
|
|
return ""
|
|
}
|
|
}
|
|
|
|
formatCategory := func(category int) string {
|
|
switch category {
|
|
case 0:
|
|
return ""
|
|
case 1:
|
|
return "Hub not accepting users"
|
|
case 2:
|
|
return "Login failed"
|
|
case 3:
|
|
return "Access denied"
|
|
case 4:
|
|
return "Protocol error"
|
|
case 5:
|
|
return "Transfer error"
|
|
default:
|
|
return ""
|
|
}
|
|
}
|
|
|
|
formatCatSub := func(cat_sub int) string {
|
|
switch cat_sub {
|
|
case 11:
|
|
return "Hub is full"
|
|
case 12:
|
|
return "Hub is disabled"
|
|
case 21:
|
|
return "Invalid nick"
|
|
case 22:
|
|
return "Nick is already in use"
|
|
case 23:
|
|
return "Invalid password"
|
|
case 24:
|
|
return "CID already connected"
|
|
case 25:
|
|
return "Access denied"
|
|
case 26:
|
|
return "Registered users only"
|
|
case 27:
|
|
return "Invalid PID"
|
|
case 31:
|
|
return "Permanently banned"
|
|
case 32:
|
|
return "Temporarily banned"
|
|
default:
|
|
return ""
|
|
}
|
|
}
|
|
|
|
parts := make([]string, 0, 4)
|
|
if fs := formatSeverity(severity); len(fs) > 0 {
|
|
parts = append(parts, fs)
|
|
}
|
|
if fc := formatCategory(category); len(fc) > 0 {
|
|
parts = append(parts, fc)
|
|
}
|
|
if fcs := formatCatSub(cat_sub); len(fcs) > 0 {
|
|
parts = append(parts, fcs)
|
|
}
|
|
if len(msg) > 0 {
|
|
parts = append(parts, msg)
|
|
}
|
|
|
|
return strings.Join(parts, ": ") + fmt.Sprintf(" (code %d)", code)
|
|
|
|
}
|