Compare commits

...

102 Commits

Author SHA1 Message Date
mappu 36c53f6042 doc: changelog for recent updates 2018-12-31 18:43:28 +13:00
mappu ee30e12d47 doc: top-level README 2018-12-31 18:41:42 +13:00
mappu a94264e91b doc: remove TODO (moved to Gitea Issues) 2018-12-31 18:39:09 +13:00
mappu bb98695ec6 convert to Go Modules 2018-12-31 18:38:26 +13:00
mappu 315149ea20 hg2git: convert ignores/tags files 2018-12-31 18:38:19 +13:00
mappu 74c20aaed6 nmdc: add extra EVENT_BAD_LOGIN_FAILURE for bad pass, hub-full etc 2018-06-04 19:28:08 +12:00
mappu 9eddfc6e58 return errors from saypublic/sayprivate et al 2018-06-04 16:27:34 +12:00
mappu 444854e586 bump default version to 0.18 2018-03-24 16:52:46 +13:00
mappu faa0645ddf Added tag v0.17.0 for changeset 800d6c415815 2018-03-24 16:51:25 +13:00
mappu b58bed9c3c doc: add 0.17 changelog 2018-03-24 16:51:19 +13:00
mappu 0a8a7a62ce re-add SayInfo() support, for changing tag/share-size on an active connection 2018-03-24 13:21:49 +13:00
mappu 92f38b4464 bump default version to 0.17 2017-11-26 19:36:20 +13:00
mappu d7ccbf1cb0 Added tag v0.16.0 for changeset a48811ff2cfe 2017-11-26 19:36:10 +13:00
mappu a23cc9e61d doc: add features section 2017-11-26 19:35:13 +13:00
mappu 8730275115 doc: add 0.16.0 changelog 2017-11-26 19:34:35 +13:00
mappu e0ca75ae9a Merge with adc 2017-11-26 19:19:18 +13:00
mappu 4c32cb965e adc: usercommand support
--HG--
branch : adc
2017-11-26 19:19:06 +13:00
mappu 882f60f157 adc: support incremental user updates
--HG--
branch : adc
2017-11-26 18:38:03 +13:00
mappu f0e5270458 adc: fix wrong client tag ordering
--HG--
branch : adc
2017-11-26 18:11:32 +13:00
mappu 2610003cdb adc: working public and private messages
--HG--
branch : adc
2017-11-26 18:11:25 +13:00
mappu 433bf712b9 adc: ignore search requests
--HG--
branch : adc
2017-11-26 17:54:56 +13:00
mappu 9e2ce27f08 adc: fix length detection in password challenge nonce
--HG--
branch : adc
2017-11-26 17:53:24 +13:00
mappu d392cd379d adc: handle user parts
--HG--
branch : adc
2017-11-26 17:53:14 +13:00
mappu 916874e8bd adc: handle passworded logins, more error message types
--HG--
branch : adc
2017-11-26 17:24:08 +13:00
mappu 4c70900ef5 doc: TODO
--HG--
branch : adc
2017-11-26 14:55:34 +13:00
mappu a5b773952c adc: parse user info messages, user<-->sid mapping
--HG--
branch : adc
2017-11-26 14:55:30 +13:00
mappu b1c6a5f56a adc: working PID/CID login
--HG--
branch : adc
2017-11-26 14:01:46 +13:00
mappu 3dc4ede0d8 adc: PID/CID, info generation+parsing, decode adc error messages
--HG--
branch : adc
2017-11-26 13:30:19 +13:00
mappu a3963f196a adc: PID management
--HG--
branch : adc
2017-11-26 13:29:44 +13:00
mappu ee91ca71c4 tth: redesign api surface
--HG--
branch : adc
2017-11-26 13:29:32 +13:00
mappu aabaa128c4 move some UserInfo properties into separate adc/nmdc sub structs
--HG--
branch : adc
2017-11-26 13:29:09 +13:00
mappu 95cd5c85f0 tth: working
--HG--
branch : adc
2017-11-25 14:25:17 +13:00
mappu 881286df39 tth: expand test coverage
--HG--
branch : adc
2017-11-25 14:22:38 +13:00
mappu da80ee1861 add tth package and test cases (currently non-functional)
--HG--
branch : adc
2017-11-25 13:45:26 +13:00
mappu b784c34d4d fix tests post-refactor
--HG--
branch : adc
2017-11-25 13:45:10 +13:00
mappu c4c6021c85 adc: some basic protocol understanding
--HG--
branch : adc
2017-11-23 18:23:39 +13:00
mappu 931b00a797 vendor github.com/cxmcc/tiger (MIT license)
--HG--
branch : adc
2017-11-23 18:23:25 +13:00
mappu 0e8d76ef2f comments
--HG--
branch : adc
2017-11-22 20:07:29 +13:00
mappu 2fa40642a9 switch to "hat" pattern for mutexes
--HG--
branch : adc
2017-11-22 20:07:26 +13:00
mappu dc3051fd38 remove pointer indirection from self userinfo
--HG--
branch : adc
2017-11-22 20:04:15 +13:00
mappu ffc81f52fb redesign how the connection API works
--HG--
branch : adc
2017-11-22 20:02:30 +13:00
mappu f5e516fe01 adc: initial handshake
--HG--
branch : adc
2017-11-22 20:02:21 +13:00
mappu 23e107f758 working protocol autodetection
--HG--
branch : adc
2017-11-22 20:02:11 +13:00
mappu 131ce0a63b protocol autodetection
--HG--
branch : adc
2017-11-22 19:22:13 +13:00
mappu b022c9534a debug AdcProtocol implementation
--HG--
branch : adc
2017-11-22 19:11:40 +13:00
mappu 5c4d6d8355 further reduce protocol interface surface
--HG--
branch : adc
2017-11-22 19:11:34 +13:00
mappu 2a60330e7c HubEvent explicit types
--HG--
branch : adc
2017-11-22 19:11:11 +13:00
mappu 7543d7058f move all nmdc-specific content to a separate NmdcProtocol class
--HG--
branch : adc
2017-11-22 19:05:36 +13:00
mappu 276ece1cf1 Added tag v0.15.0 for changeset 84fb19100701 2017-11-14 18:54:56 +13:00
mappu 4ea6cbb6f1 readme 2017-11-14 18:54:51 +13:00
mappu b2ae91959e bump DEFAULT_CLIENT_VERSION to 0.15 2017-11-14 18:45:36 +13:00
mappu f5fbce3ee6 add a fallback reconnection if no keepalives are recieved within ~24 hours 2017-11-14 18:45:06 +13:00
mappu 2cbc8f8496 fix regex matching protocol messages in the middle of multi-line public messages 2017-11-14 18:36:34 +13:00
mappu 5bf30ed95f update examples for StateChange member API change 2017-11-14 18:29:04 +13:00
mappu a20fce3725 doc: add go-get tags to README 2017-10-28 12:32:06 +13:00
mappu 5c6435cf81 retag commits in vX.Y.Z format 2017-10-28 12:16:24 +13:00
mappu b7dfcbc2f3 Added tag release-0.14 for changeset 6422ed687cd3 2017-02-11 13:56:27 +13:00
mappu 21847db9fd readme 2017-02-11 13:56:08 +13:00
mappu b8122710f5 patch a crash on incomplete supplied UserIP2 2017-02-11 13:55:20 +13:00
mappu f8c0fc54d8 Added tag release-0.13 for changeset 3ee0f4ea5142 2017-02-09 19:31:24 +13:00
mappu 4490444d1c readme 2017-02-09 19:31:19 +13:00
mappu c5f655ff78 doc: update TODO 2017-02-09 19:28:58 +13:00
mappu 4415109174 support UserIP2 extension 2017-02-09 19:28:37 +13:00
mappu 0adc13ddfa move our is-connected one half roundtrip sooner 2017-02-09 19:19:17 +13:00
mappu 59c56fea1b doc: todo 2017-02-09 19:15:53 +13:00
mappu 7f8f98aa5a add ChatOnly to our $Supports 2017-02-08 19:30:53 +13:00
mappu 8536cb0a5c implement QuickList, save a network roundtrip on supported hubs (ptokax) 2017-02-08 19:22:07 +13:00
mappu 553ec20850 fix not emitting EVENT_USER_UPDATED_INFO on recieved MyINFO (!) 2017-02-08 18:58:14 +13:00
mappu a403dac461 declare NoHello support (since our MyINFO handler will add unknown users) 2017-02-08 18:58:03 +13:00
mappu 59a37b975c more idiomatic error handling 2017-02-08 18:57:49 +13:00
mappu a4e7d876cc Added tag release-0.12 for changeset 22b156a6fc2f 2017-02-05 22:37:24 +13:00
mappu 7c3069eabb readme 2017-02-05 22:35:18 +13:00
mappu 3cc31323ad more fine-grained locking around user map (fixes deadlock if callee uses Users() in response to processEvent) 2017-02-05 19:06:17 +13:00
mappu 7f898dff32 display a system message and disconnect if we're asked for a password but don't have one 2017-02-05 17:12:59 +13:00
mappu 58c1151278 readme for 0.11 2016-11-29 20:18:17 +13:00
mappu d1c549f1f2 Added tag release-0.11 for changeset 5149ffe70ea8 2016-11-29 20:13:14 +13:00
mappu 9eb9cf0726 set a default clientversion of 0.11 2016-11-29 20:07:19 +13:00
mappu f2abf8893c rename example functions for correct godoc appearance 2016-11-29 19:50:05 +13:00
mappu 732622f4db add some function comments 2016-11-29 19:49:53 +13:00
mappu 95311e1479 ConnectionState rename member function to satisfy Stringer interface 2016-11-29 19:44:45 +13:00
mappu 084b629ad7 rename NMDC{Escape,Unescape,Unlock} functions; don't export NMDCUnlock 2016-11-29 19:44:10 +13:00
mappu 5713b58c7c don't export checkIsNetTimeout 2016-11-29 19:43:32 +13:00
mappu 56c6b7b352 remove junk strings appearing in godoc HTML output 2016-11-29 19:41:44 +13:00
mappu ea97afb01f move examples into a godoc-compatible Examples function 2016-11-29 19:40:08 +13:00
mappu 482b0d3ad8 existential fixes for the synchronous API 2016-11-29 19:39:47 +13:00
mappu 231bfeb247 sample: add a version using the synchronous API 2016-11-29 19:30:33 +13:00
mappu bd0425d6d4 sample: use UserInfo constructor 2016-11-29 19:25:41 +13:00
mappu 5564eccf22 split structs to separate files 2016-11-29 19:24:31 +13:00
mappu d373e9791a fix parsing connection modes in MyINFO 2016-11-29 19:23:00 +13:00
mappu 7e249acd6c remove errant printf 2016-11-29 19:22:45 +13:00
mappu b592e8ef7e add ConnectionMode.String() helper 2016-11-29 19:22:28 +13:00
mappu 11564b8c32 test: add MyINFO parsing test cases 2016-11-29 19:22:15 +13:00
mappu 0a53963ec0 fix special characters appearing in recieved PM's 2016-11-04 19:01:01 +13:00
mappu b63a240e9b Added tag libnmdc-r10 for changeset 3ecc037cf2d7 2016-10-08 16:32:16 +13:00
mappu 51b08dad3d readme 2016-10-08 16:32:10 +13:00
mappu 1a3e4fe072 support parsing usercommands 2016-10-08 15:19:21 +13:00
mappu 086281dab2 Added tag libnmdc-r9 for changeset e7c2c71ef24b 2016-08-27 17:39:00 +12:00
mappu 7392cbbea5 readme 2016-08-27 17:38:53 +12:00
mappu 265c0a43ce fix an issue not applying updated user profiles 2016-08-27 15:31:00 +12:00
mappu 417deff347 remove debug logging in previous 2016-08-27 15:24:18 +12:00
mappu a996f1668c better myinfo parsing for zero-length speed strings 2016-08-27 15:22:39 +12:00
mappu 9b290ebb96 Added tag libnmdc-r8 for changeset b0e57a5fcffd 2016-05-10 19:18:11 +12:00
34 changed files with 2456 additions and 531 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
__dist/

View File

@ -1,14 +0,0 @@
mode:regex
# Compilation output
\.(?:exe|a)$
^pkg/
# Dependencies
^src/(?:github.com|gopkg.in|golang.org)/
# Scratch space
^src/nmdc/
# Binary release artefacts
/?__dist/

12
.hgtags
View File

@ -1,12 +0,0 @@
945ab4b16d05aa084f71bf5da9a3f687e0ec8bbd libnmdc-r1
02a360e95480b97ddad83add5db48b2766339a99 nmdc-log-service-1.0.0
137c1b65039e03c80379826a6efdfd808f6fbc8f libnmdc-r2
d8b64d5527c2a5e4d76872e5bc3d69f7646135c6 libnmdc-r3
fca41372e400853775b02e951f9db91d87f41adb nmdc-log-service-1.0.1
050b424a7c5d5a27c9323c8810f3afbead1f5b96 libnmdc-r4
da9f123633f9c28be6435ed7898139665d4c39d9 nmdc-log-service-1.0.2
75a78f6a78f249a2cd8aa3d29f7e5e6319b4e03b libnmdc-r5
4116422bb10229d887f9296970a166fa1ef8c5fd nmdc-log-service-1.0.3
cb86f3a40115cc46f450c0c83fd9b9d3b740e820 nmdc-log-service-1.0.4
cb86f3a40115cc46f450c0c83fd9b9d3b740e820 libnmdc-r6
71343a2c641a438206d30ea7e75dc89a11dbef00 libnmdc-r7

716
AdcProtocol.go Normal file
View File

