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) }