2017-11-22 06:11:40 +00:00
package libnmdc
2017-11-23 05:23:39 +00:00
import (
2017-11-26 00:30:19 +00:00
"encoding/base32"
"fmt"
"regexp"
"strconv"
2017-11-23 05:23:39 +00:00
"strings"
)
type adcState int
const (
adcStateProtocol adcState = 0
adcStateIdentify adcState = 1
adcStateVerify adcState = 2
adcStateNormal adcState = 3
adcStateData adcState = 4
)
2017-11-22 06:11:40 +00:00
type AdcProtocol struct {
2017-11-26 00:30:19 +00:00
hc * HubConnection
state adcState
sid , pid , cid string // all in base32 encoding
supports map [ string ] struct { }
2017-11-22 06:11:40 +00:00
}
2017-11-26 00:30:19 +00:00
const (
// extra extensions that aren't flagged in SUPPORTS
adcSeparateApVe string = "SEPARATE_AP_VE" // we invented this string
)
2017-11-22 07:02:11 +00:00
func NewAdcProtocol ( hc * HubConnection ) Protocol {
2017-11-23 05:23:39 +00:00
proto := AdcProtocol {
hc : hc ,
state : adcStateProtocol ,
supports : make ( map [ string ] struct { } ) ,
}
2017-11-22 06:11:40 +00:00
2017-11-26 00:30:19 +00:00
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
2017-11-22 07:02:21 +00:00
// Start logging in
hc . SayRaw ( "HSUP ADBASE ADTIGR\n" )
2017-11-22 06:22:13 +00:00
2017-11-22 06:11:40 +00:00
return & proto
}
2017-11-26 00:30:19 +00:00
func ( this * AdcProtocol ) pid2cid ( pid_base32 string ) ( string , error ) {
pid_raw , err := base32 . StdEncoding . DecodeString ( pid_base32 + "=" )
if err != nil {
return "" , err
}
2017-11-26 01:01:46 +00:00
cid_raw := Tiger ( string ( pid_raw ) )
2017-11-26 00:30:19 +00:00
cid_base32 := Base32 ( cid_raw )
return cid_base32 , nil
}
2017-11-22 06:11:40 +00:00
func ( this * AdcProtocol ) ProcessCommand ( msg string ) {
2017-11-23 05:23:39 +00:00
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
2017-11-26 00:30:19 +00:00
this . hc . SayRaw ( "BINF " + this . escape ( this . sid ) + " " + this . ourINFO ( true ) + "\n" )
2017-11-23 05:23:39 +00:00
this . state = adcStateVerify
} else if this . state == adcStateNormal {
// OK
this . handleInfo ( flags )
} else {
this . malformed ( parts )
return
}
2017-11-26 00:30:19 +00:00
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 ) } )
2017-11-23 05:23:39 +00:00
case "IGPA" :
//
default :
2017-11-26 01:01:46 +00:00
this . hc . processEvent ( HubEvent { EventType : EVENT_DEBUG_MESSAGE , Message : msg } )
2017-11-23 05:23:39 +00:00
this . malformed ( parts )
}
}
2017-11-26 00:30:19 +00:00
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 : ]
}
2017-11-23 05:23:39 +00:00
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
2017-11-26 00:30:19 +00:00
// Special SUPPORT that is only indicated in IINF
if _ , ok := flags [ "AP" ] ; ok {
this . supports [ adcSeparateApVe ] = struct { } { }
}
2017-11-23 05:23:39 +00:00
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 )
2017-11-22 06:11:40 +00:00
}
func ( this * AdcProtocol ) SayPublic ( msg string ) {
}
func ( this * AdcProtocol ) SayPrivate ( user , message string ) {
}
func ( this * AdcProtocol ) ProtoMessageSeparator ( ) string {
return "\n"
}
2017-11-26 00:30:19 +00:00
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 )
}