@ -0,0 +1,716 @@
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 ADUCMD\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) SID2Nick(sid string) (string, bool) {
this.hc.usersMut.Lock()
defer this.hc.usersMut.Unlock()
nick, ok := this.hc.userSIDs[sid]
return nick, ok
}
func (this *AdcProtocol) Nick2SID(targetNick string) (string, bool) {
this.hc.usersMut.Lock()
defer this.hc.usersMut.Unlock()
for sid, nick := range this.hc.userSIDs {
if nick == targetNick {
return sid, true
}
}
return "", false
}
func (this *AdcProtocol) ProcessCommand(msg string) {
if len(msg) == 0 {
return
}
this.hc.processEvent(HubEvent{EventType: EVENT_DEBUG_MESSAGE, Message: msg})
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]
// State transition IDENTIFY --> VERIFY and send our own info
this.SayInfo()
this.state = adcStateVerify
case "IINF":
// Hub telling information about itself
// ADCH++ sends this once we are successfully logged in
flags, err := this.parts2flags(parts[1:])
if err != nil {
this.logError(err)
return
}
if flags["CT"] != "32" {
this.malformed(parts)
return
}
err = this.handleHubInfo(flags)
if err != nil {
this.logError(err)
return
}
if this.state != adcStateNormal {
this.enterNormalState() // successful login
}
case "BINF":
if this.state != adcStateNormal {
this.enterNormalState() // successful login
}
sid := parts[1]
flags, err := this.parts2flags(parts[2:])
if err != nil {
this.logError(err)
return
}
// Log this user in, and associate this SID with this user
this.hc.usersMut.Lock()
defer this.hc.usersMut.Unlock()
oldNick, sidExists := this.hc.userSIDs[sid]
uinfo := UserInfo{}
if sidExists {
uinfo_lookup, ok := this.hc.users[oldNick]
if !ok {
// Shouldn't happen
this.hc.processEvent(HubEvent{
EventType: EVENT_SYSTEM_MESSAGE_FROM_CONN,
Message: fmt.Sprintf("Hub connection corrupted (missing info for SID='%s' nick='%s'), disconnecting", sid, oldNick),
})
this.hc.Disconnect()
return
}
uinfo = uinfo_lookup
}
this.updateUserInfo(&uinfo, flags)
newNick := uinfo.Nick
if len(newNick) == 0 {
this.logError(fmt.Errorf("Zero-length nick for user (SID='%s')", sid))
}
shouldHandleNewUser := false
if sidExists && oldNick != newNick {
// Nick change = delete all trace of this user first, treat as new
delete(this.hc.users, oldNick)
delete(this.hc.userSIDs, sid)
this.hc.processEvent(HubEvent{EventType: EVENT_USER_PART, Nick: oldNick})
shouldHandleNewUser = true
} else if sidExists && oldNick == newNick {
// Updating existing user
this.hc.users[newNick] = uinfo
this.hc.processEvent(HubEvent{EventType: EVENT_USER_UPDATED_INFO, Nick: newNick})
} else if !sidExists {
// User joined
shouldHandleNewUser = true
}
//
if shouldHandleNewUser {
// Install this SID as pointing to this nick
this.hc.userSIDs[sid] = uinfo.Nick
// Check if this nick was in use by any other SID already
for otherSid, otherSidNick := range this.hc.userSIDs {
if otherSidNick == newNick && otherSid != sid {
this.hc.processEvent(HubEvent{
EventType: EVENT_SYSTEM_MESSAGE_FROM_CONN,
Message: fmt.Sprintf("Hub connection corrupted (duplicate SIDs '%s' and '%s' for nick '%s'), disconnecting", sid, otherSid, newNick),
})
this.hc.Disconnect()
return
}
}
// Notifications
this.hc.users[newNick] = uinfo
this.hc.processEvent(HubEvent{EventType: EVENT_USER_JOINED, Nick: newNick})
}
case "IMSG":
// General message from the hub
if len(parts) < 2 {
this.malformed(parts)
return
}
this.hc.processEvent(HubEvent{EventType: EVENT_SYSTEM_MESSAGE_FROM_HUB, Message: this.unescape(parts[1])})
case "ISTA":
// Error 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 "IQUI":
// Error message from the hub
// IQUI V3M6 DI1 MSNick\staken,\splease\spick\sanother\sone TL-1
if len(parts) < 2 {
this.malformed(parts)
return
}
sid := parts[1]
flags, err := this.parts2flags(parts[2:])
if err != nil {
return
}
if sid == this.sid {
if msg, ok := flags["MS"]; ok {
this.hc.processEvent(HubEvent{EventType: EVENT_SYSTEM_MESSAGE_FROM_HUB, Message: "The hub is closing our connection because: " + this.unescape(msg)})
} else {
this.hc.processEvent(HubEvent{EventType: EVENT_SYSTEM_MESSAGE_FROM_HUB, Message: "The hub is closing our connection"})
}
} else {
this.hc.usersMut.Lock()
defer this.hc.usersMut.Unlock()
otherSidNick, ok := this.hc.userSIDs[sid]
if ok {
delete(this.hc.userSIDs, sid)
delete(this.hc.users, otherSidNick)
this.hc.processEvent(HubEvent{EventType: EVENT_USER_PART, Nick: otherSidNick})
} else {
// ??
this.logError(fmt.Errorf("An unknown user quit the hub (SID=%s)", sid))
}
}
case "BMSG":
// Message from a user
// BMSG ZVF4 hi
if len(parts) < 3 {
this.malformed(parts)
return
}
sid := this.unescape(parts[1])
msg := this.unescape(parts[2])
this.hc.usersMut.Lock()
defer this.hc.usersMut.Unlock()
nick, ok := this.hc.userSIDs[sid]
if !ok {
this.logError(fmt.Errorf("Recieved message from unknown SID '%s'", sid))
return
}
this.hc.processEvent(HubEvent{EventType: EVENT_PUBLIC, Nick: nick, Message: msg})
case "IGPA":
// Password is needed
// IGPA 7EIAAAECLMAAAPJQAAADQQYAAAWAYAAAKVFQAAF6EAAAAAYFAAAA
// HPAS LZDIJOTZDPWHINHGPT5RHT6WLU7DRME7DQO2O3Q
if len(parts) < 2 {
this.malformed(parts)
return
}
/*
For GPA/PAS, assuming that '12345' is the random data supplied in GPA, then;
PAS = Base32( Hash( password + '12345' ) )
GPA: The data parameter is at least 24 random bytes (base32 encoded).
*/
data_base32 := parts[1]
if len(data_base32)%8 != 0 {
data_base32 += strings.Repeat("=", 8-(len(data_base32)%8))
}
data_raw, err := base32.StdEncoding.DecodeString(data_base32)
if err != nil {
this.logError(err)
return
}
resp := Base32(Tiger(this.hc.Hco.NickPassword + string(data_raw)))
this.hc.SayRaw("HPAS " + resp + "\n")
case "EMSG":
// Private message from other user
// EMSG I5RO FMWH test\spm PMI5RO
// EMSG sender recip==us message [flags...]
if len(parts) < 4 {
this.malformed(parts)
return
}
if parts[2] != this.sid {
this.logError(fmt.Errorf("Recieved a PM intended for someone else (got SID=%s expected SID=%s)", parts[2], this.sid))
return
}
senderSid := parts[1]
senderNick, ok := this.SID2Nick(parts[1])
if !ok {
this.logError(fmt.Errorf("Recieved a PM from an unknown user (SID=%s)", senderSid))
return
}
msg := this.unescape(parts[3])
this.hc.processEvent(HubEvent{EventType: EVENT_PRIVATE, Nick: senderNick, Message: msg})
case "ICMD":
// Usercommand
// ICMD ADCH++/About\sthis\shub TTHMSG\s+about\n CT3
if len(parts) < 2 {
this.malformed(parts)
return
}
uc := UserCommand{
Message: this.unescape(parts[1]),
Type: USERCOMMAND_TYPE_RAW, // default
}
flags, err := this.parts2flags(parts[2:])
if err != nil {
this.malformed(parts)
return
}
if ct, ok := flags["CT"]; ok {
ct64, _ := strconv.ParseUint(ct, 10, 64)
uc.Context = UserCommandContext(ct64)
}
if tt, ok := flags["TT"]; ok {
uc.Command = tt
}
if sp, ok := flags["SP"]; ok && sp == "1" {
uc.Type = USERCOMMAND_TYPE_SEPARATOR
}
if co, ok := flags["CO"]; ok && co == "1" {
uc.Type = USERCOMMAND_TYPE_NICKLIMITED // "Constrained" in ADC parlance
}
if rm, ok := flags["RM"]; ok && rm == "1" {
uc.RemoveThis = true
}
this.hc.processEvent(HubEvent{EventType: EVENT_USERCOMMAND, UserCommand: &uc})
// Ignored messages
// ````````````````
case "DCTM": // Client-client ConnectToMe
case "BSCH": // Search
default:
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),
"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.ClientTag, u.ClientVersion)
}
// Do not send the hub a CT (it decides what type we are)
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) SayInfo() error {
return this.hc.SayRaw("BINF " + this.escape(this.sid) + " " + this.ourINFO(true) + "\n")
}
func (this *AdcProtocol) parts2flags(parts []string) (map[string]string, error) {
flags := make(map[string]string, len(parts))
for _, flag := range parts {
if len(flag) < 2 {
return nil, fmt.Errorf("Malformed flag '%s'", flag)
}
flags[flag[0:2]] = this.unescape(flag[2:])
}
return flags, nil
}
func (this *AdcProtocol) handleHubInfo(flags map[string]string) error {
if flags["CT"] != "32" {
return fmt.Errorf("Expected 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{}{}
}
// Hub's name is in "NI", hub description in "DE"
hubName, ok := flags["NI"]
if ok {
if hubDesc, ok := flags["DE"]; ok && len(hubDesc) > 0 {
hubName += " - " + hubDesc
}
this.hc.HubName = hubName
this.hc.processEvent(HubEvent{EventType: EVENT_HUBNAME_CHANGED, Nick: this.hc.HubName})
}
return nil
}
func (this *AdcProtocol) updateUserInfo(u *UserInfo, flags map[string]string) {
// User MyINFO
// BINF GUPR IDFEARIFD33NTGC4YBEZ3UFQS5R4ZXXTFL2QN2GRY PDZMIFLG5EKZG3BDRRMIJPG7ARNA6KW3JVIH3DF7Q NIivysaur5 SL3 FS3 SS0 SF0 HN1 HR0 HO0 VEEiskaltDC++\s2.2.9 US2621440 KPSHA256/3UPRORG4BLJ4CG6TO6R3G75A67LXOGD437NALQALRWJF6XBOECTA I40.0.0.0 U418301
// BINF GUPR I4172.17.0.1 U418301 IDFEARIFD33NTGC4YBEZ3UFQS5R4ZXXTFL2QN2GRY VEEiskaltDC++\s2.2.9 SF0 NIivysaur5 SL3 HN1 HO0 KPSHA256/3UPRORG4BLJ4CG6TO6R3G75A67LXOGD437NALQALRWJF6XBOECTA HR0 FS3 SS0 US2621440 SUSEGA,ADC0,TCP4,UDP4
// Or maybe only incremental:
// BINF Z3BA HO1
// TODO
for prop, val := range flags {
switch prop {
case "ID":
u.CID = val
case "PD":
// ignore PID - it will only appear if we're talking about our own user
case "NI":
u.Nick = val
case "SL":
u.Slots, _ = strconv.ParseUint(val, 10, 64)
case "SS":
u.ShareSize, _ = strconv.ParseUint(val, 10, 64)
case "SF":
u.SharedFiles, _ = strconv.ParseUint(val, 10, 64)
case "HN":
u.HubsUnregistered, _ = strconv.ParseUint(val, 10, 64)
case "HR":
u.HubsRegistered, _ = strconv.ParseUint(val, 10, 64)
case "HO":
u.HubsOperator, _ = strconv.ParseUint(val, 10, 64)
case "US":
u.UploadSpeedBps, _ = strconv.ParseUint(val, 10, 64)
case "DS":
u.DownloadSpeedBps, _ = strconv.ParseUint(val, 10, 64)
case "KP":
u.Keyprint = val
case "I4":
u.IPv4Address = val
case "I6":
u.IPv6Address = val
case "U4":
u.IPv4UDPPort, _ = strconv.ParseUint(val, 10, 64)
case "U6":
u.IPv6UDPPort, _ = strconv.ParseUint(val, 10, 64)
case "SU":
u.SupportFlags = make(map[string]struct{})
for _, supportFlag := range strings.Split(val, ",") {
u.SupportFlags[supportFlag] = struct{}{}
}
}
}
// VE / AP
AP, hasAP := flags["AP"]
VE, hasVE := flags["VE"]
if hasAP && hasVE {
u.ClientTag = AP
u.ClientVersion = VE
} else if hasAP && !hasVE {
u.ClientTag, u.ClientVersion = this.getAPVEFromSingle(AP)
} else if !hasAP && hasVE {
u.ClientTag, u.ClientVersion = this.getAPVEFromSingle(VE)
}
}
func (this *AdcProtocol) getAPVEFromSingle(term string) (string, string) {
words := strings.Split(term, " ")
if len(words) > 1 {
return strings.Join(words[0:len(words)-1], " "), words[len(words)-1]
} else {
return term, "0"
}
}
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.logError(fmt.Errorf("Ignoring malformed, unhandled, or out-of-state protocol command %v", parts))
}
func (this *AdcProtocol) logError(e error) {
this.hc.processEvent(HubEvent{EventType: EVENT_DEBUG_MESSAGE, Message: "Protocol error: " + e.Error()})
}
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) error {
return this.hc.SayRaw("BMSG " + this.sid + " " + this.escape(msg) + "\n")
}
func (this *AdcProtocol) SayPrivate(user, message string) error {
sid, ok := this.Nick2SID(user)
if !ok {
return fmt.Errorf("Unknown user '%s'", user)
}
return this.hc.SayRaw("DMSG " + this.sid + " " + sid + " " + this.escape(message) + "\n")
}
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)
}

90
AutodetectProtocol.go Normal file
View File

@ -0,0 +1,90 @@
package libnmdc
import (
"sync"
"time"
)
type AutodetectProtocol struct {
hc *HubConnection
realProtoMut sync.Mutex
realProto Protocol
}
func NewAutodetectProtocol(hc *HubConnection) Protocol {
proto := AutodetectProtocol{
hc: hc,
realProto: nil,
}
go proto.timeout()
return &proto
}
func (this *AutodetectProtocol) timeout() {
time.Sleep(AUTODETECT_ADC_NMDC_TIMEOUT)
this.realProtoMut.Lock()
defer this.realProtoMut.Unlock()
if this.realProto == nil {
this.realProto = NewAdcProtocol(this.hc)
this.hc.processEvent(HubEvent{EventType: EVENT_DEBUG_MESSAGE, Message: "Detected ADC protocol"})
}
}
func (this *AutodetectProtocol) ProcessCommand(msg string) {
this.realProtoMut.Lock()
defer this.realProtoMut.Unlock()
if this.realProto == nil {
// We actually got some data using $ as the separator?
// Upgrade to a full NMDC protocol
this.realProto = NewNmdcProtocol(this.hc)
this.hc.processEvent(HubEvent{EventType: EVENT_DEBUG_MESSAGE, Message: "Detected NMDC protocol"})
}
this.realProto.ProcessCommand(msg)
}
func (this *AutodetectProtocol) SayPublic(msg string) error {
this.realProtoMut.Lock()
defer this.realProtoMut.Unlock()
if this.realProto == nil {
this.realProto = NewNmdcProtocol(this.hc)
}
return this.realProto.SayPublic(msg)
}
func (this *AutodetectProtocol) SayPrivate(user, message string) error {
this.realProtoMut.Lock()
defer this.realProtoMut.Unlock()
if this.realProto == nil {
this.realProto = NewNmdcProtocol(this.hc)
}
return this.realProto.SayPrivate(user, message)
}
func (this *AutodetectProtocol) SayInfo() error {
this.realProtoMut.Lock()
defer this.realProtoMut.Unlock()
if this.realProto == nil {
this.realProto = NewNmdcProtocol(this.hc)
}
return this.realProto.SayInfo()
}
func (this *AutodetectProtocol) ProtoMessageSeparator() string {
this.realProtoMut.Lock()
defer this.realProtoMut.Unlock()
if this.realProto == nil {
return "|"
}
return this.realProto.ProtoMessageSeparator()
}

