adc: PID/CID, info generation+parsing, decode adc error messages

--HG--
branch : adc
This commit is contained in:
mappu 2017-11-26 13:30:19 +13:00
parent a3963f196a
commit 3dc4ede0d8

View File

@ -1,6 +1,10 @@
package libnmdc package libnmdc
import ( import (
"encoding/base32"
"fmt"
"regexp"
"strconv"
"strings" "strings"
) )
@ -17,10 +21,15 @@ const (
type AdcProtocol struct { type AdcProtocol struct {
hc *HubConnection hc *HubConnection
state adcState state adcState
sid string sid, pid, cid string // all in base32 encoding
supports map[string]struct{} 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 { func NewAdcProtocol(hc *HubConnection) Protocol {
proto := AdcProtocol{ proto := AdcProtocol{
hc: hc, hc: hc,
@ -28,12 +37,53 @@ func NewAdcProtocol(hc *HubConnection) Protocol {
supports: make(map[string]struct{}), 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 // Start logging in
hc.SayRaw("HSUP ADBASE ADTIGR\n") hc.SayRaw("HSUP ADBASE ADTIGR\n")
return &proto return &proto
} }
func (this *AdcProtocol) pid2cid(pid_base32 string) (string, error) {
/*
Generate random data and store it in PID_raw, then;
PID = Base32( PID_raw )
CID = Base32( Hash( PID_raw ) )
For GPA/PAS, assuming that '12345' is the random data supplied in GPA, then;
PAS = Base32( Hash( password + '12345' ) )
*/
pid_raw, err := base32.StdEncoding.DecodeString(pid_base32 + "=")
if err != nil {
return "", err
}
cid_raw, err := TTH(string(pid_raw))
if err != nil {
return "", err
}
cid_base32 := Base32(cid_raw)
return cid_base32, nil
}
func (this *AdcProtocol) ProcessCommand(msg string) { func (this *AdcProtocol) ProcessCommand(msg string) {
this.hc.processEvent(HubEvent{EventType: EVENT_DEBUG_MESSAGE, Message: msg}) this.hc.processEvent(HubEvent{EventType: EVENT_DEBUG_MESSAGE, Message: msg})
@ -91,7 +141,7 @@ func (this *AdcProtocol) ProcessCommand(msg string) {
this.handleInfo(flags) this.handleInfo(flags)
// Transition to state VERIFY and send our own info // Transition to state VERIFY and send our own info
this.hc.SayRaw("BINF " + this.escape(this.sid) + " GARBAGE") // FIXME send a real info string this.hc.SayRaw("BINF " + this.escape(this.sid) + " " + this.ourINFO(true) + "\n")
this.state = adcStateVerify this.state = adcStateVerify
} else if this.state == adcStateNormal { } else if this.state == adcStateNormal {
@ -103,6 +153,17 @@ func (this *AdcProtocol) ProcessCommand(msg string) {
return 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": case "IGPA":
// //
@ -111,6 +172,62 @@ func (this *AdcProtocol) ProcessCommand(msg string) {
} }
} }
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) { func (this *AdcProtocol) handleInfo(flags map[string]string) {
if flags["CT"] == "32" { if flags["CT"] == "32" {
@ -119,6 +236,12 @@ func (this *AdcProtocol) handleInfo(flags map[string]string) {
// HI: // HI:
// Hub properties updated // Hub properties updated
// Special SUPPORT that is only indicated in IINF
if _, ok := flags["AP"]; ok {
this.supports[adcSeparateApVe] = struct{}{}
}
hubName, ok := flags["DE"] hubName, ok := flags["DE"]
if ok { if ok {
this.hc.HubName = this.unescape(hubName) this.hc.HubName = this.unescape(hubName)
@ -164,3 +287,87 @@ func (this *AdcProtocol) SayPrivate(user, message string) {
func (this *AdcProtocol) ProtoMessageSeparator() string { func (this *AdcProtocol) ProtoMessageSeparator() string {
return "\n" 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)
}