29
ConnectionMode.go Normal file
View File

@ -0,0 +1,29 @@
package libnmdc
import (
"fmt"
)
type ConnectionMode rune
const (
CONNECTIONMODE_ACTIVE ConnectionMode = 'A' // 65
CONNECTIONMODE_PASSIVE ConnectionMode = 'P' // 49
CONNECTIONMODE_SOCKS5 ConnectionMode = '5' // 53
)
func (this ConnectionMode) String() string {
switch this {
case CONNECTIONMODE_ACTIVE:
return "Active"
case CONNECTIONMODE_PASSIVE:
return "Passive"
case CONNECTIONMODE_SOCKS5:
return "SOCKS5"
default:
return fmt.Sprintf("ConnectionMode(\"%s\")", string(this))
}
}

View File

@ -12,7 +12,7 @@ const (
CONNECTIONSTATE_CONNECTED = 3
)
func (cs ConnectionState) Format() string {
func (cs ConnectionState) String() string {
switch cs {
case CONNECTIONSTATE_DISCONNECTED:
return "Disconnected"
@ -25,7 +25,7 @@ func (cs ConnectionState) Format() string {
}
}
func CheckIsNetTimeout(err error) bool {
func checkIsNetTimeout(err error) bool {
if err == nil {
return false
}

58
Example_test.go Normal file
View File

@ -0,0 +1,58 @@
package libnmdc
import (
"fmt"
)
func ExampleHubConnectionOptions_Connect() {
opts := HubConnectionOptions{
Address: "127.0.0.1",
Self: NewUserInfo("slowpoke9"),
}
events := make(chan HubEvent, 0)
hub := ConnectAsync(&opts, events)
for event := range events {
switch event.EventType {
case EVENT_CONNECTION_STATE_CHANGED:
fmt.Printf("Connection -- %s (%s)\n", event.StateChange, event.Message)
case EVENT_PUBLIC:
fmt.Printf("Message from '%s': '%s'\n", event.Nick, event.Message)
if event.Message == "how are you" {
hub.SayPublic("good thanks!")
}
default:
fmt.Printf("%+v\n", event)
}
}
}
func ExampleHubConnectionOptions_ConnectSync() {
cb := func(hub *HubConnection, event HubEvent) {
switch event.EventType {
case EVENT_CONNECTION_STATE_CHANGED:
fmt.Printf("Connection -- %s (%s)\n", event.StateChange, event.Message)
case EVENT_PUBLIC:
fmt.Printf("Message from '%s': '%s'\n", event.Nick, event.Message)
if event.Message == "how are you" {
hub.SayPublic("good thanks!")
}
default:
fmt.Printf("%+v\n", event)
}
}
opts := HubConnectionOptions{
Address: "127.0.0.1",
Self: NewUserInfo("slowpoke9"),
}
ConnectSync(&opts, cb) // blocking
}

View File

@ -11,7 +11,7 @@ func (this *HubAddress) parse() url.URL {
parsed, err := url.Parse(strings.ToLower(string(*this)))
if err != nil || len(parsed.Host) == 0 {
parsed = &url.URL{
Scheme: "nmdc",
Scheme: "",
Host: string(*this),
}
}
@ -27,9 +27,22 @@ func (this *HubAddress) parse() url.URL {
func (this *HubAddress) IsSecure() bool {
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 {
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
}
}

View File

@ -2,8 +2,9 @@ package libnmdc
import (
"crypto/tls"
"fmt"
"net"
"strings"
"regexp"
"sync"
"time"
)
@ -13,244 +14,113 @@ type HubConnection struct {
Hco *HubConnectionOptions
// Current remote status
HubName string
State ConnectionState
users map[string]UserInfo
userLock sync.RWMutex
HubName string
State ConnectionState
// Streamed events
usersMut sync.RWMutex
users map[string]UserInfo
userSIDs map[string]string
proto Protocol
// Event callback
processEvent func(HubEvent)
OnEvent chan HubEvent
// Private state
conn net.Conn // this is an interface
connValid bool
sentOurHello bool
autoReconnect bool
conn net.Conn // this is an interface
connValid bool
autoReconnect bool
lastDataRecieved time.Time
}
// Thread-safe user accessor.
func (this *HubConnection) Users(cb func(*map[string]UserInfo) error) error {
this.userLock.Lock()
defer this.userLock.Unlock()
this.usersMut.Lock()
defer this.usersMut.Unlock()
return cb(&this.users)
}
func (this *HubConnection) SayPublic(message string) {
this.SayRaw("<" + this.Hco.Self.Nick + "> " + NMDCEscape(message) + "|")
func (this *HubConnection) SayPublic(message string) error {
if this.proto == nil {
return ErrNotConnected
}
return this.proto.SayPublic(message)
}
func (this *HubConnection) SayPrivate(recipient string, message string) {
this.SayRaw("$To: " + recipient + " From: " + this.Hco.Self.Nick + " $<" + this.Hco.Self.Nick + "> " + NMDCEscape(message) + "|")
func (this *HubConnection) SayPrivate(recipient string, message string) error {
if this.proto == nil {
return ErrNotConnected
}
return this.proto.SayPrivate(recipient, message)
}
func (this *HubConnection) SayInfo() {
this.SayRaw(this.Hco.Self.toMyINFO() + "|")
func (this *HubConnection) SayInfo() error {
if this.proto == nil {
return ErrNotConnected
}
return this.proto.SayInfo()
}
func (this *HubConnection) UserExists(nick string) bool {
this.userLock.RLock()
defer this.userLock.RUnlock()
this.usersMut.RLock()
defer this.usersMut.RUnlock()
_, already_existed := this.users[nick]
return already_existed
}
func (this *HubConnection) UserCount() int {
this.userLock.RLock()
defer this.userLock.RUnlock()
this.usersMut.RLock()
defer this.usersMut.RUnlock()
return len(this.users)
}
func (this *HubConnection) userJoined_NameOnly(nick string) {
if !this.UserExists(nick) {
this.userLock.Lock()
defer this.userLock.Unlock()
this.usersMut.Lock()
this.users[nick] = *NewUserInfo(nick)
this.usersMut.Unlock() // Don't lock over a processEvent boundary
this.processEvent(HubEvent{EventType: EVENT_USER_JOINED, Nick: nick})
}
}
func (this *HubConnection) userJoined_Full(uinf *UserInfo) {
if !this.UserExists(uinf.Nick) {
this.userLock.Lock()
defer this.userLock.Unlock()
// n.b. also called when we get a replacement MyINFO for someone
this.usersMut.Lock()
_, userExisted := this.users[uinf.Nick] // don't use UserExists as it would deadlock the mutex
this.users[uinf.Nick] = *uinf
this.usersMut.Unlock() // Don't lock over a processEvent boundary
this.users[uinf.Nick] = *uinf
if !userExisted {
this.processEvent(HubEvent{EventType: EVENT_USER_JOINED, Nick: uinf.Nick})
} else {
this.processEvent(HubEvent{EventType: EVENT_USER_UPDATED_INFO, Nick: uinf.Nick})
}
}
// SayRaw sends raw bytes over the TCP socket. Callers should add the protocol
// terminating character themselves (e.g. `|` for NMDC).
// Note that protocol messages are transmitted on the caller thread, not from
// any internal libnmdc thread.
func (this *HubConnection) SayRaw(protocolCommand string) error {
if this.connValid {
_, err := this.conn.Write([]byte(protocolCommand))
return err
} else {
if !this.connValid {
return ErrNotConnected
}
_, err := this.conn.Write([]byte(protocolCommand))
return err
}
func (this *HubConnection) processProtocolMessage(message string) {
// Zero-length protocol message
// ````````````````````````````
if len(message) == 0 {
return
func (this *HubConnection) SayKeepalive() error {
if !this.connValid {
return ErrNotConnected
}
// Public chat
// ```````````
if rx_publicChat.MatchString(message) {
pubchat_parts := rx_publicChat.FindStringSubmatch(message)
this.processEvent(HubEvent{EventType: EVENT_PUBLIC, Nick: pubchat_parts[1], Message: NMDCUnescape(pubchat_parts[2])})
return
}
// System messages
// ```````````````
if message[0] != '$' {
this.processEvent(HubEvent{EventType: EVENT_SYSTEM_MESSAGE_FROM_HUB, Nick: this.HubName, Message: NMDCUnescape(message)})
return
}
// Protocol messages
// `````````````````
commandParts := strings.SplitN(message, " ", 2)
switch commandParts[0] {
case "$Lock":
this.SayRaw("$Supports NoGetINFO UserCommand UserIP2|" +
"$Key " + NMDCUnlock([]byte(commandParts[1])) + "|" +
"$ValidateNick " + NMDCEscape(this.Hco.Self.Nick) + "|")
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":
this.SayRaw("$MyPass " + NMDCEscape(this.Hco.NickPassword) + "|")
case "$Quit":
func() {
this.userLock.Lock()
defer this.userLock.Unlock()
delete(this.users, commandParts[1])
}()
this.processEvent(HubEvent{EventType: EVENT_USER_PART, Nick: commandParts[1]})
case "$MyINFO":
u := UserInfo{}
err := u.fromMyINFO(commandParts[1])
if err == nil {
this.userJoined_Full(&u)
} else {
this.processEvent(HubEvent{EventType: EVENT_DEBUG_MESSAGE, Message: err.Error()})
}
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: txparts[4]})
valid = true
}
}
if !valid {
this.processEvent(HubEvent{EventType: EVENT_DEBUG_MESSAGE, Message: "Malformed private message '" + commandParts[1] + "'"})
}
case "$UserIP":
// Final message in PtokaX connection handshake - trigger connection callback.
// This might not be the case for other hubsofts, though
if this.State != CONNECTIONSTATE_CONNECTED {
this.processEvent(HubEvent{EventType: EVENT_CONNECTION_STATE_CHANGED, StateChange: CONNECTIONSTATE_CONNECTED})
this.State = CONNECTIONSTATE_CONNECTED
}
case "$ForceMove":
this.Hco.Address = HubAddress(commandParts[1])
this.conn.Close() // we'll reconnect onto the new address
// IGNORABLE COMMANDS
case "$Supports":
case "$UserCommand": // TODO $UserCommand 1 1 Group chat\New group chat$<%[mynick]> !groupchat_new&#124;|
case "$HubTopic":
case "$Search":
case "$ConnectToMe":
default:
this.processEvent(HubEvent{EventType: EVENT_DEBUG_MESSAGE, Message: "Unhandled protocol command '" + commandParts[0] + "'"})
}
return this.SayRaw(this.proto.ProtoMessageSeparator())
}
func (this *HubConnection) Disconnect() {
@ -284,45 +154,68 @@ func (this *HubConnection) worker() {
if err != nil {
this.State = CONNECTIONSTATE_DISCONNECTED
this.connValid = false
this.proto = nil
} else {
this.State = CONNECTIONSTATE_CONNECTING
this.connValid = true
this.processEvent(HubEvent{EventType: EVENT_CONNECTION_STATE_CHANGED, StateChange: CONNECTIONSTATE_CONNECTING})
this.proto = this.Hco.Address.GetProtocol().Create(this)
}
}
// Read from socket into our local buffer (blocking)
if this.connValid {
readBuff := make([]byte, 1024)
this.conn.SetReadDeadline(time.Now().Add(SEND_KEEPALIVE_EVERY))
nbytes, err = this.conn.Read(readBuff)
if CheckIsNetTimeout(err) {
if checkIsNetTimeout(err) {
// No data before read deadline
err = nil
// Send KA packet
_, err = this.conn.Write([]byte("|"))
if this.proto == nil {
// Autodetect: switch to ADC
this.proto = NewAdcProtocol(this)
} else {
// Normal
// Send KA packet
err = this.SayKeepalive()
}
}
if nbytes > 0 {
this.lastDataRecieved = time.Now()
fullBuffer += string(readBuff[0:nbytes])
}
}
// Attempt to parse a message block
for len(fullBuffer) > 0 {
for len(fullBuffer) > 0 && fullBuffer[0] == '|' {
fullBuffer = fullBuffer[1:]
if this.proto != nil {
rxSeparator := regexp.QuoteMeta(this.proto.ProtoMessageSeparator())
rxProtocolMessage := regexp.MustCompile(`(?ms)\A[^` + rxSeparator + `]*` + rxSeparator)
// Attempt to parse a message block
for len(fullBuffer) > 0 {
// FIXME nmdc
for len(fullBuffer) > 0 && fullBuffer[0] == '|' {
fullBuffer = fullBuffer[1:]
}
protocolMessage := rxProtocolMessage.FindString(fullBuffer)
if len(protocolMessage) > 0 {
this.proto.ProcessCommand(protocolMessage[:len(protocolMessage)-1])
fullBuffer = fullBuffer[len(protocolMessage):]
} else {
break
}
}
protocolMessage := rx_protocolMessage.FindString(fullBuffer)
if len(protocolMessage) > 0 {
this.processProtocolMessage(protocolMessage[:len(protocolMessage)-1])
fullBuffer = fullBuffer[len(protocolMessage):]
} else {
break
if err == nil && time.Now().Sub(this.lastDataRecieved) > RECONNECT_IF_NO_DATA_RECIEVED_IN {
err = fmt.Errorf("No packets recieved since %s, connection presumed lost", this.lastDataRecieved.Format(time.RFC3339))
}
}
@ -333,6 +226,7 @@ func (this *HubConnection) worker() {
this.State = CONNECTIONSTATE_DISCONNECTED
this.conn = nil
this.connValid = false
this.proto = nil
this.processEvent(HubEvent{EventType: EVENT_CONNECTION_STATE_CHANGED, StateChange: CONNECTIONSTATE_DISCONNECTED, Message: err.Error()})
if this.autoReconnect {

View File

@ -1,29 +1,53 @@
package libnmdc
import (
"crypto/rand"
)
type HubConnectionOptions struct {
Address HubAddress
SkipVerifyTLS bool // using a negative verb, because bools default to false
SkipAutoReconnect bool // as above
Self UserInfo
Self *UserInfo
NickPassword string
AdcPID string // blank: autogenerate
}
// Returning messages in async mode
NumEventsToBuffer uint
func NewPID() string {
pidBytes := make([]byte, 24)
n, err := rand.Read(pidBytes)
if err != nil {
panic(err) // Insufficient cryptographic randomness
}
// Returning messages in sync mode
OnEventSync func(HubEvent)
if n != 24 {
panic("Insufficient cryptographic randomness")
}
return Base32(pidBytes)
}
func (this *HubConnectionOptions) prepareConnection() *HubConnection {
if this.Self.ClientTag == "" {
this.Self.ClientTag = DEFAULT_CLIENT_TAG
this.Self.ClientVersion = DEFAULT_CLIENT_VERSION
}
// Shouldn't be blank either
if this.Self.ClientVersion == "" {
this.Self.ClientVersion = "0"
}
if this.AdcPID == "" {
this.AdcPID = NewPID()
}
hc := HubConnection{
Hco: this,
HubName: DEFAULT_HUB_NAME,
State: CONNECTIONSTATE_DISCONNECTED,
users: make(map[string]UserInfo),
Hco: this,
HubName: DEFAULT_HUB_NAME,
State: CONNECTIONSTATE_DISCONNECTED,
users: make(map[string]UserInfo),
userSIDs: make(map[string]string),
autoReconnect: !this.SkipAutoReconnect,
}
@ -31,28 +55,25 @@ func (this *HubConnectionOptions) prepareConnection() *HubConnection {
return &hc
}
// Connects to an NMDC server, and spawns a background goroutine to handle
// protocol messages. Events will be sent by channel to the returned hc.OnEvent,
// ConnectAsync connects to a hub server, and spawns a background goroutine to handle
// protocol messages. Events will be sent by channel to the supplied onEvent channel,
// the client is responsible for selecting off this.
func (this *HubConnectionOptions) Connect() *HubConnection {
if this.NumEventsToBuffer < 1 {
this.NumEventsToBuffer = 1
}
hc := this.prepareConnection()
hc.OnEvent = make(chan HubEvent, this.NumEventsToBuffer)
func ConnectAsync(opts *HubConnectionOptions, onEvent chan HubEvent) *HubConnection {
hc := opts.prepareConnection()
hc.processEvent = func(ev HubEvent) {
hc.OnEvent <- ev
onEvent <- ev
}
go hc.worker()
return hc
}
// Connects to an NMDC server, and blocks forever to handle protocol messages.
// ConnectSync connects to a hub server, and blocks forever to handle protocol messages.
// Client code should supply an event handling function as hco.OnEventSync.
func (this *HubConnectionOptions) ConnectSync() {
hc := this.prepareConnection()
func ConnectSync(opts *HubConnectionOptions, onEvent func(hub *HubConnection, ev HubEvent)) {
hc := opts.prepareConnection()
hc.processEvent = func(ev HubEvent) {
onEvent(hc, ev)
}
hc.worker()
}

View File

@ -1,23 +1,26 @@
package libnmdc
const (
EVENT_PUBLIC = 1
EVENT_PRIVATE = 2
EVENT_SYSTEM_MESSAGE_FROM_HUB = 3
EVENT_SYSTEM_MESSAGE_FROM_CONN = 4
EVENT_USER_JOINED = 5
EVENT_USER_PART = 6
EVENT_USER_UPDATED_INFO = 7
EVENT_CONNECTION_STATE_CHANGED = 8
EVENT_HUBNAME_CHANGED = 9
EVENT_DEBUG_MESSAGE = 10
)
type HubEventType int
const (
EVENT_PUBLIC HubEventType = 1
EVENT_PRIVATE HubEventType = 2
EVENT_SYSTEM_MESSAGE_FROM_HUB HubEventType = 3
EVENT_SYSTEM_MESSAGE_FROM_CONN HubEventType = 4
EVENT_USER_JOINED HubEventType = 5
EVENT_USER_PART HubEventType = 6
EVENT_USER_UPDATED_INFO HubEventType = 7
EVENT_CONNECTION_STATE_CHANGED HubEventType = 8
EVENT_HUBNAME_CHANGED HubEventType = 9
EVENT_DEBUG_MESSAGE HubEventType = 10
EVENT_USERCOMMAND HubEventType = 11
EVENT_BAD_LOGIN_FAILURE HubEventType = 12
)
type HubEvent struct {
EventType HubEventType
Nick string
Message string
StateChange ConnectionState
UserCommand *UserCommand
}

411
NmdcProtocol.go Normal file
View File

@ -0,0 +1,411 @@
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
}
func NewNmdcProtocol(hc *HubConnection) Protocol {
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
this.hc.processEvent(HubEvent{EventType: EVENT_BAD_LOGIN_FAILURE})
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_BAD_LOGIN_FAILURE})
this.hc.processEvent(HubEvent{EventType: EVENT_SYSTEM_MESSAGE_FROM_CONN, Message: "Hub is full."})
case "$BadPass":
this.hc.processEvent(HubEvent{EventType: EVENT_BAD_LOGIN_FAILURE})
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_BAD_LOGIN_FAILURE})
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.usersMut.Lock()
delete(this.hc.users, commandParts[1])
this.hc.usersMut.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.usersMut.Lock()
defer this.hc.usersMut.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.usersMut.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.usersMut.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&#124;|
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, "&", "&amp;", -1)
v2 := strings.Replace(v1, "|", "&#124;", -1)
return strings.Replace(v2, "$", "&#36;", -1)
}
func (this *NmdcProtocol) unescape(encoded string) string {
v1 := strings.Replace(encoded, "&#36;", "$", -1)
v2 := strings.Replace(v1, "&#124;", "|", -1)
return strings.Replace(v2, "&amp;", "&", -1)
}
func (this *NmdcProtocol) SayPublic(message string) error {
return this.hc.SayRaw("<" + this.hc.Hco.Self.Nick + "> " + this.escape(message) + "|")
}
func (this *NmdcProtocol) SayPrivate(recipient, message string) error {
return this.hc.SayRaw("$To: " + recipient + " From: " + this.hc.Hco.Self.Nick + " $<" + this.hc.Hco.Self.Nick + "> " + this.escape(message) + "|")
}
func (this *NmdcProtocol) SayInfo() error {
return 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) 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
}

97
NmdcProtocol_test.go Normal file
View File

@ -0,0 +1,97 @@
package libnmdc
import (
"testing"
)
func TestMyINFOParse(t *testing.T) {
np := NewNmdcProtocol(nil).(*NmdcProtocol)
type myInfoTestPair struct {
in string
expect UserInfo
}
cases := []myInfoTestPair{
myInfoTestPair{
in: "$ALL Bxxxy description<ApexDC++ V:1.4.3,M:P,H:9/0/2,S:1>$ $0.01\x01$xyz@example.com$53054999578$",
expect: UserInfo{
Nick: "Bxxxy",
Description: "description",
ClientTag: "ApexDC++",
ClientVersion: "1.4.3",
Email: "xyz@example.com",
ShareSize: 53054999578,
Flag: FLAG_NORMAL,
Slots: 1,
HubsUnregistered: 9,
HubsRegistered: 0,
HubsOperator: 2,
UserInfo_NMDCOnly: UserInfo_NMDCOnly{
ConnectionMode: CONNECTIONMODE_PASSIVE,
Speed: "0.0",
},
},
},
myInfoTestPair{
in: "$ALL ixxxxxxx0 $P$10A$$0$",
expect: UserInfo{
Nick: "ixxxxxxx0",
ClientVersion: "0", // Auto-inserted by the parser for short-format MyINFO strings
Flag: UserFlag(rune('A')),
UserInfo_NMDCOnly: UserInfo_NMDCOnly{
ConnectionMode: CONNECTIONMODE_PASSIVE,
Speed: "1",
},
},
},
myInfoTestPair{
in: "$ALL SXXXX_XXXXXXR <ncdc V:1.19.1-12-g5561,M:P,H:1/0/0,S:10>$ $0.005Q$$0$",
expect: UserInfo{
Nick: "SXXXX_XXXXXXR",
ClientTag: "ncdc",
ClientVersion: "1.19.1-12-g5561",
Flag: UserFlag(rune('Q')),
Slots: 10,
HubsUnregistered: 1,
UserInfo_NMDCOnly: UserInfo_NMDCOnly{
ConnectionMode: CONNECTIONMODE_PASSIVE,
Speed: "0.00",
},
},
},
myInfoTestPair{
in: "$ALL mxxxu desccccc<HexChat V:2.12.1,M:P,H:1/0/0,S:0>$ $p$$0$",
expect: UserInfo{
Nick: "mxxxu",
Description: "desccccc",
ClientTag: "HexChat",
ClientVersion: "2.12.1",
Flag: UserFlag(rune('p')),
HubsUnregistered: 1,
Slots: 0,
UserInfo_NMDCOnly: UserInfo_NMDCOnly{
ConnectionMode: CONNECTIONMODE_PASSIVE,
},
},
},
}
for _, v := range cases {
got, err := np.parseMyINFO(v.in)
if err != nil {
t.Errorf("MyINFO parse warning (%s)", err.Error())
continue
}
if *got != v.expect {
t.Errorf("MyINFO parse failure\nExpected:\n%+v\nGot:\n%+v\n", v.expect, got)
continue
}
}
}

33
Protocol.go Normal file
View File

@ -0,0 +1,33 @@
package libnmdc
type Protocol interface {
ProcessCommand(msg string)
SayPublic(string) error
SayPrivate(user, message string) error
SayInfo() error
ProtoMessageSeparator() string
}
type HubProtocol int
const (
HubProtocolAutodetect HubProtocol = 0
HubProtocolNmdc HubProtocol = 1
HubProtocolAdc HubProtocol = 2
)
func (hp HubProtocol) Create(hc *HubConnection) Protocol {
if hp == HubProtocolNmdc {
return NewNmdcProtocol(hc)
} else if hp == HubProtocolAdc {
return NewAdcProtocol(hc)
} else {
return NewAutodetectProtocol(hc)
}
}

102
README.md Normal file
View File

@ -0,0 +1,102 @@
# libnmdc
An NMDC / ADC client protocol library for Golang.
Install via go get: `go get code.ivysaur.me/libnmdc`
## Features
- Connect to NMDC and ADC hubs
- SSL (NMDCS/ADCS) with option to ignore certificate validity
- Autodetect NMDC/ADC protocol by timeout
- Send public and private chat messages, UserCommand support
- Protocol keepalives
- Parse user details (including UserIP2 for NMDC)
- Fast NMDC login via NoHello and QuickList
- Both synchronous (callback) and asynchronous (channel) -based APIs, including example
## Changelog
2018-12-31 0.18.0
- BREAKING: Return error from more functions
- Feature: Add new `EVENT` constant to detect connection failures caused by bad password, hub full, etc
- Convert to Go Modules
2018-03-24 0.17.0
- Feature: Re-add sayInfo() function for reannouncing MyINFO changes
2017-11-26 0.16.0
- Feature: Support connecting to ADC hubs
- BREAKING: Simplify connection API
- Vendor new dependency on github.com/cxmcc/tiger (MIT license)
2017-11-14 0.15.0
- Feature: Fallback reconnection if no data (not even keepalives) are recieved from the hub in 24 hours
- Fix an issue with detecting protocol messages inside multi-line chat messages
- Update examples and the default client version number
2017-02-09 0.14.0
- Fix an issue with crashing on malformed IP addresses supplied by the hub
2017-02-09 0.13.0
- Feature: Implement UserIP2 extension, to retrieve IP addresses of other users
- Enhancement: Implement QuickList extension (reduce one network roundtrip during initial connection)
- Enhancement: Implement NoHello extension (faster connection performance)
- Enhancement: Implement ChatOnly extension
- Fix an issue with not notifying client on all MyINFO updates
2017-02-05 0.12.0
- Fix an issue with mutex deadlock when accessing user information from a callback
- Fix an issue with silent disconnection if a password was required but not present
2016-11-29 0.11.0
- BREAKING: Remove some exported methods
- BREAKING: Fix an issue with missing sufficient parameters in the synchronous API
- Enhancement: Improve output under godoc
- Fix an issue with special characters appearing in recieved private messages
- Fix an issue with parsing active/passive connection modes
- Fix an issue with errors appearing on stdout
2016-10-08 0.10.0
- Feature: Support `$UserCommand`
2016-08-27 0.9.0
- Fix an issue with parsing MyINFO strings with zero-length speed descriptions
- Fix an issue with not storing updated profile information
2016-05-10 0.8.0
- Enhancement: Separate `ClientTag` and `ClientVersion` in `UserInfo` structs
2016-05-08 0.7.0
- BREAKING: Remove direct access to `HubConnection.Users` map
- Feature: Threadsafe user map accessor
- Feature: Option to disable auto-reconnection
- Feature: New `Disconnect()`, `UserCount()`, `UserExists()` functions
- Enhancement: Support `$OpList`, add `IsOperator` member to `UserInfo` structs
- Refactor into multiple files
2016-04-16 0.6.0
- Fix an issue with calling `panic()` on certain types of abnormal network failure
2016-04-04 0.5.0
- Enhancement: Support protocol keepalives
- Enhancement: Support hub redirects (`$ForceMove`)
2016-04-03 0.4.0
- Feature: Add synchronous API
- Fix an issue with reading HubConnection's state parameter
- Fix an issue with buffered protocol commands
2016-04-03 0.3.0
- Feature: Add `SkipVerifyTLS` option
- Fix an issue with calling `panic()` if connection failed
2016-04-02 0.2.0
- Enhancement: Support NMDC-over-TLS (NMDCS)
- Fix an issue recieving private messages
- Fix an issue with calling `panic()` if connection failed
- Fix an issue parsing URIs without a specified port
- Move sample content into directory with excluded build
2016-02-12 0.1.0
- Initial public release

27
UserCommand.go Normal file
View File

@ -0,0 +1,27 @@
package libnmdc
type UserCommandType uint8
const (
USERCOMMAND_TYPE_SEPARATOR UserCommandType = 0
USERCOMMAND_TYPE_RAW UserCommandType = 1
USERCOMMAND_TYPE_NICKLIMITED UserCommandType = 2
USERCOMMAND_TYPE_CLEARALL UserCommandType = 255
)
type UserCommandContext uint8
const (
USERCOMMAND_CONTEXT_HUB UserCommandContext = 1
USERCOMMAND_CONTEXT_USER UserCommandContext = 2
USERCOMMAND_CONTEXT_SEARCH UserCommandContext = 4
USERCOMMAND_CONTEXT_FILELIST UserCommandContext = 8
)
type UserCommand struct {
Type UserCommandType
Context UserCommandContext
Message string
Command string
RemoveThis bool // Currently only set by ADC hubs
}

17
UserFlag.go Normal file
View File

@ -0,0 +1,17 @@
package libnmdc
type UserFlag byte
const (
FLAG_NORMAL UserFlag = 1
FLAG_AWAY_1 UserFlag = 2
FLAG_AWAY_2 UserFlag = 3
FLAG_SERVER_1 UserFlag = 4
FLAG_SERVER_2 UserFlag = 5
FLAG_SERVER_AWAY_1 UserFlag = 6
FLAG_SERVER_AWAY_2 UserFlag = 7
FLAG_FIREBALL_1 UserFlag = 8
FLAG_FIREBALL_2 UserFlag = 9
FLAG_FIREBALL_AWAY_1 UserFlag = 10
FLAG_FIREBALL_AWAY_2 UserFlag = 11
)

View File

@ -1,38 +1,5 @@
// libnmdc project UserInfo.go
package libnmdc
import (
"errors"
"fmt"
"regexp"
"strconv"
"strings"
)
type UserFlag byte
const (
FLAG_NORMAL UserFlag = 1
FLAG_AWAY_1 UserFlag = 2
FLAG_AWAY_2 UserFlag = 3
FLAG_SERVER_1 UserFlag = 4
FLAG_SERVER_2 UserFlag = 5
FLAG_SERVER_AWAY_1 UserFlag = 6
FLAG_SERVER_AWAY_2 UserFlag = 7
FLAG_FIREBALL_1 UserFlag = 8
FLAG_FIREBALL_2 UserFlag = 9
FLAG_FIREBALL_AWAY_1 UserFlag = 10
FLAG_FIREBALL_AWAY_2 UserFlag = 11
)
type ConnectionMode rune
const (
CONNECTIONMODE_ACTIVE ConnectionMode = 'A'
CONNECTIONMODE_PASSIVE ConnectionMode = 'P'
CONNECTIONMODE_SOCKS5 ConnectionMode = '5'
)
// This structure represents a user connected to a hub.
type UserInfo struct {
Nick string
@ -41,118 +8,46 @@ type UserInfo struct {
ClientVersion string
Email string
ShareSize uint64
ConnectionMode ConnectionMode
Flag UserFlag
Slots uint64
Speed string
HubsUnregistered uint64
HubsRegistered uint64
HubsOperator uint64
IsOperator bool
UserInfo_NMDCOnly
UserInfo_ADCOnly
}
var rx_myinfo *regexp.Regexp
var rx_myinfo_notag *regexp.Regexp
type UserInfo_NMDCOnly struct {
Speed string
IPAddress string
ConnectionMode ConnectionMode
}
func init() {
// $ALL <nick> <description>$ $<connection><flag>$<e-mail>$<sharesize>$
rx_myinfo = regexp.MustCompile("(?ms)^\\$ALL ([^ ]+) ([^<]*)<([^,]*)V:([^,]+),M:(.),H:([0-9]+)/([0-9]+)/([0-9]+),S:([0-9]+)>\\$.\\$(.+?)(.)\\$([^$]*)\\$([0-9]*)\\$$")
rx_myinfo_notag = regexp.MustCompile("(?ms)^\\$ALL ([^ ]+) ([^$]*)\\$.\\$(.*)(.)\\$([^$]*)\\$([0-9]*)\\$$") // Fallback for no tag
/*
sample := "$ALL Betty description<ApexDC++ V:1.4.3,M:P,H:9/0/2,S:1>$ $0.01\x01$xyz@xyz.com$53054999578$"
sample = "$ALL ivysaur80 $P$10A$$0$"
u := UserInfo{}
err := u.fromMyINFO(sample)
if err != nil {
fmt.Println(err.Error())
} else {
fmt.Printf("%+v\n", u)
}
os.Exit(1)
*/
type UserInfo_ADCOnly struct {
SharedFiles uint64
UploadSpeedBps uint64
DownloadSpeedBps uint64
IsBot bool
IsRegistered bool
IsSuperUser bool
IsHubOwner bool
IPv4Address string // Passive <==> these fields are not set
IPv6Address string
IPv4UDPPort uint64
IPv6UDPPort uint64
Keyprint string
CID string
SupportFlags map[string]struct{}
}
func NewUserInfo(username string) *UserInfo {
return &UserInfo{
Nick: username,
ConnectionMode: CONNECTIONMODE_PASSIVE,
HubsUnregistered: 1,
UserInfo_NMDCOnly: UserInfo_NMDCOnly{
ConnectionMode: CONNECTIONMODE_PASSIVE,
},
}
}
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 = NMDCUnescape(matches[2])
this.ClientTag = NMDCUnescape(matches[3])
this.ClientVersion = matches[4]
this.ConnectionMode = ConnectionMode(matches[4][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)
this.Speed = matches[10]
this.Flag = UserFlag(matches[11][0])
this.Email = NMDCUnescape(matches[12])
maybeParse(matches[13], &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 = NMDCUnescape(matches[2])
this.ClientTag = ""
this.ClientVersion = "0"
this.ConnectionMode = CONNECTIONMODE_PASSIVE
this.HubsUnregistered = 0
this.HubsRegistered = 0
this.HubsOperator = 0
this.Slots = 0
this.Speed = matches[3]
this.Flag = UserFlag(matches[4][0])
this.Email = NMDCUnescape(matches[5])
maybeParse(matches[6], &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,
)
}

View File

@ -1,47 +0,0 @@
An NMDC client protocol library for Golang.
- Offers both channel-based and synchronous APIs.
- Includes a sample logging client using the channel-based API.
- This code hosting site isn't (yet) compatible with `go get`.
Written in golang
Tags: nmdc
=CHANGELOG=
2016-05-10 r8
- Enhancement: Separate `ClientTag` and `ClientVersion` in `UserInfo` structs
2016-05-08 r7
- BREAKING: Remove direct access to `HubConnection.Users` map
- Feature: Threadsafe user map accessor
- Feature: Option to disable auto-reconnection
- Feature: New `Disconnect()`, `UserCount()`, `UserExists()` functions
- Enhancement: Support `$OpList`, add `IsOperator` member to `UserInfo` structs
- Refactor into multiple files
2016-04-16 r6
- Fix an issue with calling `panic()` on certain types of abnormal network failure
2016-04-04 r5
- Enhancement: Support protocol keepalives
- Enhancement: Support hub redirects (`$ForceMove`)
2016-04-03 r4
- Feature: Add synchronous API
- Fix an issue with reading HubConnection's state parameter
- Fix an issue with buffered protocol commands
2016-04-03 r3
- Feature: Add `SkipVerifyTLS` option
- Fix an issue with calling `panic()` if connection failed
2016-04-02 r2
- Enhancement: Support NMDC-over-TLS (NMDCS)
- Fix an issue recieving private messages
- Fix an issue with calling `panic()` if connection failed
- Fix an issue parsing URIs without a specified port
- Move sample content into directory with excluded build
2016-02-12 r1
- Initial public release

3
go.mod Normal file
View File

@ -0,0 +1,3 @@
module code.ivysaur.me/libnmdc
require github.com/cxmcc/tiger v0.0.0-20170524142333-bde35e2713d7

2
go.sum Normal file
View File

@ -0,0 +1,2 @@
github.com/cxmcc/tiger v0.0.0-20170524142333-bde35e2713d7 h1:jBEtq1t2gpn2kEzvRlCUxvvrxl5aSWkXNPwe/hwvSNQ=
github.com/cxmcc/tiger v0.0.0-20170524142333-bde35e2713d7/go.mod h1:ruCYvt9rtYymAr4rNmfYJrl1dz8HSXUFP7cufqKOsDI=

View File

@ -1,62 +1,28 @@
// libnmdc project libnmdc.go
package libnmdc
import (
"errors"
"fmt"
"regexp"
"strings"
"strconv"
"time"
)
const (
DEFAULT_CLIENT_TAG string = "libnmdc.go"
DEFAULT_HUB_NAME string = "(unknown)"
SEND_KEEPALIVE_EVERY time.Duration = 29 * time.Second
AUTO_RECONNECT_AFTER time.Duration = 30 * time.Second
DEFAULT_CLIENT_TAG string = "libnmdc.go"
DEFAULT_CLIENT_VERSION string = "0.18"
DEFAULT_HUB_NAME string = "(unknown)"
SEND_KEEPALIVE_EVERY time.Duration = 29 * time.Second
AUTO_RECONNECT_AFTER time.Duration = 30 * time.Second
RECONNECT_IF_NO_DATA_RECIEVED_IN time.Duration = 24 * time.Hour // we expect keepalives wayyyy more frequently than this
AUTODETECT_ADC_NMDC_TIMEOUT time.Duration = 3 * time.Second
)
var rx_protocolMessage *regexp.Regexp
var rx_publicChat *regexp.Regexp
var rx_incomingTo *regexp.Regexp
var ErrNotConnected error = errors.New("Not connected")
func init() {
rx_protocolMessage = regexp.MustCompile("(?ms)^[^|]*\\|")
rx_publicChat = regexp.MustCompile("(?ms)^<([^>]*)> (.*)$")
rx_incomingTo = regexp.MustCompile("(?ms)^([^ ]+) From: ([^ ]+) \\$<([^>]*)> (.*)")
}
func NMDCUnescape(encoded string) string {
v1 := strings.Replace(encoded, "&#36;", "$", -1)
v2 := strings.Replace(v1, "&#124;", "|", -1)
return strings.Replace(v2, "&amp;", "&", -1)
}
func NMDCEscape(plaintext string) string {
v1 := strings.Replace(plaintext, "&", "&amp;", -1)
v2 := strings.Replace(v1, "|", "&#124;", -1)
return strings.Replace(v2, "$", "&#36;", -1)
}
func NMDCUnlock(lock []byte) string {
nibble_swap := func(b byte) byte {
return ((b << 4) & 0xF0) | ((b >> 4) & 0x0F)
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
}
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
}

View File

@ -1,38 +0,0 @@
// +build ignore
// This sample file demonstrates use of the libnmdc.go library. It's excluded
// when building the library package, but you can run it via `go run libnmdc_sample.go`.
package main
import (
"fmt"
"libnmdc"
)
func main() {
opts := libnmdc.HubConnectionOptions{
Address: "127.0.0.1",
Self: libnmdc.UserInfo{Nick: "slowpoke9"},
}
hub := opts.Connect()
for {
event := <-hub.OnEvent
switch event.EventType {
case libnmdc.EVENT_CONNECTION_STATE_CHANGED:
fmt.Printf("Connection -- %s (%s)\n", event.StateChange.Format(), event.Message)
case libnmdc.EVENT_PUBLIC:
fmt.Printf("Message from '%s': '%s'\n", event.Nick, event.Message)
if event.Message == "how are you" {
hub.SayPublic("good thanks!")
}
default:
fmt.Printf("%+v\n", event)
}
}
}

41
tth.go Normal file
View File

@ -0,0 +1,41 @@
package libnmdc
import (
"encoding/base32"
"errors"
"strings"
"github.com/cxmcc/tiger"
)
// Base32 encodes the input slice in BASE32 string format without any trailing
// padding equals characters.
func Base32(input []byte) string {
return strings.TrimRight(base32.StdEncoding.EncodeToString(input), "=")
}
// TTH returns the TTH hash of a string in raw byte format. Use the Base32()
// function to convert it to normal string format.
// This is a basic implementation that only supports content up to 1024 bytes in length.
func TTH(input string) ([]byte, error) {
// Short segments do not need to be padded.
// Content above 1024 bytes needs tree handling (0x00 prefix for leaf nodes,
// 0x01 prefix for hash nodes) but for content less than 1024 bytes, just
// return the leaf hash
// @ref http://adc.sourceforge.net/draft-jchapweske-thex-02.html
if len(input) > 1024 {
return nil, errors.New("TTH content exceeded 1024 bytes")
}
// Single leaf hash only
leafHash := tiger.New()
leafHash.Write([]byte("\x00" + input))
return leafHash.Sum(nil), nil
}
func Tiger(input string) []byte {
leafHash := tiger.New()
leafHash.Write([]byte(input))
return leafHash.Sum(nil)
}

36
tth_test.go Normal file
View File

@ -0,0 +1,36 @@
package libnmdc
import (
"strings"
"testing"
)
func TestTTH(t *testing.T) {
// echo -n 'hello world' | tthsum
testCases := [][2]string{
[2]string{"hello world", "ZIIVRZDR2FD3W4KKNMNYUU3765LPPK7BWY64CHI"},
[2]string{"", "LWPNACQDBZRYXW3VHJVCJ64QBZNGHOHHHZWCLNQ"},
[2]string{"\x00", "VK54ZIEEVTWNAUI5D5RDFIL37LX2IQNSTAXFKSA"},
[2]string{strings.Repeat("A", 1024), "L66Q4YVNAFWVS23X2HJIRA5ZJ7WXR3F26RSASFA"},
}
short := func(s string) string {
if len(s) > 15 {
return s[0:15] + "..."
}
return s
}
for _, testCase := range testCases {
input, expected := testCase[0], testCase[1]
result, err := TTH(input)
if err != nil {
t.Fatalf("Error getting TTH for '%s': %s", short(input), err.Error())
}
if Base32(result) != expected {
t.Fatalf("Wrong TTH for '%s' (got '%s' expected '%s')", short(input), result, expected)
}
}
}

22
vendor/github.com/cxmcc/tiger/.gitignore generated vendored Normal file
View File

@ -0,0 +1,22 @@
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe

7
vendor/github.com/cxmcc/tiger/.travis.yml generated vendored Normal file
View File

@ -0,0 +1,7 @@
language: go
go:
- 1.x
- 1.6
- 1.7.x
- master

20
vendor/github.com/cxmcc/tiger/LICENSE generated vendored Normal file
View File

@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2013 Xiuming Chen
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

48
vendor/github.com/cxmcc/tiger/README.md generated vendored Normal file
View File

@ -0,0 +1,48 @@
Tiger cryptographic hash function for Go
-----
[![Build Status](https://travis-ci.org/cxmcc/tiger.svg?branch=master)](https://travis-ci.org/cxmcc/tiger)
[![GoDoc](http://godoc.org/github.com/cxmcc/tiger?status.png)](http://godoc.org/github.com/cxmcc/tiger)
### About Tiger
* Tiger cryptographic hash function is designed by Ross Anderson and Eli Biham in 1995.
* The size of a Tiger hash value is 192 bits. Truncated versions (Tiger/128, Tiger/160) are simply prefixes of Tiger/192.
* Tiger2 is a variant where the message is padded by first appending a byte 0x80, rather than 0x01 as in the case of Tiger.
* Links: [paper](http://www.cs.technion.ac.il/~biham/Reports/Tiger/), [wikipedia](http://en.wikipedia.org/wiki/Tiger_\(cryptography\))
### API Documentation
Implementing [hash.Hash](http://golang.org/pkg/hash/#Hash). Usage is pretty much the same as other stanard hashing libraries.
Documentation currently available at Godoc: [http://godoc.org/github.com/cxmcc/tiger](http://godoc.org/github.com/cxmcc/tiger)
### Installing
~~~
go get github.com/cxmcc/tiger
~~~
### Example
~~~ go
package main
import (
"fmt"
"io"
"github.com/cxmcc/tiger"
)
func main() {
h := tiger.New()
io.WriteString(h, "Example for tiger")
fmt.Printf("Output: %x\n", h.Sum(nil))
// Output: 82bd060e19f945014f0063e8f0e6d7decfa9edfd97e76743
}
~~~
### License
It's MIT License

96
vendor/github.com/cxmcc/tiger/compress.go generated vendored Normal file
View File

@ -0,0 +1,96 @@
package tiger
import (
"encoding/binary"
"unsafe"
)
var littleEndian bool
func init() {
x := uint32(0x04030201)
y := [4]byte{0x1, 0x2, 0x3, 0x4}
littleEndian = *(*[4]byte)(unsafe.Pointer(&x)) == y
}
func pass(a, b, c uint64, x []uint64, mul uint64) (uint64, uint64, uint64) {
a, b, c = round(a, b, c, x[0], mul)
b, c, a = round(b, c, a, x[1], mul)
c, a, b = round(c, a, b, x[2], mul)
a, b, c = round(a, b, c, x[3], mul)
b, c, a = round(b, c, a, x[4], mul)
c, a, b = round(c, a, b, x[5], mul)
a, b, c = round(a, b, c, x[6], mul)
b, c, a = round(b, c, a, x[7], mul)
return a, b, c
}
func round(a, b, c, x, mul uint64) (uint64, uint64, uint64) {
c ^= x
a -= t1[c&0xff] ^ t2[(c>>16)&0xff] ^ t3[(c>>32)&0xff] ^ t4[(c>>48)&0xff]
b += t4[(c>>8)&0xff] ^ t3[(c>>24)&0xff] ^ t2[(c>>40)&0xff] ^ t1[(c>>56)&0xff]
b *= mul
return a, b, c
}
func keySchedule(x []uint64) {
x[0] -= x[7] ^ 0xa5a5a5a5a5a5a5a5
x[1] ^= x[0]
x[2] += x[1]
x[3] -= x[2] ^ ((^x[1]) << 19)
x[4] ^= x[3]
x[5] += x[4]
x[6] -= x[5] ^ ((^x[4]) >> 23)
x[7] ^= x[6]
x[0] += x[7]
x[1] -= x[0] ^ ((^x[7]) << 19)
x[2] ^= x[1]
x[3] += x[2]
x[4] -= x[3] ^ ((^x[2]) >> 23)
x[5] ^= x[4]
x[6] += x[5]
x[7] -= x[6] ^ 0x0123456789abcdef
}
func (d *digest) compress(data []byte) {
// save_abc
a := d.a
b := d.b
c := d.c
var x []uint64
if littleEndian {
x = []uint64{
binary.LittleEndian.Uint64(data[0:8]),
binary.LittleEndian.Uint64(data[8:16]),
binary.LittleEndian.Uint64(data[16:24]),
binary.LittleEndian.Uint64(data[24:32]),
binary.LittleEndian.Uint64(data[32:40]),
binary.LittleEndian.Uint64(data[40:48]),
binary.LittleEndian.Uint64(data[48:56]),
binary.LittleEndian.Uint64(data[56:64]),
}
} else {
x = []uint64{
binary.BigEndian.Uint64(data[0:8]),
binary.BigEndian.Uint64(data[8:16]),
binary.BigEndian.Uint64(data[16:24]),
binary.BigEndian.Uint64(data[24:32]),
binary.BigEndian.Uint64(data[32:40]),
binary.BigEndian.Uint64(data[40:48]),
binary.BigEndian.Uint64(data[48:56]),
binary.BigEndian.Uint64(data[56:64]),
}
}
d.a, d.b, d.c = pass(d.a, d.b, d.c, x, 5)
keySchedule(x)
d.c, d.a, d.b = pass(d.c, d.a, d.b, x, 7)
keySchedule(x)
d.b, d.c, d.a = pass(d.b, d.c, d.a, x, 9)
// feedforward
d.a ^= a
d.b -= b
d.c += c
}

269
vendor/github.com/cxmcc/tiger/sboxes.go generated vendored Normal file
View File

@ -0,0 +1,269 @@
package tiger
var t1 = [...]uint64{
0x02aab17cf7e90c5e, 0xac424b03e243a8ec, 0x72cd5be30dd5fcd3, 0x6d019b93f6f97f3a,
0xcd9978ffd21f9193, 0x7573a1c9708029e2, 0xb164326b922a83c3, 0x46883eee04915870,
0xeaace3057103ece6, 0xc54169b808a3535c, 0x4ce754918ddec47c, 0x0aa2f4dfdc0df40c,
0x10b76f18a74dbefa, 0xc6ccb6235ad1ab6a, 0x13726121572fe2ff, 0x1a488c6f199d921e,
0x4bc9f9f4da0007ca, 0x26f5e6f6e85241c7, 0x859079dbea5947b6, 0x4f1885c5c99e8c92,
0xd78e761ea96f864b, 0x8e36428c52b5c17d, 0x69cf6827373063c1, 0xb607c93d9bb4c56e,
0x7d820e760e76b5ea, 0x645c9cc6f07fdc42, 0xbf38a078243342e0, 0x5f6b343c9d2e7d04,
0xf2c28aeb600b0ec6, 0x6c0ed85f7254bcac, 0x71592281a4db4fe5, 0x1967fa69ce0fed9f,
0xfd5293f8b96545db, 0xc879e9d7f2a7600b, 0x860248920193194e, 0xa4f9533b2d9cc0b3,
0x9053836c15957613, 0xdb6dcf8afc357bf1, 0x18beea7a7a370f57, 0x037117ca50b99066,
0x6ab30a9774424a35, 0xf4e92f02e325249b, 0x7739db07061ccae1, 0xd8f3b49ceca42a05,
0xbd56be3f51382f73, 0x45faed5843b0bb28, 0x1c813d5c11bf1f83, 0x8af0e4b6d75fa169,
0x33ee18a487ad9999, 0x3c26e8eab1c94410, 0xb510102bc0a822f9, 0x141eef310ce6123b,
0xfc65b90059ddb154, 0xe0158640c5e0e607, 0x884e079826c3a3cf, 0x930d0d9523c535fd,
0x35638d754e9a2b00, 0x4085fccf40469dd5, 0xc4b17ad28be23a4c, 0xcab2f0fc6a3e6a2e,
0x2860971a6b943fcd, 0x3dde6ee212e30446, 0x6222f32ae01765ae, 0x5d550bb5478308fe,
0xa9efa98da0eda22a, 0xc351a71686c40da7, 0x1105586d9c867c84, 0xdcffee85fda22853,
0xccfbd0262c5eef76, 0xbaf294cb8990d201, 0xe69464f52afad975, 0x94b013afdf133e14,
0x06a7d1a32823c958, 0x6f95fe5130f61119, 0xd92ab34e462c06c0, 0xed7bde33887c71d2,
0x79746d6e6518393e, 0x5ba419385d713329, 0x7c1ba6b948a97564, 0x31987c197bfdac67,
0xde6c23c44b053d02, 0x581c49fed002d64d, 0xdd474d6338261571, 0xaa4546c3e473d062,
0x928fce349455f860, 0x48161bbacaab94d9, 0x63912430770e6f68, 0x6ec8a5e602c6641c,
0x87282515337ddd2b, 0x2cda6b42034b701b, 0xb03d37c181cb096d, 0xe108438266c71c6f,
0x2b3180c7eb51b255, 0xdf92b82f96c08bbc, 0x5c68c8c0a632f3ba, 0x5504cc861c3d0556,
0xabbfa4e55fb26b8f, 0x41848b0ab3baceb4, 0xb334a273aa445d32, 0xbca696f0a85ad881,
0x24f6ec65b528d56c, 0x0ce1512e90f4524a, 0x4e9dd79d5506d35a, 0x258905fac6ce9779,
0x2019295b3e109b33, 0xf8a9478b73a054cc, 0x2924f2f934417eb0, 0x3993357d536d1bc4,
0x38a81ac21db6ff8b, 0x47c4fbf17d6016bf, 0x1e0faadd7667e3f5, 0x7abcff62938beb96,
0xa78dad948fc179c9, 0x8f1f98b72911e50d, 0x61e48eae27121a91, 0x4d62f7ad31859808,
0xeceba345ef5ceaeb, 0xf5ceb25ebc9684ce, 0xf633e20cb7f76221, 0xa32cdf06ab8293e4,
0x985a202ca5ee2ca4, 0xcf0b8447cc8a8fb1, 0x9f765244979859a3, 0xa8d516b1a1240017,
0x0bd7ba3ebb5dc726, 0xe54bca55b86adb39, 0x1d7a3afd6c478063, 0x519ec608e7669edd,
0x0e5715a2d149aa23, 0x177d4571848ff194, 0xeeb55f3241014c22, 0x0f5e5ca13a6e2ec2,
0x8029927b75f5c361, 0xad139fabc3d6e436, 0x0d5df1a94ccf402f, 0x3e8bd948bea5dfc8,
0xa5a0d357bd3ff77e, 0xa2d12e251f74f645, 0x66fd9e525e81a082, 0x2e0c90ce7f687a49,
0xc2e8bcbeba973bc5, 0x000001bce509745f, 0x423777bbe6dab3d6, 0xd1661c7eaef06eb5,
0xa1781f354daacfd8, 0x2d11284a2b16affc, 0xf1fc4f67fa891d1f, 0x73ecc25dcb920ada,
0xae610c22c2a12651, 0x96e0a810d356b78a, 0x5a9a381f2fe7870f, 0xd5ad62ede94e5530,
0xd225e5e8368d1427, 0x65977b70c7af4631, 0x99f889b2de39d74f, 0x233f30bf54e1d143,
0x9a9675d3d9a63c97, 0x5470554ff334f9a8, 0x166acb744a4f5688, 0x70c74caab2e4aead,
0xf0d091646f294d12, 0x57b82a89684031d1, 0xefd95a5a61be0b6b, 0x2fbd12e969f2f29a,
0x9bd37013feff9fe8, 0x3f9b0404d6085a06, 0x4940c1f3166cfe15, 0x09542c4dcdf3defb,
0xb4c5218385cd5ce3, 0xc935b7dc4462a641, 0x3417f8a68ed3b63f, 0xb80959295b215b40,
0xf99cdaef3b8c8572, 0x018c0614f8fcb95d, 0x1b14accd1a3acdf3, 0x84d471f200bb732d,
0xc1a3110e95e8da16, 0x430a7220bf1a82b8, 0xb77e090d39df210e, 0x5ef4bd9f3cd05e9d,
0x9d4ff6da7e57a444, 0xda1d60e183d4a5f8, 0xb287c38417998e47, 0xfe3edc121bb31886,
0xc7fe3ccc980ccbef, 0xe46fb590189bfd03, 0x3732fd469a4c57dc, 0x7ef700a07cf1ad65,
0x59c64468a31d8859, 0x762fb0b4d45b61f6, 0x155baed099047718, 0x68755e4c3d50baa6,
0xe9214e7f22d8b4df, 0x2addbf532eac95f4, 0x32ae3909b4bd0109, 0x834df537b08e3450,
0xfa209da84220728d, 0x9e691d9b9efe23f7, 0x0446d288c4ae8d7f, 0x7b4cc524e169785b,
0x21d87f0135ca1385, 0xcebb400f137b8aa5, 0x272e2b66580796be, 0x3612264125c2b0de,
0x057702bdad1efbb2, 0xd4babb8eacf84be9, 0x91583139641bc67b, 0x8bdc2de08036e024,
0x603c8156f49f68ed, 0xf7d236f7dbef5111, 0x9727c4598ad21e80, 0xa08a0896670a5fd7,
0xcb4a8f4309eba9cb, 0x81af564b0f7036a1, 0xc0b99aa778199abd, 0x959f1ec83fc8e952,
0x8c505077794a81b9, 0x3acaaf8f056338f0, 0x07b43f50627a6778, 0x4a44ab49f5eccc77,
0x3bc3d6e4b679ee98, 0x9cc0d4d1cf14108c, 0x4406c00b206bc8a0, 0x82a18854c8d72d89,
0x67e366b35c3c432c, 0xb923dd61102b37f2, 0x56ab2779d884271d, 0xbe83e1b0ff1525af,
0xfb7c65d4217e49a9, 0x6bdbe0e76d48e7d4, 0x08df828745d9179e, 0x22ea6a9add53bd34,
0xe36e141c5622200a, 0x7f805d1b8cb750ee, 0xafe5c7a59f58e837, 0xe27f996a4fb1c23c,
0xd3867dfb0775f0d0, 0xd0e673de6e88891a, 0x123aeb9eafb86c25, 0x30f1d5d5c145b895,
0xbb434a2dee7269e7, 0x78cb67ecf931fa38, 0xf33b0372323bbf9c, 0x52d66336fb279c74,
0x505f33ac0afb4eaa, 0xe8a5cd99a2cce187, 0x534974801e2d30bb, 0x8d2d5711d5876d90,
0x1f1a412891bc038e, 0xd6e2e71d82e56648, 0x74036c3a497732b7, 0x89b67ed96361f5ab,
0xffed95d8f1ea02a2, 0xe72b3bd61464d43d, 0xa6300f170bdc4820, 0xebc18760ed78a77a,
}
var t2 = [...]uint64{
0xe6a6be5a05a12138, 0xb5a122a5b4f87c98, 0x563c6089140b6990, 0x4c46cb2e391f5dd5,
0xd932addbc9b79434, 0x08ea70e42015aff5, 0xd765a6673e478cf1, 0xc4fb757eab278d99,
0xdf11c6862d6e0692, 0xddeb84f10d7f3b16, 0x6f2ef604a665ea04, 0x4a8e0f0ff0e0dfb3,
0xa5edeef83dbcba51, 0xfc4f0a2a0ea4371e, 0xe83e1da85cb38429, 0xdc8ff882ba1b1ce2,
0xcd45505e8353e80d, 0x18d19a00d4db0717, 0x34a0cfeda5f38101, 0x0be77e518887caf2,
0x1e341438b3c45136, 0xe05797f49089ccf9, 0xffd23f9df2591d14, 0x543dda228595c5cd,
0x661f81fd99052a33, 0x8736e641db0f7b76, 0x15227725418e5307, 0xe25f7f46162eb2fa,
0x48a8b2126c13d9fe, 0xafdc541792e76eea, 0x03d912bfc6d1898f, 0x31b1aafa1b83f51b,
0xf1ac2796e42ab7d9, 0x40a3a7d7fcd2ebac, 0x1056136d0afbbcc5, 0x7889e1dd9a6d0c85,
0xd33525782a7974aa, 0xa7e25d09078ac09b, 0xbd4138b3eac6edd0, 0x920abfbe71eb9e70,
0xa2a5d0f54fc2625c, 0xc054e36b0b1290a3, 0xf6dd59ff62fe932b, 0x3537354511a8ac7d,
0xca845e9172fadcd4, 0x84f82b60329d20dc, 0x79c62ce1cd672f18, 0x8b09a2add124642c,
0xd0c1e96a19d9e726, 0x5a786a9b4ba9500c, 0x0e020336634c43f3, 0xc17b474aeb66d822,
0x6a731ae3ec9baac2, 0x8226667ae0840258, 0x67d4567691caeca5, 0x1d94155c4875adb5,
0x6d00fd985b813fdf, 0x51286efcb774cd06, 0x5e8834471fa744af, 0xf72ca0aee761ae2e,
0xbe40e4cdaee8e09a, 0xe9970bbb5118f665, 0x726e4beb33df1964, 0x703b000729199762,
0x4631d816f5ef30a7, 0xb880b5b51504a6be, 0x641793c37ed84b6c, 0x7b21ed77f6e97d96,
0x776306312ef96b73, 0xae528948e86ff3f4, 0x53dbd7f286a3f8f8, 0x16cadce74cfc1063,
0x005c19bdfa52c6dd, 0x68868f5d64d46ad3, 0x3a9d512ccf1e186a, 0x367e62c2385660ae,
0xe359e7ea77dcb1d7, 0x526c0773749abe6e, 0x735ae5f9d09f734b, 0x493fc7cc8a558ba8,
0xb0b9c1533041ab45, 0x321958ba470a59bd, 0x852db00b5f46c393, 0x91209b2bd336b0e5,
0x6e604f7d659ef19f, 0xb99a8ae2782ccb24, 0xccf52ab6c814c4c7, 0x4727d9afbe11727b,
0x7e950d0c0121b34d, 0x756f435670ad471f, 0xf5add442615a6849, 0x4e87e09980b9957a,
0x2acfa1df50aee355, 0xd898263afd2fd556, 0xc8f4924dd80c8fd6, 0xcf99ca3d754a173a,
0xfe477bacaf91bf3c, 0xed5371f6d690c12d, 0x831a5c285e687094, 0xc5d3c90a3708a0a4,
0x0f7f903717d06580, 0x19f9bb13b8fdf27f, 0xb1bd6f1b4d502843, 0x1c761ba38fff4012,
0x0d1530c4e2e21f3b, 0x8943ce69a7372c8a, 0xe5184e11feb5ce66, 0x618bdb80bd736621,
0x7d29bad68b574d0b, 0x81bb613e25e6fe5b, 0x071c9c10bc07913f, 0xc7beeb7909ac2d97,
0xc3e58d353bc5d757, 0xeb017892f38f61e8, 0xd4effb9c9b1cc21a, 0x99727d26f494f7ab,
0xa3e063a2956b3e03, 0x9d4a8b9a4aa09c30, 0x3f6ab7d500090fb4, 0x9cc0f2a057268ac0,
0x3dee9d2dedbf42d1, 0x330f49c87960a972, 0xc6b2720287421b41, 0x0ac59ec07c00369c,
0xef4eac49cb353425, 0xf450244eef0129d8, 0x8acc46e5caf4deb6, 0x2ffeab63989263f7,
0x8f7cb9fe5d7a4578, 0x5bd8f7644e634635, 0x427a7315bf2dc900, 0x17d0c4aa2125261c,
0x3992486c93518e50, 0xb4cbfee0a2d7d4c3, 0x7c75d6202c5ddd8d, 0xdbc295d8e35b6c61,
0x60b369d302032b19, 0xce42685fdce44132, 0x06f3ddb9ddf65610, 0x8ea4d21db5e148f0,
0x20b0fce62fcd496f, 0x2c1b912358b0ee31, 0xb28317b818f5a308, 0xa89c1e189ca6d2cf,
0x0c6b18576aaadbc8, 0xb65deaa91299fae3, 0xfb2b794b7f1027e7, 0x04e4317f443b5beb,
0x4b852d325939d0a6, 0xd5ae6beefb207ffc, 0x309682b281c7d374, 0xbae309a194c3b475,
0x8cc3f97b13b49f05, 0x98a9422ff8293967, 0x244b16b01076ff7c, 0xf8bf571c663d67ee,
0x1f0d6758eee30da1, 0xc9b611d97adeb9b7, 0xb7afd5887b6c57a2, 0x6290ae846b984fe1,
0x94df4cdeacc1a5fd, 0x058a5bd1c5483aff, 0x63166cc142ba3c37, 0x8db8526eb2f76f40,
0xe10880036f0d6d4e, 0x9e0523c9971d311d, 0x45ec2824cc7cd691, 0x575b8359e62382c9,
0xfa9e400dc4889995, 0xd1823ecb45721568, 0xdafd983b8206082f, 0xaa7d29082386a8cb,
0x269fcd4403b87588, 0x1b91f5f728bdd1e0, 0xe4669f39040201f6, 0x7a1d7c218cf04ade,
0x65623c29d79ce5ce, 0x2368449096c00bb1, 0xab9bf1879da503ba, 0xbc23ecb1a458058e,
0x9a58df01bb401ecc, 0xa070e868a85f143d, 0x4ff188307df2239e, 0x14d565b41a641183,
0xee13337452701602, 0x950e3dcf3f285e09, 0x59930254b9c80953, 0x3bf299408930da6d,
0xa955943f53691387, 0xa15edecaa9cb8784, 0x29142127352be9a0, 0x76f0371fff4e7afb,
0x0239f450274f2228, 0xbb073af01d5e868b, 0xbfc80571c10e96c1, 0xd267088568222e23,
0x9671a3d48e80b5b0, 0x55b5d38ae193bb81, 0x693ae2d0a18b04b8, 0x5c48b4ecadd5335f,
0xfd743b194916a1ca, 0x2577018134be98c4, 0xe77987e83c54a4ad, 0x28e11014da33e1b9,
0x270cc59e226aa213, 0x71495f756d1a5f60, 0x9be853fb60afef77, 0xadc786a7f7443dbf,
0x0904456173b29a82, 0x58bc7a66c232bd5e, 0xf306558c673ac8b2, 0x41f639c6b6c9772a,
0x216defe99fda35da, 0x11640cc71c7be615, 0x93c43694565c5527, 0xea038e6246777839,
0xf9abf3ce5a3e2469, 0x741e768d0fd312d2, 0x0144b883ced652c6, 0xc20b5a5ba33f8552,
0x1ae69633c3435a9d, 0x97a28ca4088cfdec, 0x8824a43c1e96f420, 0x37612fa66eeea746,
0x6b4cb165f9cf0e5a, 0x43aa1c06a0abfb4a, 0x7f4dc26ff162796b, 0x6cbacc8e54ed9b0f,
0xa6b7ffefd2bb253e, 0x2e25bc95b0a29d4f, 0x86d6a58bdef1388c, 0xded74ac576b6f054,
0x8030bdbc2b45805d, 0x3c81af70e94d9289, 0x3eff6dda9e3100db, 0xb38dc39fdfcc8847,
0x123885528d17b87e, 0xf2da0ed240b1b642, 0x44cefadcd54bf9a9, 0x1312200e433c7ee6,
0x9ffcc84f3a78c748, 0xf0cd1f72248576bb, 0xec6974053638cfe4, 0x2ba7b67c0cec4e4c,
0xac2f4df3e5ce32ed, 0xcb33d14326ea4c11, 0xa4e9044cc77e58bc, 0x5f513293d934fcef,
0x5dc9645506e55444, 0x50de418f317de40a, 0x388cb31a69dde259, 0x2db4a83455820a86,
0x9010a91e84711ae9, 0x4df7f0b7b1498371, 0xd62a2eabc0977179, 0x22fac097aa8d5c0e,
}
var t3 = [...]uint64{
0xf49fcc2ff1daf39b, 0x487fd5c66ff29281, 0xe8a30667fcdca83f, 0x2c9b4be3d2fcce63,
0xda3ff74b93fbbbc2, 0x2fa165d2fe70ba66, 0xa103e279970e93d4, 0xbecdec77b0e45e71,
0xcfb41e723985e497, 0xb70aaa025ef75017, 0xd42309f03840b8e0, 0x8efc1ad035898579,
0x96c6920be2b2abc5, 0x66af4163375a9172, 0x2174abdcca7127fb, 0xb33ccea64a72ff41,
0xf04a4933083066a5, 0x8d970acdd7289af5, 0x8f96e8e031c8c25e, 0xf3fec02276875d47,
0xec7bf310056190dd, 0xf5adb0aebb0f1491, 0x9b50f8850fd58892, 0x4975488358b74de8,
0xa3354ff691531c61, 0x0702bbe481d2c6ee, 0x89fb24057deded98, 0xac3075138596e902,
0x1d2d3580172772ed, 0xeb738fc28e6bc30d, 0x5854ef8f63044326, 0x9e5c52325add3bbe,
0x90aa53cf325c4623, 0xc1d24d51349dd067, 0x2051cfeea69ea624, 0x13220f0a862e7e4f,
0xce39399404e04864, 0xd9c42ca47086fcb7, 0x685ad2238a03e7cc, 0x066484b2ab2ff1db,
0xfe9d5d70efbf79ec, 0x5b13b9dd9c481854, 0x15f0d475ed1509ad, 0x0bebcd060ec79851,
0xd58c6791183ab7f8, 0xd1187c5052f3eee4, 0xc95d1192e54e82ff, 0x86eea14cb9ac6ca2,
0x3485beb153677d5d, 0xdd191d781f8c492a, 0xf60866baa784ebf9, 0x518f643ba2d08c74,
0x8852e956e1087c22, 0xa768cb8dc410ae8d, 0x38047726bfec8e1a, 0xa67738b4cd3b45aa,
0xad16691cec0dde19, 0xc6d4319380462e07, 0xc5a5876d0ba61938, 0x16b9fa1fa58fd840,
0x188ab1173ca74f18, 0xabda2f98c99c021f, 0x3e0580ab134ae816, 0x5f3b05b773645abb,
0x2501a2be5575f2f6, 0x1b2f74004e7e8ba9, 0x1cd7580371e8d953, 0x7f6ed89562764e30,
0xb15926ff596f003d, 0x9f65293da8c5d6b9, 0x6ecef04dd690f84c, 0x4782275fff33af88,
0xe41433083f820801, 0xfd0dfe409a1af9b5, 0x4325a3342cdb396b, 0x8ae77e62b301b252,
0xc36f9e9f6655615a, 0x85455a2d92d32c09, 0xf2c7dea949477485, 0x63cfb4c133a39eba,
0x83b040cc6ebc5462, 0x3b9454c8fdb326b0, 0x56f56a9e87ffd78c, 0x2dc2940d99f42bc6,
0x98f7df096b096e2d, 0x19a6e01e3ad852bf, 0x42a99ccbdbd4b40b, 0xa59998af45e9c559,
0x366295e807d93186, 0x6b48181bfaa1f773, 0x1fec57e2157a0a1d, 0x4667446af6201ad5,
0xe615ebcacfb0f075, 0xb8f31f4f68290778, 0x22713ed6ce22d11e, 0x3057c1a72ec3c93b,
0xcb46acc37c3f1f2f, 0xdbb893fd02aaf50e, 0x331fd92e600b9fcf, 0xa498f96148ea3ad6,
0xa8d8426e8b6a83ea, 0xa089b274b7735cdc, 0x87f6b3731e524a11, 0x118808e5cbc96749,
0x9906e4c7b19bd394, 0xafed7f7e9b24a20c, 0x6509eadeeb3644a7, 0x6c1ef1d3e8ef0ede,
0xb9c97d43e9798fb4, 0xa2f2d784740c28a3, 0x7b8496476197566f, 0x7a5be3e6b65f069d,
0xf96330ed78be6f10, 0xeee60de77a076a15, 0x2b4bee4aa08b9bd0, 0x6a56a63ec7b8894e,
0x02121359ba34fef4, 0x4cbf99f8283703fc, 0x398071350caf30c8, 0xd0a77a89f017687a,
0xf1c1a9eb9e423569, 0x8c7976282dee8199, 0x5d1737a5dd1f7abd, 0x4f53433c09a9fa80,
0xfa8b0c53df7ca1d9, 0x3fd9dcbc886ccb77, 0xc040917ca91b4720, 0x7dd00142f9d1dcdf,
0x8476fc1d4f387b58, 0x23f8e7c5f3316503, 0x032a2244e7e37339, 0x5c87a5d750f5a74b,
0x082b4cc43698992e, 0xdf917becb858f63c, 0x3270b8fc5bf86dda, 0x10ae72bb29b5dd76,
0x576ac94e7700362b, 0x1ad112dac61efb8f, 0x691bc30ec5faa427, 0xff246311cc327143,
0x3142368e30e53206, 0x71380e31e02ca396, 0x958d5c960aad76f1, 0xf8d6f430c16da536,
0xc8ffd13f1be7e1d2, 0x7578ae66004ddbe1, 0x05833f01067be646, 0xbb34b5ad3bfe586d,
0x095f34c9a12b97f0, 0x247ab64525d60ca8, 0xdcdbc6f3017477d1, 0x4a2e14d4decad24d,
0xbdb5e6d9be0a1eeb, 0x2a7e70f7794301ab, 0xdef42d8a270540fd, 0x01078ec0a34c22c1,
0xe5de511af4c16387, 0x7ebb3a52bd9a330a, 0x77697857aa7d6435, 0x004e831603ae4c32,
0xe7a21020ad78e312, 0x9d41a70c6ab420f2, 0x28e06c18ea1141e6, 0xd2b28cbd984f6b28,
0x26b75f6c446e9d83, 0xba47568c4d418d7f, 0xd80badbfe6183d8e, 0x0e206d7f5f166044,
0xe258a43911cbca3e, 0x723a1746b21dc0bc, 0xc7caa854f5d7cdd3, 0x7cac32883d261d9c,
0x7690c26423ba942c, 0x17e55524478042b8, 0xe0be477656a2389f, 0x4d289b5e67ab2da0,
0x44862b9c8fbbfd31, 0xb47cc8049d141365, 0x822c1b362b91c793, 0x4eb14655fb13dfd8,
0x1ecbba0714e2a97b, 0x6143459d5cde5f14, 0x53a8fbf1d5f0ac89, 0x97ea04d81c5e5b00,
0x622181a8d4fdb3f3, 0xe9bcd341572a1208, 0x1411258643cce58a, 0x9144c5fea4c6e0a4,
0x0d33d06565cf620f, 0x54a48d489f219ca1, 0xc43e5eac6d63c821, 0xa9728b3a72770daf,
0xd7934e7b20df87ef, 0xe35503b61a3e86e5, 0xcae321fbc819d504, 0x129a50b3ac60bfa6,
0xcd5e68ea7e9fb6c3, 0xb01c90199483b1c7, 0x3de93cd5c295376c, 0xaed52edf2ab9ad13,
0x2e60f512c0a07884, 0xbc3d86a3e36210c9, 0x35269d9b163951ce, 0x0c7d6e2ad0cdb5fa,
0x59e86297d87f5733, 0x298ef221898db0e7, 0x55000029d1a5aa7e, 0x8bc08ae1b5061b45,
0xc2c31c2b6c92703a, 0x94cc596baf25ef42, 0x0a1d73db22540456, 0x04b6a0f9d9c4179a,
0xeffdafa2ae3d3c60, 0xf7c8075bb49496c4, 0x9cc5c7141d1cd4e3, 0x78bd1638218e5534,
0xb2f11568f850246a, 0xedfabcfa9502bc29, 0x796ce5f2da23051b, 0xaae128b0dc93537c,
0x3a493da0ee4b29ae, 0xb5df6b2c416895d7, 0xfcabbd25122d7f37, 0x70810b58105dc4b1,
0xe10fdd37f7882a90, 0x524dcab5518a3f5c, 0x3c9e85878451255b, 0x4029828119bd34e2,
0x74a05b6f5d3ceccb, 0xb610021542e13eca, 0x0ff979d12f59e2ac, 0x6037da27e4f9cc50,
0x5e92975a0df1847d, 0xd66de190d3e623fe, 0x5032d6b87b568048, 0x9a36b7ce8235216e,
0x80272a7a24f64b4a, 0x93efed8b8c6916f7, 0x37ddbff44cce1555, 0x4b95db5d4b99bd25,
0x92d3fda169812fc0, 0xfb1a4a9a90660bb6, 0x730c196946a4b9b2, 0x81e289aa7f49da68,
0x64669a0f83b1a05f, 0x27b3ff7d9644f48b, 0xcc6b615c8db675b3, 0x674f20b9bcebbe95,
0x6f31238275655982, 0x5ae488713e45cf05, 0xbf619f9954c21157, 0xeabac46040a8eae9,
0x454c6fe9f2c0c1cd, 0x419cf6496412691c, 0xd3dc3bef265b0f70, 0x6d0e60f5c3578a9e,
}
var t4 = [...]uint64{
0x5b0e608526323c55, 0x1a46c1a9fa1b59f5, 0xa9e245a17c4c8ffa, 0x65ca5159db2955d7,
0x05db0a76ce35afc2, 0x81eac77ea9113d45, 0x528ef88ab6ac0a0d, 0xa09ea253597be3ff,
0x430ddfb3ac48cd56, 0xc4b3a67af45ce46f, 0x4ececfd8fbe2d05e, 0x3ef56f10b39935f0,
0x0b22d6829cd619c6, 0x17fd460a74df2069, 0x6cf8cc8e8510ed40, 0xd6c824bf3a6ecaa7,
0x61243d581a817049, 0x048bacb6bbc163a2, 0xd9a38ac27d44cc32, 0x7fddff5baaf410ab,
0xad6d495aa804824b, 0xe1a6a74f2d8c9f94, 0xd4f7851235dee8e3, 0xfd4b7f886540d893,
0x247c20042aa4bfda, 0x096ea1c517d1327c, 0xd56966b4361a6685, 0x277da5c31221057d,
0x94d59893a43acff7, 0x64f0c51ccdc02281, 0x3d33bcc4ff6189db, 0xe005cb184ce66af1,
0xff5ccd1d1db99bea, 0xb0b854a7fe42980f, 0x7bd46a6a718d4b9f, 0xd10fa8cc22a5fd8c,
0xd31484952be4bd31, 0xc7fa975fcb243847, 0x4886ed1e5846c407, 0x28cddb791eb70b04,
0xc2b00be2f573417f, 0x5c9590452180f877, 0x7a6bddfff370eb00, 0xce509e38d6d9d6a4,
0xebeb0f00647fa702, 0x1dcc06cf76606f06, 0xe4d9f28ba286ff0a, 0xd85a305dc918c262,
0x475b1d8732225f54, 0x2d4fb51668ccb5fe, 0xa679b9d9d72bba20, 0x53841c0d912d43a5,
0x3b7eaa48bf12a4e8, 0x781e0e47f22f1ddf, 0xeff20ce60ab50973, 0x20d261d19dffb742,
0x16a12b03062a2e39, 0x1960eb2239650495, 0x251c16fed50eb8b8, 0x9ac0c330f826016e,
0xed152665953e7671, 0x02d63194a6369570, 0x5074f08394b1c987, 0x70ba598c90b25ce1,
0x794a15810b9742f6, 0x0d5925e9fcaf8c6c, 0x3067716cd868744e, 0x910ab077e8d7731b,
0x6a61bbdb5ac42f61, 0x93513efbf0851567, 0xf494724b9e83e9d5, 0xe887e1985c09648d,
0x34b1d3c675370cfd, 0xdc35e433bc0d255d, 0xd0aab84234131be0, 0x08042a50b48b7eaf,
0x9997c4ee44a3ab35, 0x829a7b49201799d0, 0x263b8307b7c54441, 0x752f95f4fd6a6ca6,
0x927217402c08c6e5, 0x2a8ab754a795d9ee, 0xa442f7552f72943d, 0x2c31334e19781208,
0x4fa98d7ceaee6291, 0x55c3862f665db309, 0xbd0610175d53b1f3, 0x46fe6cb840413f27,
0x3fe03792df0cfa59, 0xcfe700372eb85e8f, 0xa7be29e7adbce118, 0xe544ee5cde8431dd,
0x8a781b1b41f1873e, 0xa5c94c78a0d2f0e7, 0x39412e2877b60728, 0xa1265ef3afc9a62c,
0xbcc2770c6a2506c5, 0x3ab66dd5dce1ce12, 0xe65499d04a675b37, 0x7d8f523481bfd216,
0x0f6f64fcec15f389, 0x74efbe618b5b13c8, 0xacdc82b714273e1d, 0xdd40bfe003199d17,
0x37e99257e7e061f8, 0xfa52626904775aaa, 0x8bbbf63a463d56f9, 0xf0013f1543a26e64,
0xa8307e9f879ec898, 0xcc4c27a4150177cc, 0x1b432f2cca1d3348, 0xde1d1f8f9f6fa013,
0x606602a047a7ddd6, 0xd237ab64cc1cb2c7, 0x9b938e7225fcd1d3, 0xec4e03708e0ff476,
0xfeb2fbda3d03c12d, 0xae0bced2ee43889a, 0x22cb8923ebfb4f43, 0x69360d013cf7396d,
0x855e3602d2d4e022, 0x073805bad01f784c, 0x33e17a133852f546, 0xdf4874058ac7b638,
0xba92b29c678aa14a, 0x0ce89fc76cfaadcd, 0x5f9d4e0908339e34, 0xf1afe9291f5923b9,
0x6e3480f60f4a265f, 0xeebf3a2ab29b841c, 0xe21938a88f91b4ad, 0x57dfeff845c6d3c3,
0x2f006b0bf62caaf2, 0x62f479ef6f75ee78, 0x11a55ad41c8916a9, 0xf229d29084fed453,
0x42f1c27b16b000e6, 0x2b1f76749823c074, 0x4b76eca3c2745360, 0x8c98f463b91691bd,
0x14bcc93cf1ade66a, 0x8885213e6d458397, 0x8e177df0274d4711, 0xb49b73b5503f2951,
0x10168168c3f96b6b, 0x0e3d963b63cab0ae, 0x8dfc4b5655a1db14, 0xf789f1356e14de5c,
0x683e68af4e51dac1, 0xc9a84f9d8d4b0fd9, 0x3691e03f52a0f9d1, 0x5ed86e46e1878e80,
0x3c711a0e99d07150, 0x5a0865b20c4e9310, 0x56fbfc1fe4f0682e, 0xea8d5de3105edf9b,
0x71abfdb12379187a, 0x2eb99de1bee77b9c, 0x21ecc0ea33cf4523, 0x59a4d7521805c7a1,
0x3896f5eb56ae7c72, 0xaa638f3db18f75dc, 0x9f39358dabe9808e, 0xb7defa91c00b72ac,
0x6b5541fd62492d92, 0x6dc6dee8f92e4d5b, 0x353f57abc4beea7e, 0x735769d6da5690ce,
0x0a234aa642391484, 0xf6f9508028f80d9d, 0xb8e319a27ab3f215, 0x31ad9c1151341a4d,
0x773c22a57bef5805, 0x45c7561a07968633, 0xf913da9e249dbe36, 0xda652d9b78a64c68,
0x4c27a97f3bc334ef, 0x76621220e66b17f4, 0x967743899acd7d0b, 0xf3ee5bcae0ed6782,
0x409f753600c879fc, 0x06d09a39b5926db6, 0x6f83aeb0317ac588, 0x01e6ca4a86381f21,
0x66ff3462d19f3025, 0x72207c24ddfd3bfb, 0x4af6b6d3e2ece2eb, 0x9c994dbec7ea08de,
0x49ace597b09a8bc4, 0xb38c4766cf0797ba, 0x131b9373c57c2a75, 0xb1822cce61931e58,
0x9d7555b909ba1c0c, 0x127fafdd937d11d2, 0x29da3badc66d92e4, 0xa2c1d57154c2ecbc,
0x58c5134d82f6fe24, 0x1c3ae3515b62274f, 0xe907c82e01cb8126, 0xf8ed091913e37fcb,
0x3249d8f9c80046c9, 0x80cf9bede388fb63, 0x1881539a116cf19e, 0x5103f3f76bd52457,
0x15b7e6f5ae47f7a8, 0xdbd7c6ded47e9ccf, 0x44e55c410228bb1a, 0xb647d4255edb4e99,
0x5d11882bb8aafc30, 0xf5098bbb29d3212a, 0x8fb5ea14e90296b3, 0x677b942157dd025a,
0xfb58e7c0a390acb5, 0x89d3674c83bd4a01, 0x9e2da4df4bf3b93b, 0xfcc41e328cab4829,
0x03f38c96ba582c52, 0xcad1bdbd7fd85db2, 0xbbb442c16082ae83, 0xb95fe86ba5da9ab0,
0xb22e04673771a93f, 0x845358c9493152d8, 0xbe2a488697b4541e, 0x95a2dc2dd38e6966,
0xc02c11ac923c852b, 0x2388b1990df2a87b, 0x7c8008fa1b4f37be, 0x1f70d0c84d54e503,
0x5490adec7ece57d4, 0x002b3c27d9063a3a, 0x7eaea3848030a2bf, 0xc602326ded2003c0,
0x83a7287d69a94086, 0xc57a5fcb30f57a8a, 0xb56844e479ebe779, 0xa373b40f05dcbce9,
0xd71a786e88570ee2, 0x879cbacdbde8f6a0, 0x976ad1bcc164a32f, 0xab21e25e9666d78b,
0x901063aae5e5c33c, 0x9818b34448698d90, 0xe36487ae3e1e8abb, 0xafbdf931893bdcb4,
0x6345a0dc5fbbd519, 0x8628fe269b9465ca, 0x1e5d01603f9c51ec, 0x4de44006a15049b7,
0xbf6c70e5f776cbb1, 0x411218f2ef552bed, 0xcb0c0708705a36a3, 0xe74d14754f986044,
0xcd56d9430ea8280e, 0xc12591d7535f5065, 0xc83223f1720aef96, 0xc3a0396f7363a51f,
}

117
vendor/github.com/cxmcc/tiger/tiger.go generated vendored Normal file
View File

@ -0,0 +1,117 @@
// Package tiger implements the Tiger hash algorithm
// https://github.com/cxmcc/tiger
package tiger
import "hash"
// The size of a Tiger hash value in bytes
const Size = 24
// The blocksize of Tiger hash function in bytes
const BlockSize = 64
const (
chunk = 64
initA = 0x0123456789abcdef
initB = 0xfedcba9876543210
initC = 0xf096a5b4c3b2e187
)
type digest struct {
a uint64
b uint64
c uint64
x [chunk]byte
nx int
length uint64
ver int
}
func (d *digest) Reset() {
d.a = initA
d.b = initB
d.c = initC
d.nx = 0
d.length = 0
}
// New returns a new hash.Hash computing the Tiger hash value
func New() hash.Hash {
d := new(digest)
d.Reset()
d.ver = 1
return d
}
// New returns a new hash.Hash computing the Tiger2 hash value
func New2() hash.Hash {
d := new(digest)
d.Reset()
d.ver = 2
return d
}
func (d *digest) BlockSize() int {
return BlockSize
}
func (d *digest) Size() int {
return Size
}
func (d *digest) Write(p []byte) (length int, err error) {
length = len(p)
d.length += uint64(length)
if d.nx > 0 {
n := len(p)
if n > chunk-d.nx {
n = chunk - d.nx
}
copy(d.x[d.nx:d.nx+n], p[:n])
d.nx += n
if d.nx == chunk {
d.compress(d.x[:chunk])
d.nx = 0
}
p = p[n:]
}
for len(p) >= chunk {
d.compress(p[:chunk])
p = p[chunk:]
}
if len(p) > 0 {
d.nx = copy(d.x[:], p)
}
return
}
func (d digest) Sum(in []byte) []byte {
length := d.length
var tmp [64]byte
if d.ver == 1 {
tmp[0] = 0x01
} else {
tmp[0] = 0x80
}
size := length & 0x3f
if size < 56 {
d.Write(tmp[:56-size])
} else {
d.Write(tmp[:64+56-size])
}
length <<= 3
for i := uint(0); i < 8; i++ {
tmp[i] = byte(length >> (8 * i))
}
d.Write(tmp[:8])
for i := uint(0); i < 8; i++ {
tmp[i] = byte(d.a >> (8 * i))
tmp[i+8] = byte(d.b >> (8 * i))
tmp[i+16] = byte(d.c >> (8 * i))
}
return append(in, tmp[:24]...)
}

2
vendor/modules.txt vendored Normal file
View File

@ -0,0 +1,2 @@
# github.com/cxmcc/tiger v0.0.0-20170524142333-bde35e2713d7
github.com/cxmcc/tiger